Запись во внешний массив внутри запущенного задания powershell - PullRequest
1 голос
/ 28 марта 2020

Я пытаюсь записать данные во внешний массив при выполнении задания 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 , но конец задания и переменная l oop пустые, M не понимаю, как это будет работать, anyhelp,

Дайте мне знать, если какие-либо вопросы по этому вопросу

Ответы [ 3 ]

2 голосов
/ 28 марта 2020

Фоновые задания выполняются в отдельном (дочернем) процессе, поэтому вы принципиально не можете напрямую обновлять значения в области действия вызывающего из них. [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 *. Инфраструктура межпроцессной сериализации на основе 1139 *; только ограниченный набор известных типов десериализуется с верностью типов, включая примитивные типы. NET, хеш-таблицы и экземпляры [pscustomobject] (с ограничениями верности типов, вновь применяемыми к их свойствам и записям). - см. этот ответ для справочной информации.


Несколько сторон:

  • Нет необходимости звонить Start-Job / Get-WmiObject в al oop, потому что последний -ComputerName Параметр может принимать массив целевых компьютеров для подключения за один вызов.

    • Поскольку целевые компьютеры запрашиваются параллельно , вы не можете вообще нужно фоновое задание (Start-Job).
  • Командлеты CIM (например, Get-CimInstance) заменили командлеты WMI (например, Get-WmiObject) в PowerShell v3 (выпущен в сентябре 2012 г.). Поэтому следует избегать командлетов WMI , не в последнюю очередь потому, что PowerShell [Core] (версия 6 и выше), где все будущие усилия будут go, даже не имеет их больше.

    • Удаленное использование командлетов CIM по умолчанию требует, чтобы целевые компьютеры были настроены для подключений WS-Management, как это неявно происходит, если на них включено удаленное взаимодействие PowerShell - см. about_Remote_Requirements ; в качестве альтернативы, однако, вы можете использовать протокол DCOM (который используется для командлетов WMI) - см. этот ответ для получения дополнительной информации.

Применение выше для вашего случая:

# 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 topi c и концептуальные about_CimSession topi c обсуждение регулирование соединения (ограничение количества одновременных подключений к удаленным компьютерам для предотвращения чрезмерной нагрузки система).

  • Универсальная команда PowerShell Invoke-Command Команда удаленного взаимодействия, напротив, имеет параметр -ThrottleLimit, который по умолчанию равен 32. Обратите внимание, что удаленное взаимодействие PowerShell должно сначала быть включено на целевых компьютерах, чтобы иметь возможность удаленно использовать Invoke-Command на них - см. about_Remote_Requirements .

Поэтому, чтобы иметь больший контроль над тем, как компьютеры ориентированы параллельно, рассмотрите возможность объединения Invoke-Command с local вызовом Get-CimInstance на каждой удаленной машине ; например:

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

Также передается объект * options-options параметру Invoke-Command -SessionOption, созданный с помощью New-PSSessionOption, дополнительно дает Вы управляете различными таймаутами .


[1] В блоке сценария, выполняемом в фоновом задании, переменная automati c $args содержит десериализованный копии значений, переданных вызывающей стороной - см. этот ответ для получения справочной информации.
Обратите внимание, что обычно предпочтительный командлет Start-ThreadJob на основе потоков - см. этот ответ - может получать живые ссылки на экземпляры ссылочного типа в области вызова, хотя изменяет таких объектов, тогда требуется явная синхронизация, если несколько потоковых заданий получают к ним доступ параллельно; то же самое относится к функции PowerShell 7+ ForEach-Object -Parallel.

0 голосов
/ 28 марта 2020

Технически это возможно для потоковых заданий, но, вероятно, в вашем случае это не сработает. Массивы не являются поточно-ориентированными.

$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
0 голосов
/ 28 марта 2020

Согласно предложению @ 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
...