Gatewarden
Gatewarden

Reputation: 393

Powershell Wait for service to be stopped or started

I have searched both this forum and through google and can't find what I need. I have a quite large script and I'm looking for some code that will check if the service is started or stopped before proceeding to the next step.

The function it self need to loop untill it's either stopped or started (Going to have a function for Stopped and one for Started).

In total 4 services which almost have the same name, so Service Bus * can be used as a wildcard.

Upvotes: 29

Views: 92858

Answers (6)

Justin
Justin

Reputation: 1503

To add more details to my response to @Christoph

here is a script i recently created to stop services and ensure the processes are also stopped. in our case the processes were predictable. it may be necessary to do more work to get the service/processid mapping if you have multiple services running off the same executeable.

$MaxWait = 180 #seconds

$ServiceNames = "MyServiceName*"
$ProcName = 'MyServiceProcName' #for the services
    
$sw = [System.Diagnostics.Stopwatch]::StartNew() # to keep track of 
$WaitTS = (New-TimeSpan -Seconds $MaxServiceWait) #could also use a smaller interval if you want more progress updates
    
$InitialServiceState = get-service $ServiceNames | select Name,Status,StartType
    

    
write-Host "$ENV:COMPUTERNAME Stopping $ServiceNames"
    
$sw.Restart()
$Services = @()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending

#make sure the processes are actually stopped!
while (Get-Process | where Name -Match $ProcName)
{
    #if there were services still running
    if ($Services) {
        Write-Host "$ENV:COMPUTERNAME ...waiting up to $MaxServiceWait sec for $($Services.Name)"
        #wait for the service to stop
        $Services.WaitForStatus("Stopped",$WaitTS)
         
    }
    #if we've hit our maximum wait time
    if ($sw.Elapsed.TotalSeconds -gt $MaxServiceWait) {
        Write-Host "$ENV:COMPUTERNAME Waited long enough, killing processes!"
        Get-Process | where name -Match $ProcName | Stop-Process -Force
    }
    Start-Sleep -Seconds 1

    #get current service state and try and stop any that may still be running
    #its possible that another process tried to start a service while we were waiting
    $Services = @()
    $Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru  -NoWait #nowait requires powershell 5+
    $Services += Get-Service $ServiceNames | where Status -Like *Pending
}

Upvotes: 0

Christoph
Christoph

Reputation: 3642

In my Azure build/deployment pipelines I use it like this to start and stop services (after already having sent a 'Stop' command asynchronously before) and which works for all transitional states like Starting, Stopping, Pausing and Resuming (which are called StartPending, StopPending, PausePending and ContinuePending in the status enumeration ServiceControllerStatus).

# Wait for services to be stopped or stop them
$ServicesToStop | ForEach-Object {
  $MyService = Get-Service -Name $_ -ComputerName $Server;
  while ($MyService.Status.ToString().EndsWith('Pending')) {
    Start-Sleep -Seconds 5;
    $MyService.Refresh();
  };
  $MyService | Stop-Service -WarningAction:SilentlyContinue;
  $MyService.Dispose();
};

This needs a traditional powershell to function on a remote server, the cmdlet of pwsh.exe does not include parameter -ComputerName.

In my opinion no counters are needed as only transitional states cause the cmdlet to fail and they change anyway to one of the supported states in the near future (maximum 125 seconds for a Stop command).

Upvotes: 0

I had to tweak this a bit with multiple counters because this service purposely starts and stops slowly. The original script got me on the right track. I had to wait for the service to be in a completely stopped status before I could move on because I'm actually restarting that same service. You could probably remove the "sleep," but I don't mind leaving it in. You could probably remove everything and just use the $stopped variable. :)

    # change to Stopped if you want to wait for services to start
    $running = "Running" 
    $stopPending = "StopPending"
    $stopped = "Stopped"
    do 
    {
        $count1 = (Get-Service $service | ? {$_.status -eq $running}).count
        sleep -Milliseconds 600
        $count2 = (Get-Service $service | ? {$_.status -eq $stopPending}).count
        sleep -Milliseconds 600
        $count3 = (Get-Service $service | ? {$_.status -eq $stopped}).count
        sleep -Milliseconds 600
    } until ($count1 -eq 0 -and $count2 -eq 0 -and $count3 -eq 1)

Upvotes: 0

CodeFox
CodeFox

Reputation: 3502

In addition to the answer of mgarde this one liner might be useful if you just want to wait for a single service (also inspired by a post from Shay Levy):

(Get-Service SomeInterestingService).WaitForStatus('Running')

Upvotes: 38

mgarde
mgarde

Reputation: 693

I couldn't get the 'count' strategy, that Micky posted, to work, so here is how i solved it:

I created a function, that takes a searchString (this could be "Service Bus *") and the status that i expect the services should reach.

function WaitUntilServices($searchString, $status)
{
    # Get all services where DisplayName matches $searchString and loop through each of them.
    foreach($service in (Get-Service -DisplayName $searchString))
    {
        # Wait for the service to reach the $status or a maximum of 30 seconds
        $service.WaitForStatus($status, '00:00:30')
    }
}

The function can now be called with

WaitUntilServices "Service Bus *" "Stopped"

or

WaitUntilServices "Service Bus *" "Running"

If the timeout period is reached, a not so graceful exception is thrown:

Exception calling "WaitForStatus" with "2" argument(s): "Time out has expired and the operation has not been completed."

Upvotes: 58

Micky Balladelli
Micky Balladelli

Reputation: 9991

The following will loop and verify the status of the given services until the number of services with the "Running" state is equal to zero (hence they are stopped), so you can use this if you are waiting for services to Stop.

I've added a $MaxRepeat variable, which will prevent this from running for ever. It will run 20 times max as defined.

$services = "Service Bus *"
$maxRepeat = 20
$status = "Running" # change to Stopped if you want to wait for services to start

do 
{
    $count = (Get-Service $services | ? {$_.status -eq $status}).count
    $maxRepeat--
    sleep -Milliseconds 600
} until ($count -eq 0 -or $maxRepeat -eq 0)

Upvotes: 17

Related Questions