Managing Processes in PowerShell

Managing Processes in PowerShell

  • Comments 13

Several people have asked recently about how to manage processes in PowerShell. This blog post should answer a number of those questions.

As one might expect from the "shell" part of PowerShell, you don't have to do special anything to start a process . In a programming language such as VBScript, you have to do something fairly complex like:

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
objProcess.Create "Notepad.exe", Null, objConfig, intProcessID

but in shells ( PowerShell, cmd.exe, bash, etc.) you can simply start a program like notepad simply by typing "notepad" as shown.

PS (1) > notepad
<shell returns immediately>
PS (2) >

Now you'll notice that when you do this the shell returns immediately and prompts for the next command. This is because notepad is a win32 GUI process and runs in the background. On the other hand, if you start a console application like "ping.exe", PowerShell will wait until that process completes.

But what if I do want to wait for the Win32 process? In cmd.exe, you'd do this with "start /wait" but this command is not (directly) available in PowerShell. So what do we do?

PowerShell directly runs executables. If it waits for the process to exit, then the exit code left in $LASTEXITCODE. Now if a Win32 executable is the last command in a pipeline and is not redirected, then the process will run in the background. However, if you pipe its output into something, then PowerShell will wait for the command to exit

PS (1) > notepad foo.txt  | out-null
<exit notepad>
PS (2) >

So this is a simple (if not intuitive) wait to wait for a GUI process to exit.

Alternatively, If the process is already running, then you can use Get-Process to get the necessary information back and then do a WaitForExit() on that object.

PS (3) > get-process notepad
Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     46       2      900       3744    28     0.05   4140 notepad
PS (4) > $np = get-process notepad
PS (5) > $np.waitforexit()
<exit notepad>
PS (6) >

Here is a somewhat more sophisticated example where we open a document instead of starting a process. This time we'll find the process by window title instead of by the executable name

PS (8) > ./foo.txt
PS (9) > $fp = gps | where {$_.mainwindowtitle -match "foo.txt"}
PS (10) > $fp.WaitForExit()
PS (11) >

Unfortunately searching for a process always means that you may find the wrong process. Ideally you'd like to get the Process object directly. So for more precise process management, you will have to use the .NET class System.Diagnostics.Process and start the processes yourself. For example, you can use [diagnostics.process]::Start() to wait for a process:

PS (12) > $p = [diagnostics.process]::start("notepad.exe", "$pwd\foo.txt")
PS (13) > $p.WaitForExit()

Notice the use of "$pwd\foo.txt" to specify the full path to the file. This is because PowerShell maintains its own idea of the current directory. (This note applies to using any .NET API that accesses the file system – you need to give it an absolute path.)

Now we're starting to get back to the level of complexity that you find in a programming language. However, you also have all of the power of those languages. Here's an example script "background.ps1" that will let you run a scriptblock detached in a separate window (or in the same window if you specify the –inconsole parameter.

param(
    [scriptblock] $script,  # scriptblock to run
    [switch] $inconsole      # don't create a new window
)
# break out of the script on any errors
trap { break }
# encode the script to pass to the child process...
$encodedString = [convert]::ToBase64String(
    [Text.Encoding]::Unicode.GetBytes([string] $script))
# create a new process
$p = new-object System.Diagnostics.Process
# create a startinfo object for the process
$si = new-object System.Diagnostics.ProcessStartInfo
$si.WorkingDirectory = $pwd
if ($inconsole)
{ 
    $si.UseShellExecute = $false
}
Else
{
    $si.UseShellExecute = $true
}
# set up the command and arguments to run
$si.FileName = (get-command powershell.exe).Definition
$si.Arguments = "-encodedCommand $encodedString"
# and start the powershell process
[diagnostics.process]::Start($si)

This script let's you do things in PowerShell that you can't do from cmd.exe but can do from VBScript.

Finally, here are two more ways of working with processes. Even though PowerShell doesn't have a start command, cmd.exe does so you can do a start /wait by doing

PS (14) > cmd /c start /wait notepad.exe
PS (15) > 

And, last but not least, the PowerShell Community Extensions includes a Start-Process cmdlets. You can get a list of these features at:

http://www.codeplex.com/PowerShellCX/Wiki/View.aspx?title=PSCX%20Features

-bruce

Bruce Payette [MSFT]

Windows PowerShell Tech Lead

Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

My book: http://manning.com/powershell

Leave a Comment
  • Please add 7 and 6 and type the answer here:
  • Post
  • The VB example allows you to run the process on remote machines. Otherwise a much simpler WSH command can run processes. What is the powershell equivilent of launching processes on remote machines, does it still use WMI?

  • Is $LASTERRORCODE should be $LASTEXITCODE?

  • Yes - I fixed the blog entry. Thanks.

    -bruce

  • I find that:

    $np = get-process notepad

    is a bit hit and miss - which notepad process should it get? Worse, if there is one match, it returns a single Process object... if not, an array.

    Maybe the next version of PowerShell can preload a $LASTPROCESS variable with the process object of the last process it launched, so things like:

    notepad ; $LASTPROCESS.WaitForExit()

    are possible.

    Also, as PowerShell is a shell, shouldn't process/job control be a first class citizen and be supported with cmdlets?

  • "$np = get-process notepad" is *extemely* hit or miss if you use notepad alot. You could sort the processes by start date, created by you etc in an effort to mitigate errors but yeah - we should have provided a way of getting the most recent process created in a runspace. Rather than having a single $LASTPROCESS variable, I was thinking that a $process array along the lines of $error would be nice. The most recently created process would be kept in $process[0].

    > as PowerShell is a shell, shouldn't process/job control be a first class citizen

    That would seem obvious wouldn't it :-) I had implemented a New-Process/Start-Process cmdlet but it was cut from the project at one point. In triaging what went into V1, anything more than basic process management got a relatively low rank because PowerShell gives you have access to [Diagnostics.Process]. This means that, while it might be complex, you can do pretty much anything. To ship is to choose as Jeffrey says.

    Please post a request on the PowerShell connect site so other people can mod it up. This is an important data source in the tirage process for what goes into the next version. If a sufficient number of people ask for it, then it goes on the short list for V.Next.

    And, responding to an earlier comment:

    > What is the powershell equivilent of launching processes on remote machines, does it still use WMI?

    Remote operations in 1.0 require you to use WMI, remotable .NET APIs or other existing remoting mechanisms. PowerShell 1.0 has no explicit remote capabilities. Support for remoting is a Pri 0 for the next release.

    Thanks,

    -bruce

  • Bruce, I've been playing around with runspaces to achieve background processing of scriptblocks (at http://ps1.soapyfrog.com/2007/01/22/running-pipelines-in-the-background/ )

    Are we likely to see this becoming more friendly in upcoming  releases?

    I realise runspaces are really there for host applications to use, but they do seem serviceable.

    Your comment on the "PowerShell connect site" is noted.

  • If I pass the scriptblock that contain variable, the code doesnot seem to work. This is because child process doesnot inherit the variables from the parent. How can I overcome this?

  • Actually i am integrating VB and a program written in C++. I am able to run this C++ program by clicking a button designed in VB. Once i click the button the prgram runs on the DOS platform. I do not want this DOS platform visible.But the C++ process should continue to run in the background,invisible.

  • A short google search got: &lt; [link] &gt; where the tip to pipe output to out-null is given. notepad

  • You can find more details here: [link] Shay [link]

  • It would be really great to see an updated version of this with all of the V2 extensions like Start-Process and the Jobs extensions.

    Thanks for all the hard work, PowerShell is shaping up to be one truly amazing tool for process automation!

    -Chris

  • Thanks a lot for this article !

    -marc

  • Hi.

    I got a master powershell script calling a subscript. The subscript throw an error but $LASTEXITCODE in MASTER doesn't seem to catch it.

    Basically my process stops at the time the throw command is run and the process is not returned to MASTER.

    Is there a way around this?

Page 1 of 1 (13 items)