Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to give a user permission to start and stop a particular service using C# code

How can I give a particular user (probably the NET SERVICE user) permission to start and stop a particular service, using C# code only.

I need the resulting code to work on everything from Windows XP to Windows 8.

[Edit] I already have a service, which works, and an installation routine that sets folder permissions, installs the service, starts it, etc.

The service checks a url to see if there is an update, and, if so, downloads it, and starts an updater program to update the service (and terminates itself).

The updater program updates the service exe (and other needed files), and needs to restart the service.

I know from research that it is possible to give the service user (NETWORK SERVICE in this case) permission to start and stop an individual service, but I don't know the api to do it in code.

like image 628
Nikki Locke Avatar asked Apr 02 '13 18:04

Nikki Locke


People also ask

How do I give permission to run and stop services?

In the Permissions for User or Group list, configure the permissions that you want for the user or group. When you add a new user or group, the Allow check box next to the Start, stop and pause permission is selected by default. This setting permits the user or group to start, stop, and pause the service.

How do I give privileges to start system services?

Navigate to Computer Configuration > Windows Settings > Security Settings > Local Policies >User Rights Assignment. In the details pane, double-click Log on as a service. Click Add User or Group… and add the account to the list of accounts that have the Log on as a service right.

How do I stop a service as administrator?

Open Start. Search for PowerShell, right-click the top result, and select the Run as administrator option. Type the following command to disable a service and press Enter: Set-Service -Name "SERVICE-NAME" -Status stopped -StartupType disabled In the command, update "SERVICE-NAME" for the name of the service.


1 Answers

I got a few clues from elsewhere, and managed to figure it out:

[StructLayoutAttribute(LayoutKind.Sequential)]
struct SECURITY_DESCRIPTOR {
    public byte revision;
    public byte size;
    public short control;
    public IntPtr owner;
    public IntPtr group;
    public IntPtr sacl;
    public IntPtr dacl;
}

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool QueryServiceObjectSecurity(IntPtr serviceHandle, 
    System.Security.AccessControl.SecurityInfos secInfo, 
    ref SECURITY_DESCRIPTOR lpSecDesrBuf, 
    uint bufSize, 
    out uint bufSizeNeeded);

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool QueryServiceObjectSecurity(SafeHandle serviceHandle, 
    System.Security.AccessControl.SecurityInfos secInfo, 
    byte[] lpSecDesrBuf, 
    uint bufSize, 
    out uint bufSizeNeeded);

[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetServiceObjectSecurity(SafeHandle serviceHandle, 
    System.Security.AccessControl.SecurityInfos secInfos, 
    byte[] lpSecDesrBuf);

public void SetServicePermissions(string service, string username) {
    System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(service, ".");
    ServiceControllerStatus status = sc.Status;
    byte[] psd = new byte[0];
    uint bufSizeNeeded;
    bool ok = QueryServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, psd, 0, out bufSizeNeeded);
    if (!ok) {
        int err = Marshal.GetLastWin32Error();
        if (err == 122 || err == 0) { // ERROR_INSUFFICIENT_BUFFER
            // expected; now we know bufsize
            psd = new byte[bufSizeNeeded];
            ok = QueryServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, psd, bufSizeNeeded, out bufSizeNeeded);
        } else {
            throw new ApplicationException("error calling QueryServiceObjectSecurity() to get DACL for " + _name + ": error code=" + err);
        }
    }
    if (!ok)
        throw new ApplicationException("error calling QueryServiceObjectSecurity(2) to get DACL for " + _name + ": error code=" + Marshal.GetLastWin32Error());

    // get security descriptor via raw into DACL form so ACE
    // ordering checks are done for us.
    RawSecurityDescriptor rsd = new RawSecurityDescriptor(psd, 0);
    RawAcl racl = rsd.DiscretionaryAcl;
    DiscretionaryAcl dacl = new DiscretionaryAcl(false, false, racl);

    // Add start/stop/read access
    NTAccount acct = new NTAccount(username);
    SecurityIdentifier sid = (SecurityIdentifier) acct.Translate(typeof(SecurityIdentifier));
    // 0xf7 is SERVICE_QUERY_CONFIG|SERVICE_CHANGE_CONFIG|SERVICE_QUERY_STATUS|
    // SERVICE_START|SERVICE_STOP|SERVICE_PAUSE_CONTINUE|SERVICE_INTERROGATE
    dacl.AddAccess(AccessControlType.Allow, sid, 0xf7, InheritanceFlags.None, PropagationFlags.None);

    // convert discretionary ACL back to raw form; looks like via byte[] is only way
    byte[] rawdacl = new byte[dacl.BinaryLength];
    dacl.GetBinaryForm(rawdacl, 0);
    rsd.DiscretionaryAcl = new RawAcl(rawdacl, 0);

    // set raw security descriptor on service again
    byte[] rawsd = new byte[rsd.BinaryLength];
    rsd.GetBinaryForm(rawsd, 0);
    ok = SetServiceObjectSecurity(sc.ServiceHandle, SecurityInfos.DiscretionaryAcl, rawsd);
    if (!ok) {
        throw new ApplicationException("error calling SetServiceObjectSecurity(); error code=" + Marshal.GetLastWin32Error());
    }
}
like image 160
Nikki Locke Avatar answered Oct 18 '22 22:10

Nikki Locke