Private/Show-ProgressGui.ps1

Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase

function Show-ProgressGui {
    param(
        [array]$Devices,
        [string]$ScriptName,
        [string]$ScriptId
    )

    $xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Running Remediation" Height="450" Width="650"
        WindowStartupLocation="CenterScreen" Topmost="True"
        Background="#1E1E1E">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="#2D2D30"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="BorderBrush" Value="#3F3F46"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Padding" Value="12,6"/>
            <Setter Property="FontSize" Value="13"/>
            <Setter Property="Cursor" Value="Hand"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#3E3E42"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="Background" Value="#3E3E42"/>
                    <Setter Property="Foreground" Value="#808080"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="White"/>
        </Style>
        <Style TargetType="ProgressBar">
            <Setter Property="Background" Value="#2D2D30"/>
            <Setter Property="Foreground" Value="#0E639C"/>
            <Setter Property="BorderBrush" Value="#3F3F46"/>
            <Setter Property="BorderThickness" Value="1"/>
        </Style>
        <Style TargetType="ListBox">
            <Setter Property="Background" Value="#252526"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="BorderBrush" Value="#3F3F46"/>
            <Setter Property="BorderThickness" Value="1"/>
        </Style>
        <Style TargetType="ListBoxItem">
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Background" Value="Transparent"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="#2D2D30"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="#094771"/>
                    <Setter Property="Foreground" Value="White"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- Header -->
        <StackPanel Grid.Row="0" Margin="0,0,0,15">
            <TextBlock Text="Running Remediation Script" FontSize="16" FontWeight="Bold"/>
            <TextBlock Name="ScriptNameText" FontSize="12" Margin="0,5,0,0"/>
        </StackPanel>

        <!-- Progress Bar -->
        <StackPanel Grid.Row="1" Margin="0,0,0,15">
            <ProgressBar Name="ProgressBar" Height="20" Minimum="0" Maximum="100" Value="0"/>
            <TextBlock Name="ProgressText" Text="0 / 0 devices processed"
                       HorizontalAlignment="Center" Margin="0,8,0,0"/>
        </StackPanel>

        <!-- Results List -->
        <ListBox Name="ResultsList" Grid.Row="2" FontFamily="Consolas" FontSize="11"
                 ScrollViewer.VerticalScrollBarVisibility="Auto"/>

        <!-- Close Button -->
        <Button Name="CloseBtn" Grid.Row="3" Content="Close" Width="100" Height="30"
                HorizontalAlignment="Right" Margin="0,10,0,0" IsEnabled="False"/>
    </Grid>
</Window>
"@


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

    $scriptNameText = $window.FindName("ScriptNameText")
    $progressBar = $window.FindName("ProgressBar")
    $progressText = $window.FindName("ProgressText")
    $resultsList = $window.FindName("ResultsList")
    $closeBtn = $window.FindName("CloseBtn")

    $scriptNameText.Text = "Script: $ScriptName"
    $progressBar.Maximum = $Devices.Count

    $closeBtn.Add_Click({
        $window.Close()
    })

    # Process devices asynchronously
    $window.Add_ContentRendered({
        $total = $Devices.Count
        $completed = 0
        $succeeded = 0
        $failed = 0

        foreach ($device in $Devices) {
            $deviceName = $device.DeviceName
            $deviceId = $device.Id

            $resultsList.Items.Add("[$(Get-Date -Format 'HH:mm:ss')] Processing: $deviceName...")
            $resultsList.ScrollIntoView($resultsList.Items[$resultsList.Items.Count - 1])
            [System.Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke([Action]{}, [System.Windows.Threading.DispatcherPriority]::Background)

            try {
                Invoke-Remediation -ScriptId $ScriptId -DeviceId $deviceId
                Sync-Device -DeviceId $deviceId
                $resultsList.Items.Add("[$(Get-Date -Format 'HH:mm:ss')] SUCCESS: $deviceName - Remediation triggered and sync initiated")
                $succeeded++
            }
            catch {
                $resultsList.Items.Add("[$(Get-Date -Format 'HH:mm:ss')] FAILED: $deviceName - $($_.Exception.Message)")
                $failed++
            }

            $completed++
            $progressBar.Value = $completed
            $progressText.Text = "$completed / $total devices processed"
            $resultsList.ScrollIntoView($resultsList.Items[$resultsList.Items.Count - 1])
            [System.Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke([Action]{}, [System.Windows.Threading.DispatcherPriority]::Background)
        }

        $resultsList.Items.Add("")
        $resultsList.Items.Add("=" * 60)
        $resultsList.Items.Add("COMPLETED: $succeeded succeeded, $failed failed out of $total devices")
        $resultsList.Items.Add("=" * 60)
        $resultsList.ScrollIntoView($resultsList.Items[$resultsList.Items.Count - 1])

        $closeBtn.IsEnabled = $true
        $closeBtn.Focus()
    })

    $window.ShowDialog() | Out-Null
}