param(
    [switch]$SkipAppInstall,
    [switch]$SkipAgentInstall,
    [switch]$SkipAgentStart,
    [switch]$EnableAgentStartupTask,
    [switch]$EnableClinicalApplianceMode,
    [switch]$DryRun,
    [switch]$SkipAppLaunch,
    [switch]$ReplaceExistingApp,
    [string]$AppPackageName = "UnifiedImaging.ImageAnywhere",
    [string]$AppId = "UnifiedImaging.ImageAnywhere_ytf96cz97jc64!App",
    [string]$AgentTaskName = "ImageAnywhereAgent",
    [string]$AgentInstallDirectory = (Join-Path $env:LOCALAPPDATA "Programs\ImageAnywhereAgent"),
    [string]$OutputJsonPath
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$steps = New-Object System.Collections.Generic.List[object]
$artifacts = [ordered]@{}
$readiness = [ordered]@{
    DryRun = $DryRun.IsPresent
    ClinicalApplianceModeRequested = $EnableClinicalApplianceMode.IsPresent
    AgentPayloadPresent = $false
    AgentStartupTaskRequested = $EnableAgentStartupTask.IsPresent
    ExistingSettingsPresent = $false
    ExistingSettingsReadable = $true
    Ready = $false
    Reasons = @()
}

function Add-Step {
    param(
        [string]$Name,
        [string]$Status,
        [string]$Detail
    )

    $script:steps.Add([pscustomobject]@{
        Name = $Name
        Status = $Status
        Detail = $Detail
    }) | Out-Null
}

function Set-JsonProperty {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Target,
        [Parameter(Mandatory = $true)]
        [string]$Name,
        [object]$Value
    )

    $property = $Target.PSObject.Properties[$Name]
    if ($property) {
        $property.Value = $Value
    }
    else {
        $Target | Add-Member -NotePropertyName $Name -NotePropertyValue $Value
    }
}

function Test-IsElevated {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Import-AppCertificate {
    param([string]$CertificatePath)

    $cert = Import-Certificate -FilePath $CertificatePath -CertStoreLocation Cert:\CurrentUser\TrustedPeople
    $script:artifacts.CurrentUserCertificateThumbprint = $cert.Thumbprint

    if (Test-IsElevated) {
        & certutil.exe -addstore TrustedPeople $CertificatePath | Out-Null
        if ($LASTEXITCODE -ne 0) {
            throw "certutil.exe failed while adding the app certificate to LocalMachine TrustedPeople."
        }

        Add-Step -Name "app.certificate-trust" -Status "ok" -Detail "Imported certificate into CurrentUser and LocalMachine TrustedPeople."
    }
    else {
        $machineTrusted = $false
        try {
            $machineTrusted = [bool](Get-ChildItem -Path Cert:\LocalMachine\TrustedPeople -ErrorAction Stop | Where-Object { $_.Thumbprint -eq $cert.Thumbprint })
        }
        catch { }
        $script:artifacts.MachineTrustPresent = $machineTrusted
        if ($machineTrusted) {
            Add-Step -Name "app.certificate-trust" -Status "ok" -Detail "Certificate already trusted in CurrentUser and LocalMachine TrustedPeople."
        }
        else {
            Add-Step -Name "app.certificate-trust" -Status "warning" -Detail "Certificate trusted for CurrentUser only; machine-level trust is missing. If the app install fails on certificate trust, a one-time admin approval will be requested."
        }
    }
}

function Install-AppPackage {
    $appRoot = Join-Path $PSScriptRoot "app"
    if (-not (Test-Path -LiteralPath $appRoot)) {
        throw "The app payload folder was not found at '$appRoot'."
    }

    $package = @(Get-ChildItem -LiteralPath $appRoot -Filter *.msixbundle -File) +
        @(Get-ChildItem -LiteralPath $appRoot -Filter *.msix -File) |
        Sort-Object @{ Expression = { if ($_.Extension -eq ".msixbundle") { 0 } else { 1 } } }, LastWriteTime -Descending |
        Select-Object -First 1
    $certificate = Get-ChildItem -LiteralPath $appRoot -Filter *.cer -File | Select-Object -First 1
    $dependencyRoot = Join-Path $appRoot "Dependencies\x64"
    $dependencies = @()
    if (Test-Path -LiteralPath $dependencyRoot) {
        $dependencies = @(Get-ChildItem -LiteralPath $dependencyRoot -Filter *.appx -File)
    }

    if (-not $package) {
        throw "No .msixbundle or .msix package was found under '$appRoot'."
    }

    if (-not $certificate) {
        throw "No .cer signing certificate was found under '$appRoot'."
    }

    $script:artifacts.AppPackagePath = $package.FullName
    $script:artifacts.AppCertificatePath = $certificate.FullName
    $script:artifacts.AppDependencyCount = $dependencies.Count

    Import-AppCertificate -CertificatePath $certificate.FullName

    $existingPackage = Get-AppxPackage -Name $AppPackageName -ErrorAction SilentlyContinue
    if ($existingPackage) {
        $script:artifacts.PreExistingAppPackageFullName = $existingPackage.PackageFullName
        $script:artifacts.PreExistingAppPackageVersion = [string]$existingPackage.Version
        if ($ReplaceExistingApp.IsPresent) {
            Remove-AppxPackage -Package $existingPackage.PackageFullName -ErrorAction Stop
            Add-Step -Name "app.remove-existing" -Status "warning" -Detail "Removed the current user's existing $AppPackageName package before reinstalling."
        }
        else {
            Add-Step -Name "app.existing-package" -Status "info" -Detail "Existing $AppPackageName package is registered. Add-AppxPackage will update if Windows accepts the package version; use -ReplaceExistingApp only for a deliberate current-user reset/reinstall."
        }
    }

    $addParams = @{
        Path = $package.FullName
        ForceApplicationShutdown = $true
        ErrorAction = "Stop"
    }

    if ($dependencies.Count -gt 0) {
        $addParams.DependencyPath = @($dependencies.FullName)
    }

    $addCommand = Get-Command Add-AppxPackage
    if ($addCommand.Parameters.ContainsKey("ForceUpdateFromAnyVersion")) {
        $addParams.ForceUpdateFromAnyVersion = $true
    }

    try {
        Add-AppxPackage @addParams
        Add-Step -Name "app.install" -Status "ok" -Detail "Add-AppxPackage completed for $($package.Name)."
    }
    catch {
        $message = $_.Exception.Message
        if ($message -match "provided but not used|0x80073CF9") {
            $addParams.Remove("DependencyPath")
            Add-AppxPackage @addParams
            Add-Step -Name "app.install" -Status "ok" -Detail "Add-AppxPackage completed without explicit dependency paths after Windows reported already-satisfied dependencies."
        }
        elseif (($message -match "0x800B0109|0x800B010A|CERT_E_UNTRUSTED|certificate") -and -not (Test-IsElevated)) {
            # Package deployment validates signatures in the machine context; CurrentUser
            # trust alone is not sufficient on a machine that never trusted this cert.
            Write-Host "Windows requires machine-level trust for the app certificate. Requesting one-time admin approval..."
            $certutilArgs = '-addstore TrustedPeople "' + [string]$script:artifacts.AppCertificatePath + '"'
            $elevatedCertutil = $null
            try {
                $elevatedCertutil = Start-Process -FilePath certutil.exe -ArgumentList $certutilArgs -Verb RunAs -Wait -PassThru
            }
            catch { }
            if ($elevatedCertutil -and $elevatedCertutil.ExitCode -eq 0) {
                Add-AppxPackage @addParams
                Add-Step -Name "app.install" -Status "ok" -Detail "Add-AppxPackage completed after one-time admin certificate-trust approval."
            }
            else {
                throw "The app install needs the signing certificate trusted at machine level. Ask site IT to approve the admin prompt, or run elevated: certutil -addstore TrustedPeople `"$([string]$script:artifacts.AppCertificatePath)`" - then run this installer again as the signed-in user."
            }
        }
        elseif ($existingPackage -and ($message -match "already installed|higher version|same version|newer version|0x80073CFB|0x80073D06|0x80073CF3")) {
            Add-Step -Name "app.install" -Status "warning" -Detail "Windows did not replace the existing package: $message"
        }
        else {
            throw
        }
    }

    $expectedVersionMatch = [regex]::Match($package.Name, '_(?<version>\d+\.\d+\.\d+\.\d+)_')
    if ($expectedVersionMatch.Success) {
        $expectedVersion = $expectedVersionMatch.Groups["version"].Value
        $registeredPackage = Get-AppxPackage -Name $AppPackageName -ErrorAction SilentlyContinue
        if (-not $registeredPackage) {
            throw "The app package is not registered for this user after install."
        }
        $script:artifacts.RegisteredAppVersion = [string]$registeredPackage.Version
        if ([string]$registeredPackage.Version -eq $expectedVersion) {
            Add-Step -Name "app.version-check" -Status "ok" -Detail "Registered app version matches packaged version $expectedVersion."
        }
        else {
            Add-Step -Name "app.version-check" -Status "warning" -Detail "Registered app version $($registeredPackage.Version) differs from packaged $expectedVersion. Re-run with -ReplaceExistingApp if this machine must take exactly this package."
        }
    }
}

function Find-AgentPackageRoot {
    $agentRoot = Join-Path $PSScriptRoot "agent"
    if (-not (Test-Path -LiteralPath $agentRoot)) {
        return $null
    }

    $installScript = Get-ChildItem -LiteralPath $agentRoot -Recurse -Filter Install-Agent.ps1 -File |
        Where-Object { $_.FullName -match '[\\/]scripts[\\/]Install-Agent\.ps1$' } |
        Sort-Object FullName |
        Select-Object -First 1

    if (-not $installScript) {
        return $null
    }

    return (Split-Path -Path (Split-Path -Path $installScript.FullName -Parent) -Parent)
}

function Get-AgentSettingsPath {
    return (Join-Path $env:LOCALAPPDATA "ImageAnywhere\agent\agent-settings.json")
}

function Test-ClinicalApplianceReadiness {
    $reasons = New-Object System.Collections.Generic.List[string]
    $agentPackageRoot = Find-AgentPackageRoot
    $script:readiness.AgentPayloadPresent = ($null -ne $agentPackageRoot)
    if (-not $agentPackageRoot) {
        $reasons.Add("embedded agent payload missing") | Out-Null
    }
    if (-not $EnableAgentStartupTask.IsPresent) {
        $reasons.Add("-EnableAgentStartupTask is required") | Out-Null
    }
    if ($SkipAgentStart.IsPresent) {
        $reasons.Add("-SkipAgentStart cannot be combined with Clinical Appliance Mode") | Out-Null
    }
    if ($SkipAppLaunch.IsPresent) {
        $reasons.Add("-SkipAppLaunch cannot be combined with Clinical Appliance Mode") | Out-Null
    }

    $settingsPath = Get-AgentSettingsPath
    $script:artifacts.AgentSettingsPath = $settingsPath
    $script:readiness.ExistingSettingsPresent = Test-Path -LiteralPath $settingsPath
    if ($script:readiness.ExistingSettingsPresent) {
        try {
            $existingSettings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json
            if (-not $existingSettings) {
                throw "settings document is empty"
            }
        }
        catch {
            $script:readiness.ExistingSettingsReadable = $false
            $reasons.Add("existing agent-settings.json is not readable JSON; it will not be overwritten") | Out-Null
        }
    }

    $script:readiness.Reasons = @($reasons.ToArray())
    $script:readiness.Ready = ($reasons.Count -eq 0)
    return $script:readiness.Ready
}

function Enable-ClinicalApplianceSettings {
    $settingsPath = Get-AgentSettingsPath
    $settingsDirectory = Split-Path -Path $settingsPath -Parent
    $settingsExisted = Test-Path -LiteralPath $settingsPath
    $settings = $null

    if ($settingsExisted) {
        try {
            $settings = Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json
        }
        catch {
            throw "Existing agent settings at '$settingsPath' are not readable JSON. They were preserved unchanged; repair them before enabling Clinical Appliance Mode."
        }
        if (-not $settings) {
            throw "Existing agent settings at '$settingsPath' are empty. They were preserved unchanged; repair them before enabling Clinical Appliance Mode."
        }
    }
    else {
        $settings = [pscustomobject][ordered]@{}
    }

    $clientSupervisionProperty = $settings.PSObject.Properties["ClientSupervision"]
    if ($clientSupervisionProperty -and $null -ne $clientSupervisionProperty.Value) {
        $clientSupervision = $clientSupervisionProperty.Value
        if ($clientSupervision -isnot [pscustomobject]) {
            throw "Existing ClientSupervision settings are not a JSON object. The settings file was preserved unchanged."
        }
    }
    else {
        $clientSupervision = [pscustomobject][ordered]@{}
        Set-JsonProperty -Target $settings -Name "ClientSupervision" -Value $clientSupervision
    }

    # Merge only the appliance-owned settings. All other existing and future fields
    # remain intact. M6 preparation is deliberately shadow-only until the M5 active
    # launch gate is independently cleared.
    Set-JsonProperty -Target $clientSupervision -Name "SchemaVersion" -Value 1
    Set-JsonProperty -Target $clientSupervision -Name "Enabled" -Value $true
    Set-JsonProperty -Target $clientSupervision -Name "Mode" -Value "Shadow"
    Set-JsonProperty -Target $clientSupervision -Name "AppUserModelId" -Value $AppId

    New-Item -ItemType Directory -Path $settingsDirectory -Force | Out-Null
    $tempPath = $settingsPath + ".tmp-" + [Guid]::NewGuid().ToString("N")
    $backupPath = $settingsPath + ".pre-clinical-appliance-" + (Get-Date -Format "yyyyMMdd-HHmmssfff") + ".bak"
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    [System.IO.File]::WriteAllText($tempPath, ($settings | ConvertTo-Json -Depth 20), $utf8NoBom)
    try {
        if ($settingsExisted) {
            [System.IO.File]::Replace($tempPath, $settingsPath, $backupPath)
            $script:artifacts.AgentSettingsBackupPath = $backupPath
        }
        else {
            [System.IO.File]::Move($tempPath, $settingsPath)
        }
    }
    finally {
        if (Test-Path -LiteralPath $tempPath) {
            Remove-Item -LiteralPath $tempPath -Force
        }
    }

    $script:artifacts.AgentSettingsPath = $settingsPath
    $script:artifacts.ClientSupervisionSchemaVersion = "1"
    $script:artifacts.ClientSupervisionMode = "Shadow"
    $script:artifacts.ClientSupervisionAppUserModelId = $AppId
    $script:artifacts.RollbackCommand = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$PSScriptRoot\Rollback-ClinicalApplianceMode.ps1`""
    Add-Step -Name "clinical-appliance.settings" -Status "ok" -Detail "Merged versioned shadow supervision settings into $settingsPath without discarding other settings."
}

function Install-AgentPackage {
    $agentPackageRoot = Find-AgentPackageRoot
    if (-not $agentPackageRoot) {
        Add-Step -Name "agent.install" -Status "skipped" -Detail "No embedded agent package was found in this field setup ZIP."
        return
    }

    $runningAgents = @(Get-Process -Name "IAUWP.Agent" -ErrorAction SilentlyContinue)
    if ($runningAgents.Count -gt 0) {
        $runningAgents | Stop-Process -Force -ErrorAction Stop
        $stopDeadline = (Get-Date).AddSeconds(15)
        while ((Get-Process -Name "IAUWP.Agent" -ErrorAction SilentlyContinue) -and ((Get-Date) -lt $stopDeadline)) {
            Start-Sleep -Milliseconds 250
        }
        Add-Step -Name "agent.stop-existing" -Status "info" -Detail "Stopped $($runningAgents.Count) running agent process(es) so package files could be upgraded."
    }

    $isUpgrade = Test-Path -LiteralPath $AgentInstallDirectory
    $installScript = Join-Path $agentPackageRoot "scripts\Install-Agent.ps1"
    $previousEap = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    try {
        & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installScript -InstallDirectory $AgentInstallDirectory -Force 2>&1 | ForEach-Object { Write-Host ([string]$_) }
        $agentInstallExit = $LASTEXITCODE
    }
    finally {
        $ErrorActionPreference = $previousEap
    }
    if ($agentInstallExit -ne 0) {
        throw "Install-Agent.ps1 failed with exit code $agentInstallExit."
    }

    $script:artifacts.AgentPackageRoot = $agentPackageRoot
    $script:artifacts.AgentInstallDirectory = $AgentInstallDirectory
    if ($isUpgrade) {
        Add-Step -Name "agent.install" -Status "ok" -Detail "Upgraded agent package files in place at $AgentInstallDirectory (existing user state preserved)."
    }
    else {
        Add-Step -Name "agent.install" -Status "ok" -Detail "Installed agent package into $AgentInstallDirectory."
    }
}

function Test-AgentStartupTaskExists {
    $previousEap = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    try {
        & schtasks.exe /Query /TN $AgentTaskName 2>&1 | Out-Null
        return ($LASTEXITCODE -eq 0)
    }
    finally {
        $ErrorActionPreference = $previousEap
    }
}

function Start-AgentProcess {
    $agentExe = Join-Path $AgentInstallDirectory "agent\IAUWP.Agent.exe"
    if (-not (Test-Path -LiteralPath $agentExe)) {
        Add-Step -Name "agent.start" -Status "skipped" -Detail "Agent executable is not installed at $agentExe."
        return
    }

    $runDirectory = Join-Path $AgentInstallDirectory "run"
    New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
    $pidPath = Join-Path $runDirectory "agent.pid"

    $runningFromThisInstall = Get-Process -Name IAUWP.Agent -ErrorAction SilentlyContinue |
        Where-Object {
            try {
                [string]::Equals($_.Path, $agentExe, [StringComparison]::OrdinalIgnoreCase)
            }
            catch {
                $false
            }
        } |
        Select-Object -First 1
    if ($runningFromThisInstall) {
        $runningFromThisInstall.Id | Set-Content -Path $pidPath -Encoding ASCII
        $script:artifacts.AgentPid = $runningFromThisInstall.Id
        Add-Step -Name "agent.start" -Status "ok" -Detail "Agent is already running with PID $($runningFromThisInstall.Id)."
        return
    }

    # An elevated Start-Process would leave the agent running elevated for the rest of
    # this Windows session, so a normal (non-elevated) shell gets 'Access is denied'
    # from Stop-Agent.ps1 / Remove-Agent.ps1 until the next sign-in. The current-user
    # sign-in task runs with the user's limited token, so starting the agent through it
    # keeps the agent in the normal user context even from an elevated installer window.
    if ((Test-IsElevated) -and (Test-AgentStartupTaskExists)) {
        $previousEap = $ErrorActionPreference
        $ErrorActionPreference = "Continue"
        try {
            & schtasks.exe /Run /TN $AgentTaskName 2>&1 | ForEach-Object { Write-Host ([string]$_) }
            $taskRunExit = $LASTEXITCODE
        }
        finally {
            $ErrorActionPreference = $previousEap
        }
        if ($taskRunExit -eq 0) {
            $startDeadline = (Get-Date).AddSeconds(20)
            $taskStartedProcess = $null
            while (-not $taskStartedProcess -and ((Get-Date) -lt $startDeadline)) {
                Start-Sleep -Milliseconds 500
                $taskStartedProcess = Get-Process -Name IAUWP.Agent -ErrorAction SilentlyContinue |
                    Where-Object {
                        try {
                            [string]::Equals($_.Path, $agentExe, [StringComparison]::OrdinalIgnoreCase)
                        }
                        catch {
                            $false
                        }
                    } |
                    Select-Object -First 1
            }
            if ($taskStartedProcess) {
                $taskStartedProcess.Id | Set-Content -Path $pidPath -Encoding ASCII
                $script:artifacts.AgentPid = $taskStartedProcess.Id
                $script:artifacts.AgentStartMode = "scheduled-task"
                Add-Step -Name "agent.start" -Status "ok" -Detail "Started ImageAnywhere Agent with PID $($taskStartedProcess.Id) via scheduled task '$AgentTaskName' so it runs in the normal (non-elevated) user context."
                return
            }
        }
        Add-Step -Name "agent.start-task-run" -Status "warning" -Detail "Scheduled task '$AgentTaskName' run did not produce a running agent (schtasks exit $taskRunExit); falling back to a direct start from this elevated window."
    }

    $process = Start-Process -FilePath $agentExe `
        -WorkingDirectory (Split-Path -Path $agentExe -Parent) `
        -WindowStyle Hidden `
        -PassThru
    $process.Id | Set-Content -Path $pidPath -Encoding ASCII
    $script:artifacts.AgentPid = $process.Id
    Add-Step -Name "agent.start" -Status "ok" -Detail "Started ImageAnywhere Agent with PID $($process.Id)."
    if (Test-IsElevated) {
        $script:artifacts.AgentStartMode = "elevated-process"
        Add-Step -Name "agent.start-elevation" -Status "warning" -Detail "This window is elevated, so the agent is running ELEVATED for this Windows session. Non-elevated shells will get 'Access is denied' from Stop-Agent.ps1 / Remove-Agent.ps1 until the agent is restarted in the normal user context (sign out and back in with the sign-in task enabled, or stop it from an elevated shell)."
    }
    else {
        $script:artifacts.AgentStartMode = "process"
    }
}

function Enable-AgentStartupTask {
    $agentExe = Join-Path $AgentInstallDirectory "agent\IAUWP.Agent.exe"
    if (-not (Test-Path -LiteralPath $agentExe)) {
        Add-Step -Name "agent.startup-task" -Status "skipped" -Detail "Agent executable is not installed at $agentExe."
        return
    }

    # schtasks.exe /SC ONLOGON requires elevation; Register-ScheduledTask can create a
    # current-user logon task without admin. The wscript launcher keeps the console agent
    # window hidden at sign-in, and ExecutionTimeLimit PT0S removes the default 72-hour
    # task stop that would kill an always-on agent.
    $agentWorkingDirectory = Split-Path -Path $agentExe -Parent
    $launcherPath = Join-Path $agentWorkingDirectory "Start-AgentHidden.vbs"
    $launcherLines = @(
        'Dim iaShell'
        'Set iaShell = CreateObject("WScript.Shell")'
        ('iaShell.CurrentDirectory = "' + $agentWorkingDirectory + '"')
        ('iaShell.Run """' + $agentExe + '""", 0, False')
    )
    Set-Content -Path $launcherPath -Value $launcherLines -Encoding Unicode

    # SID-form trigger works for local, domain, MSA, and Entra/AzureAD accounts alike;
    # name-form AtLogOn triggers can fail to resolve for AzureAD identities.
    $currentUserSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    $action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument ('//B //NoLogo "' + $launcherPath + '"')
    $trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUserSid
    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Seconds 0)
    try {
        Register-ScheduledTask -TaskName $AgentTaskName -Action $action -Trigger $trigger -Settings $settings -Force -ErrorAction Stop | Out-Null
        $script:artifacts.AgentStartupTaskName = $AgentTaskName
        $script:artifacts.AgentStartupTaskCommand = 'wscript.exe //B //NoLogo "' + $launcherPath + '"'
        $script:artifacts.AgentStartupLauncherPath = $launcherPath
        Add-Step -Name "agent.startup-task" -Status "ok" -Detail "Registered current-user sign-in task '$AgentTaskName' (hidden launcher, no elevation required)."
    }
    catch {
        Add-Step -Name "agent.startup-task" -Status "failed" -Detail "Could not register sign-in task '$AgentTaskName': $($_.Exception.Message) If an old task exists from another account, have IT remove it from an elevated prompt (schtasks /Delete /TN $AgentTaskName /F) and run this installer again."
    }
}

function Invoke-FieldVerification {
    $verifyScript = Join-Path $PSScriptRoot "Verify-ImageAnywhereField.ps1"
    if (-not (Test-Path -LiteralPath $verifyScript)) {
        Add-Step -Name "verify" -Status "skipped" -Detail "Verify-ImageAnywhereField.ps1 was not found."
        return
    }

    $verifyOutputPath = Join-Path $PSScriptRoot "field-verify-result.json"
    $verifyArgs = @(
        "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $verifyScript,
        "-AppPackageName", $AppPackageName,
        "-AgentInstallDirectory", $AgentInstallDirectory,
        "-AgentTaskName", $AgentTaskName,
        "-OutputJsonPath", $verifyOutputPath
    )
    if ($EnableAgentStartupTask.IsPresent) {
        $verifyArgs += "-RequireStartupTask"
    }
    if ($EnableClinicalApplianceMode.IsPresent) {
        $verifyArgs += @("-RequireClinicalApplianceMode", "-ExpectedAppUserModelId", $AppId)
    }
    $previousEap = $ErrorActionPreference
    $ErrorActionPreference = "Continue"
    try {
        & powershell.exe @verifyArgs 2>&1 | ForEach-Object { Write-Host ([string]$_) }
        $verifyExit = $LASTEXITCODE
    }
    finally {
        $ErrorActionPreference = $previousEap
    }
    $script:artifacts.VerificationOutputPath = $verifyOutputPath
    if ($verifyExit -eq 0) {
        Add-Step -Name "verify" -Status "ok" -Detail "Verification PASSED. See $verifyOutputPath."
    }
    else {
        Add-Step -Name "verify" -Status "failed" -Detail "Verification FAILED (exit $verifyExit). See $verifyOutputPath."
    }
}

if ([string]::IsNullOrWhiteSpace($OutputJsonPath)) {
    $OutputJsonPath = Join-Path $PSScriptRoot "field-setup-result.json"
}

$script:transcriptPath = Join-Path $PSScriptRoot "field-install-log.txt"
try {
    Start-Transcript -Path $script:transcriptPath -Force | Out-Null
}
catch {
    $script:transcriptPath = Join-Path $env:TEMP "ImageAnywhere-field-install-log.txt"
    try {
        Start-Transcript -Path $script:transcriptPath -Force | Out-Null
        Write-Host "Package folder is not writable; logging to $script:transcriptPath"
    }
    catch {
        $script:transcriptPath = $null
    }
}

if (-not $DryRun.IsPresent) {
    try {
        Get-ChildItem -LiteralPath $PSScriptRoot -Recurse -ErrorAction SilentlyContinue | Unblock-File -ErrorAction SilentlyContinue
    }
    catch { }
}

# Per-user install: elevation with a DIFFERENT account would silently install the app,
# agent, and sign-in task for that other account instead of the clinic user.
if (Test-IsElevated) {
    $interactiveUser = $null
    try { $interactiveUser = (Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop).UserName } catch { }
    $invokingUser = [Security.Principal.WindowsIdentity]::GetCurrent().Name
    if (-not [string]::IsNullOrWhiteSpace($interactiveUser) -and -not [string]::Equals($interactiveUser, $invokingUser, [StringComparison]::OrdinalIgnoreCase)) {
        Write-Host ""
        Write-Host "STOP: this window is elevated as '$invokingUser' but the signed-in user is '$interactiveUser'."
        Write-Host "ImageAnywhere installs per user. Double-click the installer as the signed-in user who will use ImageAnywhere."
        Write-Host "If Windows asks for admin approval during install, IT can approve that prompt without switching accounts."
        try { Stop-Transcript | Out-Null } catch { }
        exit 1
    }
}

try {
    if ($EnableClinicalApplianceMode.IsPresent) {
        $clinicalReadinessOk = Test-ClinicalApplianceReadiness
        if (-not $clinicalReadinessOk) {
            Add-Step -Name "clinical-appliance.readiness" -Status "failed" -Detail ($readiness.Reasons -join "; ")
            throw "Clinical Appliance Mode readiness failed: $($readiness.Reasons -join '; ')"
        }
        Add-Step -Name "clinical-appliance.readiness" -Status "ok" -Detail "Agent payload, explicit startup-task opt-in, launch/start plan, and existing settings readability checks passed."
    }
    else {
        $readiness.Ready = $true
        $readiness.Reasons = @("Clinical Appliance Mode was not requested; ordinary field behavior remains unchanged.")
        Add-Step -Name "clinical-appliance.readiness" -Status "skipped" -Detail "Clinical Appliance Mode was not requested."
    }

    if ($DryRun.IsPresent) {
        Add-Step -Name "dry-run" -Status "ok" -Detail "Readiness only: no app/agent install, settings write, task registration, process start, or app launch was performed."
    }
    else {
    if ($SkipAppInstall.IsPresent) {
        Add-Step -Name "app.install" -Status "skipped" -Detail "Skipped by -SkipAppInstall."
    }
    else {
        Install-AppPackage
    }

    if ($SkipAgentInstall.IsPresent) {
        Add-Step -Name "agent.install" -Status "skipped" -Detail "Skipped by -SkipAgentInstall."
    }
    else {
        Install-AgentPackage
    }

    if ($EnableClinicalApplianceMode.IsPresent) {
        Enable-ClinicalApplianceSettings
    }
    else {
        Add-Step -Name "clinical-appliance.settings" -Status "skipped" -Detail "Not enabled; existing/default client close behavior remains unchanged."
    }

    # The sign-in task must be registered BEFORE the agent starts so an elevated install
    # can start the agent through the task (normal user context) instead of leaving an
    # elevated agent process behind for the rest of the session.
    if ($EnableAgentStartupTask.IsPresent) {
        Enable-AgentStartupTask
    }
    else {
        Add-Step -Name "agent.startup-task" -Status "skipped" -Detail "Not enabled. Re-run with -EnableAgentStartupTask to create current-user sign-in persistence."
    }

    if ($SkipAgentStart.IsPresent) {
        Add-Step -Name "agent.start" -Status "skipped" -Detail "Skipped by -SkipAgentStart."
    }
    else {
        Start-AgentProcess
    }

    if ($SkipAppLaunch.IsPresent) {
        Add-Step -Name "app.launch" -Status "skipped" -Detail "Skipped by -SkipAppLaunch."
    }
    else {
        Start-Process explorer.exe ("shell:AppsFolder\" + $AppId)
        $launchDeadline = (Get-Date).AddSeconds(12)
        $appProcess = $null
        while (-not $appProcess -and ((Get-Date) -lt $launchDeadline)) {
            Start-Sleep -Milliseconds 500
            $appProcess = Get-Process -Name "IAUWP" -ErrorAction SilentlyContinue | Select-Object -First 1
        }
        if ($appProcess) {
            Add-Step -Name "app.launch" -Status "ok" -Detail "ImageAnywhere launched (process $($appProcess.Id))."
        }
        else {
            Add-Step -Name "app.launch" -Status "warning" -Detail "Launch was requested but no ImageAnywhere process appeared within 12 seconds. Open ImageAnywhere from the Start menu and report to support if it does not start."
        }
    }

    Invoke-FieldVerification
    }
}
finally {
    $artifactSnapshot = @()
    foreach ($key in $artifacts.Keys) {
        $artifactSnapshot += [pscustomobject]@{
            Name = [string]$key
            Value = [string]$artifacts[$key]
        }
    }

    $summary = [pscustomobject]@{
        SchemaVersion = 1
        GeneratedAt = (Get-Date).ToString("O")
        Tool = "Install-ImageAnywhereField"
        PackageRoot = $PSScriptRoot
        DryRun = $DryRun.IsPresent
        EnableAgentStartupTask = $EnableAgentStartupTask.IsPresent
        EnableClinicalApplianceMode = $EnableClinicalApplianceMode.IsPresent
        ReplaceExistingApp = $ReplaceExistingApp.IsPresent
        Readiness = [pscustomobject]$readiness
        Rollback = [pscustomobject]@{
            Command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$PSScriptRoot\Rollback-ClinicalApplianceMode.ps1`""
            PreservesUwpLocalState = $true
            PreservesAgentPairingAndOtherSettings = $true
            RemovesStartupTask = $false
        }
        Steps = @($steps.ToArray())
        Artifacts = @($artifactSnapshot)
    }

    try {
        $summary | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputJsonPath -Encoding UTF8
    }
    catch {
        try {
            $fallbackJsonPath = Join-Path $env:TEMP "field-setup-result.json"
            $summary | ConvertTo-Json -Depth 10 | Set-Content -Path $fallbackJsonPath -Encoding UTF8
            Write-Host "Result JSON written to $fallbackJsonPath (package folder not writable)."
        }
        catch { }
    }
}

Write-Host ""
Write-Host "ImageAnywhere field setup summary:"
foreach ($step in $steps) {
    Write-Host ("[{0}] {1} - {2}" -f $step.Status, $step.Name, $step.Detail)
}
Write-Host ""

$failedSteps = @($steps | Where-Object { $_.Status -eq "failed" })
if ($failedSteps.Count -gt 0) {
    Write-Host ("SETUP FAILED: {0}" -f (($failedSteps | ForEach-Object { $_.Name }) -join ", "))
    try { Stop-Transcript | Out-Null } catch { }
    exit 1
}

Write-Host "Next: sign in to ImageAnywhere, choose Pair agent, then click Refresh agent in Connections (or double-click VERIFY-ImageAnywhere.cmd)."
try { Stop-Transcript | Out-Null } catch { }
exit 0