modules/Core/InstallationCommands.psm1
|
# InstallationCommands Module # Functions for installing, repairing, and uninstalling Spotify CLI. $script:ModuleName = 'SpotifyCommands' $script:DocumentsPath = [Environment]::GetFolderPath('MyDocuments') $script:InstallPath = Join-Path $script:DocumentsPath "PowerShell\Modules\$script:ModuleName" $script:AppDataPath = Join-Path $env:APPDATA "SpotifyCLI" # All nested modules declared in SpotifyCommands.psd1 $script:NestedModules = @( 'modules\SpotifyLiveFeatures.psm1', 'modules\Core\ErrorHandling.psm1', 'modules\Core\ApiClientManager.psm1', 'modules\Core\LegacyApiClient.psm1', 'modules\Core\StateManager.psm1', 'modules\Core\LegacyConfigManager.psm1', 'modules\Core\UIHelpers.psm1', 'modules\Core\InteractiveMode.psm1', 'modules\UI\SpotifyFormDisplay.psm1', 'modules\Core\AppCommands.psm1', 'modules\Core\PlaybackCommands.psm1', 'modules\Core\SearchCommands.psm1', 'modules\Core\PlaylistQueueCommands.psm1', 'modules\Quiz\QuizCommands.psm1', 'modules\UI\PeakDashboard.psm1', 'modules\Core\SetlistCommands.psm1', 'modules\Core\AliasManagement.psm1', 'modules\Core\InstallationCommands.psm1' ) $script:ProfilePaths = @( $PROFILE.CurrentUserCurrentHost $PROFILE.CurrentUserAllHosts (Join-Path $script:DocumentsPath "PowerShell\Microsoft.PowerShell_profile.ps1") (Join-Path $script:DocumentsPath "PowerShell\profile.ps1") (Join-Path $script:DocumentsPath "WindowsPowerShell\Microsoft.PowerShell_profile.ps1") (Join-Path $script:DocumentsPath "WindowsPowerShell\profile.ps1") ) | Select-Object -Unique | Where-Object { $_ } function Install-SpotifyCliDependencies { <# .SYNOPSIS Verify that all Spotify CLI dependencies are present .DESCRIPTION Checks that all required .psm1 module files exist in the installation directory, that PowerShell 5.0+ is available, and that the .env credential file is configured. Reports any missing items. .EXAMPLE Install-SpotifyCliDependencies #> $result = [PSCustomObject]@{ Success = $true Missing = @() Present = @() Errors = @() } Write-Host "" Write-Host "Checking Spotify CLI dependencies..." -ForegroundColor Cyan Write-Host "" # Check PowerShell version $psVersion = $PSVersionTable.PSVersion if ($psVersion.Major -lt 5) { $msg = "PowerShell 5.0+ required (found $($psVersion.Major).$($psVersion.Minor))" $result.Missing += $msg $result.Errors += $msg $result.Success = $false Write-Host " ❌ $msg" -ForegroundColor Red } else { Write-Host " ✅ PowerShell $($psVersion.Major).$($psVersion.Minor)" -ForegroundColor Green $result.Present += "PowerShell $($psVersion.Major).$($psVersion.Minor)" } # Check installation directory if (-not (Test-Path $script:InstallPath)) { $msg = "Installation directory not found: $script:InstallPath" $result.Missing += $msg $result.Errors += $msg $result.Success = $false Write-Host " ❌ $msg" -ForegroundColor Red Write-Host " Run .\Install-SpotifyCLI-Complete.ps1 to install" -ForegroundColor Gray } else { Write-Host " ✅ Installation directory: $script:InstallPath" -ForegroundColor Green $result.Present += "Installation directory" # Check each nested module foreach ($rel in $script:NestedModules) { $full = Join-Path $script:InstallPath $rel if (Test-Path $full) { $result.Present += $rel } else { $result.Missing += $rel $result.Success = $false Write-Host " ❌ Missing module: $rel" -ForegroundColor Red } } $presentCount = ($result.Present | Where-Object { $_ -like 'modules\*' }).Count if ($presentCount -gt 0) { Write-Host " ✅ $presentCount/$($script:NestedModules.Count) nested modules present" -ForegroundColor Green } } # Check .env file (look in both install path and current directory) $envPaths = @( (Join-Path $script:InstallPath ".env") (Join-Path $PSScriptRoot ".env") (Join-Path (Get-Location) ".env") ) $envFound = $envPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($envFound) { $envContent = Get-Content $envFound -Raw -ErrorAction SilentlyContinue $hasClientId = $envContent -match 'SPOTIFY_CLIENT_ID=\S+' $hasClientSecret = $envContent -match 'SPOTIFY_CLIENT_SECRET=\S+' if ($hasClientId -and $hasClientSecret) { Write-Host " ✅ .env file with credentials found" -ForegroundColor Green $result.Present += ".env credentials" } else { $msg = ".env found but missing SPOTIFY_CLIENT_ID or SPOTIFY_CLIENT_SECRET" $result.Missing += $msg $result.Success = $false Write-Host " ❌ $msg" -ForegroundColor Red } } else { $msg = ".env file not found — credentials required" $result.Missing += $msg $result.Success = $false Write-Host " ❌ $msg" -ForegroundColor Red Write-Host " Copy .env.example to .env and add your Spotify credentials" -ForegroundColor Gray } Write-Host "" if ($result.Success) { Write-Host "✅ All dependencies satisfied" -ForegroundColor Green } else { Write-Host "⚠️ $($result.Missing.Count) issue(s) found — run .\Install-SpotifyCLI-Complete.ps1 to fix" -ForegroundColor Yellow } Write-Host "" return $result } function Repair-SpotifyCliInstallation { <# .SYNOPSIS Attempt to repair the Spotify CLI installation .DESCRIPTION Backs up config and tokens, re-imports all modules, verifies authentication, and restores missing PowerShell profile entries. .EXAMPLE Repair-SpotifyCliInstallation #> $timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss" $backupDir = Join-Path $env:TEMP "SpotifyCLI_Repair_$timestamp" $result = [PSCustomObject]@{ Success = $false BackupPath = $backupDir RepairedItems = @() Errors = @() } Write-Host "" Write-Host "Repairing Spotify CLI installation..." -ForegroundColor Cyan Write-Host "" # Step 1: Backup config and tokens try { New-Item -ItemType Directory -Path $backupDir -Force | Out-Null $configFile = Join-Path $script:AppDataPath "config.json" $tokenFile = Join-Path $script:AppDataPath "tokens.json" foreach ($f in @($configFile, $tokenFile)) { if (Test-Path $f) { Copy-Item $f -Destination $backupDir -ErrorAction SilentlyContinue } } Write-Host " ✅ Backup created: $backupDir" -ForegroundColor Green $result.RepairedItems += "Backup created" } catch { $result.Errors += "Backup failed: $_" Write-Host " ⚠️ Backup failed: $_" -ForegroundColor Yellow } # Step 2: Re-import all modules Write-Host " Reloading modules..." -ForegroundColor Gray $moduleReloaded = $false try { $manifestPath = Join-Path $script:InstallPath "SpotifyCommands.psd1" if (Test-Path $manifestPath) { Remove-Module -Name $script:ModuleName -Force -ErrorAction SilentlyContinue Import-Module $manifestPath -Force -DisableNameChecking -ErrorAction Stop $moduleReloaded = $true Write-Host " ✅ Module reloaded" -ForegroundColor Green $result.RepairedItems += "Module reloaded" } else { $result.Errors += "Manifest not found at $manifestPath" Write-Host " ❌ Manifest not found — run .\Install-SpotifyCLI-Complete.ps1" -ForegroundColor Red } } catch { $result.Errors += "Module reload failed: $_" Write-Host " ❌ Module reload failed: $_" -ForegroundColor Red } # Step 3: Verify authentication Write-Host " Checking authentication..." -ForegroundColor Gray try { $authResult = Test-SpotifyAuth if ($authResult.IsAuthenticated) { Write-Host " ✅ Authentication valid" -ForegroundColor Green $result.RepairedItems += "Authentication verified" } else { Write-Host " ⚠️ Not authenticated — run .\spotifyCLI.ps1 to re-authenticate" -ForegroundColor Yellow } } catch { Write-Host " ⚠️ Could not verify authentication" -ForegroundColor Yellow } # Step 4: Check profile entries Write-Host " Checking PowerShell profile..." -ForegroundColor Gray $importStatement = "Import-Module $script:ModuleName" $profileFixed = $false foreach ($profilePath in $script:ProfilePaths) { if (-not (Test-Path $profilePath)) { continue } $content = Get-Content $profilePath -Raw -ErrorAction SilentlyContinue if ($content -and $content -notmatch [regex]::Escape($script:ModuleName)) { try { Add-Content -Path $profilePath -Value "`n$importStatement" -ErrorAction Stop Write-Host " ✅ Added import to $profilePath" -ForegroundColor Green $result.RepairedItems += "Profile entry added: $profilePath" $profileFixed = $true } catch { $result.Errors += "Profile update failed for $profilePath`: $_" } } } if (-not $profileFixed) { Write-Host " ✅ Profile entries OK" -ForegroundColor Green } $result.Success = $result.Errors.Count -eq 0 Write-Host "" if ($result.Success) { Write-Host "✅ Repair complete. $($result.RepairedItems.Count) items repaired." -ForegroundColor Green } else { Write-Host "⚠️ Repair finished with $($result.Errors.Count) error(s)." -ForegroundColor Yellow foreach ($e in $result.Errors) { Write-Host " - $e" -ForegroundColor Red } } Write-Host "" return $result } function Uninstall-SpotifyCli { <# .SYNOPSIS Remove the Spotify CLI installation .DESCRIPTION Unloads the module from the current session, removes PowerShell profile entries, clears session aliases, removes the installation directory, and optionally removes cached data and config. .PARAMETER KeepConfig Keep .env, config.json and tokens.json (backed up to Documents folder) .PARAMETER Force Skip the confirmation prompt .EXAMPLE Uninstall-SpotifyCli -WhatIf Uninstall-SpotifyCli -KeepConfig Uninstall-SpotifyCli -Force #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( [switch]$KeepConfig, [switch]$Force ) if ($Force) { $ConfirmPreference = 'None' } if (-not $PSCmdlet.ShouldProcess("Spotify CLI", "Uninstall")) { return } $timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss" $result = [PSCustomObject]@{ Success = $false BackupPath = $null RemovedItems = @() } Write-Host "" Write-Host "Uninstalling Spotify CLI..." -ForegroundColor Cyan Write-Host "" # Step 1: Back up config if -KeepConfig if ($KeepConfig) { $backupDir = Join-Path $script:DocumentsPath "SpotifyCLI_Backup_$timestamp" try { New-Item -ItemType Directory -Path $backupDir -Force | Out-Null foreach ($f in @("config.json", "tokens.json")) { $src = Join-Path $script:AppDataPath $f if (Test-Path $src) { Copy-Item $src -Destination $backupDir -ErrorAction SilentlyContinue } } $envSrc = Join-Path $script:InstallPath ".env" if (Test-Path $envSrc) { Copy-Item $envSrc -Destination $backupDir -ErrorAction SilentlyContinue } $result.BackupPath = $backupDir Write-Host " ✅ Config backed up to: $backupDir" -ForegroundColor Green } catch { Write-Host " ⚠️ Backup failed: $_" -ForegroundColor Yellow } } # Step 2: Unload module from session try { Remove-Module -Name $script:ModuleName -Force -ErrorAction SilentlyContinue Write-Host " ✅ Module unloaded from session" -ForegroundColor Green $result.RemovedItems += "Session module" } catch { Write-Host " ⚠️ Could not unload module: $_" -ForegroundColor Yellow } # Step 3: Remove profile entries $importPattern = "(?m)^[^\r\n]*Import-Module[^\r\n]*$script:ModuleName[^\r\n]*$" foreach ($profilePath in $script:ProfilePaths) { if (-not (Test-Path $profilePath)) { continue } try { $content = Get-Content $profilePath -Raw -ErrorAction SilentlyContinue if ($content -and $content -match $importPattern) { $cleaned = [regex]::Replace($content, $importPattern, '') Set-Content -Path $profilePath -Value $cleaned -ErrorAction Stop Write-Host " ✅ Removed profile entry from $profilePath" -ForegroundColor Green $result.RemovedItems += "Profile entry: $profilePath" } } catch { Write-Host " ⚠️ Could not update profile $profilePath`: $_" -ForegroundColor Yellow } } # Step 4: Remove session aliases $allAliases = @('plays-now','music','pn','sp','pl','vol','sh','rep','tr','q','pq', 'spotify','help','spotify-help','slw','ShowLyrics','quiz','peak', 'setlist','commands','ss','ShowSpotify') $removedAliases = 0 foreach ($alias in $allAliases) { if (Get-Alias -Name $alias -ErrorAction SilentlyContinue) { Remove-Item -Path "Alias:\$alias" -Force -ErrorAction SilentlyContinue $removedAliases++ } } if ($removedAliases -gt 0) { Write-Host " ✅ Removed $removedAliases session aliases" -ForegroundColor Green $result.RemovedItems += "$removedAliases aliases" } # Step 5: Remove installation directory if (Test-Path $script:InstallPath) { try { Remove-Item -Path $script:InstallPath -Recurse -Force -ErrorAction Stop Write-Host " ✅ Removed installation directory: $script:InstallPath" -ForegroundColor Green $result.RemovedItems += "Installation directory" } catch { Write-Host " ❌ Could not remove installation directory: $_" -ForegroundColor Red } } # Step 6: Remove cache/appdata (unless -KeepConfig) if (-not $KeepConfig -and (Test-Path $script:AppDataPath)) { try { Remove-Item -Path $script:AppDataPath -Recurse -Force -ErrorAction Stop Write-Host " ✅ Removed app data: $script:AppDataPath" -ForegroundColor Green $result.RemovedItems += "App data" } catch { Write-Host " ⚠️ Could not remove app data: $_" -ForegroundColor Yellow } } $result.Success = $true Write-Host "" Write-Host "✅ Spotify CLI uninstalled. $($result.RemovedItems.Count) items removed." -ForegroundColor Green if ($result.BackupPath) { Write-Host " Config saved to: $result.BackupPath" -ForegroundColor Gray } Write-Host "" return $result } Export-ModuleMember -Function 'Install-SpotifyCliDependencies', 'Repair-SpotifyCliInstallation', 'Uninstall-SpotifyCli' |