【问题标题】:Installing a Windows service to run as virtual user安装 Windows 服务以作为虚拟用户运行
【发布时间】:2013-02-01 14:41:19
【问题描述】:

我有一个ServiceProcessInstaller,它安装了一个 .NET Windows 服务。

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

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

但是,我希望服务以虚拟用户身份运行,a 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 function,但我似乎也无法让它改变任何东西。


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

【问题讨论】:

  • 托管和虚拟服务帐户是一项相当新的 Windows 功能。您需要在 serverfault.com 之类的网站上询问有关它的问题,在这种地方您可以找到修补此问题的系统管理员。 technet.microsoft.com/en-us/library/dd548356%28WS.10%29.aspx
  • @HansPassant 我意识到这有点边界,但我的问题是关于如何从代码中设置用户,因为我可以清楚地手动设置用户。因此,我认为这更像是一个编程问题,而不是系统管理员问题,并在此处发布。关于安装服务的 SO 问题似乎也比 SF 多——我想这并不意味着什么。

标签: .net windows-services installation


【解决方案1】:

您不能直接使用 ServiceProcessInstaller 对象来执行此操作。但是,您可以在安装服务后,在ServiceInstaller.Committed 事件中使用the Change method in WMI 设置用户名。将用户名指定为wmiParams[6]将密码留空

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 和配置文件的读取/执行权限,否则您将收到拒绝访问错误。

【讨论】:

  • 如果您像我一样是个傻瓜,那么您在wmiParams[6] 行中提供的帐户名称与您的服务名称匹配实际上很重要。
【解决方案2】:

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

    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

【讨论】:

    猜你喜欢
    • 2012-08-28
    • 2010-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 2016-12-31
    相关资源
    最近更新 更多