无法检索通过 C# 执行的 WSFC FailoverCluster Powershell 模块的结果

Unable to retrieve results of WSFC FailoverCluster Powershell Module executed via C#

由于没有集成方法或 API 来访问 WindowsServerFailoverCluster 功能,我正在尝试启动一些 PowerShell cmdlet 来确定 WSFC 的状态并将其return 发送到 C#(健康检查目的)

这是一个冒烟测试,包含 PowerShell 的控制台输出和我的对象,但不幸的是它是一个扁平字符串。

选项 1:

System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo();
processInfo.FileName = @"powershell.exe";
processInfo.Arguments = @"& {Get-ClusterResource -Name '"+ _wsfcResourceName +"'}";
processInfo.Verb = "runas";
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
processInfo.UseShellExecute = false;
processInfo.CreateNoWindow = true;

System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = processInfo;
process.Start();

var stdout = process.StandardOutput.ReadToEnd();
var stderror = process.StandardError.ReadToEnd();

一旦我尝试这样的事情:

System.Management.Automation.Runspaces.InitialSessionState initialSessionState = System.Management.Automation.Runspaces.InitialSessionState.CreateDefault();
initialSessionState.ImportPSModule(new[] { "FailoverClusters"});
initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
using System.Management.Automation.Runspaces.Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(initialSessionState);
runspace.Open();
using (PowerShell PowerShellInst = PowerShell.Create(runspace))
{
    PowerShellInst.AddCommand($@"Get-ClusterResource");
    PowerShellInst.AddParameter("Name", _wsfcResourceName);
    
    //Same result using these
    //PowerShellInst.AddScript($@"Get-ClusterResource -Name '{_wsfcResourceName}'");
    //PowerShellInst.AddScript($@"Import-Module FailoverClusters; Get-ClusterResource -Name '{_wsfcResourceName}'");

    Collection<PSObject> PSOutput = PowerShellInst.Invoke();
    var rsrcResults = PSOutput.ToList();
... etc

...应用从 PowerShell 中抛出异常

The 'Get-ClusterResource' command was found in the module 'FailoverClusters', but the module could not be loaded. Fore more information, run 'Import-Module FailoverClusters'.

选项 1 起作用时让我感到困惑(这是来自 IIS Web 应用程序的 运行)。

我尝试过的事情:

  1. 只传"Import-Module FailoverClusters -Force -Verbose"看有没有错误。它没有。
  2. 在 Runspaces.InitialSessionState
  3. 中指定 initialSessionState.ImportPSModule(new[] { "FailoverClusters"});
  4. 在处理此方法的 class 库中指定 32 位
  5. ... 多种不同的写法。

有人可以提供一些建议吗?

更新@Cpt.Whale的回答:

代码更改为:

PowerShellInst.Commands.AddCommand("Import-Module").AddArgument(@"FailoverClusters");
                        PowerShellInst.Commands.AddParameter("SkipEditionCheck");
                        PowerShellInst.Commands.AddParameter("Force");
                        PowerShellInst.Commands.AddParameter("Verbose");

当我现在打电话时:Collection<PSObject> PSOutput = PowerShellInst.Invoke();

我第一次遇到异常:

System.Management.Automation.CmdletInvocationException: Could not load type 'System.Diagnostics.Eventing.EventDescriptor' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
---> System.TypeLoadException: Could not load type 'System.Diagnostics.Eventing.EventDescriptor' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
 at Microsoft.FailoverClusters.UI.Common.TraceWriterEtw.TraceCmdletEnter(String cmdletName, String invocation, String parameters, UInt32 sequence, String source, String tag)
 at Microsoft.FailoverClusters.UI.Common.TraceCmdlet.Starting()
 at Microsoft.FailoverClusters.UI.Common.TraceSampleBase.Start() 
at Microsoft.FailoverClusters.UI.Common.ClusterLog.StartTraceCmdlet(TraceSource source, String cmdletName, String invocation, String parameters) at Microsoft.FailoverClusters.PowerShell.FCCmdlet.ProcessRecord()
 at Microsoft.FailoverClusters.PowerShell.ClusterPipeCmdlet.ProcessRecord() at System.Management.Automation.CommandProcessor.ProcessRecord() --- End of inner exception stack trace --- at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input) at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke)
 at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync) at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
 at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings) at System.Management.Automation.PowerShell.Invoke(IEnumerable input, PSInvocationSettings settings)

还不确定这意味着什么...

编辑#2

"Import-Module errors out when it attempts to load the module 
with an error message stating that Import-Module was not able to load the 
.NET type System.Diagnostics.Eventing.EventDescriptor. Since the EventDescriptor 
type is not available in .NET Core, you cannot use the ServerManager module natively. 
The only alternatives are for the Server Manager team to update their code, 
or for you to run the  cmdlet in Windows PowerShell (either explicitly or via the compatibility mechanism)" 

我相信这可以解释为什么“选项 1”有效。这让我回到了起点。有没有办法从 process.StandardOutput.ReadToEnd() 中制作某种对象?

我能弄清楚如何完成我想要的唯一方法是依靠我的 PSOBject 只有少量对象这一事实,我确保我可以安全地假设它们没有 space在他们里面。

然后我调用 StartProcess() 以我的 cmdlet 作为参数启动 powershell,但将它传送到“Format-List -HideTableHeaders”,然后使用 space 作为分隔符对其进行解析。

public async Task<string> GetClusterResource()
{
  try
   {
    System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo();
    processInfo.FileName = @"powershell.exe";

    processInfo.Arguments = @"& {Get-ClusterResource -Name '" + _wsfcResourceName + "' | Select Name, State, OwnerGroup | ft -HideTableHeaders}";
                    
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;
    processInfo.UseShellExecute = false;
    processInfo.CreateNoWindow = true;


    System.Diagnostics.Process process = new System.Diagnostics.Process();
    process.StartInfo = processInfo;
    process.Start();

    var results = await process.StandardOutput.ReadToEndAsync();
    var errors = await process.StandardError.ReadToEndAsync();

    return results;
   }
   catch (Exception ex)
   {
     throw;
   }
}
var CoreClusterResults = await GetClusterResource();

CoreClusterResults = CleanStringOfNewlineStrings(CoreClusterResults); //this removes any occurences of /r/n, /r or /n

var ParamsSplit = CoreClusterResults.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);

if (ParamsSplit.Any())
{
   var clusterParamName = ParamsSplit[0];
   var clusterParamValue = ParamsSplit[1];

     ...
}

感谢@Cpt 的指导