Last active
October 17, 2022 14:45
-
-
Save sean-m/5db2328fc84730ded86552b2fd9d02c6 to your computer and use it in GitHub Desktop.
Simple WPF dashboard script with functions mapped to buttons. Uses command pattern to map button clicks to PowerShell script blocks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Simple WPF dashboard script with functions mapped to buttons. | |
Uses command pattern to map button clicks to PowerShell script | |
blocks. | |
How it kinda works: | |
The WPF UI for PowerShell idea I got from here: | |
https://foxdeploy.com/2015/04/16/part-ii-deploying-powershell-guis-in-minutes-using-visual-studio/ | |
Really great blog post*, I suggest you read it. The method for | |
rendering the Xaml markup into a window was taken from there | |
but using the command pattern to populate the buttons is novel, | |
this is done by loading action objects into the $actions list. | |
$actions is a list of objects that have 3 properties: | |
name : String, name of operation to display on button | |
function : ScriptBlock, function which will be called | |
args : object[], list of arguments which are passed to 'function' | |
These effectively become closures which are then associated | |
to the click handler for buttons which are added to a stack | |
panel in the GUI. This snippet is responsible for wiring up | |
the click handler to the function call: | |
$btn.Add_Click({ | |
$WPFConsoleText.Text = $a.function.Invoke($a.args) | Out-String | |
}) | |
In english: when clicked, run this script block that replaces | |
the text of a texbox element with the string output of the function | |
$a.function when passing in the arguments $a.args. | |
Using this method, the function is decoupled from the UI element | |
and additional buttons can be added to the UI by adding function | |
objects to the $actions list. | |
I hope it's useful. | |
*Sections with a ~~ in the heading are derived from the blog post | |
- Sean McArdle, August 2016 | |
#> | |
#==================================# | |
# Tunable Variables # | |
#==================================# | |
$_servers = @( | |
"mcardletech.com" | |
) | |
$_services = @( | |
"ews", | |
"eap", | |
"filebeat" | |
) | |
if (-not $_creds) { | |
$_creds = $(Get-Credential -UserName "Domain\" -Message "Admin credentials") | |
} | |
if (-not $_creds) { | |
Write-Warning "Will not continue without explicit credentials!" | |
Exit 1 | |
} | |
#==================================# | |
# ~~ UI Markup # | |
#==================================# | |
$inputXML = @" | |
<Window x:Class="JbossDashboard.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:local="clr-namespace:JbossDashboard" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
Title="Posh Dashboard" | |
Width="930" | |
Height="500" | |
mc:Ignorable="d"> | |
<Grid> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="300" /> | |
<ColumnDefinition /> | |
</Grid.ColumnDefinitions> | |
<Grid Grid.Column="0"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="48" /> | |
<RowDefinition MaxHeight="200" /> | |
<RowDefinition /> | |
</Grid.RowDefinitions> | |
<Label x:Name="UserName" | |
Grid.Row="0" | |
HorizontalAlignment="Center" | |
VerticalAlignment="Center" /> | |
<Grid Grid.Row="1"> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition /> | |
<ColumnDefinition /> | |
</Grid.ColumnDefinitions> | |
<Label Grid.Column="0" | |
HorizontalAlignment="Center" | |
VerticalAlignment="Top"> | |
Servers | |
</Label> | |
<ListBox x:Name="ServerList" | |
Grid.Column="0" | |
Margin="2,20,2,0" /> | |
<Label Grid.Column="1" | |
HorizontalAlignment="Center" | |
VerticalAlignment="Top"> | |
Services | |
</Label> | |
<ListBox x:Name="ServiceList" | |
Grid.Column="1" | |
Margin="2,20,2,0" /> | |
</Grid> | |
<StackPanel x:Name="ButtonPanel" | |
Grid.Row="2" | |
Margin="2,4" /> | |
</Grid> | |
<TextBox x:Name="ConsoleText" | |
Grid.Column="1" | |
Margin="2,4" | |
Background="#FF012456" | |
FontFamily="Lucida Console" | |
FontSize="12" | |
Foreground="White" | |
IsReadOnly="True" /> | |
</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)} | |
Function Get-FormVariables{ | |
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true} | |
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan | |
get-variable WPF* | |
} | |
Get-FormVariables | |
#==================================================# | |
# Setup UI Elements # | |
#==================================================# | |
# Populate lists | |
$_servers | % { $WPFServerList.Items.Add($_) } | |
$_services | % { $WPFServiceList.Items.Add($_) } | |
# Show user name for reference | |
$WPFUserName.Content = "User: $($_creds.UserName.Replace("_","__"))" | |
#==================================================# | |
# Setup functions as command objects # | |
#==================================================# | |
<# $action is a list of objects that have 3 properties: | |
name : String, name of operation to display on button | |
function : ScriptBlock, function which will be called | |
args : object[], list of arguments which are passed to 'function' | |
#> | |
$actions = @() | |
# Function for checking specified services on servers | |
$actions += New-Object PSObject -Property @{ | |
name = "Check Services" | |
function = { | |
param ($credentials, $servers, $services) | |
foreach ($s in $local:servers) { | |
$svc = Invoke-Command -ComputerName $s -Credential $credentials -ScriptBlock { | |
Get-Service | |
} | ? { if (-not $local:services) { $true }; $local:services.Contains($_.Name.ToLower()) } | |
$svc | FT -a | |
} | |
}; | |
args = @($_creds, $_servers, $_services); | |
} | |
# Function to roll the "BlackBox" perfmon collector set log | |
$actions += New-Object PSObject -Property @{ | |
name = "Roll Perflog"; | |
function = { | |
param ($credentials, $servers) | |
foreach ($s in $local:servers) { | |
$out = Invoke-Command -ComputerName $s -Credential $credentials -ScriptBlock { | |
logman.exe | |
logman.exe stop BlackBox | |
logman.exe start BlackBox | |
} | |
Write-Host "`n$s" -ForegroundColor Green | |
$out | |
} | |
} | |
args = @($_creds, $_servers) | |
} | |
# Restart filebeat service to ensure it's read from log files | |
$actions += New-Object PSObject -Property @{ | |
name = "Restart Filebeat"; | |
function = { | |
param ($credentials, $servers) | |
foreach ($s in $local:servers) { | |
$svc = Invoke-Command -ComputerName $s -Credential $credentials -ScriptBlock { | |
Restart-Service filebeat | |
Get-Service filebeat | |
} | |
$svc | FT | |
} | |
}; | |
args = @($_creds, $_servers) | |
} | |
#========================================================# | |
# Wire up functiosn to button clicks and # | |
# add buttons to stackpanel # | |
#========================================================# | |
$actions_ht = @{} # needed so actions can be referenced by name | |
foreach ($a in $actions) { | |
$actions_ht.Add($a.name, $a) | |
$btn = new-object System.Windows.Controls.Button | |
$btn.Content = $a.name | |
$btn.Add_Click({ | |
$WPFConsoleText.Text = $actions_ht[$this.Content].function.Invoke($actions_ht[$this.Content].args) | Out-String | |
}) | |
$btn.Height = 36 | |
$WPFButtonPanel.AddChild($btn) | |
} | |
# Show form | |
$Form.ShowDialog() | out-null |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment