CreateProcessAsUser

Friday, October 29, 2004

Here is a solution you can use in rare circumstances and with caution. Comments are more then welcomed.

Anytime someone approaches me with a design requiring the creation of a server side process – I flinch. Spinning up a process on the server is something to avoid. Having said that, I found it useful in a small Intranet applications (a build engine) to put a web service / ASP.NET interface in front of some command line CM tools. The interface is available to a small number of trusted individuals.

I wanted to run these processes with the identity of the client, but this poses a problem. The Process class in System.Diagnostics can start a new process, but the process always inherits the security context of the parent process. Even if the ASP.NET thread invoking the Start method is impersonating a client, the Process still starts with the ASP.NET worker process credentials.

Enter .NET 2.0, which includes the User, Domain, and Password properties on the ProcessStartInfo type. In .NET 2.0 you can start a process under a different set of credentials. The catch is having a password to give. I don't want the burden of managing passwords when there is a domain controller handy.

Having found no solution in the framework, the next step was to look into the Win32 API. There are four basic functions to start a process: CreateProcess, CreateProcessWithLogonW, CreateProcessAsUser, and CreateProcessWithTokenW. Since CreateProcess doesn’t allow an alternate identity, and one of 11 parameters to CreateProcessWithLogonW is a password, those are both out of the running.

The remaining two (CreateProcessWithTokenW and CreateProcessAsUser) both accept a token instead of a username and password, so these look promising. CreateProcessWithTokenW allows greater fine tuning of the logon type and creation flags, which I didn't need, so I focused in on CreateProcessAsUser.

CreateProcessAsUser accepts a user token as the first parameter. We can easily get a token representing the client we are impersonating using the WindowsIdentity class in .NET, but there still exists a problem. The token will be an impersonation token - which isn’t good enough for CreateProcessAsUser. However, the docs mention that you can use DuplicateTokenEx to convert an impersonation token into a primary token. The code at the end of this post demonstrates the incantations.

Note: The calling process needs SeAssignPrimaryTokenPrivilege and SeIncreaseQuotasPrivilege privileges, which the NETWORK SERVICE account has by default on Windows 2003.

Note: I’m not sure how far the ‘primary token’ yielded by DuplicateTokenEx can go. Could I effectively delegate without delegation enabled? I wouldn't think so. Must experiment. Anyone know?

Note: If you are thinking of using this to launch an interactive GUI application on the server – don’t. The process will start in a non-interactive window station and remain invisible but consuming memory.

 

private void CreateProcessAsUser()
{
   IntPtr hToken = WindowsIdentity.GetCurrent().Token;         
   IntPtr hDupedToken = IntPtr.Zero;        
   
   ProcessUtility.PROCESS_INFORMATION pi = new ProcessUtility.PROCESS_INFORMATION();
   
   try
   {
      ProcessUtility.SECURITY_ATTRIBUTES sa = new ProcessUtility.SECURITY_ATTRIBUTE();
      sa.Length = Marshal.SizeOf(sa); 
 
      bool result = ProcessUtility.DuplicateTokenEx(
            hToken, 
            ProcessUtility.GENERIC_ALL_ACCESS,
            ref sa, 
            (int)ProcessUtility.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
            (int)ProcessUtility.TOKEN_TYPE.TokenPrimary, 
            ref hDupedToken
         );
   
      if(!result)
      {               
         throw new ApplicationException("DuplicateTokenEx failed");
      }
 
 
      ProcessUtility.STARTUPINFO si = new ProcessUtility.STARTUPINFO();
      si.cb = Marshal.SizeOf(si);
      si.lpDesktop = String.Empty;           
 
      result = ProcessUtility.CreateProcessAsUser(
                           hDupedToken, 
                           @"",
                           String.Empty,
                           ref sa, ref sa, 
                           false, 0, IntPtr.Zero, 
                           @"C:\", ref si, ref pi
                     );
 
      if(!result)
      {  
         int error = Marshal.GetLastWin32Error();
         string message = String.Format("CreateProcessAsUser Error: {0}", error);
         throw new ApplicationException(message);
      }
   }
   finally
   {
      if(pi.hProcess != IntPtr.Zero)
         ProcessUtility.CloseHandle(pi.hProcess);
      if(pi.hThread != IntPtr.Zero)
         ProcessUtility.CloseHandle(pi.hThread);
      if(hDupedToken != IntPtr.Zero)
         ProcessUtility.CloseHandle(hDupedToken);      
   }
}

 

ProcessUtility…

 

public class ProcessUtility
{
   [StructLayout(LayoutKind.Sequential)]
      public struct STARTUPINFO
   {
      public Int32 cb;
      public string lpReserved;
      public string lpDesktop;
      public string lpTitle;
      public Int32 dwX;
      public Int32 dwY;
      public Int32 dwXSize;
      public Int32 dwXCountChars;
      public Int32 dwYCountChars;
      public Int32 dwFillAttribute;
      public Int32 dwFlags;
      public Int16 wShowWindow;
      public Int16 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 Int32 dwProcessID;
      public Int32 dwThreadID;
   }
 
   [StructLayout(LayoutKind.Sequential)]
      public struct SECURITY_ATTRIBUTES
   {
      public Int32 Length;
      public IntPtr lpSecurityDescriptor;
      public bool bInheritHandle;
   }
 
   public enum SECURITY_IMPERSONATION_LEVEL
   {
      SecurityAnonymous,
      SecurityIdentification,
      SecurityImpersonation,
      SecurityDelegation
   }
 
   public enum TOKEN_TYPE
   {
      TokenPrimary = 1, 
      TokenImpersonation
   } 
 
   public const int GENERIC_ALL_ACCESS = 0x10000000;
 
   [
      DllImport("kernel32.dll", 
         EntryPoint = "CloseHandle", SetLastError = true, 
         CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
   ]
   public static extern bool CloseHandle(IntPtr handle);
 
   [
      DllImport("advapi32.dll", 
         EntryPoint = "CreateProcessAsUser", SetLastError = true, 
         CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
   ]
   public static extern bool 
      CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, 
                          ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, 
                          bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                          string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, 
                          ref PROCESS_INFORMATION lpProcessInformation);
 
   [
      DllImport("advapi32.dll", 
         EntryPoint = "DuplicateTokenEx")
   ]
   public static extern bool 
      DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess, 
                       ref SECURITY_ATTRIBUTES lpThreadAttributes,
                       Int32 ImpersonationLevel, Int32 dwTokenType, 
                       ref IntPtr phNewToken);
}

Comments
Q Man Wednesday, December 22, 2004
Does DuplicateTokenEx populate the SECURITY_ATTRIBUTES structure (sa) which is then passed to CreateProcessAsUser? That is, is it necessary to have DuplicateTokenEx fill in the SECURITY_ATTRIBUTES in order for the CreateProcessAsUser call to suceed? I keep getting an access violation error (passing in an empty SECURITY_ATTRIBUTES structure).
<br>
<br>
Scott Allen Wednesday, December 22, 2004
Did you set the Length field of SECURITY_ATTRIBUTES? That one is required.
Q Man Wednesday, December 22, 2004
Yes, I did set the length of SECURITY_ATTRIBUTES to sizeof(SECURITY_ATTRIBUTES) and also tried sizeof(sa) the variable. I get an access violation (OS error 998) in the call to DuplicateTokenEx. I don't get that error if I pass in NULL for security attributes rather than a structure.
<br>
<br>Here is the code snippet (in C++):
<br>
<br>SECURITY_ATTRIBUTES sa;
<br>sa.nLength = sizeof(sa);
<br>
<br>val = DuplicateTokenEx(existingTokenHandle, 0x10000000, &amp;sa, SecurityIdentification, TokenPrimary, &amp;userTokenHandle);
<br>
<br>This yields a 998 OS error. However, the following line doesn't:
<br>
<br>val = DuplicateTokenEx(existingTokenHandle, 0x10000000, NULL, SecurityIdentification, TokenPrimary, &amp;userTokenHandle);
<br>
<br>I get a handle to a user token with the second call but it then fails on CreateProcessAsUser (because I don't have security attributes object to pass to it). So I assume that my problem is with the security attributes structure. Am I correct in assuming that DuplicateTokenEx populates the structure which can then be used by CreateProcessAsUser?
Scott Thursday, December 23, 2004
Ah, in C++ I'd initialize lpSecurityDescriptor to NULL. I imagine the API is probably trying to dereference that one. In .NET the struct will initialize with all default NULL and 0 values, so it wasn't explicitly added here.
<br>
<br>Does that help?
David Thursday, January 13, 2005
I got a 1314 error when calling createProcessasuser, I think this is because ASPNet account doesn't have SeAssignPrimaryTokenPrivilege and SeIncreaseQuotasPrivilege privileges.
Seljo Thursday, January 20, 2005
You might want to check out the following page. It says you can launch the process in an interactive window session, given the correct parameters.
<br>
<br>http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createprocessasuser.asp
<br>
<br>Also, I found this site. Looks promising, if I can get it to compile and run.
<br>
<br>http://win32.mvps.org/index.html
Scott Friday, January 21, 2005
Yes, you can have it show up in the interactive session, but since ASP.NET is running server side - is anyone really going to see the window?
Ryan Wednesday, February 2, 2005
This code is exactly what I'm looking to do. Any chance of getting this in VB.NET? Don't really understand it enough to move it over myself...
Scott Thursday, February 3, 2005
Hey Ryan:
<br>
<br>I could probably get it into VB.NET but it might take me a little time. there are some converters available on the web that might be able to help out. Let me know if you can wait a week and I might be able to have something done over the weekend.
Ryan Thursday, February 3, 2005
That would be great. I am also working on it and have taken a stab at using the converters. Thanks for looking into it!
carlmalden Tuesday, February 15, 2005
I just wanted to mention to you, that I have a service that uses CreateProcessAsUser and starts and stops applications everyday. So it is possible to start apps....
<br>
<br>Just thought you might want to know....
Sangeetha Friday, February 25, 2005
How to create a process using CreateProcessAsuser with services rights?
<br>Always returns with ERR_PRIVILEGE_NOTHELD
<br>Kindly help
Scott Friday, February 25, 2005
It appears MS has a KB article dedicated to the topic now: http://support.microsoft.com/default.aspx?scid=kb;EN-US;889251
Memo Tuesday, March 22, 2005
Pls I want solution for that error?
Stuart Ballard Wednesday, June 22, 2005
This solution is getting 99% of the way to where I want to go but I'm having terrible trouble getting the last 1%. You see, I need to pass a couple of environment variables to the spawned process - the program I need to run provides no other way to give it this information and will not run successfully without it.
<br>
<br>I've tried turning the lpEnvironment parameter into a string and passing a value in the format that the documentation seems to suggest - &quot;var1=val1\0var2=val2\0\0&quot; - and I tried putting [MarshalAs(UnmanagedType.LPStr)] on it as well. But nothing seems to work.
<br>
<br>Any suggestions?
<br>
<br>I have a horrible feeling I'm going to have to write a whole separate wrapper program which takes the args in question from the command line and then uses a regular Process.Start(). Then if I CreateProcessAsUser on *that* then everything should work. But it's pretty evil...
Scott Thursday, June 23, 2005
Hey Stuart:
<br>
<br>I think you'll need to use CreateEnvironmentBlock from the win32 API.
<br>See: http://www.sayala.com/code.aspx?file=ProcessAsUser.cs
Michael Tuesday, July 19, 2005
I had the same problem with starting a new thread. I lost the impersonated context.

So, things like

WindowsIdentity.GetCurrent().Name

will return the name of the original process creator, which is the ASPNET user for an ASP.NET page.

Possible solution could be to store the current user in a private member before starting the new thread.

I decided to get rid of the complete thread thing, because in my case I just used a thread for spawning a new process and waiting for the process to end without blocking the main ASP.NET thread. But I just want to know, if the process is still running on refresh of the page, so I think I can ask the System.Diagnostics.Process class for that.
Michael Wednesday, July 20, 2005
Just changed to start my prcoess directly and it works. But somehow it wouln't work as expected:
I'm trying to import some access databases into a SQL server using OPENDATASOURCE for linking the access database. Works all fine with doing that from ISQL with integrated security. After CreateProcessAsUser I get an error accessing the access database. I guess that it has to do with the fact, that CreateProcessAsUser doesn't load the user profile and maybe the TEMP environment isn't set.
I changed everything to serviced components. Now it works.
scott Wednesday, July 20, 2005
I'm glad you got it working, Michael.
Matt Thursday, October 20, 2005
Scott,

Have you figured out how to catch the output of a process spawned from CreateProcessAsUser in C# code? I've tried many things but am just not getting the job done. I can create the process and it does exactly what I want it to, but I just am not able to redirect the output. Here is what I've hacked at so far.

private void RunProcess(string commandLinePath)
{
try
{
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 = true;
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( "DuplicateTokenEx failed with " + Marshal.GetLastWin32Error() + "<br>");

else
Response.Write( "DuplicateTokenEx SUCCESS<br>" );

// This is a bit field that determines whether certain STARTUPINFO
// members are used when the process creates a window.
// Any combination of the following values can be specified:
const int STARTF_USESHOWWINDOW = 0x0000000;
const int STARTF_USESIZE = 0x00000002;
const int STARTF_USEPOSITION = 0x00000004;
const int STARTF_USECOUNTCHARS = 0x00000008;
const int STARTF_USEFILLATTRIBUTE = 0x00000010;
const int STARTF_FORCEONFEEDBACK = 0x00000040;
const int STARTF_FORCEOFFFEEDBACK = 0x00000080;
const int STARTF_USESTDHANDLES = 0x00000100;
const int STARTF_USEHOTKEY = 0x00000200;


IntPtr hOutRead = new IntPtr(0);
IntPtr hOutWrite = new IntPtr(0);

CreatePipe( ref hOutRead, ref hOutWrite, 0 );

Response.Write( hOutWrite.ToString() );

STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "";
si.hStdOutput = hOutWrite;
si.hStdInput = hOutRead;
si.dwFlags = STARTF_USESTDHANDLES;



PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
ret = CreateProcessAsUser(DupedToken,null,commandLinePath, ref sa, ref sa, true, 0, (IntPtr)0, "c:\\", ref si, out pi);

if (ret == false)
Response.Write( "CreateProcessAsUser failed with " + Marshal.GetLastWin32Error() + "<br>" );
else
{
Response.Write( "CreateProcessAsUser SUCCESS. The child PID is" + pi.dwProcessId + "<br>" );
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById( int.Parse( pi.dwProcessId.ToString() ) );
Response.Write( p.StartInfo.RedirectStandardOutput.ToString() );
//Response.Write( p.StandardOutput.ReadToEnd() );
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hOutRead);
CloseHandle(hOutWrite);
}

ret = CloseHandle(DupedToken);
if (ret == false)
Response.Write( Marshal.GetLastWin32Error() + "<br>");
else
Response.Write( "CloseHandle SUCCESS" + "<br>" );
}
catch( Exception err )
{
Response.Write( err.ToString() );
}
}
Scott Allen Friday, October 21, 2005
Matt: I did give that a try once. I believe there is a MSDN article on redirecting STDOUT for just such an occasion, but I never got it working with PInvoke, so I told the process to dump output to a file and read that in...
Sandy Tuesday, November 29, 2005
Did anyone ever get all of the CreateProcessAsUser code into a vb.net version? Would be much appreciated...thx
scott Wednesday, December 28, 2005
This update is from Mark Holden:

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();
}
}
}
Nathan Tuesday, January 17, 2006
What an absolute crock it is that you have to engage in this type of painstaking coding to do something that is anything but esoteric. Just terrible
David Robinson Wednesday, January 25, 2006
Scott, I love the updated code. I have one problem though: I am using .NET 2.0 framework, and the SetProcessHandle function in the Process class was changed. The new parameter for this function is a Microsoft.Win32.SafeHandles.SafeProcessHandle, which isn't available as a public type. The reason I'm not using the Process.Username, Password, and Domain is because when you use these members the process creates a window even when CreateNoWindow is specified. So I'm looking for a way to specify CreateAsUser, Redirect StdIn, StdOut, and StdErr streams, and create as SW_HIDE. The problem with having a window is that if you are trying to run in a Windows service you need to have a default desktop to create a window. So in essence, you can create and run a process in the service, but not under a specific user (which I don't find out until runtime, so I can't create the service under a user account). Bummer huh? Do you have an update to CreateAsUser for .NET 2.0? It would be much appreciated.
scott Thursday, January 26, 2006
David:

I'm sorry, I don't have an update on the issue.
Daniel Sinclair Friday, August 18, 2006
How's this?

Assembly assSystem = typeof(Process).Assembly;
Type oProcessManager = assSystem.GetType("System.Diagnostics.ProcessManager");
object oSafeProcessHandle = oProcessManager.InvokeMember("OpenProcess",
BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static,
null, this, new object[] { processInformation.dwProcessId, 0x100000, false });

//then

typeof(Process).InvokeMember("SetProcessHandle", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, this, new object[] { oSafeProcessHandle });


gravatar Arjuna Tuesday, October 20, 2009
I followed the steps as your mentoned in the above example but only thing it works fine in windows application but it fails in windows service application it is able to create a proces and it will appears in taskbar but UI will not be displayed to the user

Note : Am using win 7 OS
Please any can guide me how i can use 'CreateProcessAsUser' appi call
gravatar ihtesham Thursday, March 11, 2010
I am getting this error:

ErrorCreateProcessAsUser Error: 3

Can any one help...
gravatar Ike Thursday, July 8, 2010
@Arjuna

Yeah me too, I have the same problem.

I need to run an app from a service, but it doesn't bring the app to the current logged user's screen. Even if it the same credentials used to run the service.

How to do that? Help me!
Gary Wednesday, December 29, 2010
@Ike

change the @"" in the parameter to @"C:\Windows\notepad.exe"
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!