I'm trying to change user name and password of a Windows service in C# via WinAPI calls. I have created the service myself using WinAPI's CreateService()
, based off of the code in this Stack Overflow answer.
If I use the same credentials in a call to ChangeServiceConfigA()
, I'm told that user name and password are incorrect. I can change everything else with ChangeServiceConfigA()
, even the password, but once I try to set user name and password, I get said error.
My code looks like this:
private const uint SERVICE_NO_CHANGE = 0xffffffff;
[Flags]
private enum ServiceAccessRights
{
QueryConfig = 0x1,
ChangeConfig = 0x2,
QueryStatus = 0x4,
EnumerateDependants = 0x8,
Start = 0x10,
Stop = 0x20,
PauseContinue = 0x40,
Interrogate = 0x80,
UserDefinedControl = 0x100,
Delete = 0x00010000,
StandardRightsRequired = 0xF0000,
AllAccess = StandardRightsRequired | QueryConfig | ChangeConfig |
QueryStatus | EnumerateDependants | Start | Stop | PauseContinue |
Interrogate | UserDefinedControl
}
private enum ServiceBootFlag : UInt32
{
Start = 0x00000000,
SystemStart = 0x00000001,
AutoStart = 0x00000002,
DemandStart = 0x00000003,
Disabled = 0x00000004,
ServiceNoChange = SERVICE_NO_CHANGE
}
private enum ServiceError : UInt32
{
Ignore = 0x00000000,
Normal = 0x00000001,
Severe = 0x00000002,
Critical = 0x00000003,
ServiceNoChange = SERVICE_NO_CHANGE
}
private enum ServiceType : UInt32
{
ServiceFileSystemDriver = 0x00000002,
ServiceKernelDrvier = 0x00000001,
ServiceWin32OwnProcess = 0x00000010,
ServiveWin32ShareProcess = 0x00000020,
ServiceNoChange = SERVICE_NO_CHANGE
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, ServiceAccessRights dwDesiredAccess, int dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool ChangeServiceConfigA(IntPtr hService, ServiceType dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string? lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword, string? lpDisplayName);
public static bool ChangeService(string serviceName, string? userName, string? password)
{
IntPtr scm = OpenSCManager(ScmAccessRights.Connect);
try
{
IntPtr service = OpenService(scm, serviceName, ServiceAccessRights.ChangeConfig);
if (service == IntPtr.Zero)
return false;
if (!ChangeServiceConfigA(service,
ServiceType.ServiceNoChange,
ServiceBootFlag.ServiceNoChange,
ServiceError.ServiceNoChange,
null, null, IntPtr.Zero, null,
userName, password, null))
throw new ApplicationException(":( sadface");
CloseServiceHandle(service);
return true;
}
finally
{
CloseServiceHandle(scm);
}
}
Argh, I found the answer by accident when doing the other WinAPI thing I meant to do:
QueryServiceConfigA()
uses ANSI strings (while QueryServiceConfigW()
uses Unicode), but most importanly, QueryServiceConfig()
is an alias that automatically chooses the right one.
Declared like this, it works like a charm:
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool ChangeServiceConfig(IntPtr hService, ServiceType dwServiceType, ServiceBootFlag dwStartType, ServiceError dwErrorControl, string? lpBinaryPathName, string? lpLoadOrderGroup, IntPtr lpdwTagId, string? lpDependencies, string? lpServiceStartName, string? lpPassword, string? lpDisplayName);