Public/Functions/WindowsAdk.ps1

<#
.SYNOPSIS
Adds PowerShell and PowerShell Gallery support to ADK's x64 winpe.wim
 
.DESCRIPTION
Adds PowerShell and PowerShell Gallery support to ADK's x64 winpe.wim. This will speed things up with MDT and MEM CM going forward
 
.LINK
https://github.com/OSDeploy/OSD/tree/master/Docs
 
.NOTES
21.3.15.2 Initial Release
#>

function Edit-AdkWinPEWIM {
    [CmdletBinding()]
    param ()

    #=================================================
    # Start the Clock
    #=================================================
    $StartTime = Get-Date
    #=================================================
    # Blocks
    #=================================================
    Block-WinPE
    Block-StandardUser
    Block-NoCurl
    #=================================================
    # Set VerbosePreference
    #=================================================
    $CurrentVerbosePreference = $VerbosePreference
    $VerbosePreference = 'Continue'
    #=================================================
    # Get ADK
    #=================================================
    $WinPEArch = 'amd64'
    $WindowsAdkPaths = Get-WindowsAdkPaths -Architecture $WinPEArch

    if ($null -eq $WindowsAdkPaths) {
        Write-Warning "Could not get ADK going, sorry"
        Break
    }
    #=================================================
    # Get WinPE.wim
    #=================================================
    $WimSourcePath = $WindowsAdkPaths.WimSourcePath
    if (-NOT (Test-Path $WimSourcePath)) {
        Write-Warning "Could not find $WimSourcePath, sorry"
        Break
    }
    $WimSourceItem = Get-Item $WimSourcePath
    #=================================================
    # Create Backup
    #=================================================
    if (-NOT (Test-Path "$($WimSourceItem.Directory)\winpe.bak")) {
        $WimSourceItem | Copy-Item -Destination "$($WimSourceItem.Directory)\winpe.bak" -Force -ErrorAction Stop
    }
    #=================================================
    # Mount-MyWindowsImage
    #=================================================
    $MountMyWindowsImage = Mount-MyWindowsImage $WimSourceItem
    $MountPath = $MountMyWindowsImage.Path
    #=================================================
    # Add Packages
    #=================================================
    $ErrorActionPreference = 'Ignore'
    $WinPEOCs = $WindowsAdkPaths.WinPEOCs

    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-WMI.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-WMI_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-HTA.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-HTA_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-NetFx.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-NetFx_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-Scripting.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-Scripting_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-PowerShell.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-PowerShell_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-SecureStartup.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-SecureStartup_en-us.cab"

    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-DismCmdlets.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-DismCmdlets_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-Dot3Svc.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-Dot3Svc_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-EnhancedStorage.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-EnhancedStorage_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-FMAPI.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-GamingPeripherals.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-PPPoE.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-PPPoE_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-PlatformId.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-PmemCmdlets.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-PmemCmdlets_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-RNDIS.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-RNDIS_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-SecureBootCmdlets.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-StorageWMI.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-StorageWMI_en-us.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\WinPE-WDS-Tools.cab"
    Add-WindowsPackage -Path $MountPath -PackagePath "$WinPEOCs\en-us\WinPE-WDS-Tools_en-us.cab"
    #=================================================
    # cURL
    #=================================================
    Write-Verbose "Adding curl.exe to $MountPath"
    if (Test-Path "$env:SystemRoot\System32\curl.exe") {
        robocopy "$env:SystemRoot\System32" "$MountPath\Windows\System32" curl.exe /ndl /nfl /njh /njs /b
    } else {
        Write-Warning "Could not find $env:SystemRoot\System32\curl.exe"
        Write-Warning "You must be using an old version of Windows"
    }
    #=================================================
    # PowerShell Execution Policy
    #=================================================
    Write-Verbose "Setting PowerShell ExecutionPolicy to Bypass in $MountPath"
    Set-WindowsImageExecutionPolicy -Path $MountPath -ExecutionPolicy Bypass
    #=================================================
    # Enable PowerShell Gallery
    #=================================================
    Write-Verbose "Enabling PowerShell Gallery support in $MountPath"
    Enable-PEWindowsImagePSGallery -Path $MountPath

    #Write-Verbose "Saving OSD to $MountPath\Program Files\WindowsPowerShell\Modules"
    #Save-Module -Name OSD -Path "$MountPath\Program Files\WindowsPowerShell\Modules" -Force
    #=================================================
    # Startnet
    #=================================================
    #Write-Verbose "Adding PowerShell.exe to Startnet.cmd"
    #Add-Content -Path "$MountPath\Windows\System32\Startnet.cmd" -Value 'start powershell.exe' -Force
    #=================================================
    # DriverPath
    #=================================================
<# foreach ($Driver in $DriverPath) {
        Add-WindowsDriver -Path "$($MountMyWindowsImage.Path)" -Driver "$Driver" -Recurse -ForceUnsigned
    } #>

    #=================================================
    # Save WIM
    #=================================================
    $MountMyWindowsImage | Dismount-MyWindowsImage -Save
    #=================================================
    # Restore VerbosePreference
    #=================================================
    $VerbosePreference = $CurrentVerbosePreference
    #=================================================
    # Complete
    #=================================================
    $EndTime = Get-Date
    $TimeSpan = New-TimeSpan -Start $StartTime -End $EndTime
    Write-Host -ForegroundColor DarkGray    "================================================"
    Write-Host -ForegroundColor Yellow      "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) $($MyInvocation.MyCommand.Name) " -NoNewline
    Write-Host -ForegroundColor Cyan        "Completed in $($TimeSpan.ToString("mm' minutes 'ss' seconds'"))"
    #=================================================
}
function Get-WindowsAdkInstallPath {
    <#
    .SYNOPSIS
    Retrieves the installation path of the Windows Assessment and Deployment Kit (Windows ADK) from the registry.
 
    .DESCRIPTION
    Retrieves the installation path of the Windows Assessment and Deployment Kit (Windows ADK) from the registry.
 
    .NOTES
    Author: David Segura
    #>

    [CmdletBinding()]
    param ()

    $WindowsKitsInstallPath = Get-WindowsKitsInstallPath

    if ($WindowsKitsInstallPath) {
        $WindowsAdkInstallPath = Join-Path $WindowsKitsInstallPath 'Assessment and Deployment Kit'

        if (Test-Path "$WindowsAdkInstallPath") {
            Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))] Windows Assessment and Deployment Kit install path is $WindowsAdkInstallPath"
            return $WindowsAdkInstallPath
        }
        else {
            Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))] Windows Assessment and Deployment Kit is not installed"
            return $null
        }

    }
    else {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))] Windows Assessment and Deployment Kit is not installed"
        return $null
    }
}
function Get-WindowsAdkInstallVersion {
    <#
    .SYNOPSIS
    Retrieves the installed version of the Windows Assessment and Deployment Kit (Windows ADK) from the registry.
 
    .DESCRIPTION
    Retrieves the installed version of the Windows Assessment and Deployment Kit (Windows ADK) from the registry.
 
    .NOTES
    Author: David Segura
    #>

    [CmdletBinding()]
    param ()

    $Result = Get-ItemProperty HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue | `
    Where-Object { $_.DisplayName -eq 'Windows Assessment and Deployment Kit' } | Select-Object -ExpandProperty DisplayVersion -First 1

    return $Result
}
function Get-WindowsAdkPaths {
    <#
    .SYNOPSIS
    Retrieves the command paths of the Windows Assessment and Deployment Kit (ADK).
 
    .DESCRIPTION
    Retrieves the command paths of the Windows Assessment and Deployment Kit (ADK).
 
    .NOTES
    Author: David Segura
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true)]
        # Windows ADK architecture to get. Valid values are 'amd64', 'x86', and 'arm64'.
        [ValidateSet('amd64', 'x86', 'arm64')]
        [Alias('Arch')]
        [string]$Architecture = $Env:PROCESSOR_ARCHITECTURE,

        [Parameter(Position = 1, ValueFromPipelineByPropertyName = $true)]
        # Path to the Windows ADK root directory. Typically 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit'
        [ValidateScript({
            if (!($_ | Test-Path)) {
                throw 'Path does not exist'
            }
            if (!($_ | Test-Path -PathType Container)) {
                throw 'Path must be a directory'
            }
            if (!(Test-Path "$($_.FullName)\Deployment Tools")) {
                throw 'Path does not contain a Deployment Tools directory'
            }
            if (!(Test-Path "$($_.FullName)\Windows Preinstallation Environment")) {
                throw 'Path does not contain a Windows Preinstallation Environment directory'
            }
            return $true
        })]
        [System.IO.FileInfo]
        [Alias('AdkRoot')]
        $WindowsAdkRoot
    )
    #=================================================
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Start"
    $Error.Clear()
    #=================================================
    # region Get Windows ADK information from the Registry
    if (-not $WindowsAdkRoot) {
        $InstalledRoots32 = 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots'
        $InstalledRoots64 = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots'
        $RegistryValue = 'KitsRoot10'
        $KitsRoot10 = $null
 
        if (Test-Path -Path $InstalledRoots64) {
            $RegistryKey = Get-Item -Path $InstalledRoots64
            if ($null -ne $RegistryKey.GetValue($RegistryValue)) {
                $KitsRoot10 = Get-ItemPropertyValue -Path $InstalledRoots64 -Name $RegistryValue -ErrorAction SilentlyContinue
            }
        }

        if (-NOT ($KitsRoot10)) {
            if (Test-Path -Path $InstalledRoots32) {
                $RegistryKey = Get-Item -Path $InstalledRoots32
                if ($null -ne $RegistryKey.GetValue($RegistryValue)) {
                    $KitsRoot10 = Get-ItemPropertyValue -Path $InstalledRoots32 -Name $RegistryValue -ErrorAction SilentlyContinue
                }
            }
        }

        if ($KitsRoot10) {
            $WindowsAdkRoot = Join-Path $KitsRoot10 'Assessment and Deployment Kit'
        }
        else {
            Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Unable to determine ADK Path"
            return
        }
    }
    #endregion
    #=================================================
    # region Validate Windows ADK Path
    $WinPERoot = Join-Path $WindowsAdkRoot 'Windows Preinstallation Environment'
    if (-NOT (Test-Path $WinPERoot -PathType Container)) {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))] WinPERoot is not a valid path $WinPERoot"
        $WinPERoot = $null
        return
    }
    #endregion
    #=================================================
    # region Set ADK Paths
    $PathDeploymentTools = Join-Path $WindowsAdkRoot (Join-Path 'Deployment Tools' $Architecture)
    $PathWinPE = Join-Path $WinPERoot $Architecture
    #endregion
    #=================================================
    # region Build Results
    $Results = [PSCustomObject] @{
        #KitsRoot = $KitsRoot10
        AdkRoot             = $WindowsAdkRoot
        PathBCDBoot         = Join-Path $PathDeploymentTools 'BCDBoot'
        PathDeploymentTools = $PathDeploymentTools
        PathDISM            = Join-Path $PathDeploymentTools 'DISM'
        PathOscdimg         = Join-Path $PathDeploymentTools 'Oscdimg'
        PathUsmt            = Join-Path $WindowsAdkRoot (Join-Path 'User State Migration Tool' $Architecture)
        PathWinPE           = Join-Path $WinPERoot $Architecture
        PathWinPEMedia      = Join-Path $PathWinPE 'Media'
        PathWinSetup        = Join-Path $WindowsAdkRoot (Join-Path 'Windows Setup' $Architecture)
        WinPEOCs            = Join-Path $PathWinPE 'WinPE_OCs'
        WinPERoot           = $WinPERoot
        WimSourcePath       = Join-Path $PathWinPE 'en-us\winpe.wim'

        bcdbootexe          = Join-Path $PathDeploymentTools (Join-Path 'BCDBoot' 'bcdboot.exe')
        bcdeditexe          = Join-Path $PathDeploymentTools (Join-Path 'BCDBoot' 'bcdedit.exe')
        bootsectexe         = Join-Path $PathDeploymentTools (Join-Path 'BCDBoot' 'bootsect.exe')
        dismexe             = Join-Path $PathDeploymentTools (Join-Path 'DISM' 'dism.exe')
        efisysbin           = Join-Path $PathDeploymentTools (Join-Path 'Oscdimg' 'efisys.bin')
        efisysnopromptbin   = Join-Path $PathDeploymentTools (Join-Path 'Oscdimg' 'efisys_noprompt.bin')
        etfsbootcom         = if ($Architecture -eq 'arm64') {
            # ARM64 does not have a etfsboot.com | Redirect to amd64 folder
            Join-Path (Join-Path $WindowsAdkRoot (Join-Path 'Deployment Tools' 'amd64')) (Join-Path 'Oscdimg' 'etfsboot.com')
        }
        else {
            Join-Path $PathDeploymentTools (Join-Path 'Oscdimg' 'etfsboot.com')
        }
        imagexexe           = Join-Path $PathDeploymentTools (Join-Path 'DISM' 'imagex.exe')
        oa3toolexe          = Join-Path $PathDeploymentTools (Join-Path 'Licensing\OA30' 'oa3tool.exe')
        oscdimgexe          = Join-Path $PathDeploymentTools (Join-Path 'Oscdimg' 'oscdimg.exe')
        pkgmgrexe           = Join-Path $PathDeploymentTools (Join-Path 'DISM' 'pkgmgr.exe')
    }
    Return $Results
    #endregion
    #=================================================
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] End"
    #=================================================
}
function Get-WindowsKitsInstallPath {
    <#
    .SYNOPSIS
    Retrieves the installation path of the Windows Kit directory.
 
    .DESCRIPTION
    Retrieves the installation path of the Windows Kit directory.
 
    .NOTES
    Author: David Segura
    #>

    [CmdletBinding()]
    param ()

    # 32-bit Registry
    $InstalledRoots32 = 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots'
    # 64-bit Registry
    $InstalledRoots64 = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots'
    
    $RegistryValue = 'KitsRoot10'
    $KitsRoot10 = $null

    # Test for 64-bit Registry
    if (Test-Path -Path $InstalledRoots64) {
        $RegistryKey = Get-Item -Path $InstalledRoots64
        if ($null -ne $RegistryKey.GetValue($RegistryValue)) {
            $KitsRoot10 = Get-ItemPropertyValue -Path $InstalledRoots64 -Name $RegistryValue -ErrorAction SilentlyContinue
        }
    }

    if (-not ($KitsRoot10)) {
        if (Test-Path -Path $InstalledRoots32) {
            $RegistryKey = Get-Item -Path $InstalledRoots32
            if ($null -ne $RegistryKey.GetValue($RegistryValue)) {
                $KitsRoot10 = Get-ItemPropertyValue -Path $InstalledRoots32 -Name $RegistryValue -ErrorAction SilentlyContinue
            }
        }
    }


    if ($KitsRoot10) {
        if (Test-Path "$KitsRoot10") {
            Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))] Windows Kits install path is $KitsRoot10"
            return $KitsRoot10
        }
        else {
            Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))] Windows Kits install path from the registry does not exist at $KitsRoot10"
            return $null
        }
    }
    else {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))] Windows Kits is not installed"
        return $null
    }
}
<#
.SYNOPSIS
Creates an ADK CopyPE Working Directory
 
.DESCRIPTION
Creates an ADK CopyPE Working Directory
 
.LINK
https://github.com/OSDeploy/OSD/tree/master/Docs
 
.NOTES
21.5.27.2 Resolved issue with paths
21.3.15.2 Renamed to make it easier to understand what it does
21.3.10 Initial Release
#>

function New-AdkCopyPE {
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Path,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('amd64','x86','arm64')]
        [string]$WinPEArch = 'amd64'
    )

    #=================================================
    # Require Admin Rights
    #=================================================
    if ((Get-OSDGather -Property IsAdmin) -eq $false) {
        Write-Warning "$($MyInvocation.MyCommand) requires Admin Rights ELEVATED"
        Break
    }
    #=================================================
    # Get Adk Paths
    #=================================================
    $WindowsAdkPaths = Get-WindowsAdkPaths -Architecture $WinPEArch
    #=================================================
    $Destination = $Path

    $AdkWimSourcePath = $WindowsAdkPaths.WimSourcePath
    $AdkPathOscdimg = $WindowsAdkPaths.PathOscdimg
    $AdkPathWinPEMedia = $WindowsAdkPaths.PathWinPEMedia

    $DestinationMedia = Join-Path $Destination 'media'
    if (-NOT (Test-Path $DestinationMedia)) {
        New-Item -Path $DestinationMedia -ItemType Directory -Force -ErrorAction Stop | Out-Null
    }

    $DestinationMount = Join-Path $Destination 'mount'
    if (-NOT (Test-Path $DestinationMount)) {
        New-Item -Path $DestinationMount -ItemType Directory -Force -ErrorAction Stop | Out-Null
    }

    $DestinationFirmwareFiles = Join-Path $Destination 'fwfiles'
    if (-NOT (Test-Path $DestinationFirmwareFiles)) {
        New-Item -Path $DestinationFirmwareFiles -ItemType Directory -Force -ErrorAction Stop | Out-Null
    }

    $DestinationSources = Join-Path $DestinationMedia 'sources'
    if (-NOT (Test-Path $DestinationSources)) {
        New-Item -Path $DestinationSources -ItemType Directory -Force -ErrorAction Stop | Out-Null
    }

    xcopy /herky "$AdkPathWinPEMedia" "$DestinationMedia\"

    Copy-Item "$AdkWimSourcePath" "$DestinationSources\boot.wim"
    Copy-Item "$AdkPathOscdimg\efisys.bin" "$DestinationFirmwareFiles"
    Copy-Item "$AdkPathOscdimg\etfsboot.com" "$DestinationFirmwareFiles"
    #=================================================
}
<#
.SYNOPSIS
Creates an .iso file from a bootable media directory. ADK is required
 
.DESCRIPTION
Creates a .iso file from a bootable media directory. ADK is required
 
.PARAMETER MediaPath
Directory containing the bootable media
 
.PARAMETER isoFileName
File Name of the ISO
 
.PARAMETER isoLabel
Label of the ISO. Limited to 16 characters
 
.PARAMETER OpenExplorer
Opens Windows Explorer to the parent directory of the ISO File
 
.LINK
https://github.com/OSDeploy/OSD/tree/master/Docs
 
.NOTES
21.3.16 Initial Release
#>

function New-AdkISO {
    [CmdletBinding()]
    param (
        [Alias('AdkRoot')]
        [System.String]
        $WindowsAdkRoot,

        [Parameter(Mandatory = $true)]
        [string]$MediaPath,

        [Parameter(Mandatory = $true)]
        [string]$isoFileName,

        [Parameter(Mandatory = $true)]
        [ValidateLength(1,16)]
        [string]$isoLabel,

        #[System.Management.Automation.SwitchParameter]$NoPrompt,
        #[System.Management.Automation.SwitchParameter]$Mount,
        [System.Management.Automation.SwitchParameter]$OpenExplorer
    )
    Write-Warning "New-AdkISO has been replaced by New-WindowsAdkISO. Please update your scripts"
    Start-Sleep -Seconds 10
    #=================================================
    # Blocks
    #=================================================
    Block-WinPE
    Block-StandardUser
    Block-PowerShellVersionLt5
    #=================================================
    # Get Adk Paths
    #=================================================
    if ($WindowsAdkRoot) {
        $AdkPaths = Get-WindowsAdkPaths -WindowsAdkRoot $WindowsAdkRoot
    } else {
        $AdkPaths = Get-WindowsAdkPaths
    }
    if ($null -eq $AdkPaths) {
        Write-Warning "Could not get ADK going, sorry"
        Break
    }
    $WorkspacePath = (Get-Item -Path $MediaPath -ErrorAction Stop).Parent.FullName
    $IsoFullName = Join-Path $WorkspacePath $isoFileName
    $PathOscdimg = $AdkPaths.PathOscdimg
    $oscdimgexe = $AdkPaths.oscdimgexe

    Write-Verbose "WorkspacePath: $WorkspacePath"
    Write-Verbose "IsoFullName: $IsoFullName"
    Write-Verbose "PathOscdimg: $PathOscdimg"
    Write-Verbose "oscdimgexe: $oscdimgexe"
    #=================================================
    # Test Paths
    #=================================================
    $DestinationBoot = Join-Path $MediaPath 'boot'
    if (-NOT (Test-Path $DestinationBoot)) {
        Write-Warning "Cannot locate $DestinationBoot"
        Write-Warning "This does not appear to be a valid bootable ISO"
        Break
    }
    $DestinationEfiBoot = Join-Path $MediaPath 'efi\microsoft\boot'
    if (-NOT (Test-Path $DestinationEfiBoot)) {
        Write-Warning "Cannot locate $DestinationEfiBoot"
        Write-Warning "This does not appear to be a valid bootable ISO"
        Break
    }
    #=================================================
    # etfsboot.com
    #=================================================
    $etfsbootcom = $AdkPaths.etfsbootcom
    Copy-Item -Path $etfsbootcom -Destination $DestinationBoot -Force -ErrorAction Stop
    $Destinationetfsbootcom = Join-Path $DestinationBoot 'etfsboot.com'
    #=================================================
    # efisys.bin and efisys_noprompt.bin
    #=================================================
    $efisysbin = $AdkPaths.efisysbin
    Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
    $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys.bin'

    $efisysnopromptbin = $AdkPaths.efisysnopromptbin
    Copy-Item -Path $efisysnopromptbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
    $Destinationefisysnopromptbin = Join-Path $DestinationEfiBoot 'efisys_noprompt.bin'

<# if ($PSBoundParameters.ContainsKey('NoPrompt')) {
        $efisysbin = $AdkPaths.efisysnopromptbin
        Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
        $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys_noprompt.bin'
    } else {
        $efisysbin = $AdkPaths.efisysbin
        Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
        $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys.bin'
    } #>


    Write-Verbose  "DestinationBoot: $DestinationBoot"
    Write-Verbose  "etfsbootcom: $etfsbootcom"
    Write-Verbose  "Destinationetfsbootcom: $Destinationetfsbootcom"

    Write-Verbose  "DestinationEfiBoot: $DestinationEfiBoot"
    Write-Verbose  "efisysbin: $efisysbin"
    Write-Verbose  "Destinationefisysbin: $Destinationefisysbin"
    Write-Verbose  "efisysnopromptbin: $efisysnopromptbin"
    Write-Verbose  "Destinationefisysnopromptbin: $Destinationefisysnopromptbin"
    #=================================================
    # Strings
    #=================================================
    $isoLabelString = '-l"{0}"' -f "$isoLabel"
    Write-Verbose  "isoLabelString: $isoLabelString"
    #=================================================
    # Create Prompt ISO
    #=================================================
    $BootDataString = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$Destinationetfsbootcom", "$Destinationefisysbin"
    Write-Verbose  "BootDataString: $BootDataString"

    $Process = Start-Process $oscdimgexe -args @("-m","-o","-u2","-bootdata:$BootDataString",'-u2','-udfver102',$isoLabelString,"`"$MediaPath`"", "`"$IsoFullName`"") -PassThru -Wait -NoNewWindow

    if (-NOT (Test-Path $IsoFullName)) {
        Write-Error "Something didn't work"
        Break
    }
    $PromptIso = Get-Item -Path $IsoFullName
    #Write-Host -ForegroundColor Yellow "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) ISO created at $PromptIso"
    #=================================================
    # Create NoPrompt ISO
    #=================================================
    $IsoFullName = "$($PromptIso.Directory)\$($PromptIso.BaseName)_NoPrompt.iso"
    $BootDataString = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$Destinationetfsbootcom", "$Destinationefisysnopromptbin"
    Write-Verbose  "BootDataString: $BootDataString"

    $Process = Start-Process $oscdimgexe -args @("-m","-o","-u2","-bootdata:$BootDataString",'-u2','-udfver102',$isoLabelString,"`"$MediaPath`"", "`"$IsoFullName`"") -PassThru -Wait -NoNewWindow

    if (-NOT (Test-Path $IsoFullName)) {
        Write-Error "Something didn't work"
        Break
    }
    $NoPromptIso = Get-Item -Path $IsoFullName
    #Write-Host -ForegroundColor Yellow "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) ISO created at $NoPromptIso"
    #=================================================
    # OpenExplorer
    #=================================================
    if ($PSBoundParameters.ContainsKey('OpenExplorer')) {
        explorer $WorkspacePath
    }
    #=================================================
    # Return Get-Item
    #=================================================
    Return $PromptIso

<# $Results += [pscustomobject]@{
        FullName = $IsoFullName
        Name = $isoFileName
        Label = $isoLabel
        isoDirectory = $MediaPath
    }
    Return $Results #>

    #=================================================
}
function New-WindowsAdkISO {
    <#
        .SYNOPSIS
        Creates an .iso file from a bootable media directory. ADK is required
 
        .DESCRIPTION
        Creates a .iso file from a bootable media directory. ADK is required
 
        .NOTES
        David Segura
        25.02.26 Initial Release replacing New-AdkISO
        25.03.01 Updated to use Get-WindowsAdkPaths
    #>

    [CmdletBinding()]
    param (
        # Path to the Windows ADK root directory. Typically 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit'
        [ValidateScript({
                if (-NOT ($_ | Test-Path)) { throw "Path does not exist: $_" }
                if (-NOT ($_ | Test-Path -PathType Container)) { throw "Path must be a directory: $_" }
                if (-NOT (Test-Path "$($_.FullName)\Deployment Tools")) { throw "Path does not contain a Deployment Tools subfolder: $_" }
                # if (-NOT (Test-Path "$($_.FullName)\Windows Preinstallation Environment")) { throw "Path does not contain a Windows Preinstallation Environment directory: $_"}
                return $true
            })]
        [System.IO.FileInfo]
        $WindowsAdkRoot,

        # Directory containing the bootable media
        [Parameter(Mandatory = $true)]
        [ValidateScript({
                if (-NOT ($_ | Test-Path)) { throw "Path does not exist: $_" }
                if (-NOT ($_ | Test-Path -PathType Container)) { throw "Path must be a directory: $_" }
                return $true
            })]
        [System.IO.FileInfo]
        $MediaPath,

        # File Name of the ISO
        [Parameter(Mandatory = $true)]
        [System.String]
        $isoFileName,

        # Label of the ISO. Limited to 16 characters
        [Parameter(Mandatory = $true)]
        [ValidateLength(1,16)]
        [System.String]
        $isoLabel,

        #[System.Management.Automation.SwitchParameter]$NoPrompt,
        #[System.Management.Automation.SwitchParameter]$Mount,

        # Opens Windows Explorer to the parent directory of the ISO File
        [System.Management.Automation.SwitchParameter]
        $OpenExplorer
    )
    #=================================================
    # Start
    $Error.Clear()
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Start"
    #=================================================
    # Blocks
    Block-WinPE
    Block-StandardUser
    Block-PowerShellVersionLt5
    #=================================================
    # Get Adk Paths
    if ($WindowsAdkRoot) {
        $WindowsAdkPaths = Get-WindowsAdkPaths -WindowsAdkRoot $WindowsAdkRoot
    } else {
        $WindowsAdkPaths = Get-WindowsAdkPaths
    }
    if ($null -eq $WindowsAdkPaths) {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Could not get ADK going, sorry"
        Break
    }
    $WorkspacePath = (Get-Item -Path $MediaPath -ErrorAction Stop).Parent.FullName
    $IsoFullName = Join-Path $WorkspacePath $isoFileName
    $PathOscdimg = $WindowsAdkPaths.PathOscdimg
    $oscdimgexe = $WindowsAdkPaths.oscdimgexe

    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] WorkspacePath: $WorkspacePath"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] IsoFullName: $IsoFullName"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] PathOscdimg: $PathOscdimg"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] oscdimgexe: $oscdimgexe"
    #=================================================
    # Test Paths
    $DestinationBoot = Join-Path $MediaPath 'boot'
    if (-NOT (Test-Path $DestinationBoot)) {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Cannot locate $DestinationBoot"
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] This does not appear to be a valid bootable ISO"
        Break
    }
    $DestinationEfiBoot = Join-Path $MediaPath 'efi\microsoft\boot'
    if (-NOT (Test-Path $DestinationEfiBoot)) {
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Cannot locate $DestinationEfiBoot"
        Write-Warning "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] This does not appear to be a valid bootable ISO"
        Break
    }
    #=================================================
    # etfsboot.com
    $etfsbootcom = $WindowsAdkPaths.etfsbootcom
    $Destinationetfsbootcom = Join-Path $DestinationBoot 'etfsboot.com'
    if (Test-Path $Destinationetfsbootcom) {
        Write-Host -ForegroundColor DarkGray "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Using existing $Destinationetfsbootcom"
    }
    else {
        Copy-Item -Path $etfsbootcom -Destination $DestinationBoot -Force -ErrorAction Stop
    }
    #=================================================
    # efisys.bin and efisys_noprompt.bin
    $efisysbin = $WindowsAdkPaths.efisysbin
    $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys.bin'
    if (Test-Path $Destinationefisysbin) {
        Write-Host -ForegroundColor DarkGray "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Using existing $Destinationefisysbin"
    }
    else {
        Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
    }

    $efisysnopromptbin = $WindowsAdkPaths.efisysnopromptbin
    $Destinationefisysnopromptbin = Join-Path $DestinationEfiBoot 'efisys_noprompt.bin'
    if (Test-Path $Destinationefisysnopromptbin) {
        Write-Host -ForegroundColor DarkGray "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Using existing $Destinationefisysnopromptbin"
    }
    else {
        Copy-Item -Path $efisysnopromptbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
    }

<# if ($PSBoundParameters.ContainsKey('NoPrompt')) {
        $efisysbin = $WindowsAdkPaths.efisysnopromptbin
        Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
        $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys_noprompt.bin'
    } else {
        $efisysbin = $WindowsAdkPaths.efisysbin
        Copy-Item -Path $efisysbin -Destination $DestinationEfiBoot -Force -ErrorAction Stop
        $Destinationefisysbin = Join-Path $DestinationEfiBoot 'efisys.bin'
    } #>


    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] DestinationBoot: $DestinationBoot"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] etfsbootcom: $etfsbootcom"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Destinationetfsbootcom: $Destinationetfsbootcom"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] DestinationEfiBoot: $DestinationEfiBoot"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] efisysbin: $efisysbin"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Destinationefisysbin: $Destinationefisysbin"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] efisysnopromptbin: $efisysnopromptbin"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Destinationefisysnopromptbin: $Destinationefisysnopromptbin"
    #=================================================
    # Strings
    $isoLabelString = '-l"{0}"' -f "$isoLabel"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] isoLabelString: $isoLabelString"
    #=================================================
    # Create Prompt ISO
    $BootDataString = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$Destinationetfsbootcom", "$Destinationefisysbin"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] BootDataString: $BootDataString"

    $Process = Start-Process $oscdimgexe -args @('-m', '-o', '-u2', "-bootdata:$BootDataString", '-u2', '-udfver102', $isoLabelString, "`"$MediaPath`"", "`"$IsoFullName`"") -PassThru -Wait -WindowStyle Hidden

    if (-NOT (Test-Path $IsoFullName)) {
        Write-Error "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Something didn't work"
        Break
    }
    $PromptIso = Get-Item -Path $IsoFullName
    Write-Host -ForegroundColor DarkCyan "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] ISO created at $PromptIso"
    #=================================================
    # Create NoPrompt ISO
    $IsoFullName = "$($PromptIso.Directory)\$($PromptIso.BaseName)_NoPrompt.iso"
    $BootDataString = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$Destinationetfsbootcom", "$Destinationefisysnopromptbin"
    Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] BootDataString: $BootDataString"

    $Process = Start-Process $oscdimgexe -args @('-m', '-o', '-u2', "-bootdata:$BootDataString", '-u2', '-udfver102', $isoLabelString, "`"$MediaPath`"", "`"$IsoFullName`"") -PassThru -Wait -WindowStyle Hidden

    if (-NOT (Test-Path $IsoFullName)) {
        Write-Error "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] Something didn't work"
        Break
    }
    $NoPromptIso = Get-Item -Path $IsoFullName
    Write-Host -ForegroundColor DarkCyan "[$((Get-Date).ToString('HH:mm:ss'))][$($MyInvocation.MyCommand)] ISO created at $NoPromptIso"
    #=================================================
    # Open Windows Explorer
    if ($PSBoundParameters.ContainsKey('OpenExplorer')) {
        explorer $WorkspacePath
    }
    #=================================================
    # Return Get-Item
    Return $PromptIso

    <# $Results += [pscustomobject]@{
        FullName = $IsoFullName
        Name = $isoFileName
        Label = $isoLabel
        isoDirectory = $MediaPath
    }
    Return $Results #>

    #=================================================
}