Process.Start and Impersonation

Did you know that Process.Start always uses the security context of the parent ASP.NET process? I just found this out the hard way; Using Process.Start on "whoami.exe" always returns the ASPNET worker process no matter what I do. Some searching turned up this entry in Scott's blog:


This is a companion discussion topic for the original blog entry at: http://www.codinghorror.com/blog/2004/11/processstart-and-impersonation.html

Sorry to just spam a load of url’s at you, but I hope these are of some use in your quest to get CreateProcessWithLogonW working in VB.net:

http://www.dotnet247.com/247reference/msgs/14/73862.aspx

http://www.eggheadcafe.com/ng/microsoft.public.vb.winapi.networks/post3994284.asp

http://weblogs.asp.net/ralfw/archive/2003/11/24/39479.aspx

Cheers,
Andy.

Excellent links particularly the last one. Thank you. Unfortunately, even if I get it to work I can’t get the output from the command line execution, which kills this as a solution for me.

Some additional detail. I am using psexec.exe from SysInternals (which requires local admin) and rclient.exe from the Win2k Resource Kit (which doesn’t). I can get output from psexec.exe but not from rclient.exe either, so I am basically back where I started.

Yes indeed, I have a list of about 10 things I can’t wait for in 2005!

I’m sure this is too late to help in solving your problems, but a solution I’m using to capture output from the started process is to run it with redirection, ie “procname foo.txt 21 args”. Then after getting the Process by it’s processId and doing WaitForExit on it, I can read the contents of that file to find out what the output was.

It’s a horrible hack, but it works. As long as you have a scratch directory you can use for the temporary files, and you have a solution for the concurrency issues (eg using a different filename for each request).

Right-- the filesystem method does work. I am using that in another project with great results. It’s just not as elegant as capturing the output directly from the stream…

!–Not sure if u’ve tried these –
System.Diagnostics.Process oProcess = new Process();
oProcess.StartInfo.FileName = “c:\folder1\filename.exe”;
oProcess.StartInfo.Arguments = “”;
oProcess.StartInfo.UseShellExecute = false;
oProcess.StartInfo.RedirectStandardOutput = true;

		oProcess.WaitForExit();
		string output = oProcess.StandardOutput.ReadToEnd();

		Response.Write("BRBRthe string: " + output);

Which is great, until you need to perform a network operation via the command line in an ASP.NET app. You will always get the ASP.NET process credentials, as stated above…

Using Process.Start on “whoami.exe” always returns the ASPNET worker process no matter what I do.

ASPNET is a local machine account. Thus, any network resources that require network credentials (eg DOMAIN\USER) won’t work at all.

I want register a dll, with regsvr32.exe using System.Diagnostics.Process but to the redirect the error(StandardError) this is empty, though there are errors.Why?

My code

string directorioReg=“C:\Windows\System32”;
System.Diagnostics.Process proc=new System.Diagnostics.Process();
proc.StartInfo.FileName=“regsvr32.exe”;
proc.StartInfo.WorkingDirectory = directorioReg;
proc.StartInfo.Arguments=" “”+ruta+""";//Aadir /s para evitar mensajes
/Pruebas/ proc.EnableRaisingEvents = true;
proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardInput = false;
proc.StartInfo.UseShellExecute = false;

/Fin pruebas/
try
{
proc.Start();
string list_outPut=proc.StandardOutput.ReadToEnd();
string list_errors=proc.StandardError.ReadToEnd();
//list_outPut and list_errors are empty.
proc.WaitForExit();
proc.Close();
}
catch(Exception e){
throw e;
}

I want to call an windows based Exe in client machine using asp.net in a button click is it possible… If so pls provide the code.

Thanks in advance
Rams

with .Net 2.0 you can’t get the standard output of a process that you launch under a different set of credentials.
you can get this output only if you run under current credentials.

we have found a solution for this: run a process under specific credentials, and this process will run another secondary process without any credentials, and so this secondary process will be able to get the standard output.

it’s not really elegant, if someone have another ideas/solution ?

Here’s C# impersonation code, someone else has already given you info on how to redirect stdio. It could have been formatted better but I selectively pulled bits out of a wrapper class I’d written for a project. Please bear in mind that there are security issues here to do with the way that CreateProcessWithLogonW searches for the executable to launch. Refer to https://buildsecurityin.us-cert.gov/portal/article/knowledge/coding_rules/RULE_0029.xml for more info:

const int logonWithProfile = 1;
const int logonTypeNetwork = 3;
const int logonProviderDefault = 0;

ProcessInformation processInfo = new ProcessInformation();
StartupInformation startupInfo = new StartupInformation();
startupInfo.cb = Marshal.SizeOf(typeof(StartupInformation));
startupInfo.reserved = null;

CreateProcessWithLogonW(“username”, “domain”, “password”, logonWithProfile, “c:\path\whoami.exe”, “c:\path\whoami.exe”, 0, IntPtr.Zero, null, ref startupInfo, out processInfo))

if (processInfo.hProcess != IntPtr.Zero) CloseHandle(processInfo.hProcess);
if (processInfo.hThread != IntPtr.Zero) CloseHandle(processInfo.hThread);

///
/// CreateProcessWithLogonW is the unmangaged method used to launch a process under the context of
/// alternative, user provided, credentials. It is called by the managed method CreateProcessAsUser
/// defined earlier in this class. Further information is available on MSDN under
/// CreateProcessWithLogonW (a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/"http://msdn.microsoft.com/library/default.asp?url=/library/en-us//a
/// dllproc/base/createprocesswithlogonw.asp).
///
/// Whether to load a full user profile(param value = 1) or perform a
/// network only (param value = 2) logon.
/// The application to execute (populate either this parameter
/// or the commandLine parameter).
/// The command to execute.
/// Flags that control how the process is created.
///
[DllImport(“advapi32.dll”, CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)]
static extern bool CreateProcessWithLogonW(
string userName,
string domain,
string password,
int logonFlags,
string applicationPath,
string commandLine,
int creationFlags,
IntPtr environment,
string currentDirectory,
ref StartupInformation startupInformation,
out ProcessInformation processInformation);

/// CloseHandle closes an open object handle.
[DllImport(“kernel32.dll”, SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr handle);

	/// 
	/// The StartupInformation structure is used to specify the window station, desktop, standard handles
	/// and appearance of the main window for the new process. Further information is available on MSDN
	/// under STARTUPINFO (a href="http://msdn.microsoft.com/library/en-us/dllproc/base/startupinfo_str.asp)."http://msdn.microsoft.com/library/en-us/dllproc/base/startupinfo_str.asp)./a
	/// 
	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
	struct StartupInformation
	{
		internal int    cb;
		internal string reserved;
		internal string desktop;
		internal string title;
		internal int    x;
		internal int    y;
		internal int    xSize;
		internal int    ySize;
		internal int    xCountChars;
		internal int    yCountChars;
		internal int    fillAttribute;
		internal int    flags;
		internal UInt16 showWindow;
		internal UInt16 reserved2;
		internal byte   reserved3;
		internal IntPtr stdInput;
		internal IntPtr stdOutput;
		internal IntPtr stdError;
	}
	
	/// 
	/// The ProcessInformation structure contains information about the newly created process and its
	/// primary thread.
	/// 
	/// hProcess is a handle to the newly created process.
	/// hThread is a handle to the primary thread of the newly created process.
	[StructLayout(LayoutKind.Sequential)]
	struct ProcessInformation
	{
		internal IntPtr hProcess;
		internal IntPtr hThread;
		internal int    processId;
		internal int    threadId;
	}

Look at full threaded shellicious:

http://www.codinghorror.com/blog/archives/000136.html

Angel - your code isn’t working because you’re asking for the contents of the StdErr before the process has finished - at that point in time, ReadToEnd probably has nothing to give you. Note that redirecting StdOut and StdErr gives threat of deadlock, so unless you’re sure there isn’t going to be very much info, you should use threading. There’s a good example on microsoft.com somewhere (sorry, I don’t have a link to hand).

Nicolas D - I’ve just come across the same thing. I want to launch a 3rdParty exe from my web app and capture the output/error. I’m very familiar with this process having used it a lot in previous versions of dot net, and I’ve been using streams for years.

I noticed that dot net 2.0 had extra domain\username\password properties so you could launch a process as someone else. I’m trying to use StdOut and StdErr redirection to grab the output of the 3rdparty EXE that I’m running. However, this isn’t working, and here’s what I’ve found out so far.

I already knew that all processes have stdin, stdout, stderr. However, processes that are launched by a mouse-click don’t get these set up. (I couldn’t believe this.) All my experience led me to think that they were there; but just redirected to (if you pardon the Unix expression) “piped to /dev/NULL”. For example, go to a command prompt and type “devenv.exe” (in the relevant folder) and it will launch Visual Studio. Type “devenv.exe /?” and the app will write stuff to standard out. Type “devenv.exe /?c:\junk.txt” and nothing appears; because the standard output has been redirected to the ‘junk.txt’ file.

Similarly, I wrote a trivial C# Windows app, which writes to Console.Out and Console.Error during startup and on button clicks. Launch it from command line with redirection, and all of my messages arrive in the file. However, create a shortcut for the same redirecting command, launch it, and the file doesn’t appear. It seems that when you run an app from the command prompt, WinForms fixes your stdin, stdout and stderr, and it all works fine. But if you click on something, it doesn’t get the Std streams.

For example, I wrote a trivial console app to wrap launching a process as a different user. I ran this from the command prompt, and it worked fine. So then I wrote a windows app to make use of the console app (which works), and it failed (System.ComponentModel.Win32Exception: The handle is invalid at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo). Bizarrely (and you won’t believe this - I didn’t) if you run the windows app from the command line it works!

The problem is caused because when the child process is launched by System.Diagnostics.Process as a different user, it tries to hijack the Std streams of the parent process; but if that was a clicked-on-Gui-app, it doesn’t have them, and rather than noticing this and setting them up for you, it crashes. So basically, you have to do it yourself (I don’t know how yet, but I’ve seen a few things around that I’ll try.)

Shellicious wraps process launching as the same user - it doesn’t use the new feature in dot net 2.0 of being able to launch as a different user, as Nicolas D and myself are trying to do. The code within the .net framework branches depending on whether you’ve given a user or not. If you supply values to the Domain, UserName and Password properties, you will find that it only works if you launch it from a Command Prompt. If you launch the same program by double-clicking the file (or a shortcut), it will fail with the Win32 invalid handle error mentioned earlier.

Ive just re-read my post on the 8th, and noticed that I didn’t make it clear that I need to launch the 3rdParty exe as a particular user…

Shellicious wraps process launching as the same user

so unless you’re sure there isn’t going to be very much info, you should use threading. There’s a good example on microsoft.com somewhere (sorry, I don’t have a link to hand).

Sorry, I wasn’t clear. I mentioned Shellicious not because of the user impersonation issue, but because of the multiple threads necessary to capture output and error.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.Principal;
using System.Text;

/// summary
/// UserSpecificProcess extends the standard Process object to
/// create new processes under a different user than the calling parent process.
/// Also, the standard output of the child process redirected back to the parent process.
/// This is class is designed to operate inside an ASP.NET web application.
/// The assumption is that the calling thread is operating with an impersonated security token.
/// The this class will change the imperonated security token to a primary token,
/// and call CreateProcessAsUser.
/// A System.Diagnostics.Process object will be returned with appropriate properties set.
/// To use this function, the following security priviliges need to be set for the ASPNET account
/// using the local security policy MMC snap-in. CreateProcessAsUser requirement.
/// “Replace a process level token”/SE_ASSIGNPRIMARYTOKEN_NAME/SeAssignPrimaryTokenPrivilege
/// “Adjust memory quotas for a process”/SE_INCREASE_QUOTA_NAME/SeIncreaseQuotaPrivilege
///
/// This class was designed for .NET 1.1 for operating systems W2k an higher.
/// Any other features or platform support can be implemented by using the .NET reflector and
/// investigating the Process class.
/// /summary
public class UserSpecificProcess : Process
{
[StructLayout(LayoutKind.Sequential)]
public class CreateProcessStartupInfo
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
public CreateProcessStartupInfo()
{
this.cb = Marshal.SizeOf(typeof(CreateProcessStartupInfo));
this.lpReserved = null;
this.lpDesktop = null;
this.lpTitle = null;
this.dwX = 0;
this.dwY = 0;
this.dwXSize = 0;
this.dwYSize = 0;
this.dwXCountChars = 0;
this.dwYCountChars = 0;
this.dwFillAttribute = 0;
this.dwFlags = 0;
this.wShowWindow = 0;
this.cbReserved2 = 0;
this.lpReserved2 = IntPtr.Zero;
this.hStdInput = IntPtr.Zero;
this.hStdOutput = IntPtr.Zero;
this.hStdError = IntPtr.Zero;
}
}

[StructLayout(LayoutKind.Sequential)]
	public class CreateProcessProcessInformation
{
	public IntPtr hProcess;
	public IntPtr hThread;
	public int dwProcessId;
	public int dwThreadId;
	public CreateProcessProcessInformation()
	{
		this.hProcess = IntPtr.Zero;
		this.hThread = IntPtr.Zero;
		this.dwProcessId = 0;
		this.dwThreadId = 0;
	}
}

[StructLayout(LayoutKind.Sequential)]
	public class SecurityAttributes
{
	public int nLength;
	public IntPtr lpSecurityDescriptor;
	public bool bInheritHandle;
	public SecurityAttributes()
	{
		this.nLength = Marshal.SizeOf(typeof(SecurityAttributes));
	}
}

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
public static extern bool CloseHandle(HandleRef handle);

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);

[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern bool CreateProcessAsUserW(IntPtr token, [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPTStr)] string lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);

[DllImport("kernel32.dll", CharSet=CharSet.Ansi, SetLastError=true)]
public static extern IntPtr GetStdHandle(int whichHandle);

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, SecurityAttributes lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, HandleRef hTemplateFile);

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern IntPtr CreateNamedPipe(string name, int openMode, int pipeMode, int maxInstances, int outBufSize, int inBufSize, int timeout, SecurityAttributes lpPipeAttributes);

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetConsoleOutputCP();

[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool DuplicateTokenEx(HandleRef hToken, int access, SecurityAttributes tokenAttributes, int impersonationLevel, int tokenType, ref IntPtr hNewToken);

// WinNT.h ACCESS TYPES
const int GENERIC_ALL = 0x10000000;

// WinNT.h enum SECURITY_IMPERSONATION_LEVEL
const int SECURITY_IMPERSONATION = 2;

// WinNT.h TOKEN TYPE
const int TOKEN_PRIMARY = 1;

// WinBase.h
const int STD_INPUT_HANDLE = -10;
const int STD_ERROR_HANDLE = -12;

// WinBase.h STARTUPINFO
const int STARTF_USESTDHANDLES = 0x100;

// Microsoft.Win23.NativeMethods
static IntPtr INVALID_HANDLE_VALUE = (IntPtr) (-1);
public static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);

/// summary
/// Starts the process with the security token of the calling thread.
/// If the security token has a token type of TokenImpersonation,
/// the token will be duplicated to a primary token before calling
/// CreateProcessAsUser.
/// /summary
/// param name="process"The process to start./param
public void StartAsUser()
{
	StartAsUser(WindowsIdentity.GetCurrent().Token);
}

/// summary
/// Starts the process with the given security token.
/// If the security token has a token type of TokenImpersonation,
/// the token will be duplicated to a primary token before calling
/// CreateProcessAsUser.
/// /summary
/// param name="process"/param
public void StartAsUser(IntPtr userToken)
{
	if (StartInfo.UseShellExecute)
	{
		throw new InvalidOperationException("can't call this with shell execute");
	}

	// just assume that the securityToken is of TokenImpersonation and create a primary.
	IntPtr primayUserToken = CreatePrimaryToken(userToken);

	CreateProcessStartupInfo startupInfo = new CreateProcessStartupInfo();
	CreateProcessProcessInformation processInformation = new CreateProcessProcessInformation();

	IntPtr stdinHandle;
	IntPtr stdoutReadHandle;
	IntPtr stdoutWriteHandle = IntPtr.Zero;
	IntPtr stderrHandle;
	try
	{
		stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
		MyCreatePipe(out stdoutReadHandle, out stdoutWriteHandle, false);
		stderrHandle = GetStdHandle(STD_ERROR_HANDLE);

		//assign handles to startup info
		startupInfo.dwFlags = STARTF_USESTDHANDLES;
		startupInfo.hStdInput = stdinHandle;
		startupInfo.hStdOutput = stdoutWriteHandle;
		startupInfo.hStdError = stderrHandle;

		string commandLine = GetCommandLine();
		int creationFlags = 0;
		IntPtr environment = IntPtr.Zero;
		string workingDirectory = GetWorkingDirectory();

		// create the process or fail trying.
		if (!CreateProcessAsUserW(
			primayUserToken,
			null,
			commandLine,
			null,
			null,
			true,
			creationFlags,
			environment,
			workingDirectory,
			startupInfo,
			processInformation))
		{
			throw new Win32Exception();
		}
	}
	finally
	{
		// close thread handle
		if (processInformation.hThread != INVALID_HANDLE_VALUE)
		{
			CloseHandle(new HandleRef(this, processInformation.hThread));
		}

		// close client stdout handle
		CloseHandle(new HandleRef(this, stdoutWriteHandle));
	}

	// get reader for standard output from the child
	Encoding encoding = Encoding.GetEncoding(GetConsoleOutputCP());
	StreamReader standardOutput = new StreamReader(new FileStream(stdoutReadHandle, FileAccess.Read, true, 0x1000, true), encoding);

	// set this on the object accordingly.
	typeof(Process).InvokeMember("standardOutput",
		BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance,
		null, this, new object[]{standardOutput});

	// scream if a process wasn't started instead of returning false.
	if (processInformation.hProcess == IntPtr.Zero)
	{
		throw new Exception("failed to create process");
	}

	// configure the properties of the Process object correctly
	typeof(Process).InvokeMember("SetProcessHandle", 
		BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
		null, this, new object[]{processInformation.hProcess});
	typeof(Process).InvokeMember("SetProcessId", 
		BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
		null, this, new object[]{processInformation.dwProcessId});
}

/// summary
/// Creates a primayToken out of an existing token.
/// /summary
/// param name="userToken"/param
private IntPtr CreatePrimaryToken(IntPtr userToken)
{
	SecurityAttributes securityAttributes = new SecurityAttributes();
	IntPtr primaryUserToken = IntPtr.Zero;
	if (!DuplicateTokenEx(new HandleRef(this, userToken), GENERIC_ALL, securityAttributes, SECURITY_IMPERSONATION, TOKEN_PRIMARY, ref primaryUserToken))
	{
		throw new Win32Exception();
	}
	return primaryUserToken;
}

/// summary
/// Gets the appropriate commandLine from the process.
/// /summary
/// param name="process"/param
/// returns/returns
private string GetCommandLine()
{
	StringBuilder builder1 = new StringBuilder();
	string text1 = StartInfo.FileName.Trim();
	string text2 = StartInfo.Arguments;
	bool flag1 = text1.StartsWith("\"")  text1.EndsWith("\"");
	if (!flag1)
	{
		builder1.Append("\"");
	}
	builder1.Append(text1);
	if (!flag1)
	{
		builder1.Append("\"");
	}
	if ((text2 != null)  (text2.Length  0))
	{
		builder1.Append(" ");
		builder1.Append(text2);
	}
	return builder1.ToString();
}

/// summary
/// Gets the working directory or returns null if an empty string was found.
/// /summary
/// returns/returns
private string GetWorkingDirectory()
{
	return (StartInfo.WorkingDirectory != string.Empty) ? StartInfo.WorkingDirectory : null;
}

/// summary
/// A clone of Process.CreatePipe.  This is only implemented because reflection with
/// out parameters are a pain.  
/// Note: This is only finished for w2k and higher machines.
/// /summary
/// param name="parentHandle"/param
/// param name="childHandle"/param
/// param name="parentInputs"Specifies whether the parent will be performing the writes./param
public static void MyCreatePipe(out IntPtr parentHandle, out IntPtr childHandle, bool parentInputs)
{
	string pipename = @"\\.\pipe\Global\" + Guid.NewGuid().ToString();

	SecurityAttributes attributes2 = new SecurityAttributes();
	attributes2.bInheritHandle = false;

	parentHandle = CreateNamedPipe(pipename, 0x40000003, 0, 0xff, 0x1000, 0x1000, 0, attributes2);
	if (parentHandle == INVALID_HANDLE_VALUE)
	{
		throw new Win32Exception();
	}

	SecurityAttributes attributes3 = new SecurityAttributes();
	attributes3.bInheritHandle = true;
	int num1 = 0x40000000;
	if (parentInputs)
	{
		num1 = -2147483648;
	}
	childHandle = CreateFile(pipename, num1, 3, attributes3, 3, 0x40000080, NullHandleRef);
	if (childHandle == INVALID_HANDLE_VALUE)
	{
		throw new Win32Exception();
	}
}

}

Rich,

I have the same issue with the invalid handle when launching from the GUI. .Net 2.0, setting username and password for ProcessStartInfo.

Did you ever figure out a workaround?

Thanks,
Julie.