Managing Processes in PowerShell

PowerShell Team

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

0 comments

Discussion is closed.

Feedback usabilla icon