Append CSV output coding issue - powershell

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

Related

Selection of only one user from list

I have this script that I need to use to retrieve the data of a particular user "ADTuser" from a list of servers the script works well, but the output file with my user add also other users' detail that is not needed for my final output how can I filter it to only the user that I need.
get-content C:\servers.txt | foreach-object {
$Comp = $_
if (test-connection -computername $Comp -count 1 -quiet) {
([ADSI]"WinNT://$comp").Children | ?{$_.SchemaClassName -eq 'user' } | %{
$groups = $_.Groups() | %{$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
$_ | Select #{n='Computername';e={$comp}},
#{n='UserName';e={$_.Name}},
#{n='Memberof';e={$groups -join ';'}},
#{n='status'; e={if($groups -like "*Administrators*"){$true} else{$false}}}
}
} Else {Write-Warning "Server '$Comp' is Unreachable hence Could not fetch data"}
} | Out-File -FilePath C:\users.txt
This should be an easier way of doing what you're looking for, Get-CimInstance and Get-CimAssociatedInstance have been around since PowerShell 3:
Get-Content C:\servers.txt | ForEach-Object {
$computer = $_
try {
$query = Get-CimInstance Win32_UserAccount -Filter "Name='ADTuser'" -ComputerName $_ -ErrorAction Stop
foreach($object in $query) {
$membership = Get-CimAssociatedInstance -InputObject $object -ResultClassName Win32_Group -ComputerName $_
[pscustomobject]#{
Computername = $_
UserName = $object.Name
Memberof = $membership.Name -join ';'
Status = $membership.Name -contains 'Administrators'
}
}
}
catch {
Write-Warning "Server '$computer' is Unreachable hence Could not fetch data"
}
} | Export-Csv C:\users.csv -NoTypeInformation
If that doesn't work for you, your code would require a simple modification on your first filtering statement:
Where-Object { $_.SchemaClassName -eq 'user' -and $_.Name.Value -eq 'ADTuser' }
It's important to note that Test-Connection -ComputerName $_ -Count 1 -Quiet is not a relevant test for this script, this command is testing for ICMP response and adsi over WinNT requires RPC connectivity as well SMB.
Putting it all together with minor improvements the script would look like this:
Get-Content C:\servers.txt | ForEach-Object {
if (-not (Test-Connection -ComputerName $_ -Count 1 -Quiet)) {
Write-Warning "Server '$_' is Unreachable hence Could not fetch data"
return
}
$computer = $_
([adsi]"WinNT://$_").Children.ForEach{
if($_.SchemaClassName -ne 'user' -and $_.Name.Value -ne 'ADTuser') {
return
}
$groups = $_.Groups().ForEach([adsi]).Name
[pscustomobject]#{
Computername = $computer
UserName = $_.Name.Value
Memberof = $groups -join ';'
Status = $groups -contains 'Administrators'
}
}
} | Export-Csv C:\users.csv -NoTypeInformation

Modifying PowerShell to export Windows Update to CSV file for specific OU only not working

I need to get the list of the server last Windows Update patch from multiple different OU and then export it as in CSV file with the below column and its sample result I gather manually running Get-HotFix locally on each server:
ServerName, Last Time Update Installed, KB Number, KB Update Name, InstalledBy
PRODSQL01-VM, 31/12/2018 02:46:55, KB4462930, Cumulative Update, NT AUTHORITY\SYSTEM
PRODSQL02-VM, 18/12/2018 12:00:00 AM, KB4471324, Security Update, DOMAIN\SVC_SCCM
PRODDC01-VM, 16/1/2019 02:16:31, KB4343669, Cumulative Update, DOMAIN\SVC_SCCM
PRODDC02-VM, 13/1/2018 03:00:00 AM, KB4457146, Security Update, DOMAIN\Admin-Staff1
This is the modified script for multiple OU processing, but somehow the result is still a blank CSV file:
$CsvFile = 'C:\Result.csv'
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
$OUList | ForEach-Object {
$OU = $_
$Computers = Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
$_
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
ForEach ($computer in $Computers) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $computer)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
Write-Host ''
Write-Host "$($computer): last time updates were installed was $($keyValue)"
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
} | Export-Csv -Path $Csvfile -NoTypeInformation
As others have mentioned, you're doing write-host rather than adding anything to your CSV file.
Note that I haven't tested any of the below code - it's just a bit of rearranging.
$CsvFile = 'C:\Result.csv'
$Results = #() #object to hold the output
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
ForEach ($OU in $OUList) {
Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $_)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
# Add result to Results array. Not Result
$results += [pscustomobject]#{
Computer = $_
LastSuccessTime = $KeyValue
}
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
}
#export result object to CSV
$Results | Export-Csv -Path $Csvfile -NoTypeInformation

Check Multiple Computers if Path Exists and Export to CSV

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

Powershell Export-CSV

I'm just having problems trying to export the following to a CSV, I've tried putting the Export-CSV within the Foreach-Object loop to no avail.
I want to put the server name $server in the first column and the description $description in the second.
In an earlier version, I was able to make a text file using Out-File -append .\outa.txt -InputObject $_.Name,$description but the formatting did not work well.
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
$RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$description = $RegKey.GetValue("srvcomment")
} | Export-Csv .\out1.csv
A ForEach as oppose to ForEach-Object:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
foreach($s in $server){
# replaced $_ with $s
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$s.Name)
$RegKey= $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
$description = $RegKey.GetValue("srvcomment")
# casting as array is required to use +=
# disclaimer: the array is destroyed and rebuilt on each run.
[array]$myData += New-Object psobject -Property #{
Server = $s.Name
Description = $description
}
}
# If you want a CSV without the top info line, use the notypeinfo switch
# Select-Object gives you the column order you want.
$myData | Select-Object Server,Description | Export-Csv .\out1.csv -NoTypeInformation
Edit - comment answer
It is possible to do it without creating an array and from inside the loop, but an object is required by Export-Csv AFAIK.
ForEach-Object:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
$RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
New-object psobject -Property #{Server=$_.Name;Description=$RegKey.GetValue("srvcomment")} | Export-Csv .\out1.csv -NoTypeInformation
}
ForEach:
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
foreach($s in $server){
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$s.Name)
$RegKey= $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
New-object psobject -Property #{Server=$s.Name;Description=$RegKey.GetValue("srvcomment")} | Export-Csv .\out1.csv -NoTypeInformation
}
If you have problems with the values, brackets/subexpressions can help:
New-object psobject -Property #{Server=($_.Name);Description=($RegKey.GetValue("srvcomment"))} | Export-Csv .\out1.csv -NoTypeInformation
New-object psobject -Property #{Server=$($_.Name);Description=$($RegKey.GetValue("srvcomment"))} | Export-Csv .\out1.csv -NoTypeInformation
Your ForEach-Object only contains statements (variable assignments). It's not returning anything. You need to return something, otherwise you're passing an empty pipeline to Export-CSV.
I'm guessing that, instead of setting variables called $Reg, $RegKey, and $description (and then never using their values), what you actually want to do is create columns in the CSV called Reg, RegKey, and description. In that case, you want to yield a [PSCustomObject] (with those properties added to it) each time through the loop.
$server = Get-ADComputer -Filter {OperatingSystem -Like "*Server*" -and Name -Notlike "*DOM*"}
$server | ForEach-Object {
[PSCustomObject] #{
Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$_.Name)
RegKey = $Reg.OpenSubKey("SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters")
description = $RegKey.GetValue("srvcomment")
}
} | Export-Csv .\out1.csv

Table not showing output in new line

I'm sure there is a simple solution, but I'm stuck. The output in the members column is like this
{domain\Domain Admins, domain\joerod...
How can I show the
$member
value on each line?
Function Get-AdminGroups{
foreach($i in (Get-Content C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (-not (Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible"
"`r"
}
else {
(invoke-command {
$members = net localgroup administrators |
? {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$members
}
} -computer $i -HideComputerName |
Select * -ExcludeProperty RunspaceID )
}
}
}
Get-AdminGroups |ft
Iterate through $members and make an object for each one. This creates an empty array, loops through the computers in your text file, and in that loop it pulls a list of the local administrators, and for each one it creates a custom object just like you are doing, and it adds it to that array.
$Results = #()
foreach($i in (GC C:\Users\joerod\Desktop\remove_users.txt)){
#test if machine is on the network
if (!(Test-Connection -computername $i -count 1 -Quiet -ErrorAction SilentlyContinue)) {
Write-Warning "$i is Unavalible`r"
Continue
}
invoke-command {
$members = net localgroup administrators |?{$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4
ForEach($member in $members){
$Results += New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Users=$member
}
}
} -computer $i -HideComputerName # | Select * -ExcludeProperty RunspaceID
}
$Results | FT