While working on an application which makes use of Process class, I found several bugs which occur in special conditions. More specifically accessing StartTime
or HasExited
properties causes a Win32Exception
with NativeErrorCode
equal to five. This indicates that the exception is caused by not having enough rights to retrieve the required information.
The exception occurs under the following conditions:
- The source application should not be elevated
- The process instance should represent an elevated process
- The instance should not be a result of
Process.Start
call
The snippet below demonstrates these points:
var explorer = Process.GetProcessesByName("explorer").First();
Console.WriteLine(explorer.StartTime);
Console.WriteLine(explorer.HasExited);
var startInfo = new ProcessStartInfo("notepad") { Verb = "runas" };
var notepad = Process.Start(startInfo);
Console.WriteLine(notepad.StartTime);
Console.WriteLine(notepad.HasExited);
var notepad2 = Process.GetProcessById(notepad.Id);
try
{
Console.WriteLine(notepad2.StartTime);
}
catch(Win32Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
Console.WriteLine(notepad2.HasExited);
}
catch(Win32Exception e)
{
Console.WriteLine(e.ToString());
}
By looking at the stacktrace
we get when accessing StartTime
property, we can see that Process.StartTime
calls Process.GetProcessTimes
which calls Process.GetProcessHandle
which in turn calls ProcessManager.OpenProcess
. This is where the exception is occurring. Using Reflector, we can see that ProcessManager.OpenProcess
invokes native function OpenProcess and passes the desired access. In our case, the access that is passed is equal to 0×400 which corresponds to PROCESS_QUERY_INFORMATION
access right. However, starting from Vista, there is a new access right called PROCESS_QUERY_LIMITED_INFORMATION
which is enough for calling GetProcessTimes function. Let’s see what we get when calling GetProcessTimes
with the new access right:
var startinfo = new ProcessStartInfo("notepad") { Verb = "runas" };
var process = Process.Start(startinfo);
var process2 = Process.GetProcessById(process.Id);
long create, exit, kernel, user;
var handle = NativeMethods.OpenProcess(0x1000, false, process2.Id);
NativeMethods.GetProcessTimes(handle, out create, out exit, out kernel, out user);
NativeMethods.CloseHandle(handle);
Console.WriteLine(DateTime.FromFileTime(create));
If you run this snippet, you will see that process start time is printed and no exception occurs. This solves the problem with StartTime
property.
Printing the stacktrace
of HasExited
property shows that the exception occurs at exactly the same place. In this case, the requested access is 0×100400 which is a combination of PROCESS_QUERY_INFORMATION
and SYNCHRONIZE
. As you have probably guessed, it is again enough to use PROCESS_QUERY_LIMITED_INFORMATION
instead of PROCESS_QUERY_INFORMATION
. I will not show the full code here as it is quite straightforward.
So, if you receive access denied exception when querying StartTime
or HasExited
, make sure that the target process is not an elevated process. If it is, you will have to manually retrieve the information you need.