PowerShell でモダンなカード型UIを作る方法(Windows PowerShell 5.x 対応)

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="&#xE80F;" 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="&#xE9D2;" 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="&#xE946;" 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 上で問題なく動作

スポンサーリンク

-IT関連