Public/Invoke-IntuneRemediation.ps1

function Invoke-IntuneRemediation {
<#
.SYNOPSIS
    Trigger Intune Proactive Remediation scripts on-demand for single or multiple devices.
 
.DESCRIPTION
    IROD (Intune Remediation On Demand) connects to Microsoft Graph and lets you trigger
    proactive remediation scripts immediately on target devices, rather than waiting for
    the scheduled run time.
 
    Features:
    - Single device or multi-device remediation
    - Import devices from CSV/TXT files for bulk operations
    - Script preview (view detection/remediation code)
    - Favorite scripts for quick access
    - Parallel execution for large batches (50+ devices)
    - History logging with 30-day retention
    - Export remediation results to CSV
 
.PARAMETER DeviceName
    The name of a specific device to run remediation on. When specified, runs in single device mode.
 
.PARAMETER MultiDevice
    Switch to enable multi-device mode with a WPF GUI for selecting multiple devices.
 
.PARAMETER ExportResults
    Switch to export remediation results for a script to CSV.
 
.PARAMETER ClientId
    Client ID of the app registration to use for authentication.
    If not provided, checks IROD_CLIENTID environment variable.
 
.PARAMETER TenantId
    Tenant ID to use with the specified app registration.
    If not provided, checks IROD_TENANTID environment variable.
 
.PARAMETER Help
    Shows detailed cmdlet help.
 
.EXAMPLE
    Invoke-IntuneRemediation
     
    Runs in interactive mode with menu options:
    [1] Single Device - Enter device name
    [2] Multi-Device - GUI selection
    [3] Import from File - Load from CSV/TXT
    [4] Export Results - Export to CSV
    [5] View History - See past remediations
    [H] Help - Documentation
 
.EXAMPLE
    Invoke-IntuneRemediation -DeviceName "DESKTOP-ABC123"
     
    Runs remediation on a single device by name.
 
.EXAMPLE
    Invoke-IntuneRemediation -MultiDevice
     
    Opens WPF GUI to search, filter, and select multiple devices.
 
.EXAMPLE
    Invoke-IntuneRemediation -ExportResults
     
    Exports remediation results (detection state, output, errors) to CSV.
 
.EXAMPLE
    Invoke-IntuneRemediation -ClientId "12345-..." -TenantId "67890-..."
     
    Uses specified app registration for authentication instead of interactive login.
 
.NOTES
    Author: IROD Project
    Version: 1.0.0
     
    Requirements:
    - PowerShell 5.1 or later
    - Microsoft.Graph.Authentication module (auto-installed if missing)
    - Graph permissions: DeviceManagementManagedDevices.ReadWrite.All,
      DeviceManagementConfiguration.Read.All
 
    Files:
    - Favorites: %APPDATA%\IROD\favorites.json
    - History: C:\Windows\Temp\IROD_history.json (30-day retention)
 
    For detailed help, run the tool and press H for interactive documentation.
 
.LINK
    https://github.com/markorr321/IROD
#>


    [CmdletBinding()]
    param(
        [string]$DeviceName,
        [switch]$MultiDevice,
        [switch]$ExportResults,
        [string]$ClientId,
        [string]$TenantId,
        [switch]$Help
    )

    # Display help if requested
    if ($Help) {
        Get-Help Invoke-IntuneRemediation -Detailed
        return
    }

    # Check for module updates
    Test-IRODUpdate

    # Check for environment variables if parameters not provided
    if ([string]::IsNullOrWhiteSpace($ClientId)) {
        $ClientId = $env:IROD_CLIENTID
    }
    if ([string]::IsNullOrWhiteSpace($TenantId)) {
        $TenantId = $env:IROD_TENANTID
    }

    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan -NoNewline
    Write-Host " v1.0.2" -ForegroundColor DarkGray
    Write-Host " with PowerShell" -ForegroundColor DarkCyan
    Write-Host ""

    # First-run theme selection
    if (-not (Test-IRODThemeConfigured)) {
        Write-Host "Welcome! Choose your preferred UI theme:" -ForegroundColor Cyan
        Write-Host " [D] Dark (default)" -ForegroundColor Green
        Write-Host " [L] Light" -ForegroundColor Green
        Write-Host ""
        $themeChoice = Read-Host "Enter choice (D/L)"
        if ($themeChoice -eq 'L' -or $themeChoice -eq 'l') {
            Set-IRODTheme -Theme 'Light'
        } else {
            Set-IRODTheme -Theme 'Dark'
        }
        Write-Host ""
    }

    # Determine execution mode FIRST
$executionMode = $null

if ($ExportResults) {
    $executionMode = 'ExportResults'
    Write-Host "[Mode] Export Results (from parameter)" -ForegroundColor Gray
}
elseif ($MultiDevice) {
    $executionMode = 'MultiDevice'
    Write-Host "[Mode] Multi-Device (from parameter)" -ForegroundColor Gray
}
elseif ($DeviceName) {
    $executionMode = 'SingleDevice'
    Write-Host "[Mode] Single Device: $DeviceName (from parameter)" -ForegroundColor Gray
}
else {
    # Prompt user to choose mode
    Write-Host " [1] Single Device" -ForegroundColor Green
    Write-Host " Run remediation on one specific device" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [2] Multi-Device" -ForegroundColor Green
    Write-Host " Select multiple devices via GUI" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [3] Import from File" -ForegroundColor Green
    Write-Host " Load device names from CSV or TXT file" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [4] Export Results" -ForegroundColor Green
    Write-Host " Export remediation results to CSV" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [5] View History" -ForegroundColor Green
    Write-Host " View recent remediation history" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [H] Help" -ForegroundColor Cyan
    Write-Host " View documentation and tips" -ForegroundColor Magenta
    Write-Host ""
    Write-Host " [Q] Quit" -ForegroundColor Red
    Write-Host ""

    do {
        $choice = Read-Host "Enter choice (1-5, H, or Q)"
        if ($choice -eq 'Q' -or $choice -eq 'q') {
            # Check if there's an active Graph session and disconnect
            try {
                $context = Get-MgContext -ErrorAction SilentlyContinue
                if ($context) {
                    Write-Host "`nDisconnecting from Microsoft Graph..." -ForegroundColor Red
                    Disconnect-MgGraph | Out-Null
                    Write-Host "Disconnected." -ForegroundColor Green
                }
            }
            catch {
                # Silently continue if there's an issue checking/disconnecting
            }
            Write-Host "Exiting." -ForegroundColor Gray
            return
        }
        if ($choice -eq 'H' -or $choice -eq 'h') {
            # Show help and loop back
            do {
                $showHelpAgain = Show-IRODHelp
            } while ($showHelpAgain -eq $true)
            
            # Restart to show menu again
            Invoke-IntuneRemediation
            return
        }
        if ($choice -eq '5') {
            Clear-Host
            Write-Host ""
            Write-Host "[ I R O D ]" -ForegroundColor Cyan
            Show-IRODHistory -Limit 20
            Write-Host ""
            Write-Host " [E] Export history to CSV" -ForegroundColor Green
            Write-Host " [Enter] Return to menu" -ForegroundColor Gray
            Write-Host ""
            $historyChoice = Read-Host "Choice"
            
            if ($historyChoice -eq 'E' -or $historyChoice -eq 'e') {
                $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                $defaultFileName = "IROD_History_$timestamp.csv"
                Write-Host ""
                Write-Host "Opening save dialog..." -ForegroundColor Cyan
                
                $exportPath = Show-SaveFileDialog -DefaultFileName $defaultFileName -Title "Export History"
                
                if (-not $exportPath) {
                    Write-Host " Export cancelled." -ForegroundColor Red
                }
                else {
                    Export-IRODHistory -Path $exportPath | Out-Null
                }
                Write-Host ""
                Write-Host "Press Enter to continue..." -ForegroundColor Gray
                Read-Host | Out-Null
            }
            
            # Restart to show menu again
            Invoke-IntuneRemediation
            return
        }
    } while ($choice -ne '1' -and $choice -ne '2' -and $choice -ne '3' -and $choice -ne '4')

    if ($choice -eq '4') {
        $executionMode = 'ExportResults'
        Clear-Host
        Write-Host ""
        Write-Host "[ I R O D ]" -ForegroundColor Cyan
        Write-Host ""
    }    elseif ($choice -eq '3') {
        $executionMode = 'ImportFromFile'
        Clear-Host
        Write-Host ""
        Write-Host "[ I R O D ]" -ForegroundColor Cyan
        Write-Host ""
        Write-Host "Import Devices from File" -ForegroundColor Cyan
        Write-Host ""
        Write-Host " Supported formats:" -ForegroundColor Gray
        Write-Host " - CSV with column: DeviceName, Name, ComputerName, or Device" -ForegroundColor Gray
        Write-Host " - TXT with one device name per line" -ForegroundColor Gray
        Write-Host ""
        Write-Host " [1] Import from CSV" -ForegroundColor Green
        Write-Host " [2] Export template CSV" -ForegroundColor Green
        Write-Host " [B] Back to main menu" -ForegroundColor Gray
        Write-Host ""
        
        $importChoice = Read-Host "Choice"
        
        if ($importChoice -eq 'B' -or $importChoice -eq 'b') {
            Invoke-IntuneRemediation
            return
        }
        
        if ($importChoice -eq '2') {
            Write-Host ""
            Write-Host "Opening save dialog..." -ForegroundColor Cyan
            
            $templatePath = Show-SaveFileDialog -DefaultFileName "IROD_Import_Template.csv" -Title "Save Import Template"
            
            if (-not $templatePath) {
                Write-Host " Template export cancelled." -ForegroundColor Red
            }
            else {
                # Create template CSV with example entries
                $templateContent = @"
DeviceName
DESKTOP-EXAMPLE1
LAPTOP-EXAMPLE2
PC-EXAMPLE3
"@

                $templateContent | Set-Content -Path $templatePath -Encoding UTF8
                Write-Host ""
                Write-Host " Template exported to: $templatePath" -ForegroundColor Green
                Write-Host ""
                Write-Host " Edit the file with your device names (one per row)," -ForegroundColor Gray
                Write-Host " then run Import from File again." -ForegroundColor Gray
            }
            Write-Host ""
            Write-Host "Press Enter to continue..." -ForegroundColor Gray
            Read-Host | Out-Null
            Invoke-IntuneRemediation
            return
        }
        
        if ($importChoice -ne '1') {
            Write-Host "`nInvalid choice." -ForegroundColor Red
            Write-Host ""
            Write-Host "Press Enter to continue..." -ForegroundColor Gray
            Read-Host | Out-Null
            Invoke-IntuneRemediation
            return
        }
        
        Write-Host ""
        Write-Host "Opening file dialog..." -ForegroundColor Cyan
        
        $script:importFilePath = Show-OpenFileDialog -Title "Select Device Import File"
        
        if (-not $script:importFilePath) {
            Write-Host "`nNo file selected. Cancelled." -ForegroundColor Red
            Write-Host ""
            Write-Host "Press Enter to continue..." -ForegroundColor Gray
            Read-Host | Out-Null
            Invoke-IntuneRemediation
            return
        }
        
        Write-Host " File: $script:importFilePath" -ForegroundColor Green
        Write-Host ""
    }    elseif ($choice -eq '1') {
        $executionMode = 'SingleDevice'
        Clear-Host
        Write-Host ""
        Write-Host "[ I R O D ]" -ForegroundColor Cyan
        Write-Host ""
        $DeviceName = Read-Host "Enter device name"
        if ([string]::IsNullOrWhiteSpace($DeviceName)) {
            Write-Host "`nError: No device name provided." -ForegroundColor Red
            Write-Host "Exiting." -ForegroundColor Gray
            return
        }
        Write-Host "Target device: $DeviceName" -ForegroundColor Green
    }
    else {
        $executionMode = 'MultiDevice'
        Clear-Host
        Write-Host ""
        Write-Host "[ I R O D ]" -ForegroundColor Cyan
        Write-Host ""
    }
}

# Connect
if (-not (Connect-ToGraph -ClientId $ClientId -TenantId $TenantId)) {
    Write-Host "`nAuthentication failed. Exiting." -ForegroundColor Red
    return
}

# Get and display scripts
Write-Host ""
Write-Host "Loading Remediation Scripts" -ForegroundColor Cyan

$scripts = Get-RemediationScripts

if ($scripts.Count -eq 0) {
    Write-Host "`nNo remediation scripts found in Intune." -ForegroundColor Yellow
    Write-Host "Disconnecting..." -ForegroundColor Red
    Disconnect-MgGraph | Out-Null
    return
}

Write-Host "Found $($scripts.Count) remediation script$(if($scripts.Count -ne 1){'s'})" -ForegroundColor Green

# Select script via WPF GUI
Write-Host ""
Write-Host "Select Remediation Script" -ForegroundColor Cyan
Write-Host "Opening script selection window..." -ForegroundColor Gray
$scriptItems = $scripts | Select-Object displayName, id, description, publisher, version | Sort-Object displayName

$columns = @(
    @{ Header = "Script Name"; Property = "displayName"; Width = 380 }
    @{ Header = "Publisher"; Property = "publisher"; Width = 140 }
    @{ Header = "Version"; Property = "version"; Width = 70 }
    @{ Header = "ID"; Property = "id"; Width = 280 }
)

$selectedScript = Show-GridSelector -Items $scriptItems -Title "Select Remediation Script" -Columns $columns -ShowPreview -ShowFavorites -TooltipProperty "description"

if ($script:exitRequested) {
    Write-Host "`nExit requested. Disconnecting from Microsoft Graph..." -ForegroundColor Yellow
    Disconnect-MgGraph | Out-Null
    Write-Host "Disconnected. Goodbye!" -ForegroundColor Red
    return
}

if (-not $selectedScript) {
    Write-Host "`nCancelled. Disconnecting from Microsoft Graph..." -ForegroundColor Red
    Disconnect-MgGraph | Out-Null
    Write-Host "Disconnected." -ForegroundColor Red
    return
}

# Handle based on mode
if ($executionMode -eq 'ExportResults') {
    # Export results mode
    Write-Host ""
    Write-Host "Selected Script: $($selectedScript.displayName)" -ForegroundColor Green
    Write-Host ""
    
    # Prompt for output path using Save File Dialog
    $timestamp = Get-Date -Format "yyyyMMdd_hhmmsstt"
    $defaultFilename = "$($selectedScript.displayName -replace '[\\/:*?"<>|]', '_')_Results_$timestamp.csv"
    Write-Host "Opening save dialog..." -ForegroundColor Cyan
    
    $csvPath = Show-SaveFileDialog -DefaultFileName $defaultFilename -Title "Export Remediation Results"
    
    if (-not $csvPath) {
        Write-Host "`nExport cancelled. Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }
    
    Write-Host ""
    Write-Host "Exporting results for: $($selectedScript.displayName)" -ForegroundColor Cyan
    Write-Host "Output file: $csvPath" -ForegroundColor Gray
    Write-Host ""
    
    # Call the export function using the script ID
    try {
        # Get device run states for this remediation with pagination
        Write-Host "Retrieving device run states..." -ForegroundColor Cyan
        $runStatesUri = "$script:GraphBaseUrl/deviceManagement/deviceHealthScripts/$($selectedScript.id)/deviceRunStates?`$expand=managedDevice"
        
        $deviceRunStates = @()
        $nextLink = $runStatesUri
        
        do {
            $response = Invoke-Graph -Uri $nextLink
            if ($response.value) {
                $deviceRunStates += $response.value
            }
            $nextLink = $response.'@odata.nextLink'
        } while ($nextLink)
        
        if ($deviceRunStates.Count -eq 0) {
            Write-Warning "No device run states found for this remediation."
        }
        else {
            Write-Host "Found $($deviceRunStates.Count) device run state(s)" -ForegroundColor Green
            
            # Process results into a more readable format
            $results = @()
            $counter = 0
            foreach ($runState in $deviceRunStates) {
                $counter++
                Write-Progress -Activity "Processing device run states" -Status "Device $counter of $($deviceRunStates.Count)" -PercentComplete (($counter / $deviceRunStates.Count) * 100)
                
                # Get device details from expanded managedDevice property
                $deviceName = "Unknown"
                $deviceUser = "Unknown"
                $deviceId = $null
                
                if ($runState.managedDevice) {
                    $deviceName = $runState.managedDevice.deviceName
                    $deviceUser = $runState.managedDevice.userPrincipalName
                    $deviceId = $runState.managedDevice.id
                }
                else {
                    # Fallback: Try to get device ID from managedDeviceId property
                    if ($runState.managedDeviceId) {
                        $deviceId = $runState.managedDeviceId
                    }
                    # Or extract from the run state ID
                    elseif ($runState.id) {
                        if ($runState.id.Contains("_")) {
                            $deviceId = $runState.id.Split("_")[1]
                        }
                        elseif ($runState.id.Contains(":")) {
                            $deviceId = $runState.id.Split(":")[1]
                        }
                    }
                    
                    if ($deviceId) {
                        try {
                            $deviceUri = "$script:GraphBaseUrl/deviceManagement/managedDevices/$deviceId"
                            $deviceDetails = Invoke-Graph -Uri $deviceUri
                            $deviceName = $deviceDetails.deviceName
                            $deviceUser = $deviceDetails.userPrincipalName
                        }
                        catch {
                            Write-Verbose "Could not retrieve device details for $deviceId"
                        }
                    }
                }
                
                $resultObject = [PSCustomObject]@{
                    DeviceName = $deviceName
                    UserPrincipalName = $deviceUser
                    DetectionState = $runState.detectionState
                    LastStateUpdateDateTime = $runState.lastStateUpdateDateTime
                    PreRemediationDetectionScriptOutput = $runState.preRemediationDetectionScriptOutput
                    RemediationState = $runState.remediationState
                    PostRemediationDetectionScriptOutput = $runState.postRemediationDetectionScriptOutput
                    RemediationScriptErrorDetails = $runState.remediationScriptErrorDetails
                    DetectionScriptErrorDetails = $runState.detectionScriptErrorDetails
                    ManagedDeviceId = $deviceId
                }
                
                $results += $resultObject
            }
            
            Write-Progress -Activity "Processing device run states" -Completed
            
            # Export to CSV
            Write-Host "Exporting results to: $csvPath" -ForegroundColor Cyan
            $results | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
            
            Write-Host "`nExport completed successfully!" -ForegroundColor Green
            Write-Host "Total records exported: $($results.Count)" -ForegroundColor Cyan
            
            # Display summary
            Write-Host "`nSummary:" -ForegroundColor Yellow
            $detectionStates = $results | Group-Object DetectionState
            foreach ($state in $detectionStates) {
                Write-Host " $($state.Name): $($state.Count)" -ForegroundColor Gray
            }
        }
    }
    catch {
        Write-Error "An error occurred during export: $_"
    }
    
    # Prompt to run again or exit
    Write-Host ""
    Write-Host "Next Action" -ForegroundColor Cyan
    Write-Host ""
    Write-Host " [R] Run again" -ForegroundColor Green
    Write-Host " [X] Exit" -ForegroundColor Red
    Write-Host ""
    
    $runAgain = Read-Host "Choice (R/X)"
    
    if ($runAgain -eq 'R' -or $runAgain -eq 'r') {
        Write-Host ""
        Write-Host "Restarting tool..." -ForegroundColor Cyan
        Write-Host ""
        Invoke-IntuneRemediation
    }
    else {
        Write-Host ""
        Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        Write-Host "Disconnected." -ForegroundColor Green
        Write-Host ""
    }
    return
}
elseif ($executionMode -eq 'MultiDevice') {
    # Multi-device mode with WPF GUI
    $allDevices = Get-AllManagedDevices

    if ($allDevices.Count -eq 0) {
        Write-Host "`nNo Windows devices found in Intune." -ForegroundColor Yellow
        Write-Host "Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }

    $selectedDevices = Show-DeviceSelectionGui -AllDevices $allDevices

    if ($script:exitRequested) {
        Write-Host "`nExit requested. Disconnecting from Microsoft Graph..." -ForegroundColor Yellow
        Disconnect-MgGraph | Out-Null
        Write-Host "Disconnected. Goodbye!" -ForegroundColor Red
        return
    }

    if (-not $selectedDevices -or $selectedDevices.Count -eq 0) {
        Write-Host "`nNo devices selected. Cancelled. Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        Write-Host "Disconnected." -ForegroundColor Red
        return
    }

    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Devices Selected" -ForegroundColor Green
    Write-Host "Selected $($selectedDevices.Count) device$(if($selectedDevices.Count -ne 1){'s'}) for remediation" -ForegroundColor White
    Write-Host ""
    Write-Host "Confirm Remediation" -ForegroundColor Yellow
    Write-Host " Script: " -NoNewline -ForegroundColor Gray
    Write-Host $selectedScript.displayName -ForegroundColor White
    Write-Host " Devices: " -NoNewline -ForegroundColor Gray
    Write-Host "$($selectedDevices.Count) selected" -ForegroundColor White
    Write-Host ""
    Write-Host " This will immediately trigger the remediation script on all selected devices." -ForegroundColor Red
    Write-Host ""

    # Require more verbose confirmation if "Select ALL Devices" was used
    if ($script:AllDevicesSelected) {
        Write-Host " WARNING: You selected ALL devices. This is a high-impact action." -ForegroundColor Yellow
        Write-Host ""
        $expectedPhrase = "I confirm remediation on all $($selectedDevices.Count) devices"
        Write-Host " To proceed, type the following phrase exactly:" -ForegroundColor Cyan
        Write-Host " $expectedPhrase" -ForegroundColor White
        Write-Host ""
        $confirm = Read-Host "Confirmation phrase"

        if ($confirm -ne $expectedPhrase) {
            Write-Host "`nConfirmation phrase did not match. Cancelled." -ForegroundColor Red
            Write-Host "Disconnecting..." -ForegroundColor Red
            Disconnect-MgGraph | Out-Null
            return
        }
    }
    else {
        $confirm = Read-Host "Type YES to proceed"

        if ($confirm -ne 'YES') {
            Write-Host "`nCancelled. Disconnecting..." -ForegroundColor Red
            Disconnect-MgGraph | Out-Null
            return
        }
    }

    # Log to history
    $deviceNamesList = @($selectedDevices | ForEach-Object { $_.DeviceName })
    $currentUser = try { (Get-MgContext).Account } catch { $env:USERNAME }
    Add-IRODHistoryEntry -ScriptId $selectedScript.id -ScriptName $selectedScript.displayName -DeviceNames $deviceNamesList -DeviceCount $selectedDevices.Count -ExecutedBy $currentUser

    # Show progress GUI
    Show-ProgressGui -Devices $selectedDevices -ScriptName $selectedScript.displayName -ScriptId $selectedScript.id

    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Remediation Completed" -ForegroundColor Green
    Write-Host "All devices have been processed." -ForegroundColor White
}
elseif ($executionMode -eq 'ImportFromFile') {
    # Import from file mode - file path already validated at menu selection
    Write-Host "Loading Intune Devices" -ForegroundColor Cyan
    $allDevices = Get-AllManagedDevices
    
    if ($allDevices.Count -eq 0) {
        Write-Host "`nNo Windows devices found in Intune." -ForegroundColor Yellow
        Write-Host "Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }
    
    Write-Host " Found $($allDevices.Count) devices in Intune" -ForegroundColor Green
    Write-Host ""
    Write-Host "Importing from File" -ForegroundColor Cyan
    
    $selectedDevices = Import-DevicesFromFile -FilePath $script:importFilePath -AllDevices $allDevices
    
    if (-not $selectedDevices -or $selectedDevices.Count -eq 0) {
        Write-Host "`nNo devices to process. Cancelled." -ForegroundColor Red
        Write-Host "Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }
    
    Write-Host ""
    Write-Host "Confirm Remediation" -ForegroundColor Yellow
    Write-Host " Script: " -NoNewline -ForegroundColor Gray
    Write-Host $selectedScript.displayName -ForegroundColor White
    Write-Host " Devices: " -NoNewline -ForegroundColor Gray
    Write-Host "$($selectedDevices.Count) matched from file" -ForegroundColor White
    Write-Host ""
    Write-Host " This will immediately trigger the remediation script on all matched devices." -ForegroundColor Red
    Write-Host ""
    
    $confirm = Read-Host "Type YES to proceed"
    
    if ($confirm -ne 'YES') {
        Write-Host "`nCancelled. Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }
    
    # Log to history
    $deviceNamesList = @($selectedDevices | ForEach-Object { $_.DeviceName })
    $currentUser = try { (Get-MgContext).Account } catch { $env:USERNAME }
    Add-IRODHistoryEntry -ScriptId $selectedScript.id -ScriptName $selectedScript.displayName -DeviceNames $deviceNamesList -DeviceCount $selectedDevices.Count -ExecutedBy $currentUser
    
    # Show progress GUI
    Show-ProgressGui -Devices $selectedDevices -ScriptName $selectedScript.displayName -ScriptId $selectedScript.id
    
    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Remediation Completed" -ForegroundColor Green
    Write-Host "All devices have been processed." -ForegroundColor White
}
else {
    # Single device mode
    Write-Host ""
        Write-Host "Looking Up Device" -ForegroundColor Cyan
    
    $device = Get-DeviceByName -Name $DeviceName

    if (-not $device) {
        Write-Host "`nError: Device '$DeviceName' not found in Intune." -ForegroundColor Red
        Write-Host "Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }

    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Device Found" -ForegroundColor Green
    Write-Host "Selected device for remediation:" -ForegroundColor White
    Write-Host ""
    Write-Host " • $($device.deviceName)" -ForegroundColor White
    Write-Host " User: $($device.userPrincipalName)" -ForegroundColor DarkGray
    Write-Host ""
    Write-Host "Confirm Remediation" -ForegroundColor Yellow
    Write-Host " Script: " -NoNewline -ForegroundColor Gray
    Write-Host $selectedScript.displayName -ForegroundColor White
    Write-Host " Device: " -NoNewline -ForegroundColor Gray
    Write-Host $device.deviceName -ForegroundColor White
    Write-Host ""
    Write-Host " This will immediately trigger the remediation script on this device." -ForegroundColor Red
    Write-Host ""

    $confirm = Read-Host "Type YES to proceed"

    if ($confirm -ne 'YES') {
        Write-Host "`nCancelled. Disconnecting..." -ForegroundColor Red
        Disconnect-MgGraph | Out-Null
        return
    }

    # Log to history
    $currentUser = try { (Get-MgContext).Account } catch { $env:USERNAME }
    Add-IRODHistoryEntry -ScriptId $selectedScript.id -ScriptName $selectedScript.displayName -DeviceNames @($device.deviceName) -DeviceCount 1 -ExecutedBy $currentUser

    # Create device object for progress GUI
    $deviceForGui = [PSCustomObject]@{
        Id = $device.id
        DeviceName = $device.deviceName
    }

    # Show progress GUI
    Show-ProgressGui -Devices @($deviceForGui) -ScriptName $selectedScript.displayName -ScriptId $selectedScript.id

    Clear-Host
    Write-Host ""
    Write-Host "[ I R O D ]" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Remediation Completed" -ForegroundColor Green
    Write-Host "Device has been processed." -ForegroundColor White
}

# Prompt to run again or exit
Write-Host ""
Write-Host "Next Action" -ForegroundColor Cyan
Write-Host ""
Write-Host " [R] Run again" -ForegroundColor Green
Write-Host " [X] Exit" -ForegroundColor Red
Write-Host ""

$runAgain = Read-Host "Choice (R/X)"

if ($runAgain -eq 'R' -or $runAgain -eq 'r') {
    Write-Host ""
    Write-Host "Restarting tool..." -ForegroundColor Cyan
    Write-Host ""
    Invoke-IntuneRemediation
}
else {
    Write-Host ""
    Write-Host "Disconnecting from Microsoft Graph..." -ForegroundColor Red
    Disconnect-MgGraph | Out-Null
    Write-Host "Disconnected." -ForegroundColor Green
    Write-Host ""
}
}