Skip to content
Closed
6 changes: 3 additions & 3 deletions .github/skills/author-winpe-profile-json/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Create profiles that configure `Invoke-WinpeStartup` without embedding workflow

## Profile location and discovery

- Store bundled profiles in `OSDCloud/core/winpestartup-profiles/`.
- Use a descriptive `.json` filename. The startup function discovers profile files from `WinpeStartup\Profiles` on attached drives.
- Store bundled profiles in a named folder under `OSDCloud/core/winpestartup-profiles/`, with the settings in `winpestartup.json`.
- Use a descriptive folder name. The startup function discovers `winpestartup.json` profile folders and legacy root-level JSON files from `WinpeStartup\Profiles` on attached drives.
- A profile is a flat map of parameter names to scalar values or arrays, with one supported nested object: the top-level `Environment` section.
- Prefer standard JSON. The loader tolerates comments, but comments are unnecessary and can make external validation fail.

Expand Down Expand Up @@ -108,7 +108,7 @@ Use an unprefixed top-level `Environment` object to set process-scoped environme
5. Parse the edited file with PowerShell 5.1:

```powershell
$profile = Get-Content -LiteralPath '.\OSDCloud\core\winpestartup-profiles\<profile>.json' -Raw | ConvertFrom-Json
$profile = Get-Content -LiteralPath '.\OSDCloud\core\winpestartup-profiles\<profile>\winpestartup.json' -Raw | ConvertFrom-Json
$profile | Out-Null
```

Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

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

## 26.10.6.1 - October 6, 2026

### Added

- Added WinpeStartup logging to `X:\Windows\Temp\winpestartup.log`; transcript start and stop failures warn without preventing startup.
- Import registry files from `X:\WinpeStartup\core\winpe-reg\*.reg` and certificates from `X:\WinpeStartup\core\winpe-root-cer\*.cer` during environment setup. Files are processed in sorted path order, and discovery, import, or launch failures warn and continue.
- Import process environment defaults from `X:\WinpeStartup\core\*\*.env`, including hidden `.env` files, in sorted path order before shell and selected-profile overrides. Entries use literal single-line assignments; invalid entries and file failures warn and continue without logging values.
- Added tests for profile discovery, environment defaults, startup logging, and registry and certificate imports.

### Changed

- WinpeStartup profile discovery now supports `<ProfileName>\winpestartup.json` folders while retaining support for legacy root-level JSON profiles.
- Module version bumped from `26.10.4.1` to `26.10.6.1`.

### Removed

- Removed the obsolete `OSDCloud German CLI` startup profile.

## 26.10.4.1 - October 4, 2026

### Changed
Expand Down
2 changes: 1 addition & 1 deletion OSDCloud/OSDCloud.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@{
# --- Identity ---
RootModule = 'OSDCloud.psm1'
ModuleVersion = '26.10.4.1'
ModuleVersion = '26.10.9.1'
CompatiblePSEditions = @('Core', 'Desktop')
GUID = '2fbd5c65-79c7-4561-9a2e-c4a4eebc89c7'

Expand Down
51 changes: 51 additions & 0 deletions OSDCloud/private/Get-WinpeStartupProfileCandidates.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
BeforeAll {
. "$PSScriptRoot\Get-WinpeStartupProfileCandidates.ps1"
}

Describe 'Get-WinpeStartupProfileCandidates' {
It 'discovers folder profiles and legacy root-level JSON files' {
Mock Test-Path {
if ($LiteralPath -eq 'C:\WinpeStartup\profiles') {
return $true
}
if ($LiteralPath -eq (Join-Path (Join-Path 'C:\WinpeStartup\profiles' 'BranchOffice') 'winpestartup.json')) {
return $true
}
return $false
}

Mock Get-ChildItem {
if ($File) {
return @(
[pscustomobject]@{
BaseName = 'Legacy'
FullName = (Join-Path 'C:\WinpeStartup\profiles' 'Legacy.json')
}
)
}
if ($Directory) {
return @(
[pscustomobject]@{
Name = 'BranchOffice'
FullName = (Join-Path 'C:\WinpeStartup\profiles' 'BranchOffice')
}
[pscustomobject]@{
Name = 'Empty'
FullName = (Join-Path 'C:\WinpeStartup\profiles' 'Empty')
}
)
}
} -ParameterFilter { $LiteralPath -eq 'C:\WinpeStartup\profiles' }

$profiles = @(Get-WinpeStartupProfileCandidates)

$profiles.Count | Should -Be 2
$profiles.Profile | Should -Contain 'BranchOffice'
$profiles.Profile | Should -Contain 'Legacy'
($profiles | Where-Object Profile -eq 'BranchOffice').Path |
Should -Be (Join-Path (Join-Path 'C:\WinpeStartup\profiles' 'BranchOffice') 'winpestartup.json')
($profiles | Where-Object Profile -eq 'Legacy').Path |
Should -Be (Join-Path 'C:\WinpeStartup\profiles' 'Legacy.json')
Should -Invoke Get-ChildItem -Times 2 -Exactly -ParameterFilter { $LiteralPath -eq 'C:\WinpeStartup\profiles' }
}
}
43 changes: 43 additions & 0 deletions OSDCloud/private/Get-WinpeStartupProfileCandidates.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
function Get-WinpeStartupProfileCandidates {
[CmdletBinding()]
param ()
Comment on lines +1 to +3

$candidateProfiles = [System.Collections.Generic.List[object]]::new()

foreach ($driveLetter in [char[]](67..90)) {
$profileRoot = '{0}:\WinpeStartup\profiles' -f $driveLetter

if (-not (Test-Path -LiteralPath $profileRoot -PathType Container)) {
continue
}

try {
$profileFiles = Get-ChildItem -LiteralPath $profileRoot -Filter '*.json' -File -ErrorAction Stop
$profileFolders = Get-ChildItem -LiteralPath $profileRoot -Directory -ErrorAction Stop

foreach ($profileFolder in $profileFolders) {
$profileFilePath = Join-Path $profileFolder.FullName 'winpestartup.json'
if (Test-Path -LiteralPath $profileFilePath -PathType Leaf) {
[void]$candidateProfiles.Add([pscustomobject]@{
Index = 0
Profile = $profileFolder.Name
Path = $profileFilePath
})
}
}

foreach ($profileFile in ($profileFiles | Sort-Object FullName)) {
[void]$candidateProfiles.Add([pscustomobject]@{
Index = 0
Profile = $profileFile.BaseName
Path = $profileFile.FullName
})
}
}
catch {
Write-Verbose "Invoke-WinpeStartup: Unable to enumerate '$profileRoot': $($_.Exception.Message)"
}
}

return $candidateProfiles.ToArray()
}
Loading
Loading