Powershell add null content and not null content to one CSV - powershell

I need to include 'Installed' and 'Not Installed' data in one CSV
I think I need to incorporate an -OR logical operator to include TRUE/FALSE
output in one CSV. Idk how to do that yet.
There's a folder with many *ServerData files that contain a list of KBs with
possible duplicate KBs.
There is a *ServerData file for each server, with possible duplicate files.
I want to test whether any of them contain KB2151757 and KB4556403.
Then output the results to a csv with a status of either Installed or Not
Installed.
Currently it only returns a list of computers with the KB installed.
If the $patch is not found, it currently returns nothing (null).
For each $computer searched, it needs to return the specified fields for the
[PSCustomObject]
I'm thinking that maybe I just need to take a function to find 'installed' and a function to find 'not installed' and add the results together to export. Idk how to do that. I feel like there must be an easier way.
Click to view a sample of the CSV
$computers = Get-Item -path F:\*ServerData | Select-Object -ExpandProperty basename
$patch = gc -path F:\*ServerData | Sort-Object -Unique | Select-String KB2151757, KB4556403 #'KB(\d+)'
$output = ForEach ($computer in $computers) {
ForEach ($kb in $patch) {
if ($null -eq $patch){
[PSCustomObject]#{
Status = 'Not Installed'
Server = $computer
KB = $kb
}
} else{
[PSCustomObject]#{
Status = 'Installed'
Server = $computer
KB = $kb
}
}
}
}
$output | Export-csv C:\KB-Report.txt -notypeinformation -delimiter ',' -encoding utf8

If you start by grouping the files by the associated computer name, then the procedure becomes straightforward (pseudocode):
for each Computer
for each ExpectedPatch
if ServerData for Computer contains ExpectedPatch
Output object with 'Installed' status for ExpectedPatch on Computer
else
Output object with 'NotInstalled' status for ExpectedPatch on Computer
So let's give that a try:
# Define the articles we're looking for
$ExpectedPatches = 'KB2151757', 'KB4556403'
# Enumerate and group data files by computer name, output as hashtable
# The resulting hashtable will have the computer name is Name and the associated files as its value
$ServerDataPerComputer = Get-Item -Path F:\*ServerData |Group BaseName -AsHashtable
foreach($Computer in $ServerDataPerComputer.GetEnumerator())
{
foreach($Patch in $ExpectedPatches)
{
# Pipe all the file references to Select-String, look for the KB ID, return after the first match if any
$Status = if($Computer.Value |Select-String "\b$Patch\b" |Select-Object -First 1){
'Installed'
}
else {
# Select-String didn't find the KB ID in any of the files
'NotInstalled'
}
[pscustomobject]#{
Status = $Status
Server = $Computer.Name
KB = $Patch
}
}
}

Related

Why Powershell outputting this table?

I'm a powershell noob. How come the following code is also outputing the table at the end after the "File to Delete" loop?
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
# use partial hashes for files larger than 100KB:
# see documentation at: https://powershell.one/tricks/filesystem/finding-duplicate-files#finding-duplicate-files-fast
$result = Find-PSOneDuplicateFileFast -Path '\\READYNAS\Pictures\2020\10' #-Debug -Verbose
$stopwatch.Stop()
# output duplicates
$allFilesToDelete = #(foreach($key in $result.Keys)
{
#filters out the LAST item in the array of duplicates, because a file name of xxxx (0) comes before one without the (0)
$filesToDelete = $result[$key][0..($result[$key].count - 2)]
#add each remaining duplicate file to table
foreach($file in $filesToDelete)
{
$file |
Add-Member -MemberType NoteProperty -Name Hash -Value $key -PassThru |
Select-Object Hash, Length, FullName
}
}
)
$allFilesToDelete | Format-Table -GroupBy Hash -Property FullName | Out-String | Write-Host
$allFilesToDelete | Sort-Object -Property FullName -OutVariable allFilesToDelete
$allFilesToDelete | Format-Table -Property FullName | Out-String | Write-Host
$confirmation = Read-Host "Are you Sure You Want To Delete $($allFilesToDelete.count) files? (y/n)"
if ($confirmation -eq 'y') {
$i = 0
foreach($fileToDelete in $allFilesToDelete)
{
$i++
Write-Host "$i File to Delete: $($fileToDelete.FullName)"
#Remove-Item $file.FullName -Force -Verbose 4>&1 | % { $x = $_; Write-Host "Deleted file ($i) $x" }
}
} else {
Write-Host "User chose NOT to delete files!"
}
$allFilesToDelete | Sort-Object -Property FullName -OutVariable allFilesToDelete produces output (the input objects in the requested sort order), and since you're not capturing or redirecting it, it prints to the host (display, terminal) by default.
It seems your intent is to sort the objects stored in $allFilesToDelete, which your command does, but it also produces output (the common -OutVariable parameter does not affect a cmdlet's output behavior, it simply also stores the output objects in the given variable); you could simply assign the output back to the original variable, which wouldn't produce any output:
$allFilesToDelete = $allFilesToDelete | Sort-Object -Property FullName
In cases where actively suppressing (discarding) output is needed, $null = ... is the simplest solution:
See this answer for details and alternatives.
Also see this blog post, which you found yourself.
Because the output resulted in implicitly Format-Table-formatted display representations (for custom objects that have no predefined formatting data), the subsequent Read-Host and Write-Host statements - surprisingly - printed first.
The reason is that this implicit use of Format-Table results in asynchronous behavior: output objects are collected for 300 msecs. in an effort to determine suitable column widths, and during that period output to other output streams may print.
The - suboptimal - workaround is to force pipeline output to print synchronously to the host (display), using Out-Host.
See this answer for details.

Unable to show export-csv in PoweSshell

I have been researching the web to see what am I missing and can't find out, I run the command it goes thru the list of computers but the export doc is always empty.
Here is the code
foreach ($computer in Get-Content "\\NETWORK PATH\user-computers.txt") {
Write-host $computer
$colDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $computer
$Report = #()
# Set our filename based on the execution time
$filenamestring = "$computer-$(get-date -UFormat "%y-%b-%a-%H%M").csv"
foreach ($objDrive in $colDrives) {
# For each mapped drive – build a hash containing information
$hash = #{
ComputerName = $computer
MappedLocation = $objDrive.ProviderName
DriveLetter = $objDrive.DeviceId
}
# Add the hash to a new object
$objDriveInfo = new-object PSObject -Property $hash
# Store our new object within the report array
$Report += $objDriveInfo
}}
# Export our report array to CSV and store as our dynamic file name
$Report | Export-Csv -LiteralPath "\\NETWORK PATH\Drive-Maps.csv" -NoTypeInformation
I want to know what each computer currently got mapped network drives, thanks for all your help and guidance.
I'm not sure why you're not getting output. I've rewritten your script for a few reasons I'd like to point out. First, your variable naming is not very clear. I'm guessing you come from a VBScripting background. Next, you're creating an array and then adding to it - this is simply not needed. You can capture the output of any loop/scriptblock/etc directly by assigning like tihs.
$Report = foreach($thing in $manythings){Do lots of stuff and everything in stdout will be captured}
If you write your script in a way that takes advantage of the pipeline, you can do even more. Next, creating the object with New-Object is slow compared to using the [PSCustomObject] type accelerator introduced in V3. Finally, it seems you create a custom csv for each computer but in the end you just export everything to one file. I'm going to assume you are wanting to collect all this info and put in one CSV.
My recommendation for you to help troubleshoot, run this against your machines and confirm the output on the screen. Whatever you see on the screen should be captured in the report variable. (Except write-host, it's special and just goes to the console)
$computerList = "\\NETWORK PATH\user-computers.txt"
$reportFile = "\\NETWORK PATH\Drive-Maps.csv"
Get-Content $computerList | ForEach-Object {
Write-host $_
$mappedDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $_
foreach ($drive in $mappedDrives)
{
# For each mapped drive – build a hash containing information
[PSCustomObject]#{
ComputerName = $_
MappedLocation = $drive.ProviderName
DriveLetter = $drive.DeviceId
}
}
} -OutVariable Report
Once you know you have all the correct info, run this to export it.
$Report | Export-Csv -LiteralPath $reportFile -NoTypeInformation

Powershell DNS forward lookup zone list then check

Ok so we are migrating to a new NS for all of our external domains. The current one has plenty of domains that are no longer valid but have not been removed. I am attempting to export a list of all forward lookup zones in DNS, ping them to see if they are alive and sort too two different files so i can recheck the bad names manually.
First need to export to a file so it can be moved to a different location for testing.
Export
dnscmd /enumzones /Forward | out-file C:\temp\zones.txt
Test
$names = Get-Content "C:\temp\zones.txt"
foreach ($name in $names){
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue){
Add-Content c:\temp\resolved.csv "$name,up"
}
else{
Add-Content C:\temp\unresolved.csv "$name,down"
}
}
Problem
The export command writes a value with extra information.
I.E.
Enumerated zone list:
Zone count = 673
Zone name Type Storage Properties
. Cache File
domain1.com.au Primary File
domain2.co.nz Primary File
domain3.com.au Primary File
No problem with just clearing out the top of the file etc, but how can I format the zone list output so the powershell can read it?
Answer
Answer for Server 2012 marked below by #sodawillow, I had to teak a little for 2008R2, but couldn't of done it with out his answer.
I also messed with it a little as i was having issues with none of the names resolving due to white space from export.
#store forward lookup zones names in an array
$zoneNames = Get-WmiObject -Namespace Root\MicrosoftDNS -Class "MicrosoftDNS_Zone" | ? { $_.ContainerName -Notlike '..RootHints' -And $_.ContainerName -NotLike '..Cache' -And !$_.Reverse } | Select Name
#declare results arrays and files paths
$resolvedZones = #()
$unresolvedZones = #()
$resolvedFile = "C:\Temp\resolved.csv"
$unresolvedFile = "C:\Temp\unresolved.csv"
#for each zone name
foreach ($zoneName in $zoneNames){
$string = $zoneName.Substring(0)
$string = $string.Trim()
#if it responds to ping
if (Test-Connection -ComputerName "$string" -Count 2 -ErrorAction SilentlyContinue) {
#build result object and add it to resolved zones array
$resolvedZones += [PSCustomObject]#{ ZoneName = $zoneName; Status = "UP" }
} else {
#build result object and add it to unresolved zones array
$unresolvedZones += [PSCustomObject]#{ ZoneName = $zoneName; Status = "DOWN" }
}
}
#export results arrays as CSV in results files (if not empty)
if($unresolvedZones.Length -gt 0) { $unresolvedZones | Export-Csv $unresolvedFile -NoTypeInformation }
if($resolvedZones.Length -gt 0) { $resolvedZones | Export-Csv $resolvedFile -NoTypeInformation }
Note : I have mixed forward and reverse in my answer, but it
demonstrates how you can retrieve and re-use DNS zone names.
There is an appropriate PowerShell cmdlet to list DNS zones, Get-DNSServerZone.
Here is an adapted script :
#store reverse lookup zones names in an array
$zoneNames = (Get-DnsServerZone | Where-Object { $_.IsReverseLookupZone -eq $true }).ZoneName
#declare results arrays and files paths
$resolvedZones = #()
$unresolvedZones = #()
$resolvedFile = "C:\Temp\resolved.csv"
$unresolvedFile = "C:\Temp\unresolved.csv"
#for each zone name
foreach ($zoneName in $zoneNames){
#if it responds to ping
if (Test-Connection -ComputerName $zoneName -Count 2 -ErrorAction SilentlyContinue) {
#build result object and add it to resolved zones array
$resolvedZones += [PSCustomObject]#{ ZoneName = $zoneName; Status = "UP" }
} else {
#build result object and add it to unresolved zones array
$unresolvedZones += [PSCustomObject]#{ ZoneName = $zoneName; Status = "DOWN" }
}
}
#export results arrays as CSV in results files (if not empty)
if($unresolvedZones.Length -gt 0) { $unresolvedZones | Export-Csv $unresolvedFile -NoTypeInformation }
if($resolvedZones.Length -gt 0) { $resolvedZones | Export-Csv $resolvedFile -NoTypeInformation }

Adding objects to array to create multiple columns

I'm trying to get the DisplayName of all Software listed in AddRemovePrograms for each computer, add it to an array under the name of the computer, then export. Here's what I have:
$Computers = gc "C:\Get Software.txt"
$CSV = "C:\Get Software.csv"
$Results = #()
If (Test-Path $CSV) {
Remove-Item $CSV
}
Foreach($Computer in $Computers){
#Get DisplayName of Software Installed on Asset
$Software = Get-WmiObject Win32Reg_AddRemovePrograms -ComputerName $Computer | Select-Object -ExpandProperty DisplayName
$counter = 0
While ($counter -lt $Software.count){
#Create a PSObject. Loops through all software and adds to $Results.
$Obj = New-Object PSOBJECT
Add-member –inputobject $Obj –membertype Noteproperty -Name $Computer -Value $Software[$counter]
$counter++
$Results+=$Obj
}
}
$Results | Export-Csv $CSV -NoTypeInformation
Unfortunately, the output only lists the first Computer in the CSV. I've tried stepping through this to understand it, I just don't understand why I can't add the $Obj variable to $Results with a different Name Property (In this case I'm looping and making a new name based on the computer name in my .txt file). It seems to only take the first input and won't put a new column header then spit out the software. Below is an example of what I'm getting and what I'd like to get instead.
OUTPUT
A01234
Program1
Program2
Program3
EXPECTED OUTPUT
A01234 B05678 C09123
Program1 Program97 Program30
Program2 Program98 Program31
Program3 Program99 Program32
Hopefully this makes some sense. Any assistance would be appreciated, I'm clearly doing something wrong with the objects and I'm not seeing it. Thanks!
This is do-able, it just isn't quite as simple as you would think. What I would suggest is setting things up as a hashtable where the keys are your computer names and your values are an array of software titles for each computer. Then you find out how many titles the computer with the most software has (that's how many rows you'll have, so it's how many times you'll have to loop), and create a loop to make that many objects. Each object will have no properties to start, and then we add a property for each computer in the hashtable, and the value for that property will be the Nth item listed in the hashtable for that computer. Here's the code, it might make this all make more sense:
$Computers = 'A01234','B05678','C09123'
$SftwrPerCmp = [ordered]#{}
ForEach($Computer in $Computers){
$SftwrPerCmp.add($Computer,#())
#Randomly generate 7-15 strings as 'Programs' for current computer
$Software = 1..$(get-random -Maximum 15 -Minimum 7)|%{(Get-Random -inputobject $("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -split ''|?{$_}) -count 6) -join ''}
ForEach($Title in $Software){
$SftwrPerCmp["$Computer"] += $Title
}
}
$MaxTitleCount = $SftwrPerCmp.Values|%{$_.count}|sort|select -last 1
$Results = #()
For($i=0;$i -lt $MaxTitleCount;$i++){
$Record = New-Object PSObject
$SftwrPerCmp.Keys | ForEach{Add-Member -InputObject $Record -NotePropertyName $_ -NotePropertyValue $SftwrPerCmp["$_"][$i]}
$Results += $Record
Clear-Variable Record
}
$Results|Format-Table -AutoSize
Now obviously you will not randomly generate software titles, you'll use your existing $Software = Get-WmiObject line that's in your existing code instead since that part was at least working for you. This should result in the listings that you were looking for. My code resulted in this:
A01234 B05678 C09123
------ ------ ------
CU7K5E 6GJWOB 97H1TY
7VCZ5T CIPWVK 760NKU
CHKPY0 J4B7D0 1QOSD3
2YEFR4 2VY6DM O68SKU
VI7ZQG WLJQN9 Q5VJAZ
ZQOKNV R9KZG1 H2XZK4
S8IZC4 GRSMPU BIZXKA
LAVNI0 TKBOUC K9DEFU
3U7KVO JZ3X4H
A6GVUK 18AC5H
NMI32Q H14GPJ
50KSZ6 XU0FWC
PAN5TC 9WXR5U
531M04

Issues with Powershell Import-CSV

Main Script
$Computers = Get-Content .\computers.txt
If ( test-path .\log.txt ) {
$Log_Successful = Import-CSV .\log.txt | Where-Object {$_.Result -eq "Succesful"}
} ELSE {
Add-Content "Computer Name,Is On,Attempts,Result,Time,Date"
}
$Log_Successful | format-table -autosize
Issues:
Log_Successful."Computer Name" works fine, but if i change 4 to read as the following
$Log_Successful = Import-CSV .\log.txt | Where-Object {$_.Result -eq "Failed"}
Log_Successful."Computer Name" no longer works... Any ideas why?
Dataset
Computer Name,Is On,Attempts,Result,Time,Date
52qkkgw-94210jv,False,1,Failed,9:48 AM,10/28/2012
HELLBOMBS-PC,False,1,Successful,9:48 AM,10/28/2012
52qkkgw-94210dv,False,1,Failed,9:48 AM,10/28/2012
In case of "Successful" a single object is returned. It contains the property "Computer Name". In case of "Failed" an array of two objects is returned. It (the array itself) does not contain the property "Computer Name". In PowerShell v3 in some cases it is possible to use notation $array.SomePropertyOfContainedObject but in PowerShell v2 it is an error always. That is what you probably see.
You should iterate through the array of result objects, e.g. foreach($log in $Log_Successful) {...} and access properties of the $log objects.
And the last tip. In order to ensure that the result of Import-Csv call is always an array (not null or a single object) use the #() operator.
The code after fixes would be:
$logs = #(Import-Csv ... | where ...)
# $logs is an array, e.g. you can use $logs.Count
# process logs
foreach($log in $logs) {
# use $log."Computer Name"
}
I'm not sure if this is the problem but you have a typo, in Where-Object you compare against "Succesful" and the value in the file is "Successful" (missing 's').
Anyway, what's not working?