PowerShell SDK + Remote - c#

I would like to run a powershell (.ps1) script on a remote machine, from a .NET program.
The remote machine is set up correctly, I can connect to it from a PowerShell console.
The run script code is the following (using System.Management.Automation, from PowerShell.SDK.7.2.0-Preview.4 nuget package)
public static void RunScript(string scriptFile, string remoteHost, string remoteUser, SecureString remotePassword)
{
PSCredential credential = new PSCredential(remoteUser, remotePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, remoteHost, 5985, "/wsman",
"http://schemas.microsoft.com/powershell/Microsoft.PowerShell", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Negotiate;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
try
{
var shell = PowerShell.Create();
shell.Runspace = runspace;
shell.Commands.AddScript(scriptFile, false);
Collection<PSObject> results = shell.Invoke();
}
finally
{
runspace.Close();
}
}
}
The remote host, and the username/password credentials are correct (I can connect to the remote machine with the exact same credentials from the PowerShell console)
The CreateRunspace going fine. But the shell.Invoke() does nothing. No exceptions, no result (results contains 0 elements)
If I run the exact same code without the runspace assignment (so the PowerShell usign a default, local runspace), the Invoke() method runs fine, and the result collection is correct.
Has anybody has an idea what should I look for?
Thanks in advance!

Related

Program or the command type does not support user interaction

I am running PowerShell scripts from C# successfully. In some instances I receive the following message:
A program that prompts a user failed because the host program or the command type does not support user interaction. The host was attempting to request confirmation with the following message "insert message here".
Here's a real example of the message:
I would like to handle and be able to respond to such prompts in the C# application.
Here's the code I am successfully running to execute PS scripts:
private ICollection<PSObject> PSExecute()
{
InitialSessionState iss = InitialSessionState.CreateDefault();
string script = #"C:\script\";
using (Runspace runSpace = RunspaceFactory.CreateRunspace(iss))
{
runSpace.Open();
//using (Pipeline pipeLine = runSpace.CreatePipeline())
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runSpace;
var ps = powershell.AddScript(script);
var results = powershell.Invoke();
return results;
}
}
}
I am looking for a way to extend this so that I can handle and pass such prompts to the C# application e.g. to a MessageBox so the user can respond.

To call a powershell script file (example.ps1) from C#

I tried running a script localwindows.ps1 from C# using the following Code :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
String file = "C:\\localwindows.ps1";
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(file);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
}
But getting exception :'The term 'C:\localwindows.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
So I tried the following :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
PSCommand new1 = new PSCommand();
String machinename = "machinename";
String file = "C:\\localwindows.ps1";
new1.AddCommand("Invoke-Command");
new1.AddParameter("computername", machinename);
new1.AddParameter("filepath", file);
powershell.Commands = new1;
Console.WriteLine(powershell.Commands.ToString());
Collection<PSObject> results = powershell.Invoke();
}
I am getting the error : "Cannot find path 'C:\localwindows.ps1' because it does not exist."
But using command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' ,from powershell in local machine created a new account in the remote machine.
How to call the script localwindows.ps1 from C#?
How to execute the command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' through C#?
The script localwindows.ps1 is
$comp = [adsi]“WinNT://machinename,computer”
$user = $comp.Create(“User”, "account3")
$user.SetPassword(“change,password.10")
$user.SetInfo()
Actually your invocation style should work. But in both of your examples, the script c:\localwindows.ps1 must reside on the local computer. In the Invoke-Command case, it will be copied from the local computer to the remote computer.
If, in the Invoke-Command case, the script already exists on the remote computer and you don't need to copy it over, remove the FilePath parameter and add this:
new1.AddParameter("Scriptblock", ScriptBlock.Create(file));
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// for your specific issue I think this would be easier
var results = machineManager.RunScript(
File.ReadAllText("C:\\LocalWindows.ps1"));
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Please mark as answer if this helps. I've been using this for a while with my automated deployments. Please leave comments if you find issues.

Why does PowerShell class not load a snapin

I have this code in which I load a snapin (from MS Dynamics NAV in this case):
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript("Add-PSSnapin Microsoft.Dynamics.Nav.Management")
.AddScript("Get-NAVServerInstance");
//This does not work. Says unknown cmdlet Get-NAVServerInstance
//ps.AddCommand("Add-PSSnapin").AddArgument("Microsoft.Dynamics.Nav.Management")
// .AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
This code works when I use the AddScript method as shown in the code.
But why does AddCommand method not work (see commented code)? Looks like the snapin is not loaded, because the error says that the Get-NAVServerInstance cmdlet is unknown.
How is this supposed to work?
I know I can create a runspace with an InitialSessionState on which I have imported the snapin. Then the ps.AddCommand("Get-NAVServerInstance") is working.
But when I want to create a remote runspace session (using WSManConnectionInfo) I can't find a way to supply an initialSessionState.
UPDATE:
So it seems that AddCommand only can be used for cmdlets available when the runspace is opened (or created?). Using an InitialSessionState or RunspaceConfiguration instance with RunspaceFactory.CreateRunspace(...) will do. So this code works:
var config = RunspaceConfiguration.Create();
PSSnapInException warning;
config.AddPSSnapIn("Microsoft.Dynamics.Nav.Management", out warning);
using (Runspace runspace = RunspaceFactory.CreateRunspace(config))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
But my problem is in that case, that I can't specify a WSManConnectionInfo instance.
So how can I create a runspace with a remote connection with a snapin (installed on the remote machine) loaded? How to supply a configuration for a remote connection?
I finally found a hint how to configure a remote session (see https://superuser.com/a/518567).
You need to register a session configuration on the remote computer with
Register-PSSessionConfiguration -Name MyShell -StartupScript 'MyInitScript.ps1'
Then you can set the shellUri parameter of WSManConnectionInfo to http://schemas.microsoft.com/powershell/MyShell
The runspace you create this way will have the commands available which are imported by the MyInitScript.ps1 startup script.
So now this code will work:
string shell = "http://schemas.microsoft.com/powershell/MyShell";
var target = new Uri("https://myserver:port/wsman");
var secured = new SecureString();
foreach (char letter in "password")
{
secured.AppendChar(letter);
}
secured.MakeReadOnly();
var credential = new PSCredential("username", secured);
var connectionInfo = new WSManConnectionInfo(target, shell, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
Try invoking AddScript like so:
.AddScript("...", false)
this will execute the command in the global scope instead of a new local scope.
I think the proper way to do this is to use the RunspaceConfiguration class. It has an AddPSSnapin method.

PowerShell remote command fail because of unencrypted traffic

The following codes run:
SecureString password = new SecureString();
string runasUsername = "USERNAME";
string runasPassword = "PASSWORD";
string liveIdconnectionUri = "http://EXCHANGE_SERVER/PowerShell";
foreach (char x in runasPassword)
{
password.AppendChar(x);
}
PSCredential credential = new PSCredential(runasUsername, password);
// Set the connection Info
WSManConnectionInfo connectionInfo = new WSManConnectionInfo((new Uri(liveIdconnectionUri)), "http://schemas.microsoft.com/powershell/Microsoft.Exchange",
credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic; //AuthenticationMechanism.Default;
// create a runspace on a remote path
// the returned instance must be of type RemoteRunspace
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo);
PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command.AddCommand("Enable-Mailbox");
command.AddParameter("Identity", "first.last");
command.AddParameter("Alias", "Fist Last");
powershell.Commands = command;
try
{
// open the remote runspace
runspace.Open();
// associate the runspace with powershell
powershell.Runspace = runspace;
// invoke the powershell to obtain the results
var result = powershell.Invoke();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// dispose the runspace and enable garbage collection
runspace.Dispose();
runspace = null;
// Finally dispose the powershell and set all variables to null to free
// up any resources.
powershell.Dispose();
powershell = null;
}
Console.WriteLine("done");
Console.Read();
Exceptions throws:
Connecting to remote server failed with the following error message :
The WinRM client cannot process the request. Unencrypted traffic is
currently disabled in the client configuration. Change the client
configuration and try the request again. For more information, see the
about_Remote_Troubleshooting Help topic.
I already set Basic Auth, allow unecrypted traffic.
I tried solution here powershell v2 remoting - How do you enable unencrypted traffic , no luck.
Sorry, struggled a for long time, kept changing possible combinations, and finally works with this:
The AuthenticationMechanism should be AuthenticationMechanism.Default, not AuthenticationMechanism.Basic (It's weird).
The final working version is:
SecureString password = new SecureString();
string runasUsername = "USERNAME";
string runasPassword = "PASSWORD";
string liveIdconnectionUri = "http://EXCHANGE_SERVER/PowerShell";
foreach (char x in runasPassword)
{
password.AppendChar(x);
}
PSCredential credential = new PSCredential(runasUsername, password);
// Set the connection Info
WSManConnectionInfo connectionInfo = new WSManConnectionInfo((new Uri(liveIdconnectionUri)), "http://schemas.microsoft.com/powershell/Microsoft.Exchange",
credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Default; //AuthenticationMechanism.Default;
// create a runspace on a remote path
// the returned instance must be of type RemoteRunspace
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command.AddCommand("Enable-Mailbox");
command.AddParameter("Identity", "MAIL_USER_ID_HERE");
powershell.Commands = command;
try
{
// open the remote runspace
runspace.Open();
// associate the runspace with powershell
powershell.Runspace = runspace;
// invoke the powershell to obtain the results
var result = powershell.Invoke();
if (result.Count > 0)
Console.WriteLine("sucessful!");
else
Console.WriteLine("failed!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// dispose the runspace and enable garbage collection
runspace.Dispose();
runspace = null;
// Finally dispose the powershell and set all variables to null to free
// up any resources.
powershell.Dispose();
powershell = null;
}
Console.WriteLine("done");
Console.Read();
I had the same issue. It should also be pointed out that, for the Virtual directory on the EXCHANGE_SERVER hosting the PowerShell instance, you should configure the SSL settings to "Accept" but not "Require SSL" in IIS Manager, assuming you still have the default self-signed certificate installed on the server. That plus the "AuthenticationMechanism.Default" setting got rid of the myriad exceptions I encountered at the line:
runspace.Open();
Also, if you want to unit test this locally, you should Install the Exchange Management Tools on your desktop.
...or, if you don't have Windows 8, try this approach: PowerShell Managed code in Exchange 2010.
AuthenticationMechanism.Default worked for me but lead to another error message...
The WinRM client cannot process the request. Default authentication
may be used with an IP address under the following conditions: the
transport is HTTPS or the destination is in the TrustedHosts list, and
explicit credentials are provided. Use winrm.cmd to configure
TrustedHosts. Note that computers in the TrustedHosts list might not
be authenticated. For more information on how to set TrustedHosts run
the following command: winrm help config. For more information, see
the about_Remote_Troubleshooting Help topic.
Note that EXCHANGE_SERVER must be a DNS name, not an IP address like I was using. I also had to set the AllowUnencrypted config setting on both the client and the Exchange server. See the link below for details on that setting.
powershell v2 remoting - How do you enable unencrypted traffic

how to invoke the powershell command with "format-list" and "out-file" pipeline from c#?

Hi I'm working on a C# program to call exchange 2010 powershell cmdlets in remote runspace. The ps command is:
"Get-MailboxDatabase -Server EX2010SVR1 -Status | Format-List
Identity,Guid,mounted,CircularLoggingEnabled,Recovery | Out-File
'C:\db.txt' -Encoding UTF8 -Width 8192".
My code is similar to:
static int Main(string[] args)
{
const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.Exchange";
const string COMMAND = "Get-MailboxDatabase -Server EX2010SVR1 -Status | Format-List Identity,Guid,mounted,CircularLoggingEnabled,Recovery | Out-File 'C:\db.txt' -Encoding UTF8 -Width 8192";
System.Uri serverUri = new Uri("http://EX2010SVR1/powershell?serializationLevel=Full");
PSCredential creds = (PSCredential)null; // Use Windows Authentication
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(serverUri, SHELL_URI, creds);
try
{
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
PowerShell psh = PowerShell.Create();
psh.Runspace = rs;
psh.AddCommand(COMMAND);
Collection results = psh.Invoke();
rs.Close();
}
}
catch (Exception ex)
{
System.Console.WriteLine("exception: {0}", ex.ToString());
}
return 0;
}
When I run the c# program on Win2008 R2 which is hosting exchange 2010 server, I always get exception:
System.Management.Automation.RemoteException: The term 'Format-List' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
at System.Management.Automation.PowerShell.CoreInvoke[TOutput](IEnumerable input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.Invoke(IEnumerable input, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.Invoke()
at RemotePS.Program.Main(String[] args)
The program is working fine without "Format-List" and "Out-File" pipelines. The entire command is also working fine in exchange 2010 management shell. I also confirmed it's powershell 2.0 on the system.
Could any one help to figure out what's going on? Any help is much appreciated.
Tom
I've got the same problem with the first embeded PowerShell I wrote. I look for a trace, but I can't find it anymore.
Here is something working for me that I adapt to your code :
static void Main(string[] args)
{
const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
const string COMMAND = #"get-process | format-List | Out-File -file c:\temp\jpb.txt";
System.Uri serverUri = new Uri("http://WM2008R2ENT/powershell?serializationLevel=Full");
PSCredential creds = (PSCredential)null; // Use Windows Authentication
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false,
"WM2008R2ENT",
5985,
"/wsman",
SHELL_URI,
creds);
try
{
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
Pipeline pipeline = rs.CreatePipeline();
string cmdLine;
cmdLine = string.Format("&{{{0}}}", COMMAND);
pipeline.Commands.AddScript(cmdLine);
Collection<PSObject> results = pipeline.Invoke();
rs.Close();
}
}
catch (Exception ex)
{
System.Console.WriteLine("exception: {0}", ex.ToString());
}
return;
}
Be carefull, I'am not using Exchange PowerShell
In the example I use pipeline, perhaps your problem comes from the way you pass the command.
You can try to work with the 'Command'-Object.
Runspace rs = RunspaceFactory.CreateRunspace();
PowerShell ps = PowerShell.Create();
Pipeline pipeline = rs.CreatePipeline();
Command cmd1 = new Command("Get-MailboxDatabase");
cmd1.Parameters.Add("Server", "EX2010SVR1");
cmd1.Parameters.Add("Status");
pipeline.Commands.Add(cmd1);
Command cmd2 = new Command("Format-List");
cmd2.Parameters.Add("Property", "Identity, Guid, mounted, CircularLoggingEnabled, Recovery");
pipeline.Commands.Add(cmd2);
Command cmd3 = new Command("Format-List");
cmd3.Parameters.Add("FilePath", "C:\db.txt");
cmd3.Parameters.Add("Encoding", "UTF8");
cmd3.Parameters.Add("Width", "8192");
pipeline.Commands.Add(cmd3);
Collection<PSObject> output = pipeline.Invoke();
See also here: Invoking powershell cmdlets from C#
I realize this is an old thread, but I wanted to present my findings, however short they are.
I ran into this same problem just recently with a colleague of mine. We managed to track the problem down to the missing runspaces. We also had to connect to the Microsoft.Exchange runspace and when we do it, the Format-List commandlet becomes unavailable. If we don't use the runspace, the commandlet works just fine.
We didn't get to solving it yet, but I intend to explore the possibility of using the RunspacePool instead of just Runspace, thus allowing the execution of both commandlets in the pipeline.

Categories

Resources