次の方法で共有


PowerShell code to wait for a background process to finish before continuing on with a script

While this code was originally developed for an Exchange script, you can use it for any PowerShell script where you want to make sure some background process/service is finished before proceeding on with the rest of your script.

I recently ran into a problem where a PowerShell scripted install/upgrade of Exchange 2016 server where the .NET Optimization Framework service was still running in the background, and it subsequently blocked the Exchange install/upgrade due to needed files being held open. Specifically, the error returned by the Exchange setup.exe was as follows:

 Performing Microsoft Exchange Server Prerequisite Check

    Configuring Prerequisites                                                                         COMPLETED
    Prerequisite Analysis                                                                             FAILED
     Setup can't continue with the upgrade because the mscorsvw (13656) has open files. Close the process, and then restart Setup.
     For more information, visit: technet.microsoft.com/library(EXCHG.150)/ms.exch.setupreadiness.ProcessNeedsToBeClosedOnUpgrade.aspx

NOTE: The process number noted for the mscorsvw process is irrelevant as it will change each time it is run. Also you can change the process name to be that of any process you want to monitor with a countdown timer.

One option was to keep re-running the PowerShell script trying to perform the Exchange install/upgrade over and over again until the .NET Optimization Service (AKA mscorsvw) completed, but that got old really quick as it takes the Exchange setup process a while to generate that error each time. 😊 Another and better option was to add a check in the PowerShell script to make sure the service had ended before proceeding on with executing the Exchange install/upgrade.

To that point I wrote the following code to place right before the call to the Exchange SETUP.EXE:

 
# Check to see if the .NET Optimization Service is still running as it can block the Exchange initialization process.
If (Get-Process mscorsvw -ErrorAction SilentlyContinue) {
    # It is so define the number of minutes the countdown timer should allow it to gracefully finish before giving up and exiting the script.
    $CountDownTimer = 10
    Write-Host " "
    Write-Warning "The .NET Optimization Service is still running. Waiting up to $CountDownTimer minutes for it to gracefully shut down so the script can proceed."
    $NETOptimizationRunning = $True
    $StartTime = Get-Date
    $EndTime = $StartTime.AddMinutes($CountDownTimer)
    $TimeSpan = New-TimeSpan (Get-Date) $EndTime
}
# While the process is still running, or there is time remaining in the timespan countdown, loop through writing a progress bar and check to see if the process is still running.
While ($NETOptimizationRunning -and ($TimeSpan.TotalSeconds -gt 0)) {
    Write-Progress -Activity "Waiting for the .NET Optimization Service to shut down." -Status "$CountDownTimer minute countdown before the script gives up and exits." `
        -SecondsRemaining $TimeSpan.TotalSeconds
    Start-Sleep -Seconds 1
    # Recheck to see if the process is still running. It will return a $Null/$False value if it isn't for evaluation at the beginning of the loop.
    $NETOptimizationRunning = Get-Process mscorsvw -ErrorAction SilentlyContinue
    # Recalculate the new timespan at the end of the loop for evaluation at the beginning of the loop.
    $TimeSpan = New-TimeSpan (Get-Date) $EndTime
}
# Close out the progress bar cleanly if it was still running. 
Write-Progress -Activity "Waiting for .NET Optimization Service to shut down." -Completed
# If the .NET Optimization Service is still running after the end count time timer exit the script. Otherwise note the time it took the services to shut down if a timer was started.
If ($NETOptimizationRunning) {
    Write-Host "The .NET Optimization Service is still running after the $CountDownTimer time expired, and it can interfere with the Exchange initialization process. Please re-run this script after some time has elapsed."
    EXIT
} ElseIf ($CountDownTimer) {
    # Calculate the completed time by taking the timespan of the start time versus the end time, where the end time has the seconds the loop took to run subtracted from it.
    $CompletedTime = (New-TimeSpan $StartTime $Endtime.AddSeconds(-$TimeSpan.TotalSeconds)).ToString("mm':'ss")
    Write-Host "The .NET Optimization Service finished after $CompletedTime. Continuing with script execution."
    Write-Host " "
}
Write-Host "Moving on with the script."

This code establishes a maximum of 10 minutes (which you can change via the $CountDownTimer variable) to allow the .NET Optimization Service to complete before it gives up and exits the script. The hope is that 10 minutes will be more than sufficient for the process to finish gracefully, but if it’s still not finished after 10 minutes then its better to exit the script versus trying to execute SETUP.EXE (which will ultimately fail with the error noted above).

While the countdown is running, a progress bar is shown indicating how much time is left in the countdown timer. If the process finishes before the 10 minute window is up, the script will note how long the timer had to wait for the process to finish. From there the script will immediately continue on its merry way to the call to the SETUP.EXE (or whatever else you want to do with your script after this countdown timer).

NOTE: If the .NET Optimization Service was already completed prior to reaching this code in the PowerShell script, nothing is displayed to the user (no reason to cause confusion/panic), and again the script continues on its merry way.

I hope this script is useful to anyone who needs to ensure a background process/service is complete before moving on with the rest of the script (such as with an Exchange install/upgrade script), and if so please give it a rating and link it to whatever social media platform you feel is appropriate. Also feel free to leave any questions/comments in the section below.

Thanks,

Dan Sheehan
Senior Premier Field Engineer

Comments

  • Anonymous
    February 18, 2018
    Thanks Dan, very useful script!
    • Anonymous
      February 21, 2018
      Thank you very much for the feedback. If you don't mind please throw a rating on the post. :)
  • Anonymous
    February 26, 2018
    Thank you for this. I already incorporated it into an application deployment script and will likely use it often in the future.
    • Anonymous
      February 27, 2018
      The comment has been removed
      • Anonymous
        March 01, 2018
        I tend to skip the use of Begin, Process, and End if I am not going to use Begin or End, but there is nothing wrong with using them for completeness.As for turning things into functions, I tend to avoid doing that unless I am going to make it part of a module or call it multiple times in a script. It looks like you intend to use it in multiple scenarios, so a function is a good fit in that case.Thank you for including a link back to this blog, it's nice to know credit included when code is recycled. :)
  • Anonymous
    February 27, 2018
    Always used this for processes,but yours has nice fancy time stamps!Function Running($proc){ $Now = "Exists" While ($Now -eq "Exists") { If(Get-Process $proc -ErrorAction silentlycontinue) { $Now = "Exists" Write-Host "INFO : $proc is running, waiting 15 seconds" Sleep -Seconds 15 } Else { $Now = "Nope" Write-Host "INFO : $proc is finished" } }}
    • Anonymous
      March 01, 2018
      The comment has been removed
  • Anonymous
    March 08, 2018
    The comment has been removed