public/Invoke-OSDeployHydration.ps1

#Requires -PSEdition Core
#Requires -Version 7.4

<#
.SYNOPSIS
    Runs the complete OSDeploy hydration workflow end-to-end.
 
.DESCRIPTION
    Automates the full OSDeploy setup sequence on a supported Windows 11 25H2 machine:
 
      1. Validates system requirements (OS version, architecture, PowerShell edition and version).
      2. Prompts to install each software component individually. Windows ADK 25H2 and 7-Zip
         are required; Git for Windows, Visual Studio Code, and VS Code Insiders are optional.
         Required components that are declined will terminate the function.
      3. Downloads and imports the latest Windows Enterprise ESD for the detected architecture.
      4. Downloads WinPE drivers for the detected architecture.
      5. Builds a WinPE boot image named 'Hydra', using the newest available WinRE.
    Pass -Force to skip all software install prompts and run every step non-interactively.
    WinPE driver downloads process all matching sources automatically. If no WinRE source
    is found the ADK WinPE is used without prompting.
 
.EXAMPLE
    Invoke-OSDeployHydration
 
    Runs the full hydration workflow with interactive pickers at each step.
 
.EXAMPLE
    Invoke-OSDeployHydration -Force
 
    Runs the full hydration workflow non-interactively end-to-end.
 
.INPUTS
    None. This function does not accept pipeline input.
 
.OUTPUTS
    None.
 
.NOTES
    Author: David Segura
    Company: Recast Software
    Requires: Windows 11 25H2 (build 26200), PowerShell 7.4+ (MSI install), Windows ADK 25H2, Run as Administrator
#>

function Invoke-OSDeployHydration {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param (
        [Parameter()]
        [switch]$Force
    )

    Write-OSDeployBanner

    #region Pre-flight checks
    if (-not (Test-IsWindows11)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Invoke-OSDeployHydration requires Windows 11."
    }

    if (-not (Test-IsWindows1125H2)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Invoke-OSDeployHydration requires Windows 11 25H2 (build 26200)."
    }

    if (-not (Test-IsAdministrator)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Invoke-OSDeployHydration requires Administrator rights. Re-run PowerShell as Administrator and try again."
    }

    if (-not (Test-PwshVersionMin)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Invoke-OSDeployHydration requires PowerShell 7.4 or higher."
    }

    if (-not (Test-PwshPSHome)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Invoke-OSDeployHydration requires the MSI installation of PowerShell 7."
    }
    #endregion

    # Detect processor architecture — default to amd64, override only for arm64
    $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }
    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Architecture: $arch"

    $adkName = 'adk-25h2'
    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OS: Windows 11 25H2 — ADK: $adkName"

    # curl.exe is required by Install-MicrosoftWindowsAdk25H2 to download the ADK installer
    if (-not (Test-CommandCurl)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] curl.exe is required but was not found in the current PATH. curl.exe ships with Windows 10 1803+."
    }

    #region Intro — describe workflow and ask for confirmation
    Write-Host ''
    Write-Host -ForegroundColor Cyan  'Invoke-OSDeployHydration will perform the following actions:'
    Write-Host ''
    Write-Host -ForegroundColor White 'Install Software'
    Write-Host -ForegroundColor Gray  ' Windows ADK for Windows 11 25H2 (required)'
    Write-Host -ForegroundColor Gray  ' 7-Zip (required)'
    Write-Host -ForegroundColor Gray  ' Git for Windows (recommended)'
    Write-Host -ForegroundColor Gray  ' Microsoft Visual Studio Code (recommended)'
    Write-Host -ForegroundColor Gray  ' Microsoft Visual Studio Code Insiders (recommended)'
    if (-not (Test-IsVM)) {
        Write-Host -ForegroundColor Gray  ' Hyper-V (recommended — physical machines only)'
    }
    Write-Host ''
    Write-Host -ForegroundColor White 'OSDeploy Actions'
    Write-Host -ForegroundColor Gray  ' Download the latest Windows 11 25H2 ESD'
    Write-Host -ForegroundColor Gray  ' Extract Windows RE from the Windows ESD'
    Write-Host -ForegroundColor Gray  ' Download and expand the Dell WinPE DriverPack'
    Write-Host -ForegroundColor Gray  ' Download and expand the HP WinPE DriverPack'
    Write-Host -ForegroundColor Gray  ' Download and expand Intel Ethernet Drivers'
    Write-Host -ForegroundColor Gray  ' Download and expand Intel Wireless Drivers'
    Write-Host -ForegroundColor Gray  ' Build OSDeploy BootMedia for OSDCloud'
    if (-not (Test-IsVM)) {
        Write-Host -ForegroundColor Gray  ' Test BootMedia in Hyper-V (physical machines only)'
    }
    Write-Host ''

    $caption = 'Invoke-OSDeployHydration – Start Workflow'
    $message = "The steps listed above will be performed in sequence.`nSome steps are interactive and will prompt before making changes.`nRequired software that is declined will terminate the workflow.`n`nContinue?"
    if (-not ($Force -or $PSCmdlet.ShouldContinue($message, $caption))) {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Hydration workflow cancelled by user."
        return
    }
    #endregion

    #region Step 1 — Install software (required and optional components confirmed individually)

    # ── Required: Windows ADK 25H2 ────────────────────────────────────────────
    if (Test-InstallAdk25H2) {
        Write-Host -ForegroundColor Green "[$(Get-Date -format s)] Windows ADK 25H2 : already installed"
    }
    else {
        $caption = 'Install Windows ADK 25H2 – Required'
        $message = "Component : Windows ADK 25H2`nRequired : Yes`nNote : Required to build WinPE boot images.`n`nInstall Windows ADK 25H2?"
        if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
            Install-OSDeploySoftware -Name 'adk-25h2' -Force
        }
        else {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Windows ADK 25H2 is required. Installation declined — Invoke-OSDeployHydration cannot continue."
        }
    }

    # ── Required: 7-Zip ───────────────────────────────────────────────────────
    if (Test-Install7Zip) {
        Write-Host -ForegroundColor Green "[$(Get-Date -format s)] 7-Zip : already installed"
    }
    else {
        $caption = 'Install 7-Zip – Required'
        $message = "Component : 7-Zip`nRequired : Yes`nNote : Required to extract WinPE app archives during boot image builds.`n`nInstall 7-Zip?"
        if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
            Install-OSDeploySoftware -Name '7zip' -Force
        }
        else {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] 7-Zip is required. Installation declined — Invoke-OSDeployHydration cannot continue."
        }
    }

    # ── Optional: Git for Windows ─────────────────────────────────────────────
    if (Test-InstallGit) {
        Write-Host -ForegroundColor Green "[$(Get-Date -format s)] Git for Windows : already installed"
    }
    else {
        $caption = 'Install Git for Windows – Optional'
        $message = "Component : Git for Windows`nRequired : No`nNote : Recommended for source control and OSD repo operations.`n`nInstall Git for Windows?"
        if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
            Install-GitForWindows -NonInteractive
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Git for Windows skipped by user."
        }
    }

    # ── Optional: Visual Studio Code ──────────────────────────────────────────
    if (Test-InstallVSCode) {
        Write-Host -ForegroundColor Green "[$(Get-Date -format s)] Visual Studio Code : already installed"
    }
    else {
        $caption = 'Install Visual Studio Code – Optional'
        $message = "Component : Visual Studio Code`nRequired : No`nNote : Recommended for editing OSD scripts and configurations.`n`nInstall Visual Studio Code?"
        if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
            Install-OSDeploySoftware -Name 'code' -Force
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Visual Studio Code skipped by user."
        }
    }

    # ── Optional: Visual Studio Code Insiders ─────────────────────────────────
    if (Test-InstallVSCodeInsiders) {
        Write-Host -ForegroundColor Green "[$(Get-Date -format s)] VS Code Insiders : already installed"
    }
    else {
        $caption = 'Install Visual Studio Code Insiders – Optional'
        $message = "Component : Visual Studio Code Insiders`nRequired : No`nNote : Pre-release channel of VS Code with the latest features.`n`nInstall Visual Studio Code Insiders?"
        if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
            Install-OSDeploySoftware -Name 'code-insiders' -Force
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Visual Studio Code Insiders skipped by user."
        }
    }

    # ── Optional: Hyper-V (physical machines only) ────────────────────────────
    if (-not (Test-IsVM)) {
        if (Test-HyperVEnabled) {
            Write-Host -ForegroundColor Green "[$(Get-Date -format s)] Hyper-V : already installed"
        }
        else {
            $caption = 'Install Hyper-V – Optional'
            $message = "Component : Hyper-V`nRequired : No`nNote : Recommended for creating test VMs with New-OSDeployHyperVM.`n`nInstall Hyper-V?"
            if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
                Install-OSDeploySoftware -Name 'hyperv' -Force
            }
            else {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Hyper-V skipped by user."
            }
        }
    }

    #endregion

    #region Step 1.5 — Import Windows ESD
    if ($PSCmdlet.ShouldProcess('Windows Enterprise ESD', 'Download and import Windows ESD')) {
        Update-OSDeployCoreOS
    }
    #endregion

    #region Step 2 — Update WinPE drivers for the detected architecture
    Initialize-OSDeployCoreDrivers

    $filteredDriverNames = [string[]]($global:OSDeployModule.WinPEDrivers.PSObject.Properties |
        Where-Object { $_.Value.Architecture -eq $arch -and ($null -eq $_.Value.PSObject.Properties['Disabled'] -or $_.Value.PSObject.Properties['Disabled'].Value -ne $true) } |
        Select-Object -ExpandProperty Name)

    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] WinPE driver sources ($arch): $($filteredDriverNames -join ', ')"

    if ($filteredDriverNames) {
        $driverParams = @{ Name = $filteredDriverNames }
        Update-OSDeployCoreDrivers @driverParams
    }
    else {
        Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No WinPE driver sources found for architecture '$arch'."
    }
    #endregion

    #region Step 3 — Build boot image (prefer WinRE, fall back to ADK WinPE)
    $winRESources = Get-OSDeployCoreWinRESource -Architecture $arch

    $buildParams = @{
        Name         = 'Hydra'
        Architecture = $arch
    }

    if ($winRESources) {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] WinRE source(s) found for $arch — using WinRE."
    }
    else {
        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No WinRE sources found for $arch — falling back to ADK WinPE."
        $buildParams['UseAdkWinPE'] = $true
    }

    Build-OSDeployBoot @buildParams
    #endregion

    #region Step 4 — Test BootMedia in Hyper-V (physical machines only)
    if (-not (Test-IsVM)) {
        $isoPath = Join-Path $global:BuildMedia.MediaRootPath 'bootmedia.iso'
        if ((Test-HyperVEnabled) -and (Test-Path -Path $isoPath -PathType Leaf)) {
            $caption = 'Test BootMedia in Hyper-V – Optional'
            $message = "Action : New-OSDeployHyperVM`nISO : $isoPath`nNote : Creates a Hyper-V VM and boots the WinPE ISO to validate the build.`n`nTest BootMedia in Hyper-V?"
            if ($Force -or $PSCmdlet.ShouldContinue($message, $caption)) {
                New-OSDeployHyperVM -ISO $isoPath
            }
            else {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Hyper-V test skipped by user."
            }
        }
        else {
            Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Hyper-V test skipped — Hyper-V not enabled or ISO not found at '$isoPath'."
        }
    }
    #endregion
}