powershell

Powershell run jobs in parallel


I'm relatively new to PowerShell and I'm trying to create a function to build some installers. I have it working to build them in series but it would be nice to run in parallel, but it also needs to wait until they are all built to continue. I believe this would be done with Start-Job and Wait-Job but I can't get it working correctly. Here is my current code:

function BuildInstallers {
  $installerPath = $env:ADVINST_COM
  $jobs = @();
  for ($year = 2023; $year -le 2026; $year++) {
      $jobs += Start-Job -ScriptBlock { Start-Process -FilePath $using:installerPath -ArgumentList "/rebuild `"Installers\install$using:year.aip`"" -Wait -NoNewWindow }
  }

  Wait-Job -Job $jobs

  Receive-Job -Job $jobs
}

When I run this it runs but just immediately gives me this output:

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
1      Job1            BackgroundJob   Completed     False           localhost             Start-Process -FilePa...
3      Job3            BackgroundJob   Completed     False           localhost             Start-Process -FilePa...
5      Job5            BackgroundJob   Completed     False           localhost             Start-Process -FilePa...
7      Job7            BackgroundJob   Completed     False           localhost             Start-Process -FilePa...

It's basically saying the processes are instantly completed but nothing happens. What I would like to happen is that it spins up a process for each installer and starts running them in parallel and then when all are complete it continues execution (returns from this function). What am I doing wrong?

EDIT

I neglected to mention that the path to the installer executable resolves to a .com file instead of a .exe (that's what the company says to call for scripts). The full current path is C:\Program Files (x86)\Caphyon\Advanced Installer 22.6\bin\x86\AdvancedInstaller.com


Solution

  • Preface:


    Instead of using Start-Process in your background jobs, invoke each instance of the installer directly; not only does this potentially capture its standard output streams (stdout and stderr), it also reflects its (process') exit code in the automatic $LASTEXITCODE variable on termination.[1]

    The following is a streamlined version of your code that implements the above recommendations; it outputs information about each of the completed jobs in the form of a [pscustomobject] instance.

    function BuildInstallers {
    
      $installerPath = $env:ADVINST_COM
    
      # Launch the installers asynchronously as jobs, 
      # each of which runs the installer synchronously, in the same
      # directory as this script ($PSScriptRoot)
      $jobs = 2023..2026 | ForEach-Object {
        Start-Job -Name $_ -WorkingDirectory $PSScriptRoot {
          & $using:installerPath /rebuild "Installers\install$using:_.aip" | Write-Output
          $LASTEXITCODE # Output the installer's exit code.
        }
      }
    
      # Wait for the jobs to complete and relay their output.
      $jobs | Wait-Job | ForEach-Object {
        $output = $_ | Receive-Job -Wait -AutoRemoveJob
        $exitCode = $output[-1]
        [pscustomobject] @{
          Year = $_.Name
          ExitCode = $exitCode
          Output = ($output | Select-Object -SkipLast 1) -join "`n"
        }
      }
    
    }
    

    Note:


    Potential alternative:


    [1] See this answer for a comprehensive juxtaposition of direct invocation vs. use of Start-Process.