5

如何仅使用 C# 代码授予特定用户(可能是 NET SERVICE 用户)启动和停止特定服务的权限。

我需要生成的代码来处理从 Windows XP 到 Windows 8 的所有内容。

[编辑] 我已经有一个可以工作的服务,以及一个设置文件夹权限、安装服务、启动它等的安装例程。

该服务检查一个 url 以查看是否有更新,如果有,则下载它,并启动一个更新程序来更新服务(并自行终止)。

更新程序更新服务 exe(和其他需要的文件),并需要重新启动服务。

我从研究中知道,可以授予服务用户(在这种情况下为网络服务)启动和停止单个服务的权限,但我不知道用代码执行此操作的 api。

4

1 回答 1

7

我从其他地方得到了一些线索,并设法弄清楚:

[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());
    }
}
于 2013-04-03T19:31:41.253 回答