Skip to content

Instantly share code, notes, and snippets.

@marcogrcr
Last active September 3, 2019 22:49
Show Gist options
  • Save marcogrcr/f4e5916c735c71992b1b1a25f7454c05 to your computer and use it in GitHub Desktop.
Save marcogrcr/f4e5916c735c71992b1b1a25f7454c05 to your computer and use it in GitHub Desktop.
How to run a non-interactive process as another user in a Windows Service using C#? [proof-of-concept]
/**
* This application converts a text message to uppercase by spawning a child process as another user and saves it to a file.
*
* Usage: app.exe MESSAGE FILE_PATH
* or
* Usage: app.exe DOMAIN USERNAME PASSWORD MESSAGE FILE_PATH
*
*
* Setup instructions:
*
* 1. Open secpol.msc
* 2. Navigate to 'Local Policies' > 'User Rights Assignment' > 'Log on as a service'.
* 3. Add your account and click the 'OK' button.
* 4. Compile this file as a console application.
* 5. Open a PowerShell console as administrator.
* 6. PS C:\> New-Service ServiceTest "CONSOLE_EXE_PATH MESSAGE FILE_PATH" -Credential YOUR_DOMAIN\YOUR_ACCOUNT
* 7. PS C:\> Start-Service ServiceTest
* 8. Verify that the file is created with the message in uppercase.
* 10. Delete the file.
* 11. PS C:\> Stop-Service ServiceTest
* 12. PS C:\> sc.exe delete ServiceTest
* 13. PS C:\> New-Service ServiceTest "CONSOLE_EXE_PATH YOUR_DOMAIN ANOTHER_ACCOUNT_USERNAME PASSWORD MESSAGE FILE_PATH" -Credential YOUR_DOMAIN\YOUR_ACCOUNT
* 14. Ensure the YOUR_DOMAIN\ANOTHER_ACCOUNT_USERNAME has access to the folder where CONSOLE_EXE_PATH is located.
* 15. PS C:\> Start-Service ServiceTest
* 16. Verify that the file is not created.
* 17. Open eventvwr
* 18. Verify that a { "Log Name": "System", "Source": "Application Popup", "EventId": 26, "Data": ["CONSOLE_EXE - Application Error", "The application was unable to start correctly (0xc0000142). Click OK to close the application."] } event is registered.
* 19. PS C:\> Stop-Service ServiceTest
* 20. PS C:\> sc.exe delete ServiceTest
*/
namespace ServiceTest
{
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.ServiceProcess;
using System.Text;
using System.Threading;
internal sealed class Program : ServiceBase
{
private const string ChildIndicator = "079a9ab7-e4b8-4192-9dc7-dc7b93ec9376";
private readonly string[] args;
private readonly ManualResetEventSlim processDone = new ManualResetEventSlim();
private Program(string[] args)
{
this.args = args;
}
public static void Main(string[] args)
{
if (Environment.UserInteractive || Program.IsChildProcess(args))
{
var p = new Program(args);
p.OnStart(null);
p.OnStop();
}
else
{
ServiceBase.Run(new Program(args));
}
}
protected override void OnStart(string[] notUsed)
{
if (Program.IsChildProcess(this.args))
{
Console.WriteLine("handshake");
var input = Console.In.ReadToEnd();
if (input != null)
{
Console.WriteLine(input.ToUpper());
}
this.processDone.Set();
}
else if (this.args.Length == 2 || this.args.Length == 5)
{
var credentials = this.args.Length == 5 ? new NetworkCredential(this.args[1], this.args[2], this.args[0]) : null;
var message = this.args.Length == 5 ? this.args[3] : this.args[0];
var outputFile = this.args.Length == 5 ? this.args[4] : this.args[1];
// Create process.
var process = new Process
{
EnableRaisingEvents = true,
StartInfo =
{
FileName = Process.GetCurrentProcess().MainModule.FileName,
Arguments = Program.ChildIndicator,
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
WorkingDirectory = @"C:\"
}
};
if (credentials != null)
{
process.StartInfo.Domain = credentials.Domain;
process.StartInfo.UserName = credentials.UserName;
process.StartInfo.Password = credentials.SecurePassword;
}
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 == "handshake")
{
process.StandardInput.Write(message);
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)
{
File.WriteAllText(outputFile, string.Format("Child process completed with an error: {0}", error));
}
else if (output.Length > 0)
{
File.WriteAllText(outputFile, output.ToString());
}
}
finally
{
process.Dispose();
this.processDone.Set();
}
};
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
}
else
{
using (var currentProcess = Process.GetCurrentProcess())
{
Console.WriteLine("Usage:");
Console.WriteLine("{0} <msg> <path_to_output_file>", currentProcess.MainModule.ModuleName);
Console.WriteLine("{0} <domain> <username> <password> <msg> <path_to_output_file>", currentProcess.MainModule.ModuleName);
this.processDone.Set();
}
}
}
protected override void OnStop()
{
this.processDone.Wait();
}
private static bool IsChildProcess(string[] args)
{
return args.Length == 1 && args[0] == Program.ChildIndicator;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment