1

This is in continuation to this Question here, I have a PowerShell command which I have created and am able to call the command in a PowerShell window, but when trying to call from C# method, I am getting error as the cmdlet is not recognized, I tried with other existing commands and get same error, so I suspect issue in Importing the Module, though I don't get that error in streams. Error. The only error I get is "Get-RowAndPartitionKey is not a recognized cmndlt, please check the spelling.....".

Would like to know if there is any other way, I should try it or if I can debug more here to see if my Module fetches all command or not. right now I am clueless how to fix this.

 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() )
                       
        {
            IAsyncResult async =
             ps.AddCommand( "Import-Module" ).AddArgument( @"C:\Users\...\.D.PowerShell.dll" )
               .AddStatement()
               .AddCommand( "Get-RowAndPartitionKey" ).AddParameter( "Properties", "test" )
               .BeginInvoke();

            StringBuilder stringBuilder = new StringBuilder();
            foreach( PSObject result in ps.EndInvoke( async ) )
            {
                stringBuilder.AppendLine( result.ToString() );
            }
            return stringBuilder.ToString();
        }
    }
}

Below method do not return any error in Streams.Error or Verbose but no output also:

public async Task<IEnumerable<object>> RunScript( string scriptContents, List<string> scriptParameters )
        {
            // create a new hosted PowerShell instance using the default runspace.
            // wrap in a using statement to ensure resources are cleaned up.

            using( PowerShell ps = PowerShell.Create() )
            {
                // specify the script code to run.
                ps.AddScript( scriptContents );

                // specify the parameters to pass into the script.
                ps.AddParameter( "Properties" ,"test") ;

                // execute the script and await the result.
                var pipelineObjects = await ps.InvokeAsync().ConfigureAwait( false );                
                return pipelineObjects;
            }
        }

scriptContent

 "\"$path = 'C:\\Users...\\.TabularData.PowerShell.dll'\\r\\nImport-Module $path\\r\\nGet-RowAndPartitionKeys\""
    
2
  • As for the second snippet: For the parameters to be passed to Get-RowAndPartitionKey (note that you've used the singular form - no s at the the end - in the first snippet, so I'm using that here too, along with a verbatim string so you don't have to escape " chars.), you must pass it @args: ps.AddScript(@"Import-Module ""C:\Users...\.TabularData.PowerShell.dll""; Get-RowAndPartitionKey @args") Commented Feb 22, 2021 at 14:10
  • However, there is no benefit to bringing further variables into the mix: if you can't get your original snippet to work, the second one won't work either. Commented Feb 22, 2021 at 14:12

1 Answer 1

0

The following is self-contained PowerShell sample code that uses on-demand compilation of C# code:

  • It shows that the approach works in principle, as described in this answer to your original question.

    • Prerequisites: The PowerShell SDK package and .NET runtime used in the C# project that calls your custom Get-RowAndPartitionKey" cmdlet must be compatible with the PowerShell SDK and .NET runtime that you used to compile the assembly DLL that houses that cmdlet, to be imported via Import-Module.

    • The sample code below ensures that implicitly, by running directly from PowerShell, using the Add-Type cmdlet to compile C# code on demand - it works in Windows PowerShell as well as in PowerShell (Core) 7+.

      • In practice I've found that a .NET Framework-compiled DLL (from Windows PowerShell) also works in PowerShell (Core) (.NET (Core) 5.0), but not vice versa.
  • It shows troubleshooting techniques, namely:

    • Adding the -Verbose switch to the Import-Module call to produce verbose output that lists the commands being imported from the given module (DLL).
    • Printing these verbose messages (look for // --- TROUBLESHOOTING CODE)
    • Printing any non-terminating PowerShell errors that occurred (as opposed to exceptions that you'd have to handle in C# code).
# Create a (temporary) assembly containing cmdlet "Get-RowAndPartitionKey".
# This assembly can directly be imported as a module from PowerShell.
# The cmdlet simply outputs "Hi from Get-RowAndPartitionKey" and
# echoes the elements of the list passed to -Properties, one by one.
$tempModuleDll = Join-Path ([IO.Path]::GetTempPath()) "TempModule_$PID.dll"
Remove-Item -ErrorAction Ignore $tempModuleDll
Add-Type @'
  using System.Management.Automation;
  using System.Collections.Generic;
  [Cmdlet("Get", "RowAndPartitionKey")]
  public class GetRowAndPartitionKeyCmdlet : PSCmdlet {
    [Parameter] public List<string> Properties { get; set; }
    protected override void ProcessRecord() {
      WriteObject("Hi from Get-RowAndPartitionKey: ");
      WriteObject(Properties, true);
    }
  }
'@ -ErrorAction Stop -OutputAssembly $tempModuleDll

# Compile a C# class ad hoc to simulate your project, and call its static
# method, which imports the module and effectively calls 
#   Get-RowAndPartitionKey -Properties "foo", "bar"
(Add-Type @"
  using System;
  using System.Management.Automation;
  using System.Collections.Generic;
  using System.Text;

  public static class Foo {
    public static string RunScript(List<string> parameterList)
    {
      using (System.Management.Automation.PowerShell ps = PowerShell.Create())
      {
        IAsyncResult async =
          // Add -Verbose to the Import-Module call, so that the list of 
          // commands being imported is written to the verbose output stream.
          ps.AddCommand("Import-Module").AddArgument(@"$tempModuleDll").AddParameter("Verbose", true)
            .AddStatement()
            .AddCommand("Get-RowAndPartitionKey").AddParameter("Properties", parameterList)
            .BeginInvoke();

        StringBuilder stringBuilder = new StringBuilder();
        foreach (PSObject result in ps.EndInvoke(async))
        {
          stringBuilder.AppendLine(result.ToString());
        }
        
        // --- 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()); }

        // ---

        return stringBuilder.ToString();
      }
    }
  }
"@ -ErrorAction Stop -PassThru)::RunScript(("foo", "bar"))

# Clean-up instructions:
if ($env:OS -eq 'Windows_NT') {
  Write-Verbose -vb "NOTE: Re-running this code requires you to start a NEW SESSION."
  Write-Verbose -vb "After exiting this session, you can delete the temporary module DLL(s) with:`n`n  Remove-Item $($tempModuleDll -replace '_.+', '_*.dll')`n "
} else {
  Write-Verbose -vb "NOTE: Re-running this code after modifying the embedded C# code requires you to start a NEW SESSION."
  Remove-Item $tempModuleDll
}

On my Windows 10 machine, both from PowerShell (Core) 7.0.5 and Windows PowerShell 5.1, the above yields (clean-up instructions omitted) the following, showing that everything worked as intended:

VERBOSE: Loading module from path 'C:\Users\jdoe\AppData\Local\Temp\TempModule_11876.dll'.
VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'.
Hi from Get-RowAndPartitionKey:
foo
bar

Specifically, line VERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'. indicates that the custom cmdlet was successfully imported into the session.

Sign up to request clarification or add additional context in comments.

3 Comments

Are these two library package have different roles: PowerShellStandard.Library and Microsft. Powershell.SDK. I see in my earlier there was PowerShellStandardLibrary 5.1.1 available but now its not there in nuget package manager
@ZZZSharePoint, yes, the former comprises just reference assemblies that at runtime rely on a regular PowerShell installation; the latter comprises the assemblies for fully hosting PowerShell (Core) as part of the application. As stated, before use one or the other: see this answer (also linked to from the answer).
Also I posted another Question, making the command as simple as possible ..maybe you can look at this stackoverflow.com/questions/66313904/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.