Starting remote Windows services with ServiceController and impersonation

David Brainer picture David Brainer · Dec 20, 2011 · Viewed 11.7k times · Source

I have a .NET MVC3 application that needs to be able to turn a remote service on and off. In order to do this I am impersonating a specific user account via WindowsIdentity.Impersonate(). To test the user's permissions I can log in as the user and execute sc.exe \\[server] start [service] from the command prompt. I also know that the impersonate command is working as expected because the application runs anonymously and therefore cannot control services on my local machine (.) without impersonation, but can control local services with impersonation. However, when I put it together and attempt to start the remote service rather than local service I always get the error "Cannot open [service] service on computer '[server]'"

Has anyone ever experienced a similar issue? I was expecting it to be a server configuration rather than .NET issue until I realized that sc.exe works without issue. Here is an abbreviated version of the class that I am using:

public class Service
{
    public string Name;
    public bool Running;
    private ServiceController serviceController;

    public Service(string name, string host)
    {
        Name = name;

        serviceController = new ServiceController(Name, host);
        Running = serviceController.Status == ServiceControllerStatus.Running;
    }

    public bool StartService()
    {
        ServiceControllerPermission scp = new ServiceControllerPermission(ServiceControllerPermissionAccess.Control, serviceController.MachineName, Name);
        scp.Assert();

        serviceController.Start();
        serviceController.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 5));
        serviceController.Refresh();

        Running = serviceController.Status == ServiceControllerStatus.Running;

        return Running;
    }
}

One additional note: If instead of the server I point the application at another Windows 7 PC on the domain and change the impersonation credentials to those of the owner of that PC, I am actually able to remotely control their services without issue.

Per request, I am adding the impersonation code here. It is a little longer so bear with me:

public class Impersonate
{
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;

    WindowsImpersonationContext impersonationContext;

    [DllImport("advapi32.dll")]
    public static extern int LogonUserA(String lpszUserName,
        String lpszDomain,
        String lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int DuplicateToken(IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern bool CloseHandle(IntPtr handle);

    public bool impersonateValidUser(String userName, String domain, String password)
    {
        WindowsIdentity tempWindowsIdentity;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        if (RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();
                    if (impersonationContext != null)
                    {
                        CloseHandle(token);
                        CloseHandle(tokenDuplicate);
                        return true;
                    }
                }
            }
        }
        if (token != IntPtr.Zero)
            CloseHandle(token);
        if (tokenDuplicate != IntPtr.Zero)
            CloseHandle(tokenDuplicate);
        return false;
    }

    public void undoImpersonation()
    {
        impersonationContext.Undo();
    }
}

I call this code just before attempting to start or stop the service:

Service s = new Service(ServiceName, MachineName);

if (Impersonation.impersonateValidUser(Username, Domain, Password))
{
    if (s.Running)
        s.StopService();
    else
        s.StartService();

    Impersonation.undoImpersonation();
}

It may be worth noting that I can list the services and get the status of an individual service (as I do here) just fine - it is only when I go to start or stop the service that I run into trouble.

Answer

Fenli picture Fenli · Sep 19, 2013

Maybe I've found the answer:

In the impersonation method LogonUserA

use LOGON32_LOGON_SERVICE (= 5) instead of LOGON32_LOGON_INTERACTIVE.

Make sure that the user that you're impersonating is the same user that runs the service.

In my case, the user is included in Local Policies --> Start session as service. I have not tested with an user not included in that local policy.

Hope it helps!