<#
.SYNOPSIS
    Installs standard business software via winget, sets Adobe PDF default, and installs Atera RMM agent.

.DESCRIPTION
    Uses winget to install the standard X9.cz MSP software bundle. Checks winget
    availability before running. Each install is logged. After Adobe Acrobat Reader,
    temporarily stops the UCPD driver (User Choice Protection Driver, present since
    Win11 23H2 / Win10 22H2 update) to allow the HKCR file association write, sets
    .pdf -> AcroRd32, then restarts UCPD. Atera RMM agent is installed for MSP
    monitoring, remote access, and ticketing integration.

.ITEMS
    7-zip-7zip-7zip: Installs 7-Zip (winget ID: 7zip.7zip). Used for archive management. Silent install with --accept-package-agreements --accept-source-agreements flags required for unattended deployment.
    adobe-acrobat-reader-64-bit-adobe-acroba: Installs Adobe Acrobat Reader DC 64-bit (Adobe.Acrobat.Reader.64-bit). Required as the default PDF viewer to prevent Edge from handling PDFs in browser mode, which limits functionality.
    openvpn-connect-openvpntechnologies-open: Installs OpenVPN Connect client. Used for client VPN access when the client network requires a VPN. The ovpn profile and credentials are configured separately per client.
    atera-agent-install: Atera RMM agent downloaded from x9.servicedesk.atera.com and installed under NT AUTHORITY\SYSTEM via a one-shot scheduled task (msiexec /qn). Running as SYSTEM registers the agent silently with no interactive MFA window, so no technician input is needed. Agent enables MSP monitoring, remote access, and ticketing integration.
    adobe-pdf-default-pdf-acrord32-po-instal: Sets .pdf -> AcroRd32 file association after Acrobat install via HKCR (system-wide, no UserChoice hash issue). UCPD driver is stopped immediately before the write and restarted after to ensure the association persists across Edge updates.
    ucpd-sys-kernel-driver-od-feb-2024-bloku: UCPD.sys (User Choice Protection Driver) is stopped before the PDF association write and restarted after. Pattern: Stop-Service ucpd -> set HKCR\.pdf -> Start-Service ucpd. Implemented in this script.
#>
param(
    [string]$ConfigPath,
    [string]$LogFile
)

. "$PSScriptRoot\common.ps1"
$Config = Load-Config $ConfigPath

# -----------------------------------------------------------------------
# Check winget availability
# -----------------------------------------------------------------------
Write-Log "Checking winget availability" -Level INFO

$wingetExe = $null
$wingetCmd = Get-Command winget -ErrorAction SilentlyContinue
if ($wingetCmd) {
    $wingetExe = $wingetCmd.Source
} else {
    # Try to find winget in known locations
    $wingetPaths = @(
        "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
        "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller*\winget.exe"
    )
    foreach ($p in $wingetPaths) {
        $found = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1
        if ($found) { $wingetExe = $found.FullName; break }
    }
}

if (-not $wingetExe) {
    Write-Log "winget not found - software installation skipped" -Level ERROR
    exit 1
}

Write-Log "winget found: $wingetExe" -Level OK

# Accept agreements upfront
& $wingetExe source update --accept-source-agreements 2>&1 | Out-Null

# -----------------------------------------------------------------------
# Install packages from config
# -----------------------------------------------------------------------
if (Get-Feature $Config "software" "wingetInstalls") {
    if (-not $Config -or -not $Config.software -or -not $Config.software.install) {
        Write-Log "No software list in config - skipping installs" -Level WARN
    } else {
        $packages = @($Config.software.install)
        Write-Log "Installing $($packages.Count) packages in parallel" -Level INFO

        # Launch all winget installs as parallel background jobs
        $jobs = @()
        foreach ($pkg in $packages) {
            Write-Log "  Starting: $($pkg.name) ($($pkg.wingetId))" -Level INFO
            $jobs += Start-Job -ArgumentList $pkg.wingetId, $pkg.name, $wingetExe -ScriptBlock {
                param($wingetId, $name, $wingetExe)
                # --source winget forces the winget community repo and bypasses the
                # msstore source, which fails on fresh Win11 ISOs whose App Installer
                # ships a stale pinned cert (0x8a15005e: server certificate did not
                # match any expected values), aborting the install.
                $output = & $wingetExe install --id $wingetId `
                    --source winget `
                    --silent `
                    --accept-package-agreements `
                    --accept-source-agreements `
                    --disable-interactivity `
                    2>&1
                [PSCustomObject]@{
                    Name     = $name
                    WingetId = $wingetId
                    ExitCode = $LASTEXITCODE
                    Output   = ($output -join "`n")
                }
            }
        }

        # Wait for all jobs and collect results
        Write-Log "  Waiting for $($jobs.Count) installs to complete..." -Level INFO
        $jobs | Wait-Job -Timeout 600 | Out-Null

        # Kill any jobs that are still running after timeout
        foreach ($job in $jobs) {
            if ($job.State -eq "Running") {
                Write-Log "  Timeout: $($job.Name) - killing" -Level ERROR
                Stop-Job -Job $job
            }
        }

        foreach ($job in $jobs) {
            $r = Receive-Job -Job $job
            if ($r.ExitCode -eq 0 -or $r.ExitCode -eq 3010) {
                Write-Log "  Installed OK: $($r.Name)" -Level OK
            } elseif ($r.ExitCode -eq -1978335189) {
                Write-Log "  Already installed: $($r.Name)" -Level OK
            } else {
                Write-Log "  Failed: $($r.Name) (exit $($r.ExitCode))" -Level ERROR
                Write-Log "  Output: $($r.Output)" -Level ERROR
            }
            Remove-Job -Job $job
        }
    }
} else {
    Write-Log "wingetInstalls feature disabled - skipping" -Level INFO
}

# -----------------------------------------------------------------------
# Set Adobe Reader as default PDF app
# -----------------------------------------------------------------------
if (Get-Feature $Config "software" "pdfDefault") {
    Write-Log "Setting Adobe Reader as default PDF app" -Level INFO

    # Stop UCPD driver before writing file association.
    # UCPD.sys (User Choice Protection Driver) blocks UserChoice registry writes
    # on Win11 23H2+ and some Win10 22H2 builds. Stopping it temporarily allows
    # the HKCR association to be written reliably.
    $ucpdStopped = $false
    $ucpdSvc = Get-Service -Name "ucpd" -ErrorAction SilentlyContinue
    if ($ucpdSvc) {
        try {
            Stop-Service -Name "ucpd" -Force -ErrorAction Stop
            # Wait for the service to fully stop before writing association
            Start-Sleep -Seconds 2
            $svcState = (Get-Service -Name "ucpd" -ErrorAction SilentlyContinue).Status
            if ($svcState -eq "Stopped") {
                $ucpdStopped = $true
                Write-Log "  UCPD driver stopped" -Level OK
            } else {
                Write-Log "  UCPD still in state '$svcState' - association may not persist" -Level WARN
            }
        }
        catch {
            Write-Log "  Could not stop UCPD: $_ (association may not persist on some builds)" -Level WARN
        }
    }

    # Find Adobe PDF viewer executable (Acrobat DC or Reader DC)
    $acroPaths = @(
        "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
        "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
        "${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
        "$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
        "${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
    )
    $acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1

    if (-not $acroExe) {
        Write-Log "  Adobe PDF viewer not found - PDF default not set" -Level WARN
    } else {
        Write-Log "  Found: $acroExe" -Level INFO

        # Mount HKCR PSDrive - not available by default in PS sessions
        if (-not (Get-PSDrive -Name HKCR -ErrorAction SilentlyContinue)) {
            New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null
        }

        # Set file type association via HKCR (system-wide, requires admin)
        $progId = "AcroExch.Document.DC"
        $openCmd = "`"$acroExe`" `"%1`""

        # HKCR\.pdf -> progId
        if (-not (Test-Path "HKCR:\.pdf")) {
            New-Item -Path "HKCR:\.pdf" -Force | Out-Null
        }
        Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId

        # HKCR\AcroExch.Document.DC\shell\open\command
        $cmdPath = "HKCR:\$progId\shell\open\command"
        if (-not (Test-Path $cmdPath)) {
            New-Item -Path $cmdPath -Force | Out-Null
        }
        Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd

        Write-Log "  PDF default set to AcroRd32 (HKCR)" -Level OK
    }

    # Restart UCPD after writing association
    if ($ucpdStopped) {
        try {
            Start-Service -Name "ucpd" -ErrorAction SilentlyContinue
            Write-Log "  UCPD driver restarted" -Level OK
        }
        catch {
            Write-Log "  Could not restart UCPD: $_" -Level WARN
        }
    }
} else {
    Write-Log "pdfDefault feature disabled - skipping" -Level INFO
}

# -----------------------------------------------------------------------
# Install Atera RMM Agent
# Download the MSI from the Atera dashboard API, then install it under the
# SYSTEM account via a one-shot scheduled task. Under SYSTEM the Atera
# installer registers silently and does NOT show the interactive MFA window
# that appears when installing in a user context - so no technician input
# is needed.
# -----------------------------------------------------------------------
if (Get-Feature $Config "software" "ateraAgent") {
    Write-Log "Installing Atera RMM Agent (under SYSTEM)" -Level INFO

    # Machine-wide temp dir readable by SYSTEM (a per-user TEMP may not be)
    $ateraMsi  = "$env:WINDIR\Temp\AteraAgent.msi"
    $ateraUrl  = "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337"
    $ateraTask = "X9-AteraInstall"

    try {
        Write-Log "  Downloading Atera MSI..." -Level INFO
        Invoke-WebRequest -Uri $ateraUrl -OutFile $ateraMsi -UseBasicParsing -ErrorAction Stop
        Write-Log "  Download complete" -Level OK

        # Run msiexec as NT AUTHORITY\SYSTEM via a temporary scheduled task.
        # SYSTEM runs in non-interactive session 0, hence /qn (no UI) and no MFA.
        Write-Log "  Installing as SYSTEM via scheduled task (no MFA)..." -Level INFO
        $action    = New-ScheduledTaskAction -Execute "msiexec.exe" -Argument "/i `"$ateraMsi`" /qn /norestart"
        $principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
        $stTask    = New-ScheduledTask -Action $action -Principal $principal
        Register-ScheduledTask -TaskName $ateraTask -InputObject $stTask -Force | Out-Null
        Start-ScheduledTask -TaskName $ateraTask

        # Wait for the task to finish (msiexec install can take a few minutes)
        $deadline = (Get-Date).AddMinutes(10)
        do {
            Start-Sleep -Seconds 3
            $state = (Get-ScheduledTask -TaskName $ateraTask -ErrorAction SilentlyContinue).State
        } while ($state -eq "Running" -and (Get-Date) -lt $deadline)

        $result = (Get-ScheduledTaskInfo -TaskName $ateraTask -ErrorAction SilentlyContinue).LastTaskResult
        if ($state -eq "Running") {
            Write-Log "  Atera install timed out after 10 min" -Level WARN
        } elseif ($result -eq 0) {
            Write-Log "  Atera installer finished (SYSTEM, exit 0)" -Level OK
        } else {
            Write-Log "  Atera installer exit code: $result" -Level WARN
        }

        # Verify install. The AteraAgent service is the most reliable signal -
        # the agent registers it regardless of install path (Atera now sometimes
        # lands under C:\ProgramData instead of Program Files). Fall back to the
        # known binary locations if the service query is inconclusive.
        $ateraSvc = Get-Service -Name "AteraAgent" -ErrorAction SilentlyContinue
        if ($ateraSvc) {
            Write-Log "  Atera agent verified (service AteraAgent present, status $($ateraSvc.Status))" -Level OK
        } else {
            $ateraPaths = @(
                "$env:ProgramFiles\ATERA Networks\AteraAgent\AteraAgent.exe"
                "${env:ProgramFiles(x86)}\ATERA Networks\AteraAgent\AteraAgent.exe"
                "$env:ProgramData\ATERA Networks\AteraAgent\AteraAgent.exe"
            )
            if ($ateraPaths | Where-Object { Test-Path $_ }) {
                Write-Log "  Atera agent binary verified" -Level OK
            } else {
                Write-Log "  Atera agent service and binary not found at expected paths" -Level WARN
            }
        }
    }
    catch {
        Write-Log "  Atera agent install failed: $_" -Level ERROR
    }
    finally {
        Unregister-ScheduledTask -TaskName $ateraTask -Confirm:$false -ErrorAction SilentlyContinue
        Remove-Item $ateraMsi -ErrorAction SilentlyContinue
    }
} else {
    Write-Log "ateraAgent feature disabled - skipping" -Level INFO
}

Write-Log "Step 2 complete" -Level OK
