PSRemoting jobs are quite amazing.  While they are a little complicated to grasp at first, they’re quite powerful.  However, with great power usually comes great responsibility.  We know we can spawn a gazillion jobs, but how about managing them?  How many do we have to do?  What did they return, etc.

Here’s a skeleton function that does nothing more than return two [Datetime] objects separated by 10 seconds.

function Get-PsRemotingJobsExample

{
    param (
        [Parameter(ValueFromPipeLine=$true)][String[]]$ComputerName = @($env:ComputerName),
        [int]$TimeoutInterval = 60,
        [int]$JobPollingInterval = 5,
        [int]$ThrottleLimit = 100,
        [switch]$ShowRunspaceID
    )

    begin
    {
        $scriptBlock = {
            Get-Date; Start-Sleep -Seconds 10; Get-Date;

        } # $scriptblock

        $jobs = New-Object System.Collections.ArrayList;

    } # begin

    process
    {
        foreach ($computer in $ComputerName)
        {
            if (($Computer -match "^$env:ComputerName\.") -or ($Computer -match "^$env:ComputerName$"))
            {
                $jobs.Add((Start-Job -ScriptBlock $scriptblock -ArgumentList $arguments)) | Out-Null;

            } # if (($Computer -match "^$env:ComputerName\.") -or ($Computer -match "^$env:ComputerName$"))
            else
            {
                $jobs.Add((Invoke-Command -AsJob -ScriptBlock $scriptblock -ArgumentList $arguments -ComputerName $computer -ThrottleLimit $ThrottleLimit )) | Out-Null;

            } # if (($Computer -match "^$env:ComputerName\.") -or ($Computer -match "^$env:ComputerName$")) ... else

        } # foreach ($computer in $ComputerName)

    } # process

    end
    {
        $initialCount = $jobs.Count;
        $notAfter = (Get-Date) + (New-TimeSpan -Seconds $TimeoutInterval);

        while ($jobs)
        {
            $now = Get-Date;

            if ($now -gt $notAfter) { break; }

            Write-Progress $now "$($jobs.Count)/$initialCount jobs and $([int]($notAfter - $now).TotalSeconds) seconds to go.";

            Get-Job -Id ($jobs | % { $_.Id }) |
            ? { $_.State -ne 'Running' } |
            % {
                
                if ($_.State -eq 'Completed')
                {
                    if ($data = (Receive-Job -Id $_.Id))
                    {
                        if ($ShowRunspaceID)
                        {
                            $data;

                        } # if ($ShowRunspaceID)
                        else
                        {
                            $properties = $data | Get-Member -MemberType *Propert* | 
                            % { 
                                if ($_.name -ne 'RunspaceId') 
                                {
                                    $_.name;

                                } # if ($_.name -ne 'RunspaceId') 
                            
                            } # $properties = $data | Get-Member -MemberType *Propert* | 

                            $data | Select-Object -Property $properties;

                        } # if ($ShowRunspaceID) ... else

                    } # if ($data = (Receive-Job -Id $_.Id))
                    else
                    {
                        Write-Warning "No data-received from job at $($_.Location)";

                    } # if ($data = (Receive-Job -Id $_.Id)) ... else

                    Remove-Job -id $_.Id;
                
                } # if ($_.State -eq 'Completed')
                else
                {
                    Write-Warning "JobId $($_.Id) failed.  Please investigate.";

                } # if ($_.State -eq 'Completed') ... else

                $jobs.Remove($_) | Out-Null;

            } # Get-Job -Id ($jobs | % { $_.Id }) | ? { $_.State -ne 'Running' }

            if ($jobs) { start-sleep $JobPollingInterval; }

        } # while ($jobs)

    } # end

} # function Get-PsRemotingJobsExample