# UpdatePackages-oxmc.ps1 # # Fetches two manifests from cdn.oxmc.me and applies any pending updates: # # iso-manifest.json -- versioned zip of C:\Windows\OEM contents # (scripts, cursors, config -- the stuff baked into the ISO) # internal-manifest.json -- individual app packages (exe/msi/msix/ps1/zip) # # Update state is written to C:\Windows\OEM\update-state.json so the script # is idempotent: already-installed versions are skipped. # # Run as: PowerShell -ExecutionPolicy Bypass -File UpdatePackages-oxmc.ps1 # # ---- iso-manifest.json schema (example) -------------------------------- # { # "schemaVersion": 1, # "package": { # "name": "oxmc-oem-core", # "version": "1.2.0", # "description": "OEM scripts, cursors, config", # "url": "https://cdn.oxmc.me/apps/windows/oem-core-1.2.0.zip", # "sha256": "abcdef...", # "installPath": "C:\\Windows\\OEM", # "postInstallScript":"post-install.ps1" # } # } # # ---- internal-manifest.json schema (example) --------------------------- # { # "schemaVersion": 1, # "packages": [ # { # "name": "myapp", # "version": "2.0.0", # "description": "My App", # "displayName": "My App", # "publishedAt": "2026-01-01T00:00:00Z", # "enabled": true, // false = skip without error # "url": "https://cdn.oxmc.me/apps/windows/myapp-2.0.0.exe", # "sha256": "abcdef...", # "type": "exe", // exe | msi | msix | ps1 | zip # "args": ["/quiet"], // extra installer args (exe/msi) # "installPath": null, // required for zip type # "minBuild": 22000, // optional: skip below this Windows build # "maxBuild": null, // optional: skip above this Windows build # "restartRequired": false, // log restart reminder after install # "checkInstalled": { // optional: detect existing install # "type": "registry", // registry | file | appx # "key": "HKLM:\\SOFTWARE\\...", # "value": "ValueName", // registry only # "match": "^2\\." // optional regex against value data # } # // file type: { "type": "file", "path": "C:\\Program Files\\..." } # // appx type: { "type": "appx", "packageFamilyName": "Publisher.App_xyz" } # }, # { # "name": "my-app-bundle", # "version": "1.0.0", # "type": "packagelist", // download + install each item individually # "enabled": true, # "minBuild": null, # "maxBuild": null, # "items": [ # { # "name": "firefox", # "url": "https://cdn.oxmc.me/...", # "sha256": "abcdef...", # "type": "exe", # "args": ["-ms"], # "installPath": null, # "conditions": { "minBuild": 10240, "editions": ["Pro","Enterprise"] }, # "checkInstalled": { "type": "registry", "key": "HKLM:\\...", "value": "CurrentVersion", "match": "." } # } # ] # }, # { # "name": "my-app", // variant: pick installer per OS/arch # "version": "2.0.0", # "enabled": true, # "minBuild": null, // outer gate runs before variant selection # "maxBuild": null, # "variants": [ # { # "displayName": "My App (Win11 x64)", # "conditions": { "minBuild": 22000, "architecture": "x64" }, # "url": "https://cdn.oxmc.me/...", # "sha256": "abcdef...", # "type": "exe", # "args": ["/quiet"], # "installPath": null, # "checkInstalled": null # }, # { # "displayName": "My App (Win10 x64)", # "conditions": { "minBuild": 10240, "maxBuild": 19045, "architecture": "x64" }, # "url": "https://cdn.oxmc.me/...", # "sha256": "abcdef...", # "type": "exe", # "args": ["/quiet"], # "installPath": null, # "checkInstalled": null # } # ] # // conditions fields: minBuild, maxBuild, editions (array), architecture (x64|x86|arm64) # // first matching variant wins; no match = skip package # } # ] # } # ------------------------------------------------------------------------- Set-StrictMode -Off $ErrorActionPreference = "Stop" # -- Path setup ---------------------------------------------------------------- if ($PSVersionTable.PSVersion.Major -ge 3) { $currentDir = $PSScriptRoot } else { $currentDir = (Get-Item .).FullName } . "$currentDir\script-helper.ps1" $windowsDrive = Split-Path $env:SystemRoot -Qualifier $oemRoot = "$windowsDrive\Windows\OEM" $stateFile = "$oemRoot\update-state.json" $tempDir = "$env:TEMP\oxmc-updates" $logFile = "$windowsDrive\Windows\Setup\PrePostInstall\Logs\UpdatePackages-$(Get-Date -Format 'yyyyMMdd_HHmmss').log" # -- Logging ------------------------------------------------------------------- function Write-Log { param([string]$Msg, [string]$Level = "INFO") $line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Msg" Write-Host $line Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue } function Write-LogWarn { param([string]$Msg) Write-Log $Msg "WARN" } function Write-LogError { param([string]$Msg) Write-Log $Msg "ERROR" } # -- Admin check --------------------------------------------------------------- if (-not (Test-Administrator)) { Write-Host "Restarting as administrator..." $argStr = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" Start-Process PowerShell -ArgumentList $argStr -Verb RunAs exit } # -- State helpers ------------------------------------------------------------- function Read-State { if (Test-Path $stateFile) { try { return Get-Content $stateFile -Raw | ConvertFrom-Json } catch {} } return [PSCustomObject]@{ isoPackage = $null; packages = [PSCustomObject]@{} } } function Save-State { param([PSCustomObject]$State) $State | ConvertTo-Json -Depth 10 | Set-Content $stateFile -Encoding UTF8 } function Get-InstalledVersion { param([PSCustomObject]$State, [string]$Name, [string]$Type = "package") if ($Type -eq "isoPackage") { return if ($State.isoPackage) { $State.isoPackage.version } else { $null } } $pkg = $State.packages."$Name" return if ($pkg) { $pkg.version } else { $null } } function Set-InstalledVersion { param([PSCustomObject]$State, [string]$Name, [string]$Version, [string]$Type = "package") if ($Type -eq "isoPackage") { $State.isoPackage = [PSCustomObject]@{ version = $Version; installedAt = (Get-Date -Format "o") } } else { $State.packages | Add-Member -MemberType NoteProperty -Name $Name ` -Value ([PSCustomObject]@{ version = $Version; installedAt = (Get-Date -Format "o") }) ` -Force } } # -- Version comparison -------------------------------------------------------- function Compare-Versions { param([string]$Installed, [string]$Available) if (-not $Installed) { return $true } try { $i = [Version]$Installed $a = [Version]$Available return $a -gt $i } catch { return $Installed -ne $Available } } # -- Condition evaluator ------------------------------------------------------- # $Conditions object fields (all optional): # minBuild int minimum Windows build (inclusive) # maxBuild int maximum Windows build (inclusive) # editions array e.g. ["Pro","Enterprise"] - null means any # architecture string "x64" | "x86" | "arm64" - null means any function Test-Conditions { param([PSCustomObject]$Conditions) if (-not $Conditions) { return $true } $build = Get-WindowsBuildNumber if ($Conditions.minBuild -and $build -lt [int]$Conditions.minBuild) { return $false } if ($Conditions.maxBuild -and $build -gt [int]$Conditions.maxBuild) { return $false } if ($Conditions.editions) { $edition = Get-WindowsEdition $hit = $Conditions.editions | Where-Object { $_ -ieq $edition } if (-not $hit) { return $false } } if ($Conditions.architecture) { $arch = Get-NativeArchitecture if ($Conditions.architecture -ine $arch) { return $false } } return $true } # -- Check if package is already installed on the system ----------------------- function Test-PackageInstalled { param([PSCustomObject]$Check) if (-not $Check) { return $false } switch ($Check.type.ToLower()) { "registry" { try { $val = (Get-ItemProperty -Path $Check.key -Name $Check.value -ErrorAction Stop).$($Check.value) if ($Check.match) { return [bool]($val -match $Check.match) } return $true } catch { return $false } } "file" { return (Test-Path $Check.path) } "appx" { try { $pkg = Get-AppxPackage -Name "$($Check.packageFamilyName)*" -ErrorAction SilentlyContinue return ($null -ne $pkg) } catch { return $false } } default { Write-LogWarn "Unknown checkInstalled type '$($Check.type)' - skipping check." return $false } } } # -- Download + verify --------------------------------------------------------- function Invoke-Download { param([string]$Url, [string]$Dest, [string]$Sha256) Write-Log "Downloading: $Url" $null = New-Item -ItemType Directory -Path (Split-Path $Dest) -Force Invoke-WebRequest -Uri $Url -OutFile $Dest -UseBasicParsing -ErrorAction Stop if ($Sha256) { $actual = (Get-FileHash $Dest -Algorithm SHA256).Hash if ($actual -ne $Sha256.ToUpper()) { Remove-Item $Dest -Force throw "SHA256 mismatch for $Dest`nExpected : $Sha256`nActual : $actual" } Write-Log "SHA256 verified OK." } else { Write-LogWarn "No SHA256 in manifest for $(Split-Path $Dest -Leaf) - skipping hash check." } } # -- ISO package installer ----------------------------------------------------- function Install-IsoPackage { param([PSCustomObject]$Pkg, [PSCustomObject]$State) $installPath = if ($Pkg.installPath) { $Pkg.installPath } else { $oemRoot } Write-Log "==> ISO package: $($Pkg.name) v$($Pkg.version)" Write-Log " Install path : $installPath" $zipPath = "$tempDir\iso-pkg-$($Pkg.version).zip" $extractDir = "$tempDir\iso-pkg-extract" try { Invoke-Download -Url $Pkg.url -Dest $zipPath -Sha256 $Pkg.sha256 # Clean previous extract if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force } New-Item -ItemType Directory -Path $extractDir | Out-Null Write-Log "Extracting package..." Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force # Copy all files from extract root into installPath, preserving structure Write-Log "Applying files to: $installPath" $null = New-Item -ItemType Directory -Path $installPath -Force Copy-Item "$extractDir\*" -Destination $installPath -Recurse -Force # Run post-install script if present if ($Pkg.postInstallScript) { $postScript = Join-Path $installPath $Pkg.postInstallScript if (Test-Path $postScript) { Write-Log "Running post-install script: $postScript" & PowerShell -NoProfile -ExecutionPolicy Bypass -File $postScript if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { Write-LogWarn "Post-install script exited with code $LASTEXITCODE" } } else { Write-LogWarn "postInstallScript '$($Pkg.postInstallScript)' not found after extraction." } } Set-InstalledVersion -State $State -Name $Pkg.name -Version $Pkg.version -Type "isoPackage" Write-Log "ISO package installed successfully." } finally { Remove-Item $zipPath -Force -ErrorAction SilentlyContinue Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue } } # -- Core download-and-run for a single installable item ----------------------- # Used by both Install-Package (single) and Install-Package (packagelist items). # $Item must have: name, url, type, sha256 (optional), args (optional), # installPath (required for zip), checkInstalled (optional) function Invoke-PackageInstall { param([PSCustomObject]$Item, [string]$Label = $Item.name) # Per-item conditions: skip if current system doesn't match if ($Item.conditions -and -not (Test-Conditions -Conditions $Item.conditions)) { Write-Log " [$Label] Conditions not met - skipping." return } # Per-item checkInstalled: skip if already present on system if ($Item.checkInstalled -and (Test-PackageInstalled -Check $Item.checkInstalled)) { Write-Log " [$Label] Already installed (check passed) - skipping." return } $ext = switch ($Item.type.ToLower()) { "exe" { ".exe" } "msi" { ".msi" } "msix" { ".msix" } "ps1" { ".ps1" } "zip" { ".zip" } default { throw "Unknown package type: $($Item.type)" } } $destFile = "$tempDir\$Label$ext" $extraArgs = if ($Item.args) { $Item.args } else { @() } try { Invoke-Download -Url $Item.url -Dest $destFile -Sha256 $Item.sha256 switch ($Item.type.ToLower()) { "exe" { Write-Log " [$Label] Running installer..." Start-Process $destFile -ArgumentList $extraArgs -Wait -NoNewWindow if ($LASTEXITCODE -and $LASTEXITCODE -notin @(0, 3010)) { throw "Installer exited with code $LASTEXITCODE" } } "msi" { Write-Log " [$Label] Running msiexec..." $msiArgs = @("/i", $destFile, "/quiet", "/norestart") + $extraArgs Start-Process msiexec.exe -ArgumentList $msiArgs -Wait -NoNewWindow if ($LASTEXITCODE -and $LASTEXITCODE -notin @(0, 3010)) { throw "msiexec exited with code $LASTEXITCODE" } } "msix" { Write-Log " [$Label] Installing MSIX/Appx..." Add-AppxPackage -Path $destFile -ErrorAction Stop } "ps1" { Write-Log " [$Label] Running PowerShell script..." & PowerShell -NoProfile -ExecutionPolicy Bypass -File $destFile @extraArgs if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "Script exited with code $LASTEXITCODE" } } "zip" { if (-not $Item.installPath) { throw "Package type 'zip' requires installPath." } Write-Log " [$Label] Extracting to $($Item.installPath)..." $null = New-Item -ItemType Directory -Path $Item.installPath -Force Expand-Archive -Path $destFile -DestinationPath $Item.installPath -Force } } Write-Log " [$Label] OK." } finally { Remove-Item $destFile -Force -ErrorAction SilentlyContinue } } # -- Individual package / packagelist installer -------------------------------- function Install-Package { param([PSCustomObject]$Pkg, [PSCustomObject]$State) Write-Log "==> Package: $($Pkg.name) v$($Pkg.version) [$($Pkg.type)]" # Outer build gate - quick reject before variant evaluation $build = Get-WindowsBuildNumber if ($Pkg.minBuild -and $build -lt [int]$Pkg.minBuild) { Write-Log " Skipping: requires build >= $($Pkg.minBuild) (current: $build)" return } if ($Pkg.maxBuild -and $build -gt [int]$Pkg.maxBuild) { Write-Log " Skipping: requires build <= $($Pkg.maxBuild) (current: $build)" return } # Variants: first matching variant wins; provides url/type/args/etc for this system if ($Pkg.variants -and $Pkg.variants.Count -gt 0) { $matched = $null foreach ($v in $Pkg.variants) { if (Test-Conditions -Conditions $v.conditions) { $matched = $v break } } if (-not $matched) { Write-Log " No variant matched current system - skipping." return } $label = if ($matched.displayName) { $matched.displayName } else { $Pkg.name } Write-Log " Variant: $label" Invoke-PackageInstall -Item $matched -Label $Pkg.name Set-InstalledVersion -State $State -Name $Pkg.name -Version $Pkg.version Write-Log " Installed OK." return } # packagelist: download and install each item individually if ($Pkg.type.ToLower() -eq "packagelist") { if (-not $Pkg.items -or $Pkg.items.Count -eq 0) { throw "packagelist '$($Pkg.name)' has no items." } $listFailed = 0 foreach ($item in $Pkg.items) { try { Invoke-PackageInstall -Item $item -Label $item.name } catch { Write-LogError " Item '$($item.name)' failed: $_" $listFailed++ } } if ($listFailed -gt 0) { throw "$listFailed item(s) in packagelist '$($Pkg.name)' failed." } Set-InstalledVersion -State $State -Name $Pkg.name -Version $Pkg.version Write-Log " PackageList installed OK." return } # Single package - delegate to shared helper Invoke-PackageInstall -Item $Pkg -Label $Pkg.name Set-InstalledVersion -State $State -Name $Pkg.name -Version $Pkg.version Write-Log " Installed OK." } # -- Entry point --------------------------------------------------------------- Write-Log "UpdatePackages-oxmc starting." Write-Log "OEM root : $oemRoot" Write-Log "State : $stateFile" $null = New-Item -ItemType Directory -Path $tempDir -Force $state = Read-State # -- 1. Fetch manifests -------------------------------------------------------- $internalManifestUrl = "https://cdn.oxmc.me/apps/windows/internal-manifest.json" $isoManifestUrl = "https://cdn.oxmc.me/apps/windows/iso-manifest.json" try { Write-Log "Fetching internal manifest..." $internalManifest = Get-ContentFromUrl -url $internalManifestUrl | ConvertFrom-Json } catch { Write-LogError "Failed to fetch internal manifest: $_" exit 1 } try { Write-Log "Fetching ISO manifest..." $isoManifest = Get-ContentFromUrl -url $isoManifestUrl | ConvertFrom-Json } catch { Write-LogError "Failed to fetch ISO manifest: $_" exit 1 } $updatesApplied = 0 $updatesFailed = 0 $restartNeeded = $false # -- 2. ISO package (C:\Windows\OEM contents) ---------------------------------- if ($isoManifest.package) { $pkg = $isoManifest.package $installedVer = Get-InstalledVersion -State $state -Name $pkg.name -Type "isoPackage" if (Compare-Versions -Installed $installedVer -Available $pkg.version) { Write-Log "ISO package update: $(if ($installedVer) { $installedVer } else { 'not installed' }) -> $($pkg.version)" try { Install-IsoPackage -Pkg $pkg -State $state Save-State -State $state $updatesApplied++ } catch { Write-LogError "ISO package install failed: $_" $updatesFailed++ } } else { Write-Log "ISO package $($pkg.name) is up to date ($installedVer)." } } else { Write-LogWarn "ISO manifest has no 'package' entry - skipping." } # -- 3. Internal packages ------------------------------------------------------ if ($internalManifest.packages) { foreach ($pkg in $internalManifest.packages) { # enabled check if ($pkg.PSObject.Properties['enabled'] -and $pkg.enabled -eq $false) { Write-Log "Package $($pkg.name) is disabled - skipping." continue } $installedVer = Get-InstalledVersion -State $state -Name $pkg.name if (Compare-Versions -Installed $installedVer -Available $pkg.version) { # No state entry: probe the live system before downloading if (-not $installedVer -and $pkg.checkInstalled -and (Test-PackageInstalled -Check $pkg.checkInstalled)) { Write-Log "Package $($pkg.name) detected as already installed - recording as $($pkg.version), skipping download." Set-InstalledVersion -State $state -Name $pkg.name -Version $pkg.version Save-State -State $state continue } Write-Log "Package update: $($pkg.name) $(if ($installedVer) { $installedVer } else { 'not installed' }) -> $($pkg.version)" try { Install-Package -Pkg $pkg -State $state Save-State -State $state $updatesApplied++ if ($pkg.restartRequired -eq $true) { $restartNeeded = $true } } catch { Write-LogError "Package '$($pkg.name)' failed: $_" $updatesFailed++ } } else { Write-Log "Package $($pkg.name) is up to date ($installedVer)." } } } else { Write-LogWarn "Internal manifest has no 'packages' array - skipping." } # -- Summary ------------------------------------------------------------------- Write-Log "Done. Applied: $updatesApplied Failed: $updatesFailed" if ($restartNeeded) { Write-Log "*** One or more packages require a restart to complete installation. ***" "WARN" } # Clean up temp dir if empty Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue if ($updatesFailed -gt 0) { Write-LogError "Some updates failed - check log: $logFile" exit 1 }