Function Invoke-GUI {
<#
.SYNOPSIS
Invoke-GUI creates a customizable threaded GUI for powershell
.DESCRIPTION
Creates a GUI from XAML file through Windows Presentation Foundation.
All XAML data that require changeable data requires a "Name" node which sets the variable name
Any XML nodes with name will get a variable assigned under $Code. and all subsequent data is accessible from there.
The main GUI will be assigned $Code.GUI
.PARAMETER File
Sets a file with the XAML data used to build the interface.
.PARAMETER XAML
Input object in form on an already created XML variable.
.PARAMETER OnTick
Code to be executed on each tick
.PARAMETER InitializationScript
Code to be performed one time before the GUI shows.
.PARAMETER TimerInterval
Timer interval
.PARAMETER LogFile
LogFile location
.Example
Invoke-GUI -File C:\Application\GUI.xaml
This loads the GUI without any tick action or custom initialization script.
.Example
Invoke-GUI -XAML $XAML -LogFile C:\Application\dump.log
Loads the GUI from an already compiled XAML object and outputs logs to dump.log
.Example
Invoke-GUI -XAML $XAML -OnTick {$Code.MessageBox.Content = "Random number: $(Get-Random)"} -TimerInterval "0:1:0.00" -LogFile C:\Application\dump.log
As example two but updates a MessageBox variable with a random number once per minute.
.Example
Invoke-GUI -XAML $XAML -TimerInterval "0:1:0.00" -LogFile C:\Application\dump.log -OnTick {
$Code.MessageBox.Content = "Random number: $(Get-Random)"
} -InitializationScript {
$Code.Company.Content = "Contoso"
$Code.Agreement.Value = 0
}
As example three but adds some company and agreement information at startup.
#>
[cmdletbinding()]
Param (
[Parameter(Mandatory=$True,ParameterSetName="File")]
[String]$File,
[Parameter(Mandatory=$True,ParameterSetName="XAML")]
[XML]$XAML,
[Parameter(Mandatory=$False)]
[ScriptBlock]$OnTick = {},
[Parameter(Mandatory=$False)]
[ScriptBlock]$InitializationScript = {},
[Parameter(Mandatory=$False)]
[TimeSpan]$TimerInterval = "0:0:1.00",
[Parameter(Mandatory=$False)]
[String]$LogFile
)
If($LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Starting GUI script" | Out-File $LogFile -Append
}
$Script:Code = [hashtable]::Synchronized(@{})
$Code.TimerInterval = $TimerInterval
$Code.OnTick = $OnTick
$Code.InitializationScript = $InitializationScript
$Code.ParameterSet = $PSCmdlet.ParameterSetName
If($PSCmdlet.ParameterSetName -eq "File"){
$Code.XAMLFile = $File
}
Else{
$Code.XAML = $XAML
}
If($LogFile){
$Code.LogFile = $LogFile
}
$Script:Runspacehash = [hashtable]::Synchronized(@{})
$Runspacehash.host = $Host
$Runspacehash.runspace = [RunspaceFactory]::CreateRunspace()
$Runspacehash.runspace.ApartmentState = “STA”
$Runspacehash.runspace.ThreadOptions = “ReuseThread”
$Runspacehash.runspace.Open()
$Runspacehash.psCmd = {Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase}.GetPowerShell()
$Runspacehash.runspace.SessionStateProxy.SetVariable("code",$Code)
$Runspacehash.runspace.SessionStateProxy.SetVariable("Runspacehash",$Runspacehash)
$Runspacehash.psCmd.Runspace = $Runspacehash.runspace
$Runspacehash.Handle = $Runspacehash.psCmd.AddScript({
$Script:Update = {
# Do stuff on tick here
.$Code.OnTick
}
If($Code.ParameterSet -eq "File"){[XML]$XAML = Get-Content $Code.XAMLFile}
Else{[XML]$XAML = $Code.XAML}
# builds gui as $Code.GUI with data from $XAML
# creates variable for each xml property with the "name" assigned.
Try {
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
$objXMLReader = (New-Object System.Xml.XmlNodeReader $XAML)
$Code.GUI = [Windows.Markup.XamlReader]::Load($objXMLReader)
$Code.GUI.WindowStartupLocation = "CenterScreen"
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Creating variables for XAML elements.." | Out-File $Code.LogFile -Append
}
$XAML.SelectNodes("//*[@Name]") | ForEach-Object {
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Variable created: `$Code.$($_.Name)" | Out-File $Code.LogFile -Append
}
$Code."$($_.Name)" = $Code.GUI.FindName($_.Name)
}
}
Catch {
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | $_" | Out-File $Code.LogFile -Append
}
Exit
}
#Timer Event
$Code.GUI.Add_SourceInitialized({
$Script:timer = new-object System.Windows.Threading.DispatcherTimer
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Timer interval is: $($Code.TimerInterval)" | Out-File $Code.LogFile -Append
}
$timer.Interval = [TimeSpan]$Code.TimerInterval
$timer.Add_Tick({
$Update.Invoke()
[Windows.Input.InputEventHandler]{$Code.GUI.UpdateLayout()}
})
#Start timer
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Starting timer" | Out-File $Code.LogFile -Append
}
$timer.Start()
If (-NOT $timer.IsEnabled) {
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Stopping GUI" | Out-File $Code.LogFile -Append
}
$Code.GUI.Close()
}
})
$Code.GUI.Add_Closed({
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | GUI disposed" | Out-File $Code.LogFile -Append
}
$timer.Stop()
$Runspacehash.PowerShell.Dispose()
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
})
Try{$Code.InitializationScript.Invoke()}
Catch{
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | $_" | Out-File $Code.LogFile -Append
}
}
# Does not work to place this in the InitializationScript yet. Need to work around this somehow
$Code.GUI.Add_MouseLeftButtonDown({
$This.DragMove()
})
$Code.GUI.ShowDialog() | Out-Null
}).BeginInvoke()
#Give gui time to initialize
Start-sleep 1
}
Function Update-GUI{
<#
.SYNOPSIS
Update-GUI Sends a scriptblock for execution in the GUI thread.
.DESCRIPTION
Takes a Scriptblock for execution in the GUI thread.
It is a simple command (9 rows) but as it is used often converted to a function.
Code is:
$Code.GUI.Dispatcher.Invoke([action]{
Try{.$ScriptBlock}
Catch{
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Could not execute command: $ScriptBlock" | Out-File $Code.LogFile -Append
}
}
})
.PARAMETER ScriptBlock
Scriptblock to run inside thread.
.Example
Update-GUI -ScriptBlock {$Code.Message.content = "Hello world"}
Updates the message node content with "hello world"
#>
Param(
[Parameter(Mandatory=$True)]
[ScriptBlock]$ScriptBlock
)
$Code.GUI.Dispatcher.Invoke([action]{
Try{.$ScriptBlock}
Catch{
If($Code.LogFile){
$time = get-date -Format "yyyy-MM-dd HH:mm:ss"
"$Time | Could not execute command: $ScriptBlock" | Out-File $Code.LogFile -Append
}
}
})
}