Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Starting remote Windows services with ServiceController and impersonation

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.

like image 671
David Brainer Avatar asked Oct 09 '22 04:10

David Brainer


1 Answers

Well, that was correct if the user you want to impersonate is a machine administrator. If it is not, then you have to give the user the particular rights to start the service.

As I am not a network admin, I didn't know how to make it, but I have found this link that explains how to do it with the subinacl tool:

  • http://www.waynezim.com/2010/02/how-to-set-permission-on-a-service-using-subinacl/

To download

  • http://www.microsoft.com/en-us/download/details.aspx?id=23510

And finally, the command line would be:

  • subinacl /service SERVICENAME /grant=DOMAINNAME\USERNAME Users=TO

Users=TO grants start/stop permissions to the user

In this case you have to impersonate the user with the INTERACTIVE mode and not with the SERVICE mode

like image 191
Fenli Avatar answered Oct 16 '22 15:10

Fenli