将PowerShell输出(碰巧)写入WPF UI控件

将PowerShell输出(碰巧)写入WPF UI控件,wpf,powershell,dispatcher,runspace,Wpf,Powershell,Dispatcher,Runspace,我一直在读关于从不同的运行空间()写入UI的博客 我基本上是在尝试创建它,这样我就可以在UI中单击一个按钮并运行PowerShell脚本,在脚本发生时捕获该脚本的输出,并在不冻结UI的情况下更新WPF UI控件 我尝试了一个直接编写一些输出的基本示例,但它似乎挂起了UI。我正在使用运行空间和调度程序,但我似乎被某些东西卡住了 有什么想法吗 谢谢 Add-Type –assemblyName PresentationFramework Add-Type –assemblyName Presenta

我一直在读关于从不同的运行空间()写入UI的博客

我基本上是在尝试创建它,这样我就可以在UI中单击一个按钮并运行PowerShell脚本,在脚本发生时捕获该脚本的输出,并在不冻结UI的情况下更新WPF UI控件

我尝试了一个直接编写一些输出的基本示例,但它似乎挂起了UI。我正在使用运行空间和调度程序,但我似乎被某些东西卡住了

有什么想法吗

谢谢

Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase


$uiHash = [hashtable]::Synchronized(@{})
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open() 
$newRunspace.SessionStateProxy.SetVariable('uiHash',$uiHash)

$psCmd = [PowerShell]::Create().AddScript({
[xml]$xaml = @"
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="Window" Title="Patcher" Height="350" Width="525" Topmost="True">
    <Grid>
        <Label Content="A Builds" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="88" RenderTransformOrigin="0.191,0.566"/>
        <ListBox HorizontalAlignment="Left" Height="269" Margin="10,41,0,0" VerticalAlignment="Top" Width="88"/>
        <Label Content="New Build" HorizontalAlignment="Left" Margin="387,10,0,0" VerticalAlignment="Top"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="387,41,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
        <Label Content="B Builds" HorizontalAlignment="Left" Margin="117,10,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.528,-0.672"/>
        <ListBox HorizontalAlignment="Left" Height="269" Margin="103,41,0,0" VerticalAlignment="Top" Width="88"/>
        <Label Content="C Builds" HorizontalAlignment="Left" Margin="185,10,0,0" VerticalAlignment="Top"/>
        <ListBox HorizontalAlignment="Left" Height="269" Margin="196,41,0,0" VerticalAlignment="Top" Width="88"/>
        <Button x:Name="PatchButton" Content="Patch!" HorizontalAlignment="Left" Margin="426,268,0,0" VerticalAlignment="Top" Width="75"/>
        <RichTextBox x:Name="OutputTextBox" HorizontalAlignment="Left" Height="194" Margin="289,69,0,0" VerticalAlignment="Top" Width="218">
            <FlowDocument>
                <Paragraph>
                    <Run Text=""/>
                </Paragraph>
            </FlowDocument>
        </RichTextBox>

    </Grid>
</Window>
"@

$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$uiHash.Window = [Windows.Markup.XamlReader]::Load($reader)

$uiHash.Button = $uiHash.Window.FindName("PatchButton")
$uiHash.OutputTextBox = $uiHash.Window.FindName("OutputTextBox")

$uiHash.OutputTextBox.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler]    {$uiHash.OutputTextBox.UpdateLayout()}, $null, $null)

$uiHash.Window.ShowDialog() | Out-Null
})
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()

# The next line fails (null-valued expression)
<#
$uiHash.OutputTextBox.Dispatcher.Invoke("Normal", [action]{
    for ($i = 0; $i -lt 10000; $++) {
        $uiHash.OutputTextBox.AppendText("hi")
    }
})#>
添加类型–assemblyName PresentationFramework
添加类型–assemblyName PresentationCore
添加类型–assemblyName WindowsBase
$uiHash=[hashtable]::已同步(@{})
$newRunspace.ApartmentState=“STA”
$newRunspace.ThreadOptions=“ReuseThread”
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable('uiHash',$uiHash)
$psCmd=[PowerShell]::Create().AddScript({
[xml]$xaml=@”
"@
$reader=(新对象System.Xml.XmlNodeReader$xaml)
$uiHash.Window=[Windows.Markup.XamlReader]::加载($reader)
$uiHash.Button=$uiHash.Window.FindName(“补丁按钮”)
$uiHash.OutputTextBox=$uiHash.Window.FindName(“OutputTextBox”)
$uiHash.OutputTextBox.Dispatcher.Invoke(“呈现”,[Windows.Input.InputEventHandler]{$uiHash.OutputTextBox.UpdateLayout()},$null,$null)
$uiHash.Window.ShowDialog()| Out Null
})
$psCmd.Runspace=$newRunspace
$data=$psCmd.BeginInvoke()
#下一行失败(空值表达式)

出于好奇,您是否考虑过,与其启动PowerShellWPF并显示一些输出,不如反过来

也许您应该从WPF应用程序中调用PowerShell,并捕获要显示的输出


我对你的代码做了一些调整。我不会在调度程序中运行For循环,而是在For循环中运行dispatcher块。我进行了测试,但它似乎没有为我锁定(有一些时候,性能不是最好的,但这是意料之中的),我一直在向窗口写信

for ($i = 0; $i -lt 10000; $i++) {
    $uiHash.OutputTextBox.Dispatcher.Invoke("Normal", [action]{
        $uiHash.OutputTextBox.AppendText("hi")
    })
}

这可能有点晚,但请尝试下载,它为WPF和Win Forms GUI脚本提供了Powershell backgroundworker cmdlet,这些脚本将在后台执行,并在收到输出或完成时更新您的GUI。

我遇到了相同的问题,并看到了一些建议,这些建议要么引用C类,要么引用将UI放置在worker中而不是实际的工人任务。 不管怎么说,我花了很长时间才明白:

Add-Type -AssemblyName PresentationFramework
$Xaml = New-Object System.Xml.XmlNodeReader([XML]@"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test" WindowStartupLocation = "CenterScreen" ShowInTaskbar = "True">
    <Grid>
        <TextBox x:Name = "TextBox"/>
    </Grid>
</Window>
"@)

Function Start-Worker {
    $TextBox = $Window.FindName("TextBox")
    $SyncHash = [hashtable]::Synchronized(@{Window = $Window; TextBox = $TextBox})
    $Runspace = [runspacefactory]::CreateRunspace()
    $Runspace.ThreadOptions = "ReuseThread"         
    $Runspace.Open()
    $Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)          
    $Worker = [PowerShell]::Create().AddScript({
        for($Progress=1; $Progress -le 10; $Progress++){
            $SyncHash.Window.Dispatcher.Invoke([action]{$SyncHash.TextBox.AppendText($Progress)})
            Start-Sleep 1                                                       # Some background work
        }
    })
    $Worker.Runspace = $Runspace
    $Worker.BeginInvoke()
}

$Window = [Windows.Markup.XamlReader]::Load($Xaml)                              # Requires -STA mode
$Window.Add_Loaded({Start-Worker})
[Void]$Window.ShowDialog()
添加类型-AssemblyName PresentationFramework
$Xaml=New Object System.Xml.XmlNodeReader([Xml]@”
"@)
函数启动工作程序{
$TextBox=$Window.FindName(“TextBox”)
$SyncHash=[hashtable]::已同步(@{Window=$Window;TextBox=$TextBox})
$Runspace=[runspacefactory]::CreateRunspace()
$Runspace.ThreadOptions=“ReuseThread”
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable(“SyncHash”,$SyncHash)
$Worker=[PowerShell]::Create().AddScript({
对于($Progress=1;$Progress-le 10;$Progress++){
$SyncHash.Window.Dispatcher.Invoke([action]{$SyncHash.TextBox.AppendText($Progress)})
开始睡眠1#一些背景工作
}
})
$Worker.Runspace=$Runspace
$Worker.BeginInvoke()
}
$Window=[Windows.Markup.XamlReader]::Load($Xaml)#Requires-STA模式
$Window.Add_已加载({Start Worker})
[Void]$Window.ShowDialog()

有关windows控件的示例,请参见:

是的,我想看看是否出于好奇可以这样做。我并不是真的被这种或那种方法束缚住了。试图适应基于这种方法的WPF解决方案是一件非常头痛的事。出于我的目的,我将在WinForms中托管PowerShell脚本并完成它。是的,我很难发现这一点。当我尝试在与其余部分相同的脚本中使用该块时,我无法访问ui元素,但它在ui运行时通过控制台工作。这是预期的吗?在任何情况下,有没有更好的方法来提高性能,或者我应该按照Doug的建议改变顺序?这取决于你自己的个人偏好。有人会说应该用C#而不是PowerShell来编写UI(就像Doug建议的那样),但我更喜欢在PowerShell中编写UI,只需确保在编写代码时小心,以防针对UI运行命令时出现任何问题。如果您对C#编码有信心,那么您可以尝试一下,否则只需继续沿着当前路径前进。UI线程可以处理连续的管道输出吗,还是会很困难?我设想将脚本输出放在文本框中,这样用户就可以保持警惕,但仍然可以使用UI做其他事情。如果脚本的输出将占据一切,我需要一种新的方法。