【问题标题】:PowerShell: Create Hashtable with three columnsPowerShell:创建具有三列的 Hashtable
【发布时间】:2021-12-09 04:16:06
【问题描述】:

最终,我正在尝试创建一个脚本,该脚本将从远程计算机列表中获取作为域服务帐户运行的所有 Windows 服务,并输出一个包含三列的 csv 文件:服务帐户名称、Windows 服务和主机名。我无法弄清楚如何使用两个数组创建哈希表。我使用 += 仅使用一个键和一个数组就取得了一些成功,但即使这样也有一些问题,我正在阅读这是低效的。

这是使所有 Win 服务在我的本地系统上作为系统运行的修改代码:

$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

这是我尝试过但不起作用的方法:

$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

我正在尝试修改 Andrea Fortuna 编写的代码,该代码几乎可以满足我的要求,但想将第二列分成两列。同样,如果可能,我也在寻找如何在不使用 += 添加到每个数组的情况下执行此操作。 https://www.andreafortuna.org/2020/03/25/windows-service-accounts-enumeration-using-powershell/

【问题讨论】:

  • 我没有按照你期望的输出样子,是$serviceList | Select-Object StartName,SystemName,Name吗?另外,如果你关心效率,Start-Job绝对不是办法。
  • @SantiagoSquarzon :我要查找的输出是一个包含帐户名、服务名和主机名的表。此外,我确实关心效率,并且一直在寻求学习更好更有效的做事方式。为什么 Start-Job 效率低下,您会怎么做?

标签: arrays powershell hashtable psobject


【解决方案1】:

如果您的目标是导出为 CSV,那么单个顶级哈希表不是您想要的数据结构。

Export-Csv 将期望 集合 单个对象,所以这就是您想要创建的:

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
        }
      }
    }
  }
}

现在你可以这样做了:

Start-Job -ScriptBlock $GetServiceAccounts -Name "read_$($server)" -ArgumentList $server | Wait-Job > $null
Process-CompletedJobs |Export-Csv ...

【讨论】:

  • 我无法相信我做这个有多么困难。这是非常直接和容易的。谢谢!
  • @ToToRoYN 很高兴向您展示了一些光明,不客气!如果我的回答解决了您的问题,请考虑通过单击左侧的复选标记将其标记为“已接受”(如果它们更合适,则接受其他答案之一):)
【解决方案2】:

这个怎么样?

$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

【讨论】:

    【解决方案3】:

    让它变得简单,它会起作用,这是完整的脚本,我已经尝试过并且对许多服务器都有效,你可以根据需要更改变量 $hostnames = $env:COMPUTERNAME,host2,host3,.. 我添加了一些参数来获得结果的网格视图来测试并添加 force 和 notypeinfo。在导出-csv

    这是代码-如果有帮助,我希望您将其标记为答案:

    $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 column 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 
    $Table  | Export-Csv $reportCsv -Force -NoTypeInformation
    

    【讨论】:

    • 我喜欢这个解决方案,感谢您的意见。我确实有一个问题,网格视图按预期生成,但 csv 文件为空。有什么想法吗?
    • 你是对的,把它放在一个单独的行中,如下所示 $Table | Out-GridView $Table |导出 Csv $reportCsv -Force -NoTypeInformation
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    • 2011-10-17
    • 1970-01-01
    • 2020-06-22
    • 2021-06-08
    相关资源
    最近更新 更多