#Requires -RunAsAdministrator
<#
.SYNOPSIS
    Captures endpoint state at three points in the imaging lifecycle so
    they can be diffed pairwise.

.DESCRIPTION
    Run at three checkpoints:

    1) After PPKG enrollment, BEFORE the tech assigns a device category in
       Intune (the device is enrolled but no category-driven policy/apps
       have been delivered):
           .\Capture-LockdownState.ps1 -Stage pre-category

    2) After the device category is assigned and the device has synced
       (apps + policies tied to that category have arrived/started, but
       lockdown has not yet flipped the user from SupportUser to ShopFloor):
           .\Capture-LockdownState.ps1 -Stage post-category

    3) After lockdown DSC has fully landed (Winlogon DefaultUserName flipped,
       AssignedAccess kiosk active, SFLD creds populated):
           .\Capture-LockdownState.ps1 -Stage post-lockdown

    Output lands under C:\ProgramData\state-<stage>-<timestamp>\.
    Copy the whole folder back to the workstation
    (\\10.9.100.1\image-upload or dump to pxe-images manually) and diff.

    Diffing tips:
      pre-category  -> post-category  : what the category-driven Intune
                                        assignments brought in (the
                                        SFLD-DSC Win32App + its registry
                                        writes + scheduled tasks)
      post-category -> post-lockdown  : what the lockdown DSC profile
                                        finished (kiosk shell, autologon
                                        flip, AppLocker rules, etc.)

.PARAMETER Stage
    One of 'pre-category', 'post-category', 'post-lockdown'. Controls the
    output folder name. Legacy 'pre' / 'post' values are still accepted
    and map to 'pre-category' / 'post-lockdown' respectively.

.EXAMPLE
    .\Capture-LockdownState.ps1 -Stage pre-category
#>

param(
    [Parameter(Mandatory = $true)]
    [ValidateSet('pre-category', 'post-category', 'post-lockdown', 'pre', 'post')]
    [string]$Stage
)

# Map legacy values to new names
if ($Stage -eq 'pre')  { $Stage = 'pre-category' }
if ($Stage -eq 'post') { $Stage = 'post-lockdown' }

$ErrorActionPreference = 'Continue'

$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$out   = "C:\ProgramData\state-$Stage-$stamp"
New-Item -ItemType Directory -Path $out -Force | Out-Null

function Step($name, [scriptblock]$block) {
    Write-Host "[$Stage] $name ..."
    try { & $block } catch { Write-Warning "  $name failed: $_" }
}

# --- MDM policy store: the subkeys that change are the clearest lockdown signal
Step "export PolicyManager registry" {
    reg export 'HKLM\SOFTWARE\Microsoft\PolicyManager\current\device' "$out\PolicyManager.reg" /y | Out-Null
}

# --- MDM enrollment and provisioning sessions (category-targeted profiles land here)
Step "export Enrollments + Provisioning Sessions" {
    reg export 'HKLM\SOFTWARE\Microsoft\Enrollments'                    "$out\Enrollments.reg"            /y | Out-Null
    reg export 'HKLM\SOFTWARE\Microsoft\Provisioning\Sessions'          "$out\ProvisioningSessions.reg"   /y 2>$null | Out-Null
    reg export 'HKLM\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts'    "$out\OMADMAccounts.reg"          /y 2>$null | Out-Null
}

# --- GE-specific state (DSC, credentials, anything under GE\SFLD)
Step "export GE\SFLD registry" {
    reg export 'HKLM\SOFTWARE\GE\SFLD'          "$out\SFLD.reg"          /y 2>$null | Out-Null
    reg export 'HKLM\SOFTWARE\WOW6432Node\GE'   "$out\GE-WOW6432.reg"    /y 2>$null | Out-Null
}

# --- Intune Management Extension Win32App state (what apps are assigned + detection results)
Step "export IME Win32Apps registry" {
    reg export 'HKLM\SOFTWARE\Microsoft\IntuneManagementExtension' "$out\IME.reg" /y 2>$null | Out-Null
}

# --- Shell / Winlogon (kiosk replaces Shell value here, autologon values, etc.)
Step "export Winlogon" {
    reg export 'HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' "$out\Winlogon.reg" /y | Out-Null
}

# --- AppLocker / software restriction (lockdown commonly adds rules here)
Step "export AppLocker + SRP" {
    reg export 'HKLM\SOFTWARE\Policies\Microsoft\Windows\SrpV2'              "$out\AppLocker.reg" /y 2>$null | Out-Null
    reg export 'HKLM\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers' "$out\SRP.reg"     /y 2>$null | Out-Null
}

# --- WDAC / Code Integrity policies
Step "export CI / WDAC" {
    reg export 'HKLM\SYSTEM\CurrentControlSet\Control\CI'                   "$out\CI.reg" /y 2>$null | Out-Null
    if (Test-Path 'C:\Windows\System32\CodeIntegrity\CiPolicies\Active') {
        Copy-Item 'C:\Windows\System32\CodeIntegrity\CiPolicies\Active\*.cip' "$out\" -Force -EA SilentlyContinue
    }
}

# --- Shell Launcher / Assigned Access config (Win11 kiosk feature)
Step "export AssignedAccess + ShellLauncher" {
    reg export 'HKLM\SOFTWARE\Microsoft\Windows\AssignedAccessConfiguration' "$out\AssignedAccess.reg" /y 2>$null | Out-Null
    reg export 'HKLM\SOFTWARE\Microsoft\ShellLauncher'                       "$out\ShellLauncher.reg"  /y 2>$null | Out-Null
}

# --- Local users and groups (kiosk accounts get created)
Step "snapshot local users and groups" {
    try { Get-LocalUser | Select Name, Enabled, LastLogon, Description, SID |
          Export-Csv "$out\LocalUsers.csv" -NoTypeInformation } catch {}
    try { Get-LocalGroupMember Administrators -EA 0 | Select Name, PrincipalSource, ObjectClass |
          Export-Csv "$out\Admins.csv" -NoTypeInformation } catch {}
}

# --- Scheduled tasks (lockdown often registers a monitor/re-enforcer task)
Step "snapshot scheduled tasks" {
    Get-ScheduledTask | Select-Object TaskPath, TaskName, State,
        @{n='Actions';e={($_.Actions | ForEach-Object { "$($_.Execute) $($_.Arguments)" }) -join '; '}},
        @{n='Triggers';e={($_.Triggers | ForEach-Object { $_.CimClass.CimClassName }) -join ','}} |
        Sort-Object TaskPath, TaskName |
        Export-Csv "$out\Tasks.csv" -NoTypeInformation
}

# --- Services
Step "snapshot services" {
    Get-Service | Select Name, DisplayName, Status, StartType |
        Sort Name | Export-Csv "$out\Services.csv" -NoTypeInformation
}

# --- Installed apps (64 + 32 bit Uninstall hives)
Step "snapshot installed apps" {
    @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
      'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') |
        ForEach-Object { Get-ItemProperty $_ -EA 0 } |
        Where-Object DisplayName |
        Select DisplayName, DisplayVersion, Publisher, InstallDate |
        Sort DisplayName -Unique |
        Export-Csv "$out\InstalledApps.csv" -NoTypeInformation
}

# --- Desktop contents (lockdown strips desktop icons on kiosk)
Step "snapshot desktops" {
    Get-ChildItem -Path 'C:\Users\Public\Desktop' -ErrorAction SilentlyContinue |
        Select Name, Length, LastWriteTime |
        Export-Csv "$out\PublicDesktop.csv" -NoTypeInformation
    Get-ChildItem -Path 'C:\Users\SupportUser\Desktop' -ErrorAction SilentlyContinue |
        Select Name, Length, LastWriteTime |
        Export-Csv "$out\SupportUserDesktop.csv" -NoTypeInformation
}

# --- Relevant logs
Step "copy Intune + Enrollment logs" {
    $imeDir   = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
    $enrollDir = 'C:\Logs'
    if (Test-Path $imeDir) {
        New-Item "$out\IME-Logs" -ItemType Directory -Force | Out-Null
        Copy-Item "$imeDir\*.log" "$out\IME-Logs\" -Force -EA SilentlyContinue
    }
    if (Test-Path $enrollDir) {
        New-Item "$out\GE-Logs" -ItemType Directory -Force | Out-Null
        Copy-Item "$enrollDir\*.log" "$out\GE-Logs\" -Recurse -Force -EA SilentlyContinue
    }
}

# --- dsregcmd status (AAD / Entra join state)
Step "dsregcmd /status" {
    dsregcmd /status 2>&1 | Out-File "$out\dsregcmd.txt"
}

# --- SFLD credential manager YAML (identifies the Intune category)
Step "snapshot SFLD CredentialManager dir" {
    $cmDir = 'C:\ProgramData\SFLD\CredentialManager'
    if (Test-Path $cmDir) {
        Get-ChildItem $cmDir -Recurse | Select FullName, Length, LastWriteTime |
            Export-Csv "$out\SFLD-CredentialManager.csv" -NoTypeInformation
    }
}

# --- SFLD DSC payload directory (where the DSC bootstrap drops scripts/creds)
Step "snapshot SFLD DSC dir" {
    $dscDir = 'C:\ProgramData\SFLD\DSC'
    if (Test-Path $dscDir) {
        Get-ChildItem $dscDir -Recurse -Force | Select FullName, Length, LastWriteTime |
            Export-Csv "$out\SFLD-DSC-Files.csv" -NoTypeInformation
    }
}

# --- DSCDeployment.log (sits at C:\pc\, not under C:\Logs - written by the
#     SFLD-DSC Win32App bootstrap. Critical for diagnosing version drift,
#     missing sastoken.txt, fallback-path failures.)
Step "copy DSCDeployment + version artifacts" {
    foreach ($p in 'C:\pc\DSCDeployment.log','C:\pc\version.txt') {
        if (Test-Path $p) { Copy-Item $p "$out\" -Force -EA SilentlyContinue }
    }
}

# --- IMECache file listing per Win32App content folder. Each subdir is
#     <AppGUID>_<contentVersion>; the file list tells us which content
#     payloads landed correctly (e.g. sastoken.txt presence/absence).
#     Avoid copying .intunewin blobs (huge, encrypted) - just list.
Step "snapshot IMECache file listing" {
    $imeContent = 'C:\Windows\IMECache'
    if (Test-Path $imeContent) {
        Get-ChildItem $imeContent -Recurse -File -Force -EA SilentlyContinue |
            Select FullName, Length, LastWriteTime |
            Export-Csv "$out\IMECache-Files.csv" -NoTypeInformation
    }
}

# --- BPRT (PPKG bulk enrollment) runtime state. Setup-WCD.ps1 dumps
#     packageInfo.json + criticalChecks.json here; their values prove
#     whether PPKG parse + critical checks passed.
Step "copy BPRT runtime state" {
    $bprtDir = 'C:\Logs\BPRT'
    if (Test-Path $bprtDir) {
        New-Item "$out\BPRT" -ItemType Directory -Force | Out-Null
        Copy-Item "$bprtDir\*" "$out\BPRT\" -Recurse -Force -EA SilentlyContinue
    }
}

# --- C:\WCDApps state (where Setup-WCD.ps1 deploys helper scripts)
Step "snapshot WCDApps dir" {
    $wcd = 'C:\WCDApps'
    if (Test-Path $wcd) {
        Get-ChildItem $wcd -Recurse -File -Force -EA SilentlyContinue |
            Select FullName, Length, LastWriteTime |
            Export-Csv "$out\WCDApps-Files.csv" -NoTypeInformation
    }
}

# --- Windows Provisioning Diagnostics (Microsoft-side PPKG runtime logs)
Step "copy Windows Provisioning Diagnostics" {
    $pdiag = 'C:\Windows\Provisioning\Diagnostics'
    if (Test-Path $pdiag) {
        New-Item "$out\Provisioning-Diagnostics" -ItemType Directory -Force | Out-Null
        Copy-Item "$pdiag\*" "$out\Provisioning-Diagnostics\" -Recurse -Force -EA SilentlyContinue
    }
}

# --- Scheduled task last-run details (need LastRunTime + LastTaskResult, not just State)
Step "snapshot scheduled task run history" {
    Get-ScheduledTask | ForEach-Object {
        $info = $_ | Get-ScheduledTaskInfo -EA SilentlyContinue
        [pscustomobject]@{
            TaskPath        = $_.TaskPath
            TaskName        = $_.TaskName
            State           = $_.State
            LastRunTime     = $info.LastRunTime
            LastTaskResult  = $info.LastTaskResult
            NextRunTime     = $info.NextRunTime
            NumberOfMissedRuns = $info.NumberOfMissedRuns
        }
    } | Sort TaskPath, TaskName |
        Export-Csv "$out\Tasks-RunHistory.csv" -NoTypeInformation
}

# --- DeviceManagement event log (outbound MDM sync errors - 429 throttles,
#     AAD token failures, policy-pull stalls. Not visible in IME log alone.)
Step "export DeviceManagement event log" {
    Get-WinEvent -LogName 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin' `
        -MaxEvents 500 -EA SilentlyContinue |
        Select TimeCreated, Id, LevelDisplayName, Message |
        Export-Csv "$out\DeviceManagement-Events.csv" -NoTypeInformation
}

# --- Provisioning event log (PPKG application errors, runtime issues)
Step "export Provisioning event log" {
    Get-WinEvent -LogName 'Microsoft-Windows-Provisioning-Diagnostics-Provider/Admin' `
        -MaxEvents 500 -EA SilentlyContinue |
        Select TimeCreated, Id, LevelDisplayName, Message |
        Export-Csv "$out\Provisioning-Events.csv" -NoTypeInformation
}

# --- MDM device certificate (confirms enrollment is healthy at the cert layer)
Step "snapshot MDM certificates" {
    Get-ChildItem Cert:\LocalMachine\My -EA SilentlyContinue |
        Where-Object { $_.Subject -match 'MS-Organization|MS-Device|Microsoft Intune' -or $_.Issuer -match 'Microsoft Intune' } |
        Select Subject, Issuer, Thumbprint, NotBefore, NotAfter |
        Export-Csv "$out\MDM-Certificates.csv" -NoTypeInformation
}

Write-Host ""
Write-Host "Snapshot complete: $out"
Write-Host ""
Write-Host "Next: copy that folder to the workstation. Easiest:"
Write-Host "  net use Z: \\10.9.100.1\image-upload /user:pxe-upload pxe /persistent:no"
Write-Host "  robocopy `"$out`" `"Z:\state-$Stage-$stamp`" /E /NFL /NDL /NJH /NJS"
Write-Host "  net use Z: /delete /y"
