Search code examples
powershellpowershell-2.0jobs

How to store powershell job information in a variable


I currently have a powershell (v2) script that takes a list of host PCs, checks remotely that some files/software are where they should be, and emails a string of results when it is complete. However, when trying to do this sequentially on over 100 rooms one at a time the script takes quite a while to finish.

To speed it up I've been trying to make the script multi threaded by using jobs to report on each host name instead. I have placed the main parts of my code below with some of the larger unrelated sections removed for clarity.

$pcHostNames = Get-Content "$scriptPath\HostNames.txt"

$scriptBlock = {
    Param(pcHostName)

    $softwareInstalled = $false

    # Test to see if host can be reached
    If (Test-Connection -ComputerName $pcHostName -Quiet)
    {
        # Multiple lines of code to check if different files/software are installed on each remote PC
        # If they are installed, set $softwareInstalled to true

        if (-not $softwareInstalled)
        {
            $reportList += "$pcHostName`: Not installed`n"
        }
        else
        {
            $reportList += "$pcHostName`: Fully installed`n"
        }
    }
    else
    {
        $reportList += "$pcHostName`: Error - Connection failed`n"
    }
} # End - $scriptBlock  

$pcHostNames | % { Start-Job -Scriptblock $scriptBlock -ArgumentList $_ } | Get-Job | Wait-Job | Receive-Job

Currently the script pings the PCs and then checks that various files/software are installed, and adds a string to $reportList which is then used in an email to send the report in another part of my code.

Since $reportList is out of scope in each job all I get is a blank email at the end of my script. Since there isn't a way to add a string value to a variable from within a job, is there a way to edit my code for the job pipeline to return a string value which I can then add to a variable?

I believe the issue may be related to the last line of my code so I've tried a few different variations on executing jobs but have not had much luck with what I have been able to find online.

Alternatively, is there a better way to multithread this script via another method?


Solution

  • Variables exist within the scope they are created.

    When you declare variables within a job, they exist only within the scope of that job.

    If you want to pass details back to the caller from a child job, you need to use Write-Output. Then use Get-Job to monitor the status of the job until it is complete, then use Receive-Job to get the output.

    start-job {write-output "hello world"}
    get-job | receive job
    

    Edit:

    Taking your example code block, the following uses write-output to get return the results from the job, filters the jobs that completed successfully, receives the output from the jobs and stores them in the $reportList variable

    $pcHostNames = Get-Content "$scriptPath\HostNames.txt"
    
    $scriptBlock = {
        Param(pcHostName)
    
        $softwareInstalled = $false
    
        # Test to see if host can be reached
        If (Test-Connection -ComputerName $pcHostName -Quiet)
        {
            # Multiple lines of code to check if different files/software are installed on each remote PC
            # If they are installed, set $softwareInstalled to true
    
            if (-not $softwareInstalled)
            {
                Write-Output "$pcHostName`: Not installed`n"
            }
            else
            {
                Write-Output "$pcHostName`: Fully installed`n"
            }
        }
        else
        {
            Write-Output "$pcHostName`: Error - Connection failed`n"
        }
    } # End - $scriptBlock  
    
    $pcHostNames | % {
        Start-Job -Scriptblock $scriptBlock -ArgumentList $_
    }
    
    $reportList = Get-Job | Wait-Job | ?{$_.state -eq "Completed"} | Receive-Job