[powershell] Powershell equivalent of bash ampersand (&) for forking/running background processes

In bash the ampersand (&) can be used to run a command in the background and return interactive control to the user before the command has finished running. Is there an equivalent method of doing this in Powershell?

Example of usage in bash:

 sleep 30 &

This question is related to powershell

The answer is


As long as the command is an executable or a file that has an associated executable, use Start-Process (available from v2):

Start-Process -NoNewWindow ping google.com

You can also add this as a function in your profile:

function bg() {Start-Process -NoNewWindow @args}

and then the invocation becomes:

bg ping google.com

In my opinion, Start-Job is an overkill for the simple use case of running a process in the background:

  1. Start-Job does not have access to your existing scope (because it runs in a separate session). You cannot do "Start-Job {notepad $myfile}"
  2. Start-Job does not preserve the current directory (because it runs in a separate session). You cannot do "Start-Job {notepad myfile.txt}" where myfile.txt is in the current directory.
  3. The output is not displayed automatically. You need to run Receive-Job with the ID of the job as parameter.

NOTE: Regarding your initial example, "bg sleep 30" would not work because sleep is a Powershell commandlet. Start-Process only works when you actually fork a process.


I've used the solution described here http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!130.entry successfully in PowerShell v1.0. It definitely will be easier in PowerShell v2.0.


You can do something like this.

$a = start-process -NoNewWindow powershell {timeout 10; 'done'} -PassThru

And if you want to wait for it:

$a | wait-process

Bonus osx or linux version:

$a = start-process pwsh '-c',{start-sleep 5; 'done'} -PassThru 

Example pinger script I have. The args are passed as an array:

$1 = start -n powershell pinger,comp001 -pa

Seems that the script block passed to Start-Job is not executed with the same current directory as the Start-Job command, so make sure to specify fully qualified path if needed.

For example:

Start-Job { C:\absolute\path\to\command.exe --afileparameter C:\absolute\path\to\file.txt }

You can use PowerShell job cmdlets to achieve your goals.

There are 6 job related cmdlets available in PowerShell.

  • Get-Job
    • Gets Windows PowerShell background jobs that are running in the current session
  • Receive-Job
    • Gets the results of the Windows PowerShell background jobs in the current session
  • Remove-Job
    • Deletes a Windows PowerShell background job
  • Start-Job
    • Starts a Windows PowerShell background job
  • Stop-Job
    • Stops a Windows PowerShell background job
  • Wait-Job
    • Suppresses the command prompt until one or all of the Windows PowerShell background jobs running in the session are complete

If interesting about it, you can download the sample How to create background job in PowerShell


From PowerShell Core 6.0 you are able to write & at end of command and it will be equivalent to running you pipeline in background in current working directory.

It's not equivalent to & in bash, it's just a nicer syntax for current PowerShell jobs feature. It returns a job object so you can use all other command that you would use for jobs. For example Receive-Job:

C:\utils> ping google.com &

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
35     Job35           BackgroundJob   Running       True            localhost            Microsoft.PowerShell.M...


C:\utils> Receive-Job 35

Pinging google.com [172.217.16.14] with 32 bytes of data:
Reply from 172.217.16.14: bytes=32 time=11ms TTL=55
Reply from 172.217.16.14: bytes=32 time=11ms TTL=55
Reply from 172.217.16.14: bytes=32 time=10ms TTL=55
Reply from 172.217.16.14: bytes=32 time=10ms TTL=55

Ping statistics for 172.217.16.14:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 10ms, Maximum = 11ms, Average = 10ms
C:\utils>

If you want to execute couple of statements in background you can combine & call operator, { } script block and this new & background operator like here:

& { cd .\SomeDir\; .\SomeLongRunningOperation.bat; cd ..; } &

Here's some more info from documentation pages:

from What's New in PowerShell Core 6.0:

Support backgrounding of pipelines with ampersand (&) (#3360)

Putting & at the end of a pipeline causes the pipeline to be run as a PowerShell job. When a pipeline is backgrounded, a job object is returned. Once the pipeline is running as a job, all of the standard *-Job cmdlets can be used to manage the job. Variables (ignoring process-specific variables) used in the pipeline are automatically copied to the job so Copy-Item $foo $bar & just works. The job is also run in the current directory instead of the user's home directory. For more information about PowerShell jobs, see about_Jobs.

from about_operators / Ampersand background operator &:

Ampersand background operator &

Runs the pipeline before it in a PowerShell job. The ampersand background operator acts similarly to the UNIX "ampersand operator" which famously runs the command before it as a background process. The ampersand background operator is built on top of PowerShell jobs so it shares a lot of functionality with Start-Job. The following command contains basic usage of the ampersand background operator.

Get-Process -Name pwsh &

This is functionally equivalent to the following usage of Start-Job.

Start-Job -ScriptBlock {Get-Process -Name pwsh}

Since it's functionally equivalent to using Start-Job, the ampersand background operator returns a Job object just like Start-Job does. This means that you are able to use Receive-Job and Remove-Job just as you would if you had used Start-Job to start the job.

$job = Get-Process -Name pwsh &
Receive-Job $job

Output

NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
------    -----      -----     ------      --  -- -----------
    0     0.00     221.16      25.90    6988 988 pwsh
    0     0.00     140.12      29.87   14845 845 pwsh
    0     0.00      85.51       0.91   19639 988 pwsh


$job = Get-Process -Name pwsh &
Remove-Job $job

For more information on PowerShell jobs, see about_Jobs.


I've used the solution described here http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!130.entry successfully in PowerShell v1.0. It definitely will be easier in PowerShell v2.0.


ps2> start-job {start-sleep 20}

i have not yet figured out how to get stdout in realtime, start-job requires you to poll stdout with get-job

update: i couldn't start-job to easily do what i want which is basically the bash & operator. here's my best hack so far

PS> notepad $profile #edit init script -- added these lines
function beep { write-host `a }
function ajp { start powershell {ant java-platform|out-null;beep} } #new window, stderr only, beep when done
function acjp { start powershell {ant clean java-platform|out-null;beep} }
PS> . $profile #re-load profile script
PS> ajp

You can do something like this.

$a = start-process -NoNewWindow powershell {timeout 10; 'done'} -PassThru

And if you want to wait for it:

$a | wait-process

Bonus osx or linux version:

$a = start-process pwsh '-c',{start-sleep 5; 'done'} -PassThru 

Example pinger script I have. The args are passed as an array:

$1 = start -n powershell pinger,comp001 -pa

From PowerShell Core 6.0 you are able to write & at end of command and it will be equivalent to running you pipeline in background in current working directory.

It's not equivalent to & in bash, it's just a nicer syntax for current PowerShell jobs feature. It returns a job object so you can use all other command that you would use for jobs. For example Receive-Job:

C:\utils> ping google.com &

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
35     Job35           BackgroundJob   Running       True            localhost            Microsoft.PowerShell.M...


C:\utils> Receive-Job 35

Pinging google.com [172.217.16.14] with 32 bytes of data:
Reply from 172.217.16.14: bytes=32 time=11ms TTL=55
Reply from 172.217.16.14: bytes=32 time=11ms TTL=55
Reply from 172.217.16.14: bytes=32 time=10ms TTL=55
Reply from 172.217.16.14: bytes=32 time=10ms TTL=55

Ping statistics for 172.217.16.14:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 10ms, Maximum = 11ms, Average = 10ms
C:\utils>

If you want to execute couple of statements in background you can combine & call operator, { } script block and this new & background operator like here:

& { cd .\SomeDir\; .\SomeLongRunningOperation.bat; cd ..; } &

Here's some more info from documentation pages:

from What's New in PowerShell Core 6.0:

Support backgrounding of pipelines with ampersand (&) (#3360)

Putting & at the end of a pipeline causes the pipeline to be run as a PowerShell job. When a pipeline is backgrounded, a job object is returned. Once the pipeline is running as a job, all of the standard *-Job cmdlets can be used to manage the job. Variables (ignoring process-specific variables) used in the pipeline are automatically copied to the job so Copy-Item $foo $bar & just works. The job is also run in the current directory instead of the user's home directory. For more information about PowerShell jobs, see about_Jobs.

from about_operators / Ampersand background operator &:

Ampersand background operator &

Runs the pipeline before it in a PowerShell job. The ampersand background operator acts similarly to the UNIX "ampersand operator" which famously runs the command before it as a background process. The ampersand background operator is built on top of PowerShell jobs so it shares a lot of functionality with Start-Job. The following command contains basic usage of the ampersand background operator.

Get-Process -Name pwsh &

This is functionally equivalent to the following usage of Start-Job.

Start-Job -ScriptBlock {Get-Process -Name pwsh}

Since it's functionally equivalent to using Start-Job, the ampersand background operator returns a Job object just like Start-Job does. This means that you are able to use Receive-Job and Remove-Job just as you would if you had used Start-Job to start the job.

$job = Get-Process -Name pwsh &
Receive-Job $job

Output

NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
------    -----      -----     ------      --  -- -----------
    0     0.00     221.16      25.90    6988 988 pwsh
    0     0.00     140.12      29.87   14845 845 pwsh
    0     0.00      85.51       0.91   19639 988 pwsh


$job = Get-Process -Name pwsh &
Remove-Job $job

For more information on PowerShell jobs, see about_Jobs.


I've used the solution described here http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!130.entry successfully in PowerShell v1.0. It definitely will be easier in PowerShell v2.0.


Seems that the script block passed to Start-Job is not executed with the same current directory as the Start-Job command, so make sure to specify fully qualified path if needed.

For example:

Start-Job { C:\absolute\path\to\command.exe --afileparameter C:\absolute\path\to\file.txt }

ps2> start-job {start-sleep 20}

i have not yet figured out how to get stdout in realtime, start-job requires you to poll stdout with get-job

update: i couldn't start-job to easily do what i want which is basically the bash & operator. here's my best hack so far

PS> notepad $profile #edit init script -- added these lines
function beep { write-host `a }
function ajp { start powershell {ant java-platform|out-null;beep} } #new window, stderr only, beep when done
function acjp { start powershell {ant clean java-platform|out-null;beep} }
PS> . $profile #re-load profile script
PS> ajp

As long as the command is an executable or a file that has an associated executable, use Start-Process (available from v2):

Start-Process -NoNewWindow ping google.com

You can also add this as a function in your profile:

function bg() {Start-Process -NoNewWindow @args}

and then the invocation becomes:

bg ping google.com

In my opinion, Start-Job is an overkill for the simple use case of running a process in the background:

  1. Start-Job does not have access to your existing scope (because it runs in a separate session). You cannot do "Start-Job {notepad $myfile}"
  2. Start-Job does not preserve the current directory (because it runs in a separate session). You cannot do "Start-Job {notepad myfile.txt}" where myfile.txt is in the current directory.
  3. The output is not displayed automatically. You need to run Receive-Job with the ID of the job as parameter.

NOTE: Regarding your initial example, "bg sleep 30" would not work because sleep is a Powershell commandlet. Start-Process only works when you actually fork a process.


tl;dr

Start-Process powershell { sleep 30 }

You can use PowerShell job cmdlets to achieve your goals.

There are 6 job related cmdlets available in PowerShell.

  • Get-Job
    • Gets Windows PowerShell background jobs that are running in the current session
  • Receive-Job
    • Gets the results of the Windows PowerShell background jobs in the current session
  • Remove-Job
    • Deletes a Windows PowerShell background job
  • Start-Job
    • Starts a Windows PowerShell background job
  • Stop-Job
    • Stops a Windows PowerShell background job
  • Wait-Job
    • Suppresses the command prompt until one or all of the Windows PowerShell background jobs running in the session are complete

If interesting about it, you can download the sample How to create background job in PowerShell


tl;dr

Start-Process powershell { sleep 30 }

I've used the solution described here http://jtruher.spaces.live.com/blog/cns!7143DA6E51A2628D!130.entry successfully in PowerShell v1.0. It definitely will be easier in PowerShell v2.0.