2

我有一个ServiceProcessInstaller安装 .NET Windows 服务的。

如果我符合以下任一条件,安装过程将完美运行:

  • 将服务设置为以 SYSTEM ( serviceProcessInstaller1.Account = ServiceAccount.LocalSystem) 身份运行。
  • serviceProcessInstaller1.Account = ServiceAccount.User通过指定UsernameandPassword属性或让安装过程提示我,将服务设置为作为普通用户 ( ) 运行。

但是,我希望该服务以虚拟用户身份运行,例如 la NT Service\ServiceName。如果您查看一些 SQL Server 服务,您会看到它们默认以自己的虚拟用户帐户登录。在http://technet.microsoft.com/en-us/library/dd548356.aspx上有更多信息,尽管有限。


我尝试过设置serviceProcessInstaller1.Username = @"NT Service\ServiceName",但是无论我提供什么密码,安装程序都会引发以下错误(我尝试过String.Empty,与用户名相同,我自己的密码,null以调出交互式对话框,甚至是随机垃圾):

未完成帐户名称和安全 ID 之间的映射

但是,如果我正常安装服务(例如以 SYSTEM 身份运行),然后我可以从services.msc管理单元进入服务的属性,在登录页面上将用户更改为NT Service\ServiceName,它工作得很好。

我也研究了这个ChangeServiceConfig2功能,但我似乎也无法让它改变任何东西。


如何NT Service\ServiceName从我的代码中将登录用户设置为虚拟用户ServiceProcessInstaller

4

2 回答 2

3

您不能直接对ServiceProcessInstaller对象执行此操作。但是,您可以在安装服务后设置用户名,ServiceInstaller.Committed如果使用WMI 中的 Change 方法。将用户名指定为wmiParams[6]并将密码保留为 null

void serviceInstaller1_Committed(object sender, InstallEventArgs e)
{
    using (ManagementObject service = new ManagementObject(new ManagementPath("Win32_Service.Name='ServiceName'")))
    {
        object[] wmiParams = new object[11];
        wmiParams[6] = @"NT Service\ServiceName";
        service.InvokeMethod("Change", wmiParams);
    }
}

最后,不要忘记授予用户对您的服务 exe 和配置文件的读取/执行权限,否则您将收到拒绝访问错误。

于 2013-02-01T16:04:42.150 回答
1

上面建议的解决方案的替代方法(使用事件)在connect.microsoft.comServiceInstaller.Committed上被描述为解决方法。这个想法是通过反射调整私有字段以允许作为有效密码。haveLoginInfonull

    const string s_ServiceName = "myservice1";
    const string s_DisplayName = "Tell admin what it is";
    const string s_Description = "Tell admin what it does";

        var procesServiceInstaller = new ServiceProcessInstaller
        {
            Account = ServiceAccount.User,
            Username = string.Format("NT Service\\{0}", s_ServiceName),
            Password = null,
        };

        //Here comes the hack.
        // ReSharper disable once PossibleNullReferenceException
        procesServiceInstaller
            .GetType()
            .GetField("haveLoginInfo", BindingFlags.Instance | BindingFlags.NonPublic)
            .SetValue(procesServiceInstaller, true);



        var serviceInstaller = new ServiceInstaller();
        var path = string.Format(
              "/assemblypath={0}",
              Assembly.GetExecutingAssembly().Location);
        string[] cmdline = { path };

        var context = new InstallContext("", cmdline);
        serviceInstaller.Context = context;
        serviceInstaller.DisplayName = s_DisplayName;
        serviceInstaller.ServiceName = s_ServiceName;
        serviceInstaller.Description = s_Description;
        serviceInstaller.StartType = ServiceStartMode.Manual;
        serviceInstaller.Parent = procesServiceInstaller;

        try
        {
            var state = new ListDictionary();
            serviceInstaller.Install(state);
        }
        catch (Win32Exception win32Exception)
        {
            //TODO: HandleException(win32Exception); 
        }
        catch (InvalidOperationException ex)
        {
            //TODO: HandleException(ex);
        }

即使是这个解决方案也不是不那么老套,至少它在视觉上不那么难看。

注意: connect.microsoft.com上的解决方法描述中有一个错误。那里提到的私有字段名称是hasLoginInfo但必须是haveLoginInfo

于 2016-01-05T14:57:30.123 回答