EDIT: I have created a working proof-of-concept of the problem. See it here
I need to run a process in a non-interactive way as another user in a Windows Service using C#. My code looks like this:
var process = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = /* process name */,
Arguments = /* arguments */,
Domain = /* domain */,
UserName = /* username */,
Password = /* password */,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false
}
};
var error = new StringBuilder();
var errorWaitHandle = new ManualResetEventSlim();
process.ErrorDataReceived += (s, e) =>
{
if (e.Data != null)
{
error.AppendLine(e.Data);
}
else
{
errorWaitHandle.Set();
}
};
var output = new StringBuilder();
var outputWaitHandle = new ManualResetEventSlim();
process.OutputDataReceived += (s, e) =>
{
if (e.Data == /* detect that it's time to write to the input */)
{
process.StandardInput.Write(/* write to the input something that will give an output and exit the process */);
process.StandardInput.Close();
}
else if (e.Data != null)
{
output.AppendLine(e.Data);
}
else
{
outputWaitHandle.Set();
}
};
process.Exited += (s, e) =>
{
try
{
errorWaitHandle.Wait();
outputWaitHandle.Wait();
if (error.Length == 0)
{
// Do something with 'output.ToString()'.
}
else
{
// Do something with 'error.ToString()'.
}
}
finally
{
process.Dispose();
}
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
When I run this from a console application it runs without any problems. However, when I run it from a Windows Service:
If the Windows Service is running as LOCAL_SYSTEM
Process.Start()
method throws an exception.
If the Windows Service is running as another account:
ErrorDataReceived
event receives null data immediately.OutputDataReceived
event receives null data immediately.- The process does not really run.
If I remove the lines:
FileName = /* process name */,
Arguments = /* arguments */,
Domain = /* domain */,
The code runs without any problems on a Windows Service with both LOCAL_SYSTEM
other accounts.
After a lot of digging around I learned the following:
- Since Windows Vista, Windows Services are not allowed to be interactive, in other words, they cannot create interactive processes (e.g. create windows). The only processes that are allowed are those that are not interactive (e.g. don't create windows).
- When
Process.Start()
is called without credentials, the Win32CreateProcess
method is called with theCREATE_NO_WINDOW
process creation flag. Since no window is created, the process is able to run under a Windows Service. - When
Process.Start()
is called with credentials, the Win32CreateProcessWithLogonW
method is called. Since this method does not support theCREATE_NO_WINDOW
process creation flag, a window is always created, hence the process is unable to run under a Windows Service.
Theoretically, there's a Win32 CreateProcessAsUser
method that supports the Win32 CreateProcess
method features with the addition of running it as another user.
- Is the Win32
CreateProcessAsUser
method the way to actually solve this? - How can I use this method from C# with the same features that the
System.Diagnostics.Process
class provides (e.g. standard input, output, error redirection, etc).