Stopping Services Remotely and Recursively (But Without Recursion)

This one is actually fun because it reminded me of my Comp Sci. 101 days (the actual course number was 53A, but the point is the same: basic algorithms.)  Here, I have an n-deep tree that I must consume leaf-first.  Specifically, I want to stop a service, but first I must stop the ones that are depending on it.  And to stop those, first I must stop any grandchildren services.

Now, services being a rather finite set, we can be reasonably sure they'll never go more than, say, five levels deep, and that's more a worst-case guess than based on anything I've seen.

Why is this important? 

Recursion.  That's when a function (or method, or program, etc.) calls itself, and possibly calls itself again, and so forth.  This blog is intended for the neophyte-to-moderate scripter, not a hardcore pinvoke() expert, so  here's the Comp Sci. 101 speech on recursion:

Don't.  Recursion is a pretty trick, but it's easy to have it run away into not just an endless loop, but an endless loop that consumes resources each time it's called.

Speaking of resources, PowerShell is not the speediest or slimmest of languages.  Recursion with PowerShell is just a bad idea.

However, this sort of tree-walking is what recursion is intended to do.  In fact, until the Comp Sci. student gets to graphics, tree-walking is probably the only use for recursion there is.

Here, recursion is most likely usable because the number of nodes is small.

However, for the reason above, and as a challenge, I wanted to do this without recursion.


At the most basic, the computer is a stack (and queue) oriented machine.  It doesn't perform recursion. It just tosses another jump-to-function call on its queue, saves the current state, then picks up execution at the newest item on the queue, the child instance resulting from the recursive call.

Here, the only requirement is that all dependent process are terminated at some point prior to the current service being stopped.  This last part is important - it means we can stop the current service's dependent services (assuming leaf nodes, not just more branches), stop some other services, then stop the current service.

We can use a simple [Array] for this. 

The first element is the service being stopped.

We use Get-Service [-ComputerName <computername] -Name <ServiceName> -DependentServices to get a list of the dependent services.

Each of these is added to the [Array].

We then go thorough each added element of the [Array] and get its dependent services.  Each of those gets appended to the [Array] if it is not already present.

After we've added all dependent services for all services named in the [Array], we reverse it, then proceed with stopping services.

Because of how we ordered the services being enqueued in the [Array], by listing all the services with dependent services (parent nodes) first, then the actual dependent services (child nodes), we can now be sure that, with the reversed [Array], we are stopping the dependent services before stopping the service on which they depend.

function Stop-RemoteService {
     param (
         [string]$ComputerName = $null,
         [string]$Name         = $null,
         [switch]$WhatIf
     );
    
     $ErrorActionPreference = 'SilentlyContinue';

     if (!$ComputerName) { Write-Warning "-ComputerName not specified.  Required."; return; }
     if (!$Name) { Write-Warning "-Name (of service) not specified.  Required."; return; }
    
     $services = @($Name);
     $i = $j = 0;
    
     while ($services.Count -ne $j) {
         $j = $services.Count;
        
         for (;$i -lt $services.Count; $i++) {
             $service = $services[$i];            
             Get-Service -ComputerName $ComputerName -Name $service -DependentServices | % {
                 $serviceName = $_.Name;
                 if ([array]::IndexOf($services, $serviceName) -eq -1) { 
                     $services += $serviceName; 
                 }
             }
         }
     }

     $return = $true;
    
     [array]::Reverse($services);
     $services | % {
         $msg = "Stopping service '$_' on computer '$ComputerName'";
         if ($WhatIf) {
             Write-Host "What if: $msg";
         } else {
             Set-Service -ComputerName $ComputerName -Name $_ -Status Stopped;
             if ((Get-Service -ComputerName $ComputerName -Name $_).Status -ne 'Stopped') {
                 Write-Warning "Service '$_' failed to change state to 'Stopped' on computer '$ComputerName'";
                 $return = $false;
             }
         }
     }
    
     $return
}

function Start-RemoteService {
     param (
         [string]$ComputerName = $null,
         [string]$Name         = $null,
         [switch]$WhatIf
     );
    
     $ErrorActionPreference = 'SilentlyContinue';

     if (!$ComputerName) { Write-Warning "-ComputerName not specified.  Required."; return; }
     if (!$Name) { Write-Warning "-Name (of service) not specified.  Required."; return; }
    
     $services = @($Name);
     $i = $j = 0;
    
     while ($services.Count -ne $j) {
         $j = $services.Count;
        
         for (;$i -lt $services.Count; $i++) {
             $service = $services[$i];            
             Get-Service -ComputerName $ComputerName -Name $service -DependentServices | % {
                 $serviceName = $_.Name;
                 if ([array]::IndexOf($services, $serviceName) -eq -1) { 
                     $services += $serviceName; 
                 }
             }
         }
     }

     $return = $true;
    
     [array]::Reverse($services);
     $services | % {
         $msg = "starting service '$_' on computer '$ComputerName'";
         if ($WhatIf) {
             Write-Host "What if: $msg";
         } else {
             Set-Service -ComputerName $ComputerName -Name $_ -Status Running;
             if ((Get-Service -ComputerName $ComputerName -Name $_).Status -ne 'Running') {
                 Write-Warning "Service '$_' failed to change state to 'Running' on computer '$ComputerName'";
                 $return = $false;
             }
         }
     }
    
     $return
}