I'm building a Powershell CmdLet for awesome printing. It should function just like Out-Print but with all the bells and whistles of winprint.
PS> get-help out-winprint
NAME
Out-WinPrint
SYNTAX
Out-WinPrint [[-Name] <string>] [-SheetDefintion <string>] [-ContentTypeEngine <string>]
[-InputObject <psobject>] [<CommonParameters>]
ALIASES
wp
For this to work, I need to take the input stream (InputObject) of my PSCmdLet implementation and pass it through Out-String so it's all expanded and formatted. I'm thinking the best way to do this is to use CommandInvocationIntrinsics.InvokeScript to invoke out-string, which should give me the output as a string...
protected override void ProcessRecord() {
if (InputObject == null || InputObject == AutomationNull.Value) {
return;
}
IDictionary dictionary = InputObject.BaseObject as IDictionary;
if (dictionary != null) {
// Dictionaries should be enumerated through because the pipeline does not enumerate through them.
foreach (DictionaryEntry entry in dictionary) {
ProcessObject(PSObject.AsPSObject(entry));
}
}
else {
ProcessObject(InputObject);
}
}
private void ProcessObject(PSObject input) {
object baseObject = input.BaseObject;
// Throw a terminating error for types that are not supported.
if (baseObject is ScriptBlock ||
baseObject is SwitchParameter ||
baseObject is PSReference ||
baseObject is PSObject) {
ErrorRecord error = new ErrorRecord(
new FormatException("Invalid data type for Out-WinPrint"),
DataNotQualifiedForWinprint,
ErrorCategory.InvalidType,
null);
this.ThrowTerminatingError(error);
}
_psObjects.Add(input);
}
protected override async void EndProcessing() {
base.EndProcessing();
//Return if no objects
if (_psObjects.Count == 0) {
return;
}
var text = this.SessionState.InvokeCommand.InvokeScript(#"Out-String", true, PipelineResultTypes.None, _psObjects, null);
// Just for testing...
this.WriteObject(text, false);
...
Assume I invoked my cmdlet like this:
PS> get-help out-winprint -full | out-winprint`
If I understand how this is supposed to work, the var text above should be a string and WriteObject call should display what out-string would display (namely the result of get-help out-winprint -full).
However, in reality text is string[] = { "" } (an array of strings with one element, an empty string).
What am I doing wrong?
You're doing two very small things wrong:
The method is called InvokeScript so literally what you're passing is a scriptblock.
Right now, your ScriptBlock is basically like this:
$args = #(<random stuff>) # this line is implicit of course,
# and $args will have the value of whatever your _psObjects has
Out-String
So as you can tell the arguments made it to the script, you're just not using them. So you want something a bit more like this instead as your script:
Out-String -InputObject $args
Only now the problem is that Out-String doesn't actually like being given a Object[] as an -InputObject so instead your script has to be something like:
$args | Out-String
Or some variation of that like using a foreach, you get the idea.
Your second error is that you're passing _psObjects onto the wrong parameter - it should be:
this.SessionState.InvokeCommand.InvokeScript(<ScriptBlock>, true, PipelineResultTypes.None, null, _psObjects);
The official documentation is really bad on this and I have absolutely no idea what the other parameter is for.
On one of the overloads is lists:
input = Optionall input to the command
args = Arguments to pass to the scriptblock
But on the next overload it says the following:
input = The list of objects to use as input to the script.
args = The array of arguments to the command.
All I can tell you is, in my tests it works when I do it as stated. Hope that helps!
For reference, tested and working PS code:
function Test
{
[CmdletBinding()]
param()
$results = $PSCmdlet.SessionState.InvokeCommand.InvokeScript('$args | Out-String', $false, "None", $null, "Hello World!")
foreach ($item in $results)
{
$item
}
}
Test
EDIT
I should add that according to my tests, if you pass something to both input and args then $args will be empty inside the script. Like I said, no actual idea what input does at all, just pass null to it.
EDIT 2
As mentioned by tig, on PowerShell issue 12137, whatever gets passed to input will be bound to the variable $input inside the scriptblock which means either input or args can be used.
That being said... be careful of using $input - it is a collection that will contain more than what is passed via the input parameter: according to my tests index 0 will contain a bool that is whatever is passed on 2nd parameter of InvokeScript() and index 1 will contain a PipelineResultTypes that is whatever was passed to InvokeScript() on the 3rd parameter.
Also I would not recommend using PowerShell.Create() in this case: why create a new instance of PowerShell when you have a PSCmdlet which implies you already have one?
I still think using args/$args is the best solution. Of course you could also make things a lot nicer (although completely unneeded in this case) by using a ScriptBlock like:
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[PSObject[]]
$objects
)
Out-String -InputObject $objects
This will be faster as well since you are no longer relying on the (slow) pipeline.
Just don't forget that now you need to wrap your _psObjects around an object[] like:
this.SessionState.InvokeCommand.InvokeScript(<ScriptBlock>, true, PipelineResultTypes.None, null, new object[] {_psObjects});
Related
I am making an app which is interfaced with at the command line by typing commands that then call corresponding methods. The thing is, some of these methods are asynchronous, and thus, according to what I've heard, should return Task instead of void, even when their return value is not used (async is not necessary for the program I am making, however a library I am using in some of the methods is asynchronous).
Because of this, I can't use a dictionary of delegates (as far as I know), as they would be different types, so I have tried using reflection.
MethodInfo command = MethodBase.GetCurrentMethod()
.DeclaringType
.GetMethod(_commands[args[0]]);
command.Invoke(null, new string[][] { args });
The above snippet is intended to get a static method by its name and then call it with argument string[] args.
According to the documentation I'm looking at, alongside other StackOverflow answers, the first argument should be null if the method being called is static, however I get a NullReferenceException anyway. Why is this, and how do I fix it?
Well, GetMethod can well return null or some non static method
MethodInfo command = MethodBase
.GetCurrentMethod()
.DeclaringType
.GetMethod(_commands[args[0]]);
So we have to check if command is valid one; since args[0] is used in finding the method, I guess it should be removed from parameters (Skip(1)):
if (command != null && command.IsStatic)
command.Invoke(null, args.Skip(1).Cast<Object>().ToArray());
please, note, that you have to do more validation if method can be overload (i.e. we have several methods with the same name), something like this:
MethodInfo command = MethodBase
.GetCurrentMethod()
.DeclaringType
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.Name == _commands[args[0]])
.Where(m => m.GetParameters().Length == args.Length - 1) // - 1 for Skip(1)
.FirstOrDefault();
You must check that the command is not null.
If you don't want to handle the case to only call it if not null, you can simply write:
command?.Invoke(null, new string[] { args });
Thus if the method does not exist, GetCurrentMethod returns null and nothing is done.
But if you want to manage the case you need to use a test and for example show a system message.
You should also hardness the code by checking if args is noty empty too.
And you should also add some bindings flags to the search.
if (args.Length == 0)
{
Console.WriteLine("No command provided.");
return;
}
string commandName = _commands[args[0]];
// You can remove non public or public depending on the nature of your methods
var flags = var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
var command = MethodBase.GetCurrentMethod().DeclaringType.GetMethod(commandName, flags);
if (command == null)
{
Console.WriteLine("Command not found: " + commandName);
Console.WriteLine("Allowed commands are:"
Console.WriteLine("- ...");
Console.WriteLine("- ...");
return;
}
command.Invoke(null, new string[] { args });
I removed the jagged array [][] as seen and suggested by #DmitryBychenko in case of it was a mistake (it should be) that causes the error in the executed method if it exists.
Here a some example of advanced command line arguments parsing:
Parsing command-line options in C#
Best way to parse command line arguments in C#?
https://codereview.stackexchange.com/questions/369/parsing-console-application-arguments
https://github.com/mykeels/CommandLineParser
https://github.com/commandlineparser/commandline
https://www.codeproject.com/Articles/19869/Powerful-and-simple-command-line-parsing-in-C
I am writing a text file parser for a specific matching condition, and in a couple of the files I need to do some custom manipulation. What I would like to do is store the name of the custom procedure that is being used in an external XML file with the other rules. Most won't use this, and I found this answer regarding the action call:
Variable for function names
The above has the following dictionary definition
private static readonly IDictionary<string,Action<string>> actionByType =
new Dictionary<string,Action<string>>
Element adding from XML file in my current program (These two elements will be added)
foreach (XmlNode node in nodes)
{
Client holding = new Client();
holding.has_custom =
Convert.ToBoolean(
node.SelectSingleNode("has_custom").InnerText);
holding.custom_call =
node.SelectSingleNode("custom_call").InnerText;
clients.Add(holding);
}
As I go through this, how do I assign the name of the custom call as an action to be called in the dictionary? And then do I use a case statement with the generic parse as the default?
Im not sure if I understand you correctly, but you can assign Actions / functions (Delegates to be more specific) like this:
actionByType.Add("write", text => Console.WriteLine(text));
actionByType.Add("write2", Console.WriteLine);
or like this:
void someAction(string someString)
{
Console.WriteLine(someString);
}
...
actionByType.Add("write", someAction);
Then Invoke like this:
actionByType["write"]("Hello World!");
So in your case it would be:
actionByType[holding.custom_call]("What ever you need that string argument for");
Here is the fiddle https://dotnetfiddle.net/oFuEeF
First, get the proper MethodInfo using reflection. This should be a static method, and should reside in a static class containing all your XML-accessible methods.
var method = typeof(MyStoredTypes).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)
You'll also need a ParameterExpression to capture the incoming string.
var param = Expression.Parameter(typeof(string));
Finally, System.Linq.Expression.Call to create the Expression tree for your call, Lambda it, and Compile it.
var act = Expression.Lambda<Action<string>>(
Expression.Call(param, method),
new ParameterExpression[] { param })
.Compile();
I'm overriding some listener methods. I've got several objects which corresponds the following grammar:
object : BEGIN o1+ END ;
o1 : ( Token1 | (name | Token2 ) );
Here is the code from the EnterObject() override method:
if (context.o1(1).name() != null)
{
object.Field = context.o1(1).name().GetChild(0).GetText();
}
else
{
object.Field = context.o1(1).Token2().GetText();
}
It works, though I have some doubts. Is there more efficient way of checking the EnterName() within the EnterObject() maybe?
I believe you can use context.o1(1).GetText() instead of the longer expression.
I also recommend you add an attribute to the method where you use this code to declare the dependency on the descendants of o1 (since those descendants implicitly define the result of the GetText() call).
[RuleDependency(
typeof(YourParserType),
YourParserType.RULE_o1,
0,
Dependents.Descendants)]
You can validate all uses of this attribute by calling the following during your application or library initialization code.
Assembly assembly = typeof(YourListenerClass).Assembly;
RuleDependencyChecker.CheckDependencies(assembly);
I am attempting unsuccessfully to pass a hashtable from C# to PowerShell as a script parameter. The script and parameters work properly when I execute within PowerShell, so I'm assuming my error is on the C# side.
In C#, I am simply using Command.Parameters.Add() as I would for any other parameter. All the other parameters that I pass to the script are being received correctly, but the hashtable is null.
From the C# side, I have tried using both a Hashtable object and a Dictionary<string, string> object, but neither appears to work. In both cases, I have confirmed that the object is instantiated and has values before passing to PowerShell. I feel like there's a very obvious solution staring me in the face, but it just isn't clicking.
Using this answer and the Command.Parameters.Add( string, object ) method overload, I was able to pass a hashtable to a script. Here is the test code:
string script = #"
param( $ht )
Write-Host $ht.Count
$ht.GetEnumerator() | foreach { Write-Host $_.Key '=' $_.Value }
";
Command command = new Command( script, isScript: true );
var hashtable = new Hashtable { { "a", "b" } };
command.Parameters.Add( "ht", hashtable );
Pipeline pipeline = runspace.CreatePipeline( );
pipeline.Commands.Add( command );
pipeline.Invoke( );
It displays 1 from $ht.Count, and a = b from enumerating the hashtable values.
You can only pass strings as command line parameters.
I don't know if there a limit but if there isn't you will need to convert the hashtable to a string and parse it in your PowerShell script.
I'm programming in Unity, using an Action event to hold a bunch of other Action delegates, in order to hook non-Monobehaviour objects into the Update() system. I'd like to be able to print the names of the actions to the Debug console, but using something like:
Delegate[] actions = updateActions.GetInvocationList();
foreach ( Delegate del in actions ) {
Debug.Log( del.ToString() );
}
... just returns "System.Action". I've tried also (del as Action).ToString() with no luck.
You can use the Method property to get a MethodInfo which should have a useful name.
Delegate[] actions = updateActions.GetInvocationList();
foreach ( Delegate del in actions )
{
Debug.Log( del.Method.ReflectedType.FullName + "." + del.Method.Name );
}
You can use del.Method.ToString() if you want the signature or del.Method.Name if you only want the name. del.Method.ReflectedType.FullName gives you the type name.
For lambdas/anonymous methods the name might not be too useful since they only have a compiler generated name. In the current implementation the name of a lambda is something like <Main>b__0 where Main is the name of the method that contains the lambda. Together with the type name this should give you a decent idea which lambda it is.
If you mean that you declare a delegate
var foo = new Action(() => { /* do something */ });
and you want to retrieve the word "foo" later, you're out of luck. To get that behavior you'll have to consume the declaration as an expression tree and parse out foo yourself.