Capturing PowerShell output in C# after execution of each pipeline command - c#

I am implementing a WPF application that executes a PowerShell script for each key/value pair given in a dictionary, using the pair as script arguments. I store each run of the script as a new command in the pipeline. However, this causes me to only get output back from the last command that was run, when I need the output after each run of the script. I have considered creating a new pipeline each time the script is executed, but I need to know when all executions of the script are done. Here's the relevant code to help explain my problem:
private void executePowerShellScript(String scriptText, Dictionary<String, String> args)
{
// Create the PowerShell object.
PowerShell powerShell = PowerShell.Create();
// If arguments were given, add the script and its arguments.
if (args != null)
{
foreach (KeyValuePair<String, String> arg in args)
{
powerShell.AddScript(scriptText);
powerShell.AddArgument(arg.Key);
powerShell.AddArgument(arg.Value);
}
}
// Otherwise, just add the script.
else
powerShell.AddScript(scriptText);
// Add the event handlers.
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
output.DataAdded += new EventHandler<DataAddedEventArgs>(Output_DataAdded);
powerShell.InvocationStateChanged +=
new EventHandler<PSInvocationStateChangedEventArgs>(Powershell_InvocationStateChanged);
// Invoke the pipeline asynchronously.
IAsyncResult asyncResult = powerShell.BeginInvoke<PSObject, PSObject>(null, output);
}
private void Output_DataAdded(object sender, DataAddedEventArgs e)
{
PSDataCollection<PSObject> myp = (PSDataCollection<PSObject>)sender;
Collection<PSObject> results = myp.ReadAll();
foreach (PSObject result in results)
{
Console.WriteLine(result.ToString());
}
}
And then I use the following method to know when all executions of the script have been completed. Since I do this by checking that the invocation state of the pipeline is completed, I can't make a new pipeline for each execution of the script:
private void Powershell_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e)
{
switch (e.InvocationStateInfo.State)
{
case PSInvocationState.Completed:
ActiveCommand.OnCommandSucceeded(new EventArgs());
break;
case PSInvocationState.Failed:
OnErrorOccurred(new ErrorEventArgs((sender as PowerShell).Streams.Error.ReadAll()));
break;
}
Console.WriteLine("PowerShell object state changed: state: {0}\n", e.InvocationStateInfo.State);
}
So, to get to my question:1) Can I force the pipeline to produce output after each command that it executes? Or,2) If I were to create a new pipeline each time I run the command, is there another way that I could check that all executions of the script have been completed?
There are few examples using the actual PowerShell class in C# and I know next to nothing about threading, so any help would be greatly appreciated.

I feel silly for answering my own question, but all I did was move the loop functionality from my C# code into my script and that worked. So now I pass all the keys and values at once as array parameters and only have the one command in the pipeline.
I would still be interested to know if it is possible to produce output after each command in the pipeline is executed, though.

I have a situation where I have an object that manages a PowerShell environment, and it takes a script or a module and cmdlet and executes it and then takes a new script or module and cmdlet or cmdlet from the original module and executes it. Each time something is executed, I need to return the results. I solved it by clearing the command queue after each execution:
powerShellInstance.Commands.Clear();
hope this helps.

Related

The term 'Get-WBSummary' is not recognized as a name of a cmdlet [duplicate]

I have below command and it returns me null object . When I run the command separately in PowerShell window I get the right result. Below is my PowerShell method which is calling the command and the also the PowerShell command which I have defined. I am basically looking to return a string value. Please let me know what wrong am I doing?
C# method:
public string RunScript( string contentScript, Dictionary<string, EntityProperty> parameters)
{
List<string> parameterList = new List<string>();
foreach( var item in parameters )
{
parameterList.Add( item.Value.ToString() );
}
using( PowerShell ps = PowerShell.Create() )
{
ps.AddScript( contentScript );
// in ContentScript I get "Get-RowAndPartitionKey" on debugging
ps.AddParameters( parameterList );//I get list of strings
IAsyncResult async = ps.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
// here i get result empty in ps.EndInvoke(async)
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
My Powershell GetRowAndPartitionKey cmdlet definition, which the code above is trying to call:
public abstract class GetRowAndPartitionKey : PSCmdlet
{
[Parameter]
public List<string> Properties { get; set; } = new List<string>();
}
[Cmdlet( VerbsCommon.Get, "RowAndPartitionKey" )]
public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKey
{
protected override void ProcessRecord()
{
string rowKey = string.Join( "_", Properties );
string pKey = string.Empty;
WriteObject( new
{
RowKey = rowKey,
PartitionKey = pKey
} );
}
}
}
When using the PowerShell SDK, if you want to pass parameters to a single command with .AddParameter() / .AddParameters() / AddArgument(), use .AddCommand(), not .AddScript()
.AddScript() is for passing arbitrary pieces of PowerShell code that is executed as a script block to which the parameters added with .AddParameters() are passed.
That is, your invocation is equivalent to & { Get-RowAndPartitionKey } <your-parameters>, and as you can see, your Get-RowAndPartitionKey command therefore doesn't receive the parameter values.
See this answer or more information.
Note: As a prerequisite for calling your custom Get-RowAndPartitionKey cmdlet, you may have to explicitly import the module (DLL) that contains it, which you can do:
either: with a separate, synchronous Import-Module call executed beforehand (for simplicity, I'm using .AddArgument() here, with passes an argument positionally, which binds to the -Name parameter (which also accepts paths)):
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>").Invoke();
or: as part of a single (in this case asynchronous) invocation - note the required .AddStatement() call to separate the two commands:
IAsyncResult async =
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>")
.AddStatement()
.AddCommand("GetRowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
"<your-module-path-here>" refers to the full file-system path of the module that contains the Get-RowAndPartitionKey cmdlet; depending on how that module is implemented, it is either a path to the module's directory, its .psd1 module manifest, or to its .dll, if it is a stand-alone assembly.
Alternative import method, using the PowerShell SDK's dedicated .ImportPSModule() method:
This method obviates the need for an in-session Import-Module call, but requires extra setup:
Create a default session state.
Call .ImportPSModule() on it to import the module.
Pass this session state to PowerShell.Create()
var iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { #"<your-module-path-here>" });
var ps = PowerShell.Create(iss);
// Now the PowerShell commands submitted to the `ps` instance
// will see the module's exported commands.
Caveat: A PowerShell instance reflects its initial session state in .Runspace.InitialSessionState, but as a conceptually read-only property; the tricky part is that it is technically still modifiable, so that mistaken attempts to modify it are quietly ignored rather than resulting in exceptions.
To troubleshoot these calls:
Check ps.HadErrors after .Invoke() / .EndInvoke() to see if the PowerShell commands reported any (non-terminating) errors.
Enumerate ps.Streams.Errors to inspect the specific errors that occurred.
See this answer to a follow-up question for self-contained sample code that demonstrates these techniques.

Unable to call a Powershell function from C#

C# Code:
The C# code is unable to call the function Trial2 which is present in the powershell script although it is being executed before.
StringBuilder sb = new StringBuilder();
PowerShell psExec = PowerShell.Create();
psExec.AddScript(# "C:\Users...\sc.ps1");
psExec.AddCommand("Trial2").AddParameter("a", "Ram");
Collection < PSObject > results;
Collection < ErrorRecord > errors;
results = psExec.Invoke();
errors = psExec.Streams.Error.ReadAll();
if (errors.Count > 0) {
foreach(ErrorRecord error in errors) {
sb.AppendLine(error.ToString());
}
} else {
foreach(PSObject result in results) {
sb.AppendLine(result.ToString());
}
}
Console.WriteLine(sb.ToString());
Powershell Script:
function Trial2($a){
"Yes! $a";
}
Error I get:
I have Set-ExecutionPolicy to Unrestricted in the Powershell as well.
Thanks in advance!
Mathias R. Jessen has provided all the necessary pointers in his comments, but let me put it all together:
PowerShell psExec = PowerShell.Create();
// Add a script block that dot-sources (.) your .ps1 file,
// which in turn defines the Trial2 function.
psExec.AddScript(#". C:\Users...\sc.ps1");
// Start a new statement to ensure that the dot-sourcing is performed
// before additional commands are executed.
psExec.AddStatement();
// Now you can add a command that calls your Trial2 function.
psExec.AddCommand("Trial2").AddParameter("a", "Ram");
// ...
Note that the API is fluent, so you could use a single statement; the following demonstrates this, and also shows how to set the execution policy directly via the API rather than by submitting a Set-ExecutionPolicy command, as you originally attempted:
// Create an initial default session state.
var iss = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault2();
// Set its script-file execution policy.
iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Bypass;
// Create a PowerShell instance with a runspace based on the
// initial session state.
PowerShell psExec = PowerShell.Create(iss);
psExec
.AddScript(#". C:\Users...\sc.ps1")
.AddStatement()
.AddCommand("Trial2").AddParameter("a", "Ram");
// ...
Note that setting the execution policy this way is the equivalent of setting it with
Set-ExecutionPolicy -Scope Process. This means that it'll stay in effect for all PowerShell sessions started from the current process.

Run PSCmdLets in C# code (Citrix XenDesktop)

I'm new to PowerShell and running PowerShell cmd-lets in C#. Specifically, I'm trying to use Citrix's XenDesktop SDK to write a web app to manage our XenDesktop environment.
Just as a quick test, I made a reference to the Citrix BrokerSnapIn.dll, which looks like it gives me good C# classes. However, when I hit the .Invoke with this error message:
"Cmdlets derived from PSCmdlet cannot be invoked directly."
I've searched and tried a bunch of stuff, but don't know how to call PSCmdlets. I'm kinda left thinking that I have to use strings and a runspace/pipeline, etc, to do this.
Thanks In Advanced,
NB
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Citrix.Broker.Admin.SDK;
namespace CitrixPowerShellSpike
{
class Program
{
static void Main(string[] args)
{
var c = new GetBrokerCatalogCommand {AdminAddress = "xendesktop.domain.com"};
var results = c.Invoke();
Console.WriteLine("all done");
Console.ReadLine();
}
}
}
You need to host the PowerShell engine in order to execute a PSCmdlet e.g. (from the MSDN docs):
// Call the PowerShell.Create() method to create an
// empty pipeline.
PowerShell ps = PowerShell.Create();
// Call the PowerShell.AddCommand(string) method to add
// the Get-Process cmdlet to the pipeline. Do
// not include spaces before or after the cmdlet name
// because that will cause the command to fail.
ps.AddCommand("Get-Process");
Console.WriteLine("Process Id");
Console.WriteLine("----------------------------");
// Call the PowerShell.Invoke() method to run the
// commands of the pipeline.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["ProcessName"].Value,
result.Members["Id"].Value);
}
}

Enumerating PowerShell object's properties throws

I have this simple program using powershell. This is just a proof of concept, I'm using the same code in a larger app. The problem is that the values of some of the properties in the code below can't be read. Reading the Value property throws a GetValueInvocationException.
This actually even happens with one of Microsoft sample projects that comes with the PowerShell SDK. Why is this and is there a solution?
static void Main(string[] args)
{
var powerShell = System.Management.Automation.PowerShell.Create();
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Runspace.DefaultRunspace = runspace;
powerShell.Runspace = runspace;
powerShell.AddScript("Get-Process");
var results = powerShell.Invoke();
foreach (var prop in results.First().Properties)
{
try
{
Console.WriteLine(prop.Name + " : " + prop.Value);
}
catch (Exception e)
{
Console.WriteLine(string.Format("Exception {0} on {1}", e.GetType(), prop.Name));
}
}
Console.ReadKey();
}
This is either by design (after all, it a target object property getter throws) or an issue (if this effect is not intentional in PowerShell). In both cases we cannot do much about this now. That is, we should use the try/catch approach in such vcases. One option we have is to submit the report to: https://connect.microsoft.com/PowerShell/Feedback
Try to get the target object in your C# code (it is System.Diagnostics.Process, get it via BaseObject property of a result object) and access that culprit property. It will throw, more likely, as well.

Powershell module loading stops in C# code when Write-Progress is used

I have this service that, when request is received, runs a powershell command and returns result. Here is the invoker class code:
public class PowerShellScript {
public PowerShellScript() {
}
public Object[] Invoke( String strScriptName, NameValueCollection nvcParams ) {
Boolean bResult = true;
int n = 0;
Object[] objResult = null;
PowerShell ps = PowerShell.Create();
String strScript = strScriptName;
for (n = 0; n < nvcParams.Count; n++) {
strScript += String.Format( " -{0} {1}", nvcParams.GetKey( n ), nvcParams[n] );
}
//ps.AddScript( #"E:\snapins\Init-profile.ps1" );
ps.AddScript( strScript );
Collection<PSObject> colpsOutput = ps.Invoke();
if (colpsOutput.Count > 0)
objResult = new Object[colpsOutput.Count];
n = 0;
foreach (PSObject psOutput in colpsOutput) {
if (psOutput != null) {
try {
objResult[n] = psOutput.BaseObject;
}
catch (Exception ex) {
//exception should be handeled properly in powershell script
}
}
n++;
}
colpsOutput.Clear();
ps.Dispose();
return objResult;
}
}
Method Invoke returns all results returned by powershell script. The problem though is that if the called script contains Write-Progress either in imported module or script itself, the Powershell class somehow believes that this is real output and completes script execution instantly, thus returning null as an object.
Ideally, one could block output with Out-Null cmdlet, but it doesn't work for Write-Progress. Any ideas as ot how to block Write-Progress?
I tried your code and it worked in a trivial Console application and even in a Windows application with a simple script with some output and calls of Write-Progress. Thus, the problem is not that easy reproducible...
Powershell class somehow believes that
this is real output and completes
script execution instantly
Hmm, perhaps it just fails, not “treats output differently” and that is why the output is empty. Can you check the error collection after execution?
Any ideas as ot how to block
Write-Progress?
If you are just about blocking it then this should work: before invoking your worker script invoke a “profile” script with this command installing a dummy replacement:
function global:Write-Progress {}
Thus, when your script calls Write-Progress then the dummy function Write-Progress is actually called and this in fact “blocks Write-Progress” effectively.

Categories