WPF - Keyboard and mouse click at the same time - powershell

is it possible in PowerShell and WPF to trigger an event out of a keyboard click and a mouse click?
I have for example following button in my WPF:
<Button Name="button_test" Content="Test" Grid.Column="0" Grid.Row="4" />
I would like to trigger to two events with it. One event with a standard mouse click:
$button_test.Add_Click({ #do something })
And something when I hold CTRL and do a mouse click on the same button.
A keyboard click I do like this
$window.add_KeyDown{
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
if($e.Key -eq 'CTRL') { #do something else}
}
But I don’t know how to combine these two events. Is it even possible?
Thanks
Stephan

sorry for the late respond. I "solved" it with an extra variable, but I am not sure if this is the correct way, so any suggestion would be appreciated.
$title = "test"
Add-Type -AssemblyName PresentationFramework
# GUI
[xml]$xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window_GuiManagement" Title="$title" WindowStartupLocation = "CenterScreen"
Width = "Auto" Height = "350.000" Visibility="Visible" WindowStyle="ToolWindow" ResizeMode="NoResize" SizeToContent="WidthAndHeight" >
<Grid>
<DockPanel Margin="5">
<StackPanel DockPanel.Dock="Bottom" >
<Button Name="button_1" Content="Button 1" />
<Button Name="button_2" Content="Button 2" />
</StackPanel>
</DockPanel>
</Grid>
</Window>
"#
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
# Declare objects
$button_1 = $window.FindName("button_1")
$button_2 = $window.FindName("button_2")
$global:ctrl = $false
$button_1.Add_Click({ Write-Host "Button 1 clicked" })
$button_2.Add_Click({
if($global:ctrl){
Write-Host "do more" $ctrl
} else {
Write-Host "do standard" $ctrl
}
})
$window.add_KeyDown{
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
#Write-Host $e.Key
if($e.Key -eq "LeftCtrl")
{
return ($global:ctrl = $true)
}
}
$window.add_KeyUp{
param
(
[Parameter(Mandatory)][Object]$sender,
[Parameter(Mandatory)][Windows.Input.KeyEventArgs]$e
)
#Write-Host $e.Key
if($e.Key -eq "LeftCtrl")
{
return ($global:ctrl = $false)
}
}
$Window.ShowDialog() | Out-Null

Related

How do I configure the NewWindowRequested in Webview2 Core using PowerShell

I am creating a PowerShell script which loads Speedtest.net but I do not want users to navigate away or click on links to open new web pages. I have found a way to detect when the source changes in the userform to go back to the desired URL but some links on the page open new windows and I am not able to suppress that. Here is the code I have:
`
$ScriptPath = [System.AppDomain]::CurrentDomain.BaseDirectory.TrimEnd('\')
[xml]$XAML = #"
<Window x:Class="WPF_Getting_Started.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="Internet Speed Test $ScriptVersion"
Height="450"
Width="800"
>
<DockPanel>
<wv2:WebView2 Name="webView"
Source="http://www.speedtest.net"
>
<wv2:WebView2.CreationProperties>
<wv2:CoreWebView2CreationProperties BrowserExecutableFolder="" UserDataFolder="$ENV:TEMP"/>
</wv2:WebView2.CreationProperties>
</wv2:WebView2>
</DockPanel>
</Window>
"#
#
# Assembly
#
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[void][reflection.assembly]::LoadFile("$ScriptPath\Microsoft.Web.WebView2.Core.dll")
[void][reflection.assembly]::LoadFile("$ScriptPath\Microsoft.Web.WebView2.WinForms.dll")
[void][reflection.assembly]::LoadFile("$ScriptPath\Microsoft.Web.WebView2.Wpf.dll")
#
# Execution
#
$XAML.Window.RemoveAttribute("x:Class")
$XAML.Window.RemoveAttribute("mc:Ignorable")
$Reader=(New-Object System.Xml.XmlNodeReader $XAML)
$Form=[Windows.Markup.XamlReader]::Load($Reader)
$XAML.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object {
New-Variable -Name $_.Name -Value $Form.FindName($_.Name) -Force
}
# Webview
$webview_SourceChanged={
$webView.Source = "http://www.speedtest.net"
}
$webView.add_SourceChanged($webview_SourceChanged)
# Other form properties
$Screens = [System.Windows.Forms.Screen]::AllScreens
$Screen = $Screens | Where {$_.Primary -eq "True"}
$script:Bounds = $Screen.Bounds
$Form.Left = $Bounds.Left
$Form.Top = $Bounds.Top
$Form.Height = $Bounds.Height
$Form.Width = $Bounds.Width
[void]$Form.ShowDialog()
`
I thought maybe I could use Event detection:
add_NewWindowRequested
I tried to create an object:
[Microsoft.Web.Webview2.Core.CoreWebView2] $webviewcore = New-Object 'Microsoft.Web.Webview2.Core.CoreWebView2'
But I get an error " A constructor was not found. Cannot find an appropriate constructor for type Microsoft.Web.Webview2.Core.CoreWebView2."
Can any please help me understand how I could stop the Webview2 control from opening a new window?

Add Click event to DataTable (powershell)

So I have a button that pulls this function to search for all the Install.Log files on a machine.
After loading the results, I want to have a double click event on the a row, where it will open the log file.
I am having a hard time adding a click event, and anytime I try to find something related to Datatables I find stuff about java. Any guidance or links would be appreciated.
Thanks in advace
TEST THE CODE FOR YOUR SELF BY RUNNING THIS IN PS ISE
$ComputerName = "your computer name here"
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = #'
<Window Name="Form"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Install Logs" Height="488.773" Width="797.65" Icon = "\\bconac01\ds-support\GS_IT\Tools\Test Tools (Alx)\Tool\icon.ico" ShowInTaskbar="False">
<Grid Margin="0,0,-8,-21">
<DataGrid Name="DataGrid1" HorizontalAlignment="Left" Height="368" VerticalAlignment="Top" Width="772" Margin="10,41,0,0"/>
<Label Content="Filter" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Name="FilterTextBox" HorizontalAlignment="Left" Height="26" Margin="78,10,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="172"/>
</Grid>
</Window>
'#
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Software=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; exit}
# Store Form Objects In PowerShell
$xaml.SelectNodes("//*[#Name]") | ForEach-Object{
Set-Variable -Name ($_.Name) -Value $Software.FindName($_.Name)
Write-host $_.Name
}
$Fields = #(
'Name'
'LastWriteTime'
)
#$Services = Get-WmiObject -Computer ($prebox.text + $device.text) -Class Win32reg_AddRemovePrograms | Select-object -Property *
$Services = Get-ChildItem \\$ComputerName\c$\build\logs -Include *install* -recurse -ErrorAction Stop | Sort-Object LastWriteTime -Descending
# Add Services to a datatable
$Datatable = New-Object System.Data.DataTable
[void]$Datatable.Columns.AddRange($Fields)
foreach ($Service in $Services)
{
$Array = #()
Foreach ($Field in $Fields)
{
$array += $Service.$Field
}
[void]$Datatable.Rows.Add($array)
}
#$filter = "DisplayName LIKE 'B%'"
#$Datatable.DefaultView.RowFilter = $filter
# Create a datagrid object and populate with datatable
$DataGrid1.ItemsSource = $Datatable.DefaultView
$DataGrid1.CanUserAddRows = $False
$DataGrid1.IsReadOnly = $True
$DataGrid1.GridLinesVisibility = "None"
$DataGrid1.Add_CellMouseClick({gridClick})
function gridClick(){
$rowIndex = $DataGrid1.CurrentRow.Index
$columnIndex = $DataGrid1.CurrentCell.ColumnIndex
Write-Host $rowIndex
Write-Host $columnIndex
Write-Host $DataGrid1.Rows[$rowIndex].Cells[0].value
Write-Host $DataGrid1.Rows[$rowIndex].Cells[$columnIndex].value}
$FilterTextBox.Add_TextChanged({
$InputText = $FilterTextBox.Text
$filter = "Name LIKE '$InputText%'"
$Datatable.DefaultView.RowFilter = $filter
$DataGrid1.ItemsSource = $Datatable.DefaultView
$form.Controls.Add($DataGrid1)
$Software.Controls.Add($DataGrid1)
})
# Shows the form
$statusBar1.Text = "Done."
$Software.Add_Shown({$Software.Activate()})
$Software.ShowDialog() | out-null
--Things Ive tried that are suggested.
[
Ok. Here's a sample with gridview cell click event. You can add cell double click event with $DataGrid1.Add_CellMouseClick({gridClick})
hope this should help
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$DataGrid1 = New-Object System.Windows.Forms.DataGridView
$DataGrid1.Size=New-Object System.Drawing.Size(800,400)
$DataGrid1.Add_CellMouseClick({gridClick})
$form.Controls.Add($DataGrid1)
#Create an unbound DataGridView by declaring a column count.
$DataGrid1.ColumnCount = 4
$DataGrid1.ColumnHeadersVisible = $true
#Set the column header names.
$DataGrid1.Columns[0].Name = "Recipe"
$DataGrid1.Columns[1].Name = "Category"
$DataGrid1.Columns[2].Name = "Third COlumn"
$DataGrid1.Columns[3].Name = "Rating"
#Populate the rows.
$row1 = #("Meatloaf","Main Dish", "boringMeatloaf", "boringMeatloafRanking")
$row2 = #("Key Lime Pie","Dessert", "lime juice evaporated milk", "****")
$row3 = #("Orange-Salsa Pork Chops","Main Dish", "pork chops, salsa, orange juice", "****")
$row4 = #("Black Bean and Rice Salad","Salad", "black beans, brown rice", "****")
$row5 = #("Chocolate Cheesecake","Dessert", "cream cheese", "***")
$row6 = #("Black Bean Dip", "Appetizer","black beans, sour cream", "***")
$rows = #( $row1, $row2, $row3, $row4, $row5, $row6 )
foreach ($row in $rows){
$DataGrid1.Rows.Add($row)
}
function gridClick(){
$rowIndex = $DataGrid1.CurrentRow.Index
$columnIndex = $DataGrid1.CurrentCell.ColumnIndex
Write-Host $rowIndex
Write-Host $columnIndex
Write-Host $DataGrid1.Rows[$rowIndex].Cells[0].value
Write-Host $DataGrid1.Rows[$rowIndex].Cells[$columnIndex].value}
$form.ShowDialog()
As per your requirement here's one another sample I have created with WPF with PowerShell. You can bind the event using $WPFListView.Add_MouseDoubleClick({gridClick}) and to access the selected cell value using column like $WPFListView.SelectedValue.OriginalFileName
Try this as it is in PowerShell ISE. This is how it looks
##Sample DataTable
$tabName = "SampleTable"
#Create Table object
$table = New-Object system.Data.DataTable “$tabName”
#Define Columns
$col1 = New-Object system.Data.DataColumn OriginalFileName,([string])
$col2 = New-Object system.Data.DataColumn FileDescription,([string])
$col3 = New-Object system.Data.DataColumn FileVersionRaw,([string])
#Add the Columns
$table.columns.add($col1)
$table.columns.add($col2)
$table.columns.add($col3)
#Create a row
$row = $table.NewRow()
$row.OriginalFileName = "Test Log"
$row.FileDescription = "Test log data"
$row.FileVersionRaw = "v1.0"
$row1 = $table.NewRow()
$row1.OriginalFileName = "IIS Log"
$row1.FileDescription = "IIS Sys log"
$row1.FileVersionRaw = "v2.0"
$row2 = $table.NewRow()
$row2.OriginalFileName = "User Data"
$row2.FileDescription = "User data details"
$row2.FileVersionRaw = "v1.0"
$row3 = $table.NewRow()
$row3.OriginalFileName = "Sys Info"
$row3.FileDescription = "System Info Details"
$row3.FileVersionRaw = "v2.0"
#Add the row to the table
$table.Rows.Add($row)
$table.Rows.Add($row1)
$table.Rows.Add($row2)
$table.Rows.Add($row3)
##Sample DataTable
$inputXML = #"
<Window x:Class="FileVersionChecker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:FileVersionChecker"
mc:Ignorable="d"
Title="FileVersionChecker" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="115*"/>
<ColumnDefinition Width="373*"/>
<ColumnDefinition Width="29*"/>
</Grid.ColumnDefinitions>
<ListView Name="ListView" Grid.Column="1" HorizontalAlignment="Left" Height="150" Margin="10,10,0,0" VerticalAlignment="Top" Width="350">
<ListView.View>
<GridView>
<GridViewColumn Header="OriginalFileName" DisplayMemberBinding ="{Binding 'OriginalFileName'}" Width="100"/>
<GridViewColumn Header="FileDescription" DisplayMemberBinding ="{Binding 'FileDescription'}" Width="100"/>
<GridViewColumn Header="FileVersionRaw" DisplayMemberBinding ="{Binding 'FileVersionRaw'}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
"#
$inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
try {
$Form = [Windows.Markup.XamlReader]::Load( $reader )
}
catch {
Write-Output "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."
}
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
$WPFListView.ItemsSource = $table.DefaultView
$WPFListView.Add_MouseDoubleClick({gridClick})
function gridClick()
{
Write-Host ""
Write-Host "$($WPFListView.SelectedValue.OriginalFileName) , $($WPFListView.SelectedValue.FileDescription), $($WPFListView.SelectedValue.FileVersionRaw)"
}
$Form.ShowDialog() | out-null;

Update Textbox if var changes

I want to update my WPF PowerShell GUI. I set up a small GUI with one button and one textbox to test my script. It looks like:
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
Add-Type -AssemblyName System.Windows.Forms
[xml]$XAML = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="test" Height="630" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<StackPanel>
<Button Name="test" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<TextBox Name="write" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</Grid>
</Window>
"#
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
try {
$Form = [Windows.Markup.XamlReader]::Load($reader)
} catch {
Write-Host "Something is wrong"; exit
}
$xaml.SelectNodes("//*[#Name]") | %{
Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)
}
$test.Add_Click({
Write-Host $varvar
Start-Process powershell -Argument ".\varchecker.ps1"
})
$Form.ShowDialog() | Out-Null
By clicking the button I start another script where I read a variable. This looks like:
while ($true) {
$global:varvar = Get-Content var.txt
Write-Host $varvar
}
Now I want to print the var from the started script to the textbox in the other script. Is this possible? Or the better question: is that a good way to do that?
I don't think you can do it this way. You are trying to start a new PowerShell process, getting some data and passing it to the parent process. I think you can sort of do it in C with pipe() and fork() but havent found a way to do it in PowerShell yet.
You can however, use Start-Job, which creates a process in the background. Alternatively, you can look at Runspaces too for multithreading.
You can also use Invoke-Expression to call the script.
All you have to do from the script is return $Varvar.
If you can tell us what problem you are trying to solve with this method, maybe folks can be more helpful.
Getting values from a new PowerShell process inside your GUI is much too complicated if all you want is to read the contents of a file.
Why not do it all from the GUI code you have?
# [System.Reflection.Assembly]::LoadWithPartialName is deprecated.
Add-Type -AssemblyName PresentationFramework
[xml]$xaml = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="test" Height="630" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<StackPanel>
<Button Name="test" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<TextBox Name="write" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</Grid>
</Window>
"#
$reader = New-Object System.Xml.XmlNodeReader $xaml
try {
$Form = [Windows.Markup.XamlReader]::Load($reader)
}
catch {
Write-Host "Something is wrong"; exit
}
# get references for the button and textbox objects into PowerShell variables.
$button = $Form.FindName("test")
$textBox = $Form.FindName("write")
$button.Add_Click({
# don't start a new PowerShell process, but simply read the file here
$textBox.Text = $var = Get-Content -Path 'PATH TO var.txt' -Raw
Write-Host $var
})
$Form.ShowDialog() | Out-Null
# clean-up
$reader.Dispose()

Runspace Dispose in Powershell (GUI specific)

I'm an active reader of StackOverflow as it usually helps me resolve all my issues.
But for my first question ever I'll ask your help about runspaces in Powershell, specifically for memory management.
I created some PS apps with GUI and now I use runspaces to avoid hang of the GUI when big operations are running.
My problem is that I can't manage to dispose my runspaces when they are finished.
Regarding the code below, it's a simple script to try to understand how I can dispose of my runspaces. I tried to incorporate a unique runspace to monitor and dispose the app runspaces, I inspired myself from a proxb's script (huge fan btw) but it doesn't seems to work.
Everytime I execute the runspace I gain 10Mb of memory, huge leak!
Can you help me to resolve this problem please ?
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Runspace" FontFamily="Calibri" Height="400" Width="400" FontSize="14">
<Grid Margin="10,10,10,10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="TB_Results" Grid.Row="0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"/>
<Button x:Name="BT_Run" Grid.Row="2" Content="Run" Height="30"/>
</Grid>
</Window>
'#
# Inits
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
$gui = [hashtable]::Synchronized(#{})
$reader = (New-Object Xml.XmlNodeReader $xaml)
$gui.Window = [Windows.Markup.XamlReader]::Load($reader)
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object { $gui.Add($_.Name,$gui.Window.FindName($_.Name)) }
$Script:Jobs = [system.collections.arraylist]::Synchronized((New-Object System.Collections.ArrayList))
$Script:JobCleanup = [hashtable]::Synchronized(#{ Host = $host })
# Test function
function RunFunc {
$newRunspace = [runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("gui",$gui)
$Code = {
$memupdate = "Memory: $($(Get-Process -Id $PID).PrivateMemorySize64 / 1mb) mb"
$gui.Window.Dispatcher.invoke("Normal",[action]{
$gui.TB_Results.Text = "$memupdate`r`n" + $gui.TB_Results.Text
})
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
[void]$Script:Jobs.Add((
[PSCustomObject]#{
PowerShell = $PowerShell
Runspace = $PowerShell.BeginInvoke()
}
))
}
# Background Runspace to clean up jobs
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("jobs",$Script:Jobs)
$Code = {
$JobCleanup = $true
do {
foreach($runspace in $jobs) {
if ($runspace.Runspace.isCompleted) {
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.dispose()
$runspace.Runspace = $null
$runspace.powershell = $null
}
}
$temphash = $jobs.clone()
$temphash | Where-Object { $_.runspace -eq $Null } | ForEach-Object { $jobs.remove($_) }
Start-Sleep -Seconds 1
} while ($JobCleanup)
}
$Powershell = [powershell]::Create().AddScript($Code)
$Powershell.Runspace = $newRunspace
$PowerShell.BeginInvoke()
# gui events
$gui.BT_Run.add_Click({ RunFunc })
$gui.Window.ShowDialog() | Out-Null
When you call $runspace.PowerShell.Dispose(), the PowerShell instance is disposed, but the runspace that it's tied to is not automatically disposed, you'll need to do that first yourself in the cleanup task:
$runspace.powershell.EndInvoke($runspace.Runspace) | Out-Null
$runspace.powershell.Runspace.Dispose() # remember to clean up the runspace!
$runspace.powershell.dispose()

How can i incorporate a progress bar into my PowerShell robocopy?

i recently made a robocopy function with PowerShell so i can transfer files over from one computer to another. However, i cannot figure out how to incorporate a progress bar into my script since i did it a bit differently. Here is an excerpt from the script:
$inputXML = #"
<Window x:Class="DataTransferV2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataTransferV2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Background="#FF0D94D2">
<Grid>
<Label x:Name="head_label" Content="Device Data Transfer" HorizontalAlignment="Left" Margin="151,10,0,0" VerticalAlignment="Top" FontSize="18" FontFamily="Segoe UI Black"/>
<Button x:Name="checkall_button" Content="Check All" HorizontalAlignment="Left" Margin="212,49,0,0" VerticalAlignment="Top" Width="75"/>
<CheckBox x:Name="documents_checkBox" Content="Documents" HorizontalAlignment="Left" Margin="57,102,0,0" VerticalAlignment="Top"/>
<Button x:Name="start_button" Content="Start" HorizontalAlignment="Left" Margin="89,246,0,0" VerticalAlignment="Top" Width="88" Height="36"/>
<Button x:Name="cancel_button" Content="Cancel" HorizontalAlignment="Left" Margin="205,246,0,0" VerticalAlignment="Top" Width="88" Height="36"/>
<Button x:Name="exit_button" Content="Exit" HorizontalAlignment="Left" Margin="320,246,0,0" VerticalAlignment="Top" Width="88" Height="36"/>
</Grid>
</Window>
"#
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
#===========================================================================
# Transfer Documents
#===========================================================================
Function global:Documents_Transfer{
if($WPFdocuments_checkBox.IsChecked -eq $true){
Write-Host "Transferring Documents..."
$quotation = ":"
$source = "$DLetter$quotation\Users\$associd\Documents"
$destination = "C:\Users\$env:USERNAME\Documents"
$copy = "/COPY:DTO"
$files = "*.*"
$backup = "/B"
$reportextrafiles = "/X"
$verboseoutput = "V"
$fullpathname = "FP"
$mirror = "/MIR"
$backupmode = "/ZB"
$subfolders = "/E"
$Retries = "/R:2"
$consolewrite = "/TEE"
robocopy $source $destination $files $backup $reportextrafiles $verboseoutput $fullpathname $mirror $backupmode $subfolders $Retries $consolewrite
Write-Host "Documents Transferred"
}
}
$WPFstart_button.Add_Click({
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$associd = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the associate ID:", "Verification", "")
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$DLetter = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the external drive letter:", "Drive", "")
Documents_Transfer
})
#===========================================================================
# Closes the form
#===========================================================================
$WPFexit_button.Add_Click({
$Form.Close()
})
#===========================================================================
# Shows the form
#===========================================================================
$Form.ShowDialog() | out-null
Currently i just have a write-host that attempts to track some progress. Can anyone offer advice as to how i can track this progress? Thanks in advanced!