Run app as specified user from ASP.NET 2.0

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

Hi,

First please forgive that this is a repost as the first time I didn't have
my proper nospam email configured...

I'm writing a web service where one method will launch another .NET program
under a specified user's account. It launches fine as the NT
AUTHORITY\NETWORK SERVICE user when I dont specify a username/password for
the ProcessStartInfo but I am having trouble getting it to work when I
specify any other username/password pair. I immediately get "The application
failed to initialize properly (0xC0000142). I think this is because these
apps want to "interact" with the desktop. For instance devenv even though
everything is passed on the cmdline and there will be no windows and/or user
interaction.

Has anyone ever gotten this to work using Process.Start/ProcessStartInfo?

Note I do *not* want to impersonate the client nor have any of the other
code run during the processing of the request run using any credentials other
than NETWORK SERVICE so I don't think I want any form of impersonation that
can be configured through web.config, machine.config, etc. I only want the
third party app I want to launch and get the stderr/stdout streams back from
to be run in the specified user account.

Also this fails identically regardless of the setting of "Allow service to
interact with desktop" for the www service although idealy I would like it to
work with that not checked. For some strange reason I don't see a desktop
property on the managed ProcessStartInfo like there is on the unmanaged
startinfo struct... If necessary I will pinvoke Win32 api's but have failed
even getting that juju correct...

Thanks in advance :-)

-john

PS yes I know MSBuild would launch ok but I'm not sure it will build all
VS2003 sln files and I know it doesn't build deployment projects and several
of the sln files I need to build include deployment projects... (groan)
 
Hi Steven,

Thanks very much for your response. I was trying to save steps by using
CreateProcessWithLogon. I still had a mysterious problem with one user
account but it went away when I removed and recreated that account. So I have
switched over using the sample code you so helpfully embedded and now have it
launching successfully. Im not sure if I'll run into trouble later with the
user profile loading issue or not (or does logonuser do that too?). I would
be interested in hearing why you chose to use the logonuser/createasuser
combination over createwithlogon if you have the time.

Anyway now I'm just trying to get the reading/writing to/from
stdin/stdout/stderr working and was wondering if you also had sample code for
creating the handles and duplicates, getting the pipes read on worker
threads, etc?

Also I wanted to ask you if as part of your role here supporting the real
world you have a chance to give some feedback to the dev teams to make your
life and ours easier. If so please comment to them on their disturbing trend
of making so many useful parts of the framework assembly scoped or worse yet
private.
Here we see the processinfo and startupinfo that are assembly scoped in
System.Win32.NativeMethods so we cant use them and have to duplicate them.
When trying to get the stdin/stdout/stderr stuff working I additionally could
have used securityattribute, createpipe, duplicatehandle, etc. Again all
exist in the framework out of reach. Other examples in the past I've
encountered were getting the soapprotocolversion from a webservice, getting
the httpworkerrequest from the httpcontext, and many many more examples. This
means we all have to duplicate the classes/code, bug you guys when we have
problems getting it to work, worry our pinvoking slightly incorrect and is
slowly leaking unmanaged resources, and find working with this framework all
in all less productive than it could be.

sorry for the frustration induced mini rant.
you personally for one have been a great help!

thanks again :-)
-john

Steven Cheng said:
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);
 
PS: Here is the code Im using so you can see what I've tried so far...
---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Text;

internal class ProcessManager
{
private static readonly int MillisPerMinute =
(int)(TimeSpan.TicksPerMinute / TimeSpan.TicksPerMillisecond);
internal static readonly int OneMinute = MillisPerMinute;
internal static readonly int TwoMinutes = 2 * MillisPerMinute;
internal static readonly int ThreeMinutes = 3 * MillisPerMinute;
internal static readonly int ThirtyMinutes = 30 * MillisPerMinute;

private static readonly int s_LogonInteractive = 2;
private static readonly int s_LogonProviderDefault = 0;
private static readonly uint s_GenericAll = 0x10000000;
private static readonly int s_SecurityImpersonation = 2;
private static readonly int s_TokenType = 1;
private static readonly int s_StillActive = 259;
private static readonly int s_TimedOut = 0x102;

private static readonly int s_PipeSize = 16 * 1024;
private static readonly int s_CloseSource = 1;
private static readonly int s_SameAccess = 2;

internal static bool SyncRunHiddenProcess(string Domain, string UserName,
string Password, string WorkingDirectory, string Command, string Args, int
Timeout, out string StdOut, out string StdErr)
{
bool result = false;

StdOut = string.Empty;
StdErr = string.Empty;

try
{
WindowsImpersonationContext impersonation = null;

if( (!string.IsNullOrEmpty( UserName )) && (!string.IsNullOrEmpty(
Password )) )
{
impersonation = ImpersonateUser( Domain, UserName, Password );
}

RunWin32Command( Command, Args, WorkingDirectory, ref StdOut, Timeout );

if( impersonation != null ) impersonation.Undo();

result = true;
}
catch(Exception e)
{
StdErr = string.Format( "SyncRunHiddenProcess({0} {1}) failed! {2}",
Command, Args, e.Message );
}

return( result );
}

private static void RunWin32Command(string Command, string Args, string
WorkingDirectory, ref string StdOut, int Timeout)
{
IntPtr token = WindowsIdentity.GetCurrent().Token;
IntPtr dupedToken = IntPtr.Zero;
SecurityAttributes sa = new SecurityAttributes( false );

if( !DuplicateTokenEx( token, s_GenericAll, ref sa,
s_SecurityImpersonation, s_TokenType, out dupedToken ) )
{
throw new ApplicationException( string.Format( "DuplicateToken
failed({0})", Marshal.GetLastWin32Error() ) );
}

try
{
StartupInfo si = new StartupInfo( true );
ProcessInformation pi = new ProcessInformation();
IntPtr stdIn = IntPtr.Zero;
IntPtr stdOut = IntPtr.Zero;
IntPtr stdErr = IntPtr.Zero;

try
{
StringBuilder sbOut = new StringBuilder( s_PipeSize );
StringBuilder sbErr = new StringBuilder( s_PipeSize );

CreateStdPipes( ref si, out stdIn, out stdOut, out stdErr );

if( !CreateProcessAsUser( dupedToken, null, string.Format( "{0} {1}",
Command, Args ), ref sa, ref sa, false, 0, IntPtr.Zero, WorkingDirectory, ref
si, out pi ) )
{
throw new ApplicationException( string.Format( "CreateProcessAsUser
failed({0})", Marshal.GetLastWin32Error() ) );
}

try
{
uint exitCode;
int cb;

if( pi.Thread != IntPtr.Zero ) CloseHandle( pi.Thread );

GetExitCodeProcess( pi.Process, out exitCode );

if( exitCode == s_StillActive )
{
if( WaitForSingleObject( pi.Process, Timeout ) == s_TimedOut )
{
throw new ApplicationException( "CreateProcessAsUser timed out" );
}
else GetExitCodeProcess( pi.Process, out exitCode );
}

// This needs to be moved to a worker thread once I get it working at
all...
ReadPipe( stdErr, out sbOut, sbOut.Capacity, out cb, IntPtr.Zero );
ReadPipe( stdOut, out sbErr, sbErr.Capacity, out cb, IntPtr.Zero );

if( exitCode != 0 )
{
throw new ApplicationException( string.Format( "CreateProcessAsUser
exited with exitcode({0})\n{1}", exitCode, sbErr ) );
}
else
{
StdOut = sbOut.ToString();
}
}
finally{ if( pi.Process != IntPtr.Zero ) CloseHandle( pi.Process ); }
}
finally
{
if( si.StdInput != IntPtr.Zero ) CloseHandle( si.StdInput );
if( si.StdOutput != IntPtr.Zero ) CloseHandle( si.StdOutput );
if( si.StdError != IntPtr.Zero ) CloseHandle( si.StdError );

if( stdIn != IntPtr.Zero ) CloseHandle( stdIn );
if( stdOut != IntPtr.Zero ) CloseHandle( stdOut );
if( stdErr != IntPtr.Zero ) CloseHandle( stdErr );
}
}
finally{ CloseHandle( dupedToken ); }
}

private static WindowsImpersonationContext ImpersonateUser(string Domain,
string UserName, string Password)
{
WindowsImpersonationContext impersonation = null;
IntPtr token;
IntPtr dupedToken;

if( LogonUser( UserName, Domain, Password, s_LogonInteractive,
s_LogonProviderDefault, out token ) != 0 )
{
try
{
if( DuplicateToken( token, s_SecurityImpersonation, out dupedToken ) !=
0 )
{
try
{
WindowsIdentity identity = new WindowsIdentity( dupedToken );

impersonation = identity.Impersonate();

if( impersonation == null ) throw new ApplicationException(
string.Format( @"ImpersonateUser {0}\{1} failed!", Domain, UserName ) );
}
finally{ CloseHandle( dupedToken ); }
}
}
finally{ CloseHandle( token ); }
}

return( impersonation );
}

private static void CreateStdPipes(ref StartupInfo si, out IntPtr
ChildStdIn, out IntPtr ChildStdOut, out IntPtr ChildStdErr)
{
SecurityAttributes sa1 = new SecurityAttributes( true );
IntPtr stdInRead = IntPtr.Zero;
IntPtr stdInWrite = IntPtr.Zero;
IntPtr stdOutRead = IntPtr.Zero;
IntPtr stdOutWrite = IntPtr.Zero;
IntPtr stdErrRead = IntPtr.Zero;
IntPtr stdErrWrite = IntPtr.Zero;
IntPtr dupedStdIn = IntPtr.Zero;
IntPtr dupedStdOut = IntPtr.Zero;
IntPtr dupedStdErr = IntPtr.Zero;
bool result = false;

si.StdInput = IntPtr.Zero;
si.StdOutput = IntPtr.Zero;
si.StdError = IntPtr.Zero;

ChildStdIn = IntPtr.Zero;
ChildStdOut = IntPtr.Zero;
ChildStdErr = IntPtr.Zero;

try
{
if( CreatePipe( out stdInRead, out stdInWrite, ref sa1, s_PipeSize ) &&
CreatePipe( out stdOutRead, out stdOutWrite, ref sa1, s_PipeSize ) &&
CreatePipe( out stdErrRead, out stdErrWrite, ref sa1, s_PipeSize ) &&
DuplicateHandle( GetCurrentProcess(), stdInWrite, GetCurrentProcess(),
out dupedStdIn, 0, false, s_SameAccess | s_CloseSource ) &&
DuplicateHandle( GetCurrentProcess(), stdOutRead, GetCurrentProcess(),
out dupedStdOut, 0, false, s_SameAccess | s_CloseSource ) &&
DuplicateHandle( GetCurrentProcess(), stdErrRead, GetCurrentProcess(),
out dupedStdErr, 0, false, s_SameAccess | s_CloseSource ) )
{
si.StdInput = stdInRead;
si.StdOutput = stdOutWrite;
si.StdError = stdErrWrite;

ChildStdIn = dupedStdIn;
ChildStdOut = dupedStdOut;
ChildStdErr = dupedStdErr;

result = true;
}
else throw new ApplicationException( "CreatePipes failed" );
}
finally
{
if( !result )
{
if( stdInRead != IntPtr.Zero ) CloseHandle( stdInRead );
if( stdOutWrite != IntPtr.Zero ) CloseHandle( stdOutWrite );
if( stdErrWrite != IntPtr.Zero ) CloseHandle( stdErrWrite );
if( dupedStdOut != IntPtr.Zero ) CloseHandle( dupedStdOut );
if( dupedStdErr != IntPtr.Zero ) CloseHandle( dupedStdErr );
}
}
}

[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
public int Length;
public IntPtr SecurityDescriptor;
public bool InheritHandle;

internal SecurityAttributes(bool Inherit): this()
{
InheritHandle = Inherit;
Length = Marshal.SizeOf( this );
SecurityDescriptor = IntPtr.Zero;
}
}

[StructLayout(LayoutKind.Sequential)]
public struct ProcessInformation
{
public IntPtr Process;
public IntPtr Thread;
public uint ProcessId;
public uint ThreadId;
}

[StructLayout(LayoutKind.Sequential)]
private struct StartupInfo
{
public int cb;
public string Reserved;
public string Desktop;
public string Title;
public uint X;
public uint Y;
public uint XSize;
public uint YSize;
public uint XCountChars;
public uint YCountChars;
public uint FillAttribute;
public uint Flags;
public short ShowWindow;
public short cbReserved2;
public IntPtr Reserved2;
public IntPtr StdInput;
public IntPtr StdOutput;
public IntPtr StdError;

internal StartupInfo(bool init): this()
{
cb = Marshal.SizeOf( this );
Desktop = string.Empty;
Flags = 0x100 | 0x400; // STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
ShowWindow = 0; // SW_HIDE
}
}

[DllImport("advapi32.dll",EntryPoint="LogonUserW",CharSet=CharSet.Unicode,SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern int LogonUser(string UserName, string Domain, string
Password, int CreateFlags, int ProviderFlags, out IntPtr Token);

[DllImport("advapi32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern int DuplicateToken(IntPtr Token, int
ImpersonationLevel, out IntPtr DupedToken);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private extern static bool CloseHandle(IntPtr Handle);

[DllImport("advapi32.dll",EntryPoint="CreateProcessAsUserW",CharSet=CharSet.Unicode,SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private extern static bool CreateProcessAsUser(IntPtr Token, string
ApplicationName, string CommandLine, ref SecurityAttributes
ProcessAttributes, ref SecurityAttributes ThreadAttributes, bool
InheritHandle, int CreationFlags, IntPtr Environment, string
CurrentDirectory, ref StartupInfo StartupInfo, out ProcessInformation
ProcessInformation);

[DllImport("advapi32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private extern static bool DuplicateTokenEx(IntPtr Token, uint
DesiredAccess, ref SecurityAttributes ThreadAttributes, int TokenType, int
ImpersonationLevel, out IntPtr DupedToken);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
static extern bool GetExitCodeProcess(IntPtr Process, out uint ExitCode);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern Int32 WaitForSingleObject(IntPtr Handle, Int32 Wait);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern bool CreatePipe(out IntPtr ReadPipe, out IntPtr
WritePipe, ref SecurityAttributes sa, int Size);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern bool DuplicateHandle(IntPtr Process1, IntPtr Handle,
IntPtr Process2, out IntPtr DupedHandle, int Flags, bool Inheritable, int
Options);

[DllImport("kernel32.dll",EntryPoint="ReadFile",CharSet=CharSet.Unicode,SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern bool ReadPipe(IntPtr Pipe, out StringBuilder Sb, int
Size, out int BytesRead, IntPtr Overlapped);

[DllImport("kernel32.dll",SetLastError=true,CallingConvention=CallingConvention.StdCall),SuppressUnmanagedCodeSecurity]
private static extern IntPtr GetCurrentProcess();
}
 
Thanks for your reply and the further information.

yes, I've noticed that accessing the output and err buffer of the new
process through PINVOKE is a hard work. I'll perform some further research
on this and will update you as soon as I get any new result. In addition,
I've also sent the problem with the managed Process class to our internal
technical group to inform them of this issue.


Thanks for your understanding!

Sincerely,

Steven Cheng

Microsoft MSDN Online Support Lead


This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi John,

Just post to inform some further results I've got, here is my current test
results:

1. I first tried redirecting the devenv.exe's output (build info) to a
file, and that can be done through the following means(i've tested):

i) Use the natural /out option of devenv.exe to redirect the output to a
file

ii) Use cmd.exe /c to execute the devenv build command and use the ">"
flag to redirect the cmd output to a file.

Therefore, if in-memory redirection is possible, this would be a workaround
you can consider(create a temp file for the output in your asp.net paeg and
read the content out).


2. I've tried use pinvoke to create NamedPipe and redirect process
stdout/stderr into the pipe and read it out. I did get the code to
work(test in console program first) and get the standard output when
testing through some simple command such as "cmd /?". However, when
executing the "devenv" command, I still can not get the stdout or stderr,
actually the whole creating process and write/read pipe code has been
executed, but there is nothing read out from the pipe. Therefore, I'm
wondering whether it is the devenv.exe itself which has hook the output and
manually print it out (when we execute it in command prompt), but can not
get it from stdout when we programmtically launch it.

I'll perform some further research on this, try creating a pure win32
program to create subprocess and read stdout/stderr, if still unable to get
it, I'm afraid it is the devenv.exe's limitation that make us fail to
programmtically get the output in-memory. I'll update you as soon as I get
the results.

Thanks for your understanding and patience!

Sincerely,

Steven Cheng

Microsoft MSDN Online Support Lead


This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Steven,

Sorry for the long pause but I've being busymaking several valiant but
unsuccessful attempts to resolve this.

I've tried to use the command line redirection tact but unfortunately that
seems to fail when the .sln file being built has projects in it that contain
pre/post build events that do things like md, copy, etc. The cmd.exe's that
the pre/post build events spawn all fail with exitcode 128.

Then i tried using ildasm to inspect what Prcocess.StartWithCreateProcess
did and duplicate that as precisely as i could. This was not exactly
possible due to the non public scope (grrr) of a lot of the helper
classes/methods. I used ildasm on those too to get as close as possible.
After trying safehandles and intptrs i still wound up without success.

I also even tried going back to the original Process.Start based
implementation I started with but using the corprofiler hooks to catch the
prejit event on the Microsoft.Win32.NatvieMethods.STARTUPINFO..ctor and munge
the bytecode to initialize the desktop field to "" (via
Marshall.Runtime.InteropServices.StringToCoTaskMemUni( string.Empty ))
instead of null. This still failed with the 142 so I think that even with the
field set to "" Process.Start( ) wont work because it boils down to
CreateProcessWithLogon instead of CreateProcessAsUser.

The short of it is I have invested a significant amount of time/effort into
this and still dont seem to be able to get what should be a simple common
thing to work.

I'm starting to feel brutally smack'd down by ASP.NET. Is there any chance
that someone on the ASP.NET team itself has already constructed a solution
for this shortcoming that they'd be willing to share with their customer base?

thanks again,
-john
 
Hello John,

Thanks for your followup.

Yes, creating a process as specific user in ASP.NET environment is a hard
work here. As you can seen in my previous thread, I've tried some pinvoke
approach with the namepipe, however, the devenv seems a special case that
the output seems not available in the redirect pipe. I've attached my test
project in the previous message which use pinvoke code to run application
under specific user and read stdout and stderr. Normal application such
cmd, ping can successfully redirect the output into pipe, however, the
devenv seems handle the output particularly.

BTW, do you think using temp file as the redirect ouput doable in your
scenario? It is convenient to redirect the output to a file and read the
output content from that file.

In addition, I've discussed this question with some other ASPNET engineers,
currently for spawning a new process in ASP.NET under a specifci account,
the pinvoke(through createProcessAsuser) is their recommended approach,
though it's a bit pity that it didn't work against the devenv application.

Anyway, as for this issue, I suggest you submit this request in our MSDN
product feedback center so that the dev team engineers and also hear more
from the community on such issue. And any comments and feedback from you is
really appreciated.

http://connect.microsoft.com/feedback/default.aspx?SiteID=210

Please feel free to let me know if there is anything else we can help.

Sincerely,

Steven Cheng

Microsoft MSDN Online Support Lead
 
hi John,

I read your blog and it helps me a lot.. but i am having a problem... I am
able to run the exe on development machine, but on client machine it doesn,t
work... please help...

Thanks in Advance
Yogesh Ghare

Steven Cheng said:
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);
 
Hi John and Steven

I ran into requirement where i had to do something similar to what John is
using.
The problem i'm having is that when i call DuplicateTokenEx, i get an
"Overlapped I/O is in progress" error (997)

Any help or suggestions?
 
Back
Top