在 运行 powershell 作业中写入外部数组

Write to external array inside a running powershell job

我正在尝试将数据写入外部数组,同时 运行 一个 powershell 作业-

这是我正在尝试的代码示例-

$datafromJob = @()
$wmijobs = @()
foreach ($c in $computers) {
    $wmijobs += Start-Job -Name WMIInventory -ScriptBlock {
        $jobdata = get-wmiobject -ComputerName $args[0] -Class win32_computersystem -Credential $Cred -ErrorVariable Err -ErrorAction SilentlyContinue
        if ($Err.length) {
            Add-content -Path D:\InventoryError.log -Force -Value $Err
            $details = @{
                Domain       = "Error"
                Manufacturer = "Error"
                Computer     = $args[0]
                Name         = "Error"
            }
            $args[3] += New-Object PSObject -Property $details
        }
        if ($jobdata.length) {
            $details = @{
                Domain       = $jobdata.Domain
                Manufacturer = $jobdata.Manufacturer
                Computer     = $args[2]
                Name         = $jobdata.Name
            }
            $args[3] += New-Object PSObject -Property $details
        }
        -ArgumentList $c, $Cred, "Test", $datafromJob
    }
}

期望在 $datafromJob 变量中输出,但是作业和循环变量的结尾是空的,M 不知道它是如何工作的,anyhelp,

如果对这个问题有任何疑问,请告诉我

后台作业运行在一个单独的(子)进程中,所以你根本不能直接从它们更新调用者范围内的值。[1]

相反,让您的作业脚本块生成调用者可以使用 Receive-Job.

捕获的 output

一个简单的例子:

# Create a 10-element array in a background job and output it.
# Receive-Job collects the output.
$arrayFromJob = Start-Job { 1..10 } | Receive-Job -Wait -AutoRemoveJob

注意:如果您从后台作业输出的是复杂对象,它们通常不会保留其原始类型,而是自定义对象仿真,由于PowerShell基于XML的跨进程序列化基础设施的限制;只有一组有限的知名类型以类型保真度反序列化,包括原始 .NET 类型、哈希表和 [pscustomobject] 实例(类型保真度限制再次应用于它们的属性和条目)。 - 有关背景信息,请参阅


一些旁白:

  • 不需要循环调用Start-Job/Get-WmiObject,因为后者的-ComputerName参数可以接受一个数组 的目标计算机在一次调用中连接。

    • 由于随后 并行查询目标计算机 ,您可能根本不需要后台作业 (Start-Job)。
  • CIM cmdlet(例如,Get-CimInstance)取代了 PowerShell v3(2012 年 9 月发布)中的 WMI cmdlet(例如,Get-WmiObject)。因此,应避免使用 WMI cmdlet,尤其是因为 PowerShell [Core](第 6 版及更高版本)甚至 都没有 他们了。

    • 默认情况下,远程使用 CIM cmdlet 需要为目标计算机设置 WS-Management 连接,因为如果在它们上启用了 PowerShell 远程处理,它们就会隐含地这样做 - 有关详细信息,请参阅 about_Remote_Requirements; alternatively, however, you can use the DCOM protocol (which is what the WMI cmdlets used) - see this answer

将以上内容应用到您的案例中:

# Create a CIM session that targets all computers.
# By default, the WS-Management protocol is used, which target computers
# are implicitly set up for if PowerShell remoting is enabled on them.
# However, you can opt to use DCOM - as the WMI cmdlets did - as follows:
#   -SessionOption (New-CimSessionOption -Protocol DCOM)
$session = New-CimSession -ComputerName $computers -Credential $Cred

# Get the CIM data from all target computers in parallel.
[array] $cimData = Get-CimInstance -CimSession $session -Class win32_computersystem -ErrorVariable Err -ErrorAction SilentlyContinue |
  ForEach-Object {
    [pscustomobject] @{
      Domain       = $_.Domain
      Manufacturer = $_.Manufacturer
      Computer     = $_.ComputerName
      Name         = $_.Name
    }
  }

# Cleanup: Remove the session.
Remove-CimSession $session

# Add error information, if any.
if ($Err) {
  Set-Content D:\InventoryError.log -Force -Value $Err
  $cimData += $Err | ForEach-Object {
    [pscustomobject] @{
      Domain       = "Error"
      Manufacturer = "Error"
      Computer     = $_.ComputerName
      Name         = "Error"
    }
  }
}

警告一次重新定位大量计算机:

  • 截至撰写本文时,Get-CimInstance help topic nor the conceptual about_CimSession 主题均未讨论 连接限制 (限制与远程计算机的并发连接数以防止压倒系统).

  • PowerShell的通用Invoke-Command remoting command, by contrast, has a -ThrottleLimit parameter that defaults to 32. Note that PowerShell remoting must first be enabled on the target computers in order to be able to use Invoke-Command on them remotely - see about_Remote_Requirements.

因此,要更好地控制并行定位计算机的方式,请考虑将 Invoke-Commandlocal 调用 Get-CimInstance 在每台远程机器上;例如:

Invoke-Command -ComputerName $computers -ThrottleLimit 16 {
    Get-CimInstance win32_computersystem  
}  -Credential $Cred -ErrorVariable Err -ErrorAction

还将 sessions-options 对象 传递给 Invoke-Command-SessionOption 参数,使用 New-PSSessionOption 创建,另外还可以让您控制各种 超时 .


[1] 在后台作业中执行的脚本块中,自动 $args 变量包含由来电者 - 请参阅 了解背景信息。
请注意,通常更可取的基于线程的 Start-ThreadJob cmdlet - 请参阅 - can receive live references to reference-type instances in the caller's scope, though modifying such objects then requires explicit synchronization, if multiple thread jobs access them in parallel; the same applies to the PowerShell 7+ ForEach-Object -Parallel 功能。

根据@mklement0 的建议,更新了我的脚本,

$wmijobs = @()
foreach ($c in $computers) {
    $wmijobs += Start-Job -Name WMIInventory -ScriptBlock {
        $jobdata = get-CimInstance -ComputerName $args[0] -Class win32_computersystem -Credential $Cred -ErrorVariable Err -ErrorAction SilentlyContinue
        if ($Err.length) {
            Add-content -Path D:\InventoryError.log -Force -Value $Err
            $details = @{
                Domain       = "Error"
                Manufacturer = "Error"
                Computer     = $args[0]
                Name         = "Error"
            }
          return New-Object PSObject -Property $details
        }
        if ($jobdata) {
            $details = @{
                Domain       = $jobdata.Domain
                Manufacturer = $jobdata.Manufacturer
                Computer     = $args[2]
                Name         = $jobdata.Name
            }
            return New-Object PSObject -Property $details
        }
        -ArgumentList $c, $Cred, "Test", $datafromJob
    }
}
$wmijobs = $wmijobs | get-job | receive-job -AutoRemoveJob -wait

线程作业在技术上是可行的,但在您的情况下可能效果不佳。数组不是线程安全的。

$a = 1,2,3
start-threadjob { $b = $using:a; $b[0] = 2 } | receive-job -wait -auto
$a

2
2
3

嗯,从底部开始线程安全地更新字典集合:https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature/

$threadSafeDictionary =
[System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()

Get-Process | ForEach-Object -Parallel {
    $dict = $using:threadSafeDictionary
    $dict.TryAdd($_.ProcessName, $_)
}

$threadSafeDictionary["pwsh"]

"Concurrent bag":

$threadSafeArray =
[System.Collections.Concurrent.ConcurrentBag[object]]::new()

1..10 | foreach-object -parallel { 
    $array = $using:threadSafeArray
    $array.Add($_)
} 

$threadSafeArray

10
9
8
7
6
5
4
3
2
1