Windows PowerShell で GUI アプリを作る際、従来の WinForms ではどうしても「古い見た目」になりがちです。
そこで今回は WPF(XAML)を利用して、モダンな“カード型UI”を実装する方法 を紹介します。
さらに、Windows PowerShell 5.x(.NET Framework 4.x)で動作確認済みの修正版を掲載します。
この記事をそのままコピーして使えば、あなたの環境でも動かせるはずです。
フォルダ構成
まずは 3 ファイルを用意します。
MyGuiApp/ ├─ Main.ps1 # ロジック(PowerShell) ├─ MainWindow.xaml # 画面レイアウト └─ Styles.xaml # 見た目のスタイル
Styles.xaml(見た目の定義)
こちらで色やカードの見た目、ボタンのスタイルをまとめています。
<!-- Styles.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- ===== Theme Colors ===== -->
<Color x:Key="BgDark">#FF0F1115</Color>
<Color x:Key="Surface">#FF171A21</Color>
<Color x:Key="CardBg">#FF1E232C</Color>
<Color x:Key="CardStroke">#22FFFFFF</Color>
<Color x:Key="Accent">#FF4C8BF5</Color>
<Color x:Key="AccentAlt">#FF6FD3B8</Color>
<Color x:Key="TextPrimary">#FFFFFFFF</Color>
<Color x:Key="TextSecondary">#B3FFFFFF</Color>
<SolidColorBrush x:Key="BgBrush" Color="{StaticResource BgDark}"/>
<SolidColorBrush x:Key="SurfaceBrush" Color="{StaticResource Surface}"/>
<SolidColorBrush x:Key="CardBrush" Color="{StaticResource CardBg}"/>
<SolidColorBrush x:Key="StrokeBrush" Color="{StaticResource CardStroke}"/>
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource Accent}"/>
<SolidColorBrush x:Key="AccentAltBrush" Color="{StaticResource AccentAlt}"/>
<SolidColorBrush x:Key="TextPrimaryBrush" Color="{StaticResource TextPrimary}"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="{StaticResource TextSecondary}"/>
<!-- ===== Typography ===== -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style x:Key="H1" TargetType="TextBlock">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}"/>
</Style>
<Style x:Key="Muted" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}"/>
</Style>
<!-- ===== Buttons ===== -->
<Style x:Key="PrimaryButton" TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Padding" Value="12,8"/>
<Setter Property="Margin" Value="0,0,8,0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="6,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.9"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Opacity" Value="0.8"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#555"/>
<Setter Property="Foreground" Value="#DDD"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="GhostButton" TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
<Setter Property="Background" Value="#00000000"/>
<Setter Property="Foreground" Value="{StaticResource AccentAltBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{StaticResource AccentAltBrush}"
BorderThickness="1"
CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="6,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="0.9"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Opacity" Value="0.8"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ===== Card ===== -->
<Style x:Key="Card" TargetType="Border">
<Setter Property="Padding" Value="16"/>
<Setter Property="CornerRadius" Value="16"/>
<Setter Property="Background" Value="{StaticResource CardBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource StrokeBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,0,16,16"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="18" ShadowDepth="2" Opacity="0.25" Color="#000000"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml(カード型UIの画面)
<!-- MainWindow.xaml -->
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Card UI Sample" Width="980" Height="640"
WindowStartupLocation="CenterScreen"
Background="{DynamicResource BgBrush}">
<Grid>
<!-- ヘッダー -->
<Border Background="{DynamicResource SurfaceBrush}" Height="64" VerticalAlignment="Top">
<Grid Margin="20,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ダッシュボード" Style="{DynamicResource H1}" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
<Button x:Name="BtnRefresh" Content="更新" Style="{DynamicResource PrimaryButton}"/>
<Button x:Name="BtnSettings" Content="設定" Style="{DynamicResource GhostButton}"/>
</StackPanel>
</Grid>
</Border>
<!-- カード一覧 -->
<ScrollViewer Margin="20,84,20,20" VerticalScrollBarVisibility="Auto">
<WrapPanel ItemWidth="300" MinWidth="300">
<!-- Card 1 -->
<Border Style="{DynamicResource Card}" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Margin="0,0,8,0"/>
<TextBlock Text="ユーザー情報" Style="{DynamicResource H1}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="ユーザー名: admin" Margin="0,0,0,4"/>
<TextBlock Text="権限: 管理者" Style="{DynamicResource Muted}" Margin="0,0,0,4"/>
<TextBlock Text="最終ログイン: 2025-09-24 08:23" Style="{DynamicResource Muted}"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button x:Name="BtnUserDetail" Content="詳細" Style="{DynamicResource GhostButton}"/>
<Button x:Name="BtnUserEdit" Content="編集" Style="{DynamicResource PrimaryButton}"/>
</StackPanel>
</Grid>
</Border>
<!-- Card 2 -->
<Border Style="{DynamicResource Card}" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Margin="0,0,8,0"/>
<TextBlock Text="ジョブ状況" Style="{DynamicResource H1}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<ProgressBar x:Name="JobProgress" Height="12" Minimum="0" Maximum="100" Value="42" Margin="0,0,0,6"/>
<TextBlock Text="ビルド #128 進行中… (42%)" Style="{DynamicResource Muted}"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button x:Name="BtnOpenLogs" Content="ログ" Style="{DynamicResource GhostButton}"/>
<Button x:Name="BtnCancelJob" Content="停止" Style="{DynamicResource PrimaryButton}"/>
</StackPanel>
</Grid>
</Border>
<!-- Card 3 -->
<Border Style="{DynamicResource Card}" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock FontFamily="Segoe MDL2 Assets" Text="" Margin="0,0,8,0"/>
<TextBlock Text="お知らせ" Style="{DynamicResource H1}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="・メンテナンス: 9/28 01:00-03:00" Margin="0,0,0,4"/>
<TextBlock Text="・新機能: レポートのエクスポートを追加" Margin="0,0,0,4"/>
<TextBlock Text="・注意: API キーの更新期限が近づいています"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button x:Name="BtnAllNews" Content="すべて見る" Style="{DynamicResource GhostButton}"/>
</StackPanel>
</Grid>
</Border>
</WrapPanel>
</ScrollViewer>
</Grid>
</Window>
Main.ps1(PowerShell 側のロジック)
Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase
function Load-Xaml([string]$path) {
[xml]$xaml = Get-Content -LiteralPath $path -Raw
$reader = New-Object System.Xml.XmlNodeReader $xaml
[Windows.Markup.XamlReader]::Load($reader)
}
# XAML 読み込み
$window = Load-Xaml ".\MainWindow.xaml"
# スタイル読み込み
$styles = Load-Xaml ".\Styles.xaml"
$window.Resources.MergedDictionaries.Add($styles) | Out-Null
# コントロール参照
$btnRefresh = $window.FindName("BtnRefresh")
$jobProgress = $window.FindName("JobProgress")
# イベント例
$btnRefresh.Add_Click({
$jobProgress.Value = [Math]::Min(100, $jobProgress.Value + 10)
[System.Windows.MessageBox]::Show("更新しました。進捗: $($jobProgress.Value)%")
})
# 表示
$null = $window.ShowDialog()
実行方法
- 上記3ファイルを同じフォルダに配置
- PowerShell をそのフォルダに移動して実行
cd .\MyGuiApp .\Main.ps1
まとめ
- デザインは XAML、処理は PowerShell に分けると見通しが良くなる
- カード型UI を採用することで、モダンでおしゃれな管理画面を作れる
- Windows PowerShell 5.x でも
.NET Framework 4.x上で問題なく動作