Public/OSDCloud/OSDCloudISO.ps1

function New-OSDCloudISO {
    <#
    .SYNOPSIS
    Creates an OSDCloud bootable ISO from an OSDCloud workspace.
 
    .DESCRIPTION
    Validates the local environment and generates an ISO from the workspace
    Media directory by calling New-WindowsAdkISO. If an OSDeploy marker file
    exists, the function creates an OSDeploy-labeled ISO for compatibility.
 
    .PARAMETER WorkspacePath
    Path to an OSDCloud workspace that contains Media\sources\boot.wim.
    If omitted, the current workspace returned by Get-OSDCloudWorkspace is used.
 
    .EXAMPLE
    New-OSDCloudISO -WorkspacePath 'C:\OSDCloud'
    Creates OSDCloud.iso from C:\OSDCloud\Media.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-09 - Updated comment-based help
    2026-07-16 - Improved validation, path handling, and error flow
    #>

    [CmdletBinding()]
    param (
        #Path to the OSDCloud Workspace containing the Media directory
        #This parameter is not necessary if Get-OSDCloudWorkspace can get a return
        [Parameter(Position=0,ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [System.String]$WorkspacePath
    )
    #=================================================
    # Dependency guard: Administrative rights, curl.exe, WinPE, and Windows 10 or later
    $CurrentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $CurrentPrincipal = [Security.Principal.WindowsPrincipal]::new($CurrentIdentity)
    if (-not $CurrentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Administrative rights are required"
    }

    # Dependency guard: Function relies on curl.exe for downloads
    if (-not (Get-Command -Name 'curl.exe' -ErrorAction SilentlyContinue)) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Requires 'curl.exe' which is not available on this system."
    }

    # Dependency guard: Must not be in WinPE environment (X: drive)
    if ($env:SystemDrive -eq 'X:') {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to run in WinPE (X: drive)."
    }

    # Dependency guard: Must be running Windows 10 or later
    if ([System.Environment]::OSVersion.Version.Major -ne 10) {
        throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Windows 10 or later is required."
    }
    #=================================================
    # Initialize
    #=================================================
    $isoFileName = 'OSDCloud.iso'
    $isoLabel = 'OSDCloud'
    $isoPath = $null
    $previousErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'
    #=================================================
    # WorkspacePath
    #=================================================
    try {
        if ($PSBoundParameters.ContainsKey('WorkspacePath')) {
            Set-OSDCloudWorkspace -WorkspacePath $WorkspacePath -ErrorAction Stop | Out-Null
        }
        $WorkspacePath = Get-OSDCloudWorkspace -ErrorAction Stop

        if (-not $WorkspacePath) {
            throw "Unable to find an OSDCloud Workspace"
        }

        if (-not (Test-Path -Path $WorkspacePath -PathType Container)) {
            throw "Unable to find an OSDCloud Workspace at $WorkspacePath"
        }

        $mediaPath = Join-Path $WorkspacePath 'Media'
        $bootWimPath = Join-Path $mediaPath 'sources\boot.wim'
        if (-not (Test-Path -Path $bootWimPath -PathType Leaf)) {
            throw "Unable to find an OSDCloud WinPE at $bootWimPath"
        }

        #region OSDeploy Compatibility
        if (Test-Path -Path (Join-Path $WorkspacePath 'OSDeploy.iso') -PathType Leaf) {
            $isoFileName = 'OSDeploy.iso'
            $isoLabel = 'OSDeploy'
        }
        #endregion
        #=================================================
        # Create ISO
        #=================================================
        $createdIso = New-WindowsAdkISO -MediaPath $mediaPath -isoFileName $isoFileName -isoLabel $isoLabel
        if ($createdIso -and $createdIso.FullName) {
            $isoPath = $createdIso.FullName
        }
        elseif ($createdIso -is [string]) {
            $isoPath = $createdIso
        }
        else {
            $isoPath = Join-Path $WorkspacePath $isoFileName
        }

        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Created ISO at $isoPath"
        Get-Item -Path $isoPath -ErrorAction SilentlyContinue
    }
    catch {
        Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] $($_.Exception.Message)"
        return
    }
    finally {
        $ErrorActionPreference = $previousErrorActionPreference
    }
}