Introduction
This article introduces how to enable executing PowerShell script remotely in PowerShell console. It also describes how to execute PowerShell scripts locally or remotely in C# code.
All the introduced means are based on my real work and my intention is to manage a remote Windows Server 2008 R2 server by executing PowerShell scripts on a Windows 7 machine. Let me call the remote Windows Server 2008 R2 server as Remote Host
and call the Windows 7 machine as Client
.
The sample code shows how to execute PowerShell commands locally or remotely. You have to first enable PS remoting on both remote host and local client before executing PowerShell commands remotely.
Enable PowerShell Remote
To enable remote PowerShell execution, I did the following steps:
- Install PowerShell 4.0 on both Remote Host and
Local
Client. 4.0 is not mandatory. 2.0+ is OK. Actually, both Windows Server 2008 R2 and Windows 7 have PowerShell 2.0 installed by default. - Add my Windows account to the Administrators group on both Remote Host and Local Client. If both machines are in the same domain, you can use your domain account; if both are in WORKGROUP, you can create one account on each machine with the same name and password, both in the Administrators group.
- Run the following command on Remote Host: winrm quickconfig. This command automatically analyzes and configures the WinRM service which is the core service for remote PowerShell execution.
The winrm quickconfig command is just for quick setup on experimental environment. For production environment, you should carefully configure the permission and firewall according to [2] and [3].
Note that you may encounter several issues when you execute winrm quickconfig or other comlets to enable PowerShell remoting. You can refer to [4] to find the solutions for the 3 most common issues.
Execute PowerShell Script in C#
To execute PowerShell commands in your C# code, you need to reference "C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll" in your project. Note: if your local PowerShell version is lower than 3.0, System.Management.Automation.dll could be in a different folder.
Your may need to use the following two namespaces:
using System.Management.Automation;
using System.Management.Automation.Runspaces;
You can use the PowerShell
instance directly but a better way is to create a Runspace
instance. Every PowerShell
instance works in a Runspace
. You can have multiple Runspaces to connect to different remote hosts at the same time.
The following code piece shows how to create a local Runspace
and execute a command via a PowerShell
instance:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript("Get-Process");
var results = ps.Invoke();
}
runspace.Close();
The following code demonstrates how to execute command remotely:
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.ComputerName = machineAddress;
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript("Get-Service");
var results = ps.Invoke();
}
runspace.Close();
Note: both Runspace
and PowerShell
classes have implemented IDisposable
. So don't forget closing or disposing their instances when you finish using them.
Most of the important methods of Runspace
and PowerShell
have both sync and async forms, for example, Runspace.Open
and OpenAsync
, Runspace.Close
and CloseAsync
, PowerShell.Invoke
and BeginInvoke
, etc.
Parse Results in C#
The return value type of PowerShell.Invoke
is Collection<PSObject>
. Each PSObject
instance reflects the actual object properties and methods as key-value pairs in PSObject.Members
. You can also use PSObject.Properties
to access instance properties and use PSObject.Methods
to access instance methods. Note: PSObject.BaseObject
can be the actual object if the command is executed locally.
Let's take the Get-Process
command as an example. When you execute Get-Process
using ps.Invoke()
like the previous code pieces demonstrates, you will get a Collection<PSObject>
. Each PSObject
instance represents a process. Then you can use the following code to retrieve process members:
foreach (var result in results)
{
Console.WriteLine(result.Members["Id"].Value);
Console.WriteLine(result.Members["ProcessName"].Value);
Console.WriteLine(result.Members["PrivateMemorySize64"].Value);
result.Methods["Kill"].Invoke();
}
<span style="color: rgb(17, 17, 17); font-family: 'Segoe UI', Arial, sans-serif; font-size: 14px;">The code piece above works for both local and remote scenarios. </span>Value
is of the Object
type. It may be a boxed value type. You can invoke the Kill
method for a process and it can succeed if you have proper privileges.
If you execute your code locally, you can cast PSObject.BaseObject
to the exact type instance like the following code piece does:
foreach (var result in results)
{
var process = (System.Diagnostics.Process)result.BaseObject;
Console.WriteLine(process.Id);
Console.WriteLine(process.ProcessName);
Console.WriteLine(process.PrivateMemorySize64);
process.Kill();
}
If you execute a big PS script via PowerShell.AddScript
and PowerShell.Invoke
(or BeginInvoke
), the return value will only contain the results of the last executed command in the script.
Please refer to [1] for more details.
References
Most of the knowledge can be found in the following links: