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

Last time we created WmiService.  Now we are going to create the Tasks so that we can call it from the build script.  This time we are going to get our Create Service and Delete Service into our Automated Deployment Scripts of MS Build and/or NAnt.

Let's define our tasks.

The MS Build Tasks

<CreateService
    MachineName="serverName"
    ServiceName="aService"
    ServiceDisplayName="A Service"
    PhysicalLocation="C:\Location\dude.exe"
    StartMode="Automatic"
    UserName="user"
    Password="password"
    Dependencies="MSMQ,hlpsvc"
    />

<DeleteService
    MachineName="serverName"
    ServiceName="aService"
    />

The NAnt Tasks

<createService
    machineName="serverName"
    name="aService"
    displayName="A Service"
    physicalLocation="C:\Location\dude.exe"
    startMode="Automatic"
    userName="user"
    password="password"
    dependencies="MSMQ,hlpsvc"
    />

<deleteService
    machineName="serverName"
    name="aService"
    />

To be honest, I haven't written a custom NAnt Task before. I looked to Jake Opines' article to learn how to do it.  There are a few steps that you have to take to be up and running with NAnt and MSBuild.

Here is what Jake says you need to do get up and running with NAnt:

Before we get started writing any code, we need to reference NAnt.Core.dll. That assembly is located in the directory structure of your NAnt install right next to NAnt.exe.

To define a NAnt task, there are five things we need to do:

  1. Define the name of the task using the TaskName class decorator.
  2. Extend NAnt.Core.Task.
  3. Create any attributes and label them with the TaskAttribute property decorator.
  4. Override the ExecuteTask method.
  5. Make sure you're output assembly ends in Tasks.dll.

With MS Build, you need to follow these steps:

  1. Add a reference to MSBuild.Framework.
  2. Implement ITask, which comes with two properties (BuildEngine and HostObject) and the method Execute.
  3. Define a .TARGETS file that defines the task(s) and where it/they came from.
  4. Point to that TARGETS file in your project file.

Once we have completed those things, we can set up our tasks.  Let's create our Create Service and Delete Service Tasks.

Create Service Task

[TaskName("createService")]
public class CreateService : Task, ITask
{
    #region MSBuild

    public IBuildEngine BuildEngine { get; set; }
    public ITaskHost HostObject { get; set; }

    /// <summary>
    /// The function for the MSBuild task that actually does the task
    /// </summary>
    /// <returns>true if the task is successful</returns>
    bool ITask.Execute()
    {
        ServiceReturnCode returnCode = CreateTheService();
        MessageImportance importance = returnCode == ServiceReturnCode.Success ? MessageImportance.Normal : MessageImportance.High;
        string message = "CreateService returned:  " + returnCode.ToString();
        BuildEngine.LogMessageEvent(new BuildMessageEventArgs(message, String.Empty, "CreateServiceTask", importance));

        return (returnCode == ServiceReturnCode.Success);
    }

    #endregion

    #region NAnt

    /// <summary>
    /// Executes the NAnt task
    /// </summary
    protected override void ExecuteTask()
    {
        Project.Log(Level.Info, string.Format("Calling Create Service to install {0} with physical location {1} on {2} with user {3} and startup mode {4}.", ServiceName, PhysicalLocation, MachineName ?? Environment.MachineName, UserName, StartMode));
        ServiceReturnCode returnCode = CreateTheService();
        if (returnCode != ServiceReturnCode.Success)
        {
            Project.Log(Level.Error, string.Format("Create Service was unsuccessful! It returned code {0}.", returnCode.ToString()));
        }
        else
        {
            Project.Log(Level.Info, string.Format("Service named {0} was installed successfully on {1}.", ServiceDisplayName, MachineName ?? Environment.MachineName));
        }
    }

    #endregion

    #region Properties

    /// <summary>
    /// The name of the machine
    /// </summary>
    [TaskAttribute("machineName", Required = false)]
    [StringValidator(AllowEmpty = false)]
    public string MachineName { get; set; }

    /// <summary>
    /// The name of the service
    /// </summary>
    [Required]
    [TaskAttribute("name", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string ServiceName { get; set; }

    /// <summary>
    /// The display name of the service - what is seen in service management
    /// </summary>
    [Required]
    [TaskAttribute("displayName", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string ServiceDisplayName { get; set; }

    ///<summary>
    /// The location of the service
    ///</summary>
    [Required]
    [TaskAttribute("physicalLocation", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string PhysicalLocation { get; set; }

    ///<summary>
    /// The start mode of the service - Automatic, Manual, Boot, System, or Disabled
    ///</summary>
    /// <remarks>If something is incorrect, Automatic is chosen</remarks>
    [Required]
    [TaskAttribute("startMode", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string StartMode { get; set; }

    /// <summary>
    /// The user name for the service to run under - if using LocalSystem or NT AUTHORITY\NetworkService, leave password blank
    /// </summary>
    [Required]
    [TaskAttribute("userName", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string UserName { get; set; }

    /// <summary>
    /// Password for the account to run under
    /// </summary>
    [TaskAttribute("password", Required = false)]
    [StringValidator(AllowEmpty = true)]
    public string Password { get; set; }

    /// <summary>
    /// A comma separated string of services that this service will depend on. Service names. Not the service display names.
    /// </summary>
    [TaskAttribute("dependencies", Required = false)]
    [StringValidator(AllowEmpty = true)]
    public string Dependencies { get; set; }

    #endregion

    /// <summary>
    /// Creates a windows service through WMI
    /// </summary>
    /// <returns>true if the task is successful</returns>
    public ServiceReturnCode CreateTheService()
    {
        WmiService wmiService = new WmiService(new WmiAccess());

        ServiceStartMode startModeEnum;
        try
        {
            startModeEnum = (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), StartMode);
        }
        catch (Exception)
        {
            startModeEnum = ServiceStartMode.Automatic;
        }

        string[] dependencies = Dependencies != null ? Dependencies.Split(',') : null;
        return wmiService.Install(MachineName ?? Environment.MachineName, ServiceName, ServiceDisplayName, PhysicalLocation, startModeEnum, UserName, Password, dependencies);
    }
}

The Delete Service Task

///<summary>
/// Deletes a windows service
///</summary>
[TaskName("deleteService")]
public class DeleteService : Task, ITask
{
    #region MSBuild

    /// <summary>
    /// The engine for the MS Build task
    /// </summary>
    public IBuildEngine BuildEngine { get; set; }

    /// <summary>
    /// The Host for the MS Build Task
    /// </summary>
    public ITaskHost HostObject { get; set; }

    /// <summary>
    /// The function for the MSBuild task that actually does the task
    /// </summary>
    /// <returns>true if the task is successful</returns>
    bool ITask.Execute()
    {
        ServiceReturnCode returnCode = DeleteTheService();
        MessageImportance importance = returnCode == ServiceReturnCode.Success ? MessageImportance.Normal : MessageImportance.High;
        string message = "DeleteServiceTask returned:  " + returnCode.ToString();
        BuildEngine.LogMessageEvent(new BuildMessageEventArgs(message, String.Empty, "DeleteServiceTask", importance));

        return (returnCode == ServiceReturnCode.Success);
    }

    #endregion

    #region NAnt

    /// <summary>
    /// Executes the NAnt task
    /// </summary>
    protected override void ExecuteTask()
    {
        Project.Log(Level.Info, string.Format("Calling Delete Service to uninstall {0} from {1}.", ServiceName, MachineName ?? Environment.MachineName));
        ServiceReturnCode returnCode = DeleteTheService();
        if (returnCode != ServiceReturnCode.Success)
        {
            Project.Log(Level.Error, string.Format("Delete Service was unsuccessful! It returned code {0}.", returnCode.ToString()));
        }
        else
        {
            Project.Log(Level.Info, string.Format("Service named {0} was uninstalled successfully on {1}.", ServiceName, MachineName ?? Environment.MachineName));
        }
    }

    #endregion

    #region Properties

    /// <summary>
    /// The name of the machine
    /// </summary>
    [TaskAttribute("machineName", Required = false)]
    [StringValidator(AllowEmpty = false)]
    public string MachineName { get; set; }

    /// <summary>
    /// The name of the service - not the display name
    /// </summary>
    [Required]
    [TaskAttribute("name", Required = true)]
    [StringValidator(AllowEmpty = false)]
    public string ServiceName { get; set; }

    #endregion

    /// <summary>
    /// Deletes a windows service through WMI
    /// </summary>
    /// <returns>true if the task is successful</returns>
    public ServiceReturnCode DeleteTheService()
    {
        WmiService wmiService = new WmiService(new WmiAccess());
        return wmiService.Uninstall(MachineName ?? Environment.MachineName, ServiceName);
    }
}

 

Then you can deploy your tasks.  NAnt information requires that log4Net, NAnt.Core.dll, and possibly some other required assemblies be close by. And that is all for now.

Print | posted @ Sunday, September 21, 2008 5:09 AM

Comments have been closed on this topic.