I'm trying to spin up a process on a remote machine from a website. When a user presses a particular button on the website, I need to spin up PowerShell on the remote machine.
I'm using System.Management from C# to connect to the remote computer and create a Win32_Process object. It all works fine between two VMs when I spin up the website under my own domain account in Visual Studio and click the button. I'm in the admin group on both VMs and I can see the script dutifully run on the remote VM.
I've deployed the website to a system test environment and the website is now running under a service account that is not an admin on either the web server or the remote box where my test PowerShell script is located.
I've given the account 开发者_运维技巧running the app pool associated with the website the following privileges on the remote VM: - full permissions on ROOT\CIMV2 in WMI and all sub-namespaces - full DCOM permissions
There is no firewall running on the remote VM.
I've basically followed the following article:
http://msdn.microsoft.com/en-us/library/aa393266.aspx
I've also tried adding the account running the website to the admin group on both VMs, but to no avail. When that doesn't work, I don't know where to go next!
Does anyone have any experience of this sort of problem?
Many thanks
Chris
Try using impersonation to connect to the remote computer with admin priveleges. Here is a class I created for programmatic impersonation:
using System;
using System.Security.Principal;
using System.Diagnostics;
using System.Runtime.InteropServices;
/// <summary>
/// Leverages the Windows API (advapi32.dll) to programmatically impersonate a user.
/// </summary>
public class ImpersonationContext : IDisposable
{
#region constants
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
#endregion
#region global variables
private WindowsImpersonationContext impersonationContext;
private bool impersonating;
#endregion
#region unmanaged code
[DllImport("advapi32.dll")]
private 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)]
private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
#endregion
#region constructors
public ImpersonationContext()
{
impersonating = false;
}
/// <summary>
/// Overloaded constructor and begins impersonating.
/// </summary>
public ImpersonationContext(string userName, string password, string domain)
{
this.BeginImpersonationContext(userName, password, domain);
}
#endregion
#region impersonation methods
/// <summary>
/// Begins the impersonation context for the specified user.
/// </summary>
/// <remarks>Don't call this method if you used the overloaded constructor.</remarks>
public void BeginImpersonationContext(string userName, string password, string domain)
{
//initialize token and duplicate variables
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)
{
using (WindowsIdentity tempWindowsIdentity = new WindowsIdentity(tokenDuplicate))
{
//begin the impersonation context and mark impersonating true
impersonationContext = tempWindowsIdentity.Impersonate();
impersonating = true;
}
}
}
}
//close the handle to the account token
if (token != IntPtr.Zero)
CloseHandle(token);
//close the handle to the duplicated account token
if (tokenDuplicate != IntPtr.Zero)
CloseHandle(tokenDuplicate);
}
/// <summary>
/// Ends the current impersonation context.
/// </summary>
public void EndImpersonationContext()
{
//if the context exists undo it and dispose of the object
if (impersonationContext != null)
{
//end the impersonation context and dispose of the object
impersonationContext.Undo();
impersonationContext.Dispose();
}
//mark the impersonation flag false
impersonating = false;
}
#endregion
#region properties
/// <summary>
/// Gets a value indicating whether the impersonation is currently active.
/// </summary>
public bool Impersonating
{
get
{
return impersonating;
}
}
#endregion
#region IDisposable implementation
~ImpersonationContext()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (impersonationContext != null)
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
}
}
#endregion
}
Thanks a lot for the code. I may well need it to solve the problem now that I know what's happening!
It turns out that the problem is to do with Kerberos. I discovered that if I went in to IIS Manager locally and "browsed" the web page, the remote WMI call worked. The website is set up to impersonate the connected user and if I connect locally the authentication token is passed to the remote box and everything works.
If I connect from outside the web server, the token supplied to the website by IIS can't currently be passed on because the web server is not registered with Kerberos. (I'm passing on what I've been told recently here. I need to understand this better in order to solve the problem definitively.)
I think the solution here is not to pass on the connected user's token, but rather to disable impersonation programmatically for the WMI call and execute it as the account under which the app pool is running. I'm sure I can find out how to do that with a quick search.
Thanks for your help
精彩评论