[備忘録] PowerShell WPFでモダンなプログレスバーを作成してみた

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b287dba0-cd96-4297-b735-88499c2c2a8f.png)

前回はPowerShellとWindows Formsでカスタムプログレスバーを作成しました。
今回はその続編として、WPFを使って、よりモダンで柔軟なUIにアップデートしてみます。

https://qiita.com/Tadataka_Takahashi/items/d1b8f833e2107038c483

## WPFに移行するメリット
レイアウトが自由：GridやStackPanelで柔軟に配置可能

デザインが豊か：XAMLで細かくスタイル設定

アニメーション対応：動きのあるUIが作れる

データ連携が強力：ViewModelとのバインディングが簡単

高解像度に強い：自動スケーリングで見た目が崩れにくい

## 今回のゴール
Forms版と同様の機能を、WPFでどれだけ見栄えよく・扱いやすく作れるかを試します。
「PowerShellでもここまでできる」ことを実感してもらえる内容です。

## 動作環境
以下の環境で動作確認しました。
Windows 11 
Powershell 5.1


## 基本的なWPFプログレスバー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/c6ac1cd2-f10e-449e-b457-8e21551dd7ca.png)

```powershell
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase

# XAMLの定義
$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF カスタムプログレスバー" 
        Width="500" Height="200"
        WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <TextBlock Grid.Row="0" Name="StatusLabel" 
                   Text="処理中..." 
                   FontSize="16" FontWeight="Bold"
                   HorizontalAlignment="Center"/>
        
        <ProgressBar Grid.Row="2" Name="MainProgressBar"
                     Height="25" 
                     Minimum="0" Maximum="100" Value="0"
                     Background="LightGray"
                     Foreground="DodgerBlue"/>
        
        <TextBlock Grid.Row="4" Name="PercentLabel"
                   Text="0%" 
                   FontSize="12"
                   HorizontalAlignment="Center"/>
    </Grid>
</Window>
"@

# XAMLをロード
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)

# コントロールの取得
$statusLabel = $window.FindName("StatusLabel")
$progressBar = $window.FindName("MainProgressBar")
$percentLabel = $window.FindName("PercentLabel")

# ウィンドウを表示
$window.Show()

# プログレス処理のシミュレーション
for ($i = 1; $i -le 100; $i++) {
    $progressBar.Value = $i
    $percentLabel.Text = "$i%"
    $statusLabel.Text = "処理中... ステップ $i/100"
    
    # UIの更新を強制
    $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})
    Start-Sleep -Milliseconds 50
}

$statusLabel.Text = "完了！"
Start-Sleep -Seconds 2
$window.Close()
```

画面例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/ce9cf9ae-526b-4950-bd2c-7cdb8855b461.png)

## 高度なカスタマイズ例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/939c8ef7-8af3-4557-bfe2-7cb82ac319e8.png)

```powershell
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase

$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="高度なWPFプログレスバー" 
        Width="600" Height="350"
        WindowStartupLocation="CenterScreen"
        Background="White">
    <Grid Margin="30">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!-- タイトル -->
        <TextBlock Grid.Row="0" 
                   Text="ファイル処理進捗" 
                   FontSize="20" FontWeight="Bold"
                   Foreground="DarkBlue"
                   HorizontalAlignment="Center"/>
        
        <!-- 現在のファイル名 -->
        <Border Grid.Row="2" 
                Background="AliceBlue" 
                BorderBrush="SteelBlue" 
                BorderThickness="1" 
                CornerRadius="5" 
                Padding="10">
            <TextBlock Name="FileLabel" 
                       Text="準備中..." 
                       FontSize="14"
                       TextWrapping="Wrap"/>
        </Border>
        
        <!-- メインプログレスバー -->
        <Grid Grid.Row="4">
            <ProgressBar Name="MainProgressBar"
                         Height="30"
                         Minimum="0" Maximum="100" Value="0"
                         Background="LightGray"
                         BorderBrush="Gray"
                         BorderThickness="1">
                <ProgressBar.Style>
                    <Style TargetType="ProgressBar">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ProgressBar">
                                    <Border BorderBrush="{TemplateBinding BorderBrush}"
                                            BorderThickness="{TemplateBinding BorderThickness}"
                                            Background="{TemplateBinding Background}"
                                            CornerRadius="15">
                                        <Grid>
                                            <Rectangle Name="PART_Track"
                                                       Fill="{TemplateBinding Background}"/>
                                            <Rectangle Name="PART_Indicator"
                                                       HorizontalAlignment="Left">
                                                <Rectangle.Fill>
                                                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                                        <GradientStop Color="DodgerBlue" Offset="0"/>
                                                        <GradientStop Color="LightSkyBlue" Offset="1"/>
                                                    </LinearGradientBrush>
                                                </Rectangle.Fill>
                                            </Rectangle>
                                        </Grid>
                                    </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </ProgressBar.Style>
            </ProgressBar>
            <TextBlock Name="ProgressText" 
                       Text="0%" 
                       FontSize="12" FontWeight="Bold"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Foreground="White"/>
        </Grid>
        
        <!-- 詳細情報 -->
        <Border Grid.Row="6" 
                Background="WhiteSmoke" 
                BorderBrush="Gray" 
                BorderThickness="1" 
                CornerRadius="3" 
                Padding="10">
            <TextBlock Name="InfoLabel" 
                       Text="" 
                       FontSize="12"
                       TextWrapping="Wrap"/>
        </Border>
        
        <!-- 時間情報 -->
        <StackPanel Grid.Row="8" Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Text="経過時間: " FontSize="10" Foreground="Gray"/>
            <TextBlock Name="ElapsedLabel" Text="00:00:00" FontSize="10" FontWeight="Bold" Foreground="Gray"/>
            <TextBlock Text="  |  推定残り時間: " FontSize="10" Foreground="Gray" Margin="20,0,0,0"/>
            <TextBlock Name="RemainingLabel" Text="--:--:--" FontSize="10" FontWeight="Bold" Foreground="Gray"/>
        </StackPanel>
    </Grid>
</Window>
"@

$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)

# コントロールの取得
$fileLabel = $window.FindName("FileLabel")
$progressBar = $window.FindName("MainProgressBar")
$progressText = $window.FindName("ProgressText")
$infoLabel = $window.FindName("InfoLabel")
$elapsedLabel = $window.FindName("ElapsedLabel")
$remainingLabel = $window.FindName("RemainingLabel")

$window.Show()

# 時間計測開始
$startTime = Get-Date
$totalItems = 100

for ($i = 1; $i -le $totalItems; $i++) {
    $progress = [math]::Round(($i / $totalItems) * 100, 1)
    
    # プログレスバーとテキストの更新
    $progressBar.Value = $progress
    $progressText.Text = "$progress%"
    
    # ファイル情報の更新
    $fileLabel.Text = "処理中: サンプルファイル_$i.txt"
    
    # 詳細情報の更新
    $infoLabel.Text = "進捗: $i/$totalItems アイテム`n完了率: $progress%`n現在の処理: データ変換中..."
    
    # 時間計算
    $elapsed = (Get-Date) - $startTime
    $elapsedLabel.Text = $elapsed.ToString("hh\:mm\:ss")
    
    if ($i -gt 1) {
        $avgTimePerItem = $elapsed.TotalSeconds / $i
        $remainingItems = $totalItems - $i
        $estimatedRemaining = [TimeSpan]::FromSeconds($avgTimePerItem * $remainingItems)
        $remainingLabel.Text = $estimatedRemaining.ToString("hh\:mm\:ss")
    }
    
    # UIの更新
    $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})
    Start-Sleep -Milliseconds 100
}

$fileLabel.Text = "処理完了！"
$infoLabel.Text = "すべてのアイテムの処理が正常に完了しました。"
$progressText.Text = "完了"
Start-Sleep -Seconds 3
$window.Close()
```

画面例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/1fd01b4b-230c-4300-898b-7774a1c91359.png)

## 複数プログレスバーのWPF版

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/19bd785e-afc9-43e1-93aa-c1fb76c9e277.png)

```powershell
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase

$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="複数プログレスバー - WPF版" 
        Width="650" Height="450"
        WindowStartupLocation="CenterScreen"
        Background="White">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- タイトル -->
        <TextBlock Grid.Row="0" 
                   Text="並列処理進捗モニター" 
                   FontSize="18" FontWeight="Bold"
                   HorizontalAlignment="Center"
                   Foreground="DarkSlateGray"/>
        
        <!-- タスク1 -->
        <TextBlock Grid.Row="2" Name="Task1Label" 
                   Text="タスク1: データベース処理" 
                   FontSize="12" FontWeight="SemiBold"/>
        <ProgressBar Grid.Row="3" Name="ProgressBar1"
                     Height="20" Minimum="0" Maximum="50" Value="0"
                     Background="LightGray" Foreground="ForestGreen"/>
        
        <!-- タスク2 -->
        <TextBlock Grid.Row="5" Name="Task2Label" 
                   Text="タスク2: ファイル変換" 
                   FontSize="12" FontWeight="SemiBold"/>
        <ProgressBar Grid.Row="6" Name="ProgressBar2"
                     Height="20" Minimum="0" Maximum="75" Value="0"
                     Background="LightGray" Foreground="RoyalBlue"/>
        
        <!-- タスク3 -->
        <TextBlock Grid.Row="8" Name="Task3Label" 
                   Text="タスク3: レポート生成" 
                   FontSize="12" FontWeight="SemiBold"/>
        <ProgressBar Grid.Row="9" Name="ProgressBar3"
                     Height="20" Minimum="0" Maximum="30" Value="0"
                     Background="LightGray" Foreground="OrangeRed"/>
        
        <!-- 全体進捗 -->
        <Border Grid.Row="11" 
                Background="AliceBlue" 
                BorderBrush="SteelBlue" 
                BorderThickness="2" 
                CornerRadius="5" 
                Padding="15">
            <StackPanel>
                <TextBlock Name="TotalLabel" 
                           Text="全体進捗" 
                           FontSize="14" FontWeight="Bold"
                           HorizontalAlignment="Center"
                           Margin="0,0,0,10"/>
                <Grid>
                    <ProgressBar Name="ProgressBarTotal"
                                 Height="25" Minimum="0" Maximum="155" Value="0"
                                 Background="LightGray" Foreground="Purple"/>
                    <TextBlock Name="TotalPercentLabel" 
                               Text="0%" 
                               FontSize="12" FontWeight="Bold"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Center"
                               Foreground="White"/>
                </Grid>
            </StackPanel>
        </Border>
    </Grid>
</Window>
"@

$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)

# コントロールの取得
$task1Label = $window.FindName("Task1Label")
$task2Label = $window.FindName("Task2Label")
$task3Label = $window.FindName("Task3Label")
$progressBar1 = $window.FindName("ProgressBar1")
$progressBar2 = $window.FindName("ProgressBar2")
$progressBar3 = $window.FindName("ProgressBar3")
$progressBarTotal = $window.FindName("ProgressBarTotal")
$totalPercentLabel = $window.FindName("TotalPercentLabel")

$window.Show()

# 処理状態の管理
$task1Complete = $false
$task2Complete = $false
$task3Complete = $false

# 並行処理のシミュレーション
while (-not ($task1Complete -and $task2Complete -and $task3Complete)) {
    # タスク1の処理
    if (-not $task1Complete -and $progressBar1.Value -lt $progressBar1.Maximum) {
        $progressBar1.Value++
        $progressBarTotal.Value++
        if ($progressBar1.Value -eq $progressBar1.Maximum) {
            $task1Complete = $true
            $task1Label.Text = "タスク1: データベース処理 - ✓ 完了"
            $task1Label.Foreground = "ForestGreen"
        }
    }
    
    # タスク2の処理（タスク1より少し遅い）
    if (-not $task2Complete -and $progressBar2.Value -lt $progressBar2.Maximum -and $progressBar1.Value -gt 10) {
        $progressBar2.Value++
        $progressBarTotal.Value++
        if ($progressBar2.Value -eq $progressBar2.Maximum) {
            $task2Complete = $true
            $task2Label.Text = "タスク2: ファイル変換 - ✓ 完了"
            $task2Label.Foreground = "RoyalBlue"
        }
    }
    
    # タスク3の処理（タスク1, 2がある程度進んでから開始）
    if (-not $task3Complete -and $progressBar3.Value -lt $progressBar3.Maximum -and $progressBar1.Value -gt 25 -and $progressBar2.Value -gt 35) {
        $progressBar3.Value++
        $progressBarTotal.Value++
        if ($progressBar3.Value -eq $progressBar3.Maximum) {
            $task3Complete = $true
            $task3Label.Text = "タスク3: レポート生成 - ✓ 完了"
            $task3Label.Foreground = "OrangeRed"
        }
    }
    
    # 全体進捗率の計算と表示
    $totalProgress = [math]::Round(($progressBarTotal.Value / $progressBarTotal.Maximum) * 100, 1)
    $totalPercentLabel.Text = "$totalProgress%"
    
    # UIの更新
    $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})
    Start-Sleep -Milliseconds 150
}

# 完了メッセージ
$totalPercentLabel.Text = "100% - 全完了！"
Start-Sleep -Seconds 3
$window.Close()
```

画面例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/55e5c3e1-873a-41c3-9ec5-42d318ae1d32.png)

## 実用的なファイル処理関数

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6bd565c1-f2f0-4cd9-9fc8-beb298ef8f40.png)

```powershell
function Show-WPFFileProcessProgress {
    param(
        [string[]]$FilePaths,
        [string]$Operation = "処理"
    )
    
    Add-Type -AssemblyName PresentationFramework
    Add-Type -AssemblyName PresentationCore
    Add-Type -AssemblyName WindowsBase
    
    $xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ファイル$Operation進捗" 
        Width="600" Height="300"
        WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="15"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        
        <!-- 現在のファイル -->
        <Border Grid.Row="0" Background="LightBlue" BorderBrush="Blue" BorderThickness="1" CornerRadius="3" Padding="10">
            <TextBlock Name="FileLabel" Text="準備中..." FontSize="14" TextWrapping="Wrap"/>
        </Border>
        
        <!-- プログレスバー -->
        <Grid Grid.Row="2">
            <ProgressBar Name="ProgressBar" Height="25" Minimum="0" Value="0" Background="LightGray" Foreground="Green"/>
            <TextBlock Name="ProgressText" Text="0%" FontSize="12" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
        </Grid>
        
        <!-- 統計情報 -->
        <Border Grid.Row="4" Background="WhiteSmoke" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10">
            <TextBlock Name="StatsLabel" Text="" FontSize="12"/>
        </Border>
        
        <!-- ログエリア -->
        <Border Grid.Row="6" BorderBrush="Gray" BorderThickness="1" CornerRadius="3">
            <ScrollViewer VerticalScrollBarVisibility="Auto">
                <TextBlock Name="LogText" Margin="10" FontFamily="Consolas" FontSize="10" TextWrapping="Wrap"/>
            </ScrollViewer>
        </Border>
    </Grid>
</Window>
"@
    
    $reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xaml)
    $window = [Windows.Markup.XamlReader]::Load($reader)
    
    $fileLabel = $window.FindName("FileLabel")
    $progressBar = $window.FindName("ProgressBar")
    $progressText = $window.FindName("ProgressText")
    $statsLabel = $window.FindName("StatsLabel")
    $logText = $window.FindName("LogText")
    
    $progressBar.Maximum = $FilePaths.Count
    $window.Show()
    
    $startTime = Get-Date
    $processedSize = 0
    
    for ($i = 0; $i -lt $FilePaths.Count; $i++) {
        $currentFile = $FilePaths[$i]
        $fileName = Split-Path $currentFile -Leaf
        
        # UI更新
        $fileLabel.Text = "${Operation}中: $fileName"
        $progressBar.Value = $i + 1
        $progress = [math]::Round((($i + 1) / $FilePaths.Count) * 100, 1)
        $progressText.Text = "$progress%"
        
        # ファイルサイズの取得
        try {
            $fileSize = (Get-Item $currentFile -ErrorAction Stop).Length
            $processedSize += $fileSize
        } catch {
            $fileSize = 0
        }
        
        # 統計情報の更新
        $elapsed = (Get-Date) - $startTime
        $avgTime = if ($i -gt 0) { $elapsed.TotalSeconds / ($i + 1) } else { 0 }
        $remaining = if ($avgTime -gt 0) { [TimeSpan]::FromSeconds($avgTime * ($FilePaths.Count - $i - 1)) } else { [TimeSpan]::Zero }
        
        $statsLabel.Text = @"
進捗: $($i + 1)/$($FilePaths.Count) ファイル | 完了率: $progress%
処理済みサイズ: $([math]::Round($processedSize / 1MB, 2)) MB
経過時間: $($elapsed.ToString("hh\:mm\:ss")) | 推定残り: $($remaining.ToString("hh\:mm\:ss"))
"@
        
        # ログ追加
        $timestamp = (Get-Date).ToString("HH:mm:ss")
        $logText.Text += "[$timestamp] $Operation完了: $fileName`n"
        
        # UIの更新
        $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [System.Action]{})
        
        # 実際の処理をここに記述
        Start-Sleep -Milliseconds 200  # 処理時間のシミュレーション
    }
    
    $fileLabel.Text = "${Operation}完了！"
    $logText.Text += "[$(Get-Date -Format "HH:mm:ss")] 全ての${Operation}が完了しました。`n"
    Start-Sleep -Seconds 3
    $window.Close()
}

# 使用例
# $files = Get-ChildItem "C:\temp\*.txt"
# Show-WPFFileProcessProgress -FilePaths $files.FullName -Operation "バックアップ"
```

画面例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4cf7c4f9-3ddd-4653-bf6a-65e565fc2db0.png)

## WPF版の主な利点

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/c4429ea1-5ca1-4a26-91f3-e5339396184b.png)

WPFを使うことで、より洗練されたUIが実現できます。たとえば、グラデーションや影、角丸といった細かなスタイリングが可能で、視覚的にも見栄えの良いデザインが作れます。また、GridやStackPanelを使った柔軟なレイアウトにより、画面サイズに応じたレスポンシブな配置が簡単に行えます。

さらに、スムーズなアニメーション効果や高DPI対応によって、動きのある表現や高解像度環境での見やすさも確保できます。XAMLを用いることで、UIデザインと処理ロジックを分離できる点も、開発効率や保守性の向上につながります。

## 注意点

WPFを使うことで、よりプロフェッショナルで洗練されたプログレスバーを作成できます。特に複雑な処理や長時間の作業を伴う場面では、ユーザーの操作感や視認性が大きく向上します。

ただし、いくつか注意点もあります。まず、PresentationFramework など必要なアセンブリを適切に読み込む必要があります。UIの更新を行う際には、Dispatcher.Invoke を用いてUIスレッド上での処理を行う必要があり、XAMLの記述も正確であることが求められます。また、大量のUI更新が発生するケースでは、適切なタイミングでの更新制御を行うことで、パフォーマンスの低下を防ぐことが重要です。

## 参考情報

https://schoolofwpf.wordpress.com/the-essentials/your-first-wpf-window/

https://learn.microsoft.com/en-us/dotnet/api/system.windows.markup.xamlreader?view=windowsdesktop-9.0

https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher.invoke?view=windowsdesktop-9.0