To tide me over until PowerShell V2 ships and we can perform remote PowerShell out of the box, I have created a simple WMI based CmdLet that will asynchronously execute a command - remotely. You might find it useful too.
The main limitation is that it does not track the result of the remote execution. You should be able to call any .EXE such as "cmd.exe /c" or "powershell.exe -command".
Here is the C# snippet - I don't code much so use with caution!
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
namespace JamesSaull.PowerShell
{
/// <summary>
/// InvokeRemoteCommand is a PowerShell CmdLet using WMI to execute a command on a remote server.
/// </summary>
/// <example>Invoke-RemoteCommand -TargetServerName WEB01 -RemoteCommand "cmd.exe /c mkdir c:\temp\testfolder"</example>
[Cmdlet("Invoke", "RemoteCommand")]
public class InvokeRemoteCommand : DCPCommandLet
{
#region Fields
private string remoteCommand;
private string targetServerName;
private string username = string.Empty;
private string password = string.Empty;
#endregion
#region Command Arguments / Public Properties
/// <summary>
/// Name of the server where the command should be executed.
/// </summary>
[Parameter(Position = 0, HelpMessage = "Name of the server where the command should be executed.", Mandatory = true)]
[ValidateNotNullOrEmpty]
public string TargetServerName
{
get { return targetServerName; }
set { targetServerName = value; }
}
/// <summary>
/// The command to be executed. e.g. cmd.exe /c mkdir c:\temp\testfolder
/// </summary>
[Parameter(Position = 1, HelpMessage = "The command to be executed. e.g. cmd.exe /c mkdir c:\temp\testfolder", Mandatory = true)]
[ValidateNotNullOrEmpty]
public string RemoteCommand
{
get { return remoteCommand; }
set { remoteCommand = value; }
}
/// <summary>
/// Username used to authenticate and authorise against Target Server. Not allowed for local connections.
/// </summary>
[Parameter(Position = 3, HelpMessage = "Username used to authenticate and authorise against Target Server. Not allowed for local connections.")]
[ValidateNotNullOrEmpty]
public string Username
{
get { return username; }
set { username = value; }
}
/// <summary>
/// Password used to authenticate and authorise against Target Server.
/// </summary>
[Parameter(Position = 4, HelpMessage = "Password used to authenticate and authorise against Target Server.")]
[ValidateNotNullOrEmpty]
public string Password
{
get { return password; }
set { password = value; }
}
#endregion
#region Overriding CmdLet
/// <summary>
/// Currently this CmdLet does not allow usage in a pipeline so it only overrides EndProcessing and not ProcessRequest or BeginProcessing.
/// </summary>
protected override void EndProcessing()
{
base.EndProcessing();
ExecuteCommand();
}
#endregion
#region Private Methods
/// <summary>
/// This is where the actual work is done. It uses WMI to create Win32_Process on the Target server and asynchronously invokes the supplied command. There is no exception handling to ensure that exceptions bubble up to the shell as no remedial action can take place here.
/// </summary>
private void ExecuteCommand()
{
ConnectionOptions connection = new ConnectionOptions();
// Point of contention: should we allow empty passwords? Currently this is disallowed by the ValidateNotNullOrEmpty attribute.
if (!string.IsNullOrEmpty(username))
{
// By default, unless you specify connection.Authority, it will use NTLM not Kerberos
connection.Username = username;
connection.Password = password;
}
ManagementScope scope = new ManagementScope(string.Format("\\\\{0}\\root\\CIMV2", targetServerName), connection);
scope.Connect();
ManagementClass classInstance = new ManagementClass(scope, new ManagementPath("Win32_Process"), null);
ManagementBaseObject inParams = classInstance.GetMethodParameters("Create");
inParams["CommandLine"] = remoteCommand;
ManagementBaseObject outParams = classInstance.InvokeMethod("Create", inParams, null);
}
#endregion
}
}