Building a Modern WPF GUI in PowerShell: The "Server Report Center"
Tired of manually hunting down and executing multiple PowerShell scripts one by one? In this post, we’ll break down how to build a centralized, modern GUI dashboard—the Server Report Center—using pure PowerShell and WPF (Windows Presentation Foundation).
This guide serves as a quick revision of the core concepts, XAML integration, and PowerShell tricks used to bring this UI to life.
Here is the code.
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
# ==============================================================================
# CONFIGURATION: ADD, REMOVE, OR MODIFY YOUR BLOCKS HERE
# ==============================================================================
$ReportCards = @(
@{
Title = "AWS Disk Info"
Description = "Get disk usage and storage details for AWS EC2 instances."
Script = "AWSDiskInfo.ps1"
Color = "#F5A623" # Orange Accent
BgColor = "#FFF8F0"
},
@{
Title = "Azure Disk Info"
Description = "Get disk usage and storage details for Azure VMs."
Script = "AzureDiskInfo.ps1"
Color = "#0078D4" # Blue Accent
BgColor = "#F0F7FF"
},
@{
Title = "MS SQL Disk Info"
Description = "Get disk usage and storage details for SQL Server instances."
Script = "MSSQLDiskInfo.ps1"
Color = "#D9534F" # Red Accent
BgColor = "#FFF0F0"
},
@{
Title = "SQL Backup Info"
Description = "Get backup details, status and history for SQL Server."
Script = "SQLBackupInfo.ps1"
Color = "#6F42C1" # Purple Accent
BgColor = "#F5F0FF"
},
@{
Title = "SQL Health Status"
Description = "Get overall health status of SQL Server instances and databases."
Script = "SQLHealthStatus.ps1"
Color = "#28A745" # Green Accent
BgColor = "#F0FFF4"
}
)
# Base Window XAML Template with Custom Frameless Title Bar
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Server Report Center" Height="550" Width="1100"
WindowStartupLocation="CenterScreen"
WindowStyle="None" AllowsTransparency="True"
Background="Transparent" Foreground="#212529">
<Window.Resources>
<!-- Style for Window Control Buttons (Min/Max/Close) -->
<Style x:Key="CaptionBtnStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Width" Value="45"/>
<Setter Property="Height" Value="40"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#106EBE"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Specific Hover style for Close Button (Red) -->
<Style x:Key="CloseBtnStyle" TargetType="Button" BasedOn="{StaticResource CaptionBtnStyle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E81123"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- Outer Border with Background, Border, and CornerRadius for the whole window -->
<Border Background="#F8F9FA" BorderBrush="#0078D4" BorderThickness="1" CornerRadius="12">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60"/> <!-- Header & Window Title Bar -->
<RowDefinition Height="250"/> <!-- Cards Area -->
<RowDefinition Height="*"/> <!-- Terminal / Status Log -->
</Grid.RowDefinitions>
<!-- Seamless Blue Top Header Bar (Clipped by parent Border's CornerRadius) -->
<Border Grid.Row="0" Background="#0078D4" x:Name="TitleBar" CornerRadius="12,12,0,0">
<Grid>
<!-- App Title Text (CENTERED) -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Server Report Center" FontSize="20" FontWeight="Bold" Foreground="White"/>
</StackPanel>
<!-- Custom Window Control Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button x:Name="BtnMinimize" Content="🗕" Style="{StaticResource CaptionBtnStyle}"/>
<Button x:Name="BtnMaximize" Content="🗖" Style="{StaticResource CaptionBtnStyle}"/>
<Button x:Name="BtnClose" Content="✕" Style="{StaticResource CloseBtnStyle}"/>
</StackPanel>
</Grid>
</Border>
<!-- Dynamic Cards Grid Container -->
<Grid x:Name="CardGrid" Grid.Row="1" Margin="15,15,15,5">
<!-- Columns generated dynamically in PowerShell -->
</Grid>
<!-- Status & Console Log View -->
<Border Grid.Row="2" Background="#1E1E1E" CornerRadius="6" Margin="20,5,20,15" Padding="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Status Information" Foreground="#89B4FA" FontWeight="Bold" FontSize="12" Margin="0,0,0,5"/>
<TextBox x:Name="TxtLog" Grid.Row="1" Background="Transparent" Foreground="#DCDCDC" BorderThickness="0"
FontFamily="Consolas" FontSize="12" IsReadOnly="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap"/>
</Grid>
</Border>
</Grid>
</Border>
</Window>
"@
# Read XML
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
# Get Controls
$CardGrid = $window.FindName("CardGrid")
$TxtLog = $window.FindName("TxtLog")
$TitleBar = $window.FindName("TitleBar")
$BtnMinimize = $window.FindName("BtnMinimize")
$BtnMaximize = $window.FindName("BtnMaximize")
$BtnClose = $window.FindName("BtnClose")
# Enable Window Dragging on the Blue Header Bar
$TitleBar.Add_MouseLeftButtonDown({ $window.DragMove() })
# Handle Custom Window Buttons
$BtnMinimize.Add_Click({ $window.WindowState = [System.Windows.WindowState]::Minimized })
$BtnMaximize.Add_Click({
if ($window.WindowState -eq [System.Windows.WindowState]::Maximized) {
$window.WindowState = [System.Windows.WindowState]::Normal
} else {
$window.WindowState = [System.Windows.WindowState]::Maximized
}
})
$BtnClose.Add_Click({ $window.Close() })
# Output Logger Helper
function Write-Log {
param([string]$Message)
$TimeStamp = Get-Date -Format "HH:mm:ss"
$TxtLog.AppendText("[$TimeStamp] $Message`n")
$TxtLog.ScrollToEnd()
$window.Dispatcher.Invoke([Action]{}, [System.Windows.Threading.DispatcherPriority]::Background)
}
# Run Script Helper
function Execute-ReportScript {
param([string]$ScriptName, [string]$ReportTitle)
$ScriptPath = "C:\Test\PowerShellScripts\$ScriptName"
Write-Log "Initiating: $ReportTitle..."
if (Test-Path $ScriptPath) {
try {
$output = & $ScriptPath | Out-String
if ($output) { Write-Log $output.Trim() }
Write-Log "SUCCESS: $ReportTitle executed successfully."
} catch {
Write-Log "ERROR: Failed to run $ReportTitle.`n$_"
}
} else {
Write-Log "WARNING: Could not locate file at $ScriptPath"
}
}
# Dynamic Card Generation
for ($i = 0; $i -lt $ReportCards.Count; $i++) {
$card = $ReportCards[$i]
# Add Column Definition
$colDef = New-Object System.Windows.Controls.ColumnDefinition
$colDef.Width = New-Object System.Windows.GridLength(1, [System.Windows.GridUnitType]::Star)
$CardGrid.ColumnDefinitions.Add($colDef)
# Build Card XML Template Dynamically with Rounded Buttons
[xml]$cardXaml = @"
<Border xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Background="$($card.BgColor)" BorderBrush="$($card.Color)" BorderThickness="1.5" CornerRadius="8" Margin="5">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Center">
<TextBlock Text="$($card.Title)" FontSize="14" FontWeight="Bold" Foreground="#212529" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
<Rectangle Height="3" Width="30" Fill="$($card.Color)" Margin="0,5,0,0"/>
</StackPanel>
<TextBlock Grid.Row="1" Text="$($card.Description)" TextWrapping="Wrap" TextAlignment="Center" FontSize="11" Foreground="#6C757D" VerticalAlignment="Center"/>
<Button Grid.Row="2" Content="► Run Report" Foreground="White" FontWeight="Bold" FontSize="13" Height="36" Cursor="Hand" BorderThickness="0">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="$($card.Color)" CornerRadius="6">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Border>
"@
$cardReader = (New-Object System.Xml.XmlNodeReader $cardXaml)
$borderElement = [Windows.Markup.XamlReader]::Load($cardReader)
[System.Windows.Controls.Grid]::SetColumn($borderElement, $i)
$btn = $borderElement.Child.Children[2]
$btn.Add_Click({
Execute-ReportScript -ScriptName $card.Script -ReportTitle $card.Title
}.GetNewClosure())
$CardGrid.Children.Add($borderElement) | Out-Null
}
Write-Log "System initialized with $($ReportCards.Count) report modules."
# Render Window
$window.ShowDialog() | Out-Null
# Code completed
# Write above code in the txt file and save it as a ReportLauncher.ps1 and call the ReportLauncher.ps1 from the .bat file.
Below is the code we can use it for the .bat file, once we click on the .bat file application get opend.
@echo off
powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Test\ReportLauncher.ps1"