Search code examples
c#winapiwindows-services

Changing user name of a Windows service in C#


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);
    }
}

Solution

  • 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);