I have a .NET MVC3 application that needs to enable and disable a remote service. To do this, I impersonate a specific user account through WindowsIdentity.Impersonate (). To verify user rights, I can log in as a user and run sc.exe \\[server] start [service] from the command line. I also know that the impersonate command works as expected because the application runs anonymously and therefore cannot manage services on my local machine ( . ) Without impersonation, but can manage local services with impersonation. However, when I get together and try to start a remote service, not a local service, I always get the error message "Unable to open the [service] on the computer" [server] "
Has anyone encountered a similar problem? I expected this to be a server configuration, not a .NET problem, until I understand that sc.exe works without problems. The following is an abridged version of the class I'm 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 more note. If instead of the server I point the application to another PC running Windows 7 in the domain and change the impersonation credentials to the name of the owner of this PC, I can really remotely control my services without problems.
Upon request, I add an impersonation code. This 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 before trying 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 a separate service (like me here) just fine - only when I go to start or stop the service in which I encountered a problem.