Write PowerShell Output (as it happens) to WPF UI Control
Solution 1
Out of curiosity, have you considered that instead of PowerShell firing up WPF and displaying some output, it should be the other way around?
Perhaps you should be invoking PowerShell from within a WPF application, and capturing the output to be displayed?
http://www.codeproject.com/Articles/18229/How-to-run-PowerShell-scripts-from-C
Solution 2
I ran into the same question and seen quiet some suggestions that either refer to C# classes or examples where the UI is placed in the worker instead of the actual worker task. Anyway, after spending quite some time I figured it out:
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()
For a windows control example see: PowerShell: Job Event Action with Form not executed
jmcdade
Updated on June 04, 2022Comments
-
jmcdade almost 2 years
I've been reading blogs about writing to the UI from different runspaces (http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/).
I'm basically trying to make it so I can click a button in the UI and run a PowerShell script and capture the output of that script as it happens and update the WPF UI control without freezing up the UI.
I've tried a basic example of just writing some output directly, but it seems to hang the UI. I'm using runspaces and dispatcher, but I seem to be stuck on something.
Any ideas?
Thanks.
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") } })#>
-
jmcdade about 10 yearsYeah, I wanted to see if I could do it this way out of curiosity. I'm not really tied to one way or the other.
-
jmcdade about 10 yearsYeah, I found that out the hard way. When I try to use that block in the same script as the rest of it, I can't access the ui elements but it works via the console as the ui is running. Is that expected? In any case, is there a better way to do it performance wise, or should I flip the order as suggested by Doug?
-
boeprox about 10 yearsIt really depends on your own personal preference. Some would say that you should write UIs in C# instead of PowerShell (much like Doug has suggested), but I prefer to write the in PowerShell and just make sure I am careful in my coding to watch out for any issues running commands against the UI. If your confident in your C# coding, then you can give it a shot, otherwise just keep moving forward with your current path.
-
jmcdade about 10 yearsCould the UI thread handle the continuous pipeline output, or would it be pretty held up? I was in envisioning having script output in a text box so the user could keep an eye out, but still do other things with the UI. If the output from the script is just going to hog everything, I'll need a new approach.
-
jmcdade about 10 yearsTrying to fit a WPF solution based on that approach was a giant headache. For my purposes I'm just going to host the PowerShell scripts within WinForms and be done with it.