lc.
lc.

Reputation: 116528

Installing a Windows service to run as virtual user

I have a ServiceProcessInstaller which installs a .NET Windows Service.

The install process works perfectly if I either:

However, I would like the service to run as a virtual user, a la NT Service\ServiceName. If you look at the some of the SQL Server services, you will see they default to log on as their own virtual user account. There is more, albeit limited, information at http://technet.microsoft.com/en-us/library/dd548356.aspx.


I have tried setting serviceProcessInstaller1.Username = @"NT Service\ServiceName", but the installer then throws the following error no matter what I give as the password (I have tried String.Empty, same as the user name, my own password, null to bring up the interactive dialog, and even random junk):

No mapping between account names and security IDs was done

However, if I install the service normally (e.g. run as SYSTEM), I can then go in to the service's properties from the services.msc snap-in, on the Log On page change the user to NT Service\ServiceName, and it works beautifully.

I have also looked into the ChangeServiceConfig2 function but I can't seem to get it to change anything either.


How can I set the log on user to the virtual user NT Service\ServiceName from code within my ServiceProcessInstaller?

Upvotes: 3

Views: 2842

Answers (2)

George Mamaladze
George Mamaladze

Reputation: 7931

An alternative approach to the solution suggested above (using ServiceInstaller.Committed event) is described as workaround on connect.microsoft.com. The idea is to tweak a private field haveLoginInfo via reflection to allow null as valid password.

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

Even this solution is not less hacky, at least it's visually less ugly.

NOTE: There is a mistake in the workaround description at connect.microsoft.com. The private field name mentioned there is hasLoginInfo but must be haveLoginInfo.

Upvotes: 1

lc.
lc.

Reputation: 116528

You cannot do it directly with the ServiceProcessInstaller object. However, you can set the username after the service is installed, in the ServiceInstaller.Committed event, using the Change method in WMI. Specify the username as wmiParams[6] and leave the password 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);
    }
}

Finally, don't forget to give the user read/execute permission on your service exe and config files, or you will get an Access Denied error.

Upvotes: 3

Related Questions