Enumerating PowerShell object's properties throws - c#

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.

Related

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.

How to cast PSCustomObject from Pipeline.Invoke in C#

I'm writing a command that connects to a remote machine over a PSsession, gets a .NET object from the remote machine and then uses it locally.
However the object I am getting back isn't the .NET object that I had created on the remote end but a PSCustomObject which refuses to be cast back to its original object.
Does anyone know how to perform this?
This method is just one of many, so a generic answer on how to handle any .NET object rather than the specific object in this method would be appreciated.
The error I get is;
Unable to cast object of type 'System.Management.Automation.PSCustomObject' to type 'System.IO.FileInfo'.
Thanks
Mark
readonly PSSession session;
public FileInfo GetFileInfo(string p)
{
Pipeline pipe = session.Runspace.CreatePipeline();
string format = "(New-object System.IO.FileInfo \"{0}\") -as [System.IO.FileInfo]"; // Have tried many ways to force it to a FileInfo
string command = string.Format(format,p);
pipe.Commands.AddScript(command);
Collection<PSObject> res = pipe.Invoke();
foreach (PSObject ps in res)
{
return (FileInfo)ps.BaseObject;
}
// no items
return null;
}

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);
}
}

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

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.

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