0

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 Win32 CreateProcess method is called with the CREATE_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 Win32 CreateProcessWithLogonW method is called. Since this method does not support the CREATE_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).
Marco
  • 5,555
  • 2
  • 17
  • 23
  • This a copy of the question posted [here](https://social.msdn.microsoft.com/Forums/vstudio/en-US/ec58f11b-5049-458f-9257-1163e9b75e25). If it gets answered, I will post the answer here for reference, in the meantime, please help :) – Marco Jun 01 '16 at 00:50
  • Hmmm, if CreateProcessAsUser creates the process the way you need, then if you do Process.GetProcessById with the id of the new process should give you acces to it. The biggest problem is to redirect stderr/stdin/stdout but you can create anonymous pipes, get their handles, set them on the STARTUPINFO struct and then you will have the same functionality. – Gusman Jun 01 '16 at 01:21
  • Well, indeed .net framework is doing precisely that, using pipes :), you can take a look at Process.Start at referencesource: http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Process.cs – Gusman Jun 01 '16 at 01:23
  • You'll have to change the permissions on the desktop and/or window station objects, or explicitly create new ones for the process to run in. If you're talking about non-admin accounts, you should create new ones, otherwise you're introducing a security vulnerability. This is likely to be pretty awkward to do in C#. – Harry Johnston Jun 01 '16 at 02:35
  • 2
    CREATE_NO_WINDOW doesn't do what you think. It's not the issue. – David Heffernan Jun 01 '16 at 04:48
  • @DavidHeffernan what is the issue then? I would appreciate if you could help me understand. – Marco Jun 01 '16 at 21:26
  • Who knows. How can we reproduce this behaviour? – David Heffernan Jun 01 '16 at 21:48
  • *Since Windows Vista, Windows Services are not allowed to be interactive [...] the only processes that are allowed are those that [...] don't create windows* - that's not true. Windows services *can* create windows perfectly happily; it's just that the user won't see them. Windows creates fake drawing surfaces as necessary. The issue is that the user doesn't have the necessary permissions to the window station and desktop objects, so when the executable tries to start up, it crashes. – Harry Johnston Jun 01 '16 at 22:56
  • ... although I'm not quite sure why you get an exception when the service is running as local system, rather than a crashed child. That one might be a .NET issue. – Harry Johnston Jun 01 '16 at 23:01
  • @DavidHeffernan I have created a proof-of-concept code. See it here: https://gist.github.com/marcogrcr/f4e5916c735c71992b1b1a25f7454c05 – Marco Jun 01 '16 at 23:30
  • @HarryJohnston Plese look at my previous comment for a working proof-of-concept. Why do you think I'm getting 0xc0000142 error when I run it as a user if the service is theoretically allowed to create windows? If I run this from console it runs correctly on both cases, however case two always displays a window. – Marco Jun 01 '16 at 23:31
  • What happens if you specify the *same* username (and password) that the service is using? – Harry Johnston Jun 01 '16 at 23:59
  • @HarryJohnston it works without any issues, the problem is when I specify another users' credentials. – Marco Jun 02 '16 at 00:10
  • There you go, then. If the problem was that it was in a service, using the same username and password wouldn't work. The problem is that the window station and desktop objects only grant access to that particular user, and you need at least minimal access to them in order to launch a process. (Depending on which API you use, Windows sometimes automatically fixes this up for you, but that only seems to work if you're logged on interactively. Granted I'm not entirely sure why, I suspect the permissions are different.) – Harry Johnston Jun 02 '16 at 00:28
  • @HarryJohnston You're right, seeing your response I was able to make it work with both [this](http://stackoverflow.com/a/678449/823371) and [this](http://stackoverflow.com/a/30687230/823371). However, I'm not very familiar with WINAPI security concepts. Do you mind telling me what are security implication of doing this? Also, do you know where can I read about these concepts? Thank you so much for the help! – Marco Jun 02 '16 at 00:58
  • *Basically,* if you use a solution that changes the permissions on the existing window station and desktop you are trusting the user whose account you are launching the new process in. If that user were malicious, they could take over control of the account the service is running in. Really, it's more complicated than that, there may be ways to mitigate or eliminate the risk - but you'd need to be an expert, which I'm not. It is my understanding that creating a new window station and desktop, with appropriate permissions, is much safer; no worse than running a service as the user directly. – Harry Johnston Jun 02 '16 at 01:05

1 Answers1

0

I was able to make the process run using this solution.

(Thanks Harry Johnston for the help).

Community
  • 1
  • 1
Marco
  • 5,555
  • 2
  • 17
  • 23