Public/Get-HDTDiskLayout.ps1
|
function Get-HDTDiskLayout { <# .SYNOPSIS Returns a named disk layout, or every layout HDT knows. .DESCRIPTION The two named layouts, as DATA rather than as code: uefi-standard GPT: EFI System 260 MB FAT32, Windows, WinRE recovery 1 GB at the end bios-standard MBR: System Reserved 500 MB active NTFS, Windows remainder NEITHER DECLARES A MICROSOFT RESERVED PARTITION, AND THAT IS THE POINT. Initialize-Disk -PartitionStyle GPT creates its own 16 MB MSR. PSD's PSDPartition.ps1 initialises GPT and then creates an MSR by hand a few lines later, which is how the spike ended up with a DUPLICATE 16 MB partition. HDT records the 16 MB as an ALLOWANCE (ReservedSizeByte), subtracts it from the space Windows can have, and never creates a partition for it. THE ESP IS CREATED AS BASIC DATA AND RETYPED AFTERWARDS. A partition created directly as an EFI System partition cannot readily be given a drive letter to format through, so the layout carries both CreateGptType (basic data) and GptType (the ESP type), and DiskPartition creates, letters, formats, then retypes. THIS IS THE FIELD RECIPE AND IT IS UNVERIFIED BY CODE - 04-04's integration task is where it first runs against a real disk. If Set-Partition -GptType after Format-Volume does not behave, the finding goes into SPIKES.md and this layout changes, not the test. LAYOUTS ARE MEANT TO LIVE IN workspace.yaml. That document does not exist yet - M4 introduces it for the boot image - so the built-ins live here and -Definition is the hook a workspace.yaml diskLayouts: block will arrive through. A supplied definition overrides a built-in of the same name and extends the set otherwise, so nothing has to be rewritten when M4 lands. .PARAMETER Name The layout to return. Matched case-insensitively. Omitted, every layout is returned in declaration order. .PARAMETER Definition Name -> layout definition, overriding and extending the built-ins. A definition is a mapping with PartitionStyle (GPT or MBR) and Partition, a list of mappings carrying Role (System, Windows or Recovery), SizeByte, UseMaximumSize, FileSystem, Label, DriveLetter, GptType, CreateGptType and IsActive. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject - Name, PartitionStyle, ReservedSizeByte, AlignmentSizeByte and Partition. .EXAMPLE Get-HDTDiskLayout -Name uefi-standard The layout a UEFI machine gets unless the sequence pins another. .EXAMPLE Get-HDTDiskLayout | Format-Table Name, PartitionStyle Every layout this engine knows. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Position = 0)] [string] $Name, [Parameter()] [AllowNull()] [System.Collections.IDictionary] $Definition ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # The GPT type GUIDs, written once. Basic data is what a partition is created # as; the other two are what it is retyped to. $basicDataType = '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' $espType = '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' $recoveryType = '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}' $allowedRole = @('System', 'Windows', 'Recovery') $allowedStyle = @('GPT', 'MBR') # -- the built-ins, one ordered literal ------------------------------------ $builtIn = [System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::OrdinalIgnoreCase) $builtIn['uefi-standard'] = @{ PartitionStyle = 'GPT' # SPIKES S6: Initialize-Disk creates this. It is subtracted, never created. ReservedSizeByte = 16777216 AlignmentSizeByte = 1048576 Partition = @( # DESIGN 9.1: "EFI System 260 MB FAT32". @{ Role = 'System'; SizeByte = 272629760; UseMaximumSize = $false; FileSystem = 'FAT32' Label = 'System'; DriveLetter = 'S'; GptType = $espType; CreateGptType = $basicDataType; IsActive = $false }, # DESIGN 9.1: "Windows (remainder minus recovery)". @{ Role = 'Windows'; SizeByte = 0; UseMaximumSize = $false; FileSystem = 'NTFS' Label = 'Windows'; DriveLetter = 'W'; GptType = ''; CreateGptType = ''; IsActive = $false }, # DESIGN 9.1: "WinRE recovery 1 GB at the end". It carries # UseMaximumSize so the alignment slack lands here rather than being # left unallocated at the end of the disk. @{ Role = 'Recovery'; SizeByte = 1073741824; UseMaximumSize = $true; FileSystem = 'NTFS' Label = 'Windows RE tools'; DriveLetter = 'R'; GptType = $recoveryType; CreateGptType = ''; IsActive = $false } ) } $builtIn['bios-standard'] = @{ PartitionStyle = 'MBR' # Initialize-Disk -PartitionStyle MBR creates nothing of its own. ReservedSizeByte = 0 AlignmentSizeByte = 1048576 Partition = @( # DESIGN 9.1: "System Reserved 500 MB active NTFS". @{ Role = 'System'; SizeByte = 524288000; UseMaximumSize = $false; FileSystem = 'NTFS' Label = 'System Reserved'; DriveLetter = 'S'; GptType = ''; CreateGptType = ''; IsActive = $true }, # DESIGN 9.1: "Windows remainder". No recovery partition on BIOS. @{ Role = 'Windows'; SizeByte = 0; UseMaximumSize = $true; FileSystem = 'NTFS' Label = 'Windows'; DriveLetter = 'W'; GptType = ''; CreateGptType = ''; IsActive = $false } ) } if ($null -ne $Definition) { foreach ($key in @($Definition.Keys)) { $builtIn[[string] $key] = $Definition[$key] } } # -- materialise and validate --------------------------------------------- $layout = New-Object -TypeName System.Collections.ArrayList foreach ($key in @($builtIn.Keys)) { $layoutName = [string] $key $source = $builtIn[$key] if (-not ($source -is [System.Collections.IDictionary])) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}' must be a mapping with PartitionStyle and Partition keys." -f $layoutName))) } $style = '' if ($source.Contains('PartitionStyle')) { $style = [string] $source['PartitionStyle'] } if ($allowedStyle -notcontains $style) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}': '{1}' is not a partition style HDT can create. The styles are {2}." -f $layoutName, $style, ($allowedStyle -join ', ')))) } $reserved = 0 if ($source.Contains('ReservedSizeByte')) { $reserved = [long] $source['ReservedSizeByte'] } $alignment = 1048576 if ($source.Contains('AlignmentSizeByte')) { $alignment = [long] $source['AlignmentSizeByte'] } $partition = New-Object -TypeName System.Collections.ArrayList $sourcePartition = @() if ($source.Contains('Partition')) { $sourcePartition = @($source['Partition']) } if ($sourcePartition.Count -eq 0) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}' declares no partition. A layout that creates nothing is not a layout." -f $layoutName))) } foreach ($row in $sourcePartition) { if (-not ($row -is [System.Collections.IDictionary])) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}': every partition must be a mapping with a Role and a FileSystem." -f $layoutName))) } $role = '' if ($row.Contains('Role')) { $role = [string] $row['Role'] } if ($allowedRole -notcontains $role) { # 'Reserved' lands here on purpose: SPIKES S6's duplicate MSR # started as a partition somebody declared. $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}': '{1}' is not a partition role HDT creates. The roles are {2}; the Microsoft Reserved partition is created by initialisation and must not be declared." -f $layoutName, $role, ($allowedRole -join ', ')))) } $fileSystem = '' if ($row.Contains('FileSystem')) { $fileSystem = [string] $row['FileSystem'] } if ([string]::IsNullOrWhiteSpace($fileSystem)) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}': the {1} partition declares no file system. HDT formats every partition it creates." -f $layoutName, $role))) } $sizeByte = 0 if ($row.Contains('SizeByte')) { $sizeByte = [long] $row['SizeByte'] } if ($sizeByte -lt 0) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}': the {1} partition declares a negative size ({2} bytes)." -f $layoutName, $role, $sizeByte))) } [void] $partition.Add([pscustomobject] @{ Role = $role SizeByte = $sizeByte UseMaximumSize = [bool] $(if ($row.Contains('UseMaximumSize')) { $row['UseMaximumSize'] } else { $false }) FileSystem = $fileSystem Label = [string] $(if ($row.Contains('Label')) { $row['Label'] } else { '' }) DriveLetter = [string] $(if ($row.Contains('DriveLetter')) { $row['DriveLetter'] } else { '' }) GptType = [string] $(if ($row.Contains('GptType')) { $row['GptType'] } else { '' }) CreateGptType = [string] $(if ($row.Contains('CreateGptType')) { $row['CreateGptType'] } else { '' }) IsActive = [bool] $(if ($row.Contains('IsActive')) { $row['IsActive'] } else { $false }) }) } if (@($partition | Where-Object { $_.Role -eq 'Windows' }).Count -ne 1) { $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $layoutName ` -Message ("disk layout '{0}' must declare exactly one Windows partition. A layout that does not say where Windows goes cannot be applied to." -f $layoutName))) } [void] $layout.Add([pscustomobject] @{ Name = $layoutName PartitionStyle = $style ReservedSizeByte = $reserved AlignmentSizeByte = $alignment Partition = [object[]] @($partition) }) } if ([string]::IsNullOrWhiteSpace($Name)) { return [object[]] @($layout) } $match = @($layout | Where-Object { $_.Name -eq $Name }) if ($match.Count -eq 1) { return $match[0] } $PSCmdlet.ThrowTerminatingError((New-HDTErrorRecord -TargetObject $Name ` -Message ("'{0}' is not a disk layout this engine knows. The layouts are {1}." -f $Name, (@($layout | ForEach-Object { $_.Name }) -join ', ')))) } |