1

I am trying to write data to external array while running a powershell job-

This is my code sample that I' m trying-

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

Expecting Output in $datafromJob Variable, but the end of job and loop variable is empty, M not getting how it will work, anyhelp,

Do let me know if any queries on this question

4

3 回答 3

2

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

$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"]

“并发包”:

$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
于 2020-03-28T15:00:03.820 回答
2

后台作业在单独的(子)进程中运行,因此您基本上不能直接从它们更新调用者范围内的值。[1]

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

一个简单的例子:

# 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)取代了Get-WmiObjectPowerShell v3(2012 年 9 月发布)中的 WMI cmdlet(例如,)。因此,应该避免使用 WMI cmdlet,尤其是因为 PowerShell [Core](版本 6 及更高版本),未来的所有努力都将用于,甚至不再拥有它们。

    • 默认情况下,远程使用 CIM cmdlet 需要为目标计算机设置 WS-Management 连接,因为如果在目标计算机上启用了 PowerShell 远程处理,它们就会隐含 - 请参阅about_Remote_Requirements;但是,或者,您可以使用 DCOM 协议(这是 WMI cmdlet 使用的协议) - 有关更多信息,请参阅此答案

将上述内容应用于您的案例:

# 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帮助主题和概念性about_CimSession主题均未讨论连接限制(限制与远程计算机的并发连接数以防止系统不堪重负)。

  • Invoke-Command相比之下,PowerShell 的通用远程处理命令有一个-ThrottleLimit默认为32. 请注意,必须首先在目标计算机上启用 PowerShell 远程处理才能Invoke-Command远程使用它们 - 请参阅about_Remote_Requirements

因此,为了更好地控制计算机如何并行定位,请考虑结合每台远程计算机上Invoke-Command本地调用;例如:Get-CimInstance

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

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


[1] 在后台作业中执行的脚本块中,自动$args变量包含调用者传递的值的反序列化副本- 有关背景信息,请参阅此答案
请注意,通常更可取的基于线程的Start-ThreadJobcmdlet(请参阅此答案可以接收对调用者范围内引用类型实例的实时引用,但如果多个线程作业并行访问它们,则修改此类对象则需要显式同步;这同样适用于 PowerShell 7+ForEach-Object -Parallel功能。

于 2020-03-28T09:03:58.037 回答
0

根据@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
于 2020-03-28T09:26:28.020 回答