# UndoFilteredFromSunshineConfig.ps1
param(
# Used by Helpers.ps1 (and for filtering undo commands)
[Parameter(Mandatory = $true)]
[Alias("n")]
[string]$ScriptName,
# When this switch is not present, the script will relaunch itself detached via WMI.
[Switch]$Detached
)
# If not already running detached, re-launch self via WMI and exit.
if (-not $Detached) {
# Get the full path of this script.
$scriptPath = $MyInvocation.MyCommand.Definition
# Build the command line; note that we add the -Detached switch.
$command = "powershell.exe -ExecutionPolicy Bypass -File `"$scriptPath`" -ScriptName `"$ScriptName`" -Detached"
Write-Host "Launching detached instance via WMI: $command"
# Launch using WMI Create process.
([wmiclass]"\\.\root\cimv2:Win32_Process").Create($command) | Out-Null
exit
}
# Now we are running in detached mode.
# Set the working directory to this script's folder.
$path = (Split-Path $MyInvocation.MyCommand.Path -Parent)
Set-Location $path
# Load helper functions (assumes Helpers.ps1 exists in the same folder)
. .\Helpers.ps1 -n $ScriptName
# Load settings (this function should be defined in Helpers.ps1)
$settings = Get-Settings
# Define a unique, system-wide mutex name.
$mutexName = "Global\SunshineUndoMutex"
$createdNew = $false
$mutex = New-Object System.Threading.Mutex($true, $mutexName, [ref] $createdNew)
if (-not $createdNew) {
Write-Host "Undo process already in progress or executed. Exiting..."
exit
}
try {
Write-Host "Acquired mutex. Running undo process..."
# Retrieve the list of script names from settings.
$desiredNames = $settings.installationOrderPreferences.scriptNames
if (-not $desiredNames) {
Write-Error "No script names defined in settings.installationOrderPreferences.scriptNames."
exit 1
}
# Create a hashtable to store unique commands by their undo command
$commandHashMap = @{}
# Function to read global_prep_cmd from a config file
function Get-GlobalPrepCommands {
param (
[string]$ConfigPath
)
if (-not $ConfigPath -or -not (Test-Path $ConfigPath)) {
Write-Host "Config path not found or not specified: $ConfigPath"
return @()
}
try {
$configContent = Get-Content -Path $ConfigPath -Raw
if ($configContent -match 'global_prep_cmd\s*=\s*(\[[^\]]+\])') {
$jsonText = $matches[1]
try {
$commands = $jsonText | ConvertFrom-Json
if (-not ($commands -is [System.Collections.IEnumerable])) {
$commands = @($commands)
}
return $commands
}
catch {
Write-Error "Failed to parse global_prep_cmd JSON from $ConfigPath`: $_"
return @()
}
}
else {
Write-Host "No valid 'global_prep_cmd' entry found in $ConfigPath."
return @()
}
}
catch {
Write-Error "Unable to read config file at '$ConfigPath'. Error: $_"
return @()
}
}
# Get commands from Sunshine config if available
if ($settings.sunshineConfigPath) {
Write-Host "Reading commands from Sunshine config: $($settings.sunshineConfigPath)"
$sunshineCommands = Get-GlobalPrepCommands -ConfigPath $settings.sunshineConfigPath
foreach ($cmd in $sunshineCommands) {
if ($cmd.undo) {
# Use the undo command as the key to avoid duplicates
$commandHashMap[$cmd.undo] = $cmd
}
}
}
# Get commands from Apollo config if available
if ($settings.apolloConfigPath) {
Write-Host "Reading commands from Apollo config: $($settings.apolloConfigPath)"
$apolloCommands = Get-GlobalPrepCommands -ConfigPath $settings.apolloConfigPath
foreach ($cmd in $apolloCommands) {
if ($cmd.undo) {
# This will overwrite any duplicate keys from Sunshine config
$commandHashMap[$cmd.undo] = $cmd
}
}
}
# Convert the hashtable values back to an array
$allPrepCommands = @($commandHashMap.Values)
Write-Host "Total unique commands found: $($allPrepCommands.Count)"
# Filter the commands to only include those matching our desired script names
$filteredCommands = @()
foreach ($name in $desiredNames) {
$regexName = [regex]::Escape($name)
$matchesForName = $allPrepCommands | Where-Object { $_.undo -match $regexName }
if ($matchesForName) {
$filteredCommands += $matchesForName
}
}
if (-not $filteredCommands) {
Write-Host "No matching undo commands found for the desired script names. Exiting."
exit 0
}
# Order the commands in reverse of the installation order
$desiredNamesReversed = $desiredNames.Clone()
[Array]::Reverse($desiredNamesReversed)
$finalCommands = @()
foreach ($name in $desiredNamesReversed) {
$cmdForName = $filteredCommands | Where-Object { $_.undo -match [regex]::Escape($name) }
if ($cmdForName) {
# Add all matching commands (if more than one per script)
$finalCommands += $cmdForName
}
}
# Execute the filtered undo commands synchronously
Write-Host "Starting undo for filtered installed scripts (in reverse order):"
foreach ($cmd in $finalCommands) {
if ($cmd.undo -and $cmd.undo.Trim() -ne "") {
# Save the original undo command text
$undoCommand = $cmd.undo
if ($undoCommand -match "PlayniteWatcher" -or $undoCommand -match "RTSSLimiter") {
Write-Host "Skipping undo command related to PlayniteWatcher or RTSSLimiter."
continue
}
# Look for the -file parameter and extract its value
if ($undoCommand -match '-file\s+"([^"]+)"') {
$origFilePath = $matches[1]
$origFileName = Split-Path $origFilePath -Leaf
# If the file isn't already Helpers.ps1, replace it
if ($origFileName -ne "Helpers.ps1") {
$folder = Split-Path $origFilePath -Parent
$newFilePath = Join-Path $folder "Helpers.ps1"
# Replace the original file path with the new Helpers.ps1 path
$undoCommand = $undoCommand -replace [regex]::Escape($origFilePath), $newFilePath
if ($undoCommand -notmatch "-t\s+1") {
$undoCommand = $undoCommand + " -t 1"
}
Write-Host "Modified undo command to: $undoCommand"
}
}
Write-Host "Running undo command:"
Write-Host " $undoCommand"
try {
# Execute the modified undo command synchronously
Invoke-Expression $undoCommand
Write-Host "Undo command completed."
}
catch {
Write-Warning "Failed to run undo command for one of the scripts: $_"
}
}
else {
Write-Host "No undo command for this entry. Skipping."
}
Start-Sleep -Seconds 1 # Optional pause between commands
}
Write-Host "All undo operations have been processed."
}
finally {
# Always release and dispose of the mutex
$mutex.ReleaseMutex()
$mutex.Dispose()
}