Skip to content

Instantly share code, notes, and snippets.

@sean-m
Last active October 17, 2022 14:45
Show Gist options
  • Save sean-m/5db2328fc84730ded86552b2fd9d02c6 to your computer and use it in GitHub Desktop.
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.
<#
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