Private/VerboseCycle.ps1

#Requires -Version 5.1
# The verbose-logging cycle (docs/08 "The verbose-logging cycle", T-027). This is the ONLY place the collector
# changes device state, so it is explicit, bounded and reversible:
# capture previous state -> enable -> wait for the reproduction (bounded) -> collect -> restore in `finally`
# `finally` also runs on Ctrl-C (PipelineStoppedException), on any error and on timeout. Restoration is verified;
# if it fails the exact manual command is returned and the caller exits with code 4.
# If the previous state cannot be determined the cycle REFUSES to run: it never enables what it cannot restore.

Set-StrictMode -Version Latest

function Wait-DDReproduction {
    # Wait until the operator presses Enter or the timeout elapses. Mockable seam.
    [CmdletBinding()]
    param([double] $TimeoutMinutes = 15, [string] $Message = 'Reproduce the issue now, then press Enter')

    Write-Information ("{0} (auto-continue in {1} min)" -f $Message, $TimeoutMinutes) -InformationAction Continue
    $deadline = (Get-Date).AddMinutes($TimeoutMinutes)
    $interactive = $false
    try { $interactive = [Environment]::UserInteractive -and -not [Console]::IsInputRedirected } catch { $interactive = $false }
    while ((Get-Date) -lt $deadline) {
        if ($interactive) {
            try { if ([Console]::KeyAvailable) { [void][Console]::ReadKey($true); return 'key' } } catch { $interactive = $false }
        }
        Start-Sleep -Seconds 1
    }
    'timeout'
}

function Invoke-DDVerboseCycle {
    <#
    .SYNOPSIS
        Run -Collect with verbose logging enabled, restoring the previous state in all cases.
    .OUTPUTS
        @{ Result = <collect output>; RestoreOk = bool; RestoreCommand = string|null; Reason = string|null }
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][scriptblock] $Collect,
        [double] $TimeoutMinutes = 15,
        # Microsoft documents Enable/Disable-DeliveryOptimizationVerboseLogs but no way to read the state. The operator can
        # state it ($false = "verbose logging is currently off"); otherwise an unknown state means the cycle refuses to run.
        [Nullable[bool]] $AssumePreviousState = $null
    )

    $previous = Get-DDDOVerboseLogState
    if ($null -eq $previous -and $null -ne $AssumePreviousState) { $previous = [bool]$AssumePreviousState }
    if ($null -eq $previous) {
        throw [System.InvalidOperationException]::new('Cannot determine the current Delivery Optimization verbose logging state (Microsoft documents no way to read it), so it will not be enabled: it could not be restored with certainty. Re-run with -AssumeVerboseLoggingOff if you know it is currently off, or collect without -ReproduceIssue.')
    }
    $enabledByUs = $false
    $result = $null
    $restoreOk = $true
    $restoreCommand = $null
    $restoreReason = $null
    $endedBy = $null
    try {
        if (-not $previous) {
            Enable-DDDOVerboseLogs
            $enabledByUs = $true
            Write-Information 'Step 1 of 3: Delivery Optimization verbose logging is on.' -InformationAction Continue
        } else {
            Write-Information 'Step 1 of 3: Delivery Optimization verbose logging was already on; it is left as it is.' -InformationAction Continue
        }
        $endedBy = Wait-DDReproduction -TimeoutMinutes $TimeoutMinutes -Message 'Step 2 of 3: reproduce the problem now (start the download, open the app or run the update that fails), then press Enter'
        Write-Information 'Step 3 of 3: capturing. DoctorDO does not stop or restart DoSvc; verbose logging is turned back off when the capture ends.' -InformationAction Continue
        $result = & $Collect
    } finally {
        if ($enabledByUs) {
            try {
                Disable-DDDOVerboseLogs
                $after = Get-DDDOVerboseLogState
                if ($null -ne $after -and $after -ne $previous) { throw 'verbose logging still reports enabled after Disable-DeliveryOptimizationVerboseLogs' }
            } catch {
                $restoreOk = $false
                $restoreReason = $_.Exception.Message
                $restoreCommand = 'Disable-DeliveryOptimizationVerboseLogs'
                Write-Warning "Could not restore Delivery Optimization verbose logging ($restoreReason). Run this command as administrator: $restoreCommand"
            }
        }
    }
    @{ Result = $result; RestoreOk = $restoreOk; RestoreCommand = $restoreCommand; Reason = $restoreReason; EnabledByCollector = $enabledByUs; EndedBy = $(if ($endedBy) { [string]$endedBy } else { $null }) }
}