|
You can use it, I'd appreciate if you leave the author information in the files...but other than that I don't care for now.
-=( atchr )=-
|
|
|
|
|
I am running a service with desktop interaction under an administrator account. I can't use CreateProcessWithLogonW under the System account because it's not allowed in SP2 and in W2003.
I get the following error when trying to run notepad:
The application failed to initialize properly (0xc0000142). Click on OK to terminate the application. The process lies in the task manager with the right username but exits as soon as I click ok.
I am only using username, domain, password and commandline. Do I have to specify anything more to create a window?
thanks,
Henrik
|
|
|
|
|
See my last post of a few minutes ago. I'm wanting to spawn processes from a Windows Service also. I found I needed to set the desktop member of StartInfo struct.
Dave
|
|
|
|
|
EyeHatePickingScreenNames wrote: I'm wanting to spawn processes from a Windows Service also. I found I needed to set the desktop member of StartInfo struct.
I've encountered another problem: my Windows Service starts a process which needs access to a network share. This process uses the correct username an domain but the networking rights got lost. If the process is started manually everything works fine.
Any ideas?
Thanks,
Martin
|
|
|
|
|
While very useful in leading me down the path, I didn't wind up using Dewey's code as is. I later found that CreateProcessWithLogon doesn't honor the lpDesktop member of the startup info which needs to be "" in this scenario. So, you have to use LogonUser, DuplicateTokenEx, LoadUserProfile (depending on app being started), and finally, CreateProcessAsUser.
If you do all that and your process still doesn't behave as if running from the command prompt, then something else is wrong. I routinely start processes that access the network. For example, copying files to shares and running rsh/ssh commands to Unix boxes. It was a long road getting there tho.
Dave
|
|
|
|
|
davelogie wrote: I later found that CreateProcessWithLogon doesn't honor the lpDesktop member of the startup info which needs to be "" in this scenario. So, you have to use LogonUser, DuplicateTokenEx, LoadUserProfile (depending on app being started), and finally, CreateProcessAsUser.
Thanks a lot for that tip. Please could you post that piece of code?
Martin
|
|
|
|
|
Here's logon stuff. You can add CreateProcessAsUser easy enough. Remember that the lpDesktop member of StartupInfo = "" (not NULL and not "WinSta0\Default") for this application. Good luck.
<br />
<br />
#region User Logon<br />
<br />
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
private static extern bool LogonUser(String lpszUsername, String lpszDomain, IntPtr lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr hToken);<br />
<br />
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
private static extern bool DuplicateTokenEx(IntPtr hExistingToken, int dwDesiredAccess, ref SecurityAttributes lpTokenAttributes,<br />
int impersonationLevel, int tokenType, out IntPtr phNewToken);<br />
<br />
[DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
private static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);<br />
<br />
[DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
private static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);<br />
<br />
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]<br />
private static extern bool CloseHandle(IntPtr handle);<br />
<br />
[StructLayout(LayoutKind.Sequential)]<br />
public struct SecurityAttributes<br />
{<br />
public int dwLength;<br />
public IntPtr lpSecurityDescriptor;<br />
public bool bInheritHandle;<br />
}<br />
<br />
[StructLayout(LayoutKind.Sequential)]<br />
public struct ProfileInfo<br />
{<br />
public int dwSize;<br />
<br />
public int dwFlags;<br />
<br />
public string lpUserName;<br />
<br />
public string lpProfilePath;<br />
<br />
public string lpDefaultPath;<br />
<br />
public string lpServerName;<br />
<br />
public string lpPolicyPath;<br />
<br />
public IntPtr hProfile;<br />
} <br />
<br />
[FlagsAttribute]<br />
public enum LogonType<br />
{<br />
Interactive = 2,<br />
Network = 3,<br />
Batch = 4,<br />
Service = 5,<br />
Unlock = 7<br />
}<br />
<br />
[FlagsAttribute]<br />
public enum LogonProvider<br />
{<br />
Default = 0,<br />
Negotiate = 3,<br />
NTLM = 2,<br />
WinNT35 = 1<br />
}<br />
<br />
[FlagsAttribute]<br />
public enum DuplicateTokenDesiredAccess<br />
{<br />
SameAsExisting = 0,<br />
MaximumAllowed = 0x02000000<br />
}<br />
[FlagsAttribute]<br />
public enum ImpersonationLevel<br />
{<br />
Anonymous = 0,<br />
Identification = 1,<br />
Impersonation = 2,<br />
Delegation = 3<br />
}<br />
[FlagsAttribute]<br />
public enum TokenType<br />
{<br />
Primary = 1,<br />
Impersonation = 2<br />
}<br />
<br />
private void LogonUser(String user, String domain, SecureString password, LogonType type, LogonProvider provider)<br />
{<br />
if (password.IsReadOnly() == false)<br />
throw new InvalidOperationException("SecureString not ReadOnly");<br />
<br />
if (string.IsNullOrEmpty(user) == true || string.IsNullOrEmpty(domain) == true)<br />
throw new InvalidOperationException("No user account specified");<br />
<br />
IntPtr handle;<br />
IntPtr bstr = Marshal.SecureStringToBSTR(password);<br />
bool result = LogonUser(user, domain, bstr, (int)type, (int)provider, out handle);<br />
Marshal.ZeroFreeBSTR(bstr);<br />
<br />
if (result == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
<br />
SecurityAttributes sa = new SecurityAttributes();<br />
sa.dwLength = Marshal.SizeOf(sa);<br />
sa.lpSecurityDescriptor = IntPtr.Zero;<br />
sa.bInheritHandle = true; <br />
<br />
IntPtr newHandle;<br />
result = DuplicateTokenEx(handle, (int)DuplicateTokenDesiredAccess.MaximumAllowed, ref sa,<br />
(int)ImpersonationLevel.Impersonation, (int)TokenType.Primary, out newHandle);<br />
if (result == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
<br />
CloseHandle(handle);<br />
handle = newHandle;<br />
<br />
hToken = handle;<br />
}<br />
<br />
public void LoadUserProfile(string username)<br />
{<br />
if (hToken == IntPtr.Zero)<br />
throw new InvalidOperationException("User not logged in");<br />
<br />
ProfileInfo info = new ProfileInfo();<br />
info.dwSize = Marshal.SizeOf(info);<br />
info.lpUserName = username;<br />
info.dwFlags = 1;
<br />
bool result = LoadUserProfile(hToken, ref info);<br />
if (result == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
<br />
hProfile = info.hProfile;<br />
}<br />
<br />
internal void LogOffUser()<br />
{<br />
#if false<br />
string identity = WindowsIdentity.GetCurrent().Name;<br />
string threadIdentity = Thread.CurrentPrincipal.Identity.Name;<br />
scriptTask.State.AddMessage(DateTime.Now, string.Format("Logging off user {0} ({1})", identity, threadIdentity));<br />
#endif<br />
<br />
WindowsIdentity.Impersonate(IntPtr.Zero);<br />
<br />
#if false<br />
identity = WindowsIdentity.GetCurrent().Name;<br />
Thread.CurrentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());<br />
threadIdentity = Thread.CurrentPrincipal.Identity.Name;<br />
scriptTask.State.AddMessage(DateTime.Now, string.Format("Identity now {0} ({1})", identity, threadIdentity));<br />
#endif<br />
<br />
if (hToken != IntPtr.Zero && hProfile != IntPtr.Zero)<br />
{<br />
bool result = UnloadUserProfile(hToken, hProfile);<br />
hProfile = IntPtr.Zero;<br />
<br />
if (result == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
}<br />
<br />
if (hToken != IntPtr.Zero)<br />
{<br />
bool result = CloseHandle(hToken);<br />
hToken = IntPtr.Zero;<br />
<br />
if (result == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
}<br />
<br />
}<br />
<br />
#endregion // User Logon<br />
<br />
<br />
|
|
|
|
|
Hi davelogie,
With your solution I got the same problems - still no network access.
Am I using wrong parameters?
<br />
public static System.Diagnostics.Process StartProcessDesktop(string userName,<br />
string domain, string password, string commandLine, short showWindow,<br />
string sCurrentDirectory)<br />
{<br />
bool retval;<br />
StringBuilder cl = new StringBuilder(commandLine.Length);<br />
cl.Append(commandLine);<br />
<br />
retval = LogonUser(userName, domain, password, LogonType.Interactive, LogonProvider.Default);<br />
if (!retval)<br />
{<br />
throw new System.ComponentModel.Win32Exception();<br />
}<br />
<br />
retval = LoadUserProfile(userName);<br />
if (!retval)<br />
{<br />
throw new System.ComponentModel.Win32Exception();<br />
}<br />
<br />
SecurityAttributes saproc = new SecurityAttributes();<br />
saproc.dwLength = Marshal.SizeOf(saproc);<br />
saproc.lpSecurityDescriptor = IntPtr.Zero;<br />
saproc.bInheritHandle = true;<br />
<br />
SecurityAttributes sathread = new SecurityAttributes();<br />
sathread.dwLength = Marshal.SizeOf(sathread);<br />
sathread.lpSecurityDescriptor = IntPtr.Zero;<br />
sathread.bInheritHandle = true;<br />
<br />
ProcessInformation processInfo;<br />
StartUpInfo startupInfo = new StartUpInfo();<br />
startupInfo.cb = Marshal.SizeOf(startupInfo);<br />
startupInfo.lpTitle = null;<br />
startupInfo.lpDesktop = "";<br />
startupInfo.dwFlags = (int)StartUpInfoFlags.UseShowWindow;<br />
startupInfo.wShowWindow = showWindow;<br />
<br />
<br />
retval = CreateProcessAsUser(_hToken, null, cl, ref saproc, ref sathread, false, (uint)(CreationFlags.NewProcessGroup | CreationFlags.NewConsole), IntPtr.Zero, <br />
sCurrentDirectory, ref startupInfo, out processInfo);<br />
<br />
if (!retval)<br />
{<br />
throw new System.ComponentModel.Win32Exception();<br />
}<br />
else<br />
{<br />
CloseHandle(processInfo.hProcess);<br />
CloseHandle(processInfo.hThread);<br />
return System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);<br />
}<br />
}<br />
Thanks,
Martin
|
|
|
|
|
So you want to show a GUI? In my application everything is GUI-less behind the scenes. Mostly console apps. the most important requirement for me was that the process was fully started as the user as if they were interactively logged in.
Are you seeing the GUI of your process? The only thing I set on the startup info is the lpDesktop = "". I mentioned the other options earlier. My creation flag is only "NoWindow".
Are you loading the Environment? This is useful but I don't see how it would effect network access.
<br />
<br />
IntPtr lpEnvironment;<br />
bool res = CreateEnvironmentBlock(out lpEnvironment, hToken, true);<br />
if (res == false)<br />
throw new System.ComponentModel.Win32Exception();<br />
<br />
ProcessInformation processInfo;<br />
<br />
bool result = CreateProcessAsUser(hToken, <br />
null, <br />
commandLine, <br />
ref sa, ref sa, <br />
true,
(int)(creationFlags | ProcessCreationFlags.UnicodeEnvironment), <br />
lpEnvironment,<br />
workingDirectory, <br />
ref startupInfo, <br />
out processInfo);<br />
<br />
if (lpEnvironment != IntPtr.Zero)<br />
DestroyEnvironmentBlock(lpEnvironment);<br />
<br />
|
|
|
|
|
Yes, I want to show a GUI and it works but there's still no network access. I also load the environment because the started application needs it (not for network access).
Any ideas left?
Thanks,
Martin
|
|
|
|
|
Got me. Try running you Windows service under a domain account and see if that changes the behavior. Must be something different about your environment.
|
|
|
|
|
Thanks for posting that, it helped out alot. One slight change though, we needed to explicitly marshal the strings in the ProfileInfo struct as UnmanagedType.LPTStr
When loading a profile that already exists, it doesn't matter, as the lpUserName member is ignored. When loading a profile that doesn't already exist, LoadUserProfile() will create a new one using lpUserName. Without the marshalling, the passed in string gets junked, so you get a profile created with a junk path which can't be loaded correctly on subsequent user logins.
Cheers
Steve
|
|
|
|
|
As of .NET 2.0, there is now adequate functionality within the framework to launch a process while impersonating a user without having to dip into P/Invoke. See the this article.
Tech, life, family, faith: Give me a visit.
Judah Himango
|
|
|
|
|
its all good... but whidbey is some time away and things need to be done usually "yesterday".
|
|
|
|
|
Thank you. I had typed up several responses to this but deemed all of them "inappropriate" and discontinued.
-=( atchr )=-
|
|
|
|
|
Of course, it was just an FYI.
Tech, life, family, faith: Give me a visit.
Judah Himango
|
|
|
|
|
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.
|
|
|
|
|