Monday, March 30, 2009
A couple of days ago a friend of mine called with a problem. She had a Windows service written in C# that, under certain conditions, needed to launch some applications on behalf of two specific users and she was getting one error after another. Oddly enough, I had just answered a question on the same subject over at Stack Overflow and I had seen a few other references to similar problems in other forums (usually with incorrect answers).
This isn't a common need but it also isn't that rare, so I thought I had better post this step by step guide to troubleshooting the launching of a process under impersonated credentials. This is based on using the Start method of the .Net Process class but it is also applicable to the underlying API calls: CreateProcessWithLogonW and CreateProcessWithTokenW.
Access Denied - The first attempt and an access denied exception right off the bat. This is the most common initial problem and is caused by the fact that the service is running under the LOCAL SYSTEM account. Strangely, the SYSTEM account is the most powerful account on the computer but one of the few things it cannot do is launch a process using CreateProcessWithLogonW which is the API underlying the call to Process.Start. So change your service account to Local Service, it's probably the more appropriate account anyway.
Access Denied Again - Aargh, I thought we solved this. Oops, double check the permissions on the application that you are trying to launch. Remember that the system tries to access the application file as the user account that the process will be running under, not the service account.
Invalid Directory Error - What? All the paths are correct. All the directories are spelled correctly, no invalid characters. This is an incredibly annoying error and is not very consistent. Usually when we run a process we don't bother setting the WorkingDirectory property and just accept the default from the parent process. When starting a process with new credentials you can't do that, you must explicitly set a path for the WorkingDirectory or you'll get a "The directory name is invalid." Win32Exception.
Failure: No Error? - Process.Start handles the creation of the Environment block for the new process for you quite well. So this is a problem only if you are using the underlying API. When calling one of the CreateProcess* APIs it is normal to leave the lpEnvironment parameter as NULL and have the system use the default of copying the block from the parent process. But when launching under new credentials you must create an environment block explicitly, either manually or using CreateEnvironmentBlock. What makes this worse is, if you leave this out the CreateProcess* call will fail but GetLastError will return ERROR_SUCCESS and if you make an error creating your environment block there will be no error but the process may just not run at all.
Application Failed to Initialize Properly - No more exceptions, you've solved all the problems and the process has been launched. Oops again, where is the process? Check the event log (or you may have received an Application Error pop-up). There should be an entry for Application Error that says that your process was the faulting application, either user32.dll or kernel32.dll was the faulting module and the exception was: 0xC0000142. There may be some minor variation in this but basically it is saying that your application could not initialize. The reason for this is that on initialization, before any application code is run, all processes are attached to a Window Station and all threads are attached to a Desktop but the user you are launching under does not have permission to access the Window Station and Desktop in which your process is being launched, ergo it can't initialize. The security descriptors for the Window Station and Desktop must be adjusted to give AllAccess permission to the user the process is being launched under. This is a devil to do directly in .Net, so you might find the security wrapper classes here useful.
No More Errors - Really, no more errors, your process should be running smoothly now. There may be some variations in what you need to do based on who the user is (for instance an administrator will already have the correct permissions in some cases) or what kind of session you are launching in. But following these steps should make your life smooth and easy (well maybe not your whole life).
Source code (C#) supporting this article can be found here.