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: http://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 (8)

  1. Dave Rendón says:

    Thanks Dan, very useful script!

    1. Thank you very much for the feedback. If you don’t mind please throw a rating on the post. 🙂

  2. Thank you for this. I already incorporated it into an application deployment script and will likely use it often in the future.

    1. Dan,
      We generalized the code a bit and turned it into a function. It accepts parameters for Process and Wait time (minutes). Feel free to comment.

      function WaitFor-Process {
      [CmdletBinding()]
      param(
      [Parameter(Mandatory=$True)]
      [ValidateNotNullOrEmpty()]
      [string[]]$Process,

      [Alias(‘Minutes’)]
      [int]$CountDownTimer = 10

      )

      BEGIN {
      }
      PROCESS {
      # This Gem is originally from here: https://blogs.technet.microsoft.com/dsheehan/2018/02/18/powershell-code-to-wait-for-a-background-process-to-finish-before-continuing-on-with-a-script/ but modified slightly
      # Check to see if $Process is still running before moving ahead.
      If (Get-Process $Process -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.
      Write-Host “”
      Write-Warning “$Process is still running. Waiting up to $CountDownTimer minutes for it to gracefully shut down so the script can proceed.”
      $ProcessRunning = $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 ($ProcessRunning -and ($TimeSpan.TotalSeconds -gt 0)) {
      Write-Progress -Activity “Waiting for $Process 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.
      $ProcessRunning = Get-Process $Process -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 $Process to shut down.” -Completed
      # If $Process 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 ($ProcessRunning) {
      Write-Host “$Process is still running after the $CountDownTimer time expired. ”
      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 “$Process finished after $CompletedTime. Continuing with script execution.”
      Write-Host “”
      }
      Write-Host “Moving on with the script.”
      }
      END {
      }
      }

      #example processes to wait for
      WaitFor-Process -Process notepad,mspaint

      1. 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. 🙂

  3. alan davis says:

    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”
    }
    }
    }

    1. Yeah – I wanted to put a cap on the script’s execute time so the person wasn’t sitting there forever waiting for it to finish, and I like to include count down timers for the impatient folks out there. 🙂 Your example is definitely more to the point.

  4. And here is the code in action from a production deployment of Exchange 2013 CU19:

    STEP 5: UPGRADING EXCHANGE SERVER 2013…
    Extracting the Exchange Installation setup files to C:\Software\Microsoft\Exchange\2013\Install\CurrentVersion.
    INSTALLING EXCHANGE 2013 – VERSION: #15.00.1365.000

    WARNING: The .NET Optimization Service is still running. Waiting up to 10 minutes for it to gracefully shut down so the
    script can proceed.
    The .NET Optimization Service finished after 01:18. Continuing with script execution.

    Microsoft Exchange Server 2013 Cumulative Update 19 Unattended Setup

    From there the setup.exe output continued as normal. So 1 minute and 18 seconds saved not trying to beat my head against the wall manually re-running the upgrade script over and over again. 🙂

Skip to main content