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.
Related
I have an app that loads a given DLL, discovers its methods by reflection, and then attempts to invoke particular methods in the DLL dynamically, i.e.,
method.Invoke ( dll, parms )
where 'parms' is an object[] with a single member. This works fine if the parameter is a simple intrinsic type (long, double, etc.). E.g., I can load and call the various Math methods this way.
The problem occurs when I try to pass an instance of a class (which is defined in the DLL) as a parameter, e.g.,
namespace NS
{
public class ABC
{
public long n;
public ABC ( long i )
{
n = i;
}
}
}
So, parms[0] contains new ABC ( 4 ). In this case, I get the following exception:
Object of type 'NS.ABC' cannot be converted to type 'NS.ABC'.
Should this work? What is the error message actually trying to tell me? (The called method is expecting ABC as its first/only argument, which I do verify in the code at run time via method.GetParameters().)
(much abbreviated code)
var assembly = Assembly.LoadFile ( pathtoDLL );
foreach ( var type in assembly.GetTypes () ) . . .
dynamic dll = Activator.CreateInstance ( type );
var methods = type.GetMethods ();
To invoke, I look up the desired method by name in 'methods', then:
var parms = new object[] { new ABC ( 4 ) };
return method.Invoke ( dll, parms );
Creating the argument instance by reflection, i.e., by getting a ConstructorInfo from the DLL, solves the problem. I.e., I call dll.GetType ().GetConstructors (), find the one I need (by class name) and then call constructor.Invoke ( new object[] { arg } ) to create the object.
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});
I am using C# as my host lang and my proxy is IronPython and I am setting methods inside of my scope pragmatically. So, my tenitive problem is my proxy method for calling the proper method via refection. the parameters are string methodName and params object[] args the code content of the method is:
initalScope.Add(obj.Name.ToLower(), new Func<object[], dynamic>( param
=> ReflectionMethodProxy( obj.Name , param ) ));
this was my initial setup. My problem with this is that when i attempt to pass one parameter in ironpython, i get the error expected Array[object], got str which is understandable, However, it should take 1 parameter since the parameter for the method calls for params.
so i changed the method call to this
initalScope.Add(obj.Name.ToLower(), new Func<object, dynamic>( param
=> ReflectionMethodProxy( obj.Name , param.GetType() != typeof (Array) ? new [] { param } : param ) ));
which works for one parameter, but obviously would not work for multiple parameters.
my goal is to be able to pass multiple parameters via Ironpython and then distribute the to the correct method in c#. Below is my code. Im probably missing something obvious, but ive been at it for a while now and really need some more eyes.
the outcome of the test code below is two errors
methodcall("test1") - provides expected Array[object] got str error
methodcall("test1",3) - provides Invoke() takes exactly 1 argument (2 given)
// dummy method for format sake
public void setScope()
{
foreach (var obj in LinkedDocument.GetType().GetMethods().Where(
method => method.GetCustomAttributes(typeof(ScriptingVoidAttribute), true).Length != 0
|| method.GetCustomAttributes(typeof(ScriptingFuncAttribute), true).Length != 0))
initalScope.Add(obj.Name.ToLower(), new Func<object[], dynamic>( param
=> ReflectionMethodProxy( obj.Name , param ) ));
}
public dynamic ReflectionMethodProxy( string methodName , params object[] args )
{
return LinkedDocument.GetType().GetMethod(methodName, args.Select(obj
=> obj.GetType()).ToArray())
.Invoke(LinkedDocument, args);
}
Thanks in advance for any help.
I have a function that calls other functions dynamically via name. I need to pass a hashtable to the function being called. I have the code working in VB.Net but while trying to convert it to C# I am running into a error when I try to pass the hashtable as a parameter. Can someone explain what is happening and how I can resolve it?
This is the working VB.Net code:
Dim objTF As New ThreadFunctions
Dim objResults As Object = CallByName(objTF, htParameters.Item("strMethodName"), CallType.Get, htParameters)
Here is the C# code:
ThreadFunctions objTF = new ThreadFunctions();
Type objType = objTF.GetType();
MethodInfo miMethod = objType.GetMethod(htParameters["strMethodName"].ToString());
object objResults = miMethod.Invoke(objTF, htParameters); //This line has the issue
Error 1 The best overloaded method match for 'System.Reflection.MethodBase.Invoke(object, object[])' has some invalid arguments
Try
object objResults = miMethod.Invoke(objTF, (object)htParameters);
As the params second argument threats the hashtable wrong.
Brief:
We have a C# method which accept parameters.
inside of that method we need to add Sql parameters (according to the method parameters).
It actually looks like this :
http://i.stack.imgur.com/QP1y2.jpg (to enlarge)
Please notice
I can write a class which will have all the method arguments. But then , I will have to create a class container for each SP ( and we have a lot).
Ok lets continue.
I was thinking to myself : "WHY should I insert each param manually - If I already have its : name , type , value in the method param ? I don't want to do this , maybe reflection will do this for me" ( lets leave for now the conversion for int <->DbType.Int32)
But I encountered a problem. it is not possible reading values using reflection.
However , I did managed to read values via reflection with a hack.
1) I did this example: ( simulation to the real scenario)
public static void MyMethod(int AddressId, string countryId, DateTime dt) //these are the arguments which need to be attached later
{
MethodBase mi = MethodInfo.GetCurrentMethod();
var hack = new {AddressId, countryId, dt}; //this is the HACK
var lstArguments = mi.GetParameters() //get the parameter name and types
.Select(p => new {
name = p.Name , type=p.GetType().Name
}).ToList();
List < dynamic > lstParamValues= new List < dynamic > ();
foreach(PropertyInfo pi in hack.GetType().GetProperties()) //get the value(!!!) of each param
{
lstParamValues.Add(pi.GetValue(hack, null));
}
dynamic dyn= myDBInstance; // get dynamic reference to the DB class instance (
for(int i = 0; i < lstArguments .Count; i++) //dynamically Invoke the method with param name+value.
{
dyn.AddInParameter(lstArguments [i].name, ((lstParamValues[i] is int) ? DbType.Int32 : DbType.String), lstParamValues[i]);
}
}
And there is the DB class ( to simulate the AddInParameter to see if im getting values)
class myDB
{
public void AddInParameter(string name, DbType dbt, object val)
{
Console.WriteLine("name=" + name + " " + "DbType=" + dbt + " " + "object val=" + val);
}
}
And it is working :
I executed the method with :
MyMethod(1, "2", DateTime.Now);
And the output was :
name=AddressId DbType=Int32 object val=1
name=countryId DbType=String object val=2
name=dt DbType=String object val=05/02/2013 19:09:32
All fine.
the C# Question
How can I get over this hack :
var hack = new {AddressId, countryId, dt}; // the method arguments names
In my sample I wrote it hard coded.
The reflection method GetValue is working only because of this line.
Is there anything I can do to add on run-time the method arguments name as a property , so I will be able to do :
foreach(PropertyInfo pi in SOMETHING.GetType().GetProperties())
{
}
Please consider this question as a c# question and not ORM question.
Also , If you want to play with running sample code ( just paste in console proj) : http://jsbin.com/azicut/1/edit
Does this provide some relief?
Do this once (in the DAL ctor):
SqlParameter sqlCmdFTSindexWordOnceParam = sqlCmdFTSindexWordOnce.Parameters.Add("#sID", SqlDbType.Int);
sqlCmdFTSindexWordOnceParam.Direction = ParameterDirection.Input;
SqlParameter sqlCmdFTSindexWordOnceParam2 = sqlCmdFTSindexWordOnce.Parameters.Add("#sIDpar", SqlDbType.Int);
sqlCmdFTSindexWordOnceParam2.Direction = ParameterDirection.Input;
Then in the call:
sqlCmdFTSindexWordOnceParam.Value = wdc.sID;
sqlCmdFTSindexWordOnceParam2.Value = wdc.sIDpar;
sqlCmdFTSindexWordOnce.BeginExecuteNonQuery(callbackFTSindexWordOnce, sqlCmdFTSindexWordOnce);
Better performance than adding the parameter for each call.
AddWithValue at least will save you from writing type name.
command.Parameters.AddWithValue("#demographics", demoXml);
But also you really shouln't pass that much parameter to a function. A simple DTO class can make your code much more readable. If I am not mistaken you are looking something like pyhton's locals (Need something like locals in python) which gives you local variables of function as key-value pair and you can't do this in c# as far as i know.
or you simple do not pass all variables you just can pass a Dictionary<string,object> and add key values to SqlParameter
i'm not familiar with the workings of c# and .net, but if this was java, i would say:
store the current parameters into an arraylist or map
you could either have 3 arraylists, one for each of the parameters that go into the addInParameters method call, or a map with all the values included in it (the type parameter would need to have some duplication as it does now)
use the array list as the only parameter in the insert/update methods
use a for loop so that the AddInParameter call is only written once, and the parameters are like (array1[0], array2[0], array3[0]) rather than the actual values.
in php you could use associative arrays, does .net have associative arrays?