Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,4 @@ Key differences:
- **Omitting the converter switch case** — a new OS XML will load without errors but produce zero OS options in the UX because the converter returns no release information for the build.
- **Stale `surface.json` driver URLs** — `Get-OSDCoreDriverPackCatalogSurface` serves MSI URLs from `OSDCloud/core/driverpacks/surface.json`; if `UpdatePage` links change and the automated workflow has not run, deployed drivers may point to outdated or removed files. Run `.github/scripts/Update-MicrosoftCatalog.ps1` manually to refresh.
- **Editing OEM XML snapshots manually** — Dell/HP/Lenovo XML is replaced wholesale from upstream; manual edits will be lost on the next snapshot refresh.
- **Adding a Windows version string to workflow configs without the catalog XML** — the UX will offer the version but `Get-OSDCloudCoreOperatingSystems` will return no matching ESD entries, causing `Initialize-DeployOSDCloud` to throw.
- **Adding a Windows version string to workflow configs without the catalog XML** — the UX will offer the version but `Get-OSDCoreOperatingSystems` will return no matching ESD entries, causing `Initialize-DeployOSDCloud` to throw.
155 changes: 155 additions & 0 deletions .github/scripts/Sync-OSDOperatingSystemParity.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<#
.SYNOPSIS
Synchronizes canonical operating-system files from RecastOSDCloud to OSD.

.DESCRIPTION
Reads the adjacent synchronization manifest and compares canonical RecastOSDCloud
operating-system functions and catalog files with an OSD repository checkout. In
check mode, reports drift and exits with an error without changing files. Otherwise,
copies missing or changed files and removes extra managed catalog files.

.PARAMETER DestinationRepositoryPath
Specifies the root of the OSD repository to validate or update.

.PARAMETER Check
Validates synchronization without changing the destination repository.

.EXAMPLE
.\Sync-OSDOperatingSystemParity.ps1 -DestinationRepositoryPath ..\..\OSD -Check

Checks an adjacent OSD repository for operating-system parity drift.

.EXAMPLE
.\Sync-OSDOperatingSystemParity.ps1 -DestinationRepositoryPath ..\..\OSD

Synchronizes the mapped operating-system files into an adjacent OSD repository.

.LINK
https://github.com/OSDeploy/OSD/tree/master/docs

.NOTES
Author: David Segura - Recast Software
2026-09-30 - Initial operating-system parity synchronization script
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$DestinationRepositoryPath,

[Parameter()]
[switch]$Check
)

$Error.Clear()
$ErrorActionPreference = 'Stop'
$sourceRepositoryPath = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
$destinationRepositoryPath = [System.IO.Path]::GetFullPath($DestinationRepositoryPath)
$manifestPath = Join-Path $PSScriptRoot 'osd-operating-system-sync.json'

if (-not (Test-Path -LiteralPath $destinationRepositoryPath -PathType Container)) {
throw "Destination OSD repository was not found at '$destinationRepositoryPath'."
}
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
throw "Operating-system synchronization manifest was not found at '$manifestPath'."
}

$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
$results = @()

foreach ($mapping in $manifest.Files) {
$sourcePath = Join-Path $sourceRepositoryPath $mapping.Source
$destinationPath = Join-Path $destinationRepositoryPath $mapping.Destination
if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
throw "Canonical operating-system file was not found at '$sourcePath'."
}

$state = if (-not (Test-Path -LiteralPath $destinationPath -PathType Leaf)) {
'Missing'
}
elseif ((Get-FileHash -LiteralPath $sourcePath -Algorithm SHA256).Hash -cne (Get-FileHash -LiteralPath $destinationPath -Algorithm SHA256).Hash) {
'Different'
}
else {
'InSync'
}

if (-not $Check -and $state -ne 'InSync' -and $PSCmdlet.ShouldProcess($destinationPath, 'Synchronize canonical operating-system file')) {
$null = New-Item -Path (Split-Path -Path $destinationPath -Parent) -ItemType Directory -Force
Copy-Item -LiteralPath $sourcePath -Destination $destinationPath -Force
$state = 'Synchronized'
}

$results += [pscustomobject]@{
Source = $mapping.Source
Destination = $mapping.Destination
State = $state
}
}

foreach ($directoryMapping in $manifest.Directories) {
$sourceDirectory = Join-Path $sourceRepositoryPath $directoryMapping.Source
$destinationDirectory = Join-Path $destinationRepositoryPath $directoryMapping.Destination
if (-not (Test-Path -LiteralPath $sourceDirectory -PathType Container)) {
throw "Canonical operating-system directory was not found at '$sourceDirectory'."
}
if (-not (Test-Path -LiteralPath $destinationDirectory -PathType Container) -and -not $Check) {
$null = New-Item -Path $destinationDirectory -ItemType Directory -Force
}
Comment on lines +96 to +98

$sourceFiles = @(Get-ChildItem -LiteralPath $sourceDirectory -Filter $directoryMapping.Pattern -File)
foreach ($sourceFile in $sourceFiles) {
$relativeDestination = Join-Path $directoryMapping.Destination $sourceFile.Name
$destinationPath = Join-Path $destinationRepositoryPath $relativeDestination
$state = if (-not (Test-Path -LiteralPath $destinationPath -PathType Leaf)) {
'Missing'
}
elseif ((Get-FileHash -LiteralPath $sourceFile.FullName -Algorithm SHA256).Hash -cne (Get-FileHash -LiteralPath $destinationPath -Algorithm SHA256).Hash) {
'Different'
}
else {
'InSync'
}

if (-not $Check -and $state -ne 'InSync' -and $PSCmdlet.ShouldProcess($destinationPath, 'Synchronize canonical operating-system catalog')) {
Copy-Item -LiteralPath $sourceFile.FullName -Destination $destinationPath -Force
$state = 'Synchronized'
}

$results += [pscustomobject]@{
Source = Join-Path $directoryMapping.Source $sourceFile.Name
Destination = $relativeDestination
State = $state
}
}

if ($directoryMapping.RemoveExtra -and (Test-Path -LiteralPath $destinationDirectory -PathType Container)) {
$sourceNames = @($sourceFiles.Name)
foreach ($destinationFile in Get-ChildItem -LiteralPath $destinationDirectory -Filter $directoryMapping.Pattern -File) {
if ($destinationFile.Name -in $sourceNames) {
continue
}

$relativeDestination = Join-Path $directoryMapping.Destination $destinationFile.Name
$state = 'Extra'
if (-not $Check -and $PSCmdlet.ShouldProcess($destinationFile.FullName, 'Remove unmanaged operating-system catalog')) {
Remove-Item -LiteralPath $destinationFile.FullName -Force
$state = 'Removed'
}

$results += [pscustomobject]@{
Source = $null
Destination = $relativeDestination
State = $state
}
}
}
}

$results
if ($Check) {
$drift = @($results | Where-Object State -NE 'InSync')
if ($drift.Count -gt 0) {
throw "Operating-system parity check found $($drift.Count) drifted file(s)."
}
}
32 changes: 32 additions & 0 deletions .github/scripts/osd-operating-system-sync.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"Files": [
{
"Source": "OSDCloud/private/core-operatingsystem/ConvertTo-OSDCoreOperatingSystemInfo.ps1",
"Destination": "Private/core-operatingsystem/ConvertTo-OSDCoreOperatingSystemInfo.ps1"
},
{
"Source": "OSDCloud/private/core-operatingsystem/Initialize-ModuleCoreOperatingSystems.ps1",
"Destination": "Private/core-operatingsystem/Initialize-ModuleCoreOperatingSystems.ps1"
},
{
"Source": "OSDCloud/private/core-operatingsystem/Get-OSDCoreOperatingSystems.ps1",
"Destination": "Private/core-operatingsystem/Get-OSDCoreOperatingSystems.ps1"
},
{
"Source": "OSDCloud/private/core-operatingsystem/Set-OSDCoreOperatingSystemCloudObject.ps1",
"Destination": "Private/core-operatingsystem/Set-OSDCoreOperatingSystemCloudObject.ps1"
},
{
"Source": "OSDCloud/private/Update-OSDCloudCoreCatalogOS.ps1",
"Destination": "Private/core-operatingsystem/Update-OSDCloudCoreCatalogOS.ps1"
}
],
"Directories": [
{
"Source": "OSDCloud/core/operatingsystems",
"Destination": "core/operatingsystems",
"Pattern": "*.xml",
"RemoveExtra": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: osdcore-operating-system-cloud-object
description: "Use when identifying, mapping, reviewing, or changing $OSDCoreOperatingSystemCloudObject, Set-OSDCoreOperatingSystemCloudObject, Get-OSDCoreOperatingSystems, or Get-OSDCloudCoreOperatingSystems across the OSD and OSDCloud PowerShell modules."
description: "Use when identifying, mapping, reviewing, or changing $OSDCoreOperatingSystemCloudObject, Set-OSDCoreOperatingSystemCloudObject, or Get-OSDCoreOperatingSystems across the OSD and OSDCloud PowerShell modules."
argument-hint: "OS filters or code path, for example: Windows 11 25H2 amd64 Retail en-us"
---

Expand All @@ -17,7 +17,7 @@ Identify the selected object by its properties before consuming it:
| Module shape | Strong indicators | Provider |
| --- | --- | --- |
| `OSD` | `Name`, `Version`, `ReleaseID`, `Architecture`, `Language`, `Activation`, `Build`, `Url`, `SHA1`, `SHA256` | `Get-OSDCoreOperatingSystems` |
| `OSDCloud` | `Id`, `OperatingSystem`, `OSName`, `OSVersion`, `OSArchitecture`, `OSActivation`, `OSLanguageCode`, `OSBuild`, `OSBuildVersion`, `FilePath`, `Sha1`, `Sha256` | `Get-OSDCloudCoreOperatingSystems` |
| `OSDCloud` | `Id`, `OperatingSystem`, `OSName`, `OSVersion`, `OSArchitecture`, `OSActivation`, `OSLanguageCode`, `OSBuild`, `OSBuildVersion`, `FilePath`, `Sha1`, `Sha256` | `Get-OSDCoreOperatingSystems` |

Prefer module context when it is available:

Expand Down Expand Up @@ -64,9 +64,7 @@ PowerShell property lookup is case-insensitive, but preserve the native property

1. Read `OSDCloud/private/core-operatingsystem/Set-OSDCoreOperatingSystemCloudObject.ps1` and the provider functions before editing.
2. Determine whether the call is running in `OSD`, `OSDCloud`, or helper/test code without a module name.
3. Load the matching catalog provider:
- `OSD`: `Get-OSDCoreOperatingSystems`
- `OSDCloud`: `Get-OSDCloudCoreOperatingSystems`
3. Load `Get-OSDCoreOperatingSystems`; it returns the native schema for its current module context.
4. Normalize input filters before comparing: architecture and language should be lowercase; map `x64` to `amd64` where user input or catalog data can contain either spelling.
5. Filter with equivalent properties, not a single module schema.
6. Sort by the full build equivalent: `Build` for `OSD`, `OSBuildVersion` for `OSDCloud`.
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/check-osd-operating-system-parity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Check OSD Operating System Parity

on:
workflow_dispatch:
pull_request:
paths:
- '.github/scripts/Sync-OSDOperatingSystemParity.ps1'
- '.github/scripts/osd-operating-system-sync.json'
- '.github/workflows/check-osd-operating-system-parity.yaml'
- 'OSDCloud/core/operatingsystems/**'
- 'OSDCloud/private/core-operatingsystem/**'
- 'OSDCloud/private/Update-OSDCloudCoreCatalogOS.ps1'

permissions:
contents: read

jobs:
check:
runs-on: windows-latest

steps:
- name: Checkout RecastOSDCloud
uses: actions/checkout@v5
with:
path: RecastOSDCloud

- name: Checkout OSD
uses: actions/checkout@v5
with:
repository: OSDeploy/OSD
path: OSD

- name: Check operating system parity
shell: pwsh
run: |
& '.\RecastOSDCloud\.github\scripts\Sync-OSDOperatingSystemParity.ps1' `
-DestinationRepositoryPath '.\OSD' `
-Check
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

All notable changes to this project will be documented in this file.

## 26.9.30.1 - September 30, 2026

### Added

- Added a manifest-driven synchronization check for canonical operating-system functions and catalog XML files shared with the OSD module.
- Added a read-only GitHub Actions workflow that detects operating-system parity drift against `OSDeploy/OSD`.

### Changed

- Module version bumped to `26.9.30.1` and the module description updated to identify the Windows 11 26H2 GA release.
- Made bundled operating-system catalogs win when an external catalog has the same build revision, while preserving higher-revision external overlays.
- Merged the OSD and OSDCloud catalog projections into `Get-OSDCoreOperatingSystems`, retaining each module's native property schema.
- Updated deployment initialization, operating-system selection, default OS resolution, catalog refresh, maintenance guidance, and function help to use the unified provider and synchronized catalog model.

### Fixed

- Made HP driver pack catalog release-date parsing culture-independent.

### Removed

- Removed the redundant private `Get-OSDCloudCoreOperatingSystems` provider after migrating its callers to the unified function.

## 26.9.29.1 - September 29, 2026

### Added
Expand Down
19 changes: 9 additions & 10 deletions OSDCloud/OSDCloud.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@
# Module manifest for module 'OSDCloud'
#
# Generated by: David Segura
# Generated on: 2026-04-22
#

@{
# --- Identity ---
RootModule = 'OSDCloud.psm1'
ModuleVersion = '26.9.29.1'
CompatiblePSEditions = @('Core','Desktop')
GUID = '2fbd5c65-79c7-4561-9a2e-c4a4eebc89c7'
RootModule = 'OSDCloud.psm1'
ModuleVersion = '26.9.30.1'
CompatiblePSEditions = @('Core', 'Desktop')
GUID = '2fbd5c65-79c7-4561-9a2e-c4a4eebc89c7'

# --- Authoring ---
Author = 'David Segura'
CompanyName = 'Recast Software'
Copyright = '(c) 2026 Recast Software. All rights reserved.'
Author = 'David Segura'
CompanyName = 'Recast Software'
Copyright = '(c) 2026 Recast Software. All rights reserved.'
Description = @'
Recast OSDCloud PowerShell Module
Windows 11 26H2 Release
Recast OSDCloud PowerShell Module.
PowerShell module for OSDCloud v2 deployment automation and WinPEStartup from recastsoftware.com.
Includes operating system, driver pack, and device provisioning workflows.
Windows 11 26H2 GA Release
'@

# --- Requirements ---
Expand Down
17 changes: 3 additions & 14 deletions OSDCloud/private/Initialize-DeployOSDCloud.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,9 @@ function Initialize-DeployOSDCloud {
Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Ready: OSDCoreDriverPacks"
#=================================================
# CoreOperatingSystems
$ModuleName = $($MyInvocation.MyCommand.Module.Name)
if ($ModuleName -eq 'OSD') {
$global:OSDCoreOperatingSystems = Get-OSDCoreOperatingSystems |
Where-Object { $_.Architecture -match $processorArchitecture }
Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Ready: OSDCoreOperatingSystems"
}
elseif ($ModuleName -eq 'OSDCloud') {
$global:OSDCoreOperatingSystems = Get-OSDCloudCoreOperatingSystems |
Where-Object { $_.OSArchitecture -match $processorArchitecture }
Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Ready: OSDCoreOperatingSystems"
}
else {
throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Unable to load core operating systems provider command."
}
$global:OSDCoreOperatingSystems = Get-OSDCoreOperatingSystems |
Where-Object { ($_.Architecture -match $processorArchitecture) -or ($_.OSArchitecture -match $processorArchitecture) }
Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Ready: OSDCoreOperatingSystems"
#=================================================
# OSDCloudDeploy
$global:OSDCloudDeploy = $null
Expand Down
5 changes: 5 additions & 0 deletions OSDCloud/private/Update-OSDCloudCoreCatalogOS.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ function Update-OSDCloudCoreCatalogOS {
System.Management.Automation.PSCustomObject. Returns the detected build, module catalog
path, publication status, synchronized drive catalog paths, item count, and SHA256 hash.

.LINK
https://github.com/OSDeploy/OSD/tree/master/docs

.NOTES
Author: David Segura - Recast Software
2026-09-30 - Added validated operating-system catalog publication
Requires Windows, Windows PowerShell 5.1 or later, internet access, expand.exe, writable
temporary storage, and write access to the module catalog directory when updating.
#>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function Get-OSDCoreDriverPackCatalogHP {
Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Building driver pack catalog"
$hpCatalogRoot = $xmlCatalogContent.NewDataSet.HPClientDriverPackCatalog
if ($hpCatalogRoot -and $hpCatalogRoot.DateReleased) {
$dtDateReleased = [datetime]::ParseExact($hpCatalogRoot.DateReleased, 'yyyy-MM-dd', $null)
$dtDateReleased = [datetime]::ParseExact($hpCatalogRoot.DateReleased, 'yyyy-MM-dd', [System.Globalization.CultureInfo]::InvariantCulture)
$catalogVersion = $dtDateReleased.ToString('yy.MM.dd')
}
else {
Expand All @@ -76,7 +76,7 @@ function Get-OSDCoreDriverPackCatalogHP {
$osVersion = $osVersion.Substring($osVersion.Length - 4)

$template = 'M/d/yyyy hh:mm:ss tt'
$dtReleaseDate = [datetime]::ParseExact($hpSoftPaq.DateReleased, $template, $null)
$dtReleaseDate = [datetime]::ParseExact($hpSoftPaq.DateReleased, $template, [System.Globalization.CultureInfo]::InvariantCulture)
$releaseDate = $dtReleaseDate.ToString('yy.MM.dd')

$systemIds = if ($item.SystemId) {
Expand Down
Loading
Loading