Adding each item from for loop to csv file - powershell

I am trying to use power shell to determine whether a server has a particular patch installed based on the KB and if not append the name to a csv. my input file has system names so I want to export that system name if it does not find the patch installed.
here is what i have so far. The export to csv part does not seem to work.
forEach-Object{
try{
$status = wmic /node:#sys.csv qfe list full /format:table | findstr /i $kb_number
if(!$status){
$output_file = New-Item C:\temp\$kb_number.csv -ItemType File
export-csv $output_file -append -Force
}
else{
write-output $status
}
}
catch{
$error_message = $_.Exception.Message
#write-output "the error message is" $error_message
write-output "Could not find any system with this patch installed."
}
}

Why your code might be failing
We don't see where you're setting the values of #sys.csv or $kb_number in the code you shared. Either of those could be throwing you off.
But the real issue is Export-Csv. For one, you're making a new CSV with every iteration of the loop. And for two, you have to pass in some item for the cmdlet to export as a CSV. Right now, you're only providing these values.
$output_file = New-Item C:\temp\$kb_number.csv -ItemType File
Export-csv -Path $output_file -append -Force
Export-Csv requires an input object. You're not giving it one now.
What do you want to export? If you just want a list of computers without a patch, do this instead.
if(-not(Test-path C:\temp\$kb_number.csv)){
#if file doesn't exist, make it
$output_file = New-Item C:\temp\$kb_number.txt -ItemType File
}
#adds computer name if it doesn't have the patch
Add-Content -Path $output_file -Value $computer
General Suggestions
Instead of using ForEach-Object, you might find it's easier to debug if you use a ForEach loop like this.
$computers = Get-Content C:\pathTo\Your\ComputerList.txt
ForEach($computer in $computers){
}
One additional source of trouble is that your code is using older dos commands in WMIC and then tries to use PowerShell to store the records. You don't need to do this and can make it easier on yourself if you swap out the calls to wmic for Get-WmiObject or Get-CimInstance, the PowerShell native versions of the commands.
To do that, change this line:
wmic /node:#sys.csv qfe list full /format:table | findstr /i $kb_number
translates into
$kb_number = "KB4576484"
Get-CimInstance Win32_QuickFixEngineering -Filter "HotfixID = '$kb_number'" -ComputerName $computer
Source Description HotFixID InstalledBy InstalledOn
------ ----------- -------- ----------- -----------
Update KB4576484 NT AUTHORITY\SYSTEM 9/14/2020 12:00:00 AM
You can store the output of that in a variable and then call Export-Csv on it and that should work.
When in doubt, remove the filter part and just get it working to export all patches to a csv. Then add complexity by adding back the filtering statements.

Related

Export-CSV is not populating separate CSV files based on source

Good morning,
Hopefully this will be a quick and easy one to answer.
I am trying to run a PS script and have it export to csv based on a list of IP addresses from a text file. At the moment, it will run but only produce one csv.
Code Revision 1
$computers = get-content "pathway.txt"
$source = "\\$computer\c$"
foreach ($computer in $computers) {
Get-ChildItem -Path "\\$Source\c$" -Recurse -Force -ErrorAction SilentlyContinue |
Select-Object Name,Extension,FullName,CreationTime,LastAccessTime,LastWriteTime,Length |
Export-CSV -Path "C:\path\$computer.csv" -NoTypeInformation
}
Edit
The script is now creating the individual server files as needed and I did change the source .txt file to list the servers by HostName rather than IP. The issue now is that no data is populating in the .csv files. It will create them but nothing populates. I have tried different source file paths to see if maybe its due to folder permissions or just empty but nothing seems to populate in the files.
The $computer file lists a number of server IP addresses so the script should run against each IP and then write out to a separate csv file with the results, naming the csv file the individual IP address accordingly.
Does anyone see any errors in the script that I provided, that would prevent it from writing out to a separate csv with each run? I feel like it has something to do with the foreach loop but I cannot seem to isolate where I am going wrong.
Also, I cannot use any third-party software as this is a closed network with very strict FW rules so I am left with powershell (which is okay). And yes this will be a very long run for each of the servers but I am okay with that.
Edit
I did forget to mention that when I run the script, I get an error indicating that the export-csv path is too long which doesn't make any sense unless it is trying to write all of the IP addresses to a single name.
"Export-CSV : The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
At line:14 char:1
TIA
Running the script against C: Drive of each computer is strongly not advisable that too with Recurse option. But for your understanding, this is how you should pass the values to the variables. I haven't tested this code.
$computer = get-content "pathway.txt"
foreach ($Source in $computer) {
Get-ChildItem -Path "\\$Source\c$" -Recurse -Force -ErrorAction SilentlyContinue |
Select-Object Name,Extension,FullName,CreationTime,LastAccessTime,LastWriteTime,Length | Export-Csv -Path "C:\Path\$source.csv" -NoTypeInformation
}
$computer will hold the whole content and foreach will loop the content and $source will get one IP at a time. I also suggest instead of IP's you can have hostname so that your output file have servername.csv for each server.
In hopes that this helps someone else. I have finally got the script to run and create the individual .csv files for each server hostname.
$servers = Get-Content "path"
Foreach ($server in $servers)
{
Get-ChildItem -Path "\\$server\c$" -Recurse -Force -ErrorAction SilentlyContinue |
Select-Object Name,Extension,FullName,CreationTime,LastAccessTime,LastWriteTime,Length |
Export-CSV -Path "path\$server.csv" -NoTypeInformation
}

System.IO.FileSystemWatcher and mapped network drives

I use following PowerShell script to monitor a mapped drive for newly created files and write these changes to a csv file.
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = 'Z:\'
$action =
{
$path = $event.SourceEventArgs.FullPath
$changetype = $event.SourceEventArgs.ChangeType
Write-output "$path was $changetype at $(get-date)" >> C:\temp\HostFileList.csv
}
Register-ObjectEvent $watcher 'Created' -Action $action
Get-EventSubscriber
while($true){ sleep -seconds 1 }
The computer I'm running this script on is heavily used by multiple users. Each user will log onto the computer with their own profile, click a desktop batch file to map a network drive folder and save their work there. My script is set up in Local Group Policy to be ran at start up. From what I can tell the script doesn't have any issues running in the background and listening to the mapped drive folder. My problem is that the script refuses to write an output file to the designated location. If I manually run the script, it creates the output file just fine. Also, when I manually run the script with the local GP startup script enabled, I get two entries in my file, which makes me believe the script is running fine from the local GP. Could anyone advise why my script creates an output file when I run it manually, but not when it is ran at startup? Any feedback is much appreciated.
As for this...
Write-output
Write-Output (Microsoft.PowerShell.Utility)
Sends the specified objects to the next command in the pipeline. If
the command is the last command in the pipeline, the objects are
displayed in the console.
Use the file cmdlets
Export-Csv
Out-File
... and the -append parameter. So, collect your properties and pipe to one of them.
| Export-Csv -Path 'C:\temp\HostFileList.csv' -Append -NoTypeInformation
Export-Csv (Microsoft.PowerShell.Utility)
Converts objects into a series of comma-separated value (CSV) strings
and saves the strings to a file.
$AppService = (Get-Service -DisplayName *Application* | Select-Object -Property DisplayName, Status)
$AppService | Export-Csv -Path .\Services.Csv -NoTypeInformation
Get-Content -Path .\Services.Csv
$WinService = (Get-Service -DisplayName *Windows* | Select-Object -Property DisplayName, Status)
$WinService | Export-Csv -Path ./Services.csv -NoTypeInformation -Append
Get-Content -Path .\Services.Csv
"DisplayName","Status"
"Application Layer Gateway Service","Stopped"
"Application Identity","Running"
"Windows Audio Endpoint Builder","Running"
"Windows Audio","Running"
"Windows Event Log","Running"
Out-File (Microsoft.PowerShell.Utility) - PowerShell
Sends output to a file.
-Append Adds the output to the end of an existing file.

Ouput Server Name Along With InstallState of Service

I'm pretty new to Powershell. I have the following simple little script:
$serversall = (Get-Content ".\Servers.txt")
foreach($vm in $serversall){
Get-WindowsFeature Web-Server -ComputerName $vm |
Select-Object InstallState |
Export-Csv -path E:\Scripts\Output\IISWebServersStatus.csv -Append
}
This just gives me one column to let me know if the status is "Installed" or "Available". I want another column that would also show the server name next to the status of the service. Any idea how I would accomplish that?
I would recommend the following approach:
$serversall = (Get-Content ".\Servers.txt")
$output = #()
foreach($vm in $serversall) {
$installed = (Get-WindowsFeature Web-Server -ComputerName $vm).InstallState
$output += New-Object PSCustomObject -Property #{'ComputerName'=$vm; 'Status'=$installed }
}
$output | Export-Csv -path E:\Scripts\Output\IISWebServersStatus.csv -Append
Create an array, $output that you can use to store all the information. On each loop iteration, build an object that holds the the server name and the install state and append it to the output array.
After the loop is done, write the output array to the csv file.
Doing one file write at the end will save on i/o operations, so could save time. In this example, Get-WindowsFeature is a relatively slow operation so it probably makes little difference, but good to understand the theory nonetheless.

Create PS script to find files

I want to start by saying coding is a bit outside of my skill set but because a certain problem keeps appearing at work, I'm trying to automate a solution.
I use the below script to read an input file for a list of name, search the C:\ for those files, then write the path to an output file if any are found.
foreach($line in Get-Content C:\temp\InPutfile.txt) {
if($line -match $regex){
gci -Path "C:\" -recurse -Filter $line -ErrorAction SilentlyContinue |
Out-File -Append c:\temp\ResultsFindFile.txt
}
}
I would like to make two modifications to this. First, to search all drives connected to the computer not just C:\. Next, be able to delete any found files. I'm using the Remove-Item -confirm command but so far can't make it delete the file it just found.

Process list and write output to files named after list members

I'm trying to run a command which accepts a list of names. It should go through each member of the list and then output it to a text file in a specific location, and the name of that text file will be the member name from the list. Then the script continues to the next member run the command on it, and then write the output to a text file whose name will be the 2nd member in the list.
I'm sure a loop is involved, and perhaps a temporary variable which I have no idea how to declare :(
Invoke-Command -ComputerName (Get-Content "C:\ServerList.txt") -ScriptBlock {
Servermanagercmd.exe -query roles.xml
} -credential baloon\yellow | Out-File C:\OutputF.txt
Pull out the Get-Content of the serverlist file so the server name values are available down the pipeline:
Get-Content c:\serverlist.txt | Where {$_} |
Foreach { Invoke-Command -ComputerName $_ -Scriptblock {...} -Credential baloon\yellow |
Out-File "$_.txt" }
Note that the Where {$_} is to weed out any empty lines in the file.
Shall I assume this issue is that the data that is being output has no context since you wont know what system it is from?
$outputfile = C:\OutputF.txt
Set-Content -Path $outputfile -Value ""
ForEach($server in (Get-Content "C:\ServerList.txt")){
Add-Content -Path $outputfile -Value "Server: $server"
Invoke-Command -ComputerName $server -ScriptBlock {Servermanagercmd.exe -query roles.xml} -credential baloon\yellow |
Add-Content -Path $outputfile
}
Wipe the file to start new (remove this if you dont want to erase the file.). For each server in your list add the server name on its own line in the file and then run the Invoke-Command also sending its output to file.
This will might be inefficient depending on the number of server you are checking. At that point I would consider using jobs.
Update
After reading Keith Hills answer I realize that I misread your question. My logic would output all to one file with the server name separating the contents. You actually wanted separate files.