在 运行 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-Command
与 local 调用 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
我正在尝试将数据写入外部数组,同时 运行 一个 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
.
一个简单的例子:
# 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 to32
. Note that PowerShell remoting must first be enabled on the target computers in order to be able to useInvoke-Command
on them remotely - see about_Remote_Requirements.
因此,要更好地控制并行定位计算机的方式,请考虑将 Invoke-Command
与 local 调用 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 - 请参阅 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