How can I access PowerShell standard error output in C#? - c#

I know that Invoke-MyCmd throws an error and writes a message to standard error.
Let's say it's one line that invokes a Java program like this:
function Invoke-MyCmd
{
java Error
}
Error.java just prints an error message and exits:
import java.io.*;
public class Error {
public static void main(String[] args) {
System.err.println("Hello, error!");
}
}
I'm invoking Invoke-MyCmd from C# like so:
PowerShell ps = PowerShell.Create();
.
.
.
ps.AddCommand("Invoke-MyCmd");
Collection<PSObject> output = ps.Invoke();
The error text, I'm assuming because it comes from Java and not directly from Invoke-MyCmd, doesn't show up in ps.Streams.Error. I'm wondering if there's another way to read that error output from C#.

I'm blatantly copy/pasting this answer using this blog as a reference
Since this is a native executable being run by powershell, you need to translate the error code into an exception like so:
'Starting script...' | Write-Verbose
$ErrorActionPreference = 'Stop'
java Thing
if ($LastExitCode -ne 0) {
throw 'An error has occurred...'
}
'Finished script' | Write-Verbose
Your java application should also return non-zero when an error occurs.
import java.io.*;
public class Error {
public static void main(String[] args) {
System.err.println("Hello, error!");
return 1;
}
}

Related

Powershell command not recognized when calling from C#

This is in continuation to this Question here, I have a PowerShell command which I have created and am able to call the command in a PowerShell window, but when trying to call from C# method, I am getting error as the cmdlet is not recognized, I tried with other existing commands and get same error, so I suspect issue in Importing the Module, though I don't get that error in streams. Error. The only error I get is "Get-RowAndPartitionKey is not a recognized cmndlt, please check the spelling.....".
Would like to know if there is any other way, I should try it or if I can debug more here to see if my Module fetches all command or not. right now I am clueless how to fix this.
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() )
{
IAsyncResult async =
ps.AddCommand( "Import-Module" ).AddArgument( #"C:\Users\...\.D.PowerShell.dll" )
.AddStatement()
.AddCommand( "Get-RowAndPartitionKey" ).AddParameter( "Properties", "test" )
.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
Below method do not return any error in Streams.Error or Verbose but no output also:
public async Task<IEnumerable<object>> RunScript( string scriptContents, List<string> scriptParameters )
{
// create a new hosted PowerShell instance using the default runspace.
// wrap in a using statement to ensure resources are cleaned up.
using( PowerShell ps = PowerShell.Create() )
{
// specify the script code to run.
ps.AddScript( scriptContents );
// specify the parameters to pass into the script.
ps.AddParameter( "Properties" ,"test") ;
// execute the script and await the result.
var pipelineObjects = await ps.InvokeAsync().ConfigureAwait( false );
return pipelineObjects;
}
}
scriptContent
"\"$path = 'C:\\Users...\\.TabularData.PowerShell.dll'\\r\\nImport-Module $path\\r\\nGet-RowAndPartitionKeys\""
The following is self-contained PowerShell sample code that uses on-demand compilation of C# code:
It shows that the approach works in principle, as described in this answer to your original question.
Prerequisites: The PowerShell SDK package and .NET runtime used in the C# project that calls your custom Get-RowAndPartitionKey" cmdlet must be compatible with the PowerShell SDK and .NET runtime that you used to compile the assembly DLL that houses that cmdlet, to be imported via Import-Module.
The sample code below ensures that implicitly, by running directly from PowerShell, using the Add-Type cmdlet to compile C# code on demand - it works in Windows PowerShell as well as in PowerShell (Core) 7+.
In practice I've found that a .NET Framework-compiled DLL (from Windows PowerShell) also works in PowerShell (Core) (.NET (Core) 5.0), but not vice versa.
It shows troubleshooting techniques, namely:
Adding the -Verbose switch to the Import-Module call to produce verbose output that lists the commands being imported from the given module (DLL).
Printing these verbose messages (look for // --- TROUBLESHOOTING CODE)
Printing any non-terminating PowerShell errors that occurred (as opposed to exceptions that you'd have to handle in C# code).
# Create a (temporary) assembly containing cmdlet "Get-RowAndPartitionKey".
# This assembly can directly be imported as a module from PowerShell.
# The cmdlet simply outputs "Hi from Get-RowAndPartitionKey" and
# echoes the elements of the list passed to -Properties, one by one.
$tempModuleDll = Join-Path ([IO.Path]::GetTempPath()) "TempModule_$PID.dll"
Remove-Item -ErrorAction Ignore $tempModuleDll
Add-Type #'
using System.Management.Automation;
using System.Collections.Generic;
[Cmdlet("Get", "RowAndPartitionKey")]
public class GetRowAndPartitionKeyCmdlet : PSCmdlet {
[Parameter] public List<string> Properties { get; set; }
protected override void ProcessRecord() {
WriteObject("Hi from Get-RowAndPartitionKey: ");
WriteObject(Properties, true);
}
}
'# -ErrorAction Stop -OutputAssembly $tempModuleDll
# Compile a C# class ad hoc to simulate your project, and call its static
# method, which imports the module and effectively calls
# Get-RowAndPartitionKey -Properties "foo", "bar"
(Add-Type #"
using System;
using System.Management.Automation;
using System.Collections.Generic;
using System.Text;
public static class Foo {
public static string RunScript(List<string> parameterList)
{
using (System.Management.Automation.PowerShell ps = PowerShell.Create())
{
IAsyncResult async =
// Add -Verbose to the Import-Module call, so that the list of
// commands being imported is written to the verbose output stream.
ps.AddCommand("Import-Module").AddArgument(#"$tempModuleDll").AddParameter("Verbose", true)
.AddStatement()
.AddCommand("Get-RowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject result in ps.EndInvoke(async))
{
stringBuilder.AppendLine(result.ToString());
}
// --- TROUBLESHOOTING CODE
// Print verbose output from the Import-Module call
foreach (var v in ps.Streams.Verbose) { Console.WriteLine("VERBOSE: " + v.ToString()); }
// Print any errors.
foreach (var e in ps.Streams.Error) { Console.WriteLine("ERROR: " + e.ToString()); }
// ---
return stringBuilder.ToString();
}
}
}
"# -ErrorAction Stop -PassThru)::RunScript(("foo", "bar"))
# Clean-up instructions:
if ($env:OS -eq 'Windows_NT') {
Write-Verbose -vb "NOTE: Re-running this code requires you to start a NEW SESSION."
Write-Verbose -vb "After exiting this session, you can delete the temporary module DLL(s) with:`n`n Remove-Item $($tempModuleDll -replace '_.+', '_*.dll')`n "
} else {
Write-Verbose -vb "NOTE: Re-running this code after modifying the embedded C# code requires you to start a NEW SESSION."
Remove-Item $tempModuleDll
}
On my Windows 10 machine, both from PowerShell (Core) 7.0.5 and Windows PowerShell 5.1, the above yields (clean-up instructions omitted) the following, showing that everything worked as intended:
VERBOSE: Loading module from path 'C:\Users\jdoe\AppData\Local\Temp\TempModule_11876.dll'.
VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'.
Hi from Get-RowAndPartitionKey:
foo
bar
Specifically, line VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'. indicates that the custom cmdlet was successfully imported into the session.

Run a PowerShell script from C#

I'm trying to build a graphic platform using Visual Studio. And I'm not a developer, I want to run PowerShell or batch files when I click a button. Thing is when I'm trying C# syntax it does not work even if I installed PowerShell extension.
I tried some code that I found on the internet, using process.start or trying to create a command in all cases the name of the command is not defined and it does not work.
private void Button1_Click(object sender, EventArgs e)
{
Process.Start("path\to\Powershell.exe",#"""ScriptwithArguments.ps1"" ""arg1"" ""arg2""");
}
I want to launch my .ps1 script but I get an error
name process is not defined
Calling C# code in Powershell and vice versa
C# in Powershell
$MyCode = #"
public class Calc
{
public int Add(int a,int b)
{
return a+b;
}
public int Mul(int a,int b)
{
return a*b;
}
public static float Divide(int a,int b)
{
return a/b;
}
}
"#
Add-Type -TypeDefinition $CalcInstance
$CalcInstance = New-Object -TypeName Calc
$CalcInstance.Add(20,30)
Powershell in C#
All the Powershell related functions are sitting in
System.Management.Automation namespace, ... reference that in your project
static void Main(string[] args)
{
var script = "Get-Process | select -Property #{N='Name';E={$_.Name}},#{N='CPU';E={$_.CPU}}";
var powerShell = PowerShell.Create().AddScript(script);
foreach (dynamic item in powerShell.Invoke().ToList())
{
//check if the CPU usage is greater than 10
if (item.CPU > 10)
{
Console.WriteLine("The process greater than 10 CPU counts is : " + item.Name);
}
}
Console.Read();
}
So, your query is also really a duplicate of many similar posts on stackoverflow.
Powershell Command in C#
Here's what it worked for me, including cases when the arguments contains spaces:
using (PowerShell PowerShellInst = PowerShell.Create())
{
PowerShell ps = PowerShell.Create();
string param1= "my param";
string param2= "another param";
string scriptPath = <path to script>;
ps.AddScript(File.ReadAllText(scriptPath));
ps.AddArgument(param1);
ps.AddArgument(param2);
ps.Invoke();
}
The .ps1 file would be something as this (make sure you declare the parameters in the .ps1 script):
Param($param1, $param2)
$message = $param1 + " & " + $param2"
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
[System.Windows.Forms.MessageBox]::Show($message)
I find this approach very easy to understand and very clear.
string path = #"C:\1.ps1";
Process.Start(new ProcessStartInfo("Powershell.exe",path) { UseShellExecute = true })

Passing output of type command as a command line argument in C#

I want to open a JSON file using the Win32 command type and re-direct its output to my C# program through command line arguments.
type Demo.json
gives me
{
"Message" : "Hello World"
}
I want to pass it to my C# program via command line arguments as follows
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe Demo.cs
Demo type Demo.json
But I get output as:
Demo.json
Demo.cs includes:
using System;
namespace Demo
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine(args[0]);
}
}
}
I want to redirect the output of the type command to pass it to the command line argument.
Don't pass the contents of a file through the command line arguments. Instead, pipe the data into stdin then have your program read from stdin:
class Program
{
static void Main(string[] args)
{
string contents = Console.In.ReadToEnd();
Console.WriteLine("Read from stdin: " + contents);
}
}
And to run it:
C:\Projects\ConsoleApp1\bin>type my-file.txt | ConsoleApp1.exe

How to get error info from dynamic C# code compiling

I have a C# application which uses a C# script interface. That means that my application will compile C# code and run it.
I am using the System.CodeDom.Compiler class to do it with.
The problem is that if I run the code below it throws an InvalidCastException because it is trying to cast a string to an int in my dynamic code.
If I catch the exception I have no indication where in the 'dynamic code' that error occured. For instance 'InvalidCastException on line 8'.
I get a stack trace, but no line numbers.
Any ideas? I want to present to our users enough information to know where their error is.
public class NotDynamicClass
{
public object GetValue()
{
return "value";
}
}
class Program
{
public static void Main()
{
var provider = CSharpCodeProvider.CreateProvider("c#");
var options = new CompilerParameters();
options.ReferencedAssemblies.Add("DynamicCodingTest.exe");
var results = provider.CompileAssemblyFromSource(options, new[]
{
#"
using DynamicCodingTest;
public class DynamicClass
{
public static void Main()
{
NotDynamicClass #class = new NotDynamicClass();
int value = (int)#class.GetValue();
}
}"
});
var t = results.CompiledAssembly.GetType("DynamicClass");
t.GetMethod("Main").Invoke(null, null);
}
}
You need to set IncludDebugInformation to true on your CompilerParameters.
Update: At the bottom of the MSDN documentation there is a community remark:
For C#, if you set this property to true you need to also set GenerateInMemory to false and set the value of OutputAssembly to a valid file name. This will generate an assembly and a .pdb file on disk and give you file and line number information in any stacktraces thrown from your compiled code.

How to capture a Powershell CmdLet's verbose output when the CmdLet is programmatically Invoked from C#

BACKGROUND
I am using Powershell 2.0 on Windows 7.
I am writing a cmdlet in a Powershell module ("module" is new to Powershell 2.0).
To test the cmdlet I am writing Unit tests in Visual Studio 2008 that programmatically invoke the cmdlet.
REFERENCE
This Article on MSDN called "How to Invoke a Cmdlet from Within a Cmdlet" shows how to call a cmdlet from C#.
THE SOURCE CODE
This is a distilled version of my actual code — I've made it as small as possible so that you can see the problem I am having clearly:
using System;
using System.Management.Automation;
namespace DemoCmdLet1
{
class Program
{
static void Main(string[] args)
{
var cmd = new GetColorsCommand();
foreach ( var i in cmd.Invoke<string>())
{
Console.WriteLine("- " + i );
}
}
}
[Cmdlet("Get", "Colors")]
public class GetColorsCommand : Cmdlet
{
protected override void ProcessRecord()
{
this.WriteObject("Hello");
this.WriteVerbose("World");
}
}
}
COMMENTS
I understand how to enable and capture verbose output from the Powershell command line; that's not the problem.
In this case I am programmatically invoking the cmdlet from C#.
Nothing I've found addresses my specific scenario. Some articles suggest I should implement my own PSHost, but seems expensive and also it seems like a have to call the cmdlet as text, which I would like to avoid because that is not as strongly typed.
UPDATE ON 2009-07-20
Here is is the source code based on the answer below.
Some things are still not clear to me:
* How to call the "Get-Colors" cmdlet (ideally without having to pass it as a string to the ps objet)
* How to get the verbose output as it is generated instead of getting an collection of them at the end.
using System;
using System.Management.Automation;
namespace DemoCmdLet1
{
class Program
{
static void Main(string[] args)
{
var ps = System.Management.Automation.PowerShell.Create();
ps.Commands.AddScript("$verbosepreference='continue'; write-verbose 42");
foreach ( var i in ps.Invoke<string>())
{
Console.WriteLine("normal output: {0}" , i );
}
foreach (var i in ps.Streams.Verbose)
{
Console.WriteLine("verbose output: {0}" , i);
}
}
}
[Cmdlet("Get", "Colors")]
public class GetColorsCommand : Cmdlet
{
protected override void ProcessRecord()
{
this.WriteObject("Red");
this.WriteVerbose("r");
this.WriteObject("Green");
this.WriteVerbose("g");
this.WriteObject("Blue");
this.WriteVerbose("b");
}
}
}
The code above generates this output:
d:\DemoCmdLet1\DemoCmdLet1>bin\Debug\DemoCmdLet1.exe
verbose output: 42
UPDATE ON 2010-01-16
by using the Powershell class (found in System.Management.Automation but only in the version of the assembly that comes with the powershell 2.0 SDK, not what comes out-of-the-box on Windows 7) I can programmatically call the cmdlet and get the verbose output. The remaining part is to actually add a custom cmdlet to that powershell instance - because that was my original goal - to unit test my cmdlets not those that come with powershell.
class Program
{
static void Main(string[] args)
{
var ps = System.Management.Automation.PowerShell.Create();
ps.AddCommand("Get-Process");
ps.AddParameter("Verbose");
ps.Streams.Verbose.DataAdded += Verbose_DataAdded;
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"output: {0,-24}{1}",
result.Members["ProcessName"].Value,
result.Members["Id"].Value);
}
Console.ReadKey();
}
static void Verbose_DataAdded(object sender, DataAddedEventArgs e)
{
Console.WriteLine( "verbose output: {0}", e.Index);
}
}
[Cmdlet("Get", "Colors")]
public class GetColorsCommand : Cmdlet
{
protected override void ProcessRecord()
{
this.WriteObject("Hello");
this.WriteVerbose("World");
}
}
Verbose output is not actually output unless $VerbosePreference is set at least to "Continue."
Use the PowerShell type to run your cmdlet, and read VerboseRecord instances from the Streams.Verbose propery
Example in powershell script:
ps> $ps = [powershell]::create()
ps> $ps.Commands.AddScript("`$verbosepreference='continue'; write-verbose 42")
ps> $ps.invoke()
ps> $ps.streams.verbose
Message InvocationInfo PipelineIterationInfo
------- -------------- ---------------------
42 System.Management.Automation.Invocat... {0, 0}
This should be easy to translate into C#.
1. string scriptFile = "Test.ps1";
2. using (PowerShell ps = PowerShell.Create())
3. {
4. const string getverbose = "$verbosepreference='continue'";
5. ps.AddScript(string.Format(getverbose));
6. ps.Invoke();
7. ps.Commands.Clear();
8. ps.AddScript(#".\" + scriptFile);
9. ps.Invoke();
10. foreach (var v in ps.Streams.Verbose)
11. {
12. Console.WriteLine(v.Message);
13. }
14. }
Important lines are line 5 and 6. This basically set the $verbosepreference for the session and for upcoming new commands and scripts.
First off, if you are unit testing cmdlets, likely Pester is a better (and easier) option.
As per your many updates, all you are likely missing at this point is a strongly typed approach to reference the C# cmdlet
ps.AddCommand(new CmdletInfo("Get-MyCS", typeof(GetMyCS)));
DISCLAIMER: I know this works for PowerShell 5.0, but don't have experience with the older PowerShell 2.0.

Categories

Resources