Have PowerShell script use C# code, then pass arguments to Main method - c#

I found a technet blog article the said it was possible to have PowerShell use C# code.
Article: Using CSharp (C#) code in Powershell scripts
I found the format I need to get the C# code to work in PowerShell, but if it don't pass the Main method an argument ([namespace.class]::Main(foo)) the script throws an error.
Is there a way I can pass a string of "on" or "off" to the main method, then depending on which string is passed run an if statement? If this is possible can you provide examples and/or links?
Below is the way I'm currently trying to structure my code.
$Assem = #( //assemblies go here)
$source = #"
using ...;
namespace AlertsOnOff
{
public class onOff
{
public static void Main(string[] args )
{
if(args == on)
{//post foo }
if(arge == off)
{ //post bar }
}
"#
Add-Type -TypeDefinition $Source -ReferencedAssumblies $Assem
[AlertsOnOff.onOff]::Main(off)
#PowerShell script code goes here.
[AlertsOnOff.onOff]::Main(on)

Well to start, if you are going to compile and run C# code, you need to write valid C# code. On the PowerShell side, if you invoke Main from PowerShell, you need to pass it an argument. PowerShell will automatically put a single argument into an array for you, but it won't insert an argument if you don't have one. That said, its not clear why this is in a Main method. It's not an executable. It could very well just have two static methods, TurnOn and TurnOff. The code below compiles and runs, modify as you see fit:
$source = #"
using System;
namespace AlertsOnOff
{
public class onOff
{
public static void Main(string[] args)
{
if(args[0] == `"on`")
{
Console.WriteLine(`"foo`");
}
if(args[0] == `"off`")
{
Console.WriteLine(`"bar`");
}
}
}
}
"#
Add-Type -TypeDefinition $Source
[AlertsOnOff.onOff]::Main("off")
# Other code here
[AlertsOnOff.onOff]::Main("on")

Related

Command Line Parser library giving 'System.Type' error in C#

I'm writing a Console App (.NET Framework) in C#. I want to use arguments from the command line, and I'm trying to use the Command Line Parser library to help me do this.
This is the package on Nuget - https://www.nuget.org/packages/CommandLineParser/
I found out about it from this StackOverflow question - Best way to parse command line arguments in C#?
MWE
using System;
using CommandLine;
namespace CLPtest
{
class Program
{
class SomeOptions
{
[Option('n', "name")]
public string Name { get; set; }
}
static void Main(string[] args)
{
var options = new SomeOptions();
CommandLine.Parser.Default.ParseArguments(args, options);
}
}
}
When I try create a minimal working example, I get an error for options on this line:
CommandLine.Parser.Default.ParseArguments(args, options);
The error is Argument 2: cannot convert from 'CLPtest.Program.SomeOptions' to 'System.Type'
I'm really confused as I have seen this same example code on at least 3 tutorials for how to use this library. (see for example - Parsing Command Line Arguments with Command Line Parser Library)
(This answer is being written at the time of v2.7 of this library)
From looking at their repository's README, it appears as if this is part of the API change that is mentioned earlier in the README. It looks as though the arguments are now handled differently since the example code you reference. So, now you should do something like this inside of Main:
...
static void Main(string[] args)
{
CommandLine.Parser.Default.ParseArguments<SomeOptions>(args);
}
...
To actually do something with those options you can use WithParsed which takes in the options that are defined in your SomeOptions class.
...
static void Main(string[] args)
{
CommandLine.Parser.Default.ParseArguments<SomeOptions>(args).WithParsed(option =>
{
// Do something with your parsed arguments in here...
Console.WriteLine(option.Name); // This is the property from your SomeOptions class.
});
}
...
The C# Example further down the README shows that you can pass in a method into WithParsed to handle your options instead of doing everything within Main.

Execute the C# console exe with command line arguments from VB Project

How to call an exe written in C# which accepts command line arguments from VB.NET application.
For Example, Let's assume the C# exe name is "SendEmail.exe" and its 4 arguments are From ,TO ,Subject and Message and If I have placed the exe in the C drive. This is how I call from the command prompt
C:\SendEmail from#email.com,to#email.com,test subject, "Email Message " & vbTab & vbTab & "After two tabs" & vbCrLf & "I am next line"
I would like to call this "SendEmail" exe from the VB.NET application and pass the command line arguments from VB (arguments will be using vb Syntax like vbCrLf, VBTab etc). This problem may look silly but I am trying to divide the complex problem into series of smaller issues and conquer it.
Because your question has the C# tag, I'll suggest a C# solution you can re-spin in your preferred language.
/// <summary>
/// This will run the EXE for the user. If arguments are passed, then arguments will be used.
/// </summary>
/// <param name="incomingShortcutItem"></param>
/// <param name="xtraArguments"></param>
public static void RunEXE(string incomingExePath, List<string> xtraArguments = null)
{
if (File.Exists(incomingExePath))
{
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
System.Diagnostics.Process proc = new System.Diagnostics.Process();
if (xtraArguments != null)
{
info.Arguments = " " + string.Join(" ", xtraArguments);
}
info.WorkingDirectory = System.IO.Path.GetDirectoryName(incomingExePath);
info.FileName = incomingExePath;
proc.StartInfo = info;
proc.Start();
}
else
{
//do your else thing here
}
}
You might not need to call it via the console. If it's done in C# and marked public instead of internal or private, or if it relies on a public type, you may be able to add it as a reference in your VB.Net solution and call the method you want directly.
This is so much cleaner and better because you don't have to worry about things like escaping spaces or quotes in your subject or body arguments.
If you have control over the SendMail program, you make it accessible with a few simple changes. By default, a C# Console project gives you something like this:
using ....
// several using blocks at the top
// class name
class Program
{
//static Main() method
static void Main(string[] args)
{
//...
}
}
You can make it available to use from VB.Net like this:
using ....
// several using blocks at the top
//Make sure an explicit namespace is declared
namespace Foo
{
// make the class public
public class Program
{
//make the method public
static void Main(string[] args)
{
//...
}
}
}
That's it. Again, just add the reference in your project, Import it at the top of your VB.Net file, and you can call the Main() method directly, without going through the console. It doesn't matter that's it an .exe instead of a .dll. In the .Net world, they're all just assemblies you can use.

Cmdlet verbose stream

I'm currently writing a C# cmdlet using the PowerShell 5.0 SDK.
I'm trying to pipe the StandardError of a third party executable to the cmdlet output when run from powershell in "real time".
I'm currently using the MedallionShell library to handle running the process. I've tried this with a normal C# win form and used the Command.StandardError.PipeToAsync(Console.OpenStandardOutput()) to get the output to print as the executable generated it to the console in "real time".
I tried to create my own Stream object that calls WriteVerbose but it didn't seem to print anything to the powershell screen (I am passing -Verbose to cmdlet when I run it).
My current flow looks something like this:
Open Powershell ISE
Load my module (C# dll)
Call my cmdlet with parameters
Command.Run
Command.StandardError.PipeToAsync(???)
Command.Wait (During this step, output should be flowing to powershell window)
Check Command.Result.Success.
Can anyone point me in the right direction on this?
You can not just call Cmdlet's Write methods (like WriteVerbose) from arbitrary thread. You need to marshal calls to this methods back to pipeline thread. A way to do that is to implement message loop, which would process messages from others threads, when other threads want to invoke something in pipeline thread.
Add-Type #‘
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
[Cmdlet(VerbsLifecycle.Invoke, "Process")]
public class InvokeProcessCmdlet : Cmdlet {
[Parameter(Position = 1)]
public string FileName { get; set; }
[Parameter(Position = 2)]
public string Arguments { get; set; }
protected override void EndProcessing() {
using(BlockingCollection<Action> messageQueue = new BlockingCollection<Action>()) {
using(Process process = new Process {
StartInfo=new ProcessStartInfo(FileName, Arguments) {
UseShellExecute=false,
RedirectStandardOutput=true,
RedirectStandardError=true
},
EnableRaisingEvents=true
}) {
int numberOfCompleteRequests = 0;
Action complete = () => {
if(Interlocked.Increment(ref numberOfCompleteRequests)==3) {
messageQueue.CompleteAdding();
}
};
process.OutputDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteObject(args.Data));
}
};
process.ErrorDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteVerbose(args.Data));
}
};
process.Exited+=(sender, args) => complete();
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
foreach(Action action in messageQueue.GetConsumingEnumerable()) {
action();
}
}
}
}
}
’# -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
And you can test it with something like this:
Invoke-Process icacls 'C:\* /c' -Verbose
if you derive from PSCmdlet instrad of Cmdlet you will have access to this.Host.UI.WriteVerboseLine which can be called from any thread at your own risk (i think it doesn't prevents in any way that the outputed string will be mixed in the wrong way)
Anyway, in my experience it have always worked well so far, and if the cmdlet is something that only you will consume i think the risk may be acceptable.
Again, it works well if used in a console, i don't know if it behaves the intended way if you later on redirect the verbose stream somewhere else than the console or something that doesn't have a "UI"
For sure #PetSerAl solution is more appropriate if you have some more time to implement it

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

Compiling C# at Runtime

I want to implement C# as the scripting language in my game.
My problem is, that my script will not compile if I want to use classes defined in the game core (exe).
The script looks like this:
using System;
using ConsoleApplication1;
class Script
{
private static void Call()
{
Console.WriteLine("called");
}
public static void Init()
{
Console.WriteLine("Script");
Call();
GameObject myO; // THIS IS WHAT I WANT TO GET WORKED,
//IF THIS IS COMMENTED OUT, IT COMPILES FINE, GAMEOBJECT
// IS DEFINED IN THE "ConsoleApplication1" NAMESPACE.
}
}
The script is compiled like in the MDX sample:
static void Main(string[] args)
{
CodeDomProvider provider = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
//cp.CompilerOptions = "/target:library";
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.IncludeDebugInformation = false;
cp.ReferencedAssemblies.Add("ConsoleApplication2.exe");
cp.ReferencedAssemblies.Add("System.dll");
CompilerResults cr = provider.CompileAssemblyFromFile(cp, "script.cs");
if (!cr.Errors.HasErrors)
{
Console.WriteLine("Success");
cr.CompiledAssembly.GetType("Script").GetMethod("Init").Invoke(null, null);
}
Console.ReadLine();
}
Is there any way to call functions or create objects defined in the "ConsoleApplication1" namespace via the script?
This is a daily programming problem. It's not working and you think it should be working. So break it down. Instead of working on a big problem, work on a smaller problem. Just tackle trying to compile the script outside of your program. Once you get that working, then you can try to compile it as a script from inside your program, knowing that you've got the basic problem of references and compiler issues sorted out.
Something like:
csc /reference:ConsoleApplication1.exe script.cs
From the looks of it, it might be as simple as changing the reference from ConsoleApplication2.exe to ConsoleApplication1.exe.

Categories