Runspace PowerShell output - c#

I have powershell script file, which does something and outputs the result. If I'm manually running it on my remote host using cmd, I'll get the clear output.
But when I'm trying to execute script via PowerShell Runspace using c# I'm not able to see script's output. Only command itself.
using (PowerShell powershell = PowerShell.Create())
{
try
{
PowerShell ps = PowerShell.Create();
ps.AddScript("c:\temp\myps.ps1");
ICollection<PSObject> results = ps.Invoke();
using (StringWriter sw = new StringWriter())
{
foreach (PSObject invoke in results)
sw.WriteLine(invoke.ToString());
outp = sw.ToString();
}
//Script errors
if (ps.Streams.Error.Count > 0)
{
outp += Environment.NewLine + string.Format("{0} errors: ", ps.Streams.Error.Count);
foreach (ErrorRecord err in ps.Streams.Error)
outp += Environment.NewLine + err.ToString();
}
}
catch (Exception e)
{
// Terminating errors
outp = "Critical error: " + e.Message;
}
}
remoteRunspace.Close();
//In the end outp for me = "c:\temp\myps.ps1"
How can I access the output from C#?

See this documentation about the PowerShell class: https://msdn.microsoft.com/en-us/library/system.management.automation.powershell(v=vs.85).aspx
Looks like you need to access the members of the PSObject to get the output. Otherwise, it will just send laim ToString() data out to the StringBuilder object. Do you have any example of what you're getting back you can show? I may be able to provide a better answer with a sample. Also, where's the remote runspace located?
Thanks!

Next code working for me:
try
{
PowerShell ps = PowerShell.Create().AddScript("c:\temp\myps.ps1", true);
ps.Runspace = remoteRunspace;
ICollection<PSObject> results = ps.Invoke();
using (StringWriter sw = new StringWriter())
{
foreach (PSObject invoke in results)
sw.WriteLine(invoke.ToString());
outp = sw.ToString();
}
//Script errors
if (ps.Streams.Error.Count > 0)
{
outp += Environment.NewLine + string.Format("{0} errors: ", ps.Streams.Error.Count);
foreach (ErrorRecord err in ps.Streams.Error)
outp += Environment.NewLine + err.ToString();
}
}
catch (Exception e)
{
// Terminating errors
outp = "Critical error: " + e.Message;
}
}
remoteRunspace.Close();

Related

Asynchronously running multiple PowerShell scripts from C#

Short description of what I`m trying to do:
I am working on a .Net WinForm application from where I am trying to run multiple PowerShell scripts on a remote server and display results on the form.
At this moment I`m executing the scripts synchronously and this is causing me problems with long running scripts.
Any idea on how I could make this function to be executed Asynchronously?
public string NewPsSession(string ServerName, string command)
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell psSession = PowerShell.Create();
psSession.Commands.AddScript("$sessions = New-PSSession -ComputerName " + ServerName + Environment.NewLine
+ "Invoke-Command -session $sessions -ScriptBlock {" + command + "}" + Environment.NewLine
+ "Remove-PSSession -Session $sessions" + Environment.NewLine);
psSession.Commands.AddCommand("Out-String");
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = psSession.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
Any suggestion would be much appreciated. Thanks!
Change your NewPsSesson method to async and return a Task<string>. Then move your code into a Task<string>.Run() block and await it. Then you can either await your NewPsSession() method or monitor it as a task as I have done in Main()
class Program
{
public static void Main(string[] args)
{
Task<string> task = NewPsSession("", "");
while (!task.IsCompleted)
{
Task.Delay(500).Wait();
Console.WriteLine("Waiting...");
}
Console.WriteLine(task.Result);
Console.WriteLine("Done");
}
public static async Task<string> NewPsSession(string ServerName, string command)
{
var result = await Task<string>.Run(() =>
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell psSession = PowerShell.Create();
psSession.Commands.AddScript("$sessions = New-PSSession -ComputerName " + ServerName + Environment.NewLine
+ "Invoke-Command -session $sessions -ScriptBlock {" + command + "}" + Environment.NewLine
+ "Remove-PSSession -Session $sessions" + Environment.NewLine);
psSession.Commands.AddCommand("Out-String");
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = psSession.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
});
return result;
}
}

Calling powershell from .Net Console application

I have written a console application wherein I have called a powershell script from the console. In the powershell script I have written hello world as a return variable and it is running as expected but next time when I change the string from hello world to How are you it is not displaying the changed string. I cannot figure out myself what needs to be done to clear the pipeline or cache.
I have used the below namespace apart from default namespaces
using System.Management;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;
static void Main(string[] args)
{
string _str = string.Empty;
_str= RunScript(#"C:\Powershell_Scripts\Test.ps1");
Console.WriteLine("Input String is =" + str);
Console.Read();
}
private static string RunScript(string scriptText)
{
// 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("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
pipeline.Commands.AddScript(scriptText);
// 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();
pipeline.Streams.ClearStreams();
// 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();
}
Powershell Script i.e. Test1.ps1
sleep 3
$a=""
$a = "Hello word"
return $a
Here is a sample how to use the PowerShell in a Runspace
using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create a runspace.
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace())
{
myRunSpace.Open();
using (PowerShell powershell = PowerShell.Create())
{
// Create a pipeline with the Get-Command command.
powershell.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
powershell.AddScript(#"C:\Users\you\Desktop\a.ps1");
// add an extra command to transform the script output objects into nicely formatted strings
// remove this line to get the actual objects
powershell.AddCommand("Out-String");
// execute the script
var results = powershell.Invoke();
powershell.Streams.ClearStreams();
powershell.Commands.Clear();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
Console.WriteLine(stringBuilder.ToString());
}
}
}
}
}
Further reference: Creating a constrained runspace
Here is a code which runs as expected
enter code here
try
{
if (System.IO.File.Exists(#"c:\disk\op.txt") && (new FileInfo(#"c:\disk\op.txt").Length != 0))
{
System.IO.File.Move(#"c:\disk\op.txt", #"c:\disk\Previous_op_" + DateTime.Now.ToString("dd_MM_yyyy hh mm") + ".txt");
log.Info("Previous Output File Successfully Renamed");
}
// TODO: Add delete logic here
log.Info("Input ActionResult - Server " + Server);
log.Info("Input ActionResult - Volume " + Volume);
log.Info("Input ActionResult - Size " + size);
string userID = "dir\\" + Session["Uname"].ToString();
string userpassword = Session["Upwd"].ToString();
log.Info("username " + userID);
StringBuilder stringBuilder = new StringBuilder();
var con = new WSManConnectionInfo();
log.Info("Pushing username in PSCredential- " + userID.ToString().Trim());
con.Credential = new PSCredential(userID.ToString().Trim(), userpassword.ToString().Trim().ToSecureString());
Runspace runspace = RunspaceFactory.CreateRunspace(con);
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
string _str = #"-Server " + Server + " -Volumeletter " + Volume + ": -deltasize " + size + " -Logfile c:\\disk\\op.txt -username " + userID + " -password " + userpassword;
log.Info("Parameter string format- " + _str.Substring(0, _str.IndexOf("-password") + 9));
pipeline.Commands.AddScript(#"C:\disk\diskerr.ps1 " + _str.ToString());
pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add("Out-String");
var results = pipeline.Invoke();
runspace.Close();
runspace.Dispose();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
log.Info("Output from powershell: " + obj.ToString());
}
if (System.IO.File.Exists(#"c:\disk\op.txt") && (new FileInfo(#"c:\disk\op.txt").Length != 0))
{
fileStream = new FileStream(#"c:\test\op.txt", FileMode.Open, FileAccess.Read);
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
{
_consoleOutput = streamReader.ReadToEnd();
}
_output = Regex.Replace(_consoleOutput, #"\r\n?|\n", "<br />");
}
return Content(_output);
}
catch (Exception ex)
{
log.Info("Error stackTrace inside Input ActionResult " + ex.StackTrace.ToString());
log.Info("Error Message inside Input ActionResult " + ex.Message.ToString());
return View();
}
You need to change the directory path and the powershell file name.

C# System.Management.Automation.PowerShell Performance

Why is performance of executing a script from PowerShell directly much quicker than when executing the script from the System.Management.Automation.PowerShell class? When I execute my script directly in PS, it takes less than a second, though when I execute it through the code below, the Invoke takes several minutes.
public bool Execute(string filename, out string result)
{
StringBuilder sb = new StringBuilder();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace.SessionStateProxy.SetVariable("filename", filename);
ps.AddScript(this.script);
Collection<PSObject> psOutput = ps.Invoke();
foreach (PSObject item in psOutput)
{
Console.WriteLine(item);
}
PSDataCollection<ErrorRecord> errors = ps.Streams.Error;
foreach (ErrorRecord err in errors)
{
Console.WriteLine(err);
}
result = sb.ToString();
return errors.Count == 0;
}
}
Script text:
[regex]$r = "\$\%\$##(.+)\$\%\$##";
(Get-Content $filename) |
Foreach-Object {
$line = $_;
$find = $r.matches($line);
if ($find[0].Success) {
foreach ($match in $find) {
$found = $match.value
$replace = $found -replace "[^A-Za-z0-9\s]", "";
$line.Replace($found, $replace);
}
}
else
{
$line;
}
} |
Set-Content $filename
I tested the execution of the script against this method in c#, and this method takes less than 2 seconds to execute the script.
public bool Execute(string filename, out string result)
{
StringBuilder standardOutput = new StringBuilder();
string currentPath = Path.GetDirectoryName(filename);
string psFile = Path.Combine(currentPath, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".ps1");
this.script = this.script.Replace("$filename", "\"" + filename + "\"");
File.WriteAllText(psFile, this.script);
using (Process process = new Process())
{
process.StartInfo = new ProcessStartInfo("powershell.exe", String.Format("-executionpolicy unrestricted \"{0}\"", psFile))
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
while (!process.HasExited)
{
standardOutput.Append(process.StandardOutput.ReadToEnd());
}
process.WaitForExit();
standardOutput.Append(process.StandardOutput.ReadToEnd());
result = standardOutput.ToString();
return process.ExitCode == 0;
}
}

Unable to read Registry value using PowerShell in C#

I am not able to read specific registry values using PowerShell in C#. Here is the code:
Calling function:
public static string UserDisplayName()
{
// PowerShell Command: (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData\1').LoggedOnDisplayName
return GetPowerShellOutputString(#"(Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData\1').LoggedOnDisplayName");
}
Function definition:
private static string GetPowerShellOutputString(string PsCmd)
{
try
{
string PsOut = string.Empty;
Debug.Write("PsCmd: " + PsCmd + "; ");
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
Pipeline pipeline = rs.CreatePipeline();
pipeline.Commands.AddScript(PsCmd);
Collection<PSObject> results = pipeline.Invoke();
rs.Close();
foreach (PSObject obj in results)
if (obj != null) PsOut += obj.ToString() + ", ";
PsOut = (PsOut == string.Empty) ? strUnavailableString : PsOut.TrimEnd(',', ' ');
Debug.WriteLine("PsOut: " + PsOut);
return PsOut;
}
catch (Exception ex)
{
Debug.WriteLine("! " + ex.Message + ex.InnerException + "\n");
return strUnavailableString;
}
}
However, the same Function definition is working perfectly if I am trying to read any other registry value e.g.:
public static string UserOUPath()
{
try
{
if (UserDomain() == SystemInformation.ComputerName) return strUnavailableString; // Non-domain account
//For consistant performance, grab OU from registry instead of AD.
string userSID = WindowsIdentity.GetCurrent().User.ToString();
string ouPath = #"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\" + userSID;
string ou = GetPowerShellOutputString("(Get-ItemProperty -Path '" + ouPath + "').'Distinguished-Name'");
ou = ou.Remove(0, ou.IndexOf(",", 0) + 1); // Drop leading CN stuff
ou = ou.Remove(ou.IndexOf(",DC=", 0), ou.Length - ou.IndexOf(",DC=", 0)); // Drop trailing DC stuff
ou = ou.Replace(",OU=", "/");
ou = ou.Replace("OU=", "/");
ou = FlipOU(ou);
if (ou == null) throw new NullReferenceException();
return ou;
}
catch
{
return strUnavailableString;
}
}
For the first call (UserDisplayName()) when I did Debug mode, the results object is returning null. However if I run the same PowerShell Command in PowerShell window it is giving the value.
I am stumbled upon this as I am not able to get why and what is happening?
A couple of things. First I agree with all the comments about just using the Microsoft.Win32.Registry* classes directly in C#. However to answer your question about doing this from PowerShell, I believe what you are running into a registry virtualization issue. If your C# project is AnyCPU and run under Visual Studio it is likely running 32-bit and that registry path will be virtualized to the SysWow64 registry node. So your reg path won't exist. That can be fixed by making the exe compile as x64. Another option is to use the .NET Registry.OpenBaseKey() method will allows you to specify which reg hive you want to view (32-bit or 64-bit).
Second, you can simplify your code by using the PowerShell class e.g.:
var ps = PowerShell.Create();
ps.AddScript(PsCmd);
var results = ps.Invoke();
foreach (PSObject obj in results)
if (obj != null) PsOut += obj.ToString() + ", ";
Third, with the PowerShell class, after the Invoke() call, check the Error stream for errors like so:
foreach (var error in ps.Streams.Error)
Console.Error.WriteLine(error);

How to call powershell script with configfile,parameters in c#

I am new powershell script in c#. I have a powershell script file ps.ps1 and powershell settingfile ConsoleSettings.psc1
C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -psconsolefile "D:\e\ConsoleSettings.psc1" -noexit -command ". 'D:\e\ps.ps1'"
run it and get "
Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false
-PasswordNotChangedFor 60 -enabled
my function result correctly.
Now, i want to get this result in c# . My code is;
private void button1_Click(object sender, EventArgs e)
{
RunScript(LoadScript(#"d:\e\ps.ps1"));
}
private string RunScript(string scriptText)
{
PSConsoleLoadException x = null; ;
RunspaceConfiguration rsconfig = RunspaceConfiguration.Create(#"d:\e\ConsoleSettings.psc1", out x);
Runspace runspace = RunspaceFactory.CreateRunspace(rsconfig);
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
pipeline.Commands.Add("Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
private string LoadScript(string filename)
{
try
{
using (StreamReader sr = new StreamReader(filename))
{
StringBuilder fileContents = new StringBuilder();
string curLine;
while ((curLine = sr.ReadLine()) != null)
{
fileContents.Append(curLine + "\n");
}
return fileContents.ToString();
}
}
catch (Exception e)
{
string errorText = "The file could not be read:";
errorText += e.Message + "\n";
return errorText;
}
}
And then i have a error : the term "Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled" 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.
How to solve this problem, or how to call powershell script with configfile, parameter like (Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled) in c#
please help me...
You are adding your command line as a Command rather than a script. Commands are intended for things like cmdlets or functions without parameters. You will use the additional methods to add the parameters. A simple solution would be to just use AddScript again.
pipeline.AddScript("Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled");

Categories