Stopping/Starting a remote Windows service and waiting for it to open/close

Solution 1:

I created a set of batch scripts that use sc.exe to do just this. They are attached below. To run these scripts, you should be a user with administration rights on the target machine and running this from a computer that is a member of the same domain. It's possible to set it up to be able to run from outside of the domain (like from a VPN) but there are a lot of layers of security to work through involving firewalls, DCOM and security credentials.

One of these days, I'm going to figure out the PowerShell equivalent, which should be much easier.

safeServiceStart.bat

@echo off
:: This script originally authored by Eric Falsken

IF [%1]==[] GOTO usage
IF [%2]==[] GOTO usage

ping -n 1 %1 | FIND "TTL=" >NUL
IF errorlevel 1 GOTO SystemOffline
SC \\%1 query %2 | FIND "STATE" >NUL
IF errorlevel 1 GOTO SystemOffline

:ResolveInitialState
SC \\%1 query %2 | FIND "STATE" | FIND "STOPPED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StartService
SC \\%1 query %2 | FIND "STATE" | FIND "RUNNING" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StartedService
SC \\%1 query %2 | FIND "STATE" | FIND "PAUSED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline
echo Service State is changing, waiting for service to resolve its state before making changes
sc \\%1 query %2 | Find "STATE"
timeout /t 2 /nobreak >NUL
GOTO ResolveInitialState

:StartService
echo Starting %2 on \\%1
sc \\%1 start %2 >NUL

GOTO StartingService
:StartingServiceDelay
echo Waiting for %2 to start
timeout /t 2 /nobreak >NUL
:StartingService
SC \\%1 query %2 | FIND "STATE" | FIND "RUNNING" >NUL
IF errorlevel 1 GOTO StartingServiceDelay

:StartedService
echo %2 on \\%1 is started
GOTO:eof

:SystemOffline
echo Server \\%1 is not accessible or is offline
GOTO:eof

:usage
echo %0 [system name] [service name]
echo Example: %0 server1 MyService
echo.
GOTO:eof

safeServiceStop.bat

@echo off
:: This script originally authored by Eric Falsken

IF [%1]==[] GOTO usage
IF [%2]==[] GOTO usage

ping -n 1 %1 | FIND "TTL=" >NUL
IF errorlevel 1 GOTO SystemOffline
SC \\%1 query %2 | FIND "STATE" >NUL
IF errorlevel 1 GOTO SystemOffline

:ResolveInitialState
SC \\%1 query %2 | FIND "STATE" | FIND "RUNNING" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService
SC \\%1 query %2 | FIND "STATE" | FIND "STOPPED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StopedService
SC \\%1 query %2 | FIND "STATE" | FIND "PAUSED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline
echo Service State is changing, waiting for service to resolve its state before making changes
sc \\%1 query %2 | Find "STATE"
timeout /t 2 /nobreak >NUL
GOTO ResolveInitialState

:StopService
echo Stopping %2 on \\%1
sc \\%1 stop %2 %3 >NUL

GOTO StopingService
:StopingServiceDelay
echo Waiting for %2 to stop
timeout /t 2 /nobreak >NUL
:StopingService
SC \\%1 query %2 | FIND "STATE" | FIND "STOPPED" >NUL
IF errorlevel 1 GOTO StopingServiceDelay

:StopedService
echo %2 on \\%1 is stopped
GOTO:eof

:SystemOffline
echo Server \\%1 or service %2 is not accessible or is offline
GOTO:eof

:usage
echo Will cause a remote service to STOP (if not already stopped).
echo This script will waiting for the service to enter the stopped state if necessary.
echo.
echo %0 [system name] [service name] {reason}
echo Example: %0 server1 MyService
echo.
echo For reason codes, run "sc stop"
GOTO:eof

safeServiceRestart.bat

@echo off
:: This script originally authored by Eric Falsken

if [%1]==[] GOTO usage
if [%2]==[] GOTO usage

ping -n 1 %1 | FIND "TTL=" >NUL
IF errorlevel 1 GOTO SystemOffline
SC \\%1 query %2 | FIND "STATE" >NUL
IF errorlevel 1 GOTO SystemOffline

:ResolveInitialState
SC \\%1 query %2 | FIND "STATE" | FIND "RUNNING" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StopService
SC \\%1 query %2 | FIND "STATE" | FIND "STOPPED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO StartService
SC \\%1 query %2 | FIND "STATE" | FIND "PAUSED" >NUL
IF errorlevel 0 IF NOT errorlevel 1 GOTO SystemOffline
echo Service State is changing, waiting for service to resolve its state before making changes
sc \\%1 query %2 | Find "STATE"
timeout /t 2 /nobreak >NUL
GOTO ResolveInitialState

:StopService
echo Stopping %2 on \\%1
sc \\%1 stop %2 %3 >NUL

GOTO StopingService
:StopingServiceDelay
echo Waiting for %2 to stop
timeout /t 2 /nobreak >NUL
:StopingService
SC \\%1 query %2 | FIND "STATE" | FIND "STOPPED" >NUL
IF errorlevel 1 GOTO StopingServiceDelay

:StopedService
echo %2 on \\%1 is stopped
GOTO StartService

:StartService
echo Starting %2 on \\%1
sc \\%1 start %2 >NUL

GOTO StartingService
:StartingServiceDelay
echo Waiting for %2 to start
timeout /t 2 /nobreak >NUL
:StartingService
SC \\%1 query %2 | FIND "STATE" | FIND "RUNNING" >NUL
IF errorlevel 1 GOTO StartingServiceDelay

:StartedService
echo %2 on \\%1 is started
GOTO:eof

:SystemOffline
echo Server \\%1 or service %2 is not accessible or is offline
GOTO:eof

:usage
echo Will restart a remote service, waiting for the service to stop/start (if necessary)
echo.
echo %0 [system name] [service name] {reason}
echo Example: %0 server1 MyService
echo.
echo For reason codes, run "sc stop"
GOTO:eof

Solution 2:

What about powershell and WaitForStatus? Eg, the script below would restart SQL Server on a remote machine:

$computer = "COMPUTER_NAME"
$me = new-object -typename System.Management.Automation.PSCredential -argumentlist "DOMAIN\user", (convertto-securestring "password" -asplaintext -force)
$restartSqlServer = { 
    $sqlServer = get-service mssqlserver
    $waitInterval = new-timespan -seconds 5
    if (-not ($sqlServer.Status -eq "Stopped")) {
        $sqlServer.Stop()
        $sqlServer.WaitForStatus('Stopped', $waitInterval) 
    }
    $sqlServer.Start()
    $sqlServer.WaitForStatus('Running', $waitInterval) 
}     
icm -ComputerName $computer -ScriptBlock $restartSqlServer -Credential $me 

Solution 3:

I've never actually seen something that does this specifically but it would be quite easy to knock such a utility out in C\C#\VB or any other language that gives easy access to the Service API. Here's a sample of something in C#.

using System;
using System.ComponentModel;
using System.ServiceProcess;

namespace SCSync
{
    class Program
    {
        private const int ERROR_SUCCESS = 0;

        private const int ERROR_INVALID_COMMAND_LINE = 1;
        private const int ERROR_NO_ACCESS = 2;
        private const int ERROR_COMMAND_TIMEOUT = 3;
        private const int ERROR_NO_SERVICE = 4;
        private const int ERROR_NO_SERVER = 5;
        private const int ERROR_INVALID_STATE = 6;
        private const int ERROR_UNSPECIFIED = 7;

        static int Main(string[] args)
        {

            if (args.Length < 2 || args.Length > 4)
            {
                ShowUsage();
                return ERROR_INVALID_COMMAND_LINE;
            }

            string serviceName = args[0];
            string command = args[1].ToUpper();
            string serverName = ".";
            string timeoutString = "30";
            int timeout;

            if (args.Length > 2)
            {
                if (args[2].StartsWith(@"\\"))
                {
                    serverName = args[2].Substring(2);
                    if (args.Length > 3)
                    {
                        timeoutString = args[3];
                    }
                }
                else
                {
                    timeoutString = args[2];
                }
            }

            if (!int.TryParse(timeoutString, out timeout))
            {
                Console.WriteLine("Invalid timeout value.\n");
                ShowUsage();
                return ERROR_INVALID_COMMAND_LINE;
            }

            try
            {
                ServiceController sc = new ServiceController(serviceName, serverName);
                switch (command)
                {
                    case "START":
                        sc.Start();
                        sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 0, timeout));
                        break;
                    case "STOP":
                        sc.Stop();
                        sc.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 0, timeout));
                        break;
                    case "PAUSE":
                        sc.Pause();
                        sc.WaitForStatus(ServiceControllerStatus.Paused, new TimeSpan(0, 0, 0, timeout));
                        break;
                    case "CONTINUE":
                        sc.Continue();
                        sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 0, timeout));
                        break;
                    default:
                        Console.WriteLine("Invalid command value.\n");
                        ShowUsage();
                        return ERROR_INVALID_COMMAND_LINE;
                }
            }
            catch (System.ServiceProcess.TimeoutException)
            {
                Console.WriteLine("Operation timed out.\n");
                return ERROR_COMMAND_TIMEOUT;
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine("You are not authorized to perform this action.\n");
                return ERROR_NO_ACCESS;
            }
            catch (InvalidOperationException opEx)
            {
                Win32Exception winEx = opEx.InnerException as Win32Exception;
                if (winEx != null)
                {
                    switch (winEx.NativeErrorCode)
                    {
                        case 5: //ERROR_ACCESS_DENIED
                            Console.WriteLine("You are not authorized to perform this action.\n");
                            return ERROR_NO_ACCESS;
                        case 1722: //RPC_S_SERVER_UNAVAILABLE
                            Console.WriteLine("The server is unavailable or does not exist.\n");
                            return ERROR_NO_SERVER;
                        case 1060: //ERROR_SERVICE_DOES_NOT_EXIST
                            Console.WriteLine("The service does not exist.\n");
                            return ERROR_NO_SERVICE;
                        case 1056: //ERROR_SERVICE_ALREADY_RUNNING
                            Console.WriteLine("The service is already running.\n");
                            return ERROR_INVALID_STATE;
                        case 1062: //ERROR_SERVICE_NOT_ACTIVE
                            Console.WriteLine("The service is not running.\n");
                            return ERROR_INVALID_STATE;
                        default:
                            break;
                    }
                }
                Console.WriteLine(opEx.ToString());
                return ERROR_UNSPECIFIED;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                return ERROR_UNSPECIFIED;
            }

            return ERROR_SUCCESS;
        }

        private static void ShowUsage()
        {
            Console.WriteLine("SCSync usage:\n");
            Console.WriteLine("SCSync.exe service command <server> <timeout>\n");
            Console.WriteLine("    service   The name of the service upon which the command will act. (Required)");
            Console.WriteLine("    command   The command to execute - one of: start|stop|pause|continue. (Required)");
            Console.WriteLine("    server    The name of the server on which the target service runs. This must start with \\. (Optional)");
            Console.WriteLine("    timeout   The timeout period in seconds in which the command should finish. The default is 30 seconds. (Optional)");
            Console.WriteLine("\n");
        }
    }
}

The WaitForStatus is just a polling loop and could be easily replaced in any other language. The rest is just OpenService and ControlService.

Solution 4:

Eric Falsken's solution works perfectly. +1.

But I'd like to add that the timeout command would sometimes fail with error: "Input redirection is not supported, exiting the process immediately"

To fix this I've had to replace the timeout command:

timeout /t 2 /nobreak >NUL

with the following:

ping -n 2 127.0.0.1  1>NUL