Private/Test-VSCodeInstall.ps1

<#
    SPDX-License-Identifier: MIT
    Copyright (c) 2026 Leon McClatchey, Linktech Engineering LLC
 
    Package: VSCode-Updater
    Author: Leon McClatchey
    Company: Linktech Engineering LLC
    Created: 2026-07-17
    Modified: 2026-07-17
    File: Private/Test-VSCodeInstall.ps1
    Version: 1.0.0
    Description: Performs a full diagnostic health check on the VS Code installation.
#>

function Test-VSCodeInstall {
    [OutputType([Int32])]
    [CmdletBinding()]
    param()

    Write-VSCodeUpdaterLog "[HEALTH] Starting VS Code installation health check"

    $InstallRoot = Join-Path $env:LOCALAPPDATA "Programs\Microsoft VS Code"
    $CodeExe = Join-Path $InstallRoot "Code.exe"
    $PkgJson = Join-Path $InstallRoot "resources\app\package.json"
    $ProdJson = Join-Path $InstallRoot "resources\app\product.json"

    function Fail-HealthCheck {
        param([string]$Reason)
        Write-VSCodeUpdaterLog "[HEALTH] FAIL: $Reason"
        $script:LastHealthCheckState = "Failed"
        $script:LastHealthCheckReason = $Reason
        return [WatchdogExitCode]::HealthCheckFailed
    }

    if (-not (Test-Path $InstallRoot)) {
        return Fail-HealthCheck "Install root missing: $InstallRoot"
    }

    if (-not (Test-Path $CodeExe)) {
        return [WatchdogExitCode]::MissingCodeExe
    }

    if (-not (Test-Path $PkgJson)) {
        return Fail-HealthCheck "package.json missing"
    }

    try {
        $pkg = Get-Content $PkgJson -Raw | ConvertFrom-Json
    }
    catch {
        return Fail-HealthCheck "package.json unreadable or invalid JSON"
    }

    if (-not $pkg.version) {
        return Fail-HealthCheck "package.json missing version field"
    }

    $pkgVersion = $pkg.version
    Write-VSCodeUpdaterLog "[HEALTH] package.json version: $pkgVersion"

    if (-not (Test-Path $ProdJson)) {
        return Fail-HealthCheck "product.json missing"
    }

    try {
        $prod = Get-Content $ProdJson -Raw | ConvertFrom-Json
    }
    catch {
        return Fail-HealthCheck "product.json unreadable or invalid JSON"
    }

    if (-not $prod.commit) {
        return Fail-HealthCheck "product.json missing commit hash"
    }

    Write-VSCodeUpdaterLog "[HEALTH] product.json commit: $($prod.commit)"

    Write-VSCodeUpdaterLog "[HEALTH] Launching Code.exe --version"
    try {
        $proc = Start-Process -FilePath $CodeExe -ArgumentList "--version" -NoNewWindow -PassThru -Wait -ErrorAction Stop
        $exit = $proc.ExitCode
    }
    catch {
        return [WatchdogExitCode]::LaunchException
    }

    if ($exit -ne 0) {
        return [WatchdogExitCode]::LaunchFailed
    }

    try {
        $versionOutput = & $CodeExe --version 2>$null
    }
    catch {
        return [WatchdogExitCode]::LaunchException
    }

    if (-not $versionOutput) {
        return Fail-HealthCheck "Code.exe produced no version output"
    }

    $exeVersion = ($versionOutput | Select-Object -First 1).Trim()
    Write-VSCodeUpdaterLog "[HEALTH] Code.exe version: $exeVersion"

    if ($exeVersion -ne $pkgVersion) {
        return Fail-HealthCheck "Version mismatch: exe=$exeVersion pkg=$pkgVersion"
    }

    $tempWorkers = Get-ChildItem $env:TEMP -Filter "is-*" -ErrorAction SilentlyContinue
    if ($tempWorkers) {
        return Fail-HealthCheck "Leftover InnoSetup temp workers detected"
    }

    $tmpExe = Join-Path $InstallRoot "Code.exe.tmp"
    if (Test-Path $tmpExe) {
        return Fail-HealthCheck "Leftover Code.exe.tmp detected"
    }

    $partial = Get-ChildItem $InstallRoot -Directory -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^app-\d+\.\d+\.\d+-\d+$' }

    if ($partial) {
        return Fail-HealthCheck "Partial install folders detected"
    }

    $dlls = @(
        "chrome_elf.dll",
        "vcruntime140.dll",
        "msvcp140.dll"
    )

    foreach ($dll in $dlls) {
        $path = Join-Path $InstallRoot $dll
        if (-not (Test-Path $path)) {
            return Fail-HealthCheck "Missing required DLL: $dll"
        }
    }

    $symlinks = @(
        "resources\app\out",
        "resources\app\node_modules"
    )

    foreach ($link in $symlinks) {
        $path = Join-Path $InstallRoot $link
        if (-not (Test-Path $path)) {
            return Fail-HealthCheck "Broken symlink or missing path: $link"
        }
    }

    $codeProcs = Get-Process -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^Code$' }

    if ($codeProcs) {
        return Fail-HealthCheck "Stale Code.exe processes detected"
    }

    $inno = Get-Process -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^is-[A-Za-z0-9]+' }

    if ($inno) {
        return Fail-HealthCheck "Stale InnoSetup workers detected"
    }

    $tmp = Get-Process -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^tmp$|tmp\.exe$' }

    if ($tmp) {
        return Fail-HealthCheck "Stale tmp workers detected"
    }

    $helpers = Get-Process -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match '^CodeHelper' }

    if ($helpers) {
        return Fail-HealthCheck "Stale CodeHelper processes detected"
    }

    Write-VSCodeUpdaterLog "[HEALTH] VS Code installation is healthy"
    $script:LastHealthCheckState = "Success"
    $script:LastHealthCheckReason = $null
    return [WatchdogExitCode]::Success
}