diff --git a/FFUDevelopment/BuildFFUUnattend/Set-FFUDataPartitionDriveLetters.ps1 b/FFUDevelopment/BuildFFUUnattend/Set-FFUDataPartitionDriveLetters.ps1 index 80b278c..3a4a774 100644 --- a/FFUDevelopment/BuildFFUUnattend/Set-FFUDataPartitionDriveLetters.ps1 +++ b/FFUDevelopment/BuildFFUUnattend/Set-FFUDataPartitionDriveLetters.ps1 @@ -99,6 +99,61 @@ function Resolve-FFUDataPartition { } } +function Get-FFUDataPartitionAssignmentMode { + param( + [Parameter(Mandatory = $true)] + [object]$ManifestEntry + ) + + $assignmentMode = 'Configured' + if ($ManifestEntry.PSObject.Properties.Name -contains 'AssignmentMode') { + $assignmentMode = ([string]$ManifestEntry.AssignmentMode).Trim() + } + if ($assignmentMode -notin @('Configured', 'Automatic')) { + throw "Partition '$($ManifestEntry.Name)' uses unsupported assignment mode '$assignmentMode'." + } + + return $assignmentMode +} + +function Get-FFUDeploymentMediaDiskNumbers { + $deploymentMediaDiskNumbers = [System.Collections.Generic.HashSet[int]]::new() + $deploymentVolumes = @(Get-Volume -ErrorAction SilentlyContinue | Where-Object { ([string]$_.FileSystemLabel).Trim() -ieq 'Deploy' }) + foreach ($deploymentVolume in $deploymentVolumes) { + $driveLetter = ([string]$deploymentVolume.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() + if ($driveLetter -notmatch '^[A-Z]$') { + continue + } + + $deploymentPartitions = @(Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue) + foreach ($deploymentPartition in $deploymentPartitions) { + $deploymentDisk = Get-Disk -Number $deploymentPartition.DiskNumber -ErrorAction SilentlyContinue + if ($null -ne $deploymentDisk -and (([string]$deploymentDisk.BusType -ieq 'USB') -or ([string]$deploymentVolume.DriveType -ieq 'Removable'))) { + $null = $deploymentMediaDiskNumbers.Add([int]$deploymentPartition.DiskNumber) + } + } + } + + return @($deploymentMediaDiskNumbers | Sort-Object) +} + +function Get-FFUNextAvailableDriveLetter { + param( + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [System.Collections.Generic.HashSet[string]]$ReservedDriveLetters + ) + + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + $candidateDriveLetter = [string][char]$driveLetterCode + if ($ReservedDriveLetters.Add($candidateDriveLetter)) { + return $candidateDriveLetter + } + } + + throw 'No drive letter from D through Z is available.' +} + function Set-FFUDataPartitionDriveLetters { param( [Parameter(Mandatory = $true)] @@ -121,37 +176,175 @@ function Set-FFUDataPartitionDriveLetters { Sort-Object -Property PartitionNumber) $resolvedEntries = [System.Collections.Generic.List[pscustomobject]]::new() + $targetPartitionKeys = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($manifestEntry in @($Manifest.Partitions)) { $requestedDriveLetter = ([string]$manifestEntry.RequestedDriveLetter).Trim().TrimEnd(':').ToUpperInvariant() if ($requestedDriveLetter -notmatch '^[D-Z]$') { throw "Partition '$($manifestEntry.Name)' requests invalid drive letter '$requestedDriveLetter'." } - $manifestEntry.RequestedDriveLetter = $requestedDriveLetter - $resolvedEntries.Add((Resolve-FFUDataPartition -ManifestEntry $manifestEntry -DataPartitions $dataPartitions)) + + $resolvedPartition = Resolve-FFUDataPartition -ManifestEntry $manifestEntry -DataPartitions $dataPartitions + $assignmentMode = Get-FFUDataPartitionAssignmentMode -ManifestEntry $manifestEntry + $targetPartitionKey = "$($resolvedPartition.Partition.DiskNumber):$($resolvedPartition.Partition.PartitionNumber)" + if (-not $targetPartitionKeys.Add($targetPartitionKey)) { + throw "Partition '$($manifestEntry.Name)' resolves to a data partition already targeted by another manifest entry." + } + $resolvedEntries.Add([pscustomobject]@{ + ManifestEntry = $resolvedPartition.ManifestEntry + Partition = $resolvedPartition.Partition + Volume = $resolvedPartition.Volume + AssignmentMode = $assignmentMode + RequestedDriveLetter = $requestedDriveLetter + }) + } + + $deploymentMediaDiskNumbers = @(Get-FFUDeploymentMediaDiskNumbers) + $deploymentMediaDiskNumberSet = [System.Collections.Generic.HashSet[int]]::new() + foreach ($deploymentMediaDiskNumber in $deploymentMediaDiskNumbers) { + $null = $deploymentMediaDiskNumberSet.Add([int]$deploymentMediaDiskNumber) + } + + $reservedDriveLetters = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($resolvedEntry in $resolvedEntries) { + if ($resolvedEntry.AssignmentMode -eq 'Configured') { + $null = $reservedDriveLetters.Add([string]$resolvedEntry.RequestedDriveLetter) + } + } + + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + $driveLetter = [string][char]$driveLetterCode + $letterOwners = @(Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue) + $hasProtectedOwner = $false + foreach ($letterOwner in $letterOwners) { + $ownerKey = "$($letterOwner.DiskNumber):$($letterOwner.PartitionNumber)" + if (-not $targetPartitionKeys.Contains($ownerKey) -and -not $deploymentMediaDiskNumberSet.Contains([int]$letterOwner.DiskNumber)) { + $hasProtectedOwner = $true + break + } + } + + if ($hasProtectedOwner -or ($letterOwners.Count -eq 0 -and $null -ne (Get-PSDrive -Name $driveLetter -PSProvider FileSystem -ErrorAction SilentlyContinue))) { + $null = $reservedDriveLetters.Add($driveLetter) + } } foreach ($resolvedEntry in $resolvedEntries) { - $requestedDriveLetter = [string]$resolvedEntry.ManifestEntry.RequestedDriveLetter + if ($resolvedEntry.AssignmentMode -ne 'Automatic') { + continue + } + + $resolvedEntry.RequestedDriveLetter = Get-FFUNextAvailableDriveLetter -ReservedDriveLetters $reservedDriveLetters + Write-FFUDataPartitionDriveLetterLog "Selected next available drive $($resolvedEntry.RequestedDriveLetter): for '$($resolvedEntry.ManifestEntry.Name)'." + } + + $requestedDriveLetters = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($resolvedEntry in $resolvedEntries) { + if (-not $requestedDriveLetters.Add([string]$resolvedEntry.RequestedDriveLetter)) { + throw "Drive letter $($resolvedEntry.RequestedDriveLetter): is requested by more than one data partition." + } + } + + $deploymentMediaDisksToRelocate = [System.Collections.Generic.HashSet[int]]::new() + foreach ($resolvedEntry in $resolvedEntries) { + $requestedDriveLetter = [string]$resolvedEntry.RequestedDriveLetter $currentDriveLetter = ([string]$resolvedEntry.Partition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() if ($currentDriveLetter -eq $requestedDriveLetter) { continue } - $requestedVolumes = @(Get-Volume -DriveLetter $requestedDriveLetter -ErrorAction SilentlyContinue) - if ($requestedVolumes.Count -gt 0) { - $requestedOwner = @(Get-Partition -DriveLetter $requestedDriveLetter -ErrorAction SilentlyContinue | Select-Object -First 1) - $ownerDescription = if ($requestedOwner.Count -gt 0) { - "disk $($requestedOwner[0].DiskNumber), partition $($requestedOwner[0].PartitionNumber)" + $requestedOwners = @(Get-Partition -DriveLetter $requestedDriveLetter -ErrorAction SilentlyContinue) + foreach ($requestedOwner in $requestedOwners) { + $ownerKey = "$($requestedOwner.DiskNumber):$($requestedOwner.PartitionNumber)" + if ($targetPartitionKeys.Contains($ownerKey)) { + continue + } + if ($deploymentMediaDiskNumberSet.Contains([int]$requestedOwner.DiskNumber)) { + $null = $deploymentMediaDisksToRelocate.Add([int]$requestedOwner.DiskNumber) + continue + } + + $requestedVolume = @($requestedOwner | Get-Volume -ErrorAction SilentlyContinue | Select-Object -First 1) + $ownerDescription = if ($requestedVolume.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace([string]$requestedVolume[0].FileSystemLabel)) { + "volume '$($requestedVolume[0].FileSystemLabel)' on disk $($requestedOwner.DiskNumber), partition $($requestedOwner.PartitionNumber)" } else { - "volume '$($requestedVolumes[0].FileSystemLabel)'" + "disk $($requestedOwner.DiskNumber), partition $($requestedOwner.PartitionNumber)" } throw "Cannot assign drive ${requestedDriveLetter}: to partition '$($resolvedEntry.ManifestEntry.Name)' because the letter is owned by $ownerDescription." } + + if ($requestedOwners.Count -eq 0) { + $existingFileSystemDrive = Get-PSDrive -Name $requestedDriveLetter -PSProvider FileSystem -ErrorAction SilentlyContinue + if ($null -ne $existingFileSystemDrive) { + throw "Cannot assign drive ${requestedDriveLetter}: to partition '$($resolvedEntry.ManifestEntry.Name)' because it is mapped to '$($existingFileSystemDrive.Root)'." + } + } + } + + $deploymentMediaPartitionsToRelocate = [System.Collections.Generic.List[pscustomobject]]::new() + foreach ($deploymentMediaDiskNumber in @($deploymentMediaDisksToRelocate | Sort-Object)) { + $deploymentMediaPartitions = @(Get-Partition -DiskNumber $deploymentMediaDiskNumber -ErrorAction Stop | Sort-Object -Property PartitionNumber) + foreach ($deploymentMediaPartition in $deploymentMediaPartitions) { + $currentDriveLetter = ([string]$deploymentMediaPartition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() + if ($currentDriveLetter -notmatch '^[D-Z]$') { + continue + } + + $deploymentMediaVolume = @($deploymentMediaPartition | Get-Volume -ErrorAction SilentlyContinue | Select-Object -First 1) + $volumeLabel = if ($deploymentMediaVolume.Count -gt 0) { [string]$deploymentMediaVolume[0].FileSystemLabel } else { '' } + $deploymentMediaPartitionsToRelocate.Add([pscustomobject]@{ + Partition = $deploymentMediaPartition + DriveLetter = $currentDriveLetter + VolumeLabel = $volumeLabel + }) + } + } + + $finalReservedDriveLetters = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + $driveLetter = [string][char]$driveLetterCode + $letterOwners = @(Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue) + $hasRemainingOwner = $false + foreach ($letterOwner in $letterOwners) { + $ownerKey = "$($letterOwner.DiskNumber):$($letterOwner.PartitionNumber)" + if (-not $targetPartitionKeys.Contains($ownerKey) -and -not $deploymentMediaDisksToRelocate.Contains([int]$letterOwner.DiskNumber)) { + $hasRemainingOwner = $true + break + } + } + + if ($hasRemainingOwner -or ($letterOwners.Count -eq 0 -and $null -ne (Get-PSDrive -Name $driveLetter -PSProvider FileSystem -ErrorAction SilentlyContinue))) { + $null = $finalReservedDriveLetters.Add($driveLetter) + } + } + foreach ($resolvedEntry in $resolvedEntries) { + $null = $finalReservedDriveLetters.Add([string]$resolvedEntry.RequestedDriveLetter) + } + $availableMediaDriveLetterCount = 0 + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + if (-not $finalReservedDriveLetters.Contains([string][char]$driveLetterCode)) { + $availableMediaDriveLetterCount++ + } + } + if ($availableMediaDriveLetterCount -lt $deploymentMediaPartitionsToRelocate.Count) { + throw 'There are not enough available drive letters to relocate the FFU deployment media.' + } + + # Remove affected access paths first so internal and deployment-media letters can be reordered safely. + foreach ($resolvedEntry in $resolvedEntries) { + $currentDriveLetter = ([string]$resolvedEntry.Partition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() + if ($currentDriveLetter -match '^[D-Z]$' -and $currentDriveLetter -ne [string]$resolvedEntry.RequestedDriveLetter) { + Write-FFUDataPartitionDriveLetterLog "Removing drive ${currentDriveLetter}: from '$($resolvedEntry.ManifestEntry.Name)' before reassignment." + Remove-PartitionAccessPath -DiskNumber $resolvedEntry.Partition.DiskNumber -PartitionNumber $resolvedEntry.Partition.PartitionNumber -AccessPath "${currentDriveLetter}:\" -ErrorAction Stop + } + } + foreach ($deploymentMediaEntry in $deploymentMediaPartitionsToRelocate) { + Write-FFUDataPartitionDriveLetterLog "Removing drive $($deploymentMediaEntry.DriveLetter): from FFU deployment media '$($deploymentMediaEntry.VolumeLabel)' before reassignment." + Remove-PartitionAccessPath -DiskNumber $deploymentMediaEntry.Partition.DiskNumber -PartitionNumber $deploymentMediaEntry.Partition.PartitionNumber -AccessPath "$($deploymentMediaEntry.DriveLetter):\" -ErrorAction Stop } foreach ($resolvedEntry in $resolvedEntries) { - $requestedDriveLetter = [string]$resolvedEntry.ManifestEntry.RequestedDriveLetter + $requestedDriveLetter = [string]$resolvedEntry.RequestedDriveLetter $currentDriveLetter = ([string]$resolvedEntry.Partition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() if ($currentDriveLetter -ne $requestedDriveLetter) { $currentDriveLetterText = if ([string]::IsNullOrWhiteSpace($currentDriveLetter)) { 'no drive letter' } else { "drive ${currentDriveLetter}:" } @@ -165,6 +358,24 @@ function Set-FFUDataPartitionDriveLetters { } Write-FFUDataPartitionDriveLetterLog "Verified '$($resolvedEntry.ManifestEntry.Name)' at drive ${requestedDriveLetter}:." } + + $mediaReservedDriveLetters = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + $driveLetter = [string][char]$driveLetterCode + $letterOwners = @(Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue) + if ($letterOwners.Count -gt 0 -or $null -ne (Get-PSDrive -Name $driveLetter -PSProvider FileSystem -ErrorAction SilentlyContinue)) { + $null = $mediaReservedDriveLetters.Add($driveLetter) + } + } + foreach ($deploymentMediaEntry in $deploymentMediaPartitionsToRelocate) { + $newDriveLetter = Get-FFUNextAvailableDriveLetter -ReservedDriveLetters $mediaReservedDriveLetters + Write-FFUDataPartitionDriveLetterLog "Assigning drive ${newDriveLetter}: to FFU deployment media '$($deploymentMediaEntry.VolumeLabel)'." + Set-Partition -DiskNumber $deploymentMediaEntry.Partition.DiskNumber -PartitionNumber $deploymentMediaEntry.Partition.PartitionNumber -NewDriveLetter $newDriveLetter -ErrorAction Stop + $verifiedMediaPartition = Get-Partition -DiskNumber $deploymentMediaEntry.Partition.DiskNumber -PartitionNumber $deploymentMediaEntry.Partition.PartitionNumber -ErrorAction Stop + if (([string]$verifiedMediaPartition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() -ne $newDriveLetter) { + throw "Drive letter verification failed for FFU deployment media '$($deploymentMediaEntry.VolumeLabel)'." + } + } } function Remove-FFUDataPartitionDriveLetterArtifacts { diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index d239051..b438cbb 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -115,7 +115,7 @@ Optional fixed size of the Recovery partition in bytes. Leave as 0 to calculate When set to $false, skips creating the Windows Recovery partition. Default is $true. .PARAMETER AdditionalDataPartitions -Optional data partitions to create after the Recovery partition. Each item supports Name, Label, DriveLetter, SizeBytes or SizeGB, FillRemaining, and FileSystem. +Optional data partitions to create after the Recovery partition. Each item supports Name, Label, DriveLetter, SizeBytes or SizeGB, FillRemaining, FileSystem, and PersistDriveLetter. The configured letter is always used in the build VM. On physical devices, persisted partitions keep that letter and other data partitions receive the next available letter from D through Z. .PARAMETER DriversFolder Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers. @@ -3675,6 +3675,8 @@ function New-FFUDataPartitionDriveLetterManifest { [string]$WindowsArch, [Parameter(Mandatory = $true)] [string]$ManifestPath, + [ValidateSet('BuildVm', 'Deployment')] + [string]$ManifestPurpose = 'Deployment', [bool]$Optimize = $false, [int]$OptimizeFFUPartitionNumber = 0 ) @@ -3684,11 +3686,33 @@ function New-FFUDataPartitionDriveLetterManifest { $resizablePartitionNumber = Get-FFUOptimizePartitionNumber -Layout $Layout -RequestedPartitionNumber $OptimizeFFUPartitionNumber -AdditionalDataPartitions $AdditionalDataPartitions } + $reservedDeploymentDriveLetters = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + if ($ManifestPurpose -eq 'Deployment') { + foreach ($dataPartitionConfig in $AdditionalDataPartitions) { + if ([bool]$dataPartitionConfig.PersistDriveLetter) { + $null = $reservedDeploymentDriveLetters.Add([string]$dataPartitionConfig.DriveLetter) + } + } + } + $manifestPartitions = [System.Collections.Generic.List[pscustomobject]]::new() for ($dataPartitionIndex = 0; $dataPartitionIndex -lt $AdditionalDataPartitions.Count; $dataPartitionIndex++) { $dataPartitionConfig = $AdditionalDataPartitions[$dataPartitionIndex] - if (-not [bool]$dataPartitionConfig.PersistDriveLetter) { - continue + $assignmentMode = 'Configured' + $requestedDriveLetter = [string]$dataPartitionConfig.DriveLetter + if ($ManifestPurpose -eq 'Deployment' -and -not [bool]$dataPartitionConfig.PersistDriveLetter) { + $assignmentMode = 'Automatic' + $requestedDriveLetter = $null + foreach ($driveLetterCode in ([int][char]'D')..([int][char]'Z')) { + $candidateDriveLetter = [string][char]$driveLetterCode + if ($reservedDeploymentDriveLetters.Add($candidateDriveLetter)) { + $requestedDriveLetter = $candidateDriveLetter + break + } + } + if ([string]::IsNullOrWhiteSpace($requestedDriveLetter)) { + throw "No deployment drive letter is available for data partition '$($dataPartitionConfig.Name)'." + } } $resolvedDataPartition = $Layout.DataPartitions[$dataPartitionIndex] @@ -3699,7 +3723,9 @@ function New-FFUDataPartitionDriveLetterManifest { $manifestPartitions.Add([pscustomobject][ordered]@{ Name = [string]$dataPartitionConfig.Name - RequestedDriveLetter = [string]$dataPartitionConfig.DriveLetter + RequestedDriveLetter = $requestedDriveLetter + ConfiguredDriveLetter = [string]$dataPartitionConfig.DriveLetter + AssignmentMode = $assignmentMode DataOrdinal = $dataPartitionIndex + 1 PartitionNumber = [int]$resolvedDataPartition.Partition.PartitionNumber PartitionGuid = $partitionGuid @@ -3711,7 +3737,7 @@ function New-FFUDataPartitionDriveLetterManifest { } if ($manifestPartitions.Count -eq 0) { - throw 'Cannot create a data partition drive-letter manifest without opted-in partitions.' + throw 'Cannot create a data partition drive-letter manifest without data partitions.' } $manifestDirectory = Split-Path -Path $ManifestPath -Parent @@ -3737,6 +3763,8 @@ function Add-FFUDataPartitionDriveLetterArtifacts { [object[]]$AdditionalDataPartitions, [Parameter(Mandatory = $true)] [string]$WindowsArch, + [ValidateSet('BuildVm', 'Deployment')] + [string]$ManifestPurpose = 'Deployment', [bool]$Optimize = $false, [int]$OptimizeFFUPartitionNumber = 0, [Parameter(Mandatory = $true)] @@ -3753,7 +3781,7 @@ function Add-FFUDataPartitionDriveLetterArtifacts { $manifestPath = Join-Path -Path $runtimeDirectory -ChildPath 'Manifest.json' New-Item -Path $runtimeDirectory -ItemType Directory -Force | Out-Null Copy-Item -LiteralPath $runtimeSourcePath -Destination $runtimeScriptPath -Force - $null = New-FFUDataPartitionDriveLetterManifest -Layout $Layout -AdditionalDataPartitions $AdditionalDataPartitions -WindowsArch $WindowsArch -ManifestPath $manifestPath -Optimize $Optimize -OptimizeFFUPartitionNumber $OptimizeFFUPartitionNumber + $null = New-FFUDataPartitionDriveLetterManifest -Layout $Layout -AdditionalDataPartitions $AdditionalDataPartitions -WindowsArch $WindowsArch -ManifestPath $manifestPath -ManifestPurpose $ManifestPurpose -Optimize $Optimize -OptimizeFFUPartitionNumber $OptimizeFFUPartitionNumber Remove-Item -LiteralPath (Join-Path $runtimeDirectory 'Audit.success'), (Join-Path $runtimeDirectory 'Audit.failure'), (Join-Path $runtimeDirectory 'Specialize.failure') -Force -ErrorAction SilentlyContinue if (-not (Test-Path -LiteralPath $runtimeScriptPath -PathType Leaf) -or -not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) { @@ -6718,7 +6746,8 @@ Set-Progress -Percentage 2 -Message "Validating parameters..." #Set build partition drive letters and validate they are available for use; this is required before any build steps that require drive access to ensure the expected drive letters are reserved and to fail fast if there are conflicts. try { $normalizedAdditionalDataPartitions = @(ConvertTo-NormalizedDataPartitions -DataPartitions $AdditionalDataPartitions) - $persistDataPartitionDriveLetters = @($normalizedAdditionalDataPartitions | Where-Object { $_.PersistDriveLetter }).Count -gt 0 + $configureDeployedDataPartitionDriveLetters = $normalizedAdditionalDataPartitions.Count -gt 0 + $enforceBuildVmDataPartitionDriveLetters = $InstallApps -and $configureDeployedDataPartitionDriveLetters $fillRemainingPartitionCount = if ($OSPartitionSize -le 0) { 1 } else { 0 } $fillRemainingPartitionCount += @($normalizedAdditionalDataPartitions | Where-Object { $_.FillRemaining }).Count if ($fillRemainingPartitionCount -gt 1) { @@ -8771,9 +8800,9 @@ if ($InstallApps) { $orchestrationBootstrapTargetFolder = "$($osPartitionDriveLetter):\Windows\Setup\Scripts" New-Item -Path $orchestrationBootstrapTargetFolder -ItemType Directory -Force | Out-Null Copy-Item -Path $orchestrationBootstrapSourcePath -Destination (Join-Path -Path $orchestrationBootstrapTargetFolder -ChildPath 'Start-FFUOrchestration.ps1') -Force | Out-Null - if ($persistDataPartitionDriveLetters) { + if ($enforceBuildVmDataPartitionDriveLetters) { $windowsPartitionRoot = "$($osPartitionDriveLetter):\" - $null = Add-FFUDataPartitionDriveLetterArtifacts -WindowsPartitionRoot $windowsPartitionRoot -Layout $partitionLayout -AdditionalDataPartitions $normalizedAdditionalDataPartitions -WindowsArch $WindowsArch -Optimize $Optimize -OptimizeFFUPartitionNumber $OptimizeFFUPartitionNumber -FFUDevelopmentPath $FFUDevelopmentPath + $null = Add-FFUDataPartitionDriveLetterArtifacts -WindowsPartitionRoot $windowsPartitionRoot -Layout $partitionLayout -AdditionalDataPartitions $normalizedAdditionalDataPartitions -WindowsArch $WindowsArch -ManifestPurpose BuildVm -Optimize $Optimize -OptimizeFFUPartitionNumber $OptimizeFFUPartitionNumber -FFUDevelopmentPath $FFUDevelopmentPath } if ($WindowsArch -eq 'x64') { Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_x64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null @@ -8785,14 +8814,14 @@ if ($InstallApps) { # Always dismount so downstream VM creation logic has a clean starting point Dismount-ScratchVhdx -VhdxPath $VHDXPath } -elseif ($persistDataPartitionDriveLetters) { +elseif ($configureDeployedDataPartitionDriveLetters) { $vhdMeta = Get-VHD -Path $VHDXPath if ($vhdMeta.Attached) { WriteLog 'VHDX already mounted; reusing existing mount for data partition drive-letter staging' $disk = Get-Disk -Number $vhdMeta.DiskNumber } else { - WriteLog 'Mounting VHDX to stage data partition drive-letter persistence' + WriteLog 'Mounting VHDX to stage data partition drive-letter assignment' $disk = Mount-VHD -Path $VHDXPath -Passthru | Get-Disk } $partitionLayout = Resolve-VhdxPartitionLayout -Disk $disk -CreateRecoveryPartition $CreateRecoveryPartition -AdditionalDataPartitions $normalizedAdditionalDataPartitions @@ -8862,7 +8891,7 @@ try { WriteLog 'Waiting for VM to shutdown' } while ($FFUVM.State -ne 'Off') WriteLog 'VM Shutdown' - if ($persistDataPartitionDriveLetters) { + if ($enforceBuildVmDataPartitionDriveLetters) { $vhdMeta = Get-VHD -Path $VHDXPath if ($vhdMeta.Attached) { WriteLog 'VHDX already mounted; reusing existing mount to validate data partition drive-letter audit results' @@ -8881,6 +8910,7 @@ try { $runtimeDirectory = Join-Path -Path $windowsPartitionRoot -ChildPath 'Windows\Setup\Scripts\FFUDL' $runtimeScriptPath = Join-Path -Path $runtimeDirectory -ChildPath 'Apply.ps1' $manifestPath = Join-Path -Path $runtimeDirectory -ChildPath 'Manifest.json' + $null = Add-FFUDataPartitionDriveLetterArtifacts -WindowsPartitionRoot $windowsPartitionRoot -Layout $partitionLayout -AdditionalDataPartitions $normalizedAdditionalDataPartitions -WindowsArch $WindowsArch -Optimize $Optimize -OptimizeFFUPartitionNumber $OptimizeFFUPartitionNumber -FFUDevelopmentPath $FFUDevelopmentPath if (-not (Test-Path -LiteralPath $runtimeScriptPath -PathType Leaf) -or -not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) { throw "Data partition drive-letter runtime artifacts are missing from $runtimeDirectory after audit validation." } @@ -8892,12 +8922,12 @@ try { $stagedInjectedUnattendPath = Join-Path -Path $AppsPath -ChildPath 'Unattend\Unattend.xml' if (Test-Path -LiteralPath $stagedInjectedUnattendPath -PathType Leaf) { Copy-Item -LiteralPath $stagedInjectedUnattendPath -Destination $deploymentUnattendPath -Force - WriteLog "Restored staged deployment unattend to $deploymentUnattendPath before adding data partition drive-letter persistence." + WriteLog "Restored staged deployment unattend to $deploymentUnattendPath before adding data partition drive-letter assignment." } else { $unattendSource = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch -UnattendX64FilePath $UnattendX64FilePath -UnattendArm64FilePath $UnattendArm64FilePath Save-StagedUnattendFile -SourcePath $unattendSource -DestinationPath $deploymentUnattendPath -DeviceNamingMode $DeviceNamingMode -DeviceNameTemplate $normalizedDeviceNameTemplate -WindowsArch $WindowsArch - WriteLog "Restaged deployment unattend to $deploymentUnattendPath before adding data partition drive-letter persistence." + WriteLog "Restaged deployment unattend to $deploymentUnattendPath before adding data partition drive-letter assignment." } } Add-FFUDataPartitionDriveLetterCommandToUnattend -UnattendPath $deploymentUnattendPath -ProcessorArchitecture $WindowsArch diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml index 83f0c1a..7f562f8 100644 --- a/FFUDevelopment/BuildFFUVM_UI.xaml +++ b/FFUDevelopment/BuildFFUVM_UI.xaml @@ -381,7 +381,7 @@ - + @@ -408,7 +408,7 @@ - +