Skip to content

Commit

Permalink
Major improvements
Browse files Browse the repository at this point in the history
* Added application of edition specific settings XMLs
* Replaced usage of unreliable Use-WindowsUnattend with direct usage of DISM
  • Loading branch information
nekoppai authored Aug 1, 2022
1 parent b6859a3 commit 5615def
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 83 deletions.
257 changes: 175 additions & 82 deletions Set-WindowsCbsEdition.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

<#
.SYNOPSIS
Changes the current Windows edition
Changes the current Windows edition
.DESCRIPTION
Changes the current Windows edition to an edition provided in the parameter. Use the -GetTargetEditions parameter to retrieve the list of possible target editions for the system.
Changes the current Windows edition to an edition provided in the parameter. Use the -GetTargetEditions parameter to retrieve the list of possible target editions for the system.
.LINK
https://github.com/Gamers-Against-Weed/Set-WindowsCbsEdition
https://github.com/Gamers-Against-Weed/Set-WindowsCbsEdition
.PARAMETER SetEdition
Provides desired edition to change to.
Provides desired edition to change to.
.PARAMETER GetTargetEditions
Get target editions for the upgrade.
Get target editions for the upgrade.
.PARAMETER StageCurrent
Sets the script to stage the current edition instead of removing it.
Sets the script to stage the current edition instead of removing it.
#>

#Requires -RunAsAdministrator

Param (
param (
[Parameter()]
[String]$SetEdition,

Expand All @@ -50,123 +50,216 @@ Param (
)

function Get-AssemblyIdentity {
param (
[String]$PackageName
)
param (
[String]$PackageName
)

$PackageName = [String]$PackageName
$packageData = ($PackageName -split '~')

if($packageData[3] -eq '') {
$packageData[3] = 'neutral'
}

return "<assemblyIdentity name=`"$($packageData[0])`" version=`"$($packageData[4])`" processorArchitecture=`"$($packageData[2])`" publicKeyToken=`"$($packageData[1])`" language=`"$($packageData[3])`" />"
}

$PackageName = [String]$PackageName
$packageData = ($PackageName -split '~')
function Get-SxsName {
param (
[String]$PackageName
)

$name = ($PackageName -replace '[^A-z0-9\-\._]', '')

if($packageData[3] -eq '') {
$packageData[3] = 'neutral'
}
if($name.Length -gt 40) {
$name = ($name[0..18] -join '') + '\.\.' + ($name[-19..-1] -join '')
}

Return "<assemblyIdentity name=`"$($packageData[0])`" version=`"$($packageData[4])`" processorArchitecture=`"$($packageData[2])`" publicKeyToken=`"$($packageData[1])`" language=`"$($packageData[3])`" />"
return $name.ToLower()
}

function Find-EditionXmlInSxs {
param (
[String]$Edition
)

$candidates = @($Edition, 'Client', 'Server')
$winSxs = $Env:SystemRoot + '\WinSxS'
$allInSxs = Get-ChildItem -Path $winSxs | select Name

foreach($candidate in $candidates) {
$name = Get-SxsName -PackageName "Microsoft-Windows-Editions-$candidate"
$packages = $allInSxs | where name -Match ('^.*_'+$name+'_31bf3856ad364e35')

if($packages.Length -eq 0) {
continue
}

$package = $packages[-1].Name
$testPath = $winSxs + "\$package\" + $Edition + 'Edition.xml'

if(Test-Path -Path $testPath -PathType Leaf) {
return $testPath
}
}

return $null
}

function Find-EditionXml {
param (
[String]$Edition
)

$servicingEditions = $Env:SystemRoot + '\servicing\Editions'
$editionXml = $Edition + 'Edition.xml'

$editionXmlInServicing = $servicingEditions + '\' + $editionXml

if(Test-Path -Path $editionXmlInServicing -PathType Leaf) {
return $editionXmlInServicing
}

return Find-EditionXmlInSxs -Edition $Edition
}

function Write-UpgradeCandidates {
param (
[HashTable]$InstallCandidates
)

$editionCount = 0
Write-Host 'Editions that can be upgraded to:'
foreach($candidate in $InstallCandidates.Keys) {
Write-Host "Target Edition : $candidate"
$editionCount = $editionCount + 1
}

if($editionCount -eq 0) {
Write-Host '(no editions are available)'
}
param (
[HashTable]$InstallCandidates
)

$editionCount = 0
Write-Host 'Editions that can be upgraded to:'
foreach($candidate in $InstallCandidates.Keys) {
Write-Host "Target Edition : $candidate"
$editionCount++
}

if($editionCount -eq 0) {
Write-Host '(no editions are available)'
}
}

function Write-UpgradeXml {
param (
param (
[Array]$RemovalCandidates,
[Array]$InstallCandidates,
[Boolean]$Stage
)

$removeAction = 'remove'
if($Stage) {
$removeAction = 'stage'
}

Write-Output '<?xml version="1.0"?>'
Write-Output '<unattend xmlns="urn:schemas-microsoft-com:unattend">'
Write-Output '<servicing>'

foreach($package in $InstallCandidates) {
Write-Output '<package action="install">'
Write-Output (Get-AssemblyIdentity -PackageName $package)
Write-Output '</package>'
}

foreach($package in $RemovalCandidates) {
Write-Output "<package action=`"$removeAction`">"
Write-Output (Get-AssemblyIdentity -PackageName $package)
Write-Output '</package>'
}

Write-Output '</servicing>'
Write-Output '</unattend>'
$removeAction = 'remove'
if($Stage) {
$removeAction = 'stage'
}

Write-Output '<?xml version="1.0"?>'
Write-Output '<unattend xmlns="urn:schemas-microsoft-com:unattend">'
Write-Output '<servicing>'

foreach($package in $InstallCandidates) {
Write-Output '<package action="install">'
Write-Output (Get-AssemblyIdentity -PackageName $package)
Write-Output '</package>'
}

foreach($package in $RemovalCandidates) {
Write-Output "<package action=`"$removeAction`">"
Write-Output (Get-AssemblyIdentity -PackageName $package)
Write-Output '</package>'
}

Write-Output '</servicing>'
Write-Output '</unattend>'
}

function Write-Usage {
Get-Help $PSCommandPath -detailed
Get-Help $PSCommandPath -detailed
}

$version = '1.0'
$getTargetsParam = $GetTargetEditions.IsPresent
$stageCurrentParam = $StageCurrent.IsPresent

if($SetEdition -eq '' -and ($false -eq $getTargetsParam)) {
Write-Usage
Exit 1
Write-Usage
Exit 1
}

Write-Host @"
============================================================
Set-WindowsCbsEdition $version
https://github.com/Gamers-Against-Weed/Set-WindowsCbsEdition
============================================================
"@ -ForegroundColor Cyan -BackgroundColor Black

$removalCandidates = @();
$installCandidates = @{};

$packages = Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages' | select Name | where name -Match '^.*\\Microsoft-Windows-.*Edition~'
foreach($package in $packages) {
$state = (Get-ItemProperty -Path "Registry::$($package.Name)").CurrentState
$packageName = ($package.Name -split '\\')[-1]
$packageEdition = (($packageName -split 'Edition~')[0] -split 'Microsoft-Windows-')[-1]

if($state -eq 0x40) {
if($null -eq $installCandidates[$packageEdition]) {
$installCandidates[$packageEdition] = @()
}

if($false -eq ($packageName -in $installCandidates[$packageEdition])) {
$installCandidates[$packageEdition] = $installCandidates[$packageEdition] + @($packageName)
}
}

if((($state -eq 0x50) -or ($state -eq 0x70)) -and ($false -eq ($packageName -in $removalCandidates))) {
$removalCandidates = $removalCandidates + @($packageName)
}
$state = (Get-ItemProperty -Path "Registry::$($package.Name)").CurrentState
$packageName = ($package.Name -split '\\')[-1]
$packageEdition = (($packageName -split 'Edition~')[0] -split 'Microsoft-Windows-')[-1]

if($state -eq 0x40) {
if($null -eq $installCandidates[$packageEdition]) {
$installCandidates[$packageEdition] = @()
}

if($false -eq ($packageName -in $installCandidates[$packageEdition])) {
$installCandidates[$packageEdition] = $installCandidates[$packageEdition] + @($packageName)
}
}

if((($state -eq 0x50) -or ($state -eq 0x70)) -and ($false -eq ($packageName -in $removalCandidates))) {
$removalCandidates = $removalCandidates + @($packageName)
}
}

if($getTargetsParam) {
Write-UpgradeCandidates -InstallCandidates $installCandidates
Exit
Write-UpgradeCandidates -InstallCandidates $installCandidates
Exit
}

if($false -eq ($SetEdition -in $installCandidates.Keys)) {
Write-Error "The system cannot be upgraded to `"$SetEdition`""
Exit 1
Write-Error "The system cannot be upgraded to `"$SetEdition`""
Exit 1
}

$xmlPath = $Env:Temp+'\CbsUpgrade.xml'
$xmlPath = $Env:Temp + '\CbsUpgrade.xml'

Write-UpgradeXml -RemovalCandidates $removalCandidates `
-InstallCandidates $installCandidates[$SetEdition] `
-Stage $stageCurrentParam >$xmlPath
-InstallCandidates $installCandidates[$SetEdition] `
-Stage $stageCurrentParam >$xmlPath

$editionXml = Find-EditionXml -Edition $SetEdition
if($null -eq $editionXml) {
Write-Warning 'Unable to find edition specific settings XML. Proceeding without it...'
}

Write-Host 'Starting the upgrade process. This may take a while...'

Use-WindowsUnattend -UnattendPath $xmlPath -NoRestart -Online -ErrorAction Stop
DISM.EXE /English /NoRestart /Online /Apply-Unattend:$xmlPath
$dismError = $LASTEXITCODE

Remove-Item -Path $xmlPath -Force

if(($dismError -ne 0) -and ($dismError -ne 3010)) {
Write-Error 'Failed to upgrade to the target edition'
Exit $dismError
}

if($null -ne $editionXml) {
$destination = $Env:SystemRoot + '\' + $SetEdition + '.xml'
Copy-Item -Path $editionXml -Destination $destination

DISM.EXE /English /NoRestart /Online /Apply-Unattend:$editionXml
$dismError = $LASTEXITCODE

if(($dismError -ne 0) -and ($dismError -ne 3010)) {
Write-Error 'Failed to apply edition specific settings'
Exit $dismError
}
}

Restart-Computer
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ Set-WindowsCbsEdition.ps1 [-SetEdition Edition] [-GetTargetEditions] [-StageCurr
```

Parameters explanation:
* `SetEdition` - Set edition the the provided one
* `SetEdition` - Set edition to the provided one
* `GetTargetEditions` - Get a list of target editions for the system
* `StageCurrent` - Sets the script to stage the current edition instead of removing it

After a successful edition switch the script will immediately reboot the machine.

License
-------
The project is licensed under the terms of the GNU General Public License v3.0

0 comments on commit 5615def

Please sign in to comment.