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