powershellparallel-processingbackground-processget-wmiobjectpowershell-jobs

Write to external array inside a running powershell job


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


Solution

  • Background jobs run in a separate (child) process, so you fundamentally cannot directly update values in the caller's scope from them.[1]

    Instead, make your job script block produce output that the caller can capture with Receive-Job.

    A simple example:

    # 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
    

    Note: If what you output from a background job are complex objects, they will typically not retain their original type and instead be custom-object emulations, due to the limitations of PowerShell's XML-based cross-process serialization infrastructure; only a limited set of well-known types deserialize with type fidelity, including primitive .NET types, hashtables and [pscustomobject] instances (with the type-fidelity limitations again applying to their properties and entries). - see this answer for background information.


    A few asides:

    Applying the above to your case:

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

    Caveat re targeting a large number of computers at once:

    Therefore, to have more control over how the computers are targeted in parallel, consider combining Invoke-Command with local invocation of Get-CimInstance on each remote machine; for instance:

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

    Also passing a sessions-options object to Invoke-Command's -SessionOption parameter, created with New-PSSessionOption, additionally gives you control over various timeouts.


    [1] In a script block executed in a background job, the automatic $args variable contains deserialized copies of the values passed by the caller - see this answer for background information.
    Note that the usually preferable, thread-based Start-ThreadJob cmdlet - see this answer - 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 feature.