Public/Import-HDTOperatingSystem.ps1
|
function Import-HDTOperatingSystem { <# .SYNOPSIS Promotes an operating system source into the workspace catalog. .DESCRIPTION Import-HDTOperatingSystem promotes a capture into the OS catalog. The workspace layout fixes where it lands - OperatingSystems\<id>\os.yaml - and this is the only writer of that document. THE IMAGE LIST IS READ FROM THE IMAGE FILE, NEVER TYPED BY HAND. It comes through IImageService.GetImageInfo, so the catalog cannot disagree with the media it describes. A catalog whose indices an author entered is a catalog that lies the first time the media is rebuilt, and it lies to the step that decides what to apply. EVERY PATH IS BUILT WITH Get-HDTWorkspacePath. That command exists because a plan once did not: Start-HDTResume built its path from the literal 'Sequences' while the layout said 'TaskSequences', the unit suite was green because nothing in it resolved a real workspace path, and a deployment would have died at its first reboot. No literal 'OperatingSystems' appears in this file. -Copy IS OPT-IN BECAUSE COPYING 4 GB IS A REAL OPERATION. Without it the tree is registered where it stands and sourcePath is recorded as given. With it, the directory holding the image is copied to <os folder>\sources and sourcePath becomes the relative sources\<file>, which is what the workspace layout shows. THE CLOCK IS MANDATORY, for the reason it is mandatory everywhere in this engine: the only default is a real clock reading inside engine code, and a timestamp nobody injected is a timestamp no test can assert on. .PARAMETER WorkspaceRoot The workspace root - a local path or a UNC share. .PARAMETER Id The catalog id. Becomes the folder name under OperatingSystems\, so it must match ^[A-Za-z0-9][A-Za-z0-9_.-]*$. .PARAMETER SourcePath The .wim or .ffu to import. .PARAMETER FileSystem An IFileSystem. .PARAMETER ImageService An IImageService. Only GetImageInfo is called. .PARAMETER Clock An IClock. Stamps importedUtc. .PARAMETER Name The display name. Defaults to the id. .PARAMETER Description A free-text note for the console. .PARAMETER Copy Copy the source tree into the workspace as well as cataloguing it. .PARAMETER Force Overwrite an existing os.yaml for this id. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject - the catalog it wrote, in the shape Get-HDTOperatingSystem returns. .EXAMPLE Import-HDTOperatingSystem -WorkspaceRoot 'X:\Deploy' -Id 'Win11-LTSC-2024' ` -SourcePath 'C:\HDTLab\media\Win11-LTSC-2024\sources\install.wim' ` -FileSystem (New-HDTFileSystem) -ImageService (New-HDTImageService) -Clock (New-HDTClock) Registers the staged media in place - seconds, not gigabytes. .EXAMPLE Import-HDTOperatingSystem -WorkspaceRoot 'X:\Deploy' -Id 'Win11-LTSC-2024' ` -SourcePath $wim -FileSystem $fs -ImageService $image -Clock $clock -Copy -Force Brings the media into the share, replacing an existing entry. #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $WorkspaceRoot, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Id, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SourcePath, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $FileSystem, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $ImageService, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $Clock, [Parameter()] [string] $Name, [Parameter()] [string] $Description, [Parameter()] [switch] $Copy, [Parameter()] [switch] $Force ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($Id -notmatch '^[A-Za-z0-9][A-Za-z0-9_.-]*$') { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Id ` -Message ("'{0}' is not a legal operating system id. It becomes a folder name under the workspace's operating system folder, so it must start with a letter or a digit and hold only letters, digits, underscore, dot and hyphen." -f $Id))) } $osFolder = Get-HDTWorkspacePath -Root $WorkspaceRoot -Kind OperatingSystems -ChildPath $Id $catalogPath = Get-HDTWorkspacePath -Root $WorkspaceRoot -Kind OperatingSystems -ChildPath $Id, 'os.yaml' if ($FileSystem.TestPath($catalogPath) -and -not $Force) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Path $catalogPath ` -Message ("an operating system with the id '{0}' is already in this workspace. Use -Force to replace it." -f $Id))) } if (-not $FileSystem.TestPath($SourcePath)) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Path $SourcePath ` -Message 'the source image does not exist.' -Category ObjectNotFound)) } $image = @($ImageService.GetImageInfo($SourcePath)) if ($image.Count -eq 0) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -Path $SourcePath ` -Message 'the source image declares no index, so there is nothing to catalogue.')) } $type = 'wim' if (([System.IO.Path]::GetExtension($SourcePath)).ToLowerInvariant() -eq '.ffu') { $type = 'ffu' } $displayName = $Id if (-not [string]::IsNullOrWhiteSpace($Name)) { $displayName = $Name } # DISM reports a NUMERIC architecture code, not a string (04-01: both staged # media report 9). An unrecognised code is omitted rather than guessed at. $architectureByCode = @{ '0' = 'x86'; '9' = 'x64'; '12' = 'arm64' } $architecture = '' $firstArchitecture = [string] $image[0].Architecture if ($architectureByCode.ContainsKey($firstArchitecture)) { $architecture = $architectureByCode[$firstArchitecture] } $recordedSourcePath = $SourcePath $sourceTree = [System.IO.Path]::GetDirectoryName($SourcePath) $sourceLeaf = [System.IO.Path]::GetFileName($SourcePath) if ($Copy) { $recordedSourcePath = [System.IO.Path]::Combine('sources', $sourceLeaf) } $document = [System.Collections.Specialized.OrderedDictionary]::new() $document['schemaVersion'] = 1 $document['id'] = $Id $document['name'] = $displayName if (-not [string]::IsNullOrWhiteSpace($Description)) { $document['description'] = $Description } $document['type'] = $type if (-not [string]::IsNullOrWhiteSpace($architecture)) { $document['architecture'] = $architecture } $document['sourcePath'] = $recordedSourcePath $document['importedUtc'] = $Clock.GetUtcNow().ToString('o') $document['defaultIndex'] = [int] $image[0].Index $imageDocument = New-Object -TypeName System.Collections.ArrayList foreach ($current in $image) { $entry = [System.Collections.Specialized.OrderedDictionary]::new() $entry['index'] = [int] $current.Index $entry['name'] = [string] $current.Name if (-not [string]::IsNullOrWhiteSpace([string] $current.Description)) { $entry['description'] = [string] $current.Description } if (-not [string]::IsNullOrWhiteSpace([string] $current.Edition)) { $entry['edition'] = [string] $current.Edition } if ([long] $current.SizeBytes -gt 0) { $entry['sizeBytes'] = [long] $current.SizeBytes } if (-not [string]::IsNullOrWhiteSpace([string] $current.Version)) { $entry['version'] = [string] $current.Version } [void] $imageDocument.Add($entry) } $document['images'] = [object[]] @($imageDocument) # The writer is held to the validator, here, before anything is written. Assert-HDTOperatingSystemDocument -Document $document -Path $catalogPath $text = ConvertTo-HDTYaml -Document $document -Path $catalogPath if (-not $PSCmdlet.ShouldProcess($catalogPath, ("Import operating system '{0}'" -f $Id))) { return $null } $FileSystem.CreateDirectory($osFolder) if ($Copy) { Copy-HDTContentTree -Source $sourceTree -Destination ([System.IO.Path]::Combine($osFolder, 'sources')) -FileSystem $FileSystem | Out-Null } $FileSystem.WriteAllText($catalogPath, $text) return (ConvertTo-HDTOperatingSystemCatalog -Document $document -OsFolder $osFolder -CatalogPath $catalogPath) } |