1

这是此问题的延续,我创建了一个 PowerShell 命令,并且能够在 PowerShell 窗口中调用该命令,但是当尝试从 C# 方法调用时,由于无法识别 cmdlet,因此出现错误,我尝试使用其他现有命令并得到相同的错误,因此我怀疑导入模块存在问题,尽管我没有在流中得到该错误。错误。我得到的唯一错误是“Get-RowAndPartitionKey 不是可识别的 cmndlt,请检查拼写.....”。

想知道是否有任何其他方法,我应该尝试一下,或者我是否可以在这里调试更多,看看我的模块是否获取所有命令。现在我不知道如何解决这个问题。

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

下面的方法不会在 Streams.Error 或 Verbose 中返回任何错误,但也没有输出:

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

脚本内容

 "\"$path = 'C:\\Users...\\.TabularData.PowerShell.dll'\\r\\nImport-Module $path\\r\\nGet-RowAndPartitionKeys\""
    
4

1 回答 1

0

以下是使用按需编译 C# 代码的自包含PowerShell示例代码:

  • 它表明该方法原则上有效,如您对原始问题的回答中所述。

    • 先决条件:调用自定义 cmdlet 的 C# 项目中使用的PowerShell SDK 包Get-RowAndPartitionKey"和 .NET 运行时必须与用于编译包含该 cmdlet 的程序集 DLL 的 PowerShell SDK 和 .NET 运行时兼容,以通过Import-Module.

    • 下面的示例代码通过直接从 PowerShell 运行,使用 cmdlet 按需编译 C# 代码,确保隐式地Add-Type在 Windows PowerShell 以及 PowerShell (Core) 7+ 中工作。

      • 在实践中,我发现 .NET Framework 编译的 DLL(来自 Windows PowerShell)也适用于 PowerShell (Core) (.NET (Core) 5.0),但反之则不然。
  • 显示了故障排除技术,即:

    • -Verbose开关添加到Import-Module调用以生成详细输出,其中列出了从给定模块 (DLL) 导入的命令。
    • 打印这些详细消息(查找// --- TROUBLESHOOTING CODE
    • 打印发生的任何非终止 PowerShell 错误(与您必须在 C# 代码中处理的异常相反)。
# 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
}

在我的 Windows 10 机器上,无论是来自 PowerShell (Core) 7.0.5 还是 Windows PowerShell 5.1,上面的结果(省略了清理说明)如下,表明一切都按预期工作:

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

具体来说,lineVERBOSE: Importing cmdlet 'Get-RowAndPartitionKey'.表示自定义 cmdlet 已成功导入到会话中。

于 2021-02-20T16:37:23.770 回答