将 GUI 命令导出到 PowerShell 运行空间?

Export GUI commands to PowerShell runspace?

在托管 PowerShell 运行空间的 .NET 应用程序中,是否可以将主机应用程序函数导出到该运行空间,以便我拥有,比如说,对我的 UI 产生影响的 PowerShell cmdlet?

我的目标只是能够从一个长 运行 PowerShell 脚本向我的主机应用程序 UI 报告进度消息(因为托管 PowerShell 我没有控制台 window.) 我在想象这样的事情:

C#:

// Speculative pseudocode:

class ReportProgressCmdlet : Cmdlet { ... } // Reports a message on the UI.
...
myRunspace.Register(new ReportProgressCmdlet(myUI)); 

PowerShell:

Report-Progress "Starting step 1." # UI shows "Starting step 1."
...
Report-Progress "Step 1 failed."

这可能吗?如果我做错了,很高兴听到不同的解决方案(事件?)我已经阅读了以下所有问题以及许多其他类似的问题,但我认为它们不是我要找的:

How to expose functionality to PowerShell from within your application

Powershell Call Assembly Delegate

How to programmatically add a PSCmdlet to a Powershell pipeline?

Provide a .NET method as a delegate callback

UPDATE:我在下面找到的技术适用于任意 GUI 操作,但就我的目的而言(以清理格式显示 PowerShell 输出)结果是更容易实现自定义 PSHost、PSHostUserInterface 和 PSHostRawUserInterface,从而使脚本编写端保持更简单(您使用熟悉的 Write-Output、Write-Host、Write-Error 等,而不是一些定制输出 API。这也使脚本保持灵活性,以防您也想 运行 它在您的主机之外)。请参阅 https://msdn.microsoft.com/en-us/library/ee706570%28v=vs.85%29.aspx 了解一个很好的例子。


Cmdlet 被证明是错误的方法 — 方法是通过 "SessionState" APIs 将 GUI 对象实例公开给 PowerShell,然后 PowerShell 代码可以直接使用它们。操作方法如下:

引入正确的命名空间:

using System.Management.Automation;
using System.Management.Automation.Runspaces;

然后创建一个运行空间并将您的 GUI 对象实例作为 PowerShell 变量注入。 (这里我传递了一个公开消息集合的视图模型,但您可能希望在现实世界中更好地分层。)您可以在创建 运行 空间时使用 InitialSessionState,例如:

var iss = InitialSessionState.CreateDefault();
iss.Variables.Add(new SessionStateVariableEntry("thing", this.DataContext, "it's the data context"));

using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
{
    runspace.ThreadOptions = PSThreadOptions.UseCurrentThread;
    runspace.Open();

    using (PowerShell ps = PowerShell.Create())
    {
        ps.Runspace = runspace;
        ps.AddScript(@"$thing.Messages.Add('in which PowerShell can make stuff happen on the GUI via InitialSessionState')");
        ps.Invoke();
    }

    runspace.Close();
}

或者您可以在创建并打开 Runspace 后使用 Runspace.SessionStateProxy.SetVariable(name, object) 注入对象实例。那个代码是一样的,只是你不必注入 InitialSessionState。

Doug Finke 归功于此,因为我是从他的截屏视频中学到这项技术的,我发现:

http://www.dougfinke.com/blog/index.php/2009/09/02/how-to-host-powershell-in-a-wpf-application/

我在 WPF + XAML 中制作了一个进度条,并 运行 在不同的运行空间中设置了它。此代码可能正是您所需要的。

https://gallery.technet.microsoft.com/Script-New-ProgressBar-329abbfd

这是一个示例,说明如何使用我制作的函数 运行 一个循环,更新新的运行空间。并检测 window/progressbar 是否已关闭,并停止执行您的代码。

# Create the window form in a different namespace 
Load-Window 

For ($i = 0; $i -lt 100; $i++) { 

  #Update the progressbar 
  $Result = Update-ProgressBar -Percent $i -Label1Text "$i / 100" 

  # If you want to simply check if the window is still active
  # Update-Window will return if the form/window is active 
  # $Result = Update-Window 

  #If the window is close, stop the loop 
  If ($Result -eq $False) { Break } 

  Sleep -Milliseconds 100 

} 

Close-Window