public/Update-OSDWorkspaceISO.ps1

function Update-OSDWorkspaceISO {
    <#
    .SYNOPSIS
    Updates or creates a bootable OSD Workspace ISO using the Windows ADK and available WinPE builds.
 
    .DESCRIPTION
    This function prepares and updates a bootable ISO for the OSD Workspace environment. It checks for required prerequisites, verifies and manages the Windows ADK installation or cache, and allows selection of the appropriate ADK version if multiple are available. The function also enables selection of a WinPE build, sets up build variables, and initiates the ISO creation process. It is intended for use on Windows 10 or higher, requires PowerShell 5.0 or above, and must be run as Administrator.
 
    .PARAMETER None
    This function does not accept parameters.
 
    .NOTES
    Author: David Segura
    Version: 1.0
    Date: May 2025
 
    Prerequisites:
      - PowerShell 5.0 or higher
      - Windows 10 or higher
      - Run as Administrator
 
    .EXAMPLE
    Update-OSDWorkspaceISO
    Runs the function to update or create the OSD Workspace ISO using the available ADK and WinPE builds.
 
    .LINK
    https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install
    #>


    
    [CmdletBinding()]
    param ()
    #=================================================
    $Error.Clear()
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start"
    Initialize-OSDWorkspace
    #=================================================
    # Requires Run as Administrator
    $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $IsAdmin ) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator"
        return
    }
    #=================================================
    # Set Variables
    $ErrorActionPreference = 'Stop'
    #=================================================
    # Block
    Block-StandardUser
    Block-WindowsVersionNe10
    Block-PowerShellVersionLt5
    Block-WindowsReleaseIdLt1703
    #=================================================
    #region Test-IsWindowsAdkInstalled
    $IsWindowsAdkInstalled = Test-IsWindowsAdkInstalled -WarningAction SilentlyContinue
    $WindowsAdkInstallVersion = Get-WindowsAdkInstallVersion -WarningAction SilentlyContinue
    $WindowsAdkInstallPath = Get-WindowsAdkInstallPath -WarningAction SilentlyContinue
    
    if ($IsWindowsAdkInstalled) {
        if ($WindowsAdkInstallVersion) {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK install version is $WindowsAdkInstallVersion"
        }
        if ($WindowsAdkInstallPath) {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK install path is $WindowsAdkInstallPath"
        }
    }
    else {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK is not installed."
    }
    #endregion
    #=================================================
    #region Get and Update the ADK Cache
    $WSCachePath = $OSDWorkspace.paths.cache
    $WSAdkVersionsPath = $OSDWorkspace.paths.adk_versions
    #endregion
    #=================================================
    #region If ADK is installed then we need to update the cache
    if ($IsWindowsAdkInstalled) {
        $WindowsAdkRootPath = Join-Path $WSAdkVersionsPath $WindowsAdkInstallVersion
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK cache content is $WindowsAdkRootPath"
        $null = robocopy.exe "$WindowsAdkInstallPath" "$WindowsAdkRootPath" *.* /e /z /ndl /nfl /np /r:0 /w:0 /xj /mt:128
    }
    else {
        Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Cannot update the ADK cache because the ADK is not installed"
        $AdkSelectCacheVersion = $true
        Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] AdkSelectCacheVersion: $AdkSelectCacheVersion"
    }
    #endregion
    #=================================================
    #region Get the WindowsAdkCacheOptions
    $WindowsAdkCacheOptions = $null
    if (Test-Path $WSAdkVersionsPath) {
        $WindowsAdkCacheOptions = Get-ChildItem -Path "$WSAdkVersionsPath\*" -Directory -ErrorAction SilentlyContinue | Sort-Object -Property Name
    }
    #endregion
    #=================================================
    #region ADK is not installed and not present in the cache
    if (($IsWindowsAdkInstalled -eq $false) -and (-not $WindowsAdkCacheOptions)) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK is not installed"
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache does not contain an offline Windows ADK"
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK will need to be installed before using this function"
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install"
        return
    }
    #endregion
    #=================================================
    #region There is no usable ADK in the cache
    if ($WindowsAdkCacheOptions.Count -eq 0) {
        # Something is wrong, there should always be at least one ADK in the cache
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache does not contain an offline Windows ADK"
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Windows ADK will need to be installed before using this function"
        return
    }
    #endregion
    #=================================================
    #region ADK is available by this point and we either have 1 or more to select from
    if ($WindowsAdkCacheOptions.Count -eq 1) {
        # Only one version of the ADK is present in the cache, so this must be used
        $WindowsAdkRootPath = $WindowsAdkCacheOptions.FullName
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ADK cache contains 1 offline Windows ADK option"
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Using ADK cache at $WindowsAdkCacheSelected"

        # Can't select an ADK Version if there is only one
        $AdkSelectCacheVersion = $false
    }
    elseif ($WindowsAdkCacheOptions.Count -gt 1) {
        Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] $($WindowsAdkCacheOptions.Count) Windows ADK options are available to select from the ADK cache"
        if ($AdkSelectCacheVersion) {
            Write-Host -ForegroundColor DarkCyan "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Select a Windows ADK option and press OK (Cancel to Exit)"
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] To remove a Windows ADK option, delete one of the ADK cache directories in $WSAdkVersionsPath"
            $WindowsAdkCacheSelected = $WindowsAdkCacheOptions | Select-Object FullName | Sort-Object FullName -Descending | Out-GridView -Title 'Select a Windows ADK to use and press OK (Cancel to Exit)' -OutputMode Single
            if ($WindowsAdkCacheSelected) {
                $WindowsAdkRootPath = $WindowsAdkCacheSelected.FullName
            }
            else {
                Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to set the ADK cache path"
                return
            }
        }
    }
    else {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Something is wrong you should not be here"
        return
    }
    #endregion
    #=================================================
    # Do we have a Boot Media?
    $SelectWinPEMedia = Select-OSDWSWinPEBuild
    if ($null -eq $SelectWinPEMedia) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No OSDWorkspace WinPE Build was found or selected"
        return
    }
    #=================================================
    # Set the variables
    $BuildDateTime = $((Get-Date).ToString('yyMMdd-HHmm'))
    $MediaIsoLabel

    $MediaPathEX = Join-Path $SelectWinPEMedia.Path 'WinPE-MediaEX'
    if (-not (Test-Path $MediaPathEX)) {
        $MediaPathEX = $null
    }
    
    $global:BuildMedia = $null
    $global:BuildMedia = [ordered]@{
        AdkRootPath             = $WindowsAdkRootPath
        MediaIsoLabel           = $BuildDateTime
        MediaIsoName            = 'BootMedia.iso'
        MediaIsoNameEX          = 'BootMediaEX.iso'
        MediaPath               = Join-Path $SelectWinPEMedia.Path 'WinPE-Media'
        MediaPathEX             = $MediaPathEX
        MediaRootPath           = $SelectWinPEMedia.Path
    }

    Step-BuildMediaIso
    #=================================================
}