|
I finally got around to updating this project and submitted the requested changes. I tried to incorporate all of the suggestions into the new solution. I haven't thoroughly tested the code, but I think it's pretty sound.
-=( atchr )=-
|
|
|
|
|
When I launch a process using this class as another user with a higher privillege level than the "real" user running it, I am not allowed to access the process using the m_result.Process handle.
I am doing it like this:
if(!result.HasException) {
result.Process.EnableRaisingEvents = true; result.Process.Exited += new EventHandler(Process_Exited); }
Trying to set EnableRaisingEvents to true results in a Win32Exception being thrown with an "Access is denied" message. I can only think this is caused because I created this process as another user, so the current "real" user has no access rights to the new process.
Am I doing something horribly incorrectly, or is there a better way to do this?
Thanks.
qasr
|
|
|
|
|
If the account that you are logged in with does not have access to the new process then it will raise an exception (Access is denied) when you try to modify the process. This is exactly like if you open up Task Manager, go to the Processes tab, and try to end or change the priority on a process that you don't own or don't have rights to. You can easily wrap the attempts to alter the process in a try/catch and determine if you have access or not.
-=( atchr )=-
|
|
|
|
|
Hi! Nice article. And it works. But that's all.
There are quite a lot possibilities to improve it:
1. The handles (hProcess, hThread) retuned within the ProcessInformation struct have to be closed! This never happens within your code. This is realy an important thing. You can close them right after the process has been successfully started, becaurse you do not query any information with this handles:
bool ret = CreateProcessWithLogonW(...);
if(!ret)
return new RunAsResult(...Win32Exception(...));
else {
CloseHandle(m_pi.hProcessId);
CloseHandle(m_pi.hThreadId);
return new RunAsResult(m_pi.dwProcessId);
}
2. There is a lot of redundant code witch makes the maintance realy ugly. The central logic (the API - Call, error handling), is done within every overloaded version of the function (copy & paste?). It would be MUCH better to implement the central logic within one overload function (normaly the one with the most parameters). The other overloads simply fill out default values and call the "big" function:
sample:
public static RunAsResult StartProcess(string user, string domain, string password, string commandline, int logonflags, string appname, uint creationflags, string currentdirectory, int startupflags)
{
bool ret = CreateProcessWithLogonW(...)
if (!ret) ...
}
public static RunAsResult StartProcess(string user, string domain, string password, string commandline)
{
uint creationFlags = (uint)PriorityFlags.NormalPriority | (uint)CreationFlags.UnicodeEnvironment;
return StartProcess(user, domain, password, commandline, (int)LogonFlags.WithProfile, null, commandline, creationFlags, null, (int)StartUpInfoFlags.UseCountChars);
}
3. Why do you copy parameter values (m_)? Remember that parameters are passed by value by default, so the runtime will perform the copy for you. But you even don't modify them within the function. And what does the prefix m_ meen? Member???
public static RunAsResult StartProcess(string user, string domain, string password, string commandline)
{
string m_username = user;
string m_domain = domain;
...
}
4. If you create a Win32Exception with the default constructor, the .Net runtime will automaticly call the GetLastError() function, and the FormatMessage() function, witch returns the Message of the error, witch is very usefull:
Write return new RunAsResult(new System.ComponentModel.Win32Exception(); instead of return new RunAsResult(new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())); !
5. Remove the "RunAsResult" class. Less is often more! This class contains only the Process object itself (when successfull), and information about the error (if unsuccessfull). The Process object can be returned by the "StartProcess" function itself. AND: Error state should NOT be returned as a return value. Throw exceptions instead! What happens if the developer using your component don't checks for the "HasException" value? -> The exception will be ignored witch can caurse other errors. If you throw exceptions they cannot be ignored. The developer have to catch them. Of caurse he can ignore (not (re)throw) the catched exception, but the error can't "get lost":
public static System.Diagnostics.Process StartProcess(...){
bool ret = CreateProcessWithLogonW(...);
if (!ret)
throw new Win32Exception();
else
return Process.GetProcessById(m_pi.dwProcessId);
}
6. The working directory is always "C:\" within your code. It would be better to provide a parameter for it.
7. Accessibility Levels are not set well. Why do you use public structs with internal members? Make the structs itself internal too. You can also think of declaring them as neested, private classes.
Think about it!
best regards, Günter Prossliner
|
|
|
|
|
Wow, thanks for the feedback. That's high quality stuff. I'll take your feedback into consideration and when I get a chance I'll re-write a bit and update the article.
-=( atchr )=-
|
|
|
|
|
Phyl:
nice suggestions, Can you explain why the handles has to be closed ?
thanks,
Rolando
|
|
|
|
|
Have a look at the official MS documentation on MSDN: http://msdn.microsoft.com/library/en-us/dllproc/base/createprocessasuser.asp[^]:
lpProcessInformation
[out] Pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.
Handles in PROCESS_INFORMATION must be closed with CloseHandle when they are no longer needed.
They have to be closed.
|
|
|
|
|
Phyl:
"...when they are no longer needed."
Do I have to wait that the process ends to close the handles ?
Rolando
|
|
|
|
|
No, you can close them right after the process has been started. These handles have nothing to do with the ProcessId / ThreadId. They are just "access handles" (see OpenProcess in MSDN for further information) to query information from the process / thread. So when you close these handles the process will nevermind.
|
|
|
|
|
Hello out there
I took all the input above and tried to create such an adjusted class. Unfortunately I failed on a known but unsolved compiler error in C# (Use of possibly unassigned field) and didn't find a workaround for some time and as the company I work for anyway programs in VB, I just took the freedom to switch to VB... (no please, keep this rotten tomatoes in the box, ah, no! NOOOO!)
Here's the code:
<pre>
Imports System
Imports System.Text
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Security.Permissions
Imports System.Diagnostics
Imports System.ComponentModel
<Assembly: SecurityPermissionAttribute(SecurityAction.RequestMinimum, UnmanagedCode:=True)>
''<summary>Runs a command with other credentials</summary>
''<author><name>Dewey Vozel</name><email>***beep***</email></author>
''<author><alias>Phyl</alias><name>Günter Prossliner</name><email>***beep***</email></author>
''<author><name>Christoph Hafner</name><email>***beep***</email></author>
<SecurityPermission(SecurityAction.Demand, ControlPrincipal:=True)> _
Public Class RunAs
'Private Fields
Private Shared _DefaultLogonFlag As LogonFlag = LogonFlag.WithProfile
Private Shared _DefaultCreationFlag As CreationFlag = CreationFlag.Default
Private Shared _DefaultPriority As PriorityFlag = PriorityFlag.NormalPriority
'Constructors
Private Sub New()
End Sub
'Public Properties
Public Shared Property DefaultLogonFlag() As LogonFlag
Get
Return _DefaultLogonFlag
End Get
Set(ByVal Value As LogonFlag)
If (Value = LogonFlag.Undefined) Then Throw New ArgumentException("You cannot specify 'Undefined'!")
_DefaultLogonFlag = Value
End Set
End Property
Public Shared Property DefaultCreationFlag() As CreationFlag
Get
Return _DefaultCreationFlag
End Get
Set(ByVal Value As CreationFlag)
If (Value = CreationFlag.Undefined) Then Throw New ArgumentException("You cannot specify 'Undefined'!")
_DefaultCreationFlag = Value
End Set
End Property
Public Shared Property DefaultPriority() As PriorityFlag
Get
Return _DefaultPriority
End Get
Set(ByVal Value As PriorityFlag)
If (Value = PriorityFlag.Undefined) Then Throw New ArgumentException("You cannot specify 'Undefined'!")
_DefaultPriority = Value
End Set
End Property
'Public Methods
''<summary>Executes a commandline statement with "cmd.exe".</summary>
''<returns>Returns a Process object, NULL (if the process terminated faster than I could reference it) or throws a Win32Exception.</returns>
Public Overloads Shared Function ExecuteCommandWithCmd(ByVal aUserName As String, ByVal aPassword As String, ByVal aCommand As String) As Process
Dim myCommand As New StringBuilder
myCommand.Append(Environment.SystemDirectory)
myCommand.Append("\cmd.exe /C ")
myCommand.Append(aCommand)
Return PrivateCreateProcess(aUserName, Nothing, aPassword, myCommand.ToString, LogonFlag.Undefined, Nothing, CreationFlag.Undefined, PriorityFlag.Undefined, Nothing, Nothing, Nothing)
End Function
''<summary>Executes a commandline statement with "command.com".</summary>
''<returns>Returns a Process object, NULL (if the process terminated faster than I could reference it) or throws a Win32Exception.</returns>
Public Overloads Shared Function ExecuteCommandWithCommandCom(ByVal aUserName As String, ByVal aPassword As String, ByVal aCommand As String) As Process
Dim myCommand As New StringBuilder
myCommand.Append(Environment.SystemDirectory)
myCommand.Append("\command.com /C ")
myCommand.Append(aCommand)
Return PrivateCreateProcess(aUserName, Nothing, aPassword, myCommand.ToString, LogonFlag.Undefined, Nothing, CreationFlag.Undefined, PriorityFlag.Undefined, Nothing, Nothing, Nothing)
End Function
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
Public Overloads Shared Function CreateProcess(ByVal aUserName As String, ByVal aPassword As String, ByVal aCommand As String) As Process
Return PrivateCreateProcess(aUserName, Nothing, aPassword, aCommand, LogonFlag.Undefined, Nothing, CreationFlag.Undefined, PriorityFlag.Undefined, Nothing, Nothing, Nothing)
End Function
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
Public Overloads Shared Function CreateProcess(ByVal aUserName As String, ByVal aDomainName As String, ByVal aPassword As String, ByVal aCommand As String) As Process
Return PrivateCreateProcess(aUserName, aDomainName, aPassword, aCommand, LogonFlag.Undefined, Nothing, CreationFlag.Undefined, PriorityFlag.Undefined, Nothing, Nothing, Nothing)
End Function
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
Public Overloads Shared Function CreateProcess(ByVal aUserName As String, ByVal aDomainName As String, ByVal aPassword As String, ByVal anApplication As String, ByVal parameters As String) As Process
Return PrivateCreateProcess(aUserName, aDomainName, aPassword, parameters, LogonFlag.Undefined, anApplication, CreationFlag.Undefined, PriorityFlag.Undefined, Nothing, Nothing, Nothing)
End Function
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
Public Overloads Shared Function CreateProcess(ByVal aUserName As String, ByVal aDomainName As String, ByVal aPassword As String, ByVal aCommand As String, ByVal aLogonFlag As LogonFlag, ByVal anApplication As String, ByVal aCreationFlag As CreationFlag, ByVal aPriority As PriorityFlag, ByVal anEnvironment As IntPtr, ByVal aCurrentDirectory As String) As Process
Return PrivateCreateProcess(aUserName, aDomainName, aPassword, aCommand, aLogonFlag, anApplication, aCreationFlag, aPriority, anEnvironment, aCurrentDirectory, Nothing)
End Function
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
''<remarks>This method is untested.</remarks>
''<param name="aUserName">This is the name of the user account to log on to. If you use the UPN format, user@domain, the Domain parameter must be NULL. The user account must have the Log On Locally permission on the local computer.</param>
''<param name="aDomainName">Specifies the name of the domain or server whose account database contains the user account. If this parameter is NULL, the user name must be specified in UPN format.</param>
''<param name="aPassword">Specifies the clear-text password for the user account.</param>
''<param name="aCommand">Specifies the command line to execute. The maximum length of this string is 32,000 characters. The commandline parameter can be NULL. In that case, the function uses the string pointed to by appname as the command line. If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period with no extension, or if the file name contains a path, .exe is not appended. If the file name does not contain a directory path, the system searches for the executable file.</param>
''<param name="aLogonFlags">Logon option. This parameter can be zero or one value from the LogonFlags enum.</param>
''<param name="anApplication">Specifies the module to execute. The specified module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer. The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. If the file name does not contain an extension, .exe is assumed. Therefore, if the file name extension is .com, this parameter must include the .com extension. The appname parameter can be NULL. In that case, the module name must be the first white space-delimited token in the commandline string. If the executable module is a 16-bit application, appname should be NULL, and the string pointed to by commandline should specify the executable module as well as its arguments.</param>
''<param name="aCreatingFlag">Controls how the process is created</param>
''<param name="aPriorityFlag">Controls the new process's priority class, which is used to determine the scheduling priorities of the process's threads.</param>
''<param name="anEnvironment"></param>
''<param name="aCurrentDirectory">Specifies the full path to the current directory for the process. The string can also specify a UNC path. If this parameter is NULL, the new process will have the same current drive and directory as the calling process.</param>
''<param name="aStartUpInfo">Specifies the window station, desktop, standard handles, and appearance of the main window for the new process.</param>
''<returns>Returns a Process object, NULL (if the process terminated faster than I could reference it) or throws a Win32Exception.</returns>
Public Overloads Shared Function CreateProcess(ByVal aUserName As String, ByVal aDomainName As String, ByVal aPassword As String, ByVal aCommand As String, ByVal aLogonFlag As LogonFlag, ByVal anApplication As String, ByVal aCreationFlag As CreationFlag, ByVal aPriority As PriorityFlag, ByVal anEnvironment As IntPtr, ByVal aCurrentDirectory As String, ByRef aStartUpInfo As StartUpInfo) As Process
Return PrivateCreateProcess(aUserName, aDomainName, aPassword, aCommand, aLogonFlag, anApplication, aCreationFlag, aPriority, anEnvironment, aCurrentDirectory, aStartUpInfo)
End Function
'Imported Interface
''<summary>The CreateProcessWithLogonW function creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function CreateProcessWithLogonW(ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, ByVal dwLogonFlags As Int32, ByVal applicationName As String, ByVal commandLine As StringBuilder, ByVal creationFlags As Int32, ByVal environment As IntPtr, ByVal currentDirectory As String, ByRef sui As StartUpInfo, ByRef processInfo As ProcessInformation) As Boolean
End Function
''+++++++ here weiterfahren +++++++
'<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
'Private Shared Function CreatePipe(ByRef phReadPipe As Long, ByRef phWritePipe As Long, ByRef lpPipeAttributes As Security_Attributes, ByVal nSize As Long) As Long
'End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function CloseHandle(ByVal aHandle As IntPtr) As Boolean
End Function
'Private Methods
''<summary>Creates a new process and its primary thread. The new process then runs the specified executable file in the security context of the specified credentials (user, domain, and password). It can optionally load the user profile for the specified user.</summary>
''<remarks>This method is untested.</remarks>
''<param name="aUserName">This is the name of the user account to log on to. If you use the UPN format, user@domain, the Domain parameter must be NULL. The user account must have the Log On Locally permission on the local computer.</param>
''<param name="aDomainName">Specifies the name of the domain or server whose account database contains the user account. If this parameter is NULL, the user name must be specified in UPN format.</param>
''<param name="aPassword">Specifies the clear-text password for the user account.</param>
''<param name="aCommand">Specifies the command line to execute. The maximum length of this string is 32,000 characters. The commandline parameter can be NULL. In that case, the function uses the string pointed to by appname as the command line. If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period with no extension, or if the file name contains a path, .exe is not appended. If the file name does not contain a directory path, the system searches for the executable file.</param>
''<param name="aLogonFlags">Logon option. This parameter can be zero or one value from the LogonFlags enum.</param>
''<param name="anApplication">Specifies the module to execute. The specified module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer. The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. If the file name does not contain an extension, .exe is assumed. Therefore, if the file name extension is .com, this parameter must include the .com extension. The appname parameter can be NULL. In that case, the module name must be the first white space-delimited token in the commandline string. If the executable module is a 16-bit application, appname should be NULL, and the string pointed to by commandline should specify the executable module as well as its arguments.</param>
''<param name="aCreatingFlag">Controls how the process is created</param>
''<param name="aPriorityFlag">Controls the new process's priority class, which is used to determine the scheduling priorities of the process's threads.</param>
''<param name="anEnvironment"></param>
''<param name="aCurrentDirectory">Specifies the full path to the current directory for the process. The string can also specify a UNC path. If this parameter is NULL, the new process will have the same current drive and directory as the calling process.</param>
''<param name="aStartUpInfo">Specifies the window station, desktop, standard handles, and appearance of the main window for the new process.</param>
''<returns>Returns a Process object, nothing (if the process terminated faster than I could reference it) or throws a Win32Exception.</returns>
Private Overloads Shared Function PrivateCreateProcess(ByVal aUserName As String, ByVal aDomainName As String, ByVal aPassword As String, ByVal aCommand As String, ByVal aLogonFlag As LogonFlag, ByVal anApplication As String, ByVal aCreationFlag As CreationFlag, ByVal aPriority As PriorityFlag, ByVal anEnvironment As IntPtr, ByVal aCurrentDirectory As String, ByVal aStartUpInfo As Object) As Process
'Initialize variables
Dim myProcessInfo As New ProcessInformation
Dim myLogonFlag As Integer = CInt(IIf(aLogonFlag = LogonFlag.Undefined, DefaultLogonFlag, aLogonFlag))
Dim myCreationFlag As Integer = CInt(IIf(aCreationFlag = CreationFlag.Undefined, DefaultCreationFlag, aCreationFlag)) _
Or CInt(IIf(aPriority = PriorityFlag.Undefined, DefaultPriority, aPriority))
Dim myCommandLine As New StringBuilder(aCommand) 'nothing results in String.Empty
'Initialize StartUpInfo object
Dim myStartUpInfo As StartUpInfo
If (aStartUpInfo Is Nothing) Then
myStartUpInfo = New StartUpInfo
myStartUpInfo.cb = Marshal.SizeOf(myStartUpInfo)
myStartUpInfo.lpReserved = Nothing
myStartUpInfo.lpDesktop = Nothing
myStartUpInfo.lpTitle = Nothing
myStartUpInfo.dwX = 0
myStartUpInfo.dwY = 0
myStartUpInfo.dwXSize = 0
myStartUpInfo.dwYSize = 0
myStartUpInfo.dwXCountChars = 255
myStartUpInfo.dwYCountChars = 800
myStartUpInfo.dwFillAttribute = 0
myStartUpInfo.dwFlags = 0
myStartUpInfo.wShowWindow = 0
myStartUpInfo.cbReserved2 = 0
myStartUpInfo.lpReserved2 = Nothing
myStartUpInfo.hStdInput = Nothing
myStartUpInfo.hStdOutput = Nothing
myStartUpInfo.hStdError = Nothing
Else
myStartUpInfo = CType(aStartUpInfo, StartUpInfo)
End If
'Adjust login name / domain name
Dim myIndex As Integer
If (aDomainName Is Nothing) OrElse (aDomainName = String.Empty) Then
'Extract domain from a login like "microsoft\BGates"
myIndex = aUserName.IndexOf("\"c)
If (myIndex > -1) Then
aDomainName = aUserName.Substring(0, myIndex)
aUserName = aUserName.Substring(myIndex + 1)
Else
'Extract domain from a login like "BGates@microsoft.com"
myIndex = aUserName.IndexOf("@"c)
If (myIndex > -1) Then
aUserName = aUserName.Substring(0, myIndex)
aDomainName = aUserName.Substring(myIndex + 1)
myIndex = aDomainName.IndexOf("."c)
If (myIndex > -1) Then
aDomainName = aDomainName.Substring(0, myIndex)
End If
End If
'Take the domain where the current user is logged on
aDomainName = Environment.UserDomainName()
End If
Else
'Remove the domain from the user name if they are the same
myIndex = aUserName.IndexOf(aDomainName & "\"c)
If (myIndex = 0) Then
aUserName = aUserName.Substring(aDomainName.Length + 1)
Else
myIndex = aUserName.IndexOf("@"c & aDomainName)
If (myIndex > -1) Then
aUserName = aUserName.Substring(0, myIndex)
End If
End If
End If
'Create process
Try
Dim Okay As Boolean = CreateProcessWithLogonW(aUserName, aDomainName, aPassword, myLogonFlag, anApplication, myCommandLine, myCreationFlag, anEnvironment, aCurrentDirectory, myStartUpInfo, myProcessInfo)
If (Okay) Then
Dim myProcess As Process = Nothing
Try
'Watch out: The process may be already terminated and not accessible anymore!
myProcess = Process.GetProcessById(myProcessInfo.dwProcessId)
Catch
End Try
Return myProcess
End If
Finally
PrivateCloseHandle(myProcessInfo.hProcess)
PrivateCloseHandle(myProcessInfo.hThread)
End Try
'Throw exception if not successful
Throw New Win32Exception
End Function
Private Shared Sub PrivateCloseHandle(ByVal aHandle As IntPtr)
Try
CloseHandle(aHandle)
Catch
End Try
End Sub
'**********************************************************************
' Inner Classes / Structures / Enums
'**********************************************************************
''<summary>The STARTUPINFO structure is used with the CreateProcess, CreateProcessAsUser, and CreateProcessWithLogonW functions to specify the window station, desktop, standard handles, and appearance of the main window for the new process.</summary>
<StructLayout(LayoutKind.Sequential)> _
Public Structure StartUpInfo
''<summary>Size of the structure, in bytes.</summary>
Public cb As Int32
''<summary>Reserved. Set this member to NULL before passing the structure to CreateProcess.</summary>
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpReserved As String
''<summary>Pointer to a null-terminated string that specifies either the name of the desktop, or the name of both the desktop and window station for this process. A backslash in the string indicates that the string includes both the desktop and window station names.</summary>
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpDesktop As String
''<summary>For console processes, this is the title displayed in the title bar if a new console window is created. If NULL, the name of the executable file is used as the window title instead. This parameter must be NULL for GUI or console processes that do not create a new console window.</summary>
<MarshalAs(UnmanagedType.LPTStr)> _
Public lpTitle As String
''<summary>The x offset of the upper left corner of a window if a new window is created, in pixels.</summary>
Public dwX As Int32
''<summary>The y offset of the upper left corner of a window if a new window is created, in pixels.</summary>
Public dwY As Int32
''<summary>The width of the window if a new window is created, in pixels.</summary>
Public dwXSize As Int32
''<summary>The height of the window if a new window is created, in pixels.</summary>
Public dwYSize As Int32
''<summary>If a new console window is created in a console process, this member specifies the screen buffer width, in character columns.</summary>
Public dwXCountChars As Int32
''<summary>If a new console window is created in a console process, this member specifies the screen buffer height, in character rows.</summary>
Public dwYCountChars As Int32
''<summary>The initial text and background colors if a new console window is created in a console application.</summary>
Public dwFillAttribute As Int32
''<summary>Bit field that determines whether certain StartUpInfo members are used when the process creates a window.</summary>
Public dwFlags As Int32
''<summary>This member can be any of the SW_ constants defined in Winuser.h.</summary>
Public wShowWindow As Int16
''<summary>Reserved for use by the C Runtime must be zero.</summary>
Public cbReserved2 As Int16
''<summary>Reserved for use by the C Runtime must be null.</summary>
Public lpReserved2 As IntPtr
''<summary>A handle to be used as the standard input handle for the process.</summary>
Public hStdInput As IntPtr
''<summary>A handle to be used as the standard output handle for the process.</summary>
Public hStdOutput As IntPtr
''<summary>A handle to be used as the standard error handle for the process.</summary>
Public hStdError As IntPtr
End Structure
''<summary>
''The ProcessInformation structure is filled in by either the CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, or CreateProcessWithTokenW function with information about the newly created process and its primary thread.
''</summary>
<StructLayout(LayoutKind.Sequential)> _
Public Structure ProcessInformation
''<summary>Handle to the newly created process. The handle is used to specify the process in all functions that perform operations on the process object.</summary>
Public hProcess As IntPtr
''<summary>Handle to the primary thread of the newly created process. The handle is used to specify the thread in all functions that perform operations on the thread object.</summary>
Public hThread As IntPtr
''<summary>Value that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</summary>
Public dwProcessId As Int32
''<summary>Value that can be used to identify a thread. The value is valid from the time the thread is created until the time the thread is terminated.</summary>
Public dwThreadId As Int32
End Structure
''<summary>
''The initial text and background colors if a new console window is created in a console application.
''</summary>
<Flags()> _
Public Enum FillAttributes
''<summary>Background color is intensified.</summary>
BackgroundIntensity = 128
''<summary>Background color contains red.</summary>
BackgroundRed = 64
''<summary>Background color contains green.</summary>
BackgroundGreen = 32
''<summary>Background color contains blue.</summary>
BackgroundBlue = 16
''<summary>Text color is intensified.</summary>
ForegroundIntensity = 8
''<summary>Text color contains red.</summary>
ForegroundRed = 4
''<summary>Text color contains green.</summary>
ForegroundGreen = 2
''<summary>Text color contains blue.</summary>
ForegroundBlue = 1
End Enum
''<summary>Logon option.</summary>
Public Enum LogonFlag As Short
''<summary>Internally used to choose the application default</summary>
Undefined = -1
''<summary>Don't know but is valid according to the comments</summary>
[Default] = 0
''<summary>Log on, then load the user's profile in the HKEY_USERS registry key. The function returns after the profile has been loaded. Loading the profile can be time-consuming, so it is best to use this value only if you must access the information in the HKEY_CURRENT_USER registry key.</summary>
WithProfile = 1
''<summary>Log on, but use the specified credentials on the network only. The new process uses the same token as the caller, but the system creates a new logon session within LSA, and the process uses the specified credentials as the default credentials.</summary>
NetworkCredentialsOnly = 2
End Enum
''<summary>Controls how the process is created. The DefaultErrorMode, NewConsole, and NewProcessGroup flags are enabled by default— even if you do not set the flag, the system will function as if it were set.</summary>
Public Enum CreationFlag As Integer
''<summary>Internally used to choose the application default</summary>
Undefined = -1
''<summary>Don't know but might be valid </summary>
[Default] = 0
''<summary>The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.</summary>
Suspended = &H4
''<summary>The new process has a new console, instead of inheriting the parent's console.</summary>
NewConsole = &H10
''<summary>The new process is the root process of a new process group.</summary>
NewProcessGroup = &H200
''<summary>This flag is only valid starting a 16-bit Windows-based application. If set, the new process runs in a private Virtual DOS Machine (VDM). By default, all 16-bit Windows-based applications run in a single, shared VDM.</summary>
SeperateWOWVDM = &H800
''<summary>Indicates the format of the lpEnvironment parameter. If this flag is set, the environment block pointed to by lpEnvironment uses Unicode characters.</summary>
UnicodeEnvironment = &H400
''<summary>The new process does not inherit the error mode of the calling process.</summary>
DefaultErrorMode = &H4000000
End Enum
''<summary>Controls the new process's priority class, which is used to determine the scheduling priorities of the process's threads.</summary>
Public Enum PriorityFlag As Integer
''<summary>Internally used to choose the application default</summary>
Undefined = -1
''<summary>Process with no special scheduling needs.</summary>
NormalPriority = &H20
''<summary>Process whose threads run only when the system is idle and are preempted by the threads of any process running in a higher priority class. An example is a screen saver. The idle priority class is inherited by child processes.</summary>
IdlePriority = &H40
''<summary>Process that performs time-critical tasks that must be executed immediately for it to run correctly. The threads of a high-priority class process preempt the threads of normal or idle priority class processes.</summary>
HighPriority = &H80
''<summary>Process that has the highest possible priority. The threads of a real-time priority class process preempt the threads of all other processes including operating system processes performing important tasks.</summary>
RealTimePriority = &H100
''<summary>Process that has priority above idle but below normal processes.</summary>
BelowNormalPriority = &H4000
''<summary>Process that has priority above normal but below high processes.</summary>
AboveNormalPriority = &H8000
End Enum
''<summary>Determines whether certain StartUpInfo members are used when the process creates a window.</summary>
<Flags()> _
Public Enum StartUpInfoFlags As Integer
''<summary>If this value is not specified, the wShowWindow member is ignored.</summary>
UseShowWindow = &H1
''<summary>If this value is not specified, the dwXSize and dwYSize members are ignored.</summary>
UseSize = &H2
''<summary>If this value is not specified, the dwX and dwY members are ignored.</summary>
UsePosition = &H4
''<summary>If this value is not specified, the dwXCountChars and dwYCountChars members are ignored.</summary>
UseCountChars = &H8
''<summary>If this value is not specified, the dwFillAttribute member is ignored.</summary>
UseFillAttribute = &H10
''<summary>Indicates that the process should be run in full-screen mode, rather than in windowed mode.</summary>
RunFullScreen = &H20
''<summary>Indicates that the cursor is in feedback mode after CreateProcess is called. The system turns the feedback cursor off after the first call to GetMessage.</summary>
ForceOnFeedback = &H40
''<summary>Indicates that the feedback cursor is forced off while the process is starting. The Normal Select cursor is displayed.</summary>
ForceOffFeedback = &H80
''<summary>Sets the standard input, standard output, and standard error handles for the process to the handles specified in the hStdInput, hStdOutput, and hStdError members of the StartUpInfo structure. If this value is not specified, the hStdInput, hStdOutput and hStdError members of the STARTUPINFO structure are ignored.</summary>
UseStandardHandles = &H100
''<summary>When this flag is specified, the hStdInput member is to be used as the hotkey value instead of the standard-input pipe.</summary>
UseHotKey = &H200
''<summary>When this flag is specified, the StartUpInfo's hStdOutput member is used to specify a handle to a monitor, on which to start the new process. This monitor handle can be obtained by any of the multiple-monitor display functions (i.e. EnumDisplayMonitors, MonitorFromPoint, MonitorFromWindow, etc...).</summary>
UseMonitor = &H400
''<summary>Use the HICON specified in the hStdOutput member (incompatible with UseMonitor).</summary>
UseIcon = &H400
''<summary>Program was started through a shortcut. The lpTitle contains the shortcut path.</summary>
TitleShortcut = &H800
''<summary>The process starts with normal priority. After the first call to GetMessage, the priority is lowered to idle.</summary>
Screensaver = &H8000000
End Enum
End Class
</pre>
The code is not yet tested but I think it's enough for today and now it's your turn to distribute again to the comunity .
(Of course if I gonna find some bugs when testing, I gonna post it here again).
Greetings
Christoph Hafner
Inserto AG
|
|
|
|
|
Thanks! This has been on my TODO list for a while, I just haven't gotten the spare time to fix it. I will eventually integrate the suggestions everyone made and fix all of the little quirks and post an updated article. Thanks for providing the fix in the meantime!
-=( atchr )=-
|
|
|
|
|
It really looks much better than the C# version. It't also a great example how comments can be used well. This is what I'm somethimes too lazy to do
Quite a little thing I would change is the type of the exception thrown if any of the static default property values. Currently you throw an ArithmeticException, witch is normally used for pure arithmetic errors, like overflows. A better choise would be an ArgumentException, witch indicates that an invalid argument has been supplied.
best wishes,
Günter Prossliner
|
|
|
|
|
Of course that should have been an ArgumentException and not an ArithmeticException. That must have been an auto-complete mistake (and then copy/paste). Thanks. I corrected it directly in the original message.
Cheers
Chris
|
|
|
|
|
In Visual Studio 2005 (Whidbey) the comments in VB work exactely like this (2 single quotation marks to initialize the JavaDoc;P-comments).
|
|
|
|
|
I have incorporated your suggestions into the project. Once the article gets updated, please let me know what you think.
-=( atchr )=-
|
|
|
|
|
Hi Dewey,
Your code works great from Windows Forms. How can I get it to work under ASP.NET? I copied RunAs.cs into my project, but I get an "Access is denied" error. I thought ASP.NET apps had Full Trust.
Help!!
|
|
|
|
|
I'm sorry. I don't do any work in ASP.NET, so I can't answer your question. It probably has something to do with Code Access Security what permission set your application falls under. If you look in the Administrative Tools you'll see one named .NET Configuration which allows you to alter the restrictions placed on applications by CAS but I work in Windows Forms applications that are installed by MSI packages so I'm no expert in these settings.
-=( atchr )=-
|
|
|
|
|
Yes, that is what I am guessing also, but I am new to Code Access Security, and don't know what to do.
Thanks for your quick reply!
|
|
|
|
|
i try to use this class in a asp.NET application too (C#) and i have the same problem : Access Denied
has someone found a solution??
thank you
claire
|
|
|
|
|
Check the last couple of issues of MSDN Magazine. I remember seeing something similar to this addressed in a recent issue. If I get some time I'll try to find the article.
-=( atchr )=-
|
|
|
|
|
How do I run the process without showing it?
I have tried lots of thing , like putting size to 0 in both X and y . But the process still appears .( in a very small window ).
|
|
|
|
|
You have to create a StartUpInfo instance and use the StartUpInfoFlag of UseShowWindow and also set the StartUpInfo.swShowWindow to the value of SW_HIDE defined in winuser.h
-=( atchr )=-
|
|
|
|
|
That was my first try
ProcessInformation m_pi;
StartUpInfo m_sui = new StartUpInfo();
m_sui.cb = Marshal.SizeOf(m_sui);
m_sui.lpTitle = null;
m_sui.dwFlags = (int)StartUpInfoFlags.UseShowWindow;
m_sui.dwYCountChars = 50;
m_sui.wShowWindow=0; // value of SW_HIDE in winuser.h
And the window still shows... I tried all the values in winuser.h and it seems that that parameter its not working...
|
|
|
|
|
This is a bug as noted in KB818858 (The CreateProcessWithLogonW() Function Ignores the startinfo.wShowWindow Flag).
You have to update the components :
06-Jun-2003 18:13 5.2.3790.51 562,176 Advapi32.dll x86
06-Jun-2003 18:13 5.2.3790.51 16,896 Seclogon.dll x86
Regards,
A. YEZZA
|
|
|
|
|
also in Dewey's code the value of "UseShowWindow" inside the "StartUpInfoFlags" enum is 0, but the correct value es 1.
Rolando
|
|
|
|
|