CLI tool for running Playbooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
23 KiB

#nullable enable
using System;
using System.Collections.Specialized;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Win32;
using TrustedUninstaller.Shared.Exceptions;
using TrustedUninstaller.Shared.Tasks;
using YamlDotNet.Serialization;
namespace TrustedUninstaller.Shared.Actions
{
public enum ServiceOperation
{
Stop,
Continue,
Start,
Pause,
Delete,
Change
}
public class ServiceAction : TaskAction, ITaskAction
{
public void RunTaskOnMainThread() { throw new NotImplementedException(); }
[YamlMember(typeof(ServiceOperation), Alias = "operation")]
public ServiceOperation Operation { get; set; } = ServiceOperation.Delete;
[YamlMember(typeof(string), Alias = "name")]
public string ServiceName { get; set; } = null!;
[YamlMember(typeof(int), Alias = "startup")]
public int? Startup { get; set; }
[YamlMember(typeof(bool), Alias = "deleteStop")]
public bool DeleteStop { get; set; } = true;
[YamlMember(typeof(bool), Alias = "deleteUsingRegistry")]
public bool RegistryDelete { get; set; } = false;
[YamlMember(typeof(string), Alias = "device")]
public bool Device { get; set; } = false;
[YamlMember(typeof(string), Alias = "weight")]
public int ProgressWeight { get; set; } = 4;
public int GetProgressWeight() => ProgressWeight;
private bool InProgress { get; set; }
public void ResetProgress() => InProgress = false;
public string ErrorString() => $"ServiceAction failed to {Operation.ToString().ToLower()} service {ServiceName}.";
private ServiceController? GetService()
{
if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetServices()
.FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
if (ServiceName.EndsWith("*")) return ServiceController.GetServices()
.FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
if (ServiceName.StartsWith("*")) return ServiceController.GetServices()
.FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));
return ServiceController.GetServices()
.FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase));
}
private ServiceController? GetDevice()
{
if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetDevices()
.FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0);
if (ServiceName.EndsWith("*")) return ServiceController.GetDevices()
.FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase));
if (ServiceName.StartsWith("*")) return ServiceController.GetDevices()
.FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase));
return ServiceController.GetDevices()
.FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase));
}
public UninstallTaskStatus GetStatus()
{
if (InProgress) return UninstallTaskStatus.InProgress;
if (Operation == ServiceOperation.Change && Startup.HasValue)
{
// TODO: Implement dev log. Example:
// if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry.");
var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}");
if (root == null) return UninstallTaskStatus.Completed;
var value = root.GetValue("Start");
return (int)value == Startup.Value ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
}
ServiceController? serviceController;
if (Device) serviceController = GetDevice();
else serviceController = GetService();
if (Operation == ServiceOperation.Delete && RegistryDelete)
{
// TODO: Implement dev log. Example:
// if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry.");
var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}");
return root == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo;
}
return Operation switch
{
ServiceOperation.Stop =>
serviceController == null ||
serviceController?.Status == ServiceControllerStatus.Stopped
|| serviceController?.Status == ServiceControllerStatus.StopPending ?
UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
ServiceOperation.Continue =>
serviceController == null ||
serviceController?.Status == ServiceControllerStatus.Running
|| serviceController?.Status == ServiceControllerStatus.ContinuePending ?
UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
ServiceOperation.Start =>
serviceController?.Status == ServiceControllerStatus.StartPending
|| serviceController?.Status == ServiceControllerStatus.Running ?
UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
ServiceOperation.Pause =>
serviceController == null ||
serviceController?.Status == ServiceControllerStatus.Paused
|| serviceController?.Status == ServiceControllerStatus.PausePending ?
UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
ServiceOperation.Delete =>
serviceController == null || Win32.ServiceEx.IsPendingDeleteOrDeleted(serviceController.ServiceName) ?
UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo,
_ => throw new ArgumentOutOfRangeException("Argument out of Range", new ArgumentOutOfRangeException())
};
}
private readonly string[] RegexNoKill = { "DcomLaunch" };
public async Task<bool> RunTask()
{
if (InProgress) throw new TaskInProgressException("Another Service action was called while one was in progress.");
if (Operation == ServiceOperation.Change && !Startup.HasValue) throw new ArgumentException("Startup property must be specified with the change operation.");
if (Operation == ServiceOperation.Change && (Startup.Value > 4 || Startup.Value < 0)) throw new ArgumentException("Startup property must be between 1 and 4.");
// This is a little cursed but it works and is concise lol
Console.WriteLine($"{Operation.ToString().Replace("Stop", "Stopp").TrimEnd('e')}ing services matching '{ServiceName}'...");
if (Operation == ServiceOperation.Change)
{
var action = new RegistryValueAction()
{
KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}",
Value = "Start",
Data = Startup.Value,
Type = RegistryValueType.REG_DWORD,
Operation = RegistryValueOperation.Set
};
await action.RunTask();
InProgress = false;
return true;
}
ServiceController? service;
if (Device) service = GetDevice();
else service = GetService();
if (service == null)
{
Console.WriteLine($"No services found matching '{ServiceName}'.");
//ErrorLogger.WriteToErrorLog($"The service matching '{ServiceName}' does not exist.", Environment.StackTrace, "ServiceAction Error");
if (Operation == ServiceOperation.Start)
throw new ArgumentException("Service " + ServiceName + " not found.");
return false;
}
InProgress = true;
var cmdAction = new CmdAction();
if ((Operation == ServiceOperation.Delete && DeleteStop) || Operation == ServiceOperation.Stop)
{
if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success))
{
Console.WriteLine($"Skipping {ServiceName}...");
return false;
}
try
{
foreach (ServiceController dependentService in service.DependentServices.Where(x => x.Status != ServiceControllerStatus.Stopped))
{
Console.WriteLine($"Killing dependent service {dependentService.ServiceName}...");
if (dependentService.Status != ServiceControllerStatus.StopPending && dependentService.Status != ServiceControllerStatus.Stopped)
{
try
{
dependentService.Stop();
}
catch (Exception e)
{
dependentService.Refresh();
if (dependentService.Status != ServiceControllerStatus.Stopped && dependentService.Status != ServiceControllerStatus.StopPending)
ErrorLogger.WriteToErrorLog("Dependent service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", dependentService.ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
}
Console.WriteLine("Waiting for the dependent service to stop...");
try
{
dependentService.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
dependentService.Refresh();
if (service.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog("Dependent service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
try
{
var killServ = new TaskKillAction()
{
ProcessID = Win32.ServiceEx.GetServiceProcessId(dependentService.ServiceName)
};
await killServ.RunTask();
}
catch (Exception e)
{
dependentService.Refresh();
if (dependentService.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog($"Could not kill dependent service {dependentService.ServiceName}.",
e.StackTrace, "ServiceAction Error", ServiceName);
}
}
}
catch (Exception e)
{
ErrorLogger.WriteToErrorLog($"Error killing dependent services: " + e.Message,
e.StackTrace, "ServiceAction Error", ServiceName);
}
}
if (Operation == ServiceOperation.Delete)
{
if (DeleteStop && service.Status != ServiceControllerStatus.StopPending && service.Status != ServiceControllerStatus.Stopped)
{
try
{
service.Stop();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending)
ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
Console.WriteLine("Waiting for the service to stop...");
try
{
service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog("Service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
try
{
var killServ = new TaskKillAction()
{
ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName)
};
await killServ.RunTask();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog($"Could not kill service {service.ServiceName}.", e.StackTrace, "ServiceAction Error");
}
}
if (RegistryDelete)
{
var action = new RegistryKeyAction()
{
KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}",
Operation = RegistryKeyOperation.Delete
};
await action.RunTask();
}
else
{
try
{
ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
ServiceInstallerObj.Context = new InstallContext();
ServiceInstallerObj.ServiceName = service.ServiceName;
ServiceInstallerObj.Uninstall(null);
}
catch (Exception e)
{
ErrorLogger.WriteToErrorLog("Service uninstall failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
}
} else if (Operation == ServiceOperation.Start)
{
try
{
service.Start();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Running)
ErrorLogger.WriteToErrorLog("Service start failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
try
{
service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Running)
ErrorLogger.WriteToErrorLog("Service start timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
} else if (Operation == ServiceOperation.Stop)
{
try
{
service.Stop();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending)
ErrorLogger.WriteToErrorLog("Service stop failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
Console.WriteLine("Waiting for the service to stop...");
try
{
service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog("Service stop timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
try
{
var killServ = new TaskKillAction()
{
ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName)
};
await killServ.RunTask();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Stopped)
ErrorLogger.WriteToErrorLog($"Could not kill dependent service {service.ServiceName}.",
e.StackTrace, "ServiceAction Error");
}
} else if (Operation == ServiceOperation.Pause)
{
try
{
service.Pause();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Paused)
ErrorLogger.WriteToErrorLog("Service pause failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
try
{
service.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Paused)
ErrorLogger.WriteToErrorLog("Service pause timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
}
else if (Operation == ServiceOperation.Continue)
{
try
{
service.Pause();
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Running)
ErrorLogger.WriteToErrorLog("Service continue failed: " + e.Message, e.StackTrace, "ServiceAction Warning", ServiceName);
}
cmdAction.Command = Environment.Is64BitOperatingSystem ?
$"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue" :
$"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue";
if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread();
try
{
service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000));
}
catch (Exception e)
{
service.Refresh();
if (service.Status != ServiceControllerStatus.Running)
ErrorLogger.WriteToErrorLog("Service continue timeout exceeded.", e.StackTrace, "ServiceAction Warning", ServiceName);
}
}
service?.Dispose();
await Task.Delay(100);
InProgress = false;
return true;
}
}
}