private/Initialize-OSDeployCorePaths.ps1
|
#Requires -PSEdition Core function Initialize-OSDeployCorePaths { <# .SYNOPSIS Initializes OSDeployCore cache and Boot-Assets paths .DESCRIPTION Ensures the standard OSDeployCore boot, cache, download, hash, software, Windows image, shared build-content, architecture, and build-profile directories exist. Existing root-level software cache content is moved to cache\software. Content is merged recursively without replacing destination conflicts. Conflicting legacy items remain in the root-level software directory with a warning. Module-managed cache and root-level winpedrivers-amd64 and winpedrivers-arm64 directories are moved to Boot-Assets. Legacy cache and Boot-Assets build-winpedrivers architecture directories are migrated to their matching Boot-Assets destinations. Root-level managed content replaces matching Boot-Assets content during migration. Profile-local build-winpedrivers content moves to the directory matching the profile architecture. The function migrates legacy Boot-Assets folders and build profiles into flat osdeployboot-profiles\<Name>-<Architecture> directories. Canonical destination conflicts are preserved in their source location with a warning. The reserved recent-amd64.json and recent-arm64.json snapshots remain at the profile root. The function creates profile-local content directories, synchronizes each migrated profile's Name property, removes retired wallpaper and WinPE app properties, renames the legacy media property, and rewrites persisted paths that refer to legacy locations. Empty retired WinPE app-script and architecture directories are removed after migration. .EXAMPLE PS> Initialize-OSDeployCorePaths Creates missing OSDeployCore paths and migrates supported legacy Boot-Assets content. .INPUTS None. This function does not accept pipeline input. .OUTPUTS None. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-08-28 This function reads $Script:OSDeployCorePath and $Script:OSDeployBootAssetsPath and modifies content below those locations without WhatIf or Confirm support. The paths must be writable. Change Summary: - Moves the root-level software cache to cache\software without replacing conflicts. - Consolidates shared WinPE drivers under Boot-Assets and migrates root-level content with replacement. - Migrates build profiles to osdeployboot-profiles\<Name>-<Architecture>\osdeployboot.json. - Synchronizes the profile Name property with its canonical folder name. - Removes WinPECustomWallpaper, WinPEApp, and WinPEAppScript from build profile copies. - Removes empty retired WinPE app-script directories without deleting their content. - Creates profile-local content directories for every saved build profile. #> [CmdletBinding()] param () $functionName = $MyInvocation.MyCommand.Name $legacyRepositoryPath = Join-Path $Script:OSDeployCorePath 'repository' $legacyOSDRepoPath = Join-Path $Script:OSDeployCorePath 'OSDRepo' Initialize-OSDeployCoreBootAssets -LegacyPath $legacyRepositoryPath, $legacyOSDRepoPath -BootAssetsPath $Script:OSDeployBootAssetsPath -Create Initialize-OSDeployBootAssetPath $legacyBuildProfilesPath = Join-Path $script:OSDeployBootAssetsPath 'build-profiles' $buildProfilesPath = Join-Path $script:OSDeployBootAssetsPath 'osdeployboot-profiles' $legacySoftwarePath = Join-Path $script:OSDeployCorePath 'software' $legacyBootAssetsWinPEDriversPath = Join-Path $script:OSDeployBootAssetsPath 'build-winpedrivers' $legacyCacheWinPEDriversPath = Join-Path $script:OSDeployCoreCachePath 'build-winpedrivers' $cachedWinPEDriversPaths = [ordered]@{ 'amd64' = Join-Path $script:OSDeployCoreCachePath 'winpedrivers-amd64' 'arm64' = Join-Path $script:OSDeployCoreCachePath 'winpedrivers-arm64' } $managedWinPEDriversPaths = [ordered]@{ 'amd64' = Join-Path $script:OSDeployCorePath 'winpedrivers-amd64' 'arm64' = Join-Path $script:OSDeployCorePath 'winpedrivers-arm64' } $bootAssetsWinPEDriversPaths = [ordered]@{ 'amd64' = Join-Path $script:OSDeployBootAssetsPath 'winpedrivers-amd64' 'arm64' = Join-Path $script:OSDeployBootAssetsPath 'winpedrivers-arm64' } $buildContentFolderMap = [ordered]@{ 'media-script' = 'boot-mediascript' 'build-mediascript' = 'boot-mediascript' 'winpe-drivers' = 'build-winpedrivers' 'winpe-script' = 'boot-winpescript' 'build-winpescript' = 'boot-winpescript' 'winpe-wallpaper' = 'boot-wallpaper' 'build-winpewallpaper' = 'boot-wallpaper' } $profilePathMoves = [System.Collections.Generic.List[System.Object]]::new() $getCanonicalProfileName = { param ( [System.String]$ProfileName, [System.String]$Architecture ) $baseName = $ProfileName -replace '(?i)-(?:amd64|arm64)$', '' "$baseName-$($Architecture.ToLowerInvariant())" } # Merge legacy directories recursively while preserving any destination-side conflicts. $moveDirectoryContent = { param ( [System.String]$SourcePath, [System.String]$DestinationPath, [System.Boolean]$ArchiveProfileConflicts = $false ) if (-not (Test-Path -LiteralPath $SourcePath -PathType Container)) { return } if (-not (Test-Path -LiteralPath $DestinationPath -PathType Container)) { New-Item -ItemType Directory -Path $DestinationPath -Force | Out-Null } foreach ($item in @(Get-ChildItem -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue)) { $destinationItem = Join-Path $DestinationPath $item.Name if (Test-Path -LiteralPath $destinationItem) { # Two matching directories can be merged safely; conflicting files remain untouched. if ($item.PSIsContainer -and (Test-Path -LiteralPath $destinationItem -PathType Container)) { & $moveDirectoryContent $item.FullName $destinationItem $ArchiveProfileConflicts continue } if ($ArchiveProfileConflicts -and -not $item.PSIsContainer -and $item.Name -in @('buildprofile.json', 'osdeployboot.json')) { $archivePath = Join-Path $DestinationPath 'profile.legacy.json' $suffix = 1 while (Test-Path -LiteralPath $archivePath) { $archivePath = Join-Path $DestinationPath ('profile.legacy-{0:d3}.json' -f $suffix) $suffix++ } try { Move-Item -LiteralPath $item.FullName -Destination $archivePath -ErrorAction Stop Write-Warning "[$functionName] Profile already exists at '$destinationItem'. Archived legacy profile to '$archivePath'." } catch { Write-Warning "[$functionName] Could not archive legacy profile '$($item.FullName)' to '$archivePath': $($_.Exception.Message)" } continue } Write-Warning "[$functionName] Could not migrate '$($item.FullName)' because '$destinationItem' already exists." continue } try { Move-Item -LiteralPath $item.FullName -Destination $DestinationPath -ErrorAction Stop Write-Verbose "[$($MyInvocation.MyCommand.Name)] [$functionName] Migrated '$($item.FullName)' to '$DestinationPath'" } catch { Write-Warning "[$functionName] Could not migrate '$($item.FullName)' to '$DestinationPath': $($_.Exception.Message)" } } # Remove the legacy directory only after all movable content has left it. if (-not (Get-ChildItem -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) { Remove-Item -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue } } # Move source content recursively and replace matching destination content. $moveDirectoryContentOverwrite = { param ( [System.String]$SourcePath, [System.String]$DestinationPath ) if (-not (Test-Path -LiteralPath $SourcePath -PathType Container)) { return } if (-not (Test-Path -LiteralPath $DestinationPath -PathType Container)) { New-Item -ItemType Directory -Path $DestinationPath -Force | Out-Null } foreach ($item in @(Get-ChildItem -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue)) { $destinationItem = Join-Path $DestinationPath $item.Name if ($item.PSIsContainer -and (Test-Path -LiteralPath $destinationItem -PathType Container)) { & $moveDirectoryContentOverwrite $item.FullName $destinationItem continue } try { if (Test-Path -LiteralPath $destinationItem) { Remove-Item -LiteralPath $destinationItem -Recurse -Force -ErrorAction Stop } Move-Item -LiteralPath $item.FullName -Destination $DestinationPath -ErrorAction Stop Write-Verbose "[$($MyInvocation.MyCommand.Name)] [$functionName] Migrated '$($item.FullName)' to '$DestinationPath'" } catch { Write-Warning "[$functionName] Could not replace '$destinationItem' with '$($item.FullName)': $($_.Exception.Message)" } } if (-not (Get-ChildItem -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) { Remove-Item -LiteralPath $SourcePath -Force -ErrorAction SilentlyContinue } } $moveWinPEDriverDirectory = { param ( [System.String]$SourcePath, [System.String]$DestinationPath ) if (-not (Test-Path -LiteralPath $SourcePath -PathType Container)) { return } if (Test-Path -LiteralPath $DestinationPath) { Write-Warning "[$functionName] Could not migrate WinPE drivers '$SourcePath' because '$DestinationPath' already exists. Both directories were preserved." return } try { Move-Item -LiteralPath $SourcePath -Destination $DestinationPath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated WinPE drivers '$SourcePath' to '$DestinationPath'" } catch { Write-Warning "[$functionName] Could not migrate WinPE drivers '$SourcePath' to '$DestinationPath': $($_.Exception.Message)" } } if (Test-Path -LiteralPath $legacyBuildProfilesPath -PathType Container) { if (-not (Test-Path -LiteralPath $buildProfilesPath -PathType Container)) { try { Move-Item -LiteralPath $legacyBuildProfilesPath -Destination $buildProfilesPath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated build profiles '$legacyBuildProfilesPath' to '$buildProfilesPath'" } catch { Write-Warning "[$functionName] Could not migrate '$legacyBuildProfilesPath' to '$buildProfilesPath': $($_.Exception.Message)" } } if (Test-Path -LiteralPath $legacyBuildProfilesPath -PathType Container) { & $moveDirectoryContent $legacyBuildProfilesPath $buildProfilesPath $true } } foreach ($legacyFolderName in $buildContentFolderMap.Keys) { $legacyContentPath = Join-Path $Script:OSDeployBootAssetsPath $legacyFolderName $contentPath = Join-Path $Script:OSDeployBootAssetsPath $buildContentFolderMap[$legacyFolderName] & $moveDirectoryContent $legacyContentPath $contentPath } if (-not (Test-Path -LiteralPath $script:OSDeployCoreCachePath -PathType Container)) { New-Item -ItemType Directory -Path $script:OSDeployCoreCachePath -Force | Out-Null } if ((Test-Path -LiteralPath $legacyBootAssetsWinPEDriversPath -PathType Container) -and -not (Test-Path -LiteralPath $legacyCacheWinPEDriversPath)) { try { Move-Item -LiteralPath $legacyBootAssetsWinPEDriversPath -Destination $legacyCacheWinPEDriversPath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated WinPE drivers '$legacyBootAssetsWinPEDriversPath' to '$legacyCacheWinPEDriversPath'" } catch { Write-Warning "[$functionName] Could not migrate '$legacyBootAssetsWinPEDriversPath' to '$legacyCacheWinPEDriversPath': $($_.Exception.Message)" } } foreach ($architecture in @('amd64', 'arm64')) { & $moveDirectoryContent (Join-Path $legacyCacheWinPEDriversPath $architecture) $managedWinPEDriversPaths[$architecture] & $moveDirectoryContent $cachedWinPEDriversPaths[$architecture] $managedWinPEDriversPaths[$architecture] & $moveWinPEDriverDirectory (Join-Path $legacyBootAssetsWinPEDriversPath $architecture) $bootAssetsWinPEDriversPaths[$architecture] & $moveDirectoryContentOverwrite $managedWinPEDriversPaths[$architecture] $bootAssetsWinPEDriversPaths[$architecture] } foreach ($legacyWinPEDriversPath in @($legacyCacheWinPEDriversPath, $legacyBootAssetsWinPEDriversPath)) { if ((Test-Path -LiteralPath $legacyWinPEDriversPath -PathType Container) -and -not (Get-ChildItem -LiteralPath $legacyWinPEDriversPath -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) { Remove-Item -LiteralPath $legacyWinPEDriversPath -Force -ErrorAction SilentlyContinue } } # Establish the shared cache and Boot-Assets roots before migration. $paths = @( (Join-Path $Script:OSDeployCorePath 'boot'), (Join-Path $Script:OSDeployCorePath 'cache'), (Join-Path $Script:OSDeployCorePath 'cache' 'config'), (Join-Path $Script:OSDeployCorePath 'cache' 'downloads'), (Join-Path $Script:OSDeployCorePath 'cache' 'hashes'), (Join-Path $Script:OSDeployCorePath 'cache' 'psrepository'), $Script:OSDeployCoreSoftwarePath, (Join-Path $Script:OSDeployCorePath 'cache' 'windows-os'), (Join-Path $Script:OSDeployCorePath 'cache' 'windows-re'), (Join-Path $Script:OSDeployCorePath 'cache' 'winpe-apps'), (Join-Path $Script:OSDeployBootAssetsPath 'boot-mediascript'), $bootAssetsWinPEDriversPaths['amd64'], $bootAssetsWinPEDriversPaths['arm64'], (Join-Path $Script:OSDeployBootAssetsPath 'boot-winpescript'), (Join-Path $Script:OSDeployBootAssetsPath 'boot-wallpaper'), $buildProfilesPath, (Join-Path $Script:OSDeployBootAssetsPath 'winpestartup-profiles') ) foreach ($path in $paths) { if (-not (Test-Path -Path $path)) { New-Item -ItemType Directory -Path $path -Force | Out-Null Write-Verbose "[$($MyInvocation.MyCommand.Name)] Created directory: $path" } } & $moveDirectoryContent $legacySoftwarePath $Script:OSDeployCoreSoftwarePath # Move a complete profile directory so all profile-local content remains together. $moveProfileDirectory = { param ( [System.IO.DirectoryInfo]$ProfileDirectory, [System.String]$FallbackArchitecture ) $profileFile = @('osdeployboot.json', 'buildprofile.json') | ForEach-Object { Join-Path $ProfileDirectory.FullName $_ } | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1 if (-not $profileFile) { Write-Warning "[$functionName] Could not migrate '$($ProfileDirectory.FullName)' because it does not contain a build profile." return } try { $profileContent = Get-Content -LiteralPath $profileFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { Write-Warning "[$functionName] Could not migrate build profile '$profileFile': $($_.Exception.Message)" return } $architecture = if ($profileContent.Architecture -in @('amd64', 'arm64')) { [System.String]$profileContent.Architecture } elseif ($FallbackArchitecture -in @('amd64', 'arm64')) { $FallbackArchitecture } else { Write-Warning "[$functionName] Could not migrate build profile '$profileFile' because Architecture is missing or unsupported." return } $canonicalName = & $getCanonicalProfileName $ProfileDirectory.Name $architecture $destinationPath = Join-Path $buildProfilesPath $canonicalName if ($ProfileDirectory.FullName.Equals($destinationPath, [System.StringComparison]::OrdinalIgnoreCase)) { return } if (Test-Path -LiteralPath $destinationPath) { Write-Warning "[$functionName] Could not migrate '$($ProfileDirectory.FullName)' because canonical profile path '$destinationPath' already exists." return } try { $sourcePath = $ProfileDirectory.FullName Move-Item -LiteralPath $sourcePath -Destination $destinationPath -ErrorAction Stop $profilePathMoves.Add([PSCustomObject]@{ Source = $sourcePath; Destination = $destinationPath }) Write-HostDateTimeDarkGray "Migrated build profile '$sourcePath' to '$destinationPath'" } catch { Write-Warning "[$functionName] Could not migrate build profile '$($ProfileDirectory.FullName)': $($_.Exception.Message)" } } # Root-level profile files predate profile directories; default unknown architectures to amd64. foreach ($file in @(Get-ChildItem -LiteralPath $buildProfilesPath -Filter '*.json' -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -notin @('recent-amd64.json', 'recent-arm64.json') })) { try { $profileContent = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $architecture = if ($profileContent.Architecture -in @('amd64', 'arm64')) { [System.String]$profileContent.Architecture } else { 'amd64' } $canonicalName = & $getCanonicalProfileName $file.BaseName $architecture $profileDirectory = Join-Path $buildProfilesPath $canonicalName $profilePath = Join-Path $profileDirectory 'osdeployboot.json' if (Test-Path -LiteralPath $profileDirectory) { Write-Warning "[$functionName] Could not migrate '$($file.FullName)' because canonical profile path '$profileDirectory' already exists." continue } New-Item -ItemType Directory -Path $profileDirectory -ErrorAction Stop | Out-Null Move-Item -LiteralPath $file.FullName -Destination $profilePath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated build profile '$($file.FullName)' to '$profilePath'" } catch { Write-Warning "[$functionName] Could not migrate build profile '$($file.FullName)': $($_.Exception.Message)" } } # Architecture-level files and directories are moved to canonical flat profile folders. foreach ($architecture in @('amd64', 'arm64')) { $architecturePath = Join-Path $buildProfilesPath $architecture if (-not (Test-Path -LiteralPath $architecturePath -PathType Container)) { continue } foreach ($file in @(Get-ChildItem -LiteralPath $architecturePath -Filter '*.json' -File -ErrorAction SilentlyContinue)) { try { $profileContent = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $profileArchitecture = if ($profileContent.Architecture -in @('amd64', 'arm64')) { [System.String]$profileContent.Architecture } else { $architecture } $canonicalName = & $getCanonicalProfileName $file.BaseName $profileArchitecture $profileDirectory = Join-Path $buildProfilesPath $canonicalName $profilePath = Join-Path $profileDirectory 'osdeployboot.json' if (Test-Path -LiteralPath $profileDirectory) { Write-Warning "[$functionName] Could not migrate '$($file.FullName)' because canonical profile path '$profileDirectory' already exists." continue } New-Item -ItemType Directory -Path $profileDirectory -ErrorAction Stop | Out-Null Move-Item -LiteralPath $file.FullName -Destination $profilePath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated build profile '$($file.FullName)' to '$profilePath'" } catch { Write-Warning "[$functionName] Could not migrate build profile '$($file.FullName)': $($_.Exception.Message)" } } foreach ($profileDirectory in @(Get-ChildItem -LiteralPath $architecturePath -Directory -ErrorAction SilentlyContinue)) { & $moveProfileDirectory $profileDirectory $architecture } if (-not (Get-ChildItem -LiteralPath $architecturePath -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) { Remove-Item -LiteralPath $architecturePath -Force -ErrorAction SilentlyContinue } } # Canonicalize any profiles that were already stored directly below the profile root. foreach ($profileDirectory in @(Get-ChildItem -LiteralPath $buildProfilesPath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notin @('amd64', 'arm64') })) { & $moveProfileDirectory $profileDirectory } $legacySharedStartupProfilesPath = Join-Path $Script:OSDeployBootAssetsPath 'winpe-profiles' $sharedStartupProfilesPath = Join-Path $Script:OSDeployBootAssetsPath 'winpestartup-profiles' & $moveDirectoryContent $legacySharedStartupProfilesPath $sharedStartupProfilesPath foreach ($profileDirectory in @(Get-ChildItem -LiteralPath $buildProfilesPath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notin @('amd64', 'arm64') })) { foreach ($legacyFolderName in $buildContentFolderMap.Keys) { $legacyContentPath = Join-Path $profileDirectory.FullName $legacyFolderName $contentPath = Join-Path $profileDirectory.FullName $buildContentFolderMap[$legacyFolderName] & $moveDirectoryContent $legacyContentPath $contentPath } $legacyProfilePath = Join-Path $profileDirectory.FullName 'buildprofile.json' $profilePath = Join-Path $profileDirectory.FullName 'osdeployboot.json' if (Test-Path -LiteralPath $legacyProfilePath -PathType Leaf) { try { if (Test-Path -LiteralPath $profilePath -PathType Leaf) { $archivePath = Join-Path $profileDirectory.FullName 'profile.legacy.json' $suffix = 1 while (Test-Path -LiteralPath $archivePath) { $archivePath = Join-Path $profileDirectory.FullName ('profile.legacy-{0:d3}.json' -f $suffix) $suffix++ } Move-Item -LiteralPath $legacyProfilePath -Destination $archivePath -ErrorAction Stop Write-Warning "[$(Get-Date -Format s)] Build profile already exists at '$profilePath'. Archived legacy profile to '$archivePath'." } else { Move-Item -LiteralPath $legacyProfilePath -Destination $profilePath -ErrorAction Stop Write-HostDateTimeDarkGray "Migrated build profile '$legacyProfilePath' to '$profilePath'" } } catch { Write-Warning "[$(Get-Date -Format s)] Could not migrate build profile '$legacyProfilePath': $($_.Exception.Message)" } } if (Test-Path -LiteralPath $profilePath -PathType Leaf) { try { $profileContent = Get-Content -LiteralPath $profilePath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop if ($profileContent.Architecture -in @('amd64', 'arm64')) { $legacyProfileDriverPath = Join-Path $profileDirectory.FullName 'build-winpedrivers' $profileDriverPath = Join-Path $profileDirectory.FullName "winpedrivers-$($profileContent.Architecture)" & $moveWinPEDriverDirectory $legacyProfileDriverPath $profileDriverPath } elseif (Test-Path -LiteralPath (Join-Path $profileDirectory.FullName 'build-winpedrivers') -PathType Container) { Write-Warning "[$functionName] Could not migrate profile-local WinPE drivers in '$($profileDirectory.FullName)' because Architecture is missing or unsupported." } } catch { Write-Warning "[$functionName] Could not inspect build profile '$profilePath' for WinPE driver migration: $($_.Exception.Message)" } $legacyProfileStartupPath = Join-Path $profileDirectory.FullName 'winpe-profiles' $profileStartupPath = Join-Path $profileDirectory.FullName 'WinPEStartup' 'profiles' & $moveDirectoryContent $legacyProfileStartupPath $profileStartupPath Initialize-OSDeployCoreBuildProfilePaths -Path $profileDirectory.FullName } } $legacyOSDRepoBuildProfilesPath = Join-Path $legacyOSDRepoPath 'build-profiles' $legacyRepositoryTokenPath = '${{ OSDeployCore }}\repository' $bootAssetsTokenPath = '${{ OSDeployCore }}\boot-assets' $pathProperties = @('WinPEDriver', 'WinPEScript', 'MediaScript', 'WinPEStartupProfile') # Remove retired app properties and rename the media-script property in every profile copy. foreach ($profileFile in @(Get-ChildItem -LiteralPath $Script:OSDeployCorePath -Filter 'osdeployboot.json' -File -Recurse -ErrorAction SilentlyContinue)) { try { $profileContent = Get-Content -LiteralPath $profileFile.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $profileChanged = $false foreach ($retiredPropertyName in @('WinPEApp', 'WinPEAppScript')) { if ($profileContent.PSObject.Properties[$retiredPropertyName]) { $profileContent.PSObject.Properties.Remove($retiredPropertyName) $profileChanged = $true Write-Verbose "[$($MyInvocation.MyCommand.Name)] Removed $retiredPropertyName from build profile: $($profileFile.FullName)" } } $legacyMediaScriptProperty = $profileContent.PSObject.Properties['WinPEMediaScript'] if ($legacyMediaScriptProperty) { $mediaScriptProperty = $profileContent.PSObject.Properties['MediaScript'] $mediaScriptValues = @( if ($mediaScriptProperty) { @($mediaScriptProperty.Value) } @($legacyMediaScriptProperty.Value) ) | Where-Object { $null -ne $_ } $seenMediaScriptValues = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $mediaScriptValues = @($mediaScriptValues | Where-Object { $seenMediaScriptValues.Add([System.String]$_) }) $legacyWasArray = $legacyMediaScriptProperty.Value -is [System.Array] if ($mediaScriptProperty) { $mediaScriptProperty.Value = if ($mediaScriptValues.Count -eq 1 -and -not ($mediaScriptProperty.Value -is [System.Array]) -and -not $legacyWasArray) { $mediaScriptValues[0] } else { [System.Object[]]$mediaScriptValues } } else { $mediaScriptValue = if ($legacyWasArray) { [System.Object[]]$mediaScriptValues } else { $mediaScriptValues[0] } $profileContent | Add-Member -NotePropertyName 'MediaScript' -NotePropertyValue $mediaScriptValue } $profileContent.PSObject.Properties.Remove('WinPEMediaScript') $profileChanged = $true Write-Verbose "[$($MyInvocation.MyCommand.Name)] Renamed WinPEMediaScript to MediaScript in build profile: $($profileFile.FullName)" } if ($profileChanged) { $temporaryPath = Join-Path $profileFile.DirectoryName ".$(New-Guid).tmp" try { $profileContent | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File -LiteralPath $temporaryPath -Encoding utf8 -ErrorAction Stop [System.IO.File]::Move($temporaryPath, $profileFile.FullName, $true) } finally { if (Test-Path -LiteralPath $temporaryPath) { Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue } } } } catch { Write-Warning "[$(Get-Date -Format s)] Could not update build profile '$($profileFile.FullName)': $($_.Exception.Message)" } } # Rewrite and tokenize persisted paths so migrated profiles continue to resolve content. foreach ($profileFile in @(Get-ChildItem -LiteralPath $Script:OSDeployCorePath -Filter 'osdeployboot.json' -File -Recurse -ErrorAction SilentlyContinue)) { try { $profileContent = Get-Content -LiteralPath $profileFile.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $profileChanged = $false $isFlatProfile = $profileFile.Directory.Parent.FullName.Equals($buildProfilesPath, [System.StringComparison]::OrdinalIgnoreCase) $canonicalProfileName = if ($profileContent.Architecture -in @('amd64', 'arm64')) { & $getCanonicalProfileName $profileFile.Directory.Name ([System.String]$profileContent.Architecture) } if ($isFlatProfile -and $profileFile.Directory.Name.Equals($canonicalProfileName, [System.StringComparison]::OrdinalIgnoreCase)) { $profileName = $profileFile.Directory.Name $nameProperty = $profileContent.PSObject.Properties['Name'] if ($nameProperty) { if ($nameProperty.Value -cne $profileName) { $nameProperty.Value = $profileName $profileChanged = $true } } else { $profileContent | Add-Member -NotePropertyName 'Name' -NotePropertyValue $profileName $profileChanged = $true } } if ($profileContent.PSObject.Properties['WinPECustomWallpaper']) { $profileContent.PSObject.Properties.Remove('WinPECustomWallpaper') $profileChanged = $true Write-Verbose "[$($MyInvocation.MyCommand.Name)] Removed WinPECustomWallpaper from build profile: $($profileFile.FullName)" } foreach ($propertyName in $pathProperties) { $property = $profileContent.PSObject.Properties[$propertyName] if (-not $property -or $null -eq $property.Value) { continue } # Preserve the original scalar-or-array shape while updating each stored path. $isArray = $property.Value -is [System.Array] $updatedValues = @( foreach ($entry in @($property.Value)) { $updatedEntry = $entry if ($entry -is [System.String]) { if ($propertyName -eq 'WinPEDriver') { foreach ($architecture in @('amd64', 'arm64')) { $legacyCacheArchitecturePath = Join-Path $legacyCacheWinPEDriversPath $architecture $legacyBootAssetsArchitecturePath = Join-Path $legacyBootAssetsWinPEDriversPath $architecture $legacyManagedTokenPath = '${{ OSDeployCore }}' + "\winpedrivers-$architecture" $driverPathCandidates = @() $legacyDriverPath = $null if ($updatedEntry.StartsWith($legacyManagedTokenPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyManagedTokenPath.Length -or $updatedEntry[$legacyManagedTokenPath.Length] -in @('\', '/'))) { $updatedEntry = $bootAssetsWinPEDriversPaths[$architecture] + $updatedEntry.Substring($legacyManagedTokenPath.Length) $profileChanged = $true break } elseif ($updatedEntry.StartsWith($legacyCacheArchitecturePath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyCacheArchitecturePath.Length -or $updatedEntry[$legacyCacheArchitecturePath.Length] -in @('\', '/'))) { $legacyDriverPath = $legacyCacheArchitecturePath $driverPathCandidates = @($bootAssetsWinPEDriversPaths[$architecture]) } elseif ($updatedEntry.StartsWith($cachedWinPEDriversPaths[$architecture], [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $cachedWinPEDriversPaths[$architecture].Length -or $updatedEntry[$cachedWinPEDriversPaths[$architecture].Length] -in @('\', '/'))) { $legacyDriverPath = $cachedWinPEDriversPaths[$architecture] $driverPathCandidates = @($bootAssetsWinPEDriversPaths[$architecture]) } elseif ($updatedEntry.StartsWith($managedWinPEDriversPaths[$architecture], [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $managedWinPEDriversPaths[$architecture].Length -or $updatedEntry[$managedWinPEDriversPaths[$architecture].Length] -in @('\', '/'))) { $legacyDriverPath = $managedWinPEDriversPaths[$architecture] $driverPathCandidates = @($bootAssetsWinPEDriversPaths[$architecture]) } elseif ($updatedEntry.StartsWith($legacyBootAssetsArchitecturePath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyBootAssetsArchitecturePath.Length -or $updatedEntry[$legacyBootAssetsArchitecturePath.Length] -in @('\', '/'))) { $legacyDriverPath = $legacyBootAssetsArchitecturePath $driverPathCandidates = @($bootAssetsWinPEDriversPaths[$architecture]) } if ($legacyDriverPath) { $driverPathSuffix = $updatedEntry.Substring($legacyDriverPath.Length) foreach ($driverPathCandidate in $driverPathCandidates) { $migratedDriverEntry = $driverPathCandidate + $driverPathSuffix if (Test-Path -LiteralPath $migratedDriverEntry) { $updatedEntry = $migratedDriverEntry $profileChanged = $true break } } break } } } if ($updatedEntry.StartsWith($legacyBuildProfilesPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyBuildProfilesPath.Length -or $updatedEntry[$legacyBuildProfilesPath.Length] -in @('\', '/'))) { $profileChanged = $true $updatedEntry = $buildProfilesPath + $updatedEntry.Substring($legacyBuildProfilesPath.Length) } elseif ($updatedEntry.StartsWith($legacyOSDRepoBuildProfilesPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyOSDRepoBuildProfilesPath.Length -or $updatedEntry[$legacyOSDRepoBuildProfilesPath.Length] -in @('\', '/'))) { $profileChanged = $true $updatedEntry = $buildProfilesPath + $updatedEntry.Substring($legacyOSDRepoBuildProfilesPath.Length) } elseif ($updatedEntry.StartsWith($legacyRepositoryTokenPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyRepositoryTokenPath.Length -or $updatedEntry[$legacyRepositoryTokenPath.Length] -in @('\', '/'))) { $profileChanged = $true $updatedEntry = $Script:OSDeployBootAssetsPath + $updatedEntry.Substring($legacyRepositoryTokenPath.Length) } elseif ($updatedEntry.StartsWith($bootAssetsTokenPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $bootAssetsTokenPath.Length -or $updatedEntry[$bootAssetsTokenPath.Length] -in @('\', '/'))) { $updatedEntry = $Script:OSDeployBootAssetsPath + $updatedEntry.Substring($bootAssetsTokenPath.Length) } elseif ($updatedEntry.StartsWith($legacyRepositoryPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyRepositoryPath.Length -or $updatedEntry[$legacyRepositoryPath.Length] -in @('\', '/'))) { $profileChanged = $true $updatedEntry = $Script:OSDeployBootAssetsPath + $updatedEntry.Substring($legacyRepositoryPath.Length) } elseif ($updatedEntry.StartsWith($legacyOSDRepoPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyOSDRepoPath.Length -or $updatedEntry[$legacyOSDRepoPath.Length] -in @('\', '/'))) { $profileChanged = $true $updatedEntry = $Script:OSDeployBootAssetsPath + $updatedEntry.Substring($legacyOSDRepoPath.Length) } foreach ($profilePathMove in $profilePathMoves) { if ($updatedEntry.StartsWith($profilePathMove.Source, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $profilePathMove.Source.Length -or $updatedEntry[$profilePathMove.Source.Length] -in @('\', '/'))) { $updatedEntry = $profilePathMove.Destination + $updatedEntry.Substring($profilePathMove.Source.Length) $profileChanged = $true break } } if ($updatedEntry.StartsWith($Script:OSDeployBootAssetsPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $Script:OSDeployBootAssetsPath.Length -or $updatedEntry[$Script:OSDeployBootAssetsPath.Length] -in @('\', '/'))) { $bootAssetsRelativePath = $updatedEntry.Substring($Script:OSDeployBootAssetsPath.Length) foreach ($legacyFolderName in $buildContentFolderMap.Keys) { $escapedFolderName = [System.Text.RegularExpressions.Regex]::Escape($legacyFolderName) $sharedFolderPattern = "^([\\/])$escapedFolderName(?=[\\/]|$)" $profileFolderPattern = "^([\\/]osdeployboot-profiles[\\/](?:(?:amd64|arm64)[\\/])?[^\\/]+[\\/])$escapedFolderName(?=[\\/]|$)" $updatedRelativePath = $bootAssetsRelativePath -ireplace $sharedFolderPattern, "`${1}$($buildContentFolderMap[$legacyFolderName])" $updatedRelativePath = $updatedRelativePath -ireplace $profileFolderPattern, "`${1}$($buildContentFolderMap[$legacyFolderName])" if ($updatedRelativePath -cne $bootAssetsRelativePath) { $bootAssetsRelativePath = $updatedRelativePath $profileChanged = $true } } $updatedEntry = $Script:OSDeployBootAssetsPath + $bootAssetsRelativePath } if ($propertyName -eq 'MediaScript') { $updatedModuleEntry = $updatedEntry -ireplace '[\\/]core[\\/](?:OSDRepo[\\/])?(?:media-script|build-mediascript)(?=[\\/]|$)', '\core\boot-mediascript' if ($updatedModuleEntry -cne $updatedEntry) { $updatedEntry = $updatedModuleEntry $profileChanged = $true } } elseif ($propertyName -eq 'WinPEScript') { $updatedModuleEntry = $updatedEntry -ireplace '[\\/]core[\\/](?:OSDRepo[\\/])?(?:winpe-script|build-winpescript)(?=[\\/]|$)', '\core\boot-winpescript' if ($updatedModuleEntry -cne $updatedEntry) { $updatedEntry = $updatedModuleEntry $profileChanged = $true } } } if ($propertyName -eq 'WinPEStartupProfile' -and $updatedEntry -is [System.String]) { $legacyProfileStartupPath = Join-Path $profileFile.DirectoryName 'winpe-profiles' $profileStartupPath = Join-Path $profileFile.DirectoryName 'WinPEStartup' 'profiles' if ($updatedEntry.StartsWith($legacySharedStartupProfilesPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacySharedStartupProfilesPath.Length -or $updatedEntry[$legacySharedStartupProfilesPath.Length] -in @('\', '/'))) { $updatedEntry = $sharedStartupProfilesPath + $updatedEntry.Substring($legacySharedStartupProfilesPath.Length) $profileChanged = $true } elseif ($updatedEntry.StartsWith($legacyProfileStartupPath, [System.StringComparison]::OrdinalIgnoreCase) -and ($updatedEntry.Length -eq $legacyProfileStartupPath.Length -or $updatedEntry[$legacyProfileStartupPath.Length] -in @('\', '/'))) { $updatedEntry = $profileStartupPath + $updatedEntry.Substring($legacyProfileStartupPath.Length) $profileChanged = $true } $updatedModuleEntry = $updatedEntry -ireplace '[\\/]core[\\/]OSDRepo[\\/]winpe-profiles(?=[\\/]|$)', '\core\winpestartup-profiles' if ($updatedModuleEntry -cne $updatedEntry) { $updatedEntry = $updatedModuleEntry $profileChanged = $true } } if ($updatedEntry -is [System.String]) { $tokenizedEntry = ConvertTo-OSDeployBuildProfileToken -Path $updatedEntry if ($tokenizedEntry -cne $updatedEntry) { $updatedEntry = $tokenizedEntry $profileChanged = $true } } $updatedEntry } ) $property.Value = if ($isArray) { [System.Object[]]$updatedValues } else { $updatedValues[0] } } if ($profileChanged) { # Replace through a temporary file so a serialization failure cannot corrupt the profile. $temporaryPath = Join-Path $profileFile.DirectoryName ".$(New-Guid).tmp" try { $profileContent | ConvertTo-Json -Depth 5 -WarningAction SilentlyContinue | Out-File -LiteralPath $temporaryPath -Encoding utf8 -ErrorAction Stop [System.IO.File]::Move($temporaryPath, $profileFile.FullName, $true) Write-Verbose "[$($MyInvocation.MyCommand.Name)] Updated build profile: $($profileFile.FullName)" } finally { if (Test-Path -LiteralPath $temporaryPath) { Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue } } } } catch { Write-Warning "[$(Get-Date -Format s)] Could not update build profile '$($profileFile.FullName)': $($_.Exception.Message)" } } $retiredAppFolderNames = @('winpe-appscript', 'build-winpeappscript', 'build-winpeapp') $cleanupRoots = @($legacyRepositoryPath, $legacyOSDRepoPath, $Script:OSDeployBootAssetsPath) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Container) } | Select-Object -Unique foreach ($cleanupRoot in $cleanupRoots) { $retiredAppFolders = @(Get-ChildItem -LiteralPath $cleanupRoot -Directory -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -in $retiredAppFolderNames } | Sort-Object { $_.FullName.Length } -Descending) foreach ($retiredAppFolder in $retiredAppFolders) { if (-not (Get-ChildItem -LiteralPath $retiredAppFolder.FullName -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) { Remove-Item -LiteralPath $retiredAppFolder.FullName -Force -ErrorAction SilentlyContinue Write-Verbose "[$($MyInvocation.MyCommand.Name)] Removed empty retired WinPE app-script directory: $($retiredAppFolder.FullName)" } } } } |