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
上で問題なく動作