Check Multiple Computers if Path Exists and Export to CSV - powershell

I want to make a script that will check whether or not a directory exists on each computer in "computers.csv".
This is what I've come up with:
$results = #()
$computers = Get-Content "C:\Users\me\Desktop\Notes\Computers.csv"
foreach ($computer in $computers) {
$path = Test-Path "\\$computer\c$\Program Files\Folder\"
if ($path -eq $true)
$Output = "True"
else
$Output = "False"
}
$details = #{
Computer_Name = $computer
Output = $Output
}
$results += New-Object PSObject -Property $details
$results |
Select-Object -Property Computer_Name,Output |
Export-Csv c:\results.csv -NoTypeInformation
Script is failing and I'm not entirely sure why. I need the script to export to a CSV due to how many computers are being queried.

You've got several syntax errors. You're missing brackets with if and else, and your foreach closing bracket is in the wrong place. Try this:
$results = #()
$computers = Get-Content "C:\Users\me\Desktop\Notes\Computers.csv"
foreach ($computer in $computers) {
$path = Test-Path "\\$computer\c$\Program Files\Folder\"
If ($path -eq $true) {
$Output = "True"
}
Else {
$Output = "False"
}
$details = #{
Computer_Name = $computer
Output = $Output
}
$results += New-Object PSObject -Property $details
}
$results | select-object -property Computer_Name, Output | Export-csv c:\results.csv -NoTypeInformation
That said, this pattern is one that should be avoided:
$results = #()
foreach ($item in $set) {
$results += $item
}
$results
The problem is that $results += $item copies the entire array into a new array and then adds the new item. It's a huge overhead as the size of the array increases.
Try something like this instead:
Get-Content "C:\Users\me\Desktop\Notes\Computers.csv" | ForEach-Object {
[PSCustomObject]#{
Computer_Name = $_
Output = Test-Path "\\$_\c$\Program Files\Folder\"
}
} | Export-Csv -Path C:\results.csv -NoTypeInformation

Related

Check all lines in a huge CSV file in PowerShell

I want to work with a CSV file of more than 300,000 lines. I need to verify information line by line and then display it in a .txt file in the form of a table to see which file was missing for all servers. For example
Name,Server
File1,Server1
File2,Server1
File3,Server1
File1,Server2
File2,Server2
...
File345,Server76
File346,Server32
I want to display in table form this result which corresponds to the example above:
Name Server1 Server2 ... Server 32 ....Server 76
File1 X X
File2 X X
File3 X
...
File345 X
File346 X
To do this actually, I have a function that creates objects where the members are the Server Name (The number of members object can change) and I use stream reader to split data (I have more than 2 columns in my csv so 0 is for the Server name and 5 for the file name)
$stream = [System.IO.StreamReader]::new($File)
$stream.ReadLine() | Out-Null
while ((-not $stream.EndOfStream)) {
$line = $stream.ReadLine()
$strTempo = $null
$strTempo = $line -split ","
$index = $listOfFile.Name.IndexOf($strTempo[5])
if ($index -ne -1) {
$property = $strTempo[0].Replace("-", "_")
$listOfFile[$index].$property = "X"
}
else {
$obj = CreateEmptyObject ($listOfConfiguration)
$obj.Name = $strTempo[5]
$listOfFile.Add($obj) | Out-Null
}
}
When I export this I have a pretty good result. But the script take so much time (between 20min to 1hour)
I didn't know how optimize actually the script. I'm beginner to PowerShell.
Thanks for the futures tips
You might use HashSets for this:
$Servers = [System.Collections.Generic.HashSet[String]]::New()
$Files = #{}
Import-Csv -Path $Path |ForEach-Object {
$Null = $Servers.Add($_.Server)
if ($Files.Contains($_.Name)) { $Null = $Files[$_.Name].Add($_.Server) }
else { $Files[$_.Name] = [System.Collections.Generic.HashSet[String]]$_.Server }
}
$Table = foreach($Name in $Files.get_Keys()) {
$Properties = [Ordered]#{ Name = $Name }
ForEach ($Server in $Servers) {
$Properties[$Server] = if ($Files[$Name].Contains($Server)) { 'X' }
}
[PSCustomObject]$Properties
}
$Table |Format-Table -Property #{ expression='*' }
Note that in contrast to PowerShell's usual behavior, the .Net HashSet class is case-sensitive by default. To create an case-insensitive HashSet use the following constructor:
[System.Collections.Generic.HashSet[String]]::New([StringComparer]::OrdinalIgnoreCase)
See if this works faster. Change filename as required
$Path = "C:\temp\test1.txt"
$table = Import-Csv -Path $Path
$columnNames = $table | Select-Object -Property Server -Unique| foreach{$_.Server} | Sort-Object
Write-Host "names = " $columnNames
$groups = $table | Group-Object {$_.Name}
$outputTable = [System.Collections.ArrayList]#()
foreach($group in $groups)
{
Write-Host "Group = " $group.Name
$newRow = New-Object -TypeName psobject
$newRow | Add-Member -NotePropertyName Name -NotePropertyValue $group.Name
$servers = $group.Group | Select-Object -Property Server | foreach{$_.Server}
Write-Host "servers = " $servers
foreach($item in $columnNames)
{
if($servers.Contains($item))
{
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue 'X'
}
else
{
#if you comment out next line code doesn't work
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue ''
}
}
$outputTable.Add($newRow) | Out-Null
}
$outputTable | Format-Table

Powershell Script assistance required

I'm piping a file's contents to Select-Object and creating two properties for each name, ComputerName and FileExists, where the latter value is the result of Test-Path
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | `
Select-Object #{Name='ComputerName';Expression={$_}},#{Name='FolderExist';Expression={ Test-Path "\\$_\c$\Data\Repository"}},
#{Name='Size';Expression={$_.Sum}}
I want to return the size of this folder if it exists on each server. How would you do it ?
Tried adding
#{Name='Size';Expression={$_.Sum}}
to my select-object but that does not return any value
I think this is what you are looking for
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | Select-Object #{Name='ComputerName';Expression={$_}},#{Name='FolderExist';Expression={ Test-Path "\\$_\c$\Data\Repository"}}, #{Name='Size';Expression={ "{0:n2}" -f ((gci -path "\\$_\c$\Data\Repository" -recurse | measure-object -property length -sum).sum /1mb) + " mb" }}
Since at least one calculated property depends on the other, you would have to do at least 2 Select-Object statements, but I would probably use ForEach-Object and New-Object instead:
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | ForEach-Object {
New-Object psobject -Property #{
'ComputerName' = $_
'FileExists' = ($FileExists = Test-Path ($FilePath = "\\$_\c$\Data\repository"))
'Size' = if($FileExists){ (Get-Item $FilePath).Length } else { $null }
}
}
(Get-Item $FilePath).Length won't get you far if C:\Data\repository is a directory, but you can substitute your own statement inside the if block
For the sake of my future coworkers I would probably avoid doing a one-liner like your example, and write multiple statements like so:
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | ForEach-Object {
$ComputerName = $_
$FilePath = "\\$ComputerName\c$\Data\repository"
$FileExists = Test-Path $FilePath
if($FileExists){
$Size = Get-Item $FilePath | Select-Object -ExpandProperty Length
} else {
$Size = $null
}
New-Object psobject -Property #{
'ComputerName' = $ComputerName
'FileExists' = $FileExists
'Size' = $Size
}
}

ForEach-Object piped to CSV is only showing last element

I'm trying to export to csv the scheduled tasks for multiple remote machines. I'm using a modified version of this script. I'm trying to export one csv per machine that lists all of the scheduled tasks. Currently my code just exports the last task for each machine.
foreach ($computerName in $computerNames) {
$Schedule.connect($computerName)
$AllFolders = Get-AllTaskSubFolders
foreach ($Folder in $AllFolders) {
if ($Tasks = $Folder.GetTasks(0)) {
$TASKS | % {[array]$results += $_}
$Tasks | Foreach-Object {
New-Object -TypeName PSCustomObject -Property #{
'Name' = $_.name
'Path' = $_.path
'Server' = $computername
} | Export-Csv $("C:\Users\MyName\Desktop\" + $computername + ".csv")
}
}
}
}
I've tried putting the Export-Csv at the end of each of the curly braces and none output what I want. Any ideas what I'm doing wrong?
EDIT
I'm not sure this fully addresses the issue, as some parts of the code seem weired to me such as $tasks = $folder.GetTasks. However OP title clearly says that the CSV is showing the last element meaning to me that his code mostly works except for the Export-CSV part.
Export-Csv expects an array as input, so the code below uses the elements in $task to generate the array of objects. Select-Object is there to decide in which order the properties are stored in the CSV.
Try this:
foreach ($computerName in $computerNames) {
$Schedule.connect($computerName)
$AllFolders = Get-AllTaskSubFolders
$result = #()
foreach ($Folder in $AllFolders) {
if ($Tasks = $Folder.GetTasks(0)) {
$TASKS | % {[array]$results += $_}
$Tasks | Foreach-Object {
$result += New-Object -TypeName PSCustomObject -Property #{
Name = $_.name;
Path = $_.path;
Server = $computername
}
}
}
}
$result | select Name, Path, Server | Export-Csv $($exportPath + $computername + ".csv")
}
This new version saves results on a per computer basis.
You are not changing either $exportPath or $computername in your loop, so each time through your outer loop, the csv file is being overwritten. Also, this
if ($Tasks = $Folder.GetTasks(0)) {
may be wrong. If you're trying to determine if $Tasks is equal to $Folder.GetTasks(0), you'd need this:
if ($Tasks -eq $Folder.GetTasks(0)) {

Powershell - an empty pipe element is not allowed

I keep receiving the following error message - "An empty pipe element is not allowed" whenever I try to pipe out my results to a csv file. Any idea why this might be happening?
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
foreach($computer in $computers) {
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob}
foreach($app in $lobApps){
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
$computerHostname = $computer.hostname
$results = new-object psobject -property #{Computer=$computer.hostname;App=$app.appname;Installed=$installed} | select Computer,App,Installed
$results
}
} | Export-csv "results.csv" -NoTypeInformation
I've tried doing this:
$results | Export-csv "results.csv" -NoTypeInformation
within the foreach loop but it only returns the last record.
A foreach loop doesn't ouput to the pipeline. You can make it do that by making the loop a sub-expression:
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
$(foreach($computer in $computers) {
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob}
foreach($app in $lobApps){
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
$computerHostname = $computer.hostname
$results = new-object psobject -property #{Computer=$computer.hostname;App=$app.appname;Installed=$installed} | select Computer,App,Installed
$results
}
}) | Export-csv "results.csv" -NoTypeInformation
I believe the problem you are having is around the use of foreach and the the pipeline, you are processing your items in the foreach statement but still expecting them to be on the pipeline.
This is a common error and is explained in more detail in the article Essential PowerShell: Understanding foreach and Essential PowerShell: Understanding foreach (Addendum).
You should be able to achieve what you want like this:
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
$computers | foreach {
$computer = $_
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob}
$lobApps | foreach {
$app = $_
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
$computerHostname = $computer.hostname
new-object psobject -property #{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
}
} | Export-csv "results.csv" -NoTypeInformation
Some of flow-control expressions do not stream their output.
But they can be assigned to variables, but no output streaming.
You can put your expression output stream in a Subexpression $() and then pipe with another command.
You can't pipe from ANY powershell statements. I'm not sure why.
if .. else
Switch
Do .. while
ForEach
For
While
EDIT: Another way. You can stream from a call operator and a scriptblock (or a function). Put the foreach or any other statement inside it. You won't have to wait until it finishes.
& { foreach ($i in 1..3) { $i } } | measure
Count : 3
Average :
Sum :
Maximum :
Minimum :
StandardDeviation :
Property :

Append CSV output coding issue

Hi i created the below script to audit local admin groups on remote hosts. It works fine, but since it only outputs the data after it has went through all the hosts, i am afraid the array will run out of buffer space before it has a chance to export to csv, so i have been trying to have it create and append the output from each host as it goes through the list except i cannot get the headers to display on the first line and append additonal lines below it. Below is the output i get when i try to append. The italicized words should be the headers and the other info should be listed in the next row. what am i doing wrong?
#{Server=pc1; Members=Administrator;DistinguishedName=DC=Domain,DC=com
This is how it should look. It looks this way if i dont append and i let it create the csv after it has finished going through the list of hosts
Server Members DistinguishedName
host1 Administrator;Admin2 DC=DOMAIN,DC=COM
$servers= get-content "C:\Scripts\AD Audits\Local Admin\workstations.txt"
$output = "c:\temp\local admin audit $CurrentDate.csv"
$results = #()
$disconnected = "Did not respond"
foreach($server in $servers)
{
$connected = Test-Connection $server -count 1 -quiet
if ($connected) {
"$server responded" | Out-File -append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"}
else {
"$server did not respond" | Out-File -append "c:\temp\LocalAdmin badhosts $CurrentDate.txt"}
$group =[ADSI]"WinNT://$server/Administrators"
$members = $group.Members() | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
$results += New-Object PsObject -Property #{
DistinguishedName = (get-adcomputer ($server) -properties * | select distinguishedname).distinguishedname
Server = $server
Members = $members -join ";"
}
$results | Export-Csv $Output -NoTypeInformation
}`
if($connected -eq $True) {
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}} else {write-host ""}
My suggestion is to use the pipeline rather than a foreach statement, so each object is written to the file as soon as it's processed.
$servers | ForEach-Object{
$connected = Test-Connection $_ -Count 1 -Quiet -ErrorAction SilentlyContinue
$state = if($connected) {"$_ responded"} else {"$_ did not respond"}
$state | Out-File -Append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"
$group =[ADSI]"WinNT://$_/Administrators,group"
$members = $group.Members() | ForEach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
if($connected)
{
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}
}
} | Export-Csv $Output -NoTypeInformation