I have below command and it returns me null object . When I run the command separately in PowerShell window I get the right result. Below is my PowerShell method which is calling the command and the also the PowerShell command which I have defined. I am basically looking to return a string value. Please let me know what wrong am I doing?
C# method:
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() )
{
ps.AddScript( contentScript );
// in ContentScript I get "Get-RowAndPartitionKey" on debugging
ps.AddParameters( parameterList );//I get list of strings
IAsyncResult async = ps.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
// here i get result empty in ps.EndInvoke(async)
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
My Powershell GetRowAndPartitionKey cmdlet definition, which the code above is trying to call:
public abstract class GetRowAndPartitionKey : PSCmdlet
{
[Parameter]
public List<string> Properties { get; set; } = new List<string>();
}
[Cmdlet( VerbsCommon.Get, "RowAndPartitionKey" )]
public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKey
{
protected override void ProcessRecord()
{
string rowKey = string.Join( "_", Properties );
string pKey = string.Empty;
WriteObject( new
{
RowKey = rowKey,
PartitionKey = pKey
} );
}
}
}
When using the PowerShell SDK, if you want to pass parameters to a single command with .AddParameter() / .AddParameters() / AddArgument(), use .AddCommand(), not .AddScript()
.AddScript() is for passing arbitrary pieces of PowerShell code that is executed as a script block to which the parameters added with .AddParameters() are passed.
That is, your invocation is equivalent to & { Get-RowAndPartitionKey } <your-parameters>, and as you can see, your Get-RowAndPartitionKey command therefore doesn't receive the parameter values.
See this answer or more information.
Note: As a prerequisite for calling your custom Get-RowAndPartitionKey cmdlet, you may have to explicitly import the module (DLL) that contains it, which you can do:
either: with a separate, synchronous Import-Module call executed beforehand (for simplicity, I'm using .AddArgument() here, with passes an argument positionally, which binds to the -Name parameter (which also accepts paths)):
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>").Invoke();
or: as part of a single (in this case asynchronous) invocation - note the required .AddStatement() call to separate the two commands:
IAsyncResult async =
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>")
.AddStatement()
.AddCommand("GetRowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
"<your-module-path-here>" refers to the full file-system path of the module that contains the Get-RowAndPartitionKey cmdlet; depending on how that module is implemented, it is either a path to the module's directory, its .psd1 module manifest, or to its .dll, if it is a stand-alone assembly.
Alternative import method, using the PowerShell SDK's dedicated .ImportPSModule() method:
This method obviates the need for an in-session Import-Module call, but requires extra setup:
Create a default session state.
Call .ImportPSModule() on it to import the module.
Pass this session state to PowerShell.Create()
var iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { #"<your-module-path-here>" });
var ps = PowerShell.Create(iss);
// Now the PowerShell commands submitted to the `ps` instance
// will see the module's exported commands.
Caveat: A PowerShell instance reflects its initial session state in .Runspace.InitialSessionState, but as a conceptually read-only property; the tricky part is that it is technically still modifiable, so that mistaken attempts to modify it are quietly ignored rather than resulting in exceptions.
To troubleshoot these calls:
Check ps.HadErrors after .Invoke() / .EndInvoke() to see if the PowerShell commands reported any (non-terminating) errors.
Enumerate ps.Streams.Errors to inspect the specific errors that occurred.
See this answer to a follow-up question for self-contained sample code that demonstrates these techniques.
Related
Scenario: I want to parse a console app's command line that has a number of options (that don't relate to a command), and a command that has a number of options. I've simplified what I'm trying to into a fictitious example. E.g. myapp --i infile.dat --o outfile.dat conversion --threshold 42
Problem: I'm finding that if I put the "conversion" command and it's option on the command-line then only its handler gets called, but not the handler for the root command. So I have no way to determine the values of the root command's --i and --o options.
(Conversely, if I omit the "conversion" command then root command's handler only is called - which is what i would expect.)
Example code:
public class Program
{
public static async Task<int> Main(string[] args)
{
// Conversion command
var thresholdOpt = new Option<int>("--threshold");
var conversionCommand = new Command("conversion") { thresholdOpt };
conversionCommand.SetHandler(
(int threshold) => { Console.WriteLine($"threshold={threshold}"); },
thresholdOpt);
// Root command
var infileOpt = new Option<string>("--i");
var outfileOpt = new Option<string>("--o");
var rootCommand = new RootCommand("test") { infileOpt, outfileOpt, conversionCommand };
rootCommand.SetHandler(
(string i, string o) => { Console.WriteLine($"i={i}, o={o}"); },
infileOpt, outfileOpt);
return await rootCommand.InvokeAsync(args);
}
}
Unexpected outputs:
> myapp --i infile.dat --o outfile.dat conversion --threshold 42
threshold=42
In the above I expect to see the value for the --i and --o options, as well as the threshold options associated with the conversion command, but the root command's handler isn't invoked.
Expected outputs:
> myapp --i infile.dat --o outfile.dat
i=infile.dat, o=outfile.dat
> myapp conversion --threshold 42
threshold=42
The above are what I'd expect to see.
Dependencies: I'm using System.CommandLine 2.0.0-beta3.22114.1, System.CommandLine.NamingConventionBinder v2.0.0-beta3.22114.1, .net 6.0, and Visual Studio 17.1.3.
I'd be grateful for help in understanding what I'm doing wrong. Thanks.
Based on the docs sample it seems only one verb gets executed. For example next:
var rootCommand = new RootCommand();
rootCommand.SetHandler(() => Console.WriteLine("root"));
var verbCommand = new Command("verb");
verbCommand.SetHandler(() => Console.WriteLine("verb"));
rootCommand.Add(verbCommand);
var childVerbCommand = new Command("childverb");
childVerbCommand.SetHandler(() => Console.WriteLine("childverb"));
verbCommand.Add(childVerbCommand);
return await rootCommand.InvokeAsync(args);
For no arguments will print root, for verb will print verb and for verb childverb will print childverb.
So if you need multiple actions performed it seems you will need to use another approach (for example manually processing rootCommand.Parse() result).
If you just want "--i" and "--o" accessible for conversion then add them to corresponding command:
// actually works without specifying infileOpt, outfileOpt on conversionCommand
// but should be still present on the root one
// also rootCommand.AddGlobalOption can be a more valid approach
var conversionCommand = new Command("conversion") { thresholdOpt, infileOpt, outfileOpt};
// add here for handler
conversionCommand.SetHandler(
(int threshold, string i, string o) => { Console.WriteLine($"threshold={threshold}i={i}, o={o}"); },
thresholdOpt, infileOpt, outfileOpt);
With Mono.Cecil it looks quite simple when we can just set the Body of the target MethodDefinition to the Body of the source MethodDefinition. For simple methods, that works OK. But for some methods whereas a custom type is used (such as to init a new object), it won't work (with an exception thrown at the time writing the assembly back).
Here is my code:
//in current app
public class Form1 {
public string Test(){
return "Modified Test";
}
}
//in another assembly
public class Target {
public string Test(){
return "Test";
}
}
//the copying code, this works for the above pair of methods
//the context here is of course in the current app
var targetAsm = AssemblyDefinition.ReadAssembly("target_path");
var mr1 = targetAsm.MainModule.Import(typeof(Form1).GetMethod("Test"));
var targetType = targetAsm.MainModule.Types.FirstOrDefault(e => e.Name == "Target");
var m2 = targetType.Methods.FirstOrDefault(e => e.Name == "Test");
var m1 = mr1.Resolve();
var m1IL = m1.Body.GetILProcessor();
foreach(var i in m1.Body.Instructions.ToList()){
var ci = i;
if(i.Operand is MethodReference){
var mref = i.Operand as MethodReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(mref));
}
else if(i.Operand is TypeReference){
var tref = i.Operand as TypeReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(tref));
}
if(ci != i){
m1IL.Replace(i, ci);
}
}
//here the source Body should have its Instructions set imported fine
//so we just need to set its Body to the target's Body
m2.Body = m1.Body;
//finally write to another output assembly
targetAsm.Write("modified_target_path");
The code above was not referenced from anywhere, I just tried it myself and found out it works for simple cases (such as for the 2 methods Test I posted above). But if the source method (defined in the current app) contains some Type reference (such as some constructor init ...), like this:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
Then it will fail at the time writing the assembly back. The exception thrown is ArgumentException with the following message:
"Member 'System.Uri' is declared in another module and needs to be imported"
In fact I've encountered a similar message before but it's for method calls like (string.Concat). And that's why I've tried importing the MethodReference (you can see the if inside the foreach loop in the code I posted). And really that worked for that case.
But this case is different, I don't know how to import the used/referenced types (in this case it is System.Uri) correctly. As I know the result of Import should be used, for MethodReference you can see that the result is used to replace the Operand for each Instruction. But for Type reference in this case I totally have no idea on how.
All my code posted in my question is fine BUT not enough. Actually the exception message:
"Member 'System.Uri' is declared in another module and needs to be imported"
complains about the VariableDefinition's VariableType. I just import the instructions but not the Variables (which are just referenced exactly from the source MethodBody). So the solution is we need to import the variables in the same way as well (and maybe import the ExceptionHandlers as well because an ExceptionHandler has CatchType which should be imported).
Here is just the similar code to import VariableDefinition:
var vars = m1.Body.Variables.ToList();
m1.Body.Variables.Clear();
foreach(var v in vars){
var nv = new VariableDefinition(v.Name, targetType.Module.Import(v.VariableType));
m1.Body.Variables.Add(nv);
}
I am attempting to convert established code into a .dll which will be able to be loaded as required by the main program. The .dll does not require any input parameters from the main program and is intended to only return a string value. My primary resource has been this answer.
The dll code is structured:
namespace DLL
{
class DLLClass
{
public string PublicString(string OutputString)
{
// ... existing code ...
return OutputString;
}
}
}
The main program attempts to load the .dll, execute the logic, and retrieve the returned string for display in the console:
static void Main()
{
var DLLPath = new FileInfo("DLL.dll");
Assembly assembly = Assembly.LoadFile(DLLPath.FullName);
Type t = assembly.GetType("DLL.DLLClass");
object obj = Activator.CreateInstance(t);
MethodInfo method = t.GetMethod("PublicString");
string TargetString = (string)method.Invoke(obj, null);
Console.WriteLine("End of dll");
Console.WriteLine(TargetString);
Console.ReadLine();
}
This method presently fails as a TargetParameterCountException ("Parameter count mismatch") occurs at the .Invoke line. The debug information indicates the OutputString remains null at the time of the exception, meaning the code within the .dll does not appear to have run yet.
Thank you in advance for any assistance in this matter.
Change the below code
string TargetString = (string)method.Invoke(obj, null);
to
object[] parametersArray = new object[] { "Hello" };
string TargetString = (string)method.Invoke(obj, parametersArray);
You are not passing parameter value to the calling method so that it is having such issue.
I'm writing a CmdLet in C# and use the Powershell-Class and invoke different commands. Here my Code:
PowerShell poswershell;
public Construcor()
{
powershell = PowerShell.Create()
}
public Collection<PSObject> InvokeRestMethod(string uri)
{
return InvokePSCommand("Invoke-RestMethod", new[] { uri });
}
public Collection<PSObject> InvokePSCommand(string command, string[] args)
{
var execute = command;
execute = GetExecuteCommand(args, execute);
powershell.Commands.AddScript(execute);
return powershell.Invoke();
}
private static string GetExecuteCommand(string[] args, string execute)
{
if (args == null) return execute;
for (var i = 0; i < args.Count(); i++)
{
execute += " " + args[i];
}
return execute;
}
It works like I want, but really slowly. I want the same function, which gives me a Collection<PSObject> back. But when I call the InvokeRestMehtod several times, it takes a long time to get trough this.
Why I use this just for a simple WebRequest you ask?
The answer is, that I have to read from an uri (which returns json) some information. Fact is that the json structure is always different. Therefor the Invoke-RestMehtod gives me exactly what I need, a dynamic object(PSObject). I have to have this kind of object, because after that process I need to give this in the powershell user back, so he can pipeline the object and use it furhter.
My question is now, how could I get the same result, which I can pass into powershell, from an uri which return json?
EDIT
I found this dll=> Microsoft.PowerShell.Commands.Utility.dll, it includes in C# code the InvokeRestMethod-CmdLet what is called by the powershell. If I could read this code well, I could (would) use it in my code. Then I wouldn't create a PowerShell instance and invoke from my powershell-CmdLet an other CmdLet, which I do not like much and takes a way too long. Someone knows this dll and could help me to customize this code for my project?
I found dottrace and analyzed the process, here a screenshot I can't get any useful information out of that, perhaps someone of you? But I'm pretty sure Powershell.Invoke() takes most of the time while executing.
Why don't you reuse the same PowerShell object rather than create a new one each time? Each instantiation causes the PowerShell to have to initialize again. Just be sure to call powershell.Commands.Clear() between Invoke() calls.
I solved the problem now, here is the basic code which reads an uri (returns JSON) and returns an object:
private object InvokeRest(string uri)
{
return InvokeRest(uri, null);
}
private object InvokeRest(string uri, NetworkCredential networkCredential)
{
var webRequest = WebRequest.Create(uri);
if (networkCredential!=null)
webRequest.Credentials = networkCredential;
_webResponse = webRequest.GetResponse();
var streamReader = new StreamReader(_webResponse.GetResponseStream());
var str = streamReader.ReadToEnd();
ErrorRecord exRef;
var doc = JsonObject.ConvertFromJson(str, out exRef);
return doc;
}
Because I found out that every object you output in the powershell, converts in a PSObject and formats it.
I am using dynamic method calls to access a method in a dynamically loaded dll.
I am doing:
dynamic classInstance = Activator.CreateInstance(cmd.Type);
classInstance.AddString(); //This line works
classInstance.AddString(cmd); //this line does not work
The methods in the dll are:
public CustomCommandTest1()
{
}
public void AddString(Command cmd, ExposedVariables exv)
{
exv.ChopResults += "Add string Command";
}
public void AddString(ExposedVariables exv)
{
exv.ChopResults += "Add string Command";
}
public void AddString()
{
string ChopResults = "Add string Command";
}
I can access (call) all the methods that do not have parameters but those that do give me a RuntimeBindingInternalCompilerException. There is no usable info in the exception to try to figure this out.
I have done this before and it has worked. I don't know what I am doing differently here.
Further discovery here reveals that it is related to the complex variable types. Simple builtin variable types works. There is no difference in the definition of the complex variables however as I refer to the definition in a common file.
AddString(cmd) could only work if cmd is actually an instance of ExposedVariables. There's no overload of just
public void AddString(Command cmd)
which is what it looks like you're expecting.
This has nothing to do with complex variable types - it has everything to do with you trying to call a method which doesn't exist. Which overload did you expect to be called, out of the ones you presented to us?
If the cmd variable in your example is a reference to a Command instance rather than an ExposedVariablesinstance, then the call is wrong. You do not have an AddString overload that takes a Command only.
try
ExposedVariables exv = new ExposedVariables();
classInstance.AddString(cmd, exv);
as you don't seem to have an overload that takes just cmd.