Fervent Coder

Coding Towards Utopia...by Rob Reynolds
posts - 278 , comments - 431 , trackbacks - 0

My Links

News


Rob Reynolds

Subscribe to Fervent Coder RSS
Subscribe to Fervent Coder by Email

About Me

I manage several open source projects. Need...
   ...package management for Windows?
   ...automated builds?
   ...database change management (migrations)?
   ...your application to check email?
   ...a monitoring utility?

I also write for



Like what you are reading? Want to buy me a cup of coffee?
PayPal - The safer, easier way to pay online!

Article Categories

Archives

Post Categories

Image Galleries

Sites

How to Programmatically Install A Windows Service (.NET) on a Remote Machine (with or without Dependencies) - Part One

Sometimes there is no good way to do things on remote computers except through something as low level as WMI (Windows Management Instrumentation).  Installing a service is a great example of this. Fortunately for us, Jared Boelen has already figured this out.  I recommend reading his posts on how to do this. We are not going to get into the actual MS Build Task that he put forth this time, just the code that would allow you to install a service remotely.

NOTE: This is based heavily on Jared's code for Installing Services Remotely. In fact most of it is reused albeit renamed and commented (I took out some of the comments for readability of code). I also updated the service installer so that you can send in dependencies for your service.  I found that dependencies could be added if you look in the MSDN reference for the Create Method of Win32_Service.

First we are going to need to access the WMI.  I changed the name of Jared's WmiHelper to WmiAccess and put an interface on the front of it. I also took away some of the static methods so I could test items that use it both with integration and just functionality.

public interface IWmiAccess
{
    int InvokeInstanceMethod(string machineName, string className, string name, string methodName);

    /// <summary>
    /// Calls a named instance of WMI on the remote machine invoking a method on a WMI Class
    /// </summary>
    /// <param name="machineName">Name of the computer to perform the operation on</param>
    /// <param name="className">The WMI Class to invoke</param>
    /// <param name="name">The name of the WMI Instance</param>
    /// <param name="methodName">The method to call on the WMI Class</param>
    /// <param name="parameters">Parameters for the method</param>
    /// <returns>A return code from the invoked method on the WMI Class</returns>
    int InvokeInstanceMethod(string machineName, string className, string name, string methodName, object[] parameters);

    int InvokeStaticMethod(string machineName, string className, string methodName);

    /// <summary>
    /// Calls WMI on the remote machine invoking a method on a WMI Class
    /// </summary>
    /// <param name="machineName">Name of the computer to perform the operation on</param>
    /// <param name="className">The WMI Class to invoke</param>
    /// <param name="methodName">The method to call on the WMI Class</param>
    /// <param name="parameters">Parameters for the method</param>
    /// <returns>A return code from the invoked method on the WMI Class</returns>
    int InvokeStaticMethod(string machineName, string className, string methodName, object[] parameters);
}


public class WmiAccess : IWmiAccess
{
    private static ManagementScope Connect(string machineName)
    {
        ConnectionOptions options = new ConnectionOptions();
        string path = string.Format("\\\\{0}\\root\\cimv2", machineName);
        ManagementScope scope = new ManagementScope(path, options);
        scope.Connect();

        return scope;
    }

    private static ManagementObject GetInstanceByName(string machineName, string className, string name)
    {
        ManagementScope scope = Connect(machineName);
        ObjectQuery query = new ObjectQuery("SELECT * FROM " + className + " WHERE Name = '" + name + "'");
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
        ManagementObjectCollection results = searcher.Get();
        foreach (ManagementObject manObject in results)
            return manObject;

        return null;
    }

    private static ManagementClass GetStaticByName(string machineName, string className)
    {
        ManagementScope scope = Connect(machineName);
        ObjectGetOptions getOptions = new ObjectGetOptions();
        ManagementPath path = new ManagementPath(className);
        ManagementClass manClass = new ManagementClass(scope, path, getOptions);
        return manClass;
    }

    public int InvokeInstanceMethod(string machineName, string className, string name, string methodName)
    {
        return InvokeInstanceMethod(machineName, className, name, methodName, null);
    }

    public int InvokeInstanceMethod(string machineName, string className, string name, string methodName, object[] parameters)
    {
        try
        {
            ManagementObject manObject = GetInstanceByName(machineName, className, name);
            object result = manObject.InvokeMethod(methodName, parameters);
            return Convert.ToInt32(result);
        }
        catch
        {
            return -1;
        }
    }

    public int InvokeStaticMethod(string machineName, string className, string methodName)
    {
        return InvokeStaticMethod(machineName, className, methodName, null);
    }

    public int InvokeStaticMethod(string machineName, string className, string methodName, object[] parameters)
    {
        try
        {
            ManagementClass manClass = GetStaticByName(machineName, className);
            object result = manClass.InvokeMethod(methodName, parameters);
            return Convert.ToInt32(result);
        }
        catch
        {
            return -1;
        }
    }
}

Next we will need enumerations and the Win32_Service's return codes.

///<summary>
/// How the service starts. Example would be at boot or automatic.
///</summary>
public enum ServiceStartMode
{
    Automatic,
    Boot,
    System,
    Manual,
    Disabled,
}

/// <summary>
/// The return code from the WMI Class Win32_Service
/// </summary>
public enum ServiceReturnCode
{
    Success = 0,
    NotSupported = 1,
    AccessDenied = 2,
    DependentServicesRunning = 3,
    InvalidServiceControl = 4,
    ServiceCannotAcceptControl = 5,
    ServiceNotActive = 6,
    ServiceRequestTimeout = 7,
    UnknownFailure = 8,
    PathNotFound = 9,
    ServiceAlreadyRunning = 10,
    ServiceDatabaseLocked = 11,
    ServiceDependencyDeleted = 12,
    ServiceDependencyFailure = 13,
    ServiceDisabled = 14,
    ServiceLogonFailure = 15,
    ServiceMarkedForDeletion = 16,
    ServiceNoThread = 17,
    StatusCircularDependency = 18,
    StatusDuplicateName = 19,
    StatusInvalidName = 20,
    StatusInvalidParameter = 21,
    StatusInvalidServiceAccount = 22,
    StatusServiceExists = 23,
    ServiceAlreadyPaused = 24
}

/// <summary>
/// What type of service is it? Most of the time it will be OwnProcess
/// </summary>
public enum ServiceType
{
    KernalDriver = 1,
    FileSystemDriver = 2,
    Adapter = 4,
    RecognizerDriver = 8,
    OwnProcess = 16,
    ShareProcess = 32,
    InteractiveProcess = 256,
}

internal enum ServiceErrorControl
{
    UserNotNotified = 0,
    UserNotified = 1,
    SystemRestartedWithLastKnownGoodConfiguration = 2,
    SystemAttemptsToStartWithAGoodConfiguration = 3
}

Now we can set up the WmiService. This code is probably the least like Jared's original implementation of everything presented so far.

/// <summary>
/// Provides functionality to access and modify windows services, not just on the local computer
/// </summary>
public class WmiService
{
    private const string CLASS_NAME = "Win32_Service";
    private readonly IWmiAccess _wmi;

    /// <summary>
    /// Creates a new WmiService for use to access Windows Services
    /// </summary>
    /// <param name="wmi">The WMI access object - the tool that does the low level work</param>
    public WmiService(IWmiAccess wmi)
    {
        _wmi = wmi;
    }

    public ServiceReturnCode Install(string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies)
    {
        return Install(Environment.MachineName, name, displayName, physicalLocation, startMode, userName, password, dependencies, false);
    }

    public ServiceReturnCode Install(string machineName, string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies)
    {
        return Install(machineName, name, displayName, physicalLocation, startMode, userName, password, dependencies, false);
    }

    /// <summary>
    /// Installs a service on any machine
    /// </summary>
    /// <param name="machineName">Name of the computer to perform the operation on</param>
    /// <param name="name">The name of the service in the registry</param>
    /// <param name="displayName">The display name of the service in the service manager</param>
    /// <param name="physicalLocation">The physical disk location of the executable</param>
    /// <param name="startMode">How the service starts - usually Automatic</param>
    /// <param name="userName">The user for the service to run under</param>
    /// <param name="password">The password fo the user</param>
    /// <param name="dependencies">Other dependencies the service may have based on the name of the service in the registry</param>
    /// <param name="interactWithDesktop">Should the service interact with the desktop?</param>
    /// <returns>A service return code that defines whether it was successful or not</returns>
    public ServiceReturnCode Install(string machineName, string name, string displayName, string physicalLocation, ServiceStartMode startMode, string userName, string password, string[] dependencies, bool interactWithDesktop)
    {
        const string methodName = "Create";
        //string[] serviceDependencies = dependencies != null ? dependencies.Split(',') : null;
        if (userName.IndexOf('\\') < 0)
        {
            //userName = ".\\" + userName;
            //UNCOMMENT the line above - it caused issues with color coding in THIS ARTICLE
        }

        try
        {
            object[] parameters = new object[]
                                      {
                                          name, // Name
                                          displayName, // Display Name
                                          physicalLocation, // Path Name | The Location "E:\somewhere\something"
                                          Convert.ToInt32(ServiceType.OwnProcess), // ServiceType
                                          Convert.ToInt32(ServiceErrorControl.UserNotified), // Error Control
                                          startMode.ToString(), // Start Mode
                                          interactWithDesktop, // Desktop Interaction
                                          userName, // StartName | Username
                                          password, // StartPassword |Password
                                          null, // LoadOrderGroup | Service Order Group
                                          null, // LoadOrderGroupDependencies | Load Order Dependencies
                                          dependencies // ServiceDependencies
                                      };
            return (ServiceReturnCode)_wmi.InvokeStaticMethod(machineName, CLASS_NAME, methodName, parameters);
        }
        catch
        {
            return ServiceReturnCode.UnknownFailure;
        }
    }

    public ServiceReturnCode Uninstall(string name)
    {
        return Uninstall(Environment.MachineName, name);
    }

    /// <summary>
    /// Uninstalls a service on any machine
    /// </summary>
    /// <param name="machineName">Name of the computer to perform the operation on</param>
    /// <param name="name">The name of the service in the registry</param>
    /// <returns>A service return code that defines whether it was successful or not</returns>
    public ServiceReturnCode Uninstall(string machineName, string name)
    {
        try
        {
            const string methodName = "Delete";
            return (ServiceReturnCode)_wmi.InvokeInstanceMethod(machineName, CLASS_NAME, name, methodName);
        }
        catch
        {
            return ServiceReturnCode.UnknownFailure;
        }
    }
}

I snipped out the other methods that are part of WmiService so we could concentrate on Install and Uninstall.

Now, let's talk. We need to be confident in the behavior of our WmiService. We need to test.  I was changing already existing functionality, but to gain trust I needed to get tests into the code. Here are the integration tests.

[TestFixture]
public class As_A_WmiService
{
    private readonly string _machineName = Environment.MachineName;
    private const string _serviceName = "_TESTDELETEME";
    private const string _remoteMachineName = "remoteMachineName";
    private const string _serviceLocation = @"c:\WINDOWS\system32\taskmgr.exe";
    private const string _serviceDisplayName = "_TEST DELETE ME";
    private const string _username = "username";
    private const string _password = "password";
    private readonly string[] _dependency = new[] { "MSMQ" };
    private readonly string[] _multipleDependencies = new[] { "MSMQ", "helpsvc" };
    private WmiService _wmiService;
    private const string _localSystemAccount = "LocalSystem";
    private const string _networkAccount = @"NT AUTHORITY\NetworkService";

    [SetUp]
    public void I_want_to()
    {
        _wmiService = new WmiService(new WmiAccess());
    }

    [Test]
    [TestSequence(0)]
    public void Install_a_service_with_no_dependencies_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _serviceName + "1",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            null
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
    }

    [Test]
    [TestSequence(1)]
    public void Uninstall_a_service_successfully()
    {
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_machineName, _serviceName + "1"));
    }

    [Test]
    public void Install_and_uninstall_a_service_with_just_one_dependency_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _serviceName + "2",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            _dependency
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_machineName, _serviceName + "2"));
    }

    [Test]
    public void Install_and_uninstall_a_service_with_multiple_dependencies_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _machineName,
            _serviceName + "3",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            _multipleDependencies
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_machineName, _serviceName + "3"));
    }

    [Test]
    public void Install_and_uninstall_a_service_on_a_remote_machine_with_no_dependencies_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _remoteMachineName,
            _serviceName + "4",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            null
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_remoteMachineName, _serviceName + "4"));
    }

    [Test]
    public void Install_and_uninstall_a_service_on_a_remote_machine_with_just_one_dependency_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _remoteMachineName,
            _serviceName + "5",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            _dependency
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_remoteMachineName, _serviceName + "5"));
    }

    [Test]
    public void Install_and_uninstall_a_service_on_a_remote_machine_with_multiple_dependencies_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _remoteMachineName,
            _serviceName + "6",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _username,
            _password,
            _multipleDependencies
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_remoteMachineName, _serviceName + "6"));
    }

    [Test]
    public void Install_and_uninstall_a_service_using_the_local_system_account_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _serviceName + "7",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _localSystemAccount,
            null,
            null
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_machineName, _serviceName + "7"));
    }

    [Test]
    public void Install_and_uninstall_a_service_using_the_network_service_account_successfully()
    {
        ServiceReturnCode returnCode = _wmiService.Install(
            _serviceName + "8",
            _serviceDisplayName,
            _serviceLocation,
            ServiceStartMode.Automatic,
            _networkAccount,
            null,
            null
            );
        Assert.AreEqual(ServiceReturnCode.Success, returnCode);
        Assert.AreEqual(ServiceReturnCode.Success, _wmiService.Uninstall(_machineName, _serviceName + "8"));
    }
}

Now, to actually use the service, you would do the same thing you did in the tests.  Next time I will show the MS Build and NAnt Tasks for a CreateService task.

Print | posted on Sunday, September 21, 2008 12:50 AM | Filed Under [ Code ]

Feedback

Gravatar

# re: How to Programmatically Install A Windows Service (.NET) on a Remote Machine (with or without Dependencies) - Part One

This is just what I was looking for .. thanks a ton ..
6/5/2009 1:22 AM | Soni
Comments have been closed on this topic.

Powered by: