Skip to content

Instantly share code, notes, and snippets.

@Fred-Vatin
Last active August 13, 2025 14:43
Show Gist options
  • Save Fred-Vatin/87281696b171ec09ef97e1200f11c6d4 to your computer and use it in GitHub Desktop.
Save Fred-Vatin/87281696b171ec09ef97e1200f11c6d4 to your computer and use it in GitHub Desktop.
Backup/Restore PhoenixPE/PEBakery scripts state
# backup-restore.ps1
# Initial idea from Peter Siering, c't, 11/2022: https://github.com/PhoenixPE/PhoenixPE/issues/13#issuecomment-1371405100
# @Fred-Vatin, rewritten 2025-08-07: https://gist.github.com/Fred-Vatin/87281696b171ec09ef97e1200f11c6d4
#
# This script helps to read and write selected state of each script within a PhoenixPE project.
#
# Put this script in a backup folder at the same level of the Projects folder.
#
# Should run on every platform with pwsh (not tested).
#
# If called without parameter it prints the selected variables in console for all script files in projects folder
# Example: .\backup-restore.ps1
#
# If called with -backcup, it saves the output in "CustomSettings.sel" file at the same level of this script
# Example: .\backup-restore.ps1 -backup
#
# If called with -restore, it writes the selected variables for all scripts mentioned in "CustomSettings.sel"
# Example: .\backup-restore.ps1 -restore
#
# You can saved your settings into another file name if you use -backup -name "my custom file name".
# You can restore your settings from a specific file name if you use -restore -name "my custom file name".
# The file name will be saved in the same directory as this script with the .sel extension.
# This allows to save/restore multiple use cases.
# $PSDefaultParameterValues['Out-File:Encoding'] = 'oem'
param(
[switch]$backup,
[switch]$restore,
[string]$name,
[switch]$test
)
<#*==========================================================================
* ℹ FUNCTIONS
===========================================================================#>
function Test-ValidWindowsFileName {
param (
[string]$name
)
# Check if the string is empty or null
if ([string]::IsNullOrWhiteSpace($name)) {
Write-Error "The name is empty or null."
return $false
}
# Check the length (max 255 characters for file name)
if ($name.Length -gt 255) {
Write-Error "The name exceeds 255 characters."
return $false
}
# Check for forbidden characters (<, >, :, ", /, , |, ?, *)
$invalidChars = '[<>:"/\\|?*]'
if ($name -match $invalidChars) {
Write-Error "The name contains forbidden characters ($invalidChars)."
return $false
}
# Check that it is not a path (no \ or /)
if ($name -match '[\\/]') {
Write-Error "The name contains path separators (\ or /)."
return $false
}
# Check Windows reserved names (e.g. CON, PRN, AUX, NUL, COM1, LPT1, etc.)
$reservedNames = @("CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9")
if ($reservedNames -contains $name.ToUpper()) {
Write-Error "'$name' is a reserved name by Windows."
return $false
}
# If all the checks pass
# Write-Host "The '$name' is a valid filename for Windows." -ForegroundColor Green
return $true
}
# Function to get selected variables
function Get-SelectedVariables {
$result = @("CTMZ")
Get-ChildItem "$projects" -Recurse -Filter *.script |
ForEach-Object {
$output = $null
foreach ($line in Get-Content $_.FullName) {
if ($line -imatch "^selected=(\w+)") {
$output = $matches[1]
}
}
if ($output) {
$relativePath = $_.FullName.replace($projects, '').SubString(1)
"$relativePath`:Selected=$output"
}
} | ForEach-Object { $result += $_ }
return $result
}
<#*==========================================================================
* ℹ LOGIC
===========================================================================#>
# Get script folder path
$scriptPath = $PSScriptRoot
$settingsfile = Join-Path -Path $scriptPath -ChildPath "CustomSettings.sel"
# Get path from one level higher and save it in $baseDir
$baseDir = Split-Path -Path $scriptPath -Parent
# get projects dir path
$projects = Join-Path -Path $baseDir -ChildPath "Projects"
if (!(Test-Path $projects)) {
Write-Error "Projects dir not found in : `"$baseDir`". Nothing can be saved or restored"
exit 1
}
# Check for parameters
$backup = $PSBoundParameters.ContainsKey('backup')
$restore = $PSBoundParameters.ContainsKey('restore')
$hasname = $PSBoundParameters.ContainsKey('name')
$test = $PSBoundParameters.ContainsKey('test')
# Check if in valid project directory
if (!$test) {
if (!((Test-Path (Join-Path -Path $projects -ChildPath "MyApps")) -and (Test-Path (Join-Path -Path $projects -ChildPath "PhoenixPE")))) {
Write-Error "`"$projects`" doesn’t contain the required PhoenixPE directory"
exit 1
}
}
if ($hasname) {
if (!(Test-ValidWindowsFileName($name))) {
Write-Error "$name is not a valid value for -name"
exit 1
}
# get custom settings file
$customSetting = Join-Path -Path $scriptPath -ChildPath "$name.sel"
if ($restore) {
if (!(Test-Path $customSetting)) {
Write-Error "`"$name.sel`" can not be found in `"$scriptPath`""
exit 1
}
}
$settingsfile = $customSetting
}
# No parameters: print selected variables to console
if (!$backup -and !$restore) {
Get-SelectedVariables
exit 0
}
# Backup mode: save selected variables to CustomSettings.sel
if ($backup) {
Get-SelectedVariables | Out-File -FilePath $settingsfile
Write-Host "`nData saved in: $settingsfile" -ForegroundColor Cyan -NoNewline
exit 0
}
# Restore mode: apply settings from CustomSettings.sel
if ($restore) {
if (!(Test-Path "$settingsfile")) {
Write-Error "Custom settings file doesn’t not exist. Nothing to restore."
exit 1
}
# Settings file provided: apply settings from specified file
$filecount = 0
$signaturefound = $false
foreach ($setting in Get-Content $settingsfile) {
if ($setting -eq "CTMZ") {
$signaturefound = $true
continue
}
if ($signaturefound) {
$filecount++
($path, $state) = $setting.Split(':', 2)
$path = Join-Path -Path "$projects" -ChildPath $path
($selname, $selval) = $state.Split('=', 2)
if (Test-Path $path) {
(Get-Content -Raw $path) |
ForEach-Object { $_ -replace "$selname=(.+)", "$selname=$selval" } |
Set-Content -NoNewline $path
Write-Output "Edited: $path"
}
else {
Write-Warning "File not found: $path"
}
}
else {
Write-Error "The settings file doesn’t contain a valid signature"
exit 1
}
}
Write-Host "`n$filecount files processed" -ForegroundColor Cyan
exit 0
}
@Whatslp
Copy link

Whatslp commented Aug 13, 2025

I modified the script so that it only modifies PhoenixPE-scripts during settings recovery if the settings are different. Otherwise, all scripts get a new timestamp. It also returns a summary of the modifications.

# peselsetter.ps1
# https://github.com/PhoenixPE/PhoenixPE/issues/13

# backup all settings
# powershell -ExecutionPolicy Unrestricted -f .\peselsetter.ps1 > default.sel
# restore all settings
# powershell -ExecutionPolicy Unrestricted -f .\peselsetter.ps1 .\default.sel

# Peter Siering, c't, 11/2022
#
# this script helps to read and write script variables 
# within a PhoenixPE project; it avoids commiting those
# variable settings within a git project
#
# should run on every platform with pwsh (even CI/CD) 
# 
# if called without parameter it prints the selected
# variable for all script files in project, call in 
# Windows with:
#
# ! wrong ! powershell -ExecutionPolicy Unrestricted -f .\peselsetter.ps1 | sc default.sel
#
# piping through sc avoids utf8-bom-codings, so generated
# files are recognized as text in GitLab & Co.
#
# if called with a filename as parameter it writes 
# the selected variables for all scripts mentioned in file
#
# powershell -ExecutionPolicy Unrestricted -f .\peselsetter.ps1 .\default.sel
#
# (edited) output should be used as input; script writes
# and expects a signature in file; if missing, nothing gets
# written
# 

$PSDefaultParameterValues['Out-File:Encoding'] = 'oem'

# check if in valid project directory
if (!((Test-Path( "MyApps")) -and (Test-Path( "PhoenixPE")))) {
    Write-Error "Working directory has be root of PhoenixPE project"
    Exit
}

# check if specified settings file exists
$settingsfile= $args[0]
if (($settingsfile) -and !(Test-Path( $settingsfile))) {
    Write-Error "Must specify existing settings file"
    Exit
}

# if no file specify output selected variable for all scripts
if (!($settingsfile)) {
    # write signature
    "CTMZ"
    # Get-ChildItem "." -Recurse -Filter *.script | 
	# echo \\?\$PWD
    Get-ChildItem "\\?\$PWD" -Recurse -Filter *.script | 
    Foreach-Object {
        foreach ($line in Get-Content $_.FullName) {
            if ( $line -imatch "^selected=(\w+)") {
				# $line
                $output=$matches[1]
            }
        }  
        $output= $_.FullName.replace( '\\?\'+$PWD,'').SubString(1)+ ":Selected="+ $output
        $output
    }
    Exit
}

# cycle through all scripts and set selected variable
# todo: count replacements
foreach ( $setting in Get-Content $settingsfile) {
    # check signature
    if ( $setting -eq "CTMZ") {
        $signaturefound="true"
        continue
    }
    $filecount++
    ($path,$varBackup)=$setting.Split(':', 2)
    # be sure to use right path seperator for platform running on by Join-Path
    $path=Join-Path -Path "." -ChildPath $path
    if ( $signaturefound) {
        ($varCfgName,$varBackupValue)=$varBackup.Split('=', 2)
		foreach ($line in Get-Content $path) {
			if ( $line -imatch "^selected=(\w+)") {
				$scriptSelected=$matches[1]
				
				if ($scriptSelected -ne $varBackupValue) {
					(Get-Content -Raw $path) | ForEach-Object { $_ -replace "$varCfgName=(.+)","$varCfgName=$varBackupValue"} | Set-Content -NoNewLine $path
					"$setting"
					"$path"
					"$scriptSelected => $varBackupValue"
				}
				continue
			}
		}  
	}
}
" $filecount files processed"


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment