I have web application deployed on my local IIS 7, with application pool configured to work under built-in NETWORK SERVICE account. From this web application I need to check the status of my windows service (if it is started, stopped, etc). I've used such statements to get it:
public string GetServiceStatus(string machine, string service) { var service = new ServiceController(machine, service); service.Refresh(); return service.Status; }
The machine
is an IP address of the host in my intranet (let it be 192.168.0.7), where the windows service is running - also under built-in NETWORK SERVICE account.
Unfortunately the code gives an exception:
service.Status threw an exception of type 'System.InvalidOperationException' Cannot open MyService service on computer '192.168.0.7'. Access is denied.
Where is the problem ?
The problem is NETWORK SERVICE doesn't have sufficient rights for controlling windows services. I needed to switch to another user context to be able to control it. But I didn't want to do it for entire application. Instead I was searching for arbitrary piece of code execution under specific identity.
I've checked a lot of resources for impersonation included that shown by Malcolm Frexner. Because I'm working with Windows 7 (64bit) and also with Windows Server 2008 R2 (64bit), that I've found were not working for me. I ended up with such generic solution:
using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.Principal; namespace Thing.Namespace { public enum LogOnType { LogOn32LogOnInteractive = 2, LogOn32LogOnNetwork = 3, LogOn32LogOnBatch = 4, LogOn32LogOnService = 5, LogOn32LogOnUnlock = 7, LogOn32LogOnNetworkCleartext = 8, LogOn32LogOnNewCredentials = 9 } public enum LogOnProvider { LogOn32ProviderDefault = 0, LogOn32ProviderWinnt35 = 1, LogOn32ProviderWinnt40 = 2, LogOn32ProviderWinnt50 = 3 } public enum ImpersonationLevel { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3 } public static class IdentityBoss { private static WindowsImpersonationContext _impersonationContext; private static readonly object _locker = new object(); private static class NativeMethods { [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int LogonUser(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool RevertToSelf(); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr handle); } public static void Impersonate(Action action, string user, string domain, string password, LogOnType logOnType, LogOnProvider logOnProvider, ImpersonationLevel impersonationLevel) { try { ImpersonateValidUser(user, domain, password, logOnType, logOnProvider, impersonationLevel); action(); } finally { UndoImpersonation(); } } public static void ImpersonateHappily(Action action, string user, string domain, string password) { Impersonate(action, user, domain, password, LogOnType.LogOn32LogOnNetworkCleartext, LogOnProvider.LogOn32ProviderDefault, ImpersonationLevel.SecurityImpersonation); } public static TResult Impersonate<TResult>(Func<TResult> action, string user, string domain, string password, LogOnType logOnType, LogOnProvider logOnProvider, ImpersonationLevel impersonationLevel) { try { ImpersonateValidUser(user, domain, password, logOnType, logOnProvider, impersonationLevel); return action(); } finally { UndoImpersonation(); } } public static TResult ImpersonateHappily<TResult>(Func<TResult> action, string user, string domain, string password) { return Impersonate(action, user, domain, password, LogOnType.LogOn32LogOnNetworkCleartext, LogOnProvider.LogOn32ProviderDefault, ImpersonationLevel.SecurityImpersonation); } private static void ImpersonateValidUser(String userName, String domain, String password, LogOnType logonType, LogOnProvider logonProvider, ImpersonationLevel impersonationLevel) { lock (_locker) { var token = IntPtr.Zero; var tokenDuplicate = IntPtr.Zero; WindowsIdentity tempWindowsIdentity = null; try { if (!NativeMethods.RevertToSelf()) throw new Win32Exception(Marshal.GetLastWin32Error()); if (NativeMethods.LogonUser(userName, domain, password, (int) logonType, (int) logonProvider,ref token) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); if (NativeMethods.DuplicateToken(token, (int) impersonationLevel, ref tokenDuplicate) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); tempWindowsIdentity = new WindowsIdentity(tokenDuplicate); _impersonationContext = tempWindowsIdentity.Impersonate(); } finally { if (token != IntPtr.Zero) NativeMethods.CloseHandle(token); if (tokenDuplicate != IntPtr.Zero) NativeMethods.CloseHandle(tokenDuplicate); if (tempWindowsIdentity != null) tempWindowsIdentity.Dispose(); } } } private static void UndoImpersonation() { lock (_locker) { if (_impersonationContext != null) { _impersonationContext.Undo(); } } } } }
In addition I needed to create new user on my machine where the service is installed. User has to have permissions for controlling windows services - for that purpose it can be added to Administrators group.
Now I can start / stop my services and getting theirs current statuses in such way:
private const string user = "MyUser"; private const string domain = "."; private const string password = "MyPa$$w0rd"; public string StartService(string machine, string service) { IdentityBoss.ImpersonateHappily( () => { Controller.Instance.StartService(machine, service); }, user, domain, password ); } public string GetServiceStatus(string machine, string service) { return IdentityBoss.ImpersonateHappily( () => { return Controller.Instance.GetServiceStatus(machine, service); }, user, domain, password ); }
ImpersonateHappily
is just a function which takes parameters which are working with my operating system. Other similar solutions from the web used dwLogonType
parameter passed to win 32 api function LogonUserA
with values 2 or 9, while under my system value 8 is correct.
BTW: Impersonate
is a wrapper function which sets up the impersonation and then passes it a lambda which does the actual work. The fancy computer science term for this style of writing code is higher-order programming.