Ultimately, I'm trying to create a script that will get all Windows Services Running as a domain Service Account from a list of remote machines and output a csv file with three columns: the Service Account Name, the Windows Service, and the Hostname. I cannot figure out how to create the hashtable with two arrays. I've had some success with just one key and one array using = but even that has some issues and I'm reading this is inefficient.
This is modified code that gets all Win Services running as System on my local system:
$server = $env:COMPUTERNAME
$tgtAcct = 'SYSTEM'
$reportCsv = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath ("report.$(Get-Date -Format `"yyyMMdd_hhmmss`").csv")
$GetServiceAccounts = {
[CmdletBinding()]
param(
$hostname
)
$serviceList = @( Get-WmiObject -Class Win32_Service -ComputerName $hostname -Property Name, StartName, SystemName -ErrorAction Stop )
$serviceList
}
Function Process-CompletedJobs(){
$jobs = Get-Job -State Completed
ForEach ($job in $jobs) {
$data = Receive-Job $job
Remove-Job $job
If ($data.GetType() -eq [System.Object[]]) {
$serviceList = $data | Where-Object { $_.StartName -ne $null -and $_.StartName.ToUpper().Contains($tgtAcct) }
ForEach ($service in $serviceList) {
$account = $service.StartName
$winService = $service.Name
$occurance = $service.SystemName
}
}
}
}
Start-Job -ScriptBlock $GetServiceAccounts -Name "read_$($server)" -ArgumentList $server | Wait-Job > $null
Process-CompletedJobs
Here is what I've tried that isn't working:
$server = $env:COMPUTERNAME
$tgtAcct = 'SYSTEM'
$serviceAccounts = @{}
$accountTable = @()
$winSvcTable = @()
$occurTable = @()
$reportCsv = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath ("report.$(Get-Date -Format `"yyyMMdd_hhmmss`").csv")
$GetServiceAccounts = {
[CmdletBinding()]
param(
$hostname
)
$serviceList = @( Get-WmiObject -Class Win32_Service -ComputerName $hostname -Property Name, StartName, SystemName -ErrorAction Stop )
$serviceList
}
Function Process-CompletedJobs(){
$jobs = Get-Job -State Completed
ForEach ($job in $jobs) {
$data = Receive-Job $job
Remove-Job $job
If ($data.GetType() -eq [System.Object[]]) {
$serviceList = $data | Where-Object { $_.StartName -ne $null -and $_.StartName.ToUpper().Contains($tgtAcct) }
ForEach ($service in $serviceList) {
$account = $service.StartName
$winService = $service.Name
$occurance = $service.SystemName
$script:serviceAccounts.Item($account) = $winService
$script:serviceAccounts.Item($account) = $occurance
}
}
}
}
Start-Job -ScriptBlock $GetServiceAccounts -Name "read_$($server)" -ArgumentList $server | Wait-Job > $null
Process-CompletedJobs
ForEach ($serviceAccount in $serviceAccounts.Keys) {
ForEach ($occurance in $serviceAccounts.Item($serviceAccount)) {
ForEach ($winService in $serviceAccounts.Item($serviceAccount)) {
$row = New-Object PSObject
Add-Member -InputObject $row -MemberType NoteProperty -Name "Account" -Value $serviceAccount
Add-Member -InputObject $row -MemberType NoteProperty -Name "Service" -Value $winService
Add-Member -InputObject $row -MemberType NoteProperty -Name "Hostname" -Value $occurance
$accountTable = $row
}
}
}
$accountTable | Export-Csv $reportCsv
I'm trying to modify code written by Andrea Fortuna that almost does what I want but want to split the second column into two. Again, I'm also looking for how to do this without adding to each array using = if possible. https://www.andreafortuna.org/2020/03/25/windows-service-accounts-enumeration-using-powershell/
CodePudding user response:
If your goal is to export to CSV, then a single top-level hashtable is not the data structure you want.
Export-Csv
will expect a collection of individual objects, so that's what you'll want to create:
Function Process-CompletedJobs(){
$jobs = Get-Job -State Completed
ForEach ($job in $jobs) {
$data = Receive-Job $job
Remove-Job $job
If ($data.GetType() -eq [System.Object[]]) {
$serviceList = $data | Where-Object { $_.StartName -ne $null -and $_.StartName.ToUpper().Contains($tgtAcct) }
ForEach ($service in $serviceList) {
# don't assign this new object to anything - let it "bubble up" as output instead
[pscustomobject]@{
Account = $service.StartName
Service = $service.Name
Occurrence = $service.SystemName
}
}
}
}
}
Now you can do:
Start-Job -ScriptBlock $GetServiceAccounts -Name "read_$($server)" -ArgumentList $server | Wait-Job > $null
Process-CompletedJobs |Export-Csv ...
CodePudding user response:
What about this?
$server = $env:COMPUTERNAME
$tgtAcct = 'SYSTEM'
$reportCsv = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath ("report.$(Get-Date -Format `"yyyMMdd_hhmmss`").csv")
$GetServiceAccounts = {
[CmdletBinding()]
param(
$hostname
)
$serviceList = @( Get-WmiObject -Class Win32_Service -ComputerName $hostname -Property Name, StartName, SystemName -ErrorAction Stop )
$serviceList
}
Function Process-CompletedJobs(){
$jobs = Get-Job -State Completed
$hashtable = @{}
ForEach ($job in $jobs) {
$data = Receive-Job $job
Remove-Job $job
If ($data.GetType() -eq [System.Object[]]) {
$serviceList = $data | Where-Object { $_.StartName -ne $null -and $_.StartName.ToUpper().Contains($tgtAcct) }
ForEach ($service in $serviceList) {
$account = $service.StartName
$winService = $service.Name
$occurance = $service.SystemName
$hashtable[$account] = @{winService = $winService; occurance = $occurance}
}
}
}
return $hashtable
}
Start-Job -ScriptBlock $GetServiceAccounts -Name "read_$($server)" -ArgumentList $server | Wait-Job > $null
$myHashTable = Process-CompletedJobs
CodePudding user response:
Make it simple it will work
this is the complete script and I have tried and valid for many servers you can change the variable $hostnames = $env:COMPUTERNAME,host2,host3,..
as you need
and I added some parameters to get a grid view of result to test and add force and notypeinfo. in export-csv
here is the code I hope you mark it answer if itis help
$hostnames = $env:COMPUTERNAME
$tgtAcct = 'SYSTEM'
$reportCsv = Join-Path -Path ([Environment]::GetFolderPath("Desktop")) -ChildPath ("report.$(Get-Date -Format `"yyyMMdd_hhmmss`").csv")
$TableName = "System Accounts"
#Create a table
$Table = new-object System.Data.DataTable "$TableName"
#Create a coloumn and you can increase it as many as you need
$col1 = New-Object System.Data.DataColumn "Service Account Name",([string])
$col2 = New-Object System.Data.DataColumn "Windows Service",([string])
$col3 = New-Object System.Data.DataColumn "Hostname",([string])
# Add the Columns
$Table.columns.add($col1)
$Table.columns.add($col2)
$Table.columns.add($col3)
foreach($hostname in $hostnames){
$serviceList = Get-WmiObject -Class Win32_Service -ComputerName $hostname -Property Name, StartName, SystemName -ErrorAction Stop | Where-Object { $_.StartName -ne $null -and $_.StartName.ToUpper().Contains($tgtAcct) }
foreach ($service in $serviceList){
$Row = $Table.NewRow()
$Row."Hostname" = $hostname
$Row."Service Account Name" = $service.StartName
$servicename = $service.Name.ToString()
$Row."Windows Service" = $servicename
$Table.Rows.Add($Row)
}}
$Table | Out-GridView | Export-Csv $reportCsv -Force -NoTypeInformation