Public/Export-PatchReport.ps1

function Export-PatchReport {
    <#
    .SYNOPSIS
        Exports a patch status report
    .DESCRIPTION
        Generates a comprehensive report of patch status for compliance reporting
    .PARAMETER Path
        Output file path (supports .json, .csv, .html)
    .PARAMETER Format
        Output format: JSON, CSV, HTML, or Console
    .PARAMETER IncludeHistory
        Include installation history
    .EXAMPLE
        Export-PatchReport -Path "C:\Reports\patch_status.json"
    .EXAMPLE
        Export-PatchReport -Format Console
        Displays report to console
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Path,
        
        [Parameter()]
        [ValidateSet('JSON', 'CSV', 'HTML', 'Console')]
        [string]$Format = 'JSON',
        
        [Parameter()]
        [switch]$IncludeHistory
    )
    
    try {
        Write-PatchLog "Generating patch report..." -Type Info
        
        # Gather data
        $updates = Get-PatchStatus -ManagedOnly
        $compliance = Get-PatchCompliance
        $schedule = Get-PatchSchedule
        $deferralStates = Get-AllDeferralStates
        
        $report = [PSCustomObject]@{
            ReportTimestamp   = [datetime]::UtcNow.ToString('o')
            ComputerName      = $env:COMPUTERNAME
            OSVersion         = [Environment]::OSVersion.Version.ToString()
            WingetVersion     = $(try { Get-WinGetVersion -ErrorAction SilentlyContinue } catch { 'Not Installed' })
            Compliance        = @{
                IsCompliant      = $compliance.Compliant
                TotalPending     = $compliance.TotalPending
                CriticalPending  = $compliance.CriticalPending
                HighPending      = $compliance.HighPending
                PastDeadline     = $compliance.PastDeadline
            }
            PendingUpdates    = @($updates | ForEach-Object {
                @{
                    AppId            = $_.AppId
                    AppName          = $_.AppName
                    InstalledVersion = $_.InstalledVersion
                    AvailableVersion = $_.AvailableVersion
                    Priority         = $_.Priority.ToString()
                    ProcessesRunning = $_.ProcessesRunning
                }
            })
            DeferralStatus    = @($deferralStates | ForEach-Object {
                @{
                    AppId           = $_.AppId
                    DeferralCount   = $_.DeferralCount
                    MaxDeferrals    = $_.MaxDeferrals
                    Phase           = $_.Phase.ToString()
                    DeadlineDate    = $_.DeadlineDate.ToString('o')
                }
            })
            ScheduledTasks    = @($schedule | ForEach-Object {
                @{
                    TaskName    = $_.TaskName
                    State       = $_.State.ToString()
                    LastRunTime = $_.LastRunTime
                    NextRunTime = $_.NextRunTime
                }
            })
        }
        
        # Add history if requested
        if ($IncludeHistory) {
            $report | Add-Member -NotePropertyName 'History' -NotePropertyValue @(
                Get-PatchMyPCLogs -Days 30 -Type Info | 
                    Where-Object { $_.Component -like '*Install*' -or $_.Component -like '*Update*' } |
                    Select-Object -First 100 |
                    ForEach-Object {
                        @{
                            Timestamp = $_.Timestamp.ToString('o')
                            Type      = $_.Type
                            Message   = $_.Message
                        }
                    }
            )
        }
        
        # Output based on format
        switch ($Format) {
            'JSON' {
                $json = $report | ConvertTo-Json -Depth 10
                if ($Path) {
                    $json | Out-File -FilePath $Path -Encoding UTF8 -Force
                    Write-PatchLog "Exported JSON report to $Path" -Type Info
                }
                else {
                    return $json
                }
            }
            'CSV' {
                $csvData = $updates | Select-Object AppId, AppName, InstalledVersion, AvailableVersion, Priority
                if ($Path) {
                    $csvData | Export-Csv -Path $Path -NoTypeInformation -Force
                    Write-PatchLog "Exported CSV report to $Path" -Type Info
                }
                else {
                    return $csvData
                }
            }
            'HTML' {
                $html = ConvertTo-HtmlReport -Report $report
                if ($Path) {
                    $html | Out-File -FilePath $Path -Encoding UTF8 -Force
                    Write-PatchLog "Exported HTML report to $Path" -Type Info
                }
                else {
                    return $html
                }
            }
            'Console' {
                Write-Host "`n=== PsPatchMyPC Status Report ===" -ForegroundColor Cyan
                Write-Host "Generated: $($report.ReportTimestamp)"
                Write-Host "Computer: $($report.ComputerName)"
                Write-Host ""
                
                Write-Host "Compliance Status:" -ForegroundColor Yellow
                Write-Host " Compliant: $(if($report.Compliance.IsCompliant){'Yes'}else{'No'})"
                Write-Host " Pending Updates: $($report.Compliance.TotalPending)"
                Write-Host " Critical: $($report.Compliance.CriticalPending)"
                Write-Host " Past Deadline: $($report.Compliance.PastDeadline)"
                Write-Host ""
                
                if ($report.PendingUpdates.Count -gt 0) {
                    Write-Host "Pending Updates:" -ForegroundColor Yellow
                    foreach ($upd in $report.PendingUpdates) {
                        Write-Host " - $($upd.AppName): $($upd.InstalledVersion) -> $($upd.AvailableVersion) [$($upd.Priority)]"
                    }
                }
                else {
                    Write-Host "No pending updates." -ForegroundColor Green
                }
                
                return $report
            }
        }
    }
    catch {
        Write-PatchLog "Failed to export report: $_" -Type Error
        throw
    }
}

function ConvertTo-HtmlReport {
    <#
    .SYNOPSIS
        Converts report object to HTML
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Report
    )
    
    $complianceColor = if ($Report.Compliance.IsCompliant) { '#28a745' } else { '#dc3545' }
    
    $html = @"
<!DOCTYPE html>
<html>
<head>
    <title>PsPatchMyPC Status Report</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; margin: 40px; background: #f5f5f5; }
        .container { max-width: 900px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        h1 { color: #0078d4; border-bottom: 2px solid #0078d4; padding-bottom: 10px; }
        h2 { color: #333; margin-top: 30px; }
        .compliance-badge { display: inline-block; padding: 8px 16px; border-radius: 4px; color: white; font-weight: bold; background: $complianceColor; }
        table { width: 100%; border-collapse: collapse; margin: 15px 0; }
        th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
        th { background: #0078d4; color: white; }
        tr:hover { background: #f5f5f5; }
        .stats { display: flex; gap: 20px; margin: 20px 0; }
        .stat-box { flex: 1; padding: 20px; background: #f8f9fa; border-radius: 4px; text-align: center; }
        .stat-value { font-size: 32px; font-weight: bold; color: #0078d4; }
        .stat-label { color: #666; }
        .meta { color: #666; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>PsPatchMyPC Status Report</h1>
        <p class="meta">Generated: $($Report.ReportTimestamp) | Computer: $($Report.ComputerName) | OS: $($Report.OSVersion)</p>
         
        <h2>Compliance Status</h2>
        <span class="compliance-badge">$(if($Report.Compliance.IsCompliant){'COMPLIANT'}else{'NON-COMPLIANT'})</span>
         
        <div class="stats">
            <div class="stat-box">
                <div class="stat-value">$($Report.Compliance.TotalPending)</div>
                <div class="stat-label">Pending Updates</div>
            </div>
            <div class="stat-box">
                <div class="stat-value">$($Report.Compliance.CriticalPending)</div>
                <div class="stat-label">Critical</div>
            </div>
            <div class="stat-box">
                <div class="stat-value">$($Report.Compliance.PastDeadline)</div>
                <div class="stat-label">Past Deadline</div>
            </div>
        </div>
         
        <h2>Pending Updates</h2>
        <table>
            <tr><th>Application</th><th>Installed</th><th>Available</th><th>Priority</th></tr>
            $(foreach ($upd in $Report.PendingUpdates) {
                "<tr><td>$($upd.AppName)</td><td>$($upd.InstalledVersion)</td><td>$($upd.AvailableVersion)</td><td>$($upd.Priority)</td></tr>"
            })
            $(if ($Report.PendingUpdates.Count -eq 0) { "<tr><td colspan='4'>No pending updates</td></tr>" })
        </table>
         
        <h2>Scheduled Tasks</h2>
        <table>
            <tr><th>Task</th><th>State</th><th>Last Run</th><th>Next Run</th></tr>
            $(foreach ($task in $Report.ScheduledTasks) {
                "<tr><td>$($task.TaskName)</td><td>$($task.State)</td><td>$($task.LastRunTime)</td><td>$($task.NextRunTime)</td></tr>"
            })
        </table>
    </div>
</body>
</html>
"@

    
    return $html
}

function Get-ManagedApplications {
    <#
    .SYNOPSIS
        Gets the list of managed applications
    .DESCRIPTION
        Returns applications defined in the management catalog
    .PARAMETER EnabledOnly
        Only return enabled applications
    .EXAMPLE
        Get-ManagedApplications
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$EnabledOnly
    )
    
    $apps = Get-ManagedApplicationsInternal
    
    if ($EnabledOnly) {
        $apps = $apps | Where-Object { $_.Enabled }
    }
    
    return $apps
}

function Add-ManagedApplication {
    <#
    .SYNOPSIS
        Adds an application to the managed catalog
    .DESCRIPTION
        Adds a new application with its winget ID and configuration
    .PARAMETER Id
        Winget package ID
    .PARAMETER Name
        Display name
    .PARAMETER Priority
        Update priority
    .PARAMETER ConflictingProcesses
        Processes that must be closed before update
    .EXAMPLE
        Add-ManagedApplication -Id 'Google.Chrome' -Name 'Google Chrome' -ConflictingProcesses @('chrome.exe')
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Id,
        
        [Parameter(Mandatory)]
        [string]$Name,
        
        [Parameter()]
        [ValidateSet('Critical', 'High', 'Normal', 'Low')]
        [string]$Priority = 'Normal',
        
        [Parameter()]
        [string[]]$ConflictingProcesses = @()
    )
    
    try {
        $config = Get-ModuleConfiguration
        $catalogPath = Join-Path $config.ConfigPath 'applications.json'
        
        # Load existing catalog
        $catalog = @{ applications = @() }
        if (Test-Path $catalogPath) {
            $catalog = Get-Content -Path $catalogPath -Raw | ConvertFrom-Json
        }
        
        # Check if already exists
        $existing = $catalog.applications | Where-Object { $_.id -eq $Id }
        if ($existing) {
            Write-PatchLog "Application $Id already exists in catalog" -Type Warning
            return $false
        }
        
        # Add new application
        $newApp = @{
            id = $Id
            name = $Name
            enabled = $true
            priority = $Priority
            conflictingProcesses = $ConflictingProcesses
            preScript = $null
            postScript = $null
            installArguments = $null
            requiresReboot = $false
            deferralOverride = $null
        }
        
        $catalog.applications += $newApp
        
        # Save catalog
        $catalog | ConvertTo-Json -Depth 10 | Out-File -FilePath $catalogPath -Encoding UTF8 -Force
        
        Write-PatchLog "Added $Name ($Id) to managed applications" -Type Info
        return $true
    }
    catch {
        Write-PatchLog "Failed to add application: $_" -Type Error
        return $false
    }
}

function Remove-ManagedApplication {
    <#
    .SYNOPSIS
        Removes an application from the managed catalog
    .PARAMETER Id
        Winget package ID to remove
    .EXAMPLE
        Remove-ManagedApplication -Id 'Google.Chrome'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Id
    )
    
    try {
        $config = Get-ModuleConfiguration
        $catalogPath = Join-Path $config.ConfigPath 'applications.json'
        
        if (-not (Test-Path $catalogPath)) {
            Write-PatchLog "Application catalog not found" -Type Warning
            return $false
        }
        
        $catalog = Get-Content -Path $catalogPath -Raw | ConvertFrom-Json
        $catalog.applications = @($catalog.applications | Where-Object { $_.id -ne $Id } | Where-Object { $null -ne $_ })
        
        $catalog | ConvertTo-Json -Depth 10 | Out-File -FilePath $catalogPath -Encoding UTF8 -Force
        
        Write-PatchLog "Removed $Id from managed applications" -Type Info
        return $true
    }
    catch {
        Write-PatchLog "Failed to remove application: $_" -Type Error
        return $false
    }
}