Executing a task remotely with MsBuild
Recently, we were in need of executing a command on a remote server as part of our automated build process. For this we decided to make use of Windows Management Instrumentation (WMI). Before jumping to the code, copying and pasting it, there are always some security issues that need to be addressed. For this task there are also some service configurations that are required on the remote machine.
Note: All of the following settings are configured on the remote machine on which the command will be run remotely.
Security
Follow the steps below to set the WMI permissions for the build service account:
- Click on Start > Control Panel > Administrative Tools > Computer Management.
- Expand Service and Applications.
- Right-click on WMI Control and click on Properties.
- On the property dialog select the Security tab.
- Expand Root > CIMV2.
- Click on the Security button.
- Click on Add and add <TFSSERVICE> account.
- In the list of permissions, allow the following permissions:
- Execute methods
- Remote enable
- Click on OK.
Follow the step below to set the Local Security Policy for the build service account:
- Click on Start > Control Panel > Administrative Tools > Local Security Policy.
- Expand Local Policies > User Rights Assignment.
- Find and double-click on Log on as a service.
- Click on Add User or Group and add <TFSSERVICE> account.
- Click on OK.
- Find and double-click Log on as a batch job.
- Click on Add User or Group and add <TFSSERVICE> account.
- Click on OK and close the Local Security Policy window.
Services
Follow the steps below set the dependent services to automatically start:
- Click on Start > Control Panel > Administrative Tools.
- Double click on Services.
- Ensure that the following services are Started, and are set to start Automatic.
- COM+ Event System
- Remote Access Auto Connection Manager
- Remote Access Connection Manager
- Remote Procedure Call (RPC)
- Remote Procedure Call (RPC) Locator
- Remote Registry
- Server
- Windows Management Instrumentation
- Windows Management Instrumentation Driver Extensions
- WMI Performance Adapter
- Workstation
- Close the services dialog.
Here is the code for the build task that will execute a command on a remote machine:
/// <summary>
/// Executes a command line on a remote machine.
/// </summary>
public class RemoteExec : Task
{
/// <summary>
/// Initialises a new instance of the <see cref="RemoteExec"/> class.
/// </summary>
public RemoteExec()
{
RemoteMachine = Environment.MachineName;
}
/// <summary>
/// Gets or sets the name of the machine on which to execute the command.
/// </summary>
public string RemoteMachine
{
get;
set;
}
/// <summary>
/// Gets or sets the command to execute on the remote machine.
/// </summary>
[Required]
public string Command
{
get;
set;
}
/// <summary>
/// Executes the task on the remote machine.
/// </summary>
/// <returns>true if the task succeeded, otherwise, false</returns>
public override bool Execute()
{
if (string.IsNullOrEmpty(Command))
{
Log.LogError("Command property was not set.");
throw new ArgumentNullException("Command");
}
var connOptions = new ConnectionOptions();
connOptions.Impersonation = ImpersonationLevel.Impersonate;
connOptions.EnablePrivileges = true;
var managementScope = new ManagementScope(
string.Format(@"\\{0}\ROOT\CIMV2", RemoteMachine), connOptions);
managementScope.Connect();
Log.LogMessage(string.Format(CultureInfo.CurrentCulture, "Connected to {0}: {1}",
RemoteMachine, managementScope.IsConnected));
if (!managementScope.IsConnected)
return false;
var objectGetOptions = new ObjectGetOptions();
var managementPath = new ManagementPath("Win32_Process");
var processClass = new ManagementClass(managementScope, managementPath, objectGetOptions);
var processParameters = processClass.GetMethodParameters("Create");
processParameters["CommandLine"] = Command;
try
{
var outParams = processClass.InvokeMethod("Create", processParameters, null);
var processID = Convert.ToUInt32(outParams["processId"]);
Log.LogMessage(string.Format(CultureInfo.CurrentCulture,
"Creation of the process {0} returned: {1}", processID, outParams["returnValue"]));
var stopQuery = new WqlEventQuery(string.Format(CultureInfo.CurrentCulture,
"select * from Win32_ProcessStopTrace where ProcessID={0}", processID));
var processStopEvent = new ManagementEventWatcher(managementScope, stopQuery);
processStopEvent.Options.Timeout = new TimeSpan(0, 1, 0);
Log.LogMessage("Waiting for process to complete...");
processStopEvent.WaitForNextEvent();
processStopEvent.Stop();
Log.LogMessage("Completed.");
}
catch (Exception ex)
{
Log.LogError(ex.Message);
}
return true;
}
}
The task can then be used as follows:
<UsingTask TaskName="RemoteExec" AssemblyFile="BuildTasks.dll"/> <RemoteExec RemoteMachine="$(Server)" Command="application.exe" />
There we have it, executing a command remotely from MsBuild!

leave a comment