Private/Wait-ForDocumentationAndProcess.ps1

function Wait-ForDocumentationAndProcess {
    [CmdletBinding()]
    param(
        [string]$ProjectPath,
        [string]$InstallerType,

        # Exact capture file this run's sandbox will produce (returned by New-TargetedDocumentation).
        # When provided, the wait is satisfied ONLY by this file — immune to stale captures from
        # previous runs. When omitted (back-compat), the newest capture is selected
        # (Get-LatestInstallationCapture; note: filter is 'InstallationChanges_*.json', with underscore).
        [string]$ExpectedJsonPath
    )

    try {
        Write-Host "Monitoring for documentation completion..." -ForegroundColor Yellow

        $jsonFound = $false
        $jsonFile = $null
        $maxWaitMinutes = 30
        # 2 s cadence (was 10 s): a Test-Path + small-file read on a local/VSMB path is trivial, and the
        # 10 s quantum added ~5 s of dead tail to every capture. The 30-min CEILING is unchanged — it is
        # the correct bound for an unobservable guest.
        $checkIntervalSeconds = 2
        $totalChecks = ($maxWaitMinutes * 60) / $checkIntervalSeconds

        if ($ExpectedJsonPath) {
            Write-Host "Waiting for this run's capture: $(Split-Path $ExpectedJsonPath -Leaf)" -ForegroundColor Cyan
        } else {
            Write-Verbose "Checking for InstallationChanges JSON file every $checkIntervalSeconds seconds..."
        }
        Write-Verbose "Maximum wait time: $maxWaitMinutes minutes"

        for ($i = 1; $i -le $totalChecks; $i++) {
            $candidate = $null
            if ($ExpectedJsonPath) {
                if (Test-Path -LiteralPath $ExpectedJsonPath) { $candidate = $ExpectedJsonPath }
            }
            else {
                $latest = Get-LatestInstallationCapture -ProjectPath $ProjectPath
                if ($latest) { $candidate = $latest.FullName }
            }

            if ($candidate) {
                # The guest writes the JSON over VSMB / to the mapped folder while we poll — the faster
                # cadence raises the odds of catching it MID-WRITE. Only accept a capture that parses as
                # complete JSON; a torn read simply stays in the loop and is retried next tick (a partial
                # capture feeding requirement/uninstall generation would corrupt the package quietly).
                $parsed = $null
                try { $parsed = Get-Content -LiteralPath $candidate -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { $parsed = $null }
                if ($parsed) {
                    $jsonFile = $candidate
                    $jsonFound = $true
                    Write-Host "✓ JSON documentation file found: $(Split-Path $candidate -Leaf)" -ForegroundColor Green
                    break
                }
                Write-Verbose "Capture file present but not yet complete JSON — still being written; retrying..."
            }

            $minutesWaited = ($i * $checkIntervalSeconds) / 60
            Write-Verbose "Waiting... ($([math]::Round($minutesWaited, 1)) minutes elapsed)"
            Start-Sleep -Seconds $checkIntervalSeconds
        }

        if (-not $jsonFound) {
            Write-Warning "Documentation JSON file not found after $maxWaitMinutes minutes. Please check the Windows Sandbox manually."
            return $false
        }
        
        Write-Host "`nProcessing documentation results..." -ForegroundColor Yellow
        
        # Generate requirement script
        Write-Verbose "Generating Intune requirement script..."
        $reqSuccess = New-IntuneRequirementScript -ProjectPath $ProjectPath -JsonFilePath $jsonFile
        
        if ($reqSuccess) {
            Write-Host "✓ Intune requirement script generated" -ForegroundColor Green
        } else {
            Write-Warning "Failed to generate requirement script"
        }
        
        # Uninstall generation depends on installer type: EXE from the capture, MSI via PSADT
        # Zero-Config, MSIX/APPX identity-driven (written at configure time; re-run here as
        # belt-and-braces — it is an idempotent read-modify-write of AppConfig.json).
        switch ($InstallerType) {
            'exe' {
                Write-Verbose "Generating uninstall logic for EXE installer..."
                $uninstallSuccess = Update-PSADTUninstallLogic -ProjectPath $ProjectPath -JsonFilePath $jsonFile
                if ($uninstallSuccess) {
                    Write-Host "✓ Uninstall logic generated for EXE installer" -ForegroundColor Green
                } else {
                    Write-Warning "Failed to generate uninstall logic"
                }
            }
            'msi' {
                Write-Host "✓ MSI installer detected - PSADT Zero-Config uninstall handles removal" -ForegroundColor Green
            }
            { $_ -in @('msix', 'appx') } {
                if (Update-PSADTMsixUninstallLogic -ProjectPath $ProjectPath) {
                    Write-Host "✓ MSIX/APPX package - uninstall via Remove-AppxPackage (identity from AppxManifest)" -ForegroundColor Green
                } else {
                    Write-Warning 'Failed to write MSIX uninstall data'
                }
            }
            default {
                Write-Warning "Unknown installer type '$InstallerType' - no uninstall logic generated."
            }
        }

        # Populate AppProcessesToClose for all installer types
        Write-Verbose "Detecting processes to close..."
        $procSuccess = Update-PSADTProcessesToClose -ProjectPath $ProjectPath -JsonFilePath $jsonFile
        if (-not $procSuccess) {
            Write-Warning "Could not auto-detect processes to close - AppProcessesToClose left empty"
        }
        
        return $true
    }
    catch {
        Write-Warning "Error processing documentation: $($_.Exception.Message)"
        return $false
    }
}