Search code examples
c#powershellpowershell-modulepowershell-sdk

Whats the correct way to get output for this Powershell in C#


I am not getting any error below but I am also not getting the output. Below is the Powershell cmd and the C# method which is calling it. I would like to know if it is written correctly and how can I get the output as coming from powershell. It works fine when I run from PowerShell window

Pwsh cmd:

public class GetRowAndPartitionKeys : Cmdlet
    {
        [Parameter(Mandatory = false)]
        public List<string> Properties { get; set; } = new List<string>();
    }

    [Cmdlet( VerbsCommon.Get, "RowAndPartitionKeys" )]
    public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKeys

    {
        protected override void ProcessRecord()
        {            
            WriteObject ("Hi");
        }
    }

}

C# method:

 public async Task<IEnumerable<object>> RunScript(  )
        {
            // create a new hosted PowerShell instance using the default runspace.
            // wrap in a using statement to ensure resources are cleaned up.
           string scriptContents = "Import-Module 'C:\Users\...\Powershell.dll";
            using( PowerShell ps = PowerShell.Create() )
            {
                // specify the script code to run.
            ps.AddScript( scriptContents ).AddCommand( "Get-RowAndPartitionKeys" );


            // execute the script and await the result.
            var pipelineObjects = await ps.InvokeAsync().ConfigureAwait( false );
           
            foreach( var item in pipelineObjects )
            {
                Console.WriteLine( item.BaseObject.ToString() );
            }
            return pipelineObjects;
        }

Solution

  • Similar to the answer to your previous question, the following self-contained sample code demonstrates that the approach works in principle, after correcting the following problems in your code:

    • An .AddStatement() call is missing between the .AddScript() and the .AddCommand() call; this is necessary for the (script block-based) Import-Module call and the Get-RowAndPartitionKeys call to be treated as separate statements.

    • Pseudo-code line string scriptContents = "Import-Module 'C:\Users\...\Powershell.dll"; is missing a closing ' (possibly just an artifact of posting here).

    • Also, troubleshooting code is added below.

    While the orchestration code is in PowerShell, actual C# projects, compiled via the .NET SDK are used, in combination with version 7.1.2 of the PowerShell (Core) SDK package, Microsoft.PowerShell.SDK.

    After running the code, which creates and runs the test projects, you can inspect and experiment with them yourself (./module is the project for the module DLL that defines the Get-RowAndPartitionKeys cmdlet, ./app is the project for the application that calls it):

    $tmpDir = (New-Item -Force -Type Directory (Join-Path temp: $PID)).FullName
    
    $tmpModuleDir = (New-Item -Force -Type Directory (Join-Path $tmpDir module)).FullName
    $tmpAppDir = (New-Item -Force -Type Directory (Join-Path $tmpDir app)).FullName
    $tmpPublishDir = (New-Item -Force -Type Directory (Join-Path $tmpDir publish)).FullName
    
    $tmpModuleDll = Join-Path $tmpPublishDir module.dll
    
    Push-Location
    
    # ---
    
    Write-Verbose -vb "Creating module DLL with sample cmdlet..."
    
    Set-Location $tmpModuleDir
    
    dotnet new classlib --force >$null || $(exit $LASTEXITCODE)
    
    dotnet add package Microsoft.PowerShell.SDK >$null || $(exit $LASTEXITCODE)
    
    @'
    using System;
    using System.Collections.Generic;
    using System.Management.Automation;
    
    namespace demo {
        public class GetRowAndPartitionKeys : Cmdlet 
        {
          public List<string> Properties { get; set; }
        }
        
        [Cmdlet( VerbsCommon.Get, "RowAndPartitionKeys" )]
        public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKeys
        {
          protected override void ProcessRecord()
          {
              WriteObject ("Hi");
          }
        }
    }
    '@ | Set-Content Class1.cs
    
    dotnet publish -o $tmpPublishDir >$null || $(exit $LASTEXITCODE)
    
    # ---
    
    Write-Verbose -vb "Creating console application that imports the module DLL and calls the sample cmdlet..."
    Set-Location $tmpAppDir
    
    dotnet new console --force >$null  || $(exit $LASTEXITCODE)
    
    dotnet add package Microsoft.PowerShell.SDK >$null || $(exit $LASTEXITCODE)
    
    @"
    using System;
    using System.Collections.Generic;
    using System.Management.Automation;
    using System.Threading.Tasks;
    
    namespace demo {
    
        public static class App {
            static void Main(string[] args)
            {
                var unused = new Foo().RunScript().Result;
            }
        }    
    
        public class Foo {
            public async Task<IEnumerable<object>> RunScript()
            {
                string scriptContents = @"Import-Module -Verbose ""$tmpModuleDll""";
                using(PowerShell ps = PowerShell.Create())
                {
                    ps.AddScript(scriptContents).AddStatement().AddCommand("Get-RowAndPartitionKeys");
                    var pipelineObjects = await ps.InvokeAsync().ConfigureAwait( false );
                    
                    // --- TROUBLESHOOTING CODE
    
                    // Print verbose output from the Import-Module call
                    foreach (var v in ps.Streams.Verbose) { Console.WriteLine("VERBOSE: " + v.ToString()); }
            
                    // Print any errors.
                    foreach (var e in ps.Streams.Error) { Console.WriteLine("ERROR: " + e.ToString()); }
            
                    // ---                
    
                    foreach (var item in pipelineObjects)
                    {
                        Console.WriteLine(item.BaseObject.ToString());
                    }
    
                    return pipelineObjects;
                }
            }
        }
    }
    "@ | Set-Content Program.cs
    
    # ---
    
    Write-Verbose -vb "Compiling and invoking the console application..."
    dotnet run 
    
    Pop-Location
    
    Write-Verbose -vb @"
    The test projects are located in $tmpDir.
    To clean up, run:
    
       Remove-Item "$tmpdir" -Recurse
     
    "@
    

    On my Windows 10 machine (running from PowerShell 7.1.2), I get:

    results

    As you can see:

    • the verbose output confirms that the cmdlet was properly imported
    • the Hi shows that the cmdlet was successfully called.