Hello John,
Welcome to the ASP.NET newsgroup.
From your description, I understand you're going to launch the visual
studio's devenv.exe program(in a new process) to build some projects in
ASP.NET web application. You've tried using the new
System.Diagnostics.Process/ProcessStartInfo class with no success, correct?
According to your test cases, I've also performed some tests on my local
side and also encoutered the similiar behavior as yours. Here are the
results I got:
1. Let the new process(through Process class) start under the default
security context( the default process identity-----NT AUTHORITY\NETWORK
SERVICE). The sub process started correctly and finished the build task.
2. Use Process class with ProcessStartInfo(supply a different user
account's credentials), after the Process.Start call, the page hangs(I call
Process.WaitforExit), I think it is due to the same error you mentioned and
since ASP.NET process(or any created sub process) running under a
non-interactive winstation, the popup error message is not displayed and
the process hangs.
I've analyized this issue with some other engineers and we've concluded
that it is likely caused by the ProcessStartInfo class. Though the managed
ProcessStartInfo provide username/password properties for launching
processes under different security context, it still doesn't provide some
advanced options like desktop. For this issue, when we try creating the new
process through a new specific account, it internally require an
interactive/desktop(at least not the original ASP.NET server process's
desktop), then the error raise out. Therefore, the managed Process class
may only be supposed to work in interactive application context (such as
console or winform) if we want to use different security context(by
assigning the username/password) property.
For your scenario, I've tried some other approachs and currently we can
managed programmtically execute separate process under specific user
account by calling the win32 "CreateProcessAsUser" function(through
pinvoke). Also, before calling this function, we need to get the security
token of the specific user which we also need to programmatically call some
win32 API to logon the user and programmtically impersonate it. Here we're
using programmtic impersonate rather than use web.config or machine.config.
And all these tasks are well demonstrated in the following two knowledge
base articles:
#How to implement impersonation in an ASP.NET application
http://support.microsoft.com/kb/306158/en-us
#How to spawn a process that runs under the context of the impersonated
user in Microsoft ASP.NET pages
http://support.microsoft.com/kb/889251/en-us
Further more, I've created a simple sample page which use the win32 API to
programmtically launch the devenv.exe to build a project in a button's
postback event. Here is the page's complete codebehind (in case the code
may display incorrect on page, I've also attached the code file in this
message, you can get the file if you're using outlook express to access the
newsgroup):
===========page code=====
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Diagnostics;
using System.Security;
using System.Web.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;
public partial class Execute_Default : System.Web.UI.Page
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
WindowsImpersonationContext impersonationContext;
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnExecute_Click(object sender, EventArgs e)
{
if (impersonateValidUser("Administrator", "machinename",
"password"))
{
Response.Write("<br/>User:" +
System.Security.Principal.WindowsIdentity.GetCurrent().Name);
RunWin32Command();
undoImpersonation();
}
else
{
Response.Write("<br/>Impersonate failed...");
}
}
void RunWin32Command()
{
IntPtr Token = new IntPtr(0);
IntPtr DupedToken = new IntPtr(0);
bool ret;
//Label2.Text += WindowsIdentity.GetCurrent().Name.ToString();
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.bInheritHandle = false;
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = (IntPtr)0;
Token = WindowsIdentity.GetCurrent().Token;
const uint GENERIC_ALL = 0x10000000;
const int SecurityImpersonation = 2;
const int TokenType = 1;
ret = DuplicateTokenEx(Token, GENERIC_ALL, ref sa,
SecurityImpersonation, TokenType, ref DupedToken);
if (ret == false)
Response.Write("<br/>" + "DuplicateTokenEx failed with " +
Marshal.GetLastWin32Error());
else
Response.Write("<br/>" + "DuplicateTokenEx SUCCESS");
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
string filename = @"C:\Program Files\Microsoft Visual Studio
8\Common7\IDE\devenv.exe";
string arguments = " \"D:\\temp\\workspace\\ProtectConfig.sln\"
/build Debug /project \"ProtectConfig\\ProtectConfig.csproj\"
/projectconfig Debug";
string commandLinePath;
commandLinePath = filename + arguments;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
ret = CreateProcessAsUser(DupedToken, null, commandLinePath, ref
sa, ref sa, false, 0, (IntPtr)0, "d:\\temp", ref si, out pi);
if (ret == false)
Response.Write("<br/>" + "CreateProcessAsUser failed with " +
Marshal.GetLastWin32Error());
else
{
Response.Write("<br/>" + "CreateProcessAsUser SUCCESS. The
child PID is" + pi.dwProcessId);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
ret = CloseHandle(DupedToken);
if (ret == false)
Response.Write("<br/>" + Marshal.GetLastWin32Error());
else
Response.Write("<br/>" + "CloseHandle SUCCESS");
}
private 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;
}
private void undoImpersonation()
{
impersonationContext.Undo();
}
//for impersonate/////////////////
[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);
///////////////////////////////////////////////////////
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError =
true, CharSet = CharSet.Auto, CallingConvention =
CallingConvention.StdCall)]
public extern static bool CloseHandle(IntPtr handle);