Keeping a powershell runspace alive in C# WPF? - c#

I'm planning on building an Active Directory/Exchange admin console using C# talking powershell to DC and Exchange servers.
I want to on application launch establish powershell connections to these servers and then keep them alive so I can keep running queries or scripts or whatever because it takes a couple of seconds to establish the remote connection and it just won't work to have that kind of delay on everything you do.
I'm currently just testing a local powershell runspace but every time I send a command to it it closes and I can't reuse it after the initial command.
How can I prevent the runspace from closing so I can use it over and over again?
edit: code
Very basic, just creating a runspace, planning on being able to include modules later on when I've got the basic functionality down. The idea was to create a runspace and when calling the function that executes powershell code assign that runspace to another variable so I could reuse it but I'm probably stupid. Currently I just have a dummy "Get-Process" that's sent when clicking a button and a textbox that displays the output.
public partial class MainWindow : Window
{
Runspace powerShellRunspace = RunspaceFactory.CreateRunspace();
public MainWindow()
{
InitializeComponent();
powerShellRunspace.Open();
string[] modules;
scriptOutput.Text = "test";
modules = new string[5];
modules[0] = "john";
//string result = powerShellRun("Get-Process");
//powerShellInitialize(modules);
}
public static void powerShellInitialize(string[] modules)
{
Runspace powerShellRunspace = RunspaceFactory.CreateRunspace();
powerShellRunspace.Open();
}
public string powerShellRun(string commands, Runspace powerShellRunspace)
{
Runspace powerShellRunspace2 = powerShellRunspace;
Pipeline powerShellPipeline = powerShellRunspace2.CreatePipeline();
powerShellPipeline.Commands.Add(commands);
Collection<PSObject> powerShellResult = powerShellPipeline.Invoke();
//string result="temp";
//return result;
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in powerShellResult)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
}

This question was already answered on Keeping Powershell runspace open in .Net
In summary, you can keep your Runspace Open, but for each independent query, you need to create a new Powershell instance.
Example:
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
//First Query
var firstQuery = PowerShell.Create();
firstQuery.Runspace = runspace;
firstQuery.AddScript("Write-Host 'hello'")
//Second Query
var secondQuery = PowerShell.Create();
secondQuery.Runspace = runspace;
secondQuery.AddScript("Write-Host 'world'")

Related

PowerShell SDK + Remote

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!

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.

How to run PowerShell from C# as administrator?

I want to execute some PowerShell script through C# but it requires admin privilege. This is my code (I got it here):
using (new Impersonator("user", "domain", "password"))
{
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
// add parameters if any
foreach (var parameter in parameters)
{
pipeline.Commands[0].Parameters.Add(parameter.Key, parameter.Value);
}
// add an extra command to transform the script
// output objects into nicely formatted strings
// remove this line to get the actual objects
// that the script returns. For example, the script
// "Get-Process" returns a collection
// of System.Diagnostics.Process instances.
pipeline.Commands.Add("Out-String");
// execute the script
Collection<PSObject> results = pipeline.Invoke();
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
Anyway, this doesn't work on my machine. For example, if the script text is "Set-ExecutionPolicy Unrestricted" then I get "Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied."
And in my case, it cannot get list of virtual machines through Get-VM command. (I found that Get-VM only return results if it runs under Admin privilege.)
Do I do something wrong? Is there another solution for this problem?
This will launch PowerShell as an Administrator:
var newProcessInfo = new System.Diagnostics.ProcessStartInfo();
newProcessInfo.FileName = #"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe";
newProcessInfo.Verb = "runas";
System.Diagnostics.Process.Start(newProcessInfo);
If you need to pass in a script to run, then use:
newProcessInfo.Arguments = #"C:\path\to\script.ps1";

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.

add-windowsfeature is not recognized

I'm trying to run powershell commands using C# but I keep getting errors when I invoke the pipeline. I was wondering if anyone know why I keep getting add-windowsfeature is not recognized. Thanks in advance.
private static void RunScript(string name)
{
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new[] { "ServerManager"});
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
// create Powershell runspace
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
Command cm = new Command("Import-module");
cm.Parameters.Add("name","ServerManager");
pipeline.Commands.Add(cm);
Command command = new Command("add-windowsfeature");
command.Parameters.Add(null, name);
pipeline.Commands.Add(command);
var a = pipeline.Invoke();
foreach (var psObject in a)
{
Console.WriteLine(psObject);
}
runspace.Close();
}
ServerManager is a 64-bit only module (it doesn't exist under C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules but will exist under C:\Windows\System32\WindowsPowerShell\v1.0\Modules). Compile as x64 and your code should work.

Categories

Resources