1

We have a Powershell script in C:\test\test.ps1. The script has the following content (nothing removed):

Start-Process -NoNewWindow powershell { sleep 30; }
sleep 10

When we open a command line window (cmd.exe) and execute that script by the following command

c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1

we can see in the Windows task manager (as well as Sysinternals Process Explorer) that the script behaves as expected:

  • Immediately after having executed the command above, two new entries appear in the process list, one being Powershell executing the "main" script (test.ps1), and one being Powershell executing the "background script" ({ sleep 30; }).
  • When 10 seconds have passed, the first entry (related to test.ps1) disappears from the process list, while the second entry remains in the process list.
  • When additional 20 seconds have passed (that is, 30 seconds in sum), the second entry (related to { sleep 30; }) also disappears from the process list.

This is the expected behavior, because Start-Process starts new processes in the background no matter what, unless -Wait is given. So far, so good.

But now we have a hairy problem which already has cost us two days of debugging until we finally figured out the reason for the misbehavior of one of our scripts:

Actually, test.ps1 is executed via SSH.

That is, we have installed Microsoft's implementation of the OpenSSH server on a Windows Server 2019 and have configured it correctly. Using SSH clients on other machines (Linux and Windows), we can log into the Windows Server, and we can execute test.ps1 on the server via SSH by executing the following command on the clients:

ssh -i <appropriate_key> administrator@ip.of.windows.server c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File C:\test\test.ps1

When observing the task manager on the Windows Server, we can again see the two new entries in the process list as described above as soon as this command is executed on a client.

However, both entries then disappear from the process list on the server after 10 seconds.

This means that the background process ({ sleep 30; }) gets killed as soon as the main process ends. This is the opposite of what is documented and of what should happen, and we really need to prevent it.

So the question is:

How can we change test.ps1 so that the background process ({ sleep 30; }) does not get killed under any circumstances when the script ends, even when the script is started via SSH?

Some side notes:

  • This is not an academic example. Actually, we have a fairly complex script system in place on that server which consists of about a dozen of Powershell scripts, one of them being the "main" script which executes the other scripts in the background as shown above; the background scripts themselves in turn might start further background scripts.

  • It is important to not start the main script in the background via SSH in the first place. The clients must process the output and the exit code of the main script, and must wait until the main script has done some work and returns.

    That means that we must use Powershell's capabilities to kick off the background processes in the main script (or to kick off a third-party program which is able to launch background processes which don't get killed when the main script ends).

Binarus
  • 4,005
  • 3
  • 25
  • 41

1 Answers1

1

Note: The original advice to use jobs here was incorrect, as when the job is stopped along with the parent session, any child processes still get killed. As it was incorrect advice for this scenario, I've removed that content from this answer.

Unfortunately, when the PowerShell session ends, so too are child processes created from that session. However, when using PSRemoting we can tell Invoke-Command to run the command in a disconnected session with the -InDisconnectedSession parameter (aliased to -Disconnected):

$icArgs = @{
  ComputerName = 'RemoteComputerName'
  Credential = ( Get-Credential )
  Disconnected = $true
}

Invoke-Command @icArcs { ping -t 127.0.0.1 }

From my testing with an infinite ping, if the parent PowerShell session closes, the remote session should continue executing. In my case, the ping command continued running until I stopped the process myself on the remote server.

The downside here is that it doesn't seem you are using PowerShell Remoting, but instead are invoking shell commands over SSH that happen to be a PowerShell script. You will also have to use PowerShell Core if you require the SSH transport for PowerShell remoting.

codewario
  • 19,553
  • 20
  • 90
  • 159
  • Thank you very much for the answer, and +1. However, we've already tested `Start-Job` and the `&` method, and it lead to the same problem ... – Binarus Mar 10 '22 at 06:23
  • So, turns out background jobs get killed (and their child processes) when the parent session ends. This is true whether the session is local or remote. Fortunately, there is a simple workaround to be found. I'll update my answer shortly. – codewario Mar 14 '22 at 14:06
  • Thank you very much again, and accepted. Your solution is correct. In the meantime, we had already found a different solution: `Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "..."`. The argument list then contains the path to the executable plus all command line arguments, if any. Your solution is probably better if we needed to start a process on another machine, but that isn't necessary in our case. – Binarus Mar 14 '22 at 14:36
  • Am I understanding correctly that you are using SSH to start another process on the *same machine*? – codewario Mar 14 '22 at 14:37
  • No, that is a misunderstanding. We are using SSH to connect to a VM and to start the processes in question in that VM. However, with your solution (i.e. `Invoke-Command`), we could even SSH into the VM and start processes on *another* Windows machine (without SSH). That is, we could SSH into `VM 1` and then start a process in `VM 2` using `Invoke-Command`. This is reflected by the fact that `-ComputerName` is an argument to `Invoke-Command`. I didn't research yet if `Invoke-WmiMethod` could start a process on a different machine, too, but I currently guess it can't. – Binarus Mar 14 '22 at 14:41
  • @Binarus I know it's been a while, but I'm running into this exact same issue now. If you remember, what exactly did you put inside the Invoke-WmiMethod arguments? Was it the original powershell executable call, the powershell script file, or the process within the script? – Riggs Markham Feb 15 '23 at 23:16