Search code examples
c#powershellpowershell-modulepowershell-sdk

PowerShell Object returns null


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

Solution

  • 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.