Summary: Learn how to use Windows PowerShell to pause a script and wait for another process to exit before continuing.
Hey, Scripting Guy! I am attempting to use Windows PowerShell to shut down an application, but the application is rather complicated. I need to stop one process, and wait for that process to complete before starting the second process. I have attempted to use the Start-Sleep cmdlet to pause script execution to allow for the different processes to complete, but unfortunately different systems are faster than others so this is a hit-or-miss proposition. Can you figure out a better way of doing this?
—SK
Hello SK,
Microsoft Scripting Guy Ed Wilson here. Well, I am floating along at 35,000 feet (10,850 meters) listening to Stan Kenton’s Waltz of the Prophets, which in my mind (as a former saxophone major at the university) is one of the absolutely best jazz standards ever written. I have been playing around with Windows PowerShell for the last several hours (decided to group a bunch of my utility functions into a Windows PowerShell module, create a manifest, and install them into my Windows PowerShell folder by using my Copy-Modules function). You should really be investing in writing Windows PowerShell modules instead of writing one-off scripts, if possible. The effort will pay great dividends.
Anyway, I am sitting in First Class (I was upgraded) with a decent worktable, and my new laptop will get over eight hours and still provide decent performance, so life is good. The solitude provides a wonderful time for exploring and for playing with Windows PowerShell. I am forcing myself to stay away from WMI for now, and am instead focusing on different ways of putting together cmdlets, to see if I can come up with something cool. I was pretty pleased with the path trick I came up with in yesterday’s Two Powerful Tricks for Finding PowerShell Scripts post.
One of the things I was playing around with is the Wait-Process cmdlet. It is most useful when used in a script in that it will halt execution of a script until a process terminates. This allows an easy way to write a script that terminates multiple processes—one at a time.
There are two ways that the Wait-Process cmdlet accepts input: either a process name or a process ID. For example, I can start an instance of Notepad and then use Wait-Process to pause until Notepad closes.
Notepad
Wait-Process notepad
When any instance of the Notepad process exits, control to the script (or Windows PowerShell console) returns. This works when you do not care which copy of Notepad closes, or when you know there is only a single instance of a process running.
A more useful way to do this is to capture the process ID of the process when it launches, and use that specific process ID with the Wait-Process cmdlet. This technique uses the Invoke-WmiMethod cmdlet and is shown here:
$proc = Invoke-WmiMethod -Class win32_process -Name create -ArgumentList "notepad"
Wait-Process -Id $proc.ProcessId
One of my favorite tricks (but it is not in my top ten favorite Windows PowerShell tricks) pipes Notepad to Out-Null to halt processing the script until Notepad closes. I then remove the copy of the text file that was viewed. This is a great way to handle temporary files. This technique is shown in the following code:
$tmpfile = [io.path]::GetTempFileName()
get-process >> $tmpFile
notepad $tmpfile | out-null
Remove-Item $tmpfile
test-path $tmpfile
The problem with this “trick” is that it does not work for other things. For example, if I write process information in HTML format and display that in Internet Explorer, the Out-Null trick does not halt the script. In addition, if I attempt to close Internet Explorer without a specific process ID, I might close more than once instance of Internet Explorer. The code that follows creates a temporary file in a temporary location. It changes the file extension to HTML, and then uses the Convertto-HTML cmdlet to output HTML formatted data from the Get-Process cmdlet. I use the Out-File cmdlet to write my HTML formatted data to a file, and then I use the Invoke-Item cmdlet to open the HTML file in Internet Explorer (at least on my system; this methodology will open the HTML file with the default program associated with that extension):
$tmpfile = [io.path]::GetTempFileName()
$tmpFile = "{0}.{1}" -f ($tmpfile).split(".")[0],"html"
Get-process | ConvertTo-HTML |
Out-File -FilePath $tmpFile -Encoding ascii -append
invoke-item $tmpfile
Note See the excellent guest Hey, Scripting Guy! Blog post, Proxy Functions: Spice Up Your PowerShell Core Cmdlets, written by Microsoft PowerShell MVP Shay Levy for a way to add a filepath parameter to the ConvertTo-HTML cmdlet. By creating a proxy function, you can avoid a call to Out-File.
The Out-Null trick will not pause the script and allow for cleanup, and we would not know which version or instance of Internet Explorer might be killed anyway, so there needs to be a new way. Here are the steps involved:
- Create a temporary file name.
- Change the file extension to html.
- Pipe data to the ConvertTo-Html cmdlet.
- Pipe the data from ConvertTo-Html to Out-File, and create an HTML document.
- Use Invoke-WMIMethod to open the file and store the return values.
- Use the Wait-Process cmdlet to pause execution of the script and wait for the process to close.
- Use Remove-Item to remove the temporary file.
The GetProcessInfoDisplayHTMLtrackProcessAndRemoveTmpFile.ps1 script illustrates these steps.
GetProcessInfoDisplayHTMLtrackProcessAndRemoveTmpFile.ps1
$tmpfile = [io.path]::GetTempFileName()
$tmpFile = "{0}.{1}" -f ($tmpfile).split(".")[0],"html"
$iepath = Join-Path -Path ${env:ProgramFiles(x86)} `
-ChildPath "internet explorer\iexplore.exe"
Get-process | ConvertTo-HTML |
Out-File -FilePath $tmpFile -Encoding ascii -append
$iepid = Invoke-WmiMethod -Class win32_process -Name create -Argument "$iepath $tmpfile"
Wait-Process -Id $iepid.ProcessId
Remove-Item $tmpfile
In the script, the first thing I do is get a temporary file name. The GetTempFileName method creates a temporary file name in the temporary location. An example of such a file name is shown here:
C:\Users\edwilson\AppData\Local\Temp\tmp1573.tmp
I split the string at the period. This is not a reliable method for splitting file names and removing the file extension, but it does work here. For example, this will fail if a file name has two periods in the name. I have, on occasion concatenated the temporary file name with “.html” and ended up with a filename with two periods in it, which is, of course, a perfectly acceptable file name. Here I am simply exploring other ways of creating a temporary file name with an HTML file extension. Internet Explorer requires a file to have an HTML file extension, or it will not render the page properly. For example, if I stick with the .tmp file extension, the page renders as shown in the following figure.
I like to use the Join-Path cmdlet to build paths. To do this, I need to supply the path (parent path) portion and the childpath (child path) portion of the path. I am not restricted to simply path\executable in my formatting. I can, for example, include additional directories in my childpath portion of the command. In this example, I use an environmental variable to retrieve the path to the x86 program files, and then I add the remainder of the path to the Internet Explorer executable. I broke the command, and used the backtick (`) character for line continuation. I would not normally do this, but it is needed to fit it on the page. This portion of the script follows:
$iepath = Join-Path -Path ${env:ProgramFiles(x86)} `
-ChildPath "internet explorer\iexplore.exe"
Note When copying code from the Hey, Scripting Guy! Blog, it is possible that extraneous characters appear after the backtick character, and that invisible character will actually break the code. The fix is to paste the code in the Windows PowerShell ISE and use the backspace character to erase the invisible characters that follow the backtick character.
I now create a HTML page by using the Convertto-HTML cmdlet. I write the HTML code to a file by using the Out-File cmdlet. This code is shown here:
Get-process | ConvertTo-HTML |
Out-File -FilePath $tmpFile -Encoding ascii -append
I use the Invoke-WMIMethod cmdlet to call the create method from the Win32_Process WMI class. I use this methodology because it returns the process ID of the newly created instance of Internet Explorer. I store the process ID in a variable called $iepid. This code is shown here:
$iepid = Invoke-WmiMethod -Class win32_process -Name create -Argument "$iepath $tmpfile"
I now halt execution of the script until the newly created instance of Internet Explorer goes away. After it goes away, I call the Remove-Item cmdlet, and I delete the temporary HTML file, as shown here:
Wait-Process -Id $iepid.ProcessId
Remove-Item $tmpfile
The HTML file that the script creates and displays is shown in the following figure.
SK that is all there is to using the Wait-Process cmdlet to pause script execution and wait for a process to end. I invite you to join me tomorrow for more cool Windows PowerShell stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy