Use PowerShell to Pause a Script While Another Process Exits

Use PowerShell to Pause a Script While Another Process Exits

  • Comments 5
  • Likes

Summary: Learn how to use Windows PowerShell to pause a script and wait for another process to exit before continuing.

Hey, Scripting Guy! QuestionHey, 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

 

Hey, Scripting Guy! AnswerHello 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:

  1. Create a temporary file name.
  2. Change the file extension to html.
  3. Pipe data to the ConvertTo-Html cmdlet.
  4. Pipe the data from ConvertTo-Html to Out-File, and create an HTML document.
  5. Use Invoke-WMIMethod to open the file and store the return values.
  6. Use the Wait-Process cmdlet to pause execution of the script and wait for the process to close.
  7. 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.

Image of how page renders with .tmp file extension

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.

Image of HTML file the script creates and displays

 

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

 

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • <p>Hi Ed,</p> <p>travelling First Class (he was upgraded) looks like the importance of the HSG blog has increased again!</p> <p>Your statement &quot;I am forcing myself to stay away from WMI for now&quot; makes me laugh, if I look at this blog article, noticing that Invoke-WMImethod appeared as soon as I can count to three :-)))</p> <p>Well, back to seiousity, I agree that we can control a lot of the operating functionality with powershell if we are able to stay with PS during the whole process, which is something that might not be the case with SK&#39;s problem. Its getting complicated if he has started his application outside powershell and waits for some parts of it to shutdown. We probably have not created the root applictaion process in PS and don&#39;t have the PID handy ( somebody has to llok it up, if we can&#39;t use the process name ). If the application creates its own processes during the run, it might not be easy to follow the sequence of new processe spawned and terminated, so we could most likely only wait for the end of the complete application if we don&#39;t start each part of it seperately.</p> <p>Anyway, your examples show what we can do in powershell to control the workflow of processes that we have created with PS. That&#39;s good enough as long we don&#39;t have to control multiple applications in parallel or have to do further computations in the meanwhile. That&#39;s when we have to consider background jobs and async events that may help us to stay tuned with the workflow in progress. </p> <p>I remember some former blog articles here that did point in this direction.</p> <p>But that topic won&#39;t be everybody&#39;s darling ... for sure! ... That&#39;s powershell Ninja stuff :-)</p> <p>Klaus,</p>

  • <p>@Klaus Schulte the flight attendent said, &quot;Its the Scripting Guy&quot; and moved me to first (not really, but it sounds good). Also, you caught me, I did not make it through an entire article without talking about WMI :-) Yes, this example, is an edge case to be sure. But the cool thing is there is the Wait-Process cmdlet, and it can be used easily to solve some real world types of problems. It is also one of those cmdlets that &quot;no one talks about&quot; and I wanted to help correct that. Thank you for your comments Klaus. I look for them every morning :-)</p>

  • <p>If you are depending on a process spawned elsewhere, WMI works. &nbsp;But if it&#39;s a process spawned by your own script, why won&#39;t Start-Process work? &nbsp;It gives you the handle and you can -wait if you need it to complete before starting other work.</p> <p>What&#39;s more, you can get the CPU usage of the spawned process and at least watchdog it and restart it if it stalls. &nbsp;I&#39;ve used that to control a media encoder that needed to run 24/7 and liked to hang from time to time.</p>

  • <p>@Why not Start-Process; By default the Start-Process cmdlet does not return anything. In order to get anything from the Start-Process cmdlet, it is necessary to use the Start-Process -Passthru option. When -passthru is used, it will return a diagnostics.process object, which does indeed include the Process ID. I was just sort of making a joke about not using WMI, and I wanted to reinforce using the Invoke-WMIMethod. As always, there are many many different ways of doing things in powershell. Thank you for your comment, and for helping to mention a different way of doing things.</p>

  • <p>Hello Ed</p> <p>A quick question. </p> <p>Im having a problem with Enter-PSSession that the connection takes a while before it comes trough and the script &quot;continues&quot; so that it does not pick up the next few command lines. I have tried with start-sleep and wait-event, but no luck.</p> <p>I use this to login to the server.</p> <p> Get-PSSession</p> <p> $username = &quot;&quot;</p> <p> $password = &quot;&quot;</p> <p> $secstr = New-Object -TypeName System.Security.SecureString</p> <p> $password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}</p> <p> $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr</p> <p> $no91gpotestsrv = New-PSSession -Name no91-gpotestsrv -ComputerName no91-gpotestsrv -Credential $cred</p> <p> Enter-PSSession $no91gpotestsrv</p> <p>(here it does not stop for the Enter-PSSession to stop but just continues, what can i do here?)</p> <p>cd ..\..\..\bin</p> <p>#Mapping av network drive hvor filer ligger</p> <p>{</p> <p> &nbsp; &nbsp; $net = new-object -ComObject WScript.Network</p> <p> &nbsp; &nbsp; $net.MapNetworkDrive(&quot;r:&quot;, &quot;\\osl9fil01\release&quot;, $false, &quot;username&quot;, &quot;password&quot;)</p> <p>}</p> <p>#Kopiering av filer og rename</p> <p>$return_fileversion1 = (Get-Command &quot;c:\infront\bin\streamingserver_x64.exe&quot;).FileVersionInfo.Fileversion</p> <p>$return_fileversion2 = (Get-Command &quot;r:\Servers\StreamingServer\Latest\x64\streamingserver_x64.exe&quot;).FileVersionInfo.Fileversion</p> <p>IF($return_fileversion2 -gt $return_fileversion1)</p> <p> &nbsp; &nbsp;{</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;$currentdate = get-date -Format yyyyMMdd</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;rename-item c:\temp\bin\server_x64.exe -newname (&quot;server_x64.exe.&quot; + $currentdate)</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;Copy-item r:\Servers\buildServer\Latest\x64\server_x64.exe c:\temp\bin\</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;$text = &quot;New file copied in with Fileversion + $return_fileversion2&quot;</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;Write-host &quot;$text&quot;</p> <p> &nbsp; &nbsp;}</p> <p>ELSEif($return_fileversion2 -eq $return_fileversion1)</p> <p> &nbsp; &nbsp;{</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;Write-host &quot;Destination file is equal as source file&quot;</p> <p> &nbsp; &nbsp;}</p> <p>ELSEif($return_fileversion1 -gt $return_fileversion2)</p> <p> &nbsp; &nbsp;{</p> <p> &nbsp; &nbsp; &nbsp; &nbsp;Write-host &quot;Destination file is greater then source file&quot;</p> <p>}</p>