Home > Back-end >  PowerShell: Create Hashtable with three columns
PowerShell: Create Hashtable with three columns

Time:10-23

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
  • Related