From 59a9b69ff4145eb01816e3b046492150233f1b42 Mon Sep 17 00:00:00 2001 From: algiers Date: Fri, 7 Mar 2025 21:22:32 +0100 Subject: [PATCH 1/2] Test new menu capability --- ExcuteLocally.ps1 | 11 +- ExecuteLocally.txt | 16 ++ Scripts/Registry/OptionsRegionales 7.reg | Bin 2804 -> 0 bytes ...l visible sur tous les r\303\251seaux.reg" | Bin 1132 -> 0 bytes Scripts/Registry/exxxxx.reg | Bin 164900 -> 0 bytes Scripts/Registry/fix-0x0000011b.reg | 4 - Scripts/RegistryManagement.ps1 | 216 +++++++++++--- menu.ps1 | 268 ++++++++++++++---- 8 files changed, 422 insertions(+), 93 deletions(-) create mode 100644 ExecuteLocally.txt delete mode 100644 Scripts/Registry/OptionsRegionales 7.reg delete mode 100644 "Scripts/Registry/Rendre l'appareil visible sur tous les r\303\251seaux.reg" delete mode 100644 Scripts/Registry/exxxxx.reg delete mode 100644 Scripts/Registry/fix-0x0000011b.reg diff --git a/ExcuteLocally.ps1 b/ExcuteLocally.ps1 index aab98d7..e58c87c 100644 --- a/ExcuteLocally.ps1 +++ b/ExcuteLocally.ps1 @@ -1,14 +1,15 @@ $scriptUrl = "https://tinyurl.com/cpwrshell" $localPath = "$env:TEMP\menu.ps1" -# Download the script with correct encoding +# Download the script Invoke-WebRequest -Uri $scriptUrl -OutFile $localPath -# Convert file encoding to UTF-8 without BOM -$scriptContent = Get-Content -Path $localPath -Raw -$scriptContent | Set-Content -Path $localPath -Encoding utf8 +# Force UTF-8 Encoding (Remove incorrect characters) +$scriptContent = Get-Content -Path $localPath -Raw -Encoding Byte +$utf8Content = [System.Text.Encoding]::UTF8.GetString($scriptContent) +$utf8Content | Set-Content -Path $localPath -Encoding utf8 -# Unblock the file to avoid execution warnings +# Unblock the script (prevents execution warnings) Unblock-File -Path $localPath # Execute the script diff --git a/ExecuteLocally.txt b/ExecuteLocally.txt new file mode 100644 index 0000000..e58c87c --- /dev/null +++ b/ExecuteLocally.txt @@ -0,0 +1,16 @@ +$scriptUrl = "https://tinyurl.com/cpwrshell" +$localPath = "$env:TEMP\menu.ps1" + +# Download the script +Invoke-WebRequest -Uri $scriptUrl -OutFile $localPath + +# Force UTF-8 Encoding (Remove incorrect characters) +$scriptContent = Get-Content -Path $localPath -Raw -Encoding Byte +$utf8Content = [System.Text.Encoding]::UTF8.GetString($scriptContent) +$utf8Content | Set-Content -Path $localPath -Encoding utf8 + +# Unblock the script (prevents execution warnings) +Unblock-File -Path $localPath + +# Execute the script +PowerShell -ExecutionPolicy Bypass -File $localPath diff --git a/Scripts/Registry/OptionsRegionales 7.reg b/Scripts/Registry/OptionsRegionales 7.reg deleted file mode 100644 index bb244527291a1fc8dc2a7238860f088819f6949b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2804 zcmc(hTTc^F5Xa})#P87XqzRXDDI&xN0~U;>5LzT?jj6PhjnFo25%AOaN!8z++3B98 zMH>@hHtp^?bN$cEnK{3Ho=75HX-Q9RWGDqWlD2f%8_Gxqa>vdas0phc>w>(8K7g9A z?y@e)q7-?a%9HT>D0}h_nN#^J+j5M)BUV+Zvp$v@-$&AbqS9)iCzohBlqTyo-wk;i zC~Bq4?kKg?g!U;|n3D=pXV5N@n3Grh)B}6gcyF_#{^U8UNL-C2&e6XhWxR3Tp)B*1 z1!%U{$oIOukd06cp2aFMok+eGzk2hPATKzAXJp+dXJO4$<7u3!w?b2oq6ffW-*h<5Sp*4$~k%BR>_y!R}MRAlet})!j(Udv7UTB zVjupbxr+5GYVQIZ(-D-(jP_5<_?;ZcxtxHtEnic-*_ln~PtNT;%I`6@_{97b`G6+v z29vSdhtkBd#>ZnD%UX4N{7il$ikZ<3;g?`#(uVeN#XTSfiF_a1(VlA6l#t!HHsi=T zrk=63`-n!V#Z5*jE$sWP zz~0Mg4Z5a!@O2dMwc$^gvj3(@JDB!+pT4vEpuQx;c9h;~tPA{I-m??mh4k*xqyOBW q^!fZ9a6(7)9gX5F@%n$2)*wG!ZfSaN^r|Z|n~O~4IJdSlE0*8H0iJ;X diff --git "a/Scripts/Registry/Rendre l'appareil visible sur tous les r\303\251seaux.reg" "b/Scripts/Registry/Rendre l'appareil visible sur tous les r\303\251seaux.reg" deleted file mode 100644 index 30e18f7a160a3b452c83303f7a5a7f3d57b7c7b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1132 zcmb_cT}#4X6n?IQ{)2c`AW@p{Na#XVl%yGHNo0uW%F*1)Y384=o^#f|yh$j=-ks0q zJm>6eU!Og=@G(Y!B|;Q%4F}J(g_vW86)mUqy3_*d1>Dj57#E|h*J$uUUMR^YyAMxwsiryLTsiH*OACzg4wS9UXS;JwRM_FdkrS?Gt$qacd z6?u#zjyZ`~t7+HLd3?zP@|ePSq@Bt9R_ws#Y^CO0tLwErEHv5OkdDoWa8iBctzEgWU!to<;tn@q0Z>{6U^A{#ud$$jm*z}=F` zmS7=8%(EFKng7MClIgI*-c!bDt(2cdZgGU+9>ubiLCMP|i7}a&GAADAnoPHb+mqT; zIhu={(az<+jOb0?aL+t$?vQhDQ|@r*0=#axadq7W`6o1(8zy_~;Ea1HU*DYH^MdDO IBShi(4dD@?PXGV_ diff --git a/Scripts/Registry/exxxxx.reg b/Scripts/Registry/exxxxx.reg deleted file mode 100644 index 22d61c1fdf3b7dc0cf403759de9f3bdc5b1d6852..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164900 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 diff --git a/Scripts/Registry/fix-0x0000011b.reg b/Scripts/Registry/fix-0x0000011b.reg deleted file mode 100644 index aefe2ed..0000000 --- a/Scripts/Registry/fix-0x0000011b.reg +++ /dev/null @@ -1,4 +0,0 @@ -Windows Registry Editor Version 5.00 - -[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Print] -"RpcAuthnLevelPrivacyEnabled"=dword:00000000 \ No newline at end of file diff --git a/Scripts/RegistryManagement.ps1 b/Scripts/RegistryManagement.ps1 index 395b5bb..3a5fd98 100644 --- a/Scripts/RegistryManagement.ps1 +++ b/Scripts/RegistryManagement.ps1 @@ -1,49 +1,187 @@ # Registry Merge Menu - Fetch and Apply .reg Files from GitHub -# Define GitHub API URLs -$apiUrl = "https://api.github.com/repos/algiers/powershell-scripts/contents/Scripts/Registry" +# Configuration file path +$configFilePath = Join-Path $PSScriptRoot "registry_paths.json" + +# Configuration +$config = @{ + ApiUrl = "https://api.github.com/repos/algiers/powershell-scripts/contents/Scripts/Registry" + # Array of paths to search for registry files + RegistryPaths = @( + (Join-Path $PSScriptRoot "Registry") # Default local path + ) +} + +# Function to load registry paths from configuration file +function Load-RegistryPaths { + if (Test-Path $configFilePath) { + $savedPaths = Get-Content $configFilePath -Raw | ConvertFrom-Json + $config.RegistryPaths = $savedPaths + } +} + +# Function to save registry paths to configuration file +function Save-RegistryPaths { + $config.RegistryPaths | ConvertTo-Json | Set-Content $configFilePath +} + +# Function to add a new registry path +function Add-RegistryPath { + param([string]$Path) + + if (-not (Test-Path $Path)) { + throw "Path does not exist: $Path" + } + + if (-not ($config.RegistryPaths -contains $Path)) { + $config.RegistryPaths += $Path + Save-RegistryPaths + return $true + } + return $false +} + +# Function to remove a registry path +function Remove-RegistryPath { + param([string]$Path) + + $config.RegistryPaths = $config.RegistryPaths | Where-Object { $_ -ne $Path } + Save-RegistryPaths +} # Function to check if running as Administrator function Test-Admin { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { - Write-Host "⚠️ This script requires Administrator privileges. Restarting as Admin..." -ForegroundColor Yellow + Write-Host "! This script requires Administrator privileges. Restarting as Admin..." -ForegroundColor Yellow $proc = Start-Process PowerShell -ArgumentList "-File `"$PSCommandPath`"" -Verb RunAs -PassThru $proc.WaitForExit() exit } } -# Function to fetch .reg files from GitHub +# Function to manage registry paths +function Show-PathManager { + $selectedIndex = 0 + + try { + while ($true) { + Clear-Host + Write-Host "===== Registry Path Manager =====" -ForegroundColor Cyan + Write-Host "" + Write-Host "Current registry paths:" -ForegroundColor Yellow + Write-Host "(Local files take precedence over GitHub files)" -ForegroundColor DarkGray + + for ($i = 0; $i -lt $config.RegistryPaths.Count; $i++) { + if ($i -eq $selectedIndex) { + Write-Host " > " -NoNewline -ForegroundColor Green + } else { + Write-Host " " -NoNewline + } + Write-Host "$($config.RegistryPaths[$i])" + } + + Write-Host "`nControls:" -ForegroundColor Yellow + Write-Host " [A] Add new path" + Write-Host " [D] Delete selected path" + Write-Host " [ESC] Return to main menu" + + $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode + + switch ($key) { + 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow + 40 { if ($selectedIndex -lt ($config.RegistryPaths.Count - 1)) { $selectedIndex++ } } # Down Arrow + 65 { # 'A' key - Add path + Clear-Host + Write-Host "Enter new registry path (or press Enter to cancel):" -ForegroundColor Yellow + $newPath = Read-Host + if ($newPath) { + try { + if (Add-RegistryPath $newPath) { + Write-Host "Path added successfully!" -ForegroundColor Green + } else { + Write-Host "Path already exists." -ForegroundColor Yellow + } + Start-Sleep -Seconds 1 + } catch { + Write-Host "Error: $_" -ForegroundColor Red + Start-Sleep -Seconds 2 + } + } + } + 68 { # 'D' key - Delete path + if ($config.RegistryPaths.Count -gt 1 -and $selectedIndex -lt $config.RegistryPaths.Count) { + Remove-RegistryPath $config.RegistryPaths[$selectedIndex] + if ($selectedIndex -ge $config.RegistryPaths.Count) { + $selectedIndex = $config.RegistryPaths.Count - 1 + } + } + } + 27 { return } # ESC key - Return to main menu + } + } + } + finally { + $Host.UI.RawUI.CursorVisible = $true + } +} + +# Function to fetch .reg files function Get-RegFiles { Write-Host "`nFetching registry files, please wait..." -ForegroundColor Yellow $loadingChars = @("-", "\", "|", "/") $job = Start-Job -ScriptBlock { - # Try local files first - $localRegPath = Join-Path $using:PSScriptRoot "Registry" - if (Test-Path $localRegPath) { - $localFiles = Get-ChildItem -Path $localRegPath -Filter "*.reg" - if ($localFiles) { - return $localFiles | ForEach-Object { - @{ - name = $_.Name - download_url = $_.FullName - isLocal = $true + try { + # Initialize unique files dictionary + $uniqueFiles = @{} + + # First check local paths (they take precedence) + foreach ($path in $using:config.RegistryPaths) { + if (Test-Path $path) { + $localFiles = Get-ChildItem -Path $path -Filter "*.reg" -Recurse -ErrorAction SilentlyContinue + if ($localFiles) { + $localFiles | ForEach-Object { + # Only add if not already present + if (-not $uniqueFiles.ContainsKey($_.Name)) { + $uniqueFiles[$_.Name] = @{ + name = $_.Name + download_url = $_.FullName + isLocal = $true + location = $_.DirectoryName + } + } + } } } } - } - - # Try GitHub if no local files - $regFiles = Invoke-RestMethod -Uri $using:apiUrl - if ($regFiles) { - return $regFiles | Where-Object { $_.name -match '\.reg$' } | ForEach-Object { - $_ | Add-Member -NotePropertyName isLocal -NotePropertyValue $false -PassThru + + # Then check GitHub for any additional files + try { + $apiUrl = $using:config.ApiUrl + $files = Invoke-RestMethod -Uri $apiUrl -ErrorAction Stop + if ($files) { + $files | Where-Object { $_.name -match '\.reg$' } | ForEach-Object { + # Only add if we don't already have a local version + if (-not $uniqueFiles.ContainsKey($_.name)) { + $_ | Add-Member -NotePropertyName isLocal -NotePropertyValue $false -PassThru + $_ | Add-Member -NotePropertyName location -NotePropertyValue "GitHub" -PassThru + $uniqueFiles[$_.name] = $_ + } + } + } + } catch { + Write-Warning "Could not fetch GitHub registry files: $_" } + + # Convert dictionary values to array + return @($uniqueFiles.Values) + } + catch { + Write-Warning "Error fetching registry files: $_" + return $null } - return $null } $i = 0 @@ -56,8 +194,8 @@ function Get-RegFiles { $result = Receive-Job -Job $job -Wait -AutoRemoveJob Write-Host "`r " -NoNewline # Clear loading animation - if ($null -eq $result) { - Write-Host "`n❌ No registry files found locally or on GitHub!" -ForegroundColor Red + if ($null -eq $result -or $result.Count -eq 0) { + Write-Host "`nx No registry files found locally or on GitHub!" -ForegroundColor Red Read-Host "Press ENTER to return to the main menu" return @() } @@ -75,13 +213,13 @@ function Show-Menu { while ($true) { Clear-Host Write-Host "============================" -ForegroundColor Cyan - Write-Host " GitHub Registry Merger " -ForegroundColor Cyan + Write-Host " Registry File Manager " -ForegroundColor Cyan Write-Host "============================" -ForegroundColor Cyan - Write-Host "`nUse ↑ ↓ arrow keys to navigate, ENTER to merge, or ESC to exit.`n" + Write-Host "`nUse Up/Down arrows to navigate, Enter to merge, P to manage paths, or ESC to exit.`n" # Option to merge all .reg files if ($selectedIndex -eq $maxIndex) { - Write-Host " ➜ [ALL] Merge ALL registry files" -ForegroundColor Green + Write-Host " > [ALL] Merge ALL registry files" -ForegroundColor Green } else { Write-Host " [ALL] Merge ALL registry files" -ForegroundColor White } @@ -89,18 +227,20 @@ function Show-Menu { # Display individual files for ($i = 0; $i -lt $regFiles.Count; $i++) { if ($i -eq $selectedIndex) { - Write-Host " ➜ [$($i+1)] $($regFiles[$i].name)" -ForegroundColor Green + Write-Host " > [$($i+1)] $($regFiles[$i].name)" -ForegroundColor Green + Write-Host " Location: $($regFiles[$i].location)" -ForegroundColor DarkGray } else { Write-Host " [$($i+1)] $($regFiles[$i].name)" -ForegroundColor White } } - $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").VirtualKeyCode + $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode switch ($key) { 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow 40 { if ($selectedIndex -lt $maxIndex) { $selectedIndex++ } } # Down Arrow 13 { return $selectedIndex } # Enter Key - 27 { return -1 } # Escape Key to return to main menu + 27 { return -1 } # Escape Key - Return to main menu + 80 { Show-PathManager; return -2 } # 'P' key - Show path manager } } } @@ -120,15 +260,15 @@ function Merge-RegFile { $tempRegFile = "$env:TEMP\$regFileName" Write-Host "`nDownloading registry file: $regFileName..." -ForegroundColor Yellow Invoke-WebRequest -Uri $regUrl -OutFile $tempRegFile -ErrorAction Stop - Write-Host "✅ Registry file downloaded successfully!" -ForegroundColor Green + Write-Host "+ Registry file downloaded successfully!" -ForegroundColor Green $tempRegFile } Write-Host "`nMerging $regFileName into the registry..." -ForegroundColor Yellow Start-Process -FilePath "regedit.exe" -ArgumentList "/s `"$regFilePath`"" -Wait -NoNewWindow - Write-Host "✅ Registry file merged successfully!" -ForegroundColor Green + Write-Host "+ Registry file merged successfully!" -ForegroundColor Green } catch { - Write-Host "❌ Error merging registry file: $_" -ForegroundColor Red + Write-Host "x Error merging registry file: $_" -ForegroundColor Red } Read-Host "Press ENTER to return to the menu" @@ -144,10 +284,13 @@ function Merge-AllRegFiles { Merge-RegFile -regUrl $regFile.download_url -regFileName $regFile.name -isLocal:$regFile.isLocal } - Write-Host "`n✅ All registry files have been merged successfully!" -ForegroundColor Green + Write-Host "`n+ All registry files have been merged successfully!" -ForegroundColor Green Read-Host "Press ENTER to return to the menu" } +# Initialize configuration +Load-RegistryPaths + # Ensure the script runs as Administrator Test-Admin @@ -160,6 +303,11 @@ while ($true) { # Show menu and get user selection $selectedIndex = Show-Menu -regFiles $regFiles + if ($selectedIndex -eq -2) { + # User accessed path manager, refresh files + continue + } + # Check if user wants to return to main menu if ($selectedIndex -eq -1) { Write-Host "`nReturning to main menu..." -ForegroundColor Yellow diff --git a/menu.ps1 b/menu.ps1 index cb07c42..21d6ca2 100644 --- a/menu.ps1 +++ b/menu.ps1 @@ -4,10 +4,16 @@ $ErrorActionPreference = "Stop" $VerbosePreference = "Continue" -# Configuration +# Configuration file path +$configFilePath = Join-Path $PSScriptRoot "script_paths.json" + +# Default configuration $config = @{ - Title = "GitHub PowerShell Scripts" + Title = "PowerShell Scripts Manager" ApiUrl = "https://api.github.com/repos/algiers/powershell-scripts/contents/Scripts" + ScriptPaths = @( + (Join-Path $PSScriptRoot "Scripts") # Default local path + ) Colors = @{ Primary = [System.ConsoleColor]::Cyan Secondary = [System.ConsoleColor]::DarkCyan @@ -18,12 +24,12 @@ $config = @{ Header = [System.ConsoleColor]::Magenta } Symbols = @{ - Loading = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" - Selected = "►" - Bullet = "○" - Success = "✓" - Error = "✗" - Info = "ℹ" + Loading = "-\|/" # Simple ASCII spinner + Selected = ">" + Bullet = "*" + Success = "+" + Error = "x" + Info = "i" } WindowTitle = "PowerShell Script Manager" } @@ -45,7 +51,7 @@ function Set-CursorVisible { # Function to create a horizontal line function New-HorizontalLine { param ( - [string]$Char = '─', + [string]$Char = '-', [int]$Length = ($Host.UI.RawUI.WindowSize.Width - 2) ) return (-join ($Char * $Length)) @@ -122,17 +128,94 @@ function Show-LoadingAnimation { } } +# Function to load script paths from configuration file +function Load-ScriptPaths { + if (Test-Path $configFilePath) { + $savedPaths = Get-Content $configFilePath -Raw | ConvertFrom-Json + $config.ScriptPaths = $savedPaths + } +} + +# Function to save script paths to configuration file +function Save-ScriptPaths { + $config.ScriptPaths | ConvertTo-Json | Set-Content $configFilePath +} + +# Function to add a new script path +function Add-ScriptPath { + param([string]$Path) + + if (-not (Test-Path $Path)) { + throw "Path does not exist: $Path" + } + + if (-not ($config.ScriptPaths -contains $Path)) { + $config.ScriptPaths += $Path + Save-ScriptPaths + return $true + } + return $false +} + +# Function to remove a script path +function Remove-ScriptPath { + param([string]$Path) + + $config.ScriptPaths = $config.ScriptPaths | Where-Object { $_ -ne $Path } + Save-ScriptPaths +} + # Function to fetch scripts with a loading animation function Get-Scripts { $maxRetries = 3 $retryCount = 0 while ($retryCount -lt $maxRetries) { - $result = Show-LoadingAnimation -LoadingText "Fetching scripts" -ScriptBlock { + $result = Show-LoadingAnimation -LoadingText "Scanning for scripts" -ScriptBlock { try { - $apiUrl = $using:config.ApiUrl - $scripts = Invoke-RestMethod -Uri $apiUrl -ErrorAction Stop - return $scripts | Where-Object { $_.name -match '\.ps1$' } + # Initialize unique files dictionary + $uniqueFiles = @{} + + # First check local paths (they take precedence) + foreach ($path in $using:config.ScriptPaths) { + if (Test-Path $path) { + $localFiles = Get-ChildItem -Path $path -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue + if ($localFiles) { + $localFiles | ForEach-Object { + # Only add if not already present + if (-not $uniqueFiles.ContainsKey($_.Name)) { + $uniqueFiles[$_.Name] = @{ + name = $_.Name + download_url = $_.FullName + isLocal = $true + location = $_.DirectoryName + } + } + } + } + } + } + + # Then check GitHub for any additional scripts + try { + $apiUrl = $using:config.ApiUrl + $scripts = Invoke-RestMethod -Uri $apiUrl -ErrorAction Stop + if ($scripts) { + $scripts | Where-Object { $_.name -match '\.ps1$' } | ForEach-Object { + # Only add if we don't already have a local version + if (-not $uniqueFiles.ContainsKey($_.name)) { + $_ | Add-Member -NotePropertyName isLocal -NotePropertyValue $false -PassThru + $_ | Add-Member -NotePropertyName location -NotePropertyValue "GitHub" -PassThru + $uniqueFiles[$_.name] = $_ + } + } + } + } catch { + Write-Warning "Could not fetch GitHub scripts: $_" + } + + # Convert dictionary values to array + return @($uniqueFiles.Values) } catch { # Return the error instead of null @@ -162,7 +245,7 @@ function Get-Scripts { } } elseif ($null -eq $result -or $result.Count -eq 0) { - Show-Notification -Message "No PowerShell scripts found in the repository." -Color $config.Colors.Warning -Symbol $config.Symbols.Info + Show-Notification -Message "No PowerShell scripts found." -Color $config.Colors.Warning -Symbol $config.Symbols.Info Start-Sleep -Seconds 2 Write-Host "" @@ -198,41 +281,110 @@ function Show-TextBox { $titleLeft = [Math]::Floor($titlePadding) # Top border with title - Write-Host " ┌" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join ("─" * $titleLeft)) -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host " +" -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host (-join ("-" * $titleLeft)) -NoNewline -ForegroundColor $config.Colors.Primary Write-Host " $Title " -NoNewline -ForegroundColor $config.Colors.Header - Write-Host (-join ("─" * ($width - $titleLeft - $Title.Length - 2))) -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host "┐" -ForegroundColor $config.Colors.Primary + Write-Host (-join ("-" * ($width - $titleLeft - $Title.Length - 2))) -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host "+" -ForegroundColor $config.Colors.Primary # Empty line - Write-Host " │" -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host " |" -NoNewline -ForegroundColor $config.Colors.Primary Write-Host (-join (" " * $contentWidth)) -NoNewline - Write-Host "│" -ForegroundColor $config.Colors.Primary + Write-Host "|" -ForegroundColor $config.Colors.Primary # Content foreach ($line in $Content) { - Write-Host " │ " -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host " | " -NoNewline -ForegroundColor $config.Colors.Primary Write-Host $line -NoNewline # Calculate padding to right border $padding = $contentWidth - $line.Length - 1 if ($padding -gt 0) { Write-Host (-join (" " * $padding)) -NoNewline } - Write-Host "│" -ForegroundColor $config.Colors.Primary + Write-Host "|" -ForegroundColor $config.Colors.Primary } # Empty line - Write-Host " │" -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host " |" -NoNewline -ForegroundColor $config.Colors.Primary Write-Host (-join (" " * $contentWidth)) -NoNewline - Write-Host "│" -ForegroundColor $config.Colors.Primary + Write-Host "|" -ForegroundColor $config.Colors.Primary # Bottom border - Write-Host " └" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join ("─" * $width)) -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host "┘" -ForegroundColor $config.Colors.Primary + Write-Host " +" -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host (-join ("-" * $width)) -NoNewline -ForegroundColor $config.Colors.Primary + Write-Host "+" -ForegroundColor $config.Colors.Primary +} + +# Function to manage script paths +function Show-PathManager { + $selectedIndex = 0 + $cursorSupported = Set-CursorVisible $false + + try { + while ($true) { + Clear-Host + Write-Host "===== Script Path Manager =====" -ForegroundColor Cyan + Write-Host "" + Write-Host "Current script paths:" -ForegroundColor Yellow + Write-Host "(Local files take precedence over GitHub files)" -ForegroundColor DarkGray + + for ($i = 0; $i -lt $config.ScriptPaths.Count; $i++) { + if ($i -eq $selectedIndex) { + Write-Host " > " -NoNewline -ForegroundColor Green + } else { + Write-Host " " -NoNewline + } + Write-Host "$($config.ScriptPaths[$i])" + } + + Write-Host "`nControls:" -ForegroundColor Yellow + Write-Host " [A] Add new path" + Write-Host " [D] Delete selected path" + Write-Host " [ESC] Return to main menu" + + $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') + + switch ($key.VirtualKeyCode) { + 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow + 40 { if ($selectedIndex -lt ($config.ScriptPaths.Count - 1)) { $selectedIndex++ } } # Down Arrow + 65 { # 'A' key - Add path + Clear-Host + Write-Host "Enter new script path (or press Enter to cancel):" -ForegroundColor Yellow + $newPath = Read-Host + if ($newPath) { + try { + if (Add-ScriptPath $newPath) { + Write-Host "Path added successfully!" -ForegroundColor Green + } else { + Write-Host "Path already exists." -ForegroundColor Yellow + } + Start-Sleep -Seconds 1 + } catch { + Write-Host "Error: $_" -ForegroundColor Red + Start-Sleep -Seconds 2 + } + } + } + 68 { # 'D' key - Delete path + if ($config.ScriptPaths.Count -gt 1 -and $selectedIndex -lt $config.ScriptPaths.Count) { + Remove-ScriptPath $config.ScriptPaths[$selectedIndex] + if ($selectedIndex -ge $config.ScriptPaths.Count) { + $selectedIndex = $config.ScriptPaths.Count - 1 + } + } + } + 27 { return } # ESC key - Return to main menu + } + } + } + finally { + if ($cursorSupported) { + Set-CursorVisible $true + } + } } -# Function to display the script menu with keyboard navigation +# Function to display the script menu function Show-Menu { param([array]$Scripts) @@ -249,11 +401,11 @@ function Show-Menu { # Header $headerTitle = $config.Title Write-Host (Get-CenteredText -Text $headerTitle) -ForegroundColor $config.Colors.Header - Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '═' -Length $headerTitle.Length)) -ForegroundColor $config.Colors.Header + Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '=' -Length $headerTitle.Length)) -ForegroundColor $config.Colors.Header Write-Host "" # Menu box - $helpText = "Use ↑↓ or j/k to navigate, Enter to select, Esc to exit" + $helpText = "Use Up/Down arrows or j/k to navigate, Enter to select, P to manage paths, Esc to exit" Show-TextBox -Title "Available Scripts" -Content @($helpText, "") # Calculate visible range @@ -268,7 +420,7 @@ function Show-Menu { # Show scroll indicators if ($scrollOffset -gt 0) { - Write-Host " ⟰ More scripts above" -ForegroundColor $config.Colors.Secondary + Write-Host " ^ More scripts above" -ForegroundColor $config.Colors.Secondary } # Display visible scripts @@ -279,20 +431,18 @@ function Show-Menu { if ($i -eq $selectedIndex) { $scriptSymbol = $config.Symbols.Selected $scriptColor = $config.Colors.Highlight + Write-Host " $scriptSymbol $scriptName" -ForegroundColor $scriptColor + Write-Host " Location: $($Scripts[$i].location)" -ForegroundColor DarkGray } else { $scriptSymbol = $config.Symbols.Bullet $scriptColor = $config.Colors.Text + Write-Host " $scriptSymbol $scriptName" -ForegroundColor $scriptColor } - - Write-Host " " -NoNewline - Write-Host $scriptSymbol -NoNewline -ForegroundColor $scriptColor - Write-Host " " -NoNewline - Write-Host "$scriptName" -ForegroundColor $scriptColor } # Show scroll indicators if ($endIndex -lt $Scripts.Count - 1) { - Write-Host " ⟱ More scripts below" -ForegroundColor $config.Colors.Secondary + Write-Host " v More scripts below" -ForegroundColor $config.Colors.Secondary } Write-Host "" @@ -306,13 +456,14 @@ function Show-Menu { 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow 40 { if ($selectedIndex -lt $Scripts.Count - 1) { $selectedIndex++ } } # Down Arrow 13 { return $selectedIndex } # Enter Key - 27 { return -1 } # Escape Key - Return -1 instead of exit + 27 { return -1 } # Escape Key - Return to main menu 75 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # K key (vim-style) 74 { if ($selectedIndex -lt $Scripts.Count - 1) { $selectedIndex++ } } # J key (vim-style) 36 { $selectedIndex = 0 } # Home key 35 { $selectedIndex = $Scripts.Count - 1 } # End key 33 { $selectedIndex = [Math]::Max(0, $selectedIndex - $maxVisibleItems) } # Page Up 34 { $selectedIndex = [Math]::Min($Scripts.Count - 1, $selectedIndex + $maxVisibleItems) } # Page Down + 80 { Show-PathManager; return -2 } # 'P' key - Show path manager } } } @@ -335,18 +486,23 @@ function Invoke-SelectedScript { Clear-Host Write-Host (Get-CenteredText -Text "Executing Script") -ForegroundColor $config.Colors.Header - Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '═' -Length 16)) -ForegroundColor $config.Colors.Header + Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '=' -Length 16)) -ForegroundColor $config.Colors.Header Write-Host "" - Show-TextBox -Title $Script.name -Content @("Downloading and preparing to execute...") + $loadingMessage = if ($Script.isLocal) { "Preparing to execute..." } else { "Downloading and preparing to execute..." } + Show-TextBox -Title $Script.name -Content @($loadingMessage) try { - $scriptContent = Show-LoadingAnimation -LoadingText "Downloading script" -ScriptBlock { - try { - Invoke-RestMethod -Uri $using:Script.download_url -ErrorAction Stop - } - catch { - return @{ Error = $_ } + $scriptContent = if ($Script.isLocal) { + Get-Content -Path $Script.download_url -Raw + } else { + Show-LoadingAnimation -LoadingText "Downloading script" -ScriptBlock { + try { + Invoke-RestMethod -Uri $using:Script.download_url -ErrorAction Stop + } + catch { + return @{ Error = $_ } + } } } @@ -389,7 +545,7 @@ function Invoke-SelectedScript { $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } -# Handles exiting the application +# Function to handle exiting the application function Exit-Application { Clear-Host Write-Host (Get-CenteredText -Text "Thank you for using PowerShell Script Manager") -ForegroundColor $config.Colors.Header @@ -419,21 +575,33 @@ function Start-Application { # Show menu and get user selection $selectedIndex = Show-Menu -Scripts $scripts + if ($selectedIndex -eq -2) { + # User accessed path manager, refresh files + continue + } + # Check if user pressed Esc to exit if ($selectedIndex -eq -1) { Exit-Application } + # Get selected script and execute it $selectedScript = $scripts[$selectedIndex] - - # Execute the selected script Invoke-SelectedScript -Script $selectedScript } } +# Initialize configuration +Load-ScriptPaths + # Start the application try { - Start-Application + while ($true) { + $result = Start-Application + if ($result -eq "exit") { + break + } + } } catch { Clear-Host From 88944897340593dc00fbf5b57788ad240cf6bca6 Mon Sep 17 00:00:00 2001 From: algiers Date: Sat, 8 Mar 2025 00:01:45 +0100 Subject: [PATCH 2/2] Add ExecuteRemotely.ps1 script for downloading and executing PowerShell scripts from GitHub; update README.md for new usage instructions --- ExecuteRemotely.ps1 | 59 ++++ README.md | 202 +++++--------- menu.ps1 | 647 ++++++++++---------------------------------- 3 files changed, 266 insertions(+), 642 deletions(-) create mode 100644 ExecuteRemotely.ps1 diff --git a/ExecuteRemotely.ps1 b/ExecuteRemotely.ps1 new file mode 100644 index 0000000..acd7697 --- /dev/null +++ b/ExecuteRemotely.ps1 @@ -0,0 +1,59 @@ +# Bootstrap script to download and execute PowerShell Script Manager + +# GitHub repository information +$repo = @{ + Owner = "algiers" + Name = "powershell-scripts" + Branch = "main" +} + +# Function to safely create directory +function New-SafeDirectory { + param([string]$Path) + + if (-not (Test-Path $Path)) { + try { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + return $true + } catch { + Write-Host "Error creating directory $Path : $_" -ForegroundColor Red + return $false + } + } + return $true +} + +# Main execution block +try { + # Create base paths + $basePath = Join-Path $env:USERPROFILE "PowerShellScripts" + $scriptsPath = Join-Path $basePath "Scripts" + + # Ensure directories exist + if (-not (New-SafeDirectory $basePath) -or -not (New-SafeDirectory $scriptsPath)) { + throw "Failed to create required directories" + } + + # Download menu.ps1 + $menuUrl = "https://raw.githubusercontent.com/$($repo.Owner)/$($repo.Name)/$($repo.Branch)/menu.ps1" + $menuPath = Join-Path $basePath "menu.ps1" + + Write-Host "Downloading script manager..." -ForegroundColor Yellow + try { + $menuContent = (Invoke-WebRequest -Uri $menuUrl -UseBasicParsing).Content + if ([string]::IsNullOrWhiteSpace($menuContent)) { + throw "Received empty content" + } + $menuContent | Out-File -FilePath $menuPath -Encoding UTF8 -Force + } catch { + throw "Failed to download menu script: $_" + } + + Write-Host "Starting script manager..." -ForegroundColor Green + & $menuPath + +} catch { + Write-Host "Error: $_" -ForegroundColor Red + Write-Host "Press any key to exit..." + $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') +} diff --git a/README.md b/README.md index 445c799..ad489e2 100644 --- a/README.md +++ b/README.md @@ -1,140 +1,80 @@ -# PowerShell Scripts Menu +# PowerShell Script Manager -A dynamic PowerShell script menu that allows executing scripts directly from GitHub. +A modern PowerShell script management tool that allows you to run scripts from both local paths and GitHub. +## Quick Start - -## Usage - -Run this one-liner to access the menu: +Run this command to download and start the Script Manager: ```powershell -iex (iwr "https://raw.githubusercontent.com/algiers/powershell-scripts/master/menu.ps1").Content +iex (iwr "https://raw.githubusercontent.com/algiers/powershell-scripts/main/ExecuteRemotely.ps1" -UseBasicParsing).Content ``` ## Features -- Dynamically lists all .ps1 scripts in the repository -- Interactive menu for script selection -- Direct execution from GitHub -- Simple one-liner access - -## Structure - -- `menu.ps1` - Main menu script that lists and executes available scripts -- `/Scripts` - Directory containing PowerShell scripts - - `hello.ps1` - Example script that displays a greeting and current time - - `Reset-UsbDevices.ps1` - Script to reset all connected USB devices (Must be run as Administrator) - - Automatically downloads and sets up required utilities - - Smart device filtering (skips root hubs, keyboards, mice) - - Reliable device management with enhanced error handling - - Real-time progress tracking with color coding - - Detailed success/failure reporting per device - - Safe sequential device reset process - - Improved device identification and status tracking - - `renamePC_IP_DNS.ps1` - Computer Name and Network Configuration Tool - - Interactive menu-driven interface - - Computer renaming with restart management - - Network interface configuration: - - IP address setup - - Subnet mask configuration - - Default gateway assignment - - Primary and secondary DNS configuration - - Features: - - Network interface listing and validation - - Automatic removal of existing configurations - - Color-coded status messages - - Comprehensive error handling - - Administrative privilege verification - - Requirements: - - Administrator privileges required - - Windows OS with network adapter - - `ManagePostgreSQLService.ps1` - PostgreSQL Service Registration and Startup Script - - Registers PostgreSQL service using pg_ctl if not present - - Verifies and manages service startup state - - Provides comprehensive status verification - - Clear error handling and reporting - - Prerequisites: - - PostgreSQL installed at: D:\CHIFAPLUS\PostgreSQL\9.3\ - - Administrative privileges required - - Data directory: D:\CHIFAPLUS\PostgreSQL\9.3\data - - `RegistryManagement.ps1` - Automated Registry Files Manager - - 100% Automated registry file management - - Auto-detects all .reg files in Registry folder - - Interactive keyboard navigation menu - - Up/Down arrows (↑ ↓) to navigate - - ENTER to merge selected file - - ESC to exit - - Administrator mode detection - - Comprehensive error handling - - Compatible with older Windows 10 versions - - Features: - - Automatic .reg file download - - Single or batch registry merging - - Clear success/error feedback - - Returns to menu after execution - - Latest Improvements: - - Enhanced error handling for registry file fetching - - Menu return option when no .reg files found - - Graceful handling of GitHub API issues - - Persistent operation - stays open after file merging - - Better user experience with clear status messages - - `PrinterSharingSolver.ps1` - Print Service Configuration Script - - Comprehensive print service setup and configuration - - Feature Installation: - - LPD Print Service - - LPR Port Monitor - - Registry Optimizations: - - Named pipe protocol enablement - - Kerberos authentication management - - Security protocol adjustments - - Print Spooler Management: - - Spooler file cleanup - - Service restart handling - - Security Configurations: - - RPC authentication adjustments - - Enhanced compatibility settings - - Requirements: - - Administrator privileges required - - Windows OS with print services - - - `DisableFirewall.ps1` - Windows Firewall Management Script - - Safe and controlled firewall management - - Features: - - Disables firewall across all profiles (Domain, Public, Private) - - Interactive confirmation prompt - - Color-coded status messages - - Comprehensive error handling - - Security Features: - - Administrator privilege verification - - Execution confirmation prompt - - Safe exit on unauthorized access - - Requirements: - - Administrator privileges required - - Windows OS with Windows Firewall - - - `PrintFlush.ps1` - Print Spooler Reset and Cleanup Tool - - Comprehensive printer spooler management - - Features: - - Stops print spooler service safely - - Cleans up stuck print jobs - - Resets spooler dependencies - - Restarts print services - - Advanced Features: - - Lexmark printer compatibility fixes - - Color-coded progress indicators - - Status verification at each step - - Comprehensive error handling - - Requirements: - - Administrator privileges required - - Windows OS with Print Spooler service - -## Adding New Scripts - -1. Create your PowerShell script (`.ps1` file) -2. Place it in the `/Scripts` directory -3. The menu will automatically detect and list it - -## Security Note - -Scripts are executed directly from GitHub. Always review scripts before running them in your environment. +- Run scripts from local paths and GitHub +- Manage multiple script locations +- Modern, interactive UI with keyboard navigation +- Registry file management +- Local files take precedence over GitHub versions +- Easy path management + +## Installation + +1. **Quick Install (Temporary)** + ```powershell + iex (iwr "https://raw.githubusercontent.com/algiers/powershell-scripts/main/ExecuteRemotely.ps1" -UseBasicParsing).Content + ``` + +2. **Manual Installation** + - Clone the repository: + ```powershell + git clone https://github.com/algiers/powershell-scripts.git + cd powershell-scripts + ``` + - Run the menu script: + ```powershell + .\menu.ps1 + ``` + +## Usage + +### Script Manager (menu.ps1) +- Use Up/Down arrows or j/k to navigate +- Enter to select and run a script +- Press 'P' to manage script paths +- ESC to exit + +### Registry Manager (RegistryManagement.ps1) +- Requires Administrator privileges +- Use Up/Down arrows to navigate +- Enter to merge registry files +- Press 'P' to manage registry paths +- ESC to exit + +## Path Management + +Both tools support managing multiple paths: +1. Press 'P' to open the path manager +2. Use 'A' to add new paths +3. Use 'D' to delete selected paths +4. Local files take precedence over GitHub versions + +## File Locations + +- Scripts are stored in `%USERPROFILE%\PowerShellScripts` by default +- Path configurations are saved in: + - `script_paths.json` for PowerShell scripts + - `registry_paths.json` for Registry files + +## Requirements + +- PowerShell 5.1 or later +- Internet connection for GitHub features (optional) +- Administrator rights for registry operations + +## Notes + +- Local files with the same name as GitHub files take precedence +- All paths are remembered between sessions +- Registry operations require elevation to Administrator diff --git a/menu.ps1 b/menu.ps1 index 21d6ca2..e2fda34 100644 --- a/menu.ps1 +++ b/menu.ps1 @@ -1,18 +1,33 @@ -# menu.ps1 - Modern PowerShell Menu with Enhanced UI (PowerShell 5.1 Compatible) +# menu.ps1 - Modern PowerShell Script Manager (PowerShell 5.1 Compatible) #requires -Version 5.0 $ErrorActionPreference = "Stop" $VerbosePreference = "Continue" +# Determine base paths +$scriptPath = $MyInvocation.MyCommand.Path +$baseDir = if ($scriptPath) { + Split-Path $scriptPath -Parent +} else { + Join-Path $env:USERPROFILE "PowerShellScripts" +} + +# Ensure Scripts directory exists +$scriptsDir = Join-Path $baseDir "Scripts" +if (-not (Test-Path $scriptsDir)) { + New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null +} + # Configuration file path -$configFilePath = Join-Path $PSScriptRoot "script_paths.json" +$configFilePath = Join-Path $baseDir "script_paths.json" # Default configuration $config = @{ Title = "PowerShell Scripts Manager" ApiUrl = "https://api.github.com/repos/algiers/powershell-scripts/contents/Scripts" + RemoteSource = "GitHub" ScriptPaths = @( - (Join-Path $PSScriptRoot "Scripts") # Default local path + $scriptsDir # Default local path ) Colors = @{ Primary = [System.ConsoleColor]::Cyan @@ -24,454 +39,106 @@ $config = @{ Header = [System.ConsoleColor]::Magenta } Symbols = @{ - Loading = "-\|/" # Simple ASCII spinner - Selected = ">" - Bullet = "*" - Success = "+" - Error = "x" - Info = "i" + Loading = "-\|/" + Selected = ">" + Bullet = "*" + Success = "+" + Error = "x" + Info = "i" } WindowTitle = "PowerShell Script Manager" } -# Set window title -$Host.UI.RawUI.WindowTitle = $config.WindowTitle - -# Function to safely manage cursor visibility -function Set-CursorVisible { - param([bool]$Visible) - try { - $Host.UI.RawUI.CursorVisible = $Visible - return $true - } catch { - return $false - } -} - -# Function to create a horizontal line -function New-HorizontalLine { - param ( - [string]$Char = '-', - [int]$Length = ($Host.UI.RawUI.WindowSize.Width - 2) - ) - return (-join ($Char * $Length)) -} - -# Function to center text -function Get-CenteredText { - param ( - [string]$Text, - [int]$Width = ($Host.UI.RawUI.WindowSize.Width) - ) - $padding = [Math]::Max(0, ($Width - $Text.Length) / 2) - return (-join (' ' * [Math]::Floor($padding))) + $Text -} - -# Function to show a notification -function Show-Notification { - param ( - [string]$Message, - [System.ConsoleColor]$Color = $config.Colors.Text, - [string]$Symbol = "", - [switch]$NoNewLine - ) - - if ($Symbol) { - Write-Host "$Symbol " -NoNewline -ForegroundColor $Color - } - - if ($NoNewLine) { - Write-Host $Message -NoNewline -ForegroundColor $Color - } - else { - Write-Host $Message -ForegroundColor $Color - } -} - -# Function to display an animated loading indicator -function Show-LoadingAnimation { - param ( - [scriptblock]$ScriptBlock, - [string]$LoadingText = "Loading" - ) - - $cursorSupported = Set-CursorVisible $false - $job = Start-Job -ScriptBlock $ScriptBlock - - $symbols = $config.Symbols.Loading.ToCharArray() - $i = 0 - $dots = "" - - try { - while ($job.State -eq 'Running') { - $dots += "." - if ($dots.Length -gt 3) { $dots = "." } - - Write-Host "`r$($symbols[$i]) $LoadingText$dots" -NoNewline -ForegroundColor $config.Colors.Warning - - Start-Sleep -Milliseconds 100 - $i = ($i + 1) % $symbols.Length - } - - Write-Host "`r `r" -NoNewline - - $result = Receive-Job -Job $job -Wait -AutoRemoveJob - return $result - } - finally { - if ($cursorSupported) { - Set-CursorVisible $true - } - if ($null -ne $job) { - Remove-Job -Job $job -Force -ErrorAction SilentlyContinue - } - } -} - # Function to load script paths from configuration file function Load-ScriptPaths { if (Test-Path $configFilePath) { - $savedPaths = Get-Content $configFilePath -Raw | ConvertFrom-Json - $config.ScriptPaths = $savedPaths + try { + $savedConfig = Get-Content $configFilePath -Raw | ConvertFrom-Json + if ($savedConfig.PSObject.Properties.Name -contains "ScriptPaths") { + $config.ScriptPaths = $savedConfig.ScriptPaths + } else { + Write-Warning "Invalid script_paths.json structure. Resetting to defaults." + } + } catch { + Write-Warning "Error loading script paths from ${configFilePath}: $_" + } } } # Function to save script paths to configuration file function Save-ScriptPaths { - $config.ScriptPaths | ConvertTo-Json | Set-Content $configFilePath -} - -# Function to add a new script path -function Add-ScriptPath { - param([string]$Path) - - if (-not (Test-Path $Path)) { - throw "Path does not exist: $Path" - } - - if (-not ($config.ScriptPaths -contains $Path)) { - $config.ScriptPaths += $Path - Save-ScriptPaths - return $true + try { + $configToSave = @{ ScriptPaths = $config.ScriptPaths } + $configToSave | ConvertTo-Json -Depth 10 | Set-Content $configFilePath + } catch { + Write-Warning "Error saving script paths to ${configFilePath}: $_" } - return $false } -# Function to remove a script path -function Remove-ScriptPath { - param([string]$Path) - - $config.ScriptPaths = $config.ScriptPaths | Where-Object { $_ -ne $Path } - Save-ScriptPaths +# Set window title +$Host.UI.RawUI.WindowTitle = $config.WindowTitle + +# Function to display a notification +function Show-Notification { + param( + [string]$Message, + [System.ConsoleColor]$Color = $config.Colors.Text, + [string]$Symbol = "*" + ) + Write-Host "$Symbol $Message" -ForegroundColor $Color } -# Function to fetch scripts with a loading animation +# Function to get available scripts function Get-Scripts { - $maxRetries = 3 - $retryCount = 0 - - while ($retryCount -lt $maxRetries) { - $result = Show-LoadingAnimation -LoadingText "Scanning for scripts" -ScriptBlock { - try { - # Initialize unique files dictionary - $uniqueFiles = @{} - - # First check local paths (they take precedence) - foreach ($path in $using:config.ScriptPaths) { - if (Test-Path $path) { - $localFiles = Get-ChildItem -Path $path -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue - if ($localFiles) { - $localFiles | ForEach-Object { - # Only add if not already present - if (-not $uniqueFiles.ContainsKey($_.Name)) { - $uniqueFiles[$_.Name] = @{ - name = $_.Name - download_url = $_.FullName - isLocal = $true - location = $_.DirectoryName - } - } - } - } - } - } - - # Then check GitHub for any additional scripts - try { - $apiUrl = $using:config.ApiUrl - $scripts = Invoke-RestMethod -Uri $apiUrl -ErrorAction Stop - if ($scripts) { - $scripts | Where-Object { $_.name -match '\.ps1$' } | ForEach-Object { - # Only add if we don't already have a local version - if (-not $uniqueFiles.ContainsKey($_.name)) { - $_ | Add-Member -NotePropertyName isLocal -NotePropertyValue $false -PassThru - $_ | Add-Member -NotePropertyName location -NotePropertyValue "GitHub" -PassThru - $uniqueFiles[$_.name] = $_ - } - } - } - } catch { - Write-Warning "Could not fetch GitHub scripts: $_" + $allScripts = @() + + # Get local scripts + foreach ($path in $config.ScriptPaths) { + if (Test-Path $path) { + Get-ChildItem -Path $path -Filter *.ps1 | ForEach-Object { + $allScripts += @{ + name = $_.Name + location = $_.DirectoryName + download_url= $_.FullName + isLocal = $true } - - # Convert dictionary values to array - return @($uniqueFiles.Values) } - catch { - # Return the error instead of null - return @{ Error = $_ } - } - } - - # Check if we got an error object back - if ($result -is [hashtable] -and $result.ContainsKey('Error')) { - $retryCount++ - if ($retryCount -ge $maxRetries) { - Clear-Host - Show-Notification -Message "Failed to fetch scripts after $maxRetries attempts." -Color $config.Colors.Error -Symbol $config.Symbols.Error - Write-Host "" - Show-Notification -Message "Error details: $($result.Error.Message)" -Color $config.Colors.Error - Write-Host "" - Show-Notification -Message "Press any key to retry or Esc to exit..." -Color $config.Colors.Warning - $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - if ($key.VirtualKeyCode -eq 27) { - return $null - } - $retryCount = 0 # Reset retry count if user wants to try again - } - else { - Show-Notification -Message "Connection attempt $retryCount failed. Retrying in 2 seconds..." -Color $config.Colors.Warning - Start-Sleep -Seconds 2 - } - } - elseif ($null -eq $result -or $result.Count -eq 0) { - Show-Notification -Message "No PowerShell scripts found." -Color $config.Colors.Warning -Symbol $config.Symbols.Info - Start-Sleep -Seconds 2 - - Write-Host "" - Show-Notification -Message "Press any key to retry or Esc to exit..." -Color $config.Colors.Warning - $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - if ($key.VirtualKeyCode -eq 27) { - return $null - } - $retryCount = 0 # Reset retry count if user wants to try again - } - else { - # Success - return the scripts - return $result - } - } - - # If we've exhausted all retries and user chose not to continue - return $null -} - -# Function to draw a box around text -function Show-TextBox { - param ( - [string]$Title, - [string[]]$Content - ) - - $width = ($Host.UI.RawUI.WindowSize.Width - 4) - $contentWidth = $width - 2 - - # Calculate spacing - $titlePadding = [Math]::Max(0, ($width - $Title.Length - 2) / 2) - $titleLeft = [Math]::Floor($titlePadding) - - # Top border with title - Write-Host " +" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join ("-" * $titleLeft)) -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host " $Title " -NoNewline -ForegroundColor $config.Colors.Header - Write-Host (-join ("-" * ($width - $titleLeft - $Title.Length - 2))) -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host "+" -ForegroundColor $config.Colors.Primary - - # Empty line - Write-Host " |" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join (" " * $contentWidth)) -NoNewline - Write-Host "|" -ForegroundColor $config.Colors.Primary - - # Content - foreach ($line in $Content) { - Write-Host " | " -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host $line -NoNewline - # Calculate padding to right border - $padding = $contentWidth - $line.Length - 1 - if ($padding -gt 0) { - Write-Host (-join (" " * $padding)) -NoNewline } - Write-Host "|" -ForegroundColor $config.Colors.Primary } - - # Empty line - Write-Host " |" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join (" " * $contentWidth)) -NoNewline - Write-Host "|" -ForegroundColor $config.Colors.Primary - - # Bottom border - Write-Host " +" -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host (-join ("-" * $width)) -NoNewline -ForegroundColor $config.Colors.Primary - Write-Host "+" -ForegroundColor $config.Colors.Primary -} -# Function to manage script paths -function Show-PathManager { - $selectedIndex = 0 - $cursorSupported = Set-CursorVisible $false - - try { - while ($true) { - Clear-Host - Write-Host "===== Script Path Manager =====" -ForegroundColor Cyan - Write-Host "" - Write-Host "Current script paths:" -ForegroundColor Yellow - Write-Host "(Local files take precedence over GitHub files)" -ForegroundColor DarkGray - - for ($i = 0; $i -lt $config.ScriptPaths.Count; $i++) { - if ($i -eq $selectedIndex) { - Write-Host " > " -NoNewline -ForegroundColor Green - } else { - Write-Host " " -NoNewline - } - Write-Host "$($config.ScriptPaths[$i])" - } - - Write-Host "`nControls:" -ForegroundColor Yellow - Write-Host " [A] Add new path" - Write-Host " [D] Delete selected path" - Write-Host " [ESC] Return to main menu" - - $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - - switch ($key.VirtualKeyCode) { - 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow - 40 { if ($selectedIndex -lt ($config.ScriptPaths.Count - 1)) { $selectedIndex++ } } # Down Arrow - 65 { # 'A' key - Add path - Clear-Host - Write-Host "Enter new script path (or press Enter to cancel):" -ForegroundColor Yellow - $newPath = Read-Host - if ($newPath) { - try { - if (Add-ScriptPath $newPath) { - Write-Host "Path added successfully!" -ForegroundColor Green - } else { - Write-Host "Path already exists." -ForegroundColor Yellow - } - Start-Sleep -Seconds 1 - } catch { - Write-Host "Error: $_" -ForegroundColor Red - Start-Sleep -Seconds 2 - } + # Get remote scripts if enabled + if ($config.RemoteSource -eq "GitHub") { + try { + $githubContent = Invoke-RestMethod -Uri $config.ApiUrl -UseBasicParsing + foreach ($item in $githubContent) { + if ($item.name -match "\.ps1$") { + $allScripts += @{ + name = $item.name + location = "GitHub" + download_url= $item.download_url + isLocal = $false } } - 68 { # 'D' key - Delete path - if ($config.ScriptPaths.Count -gt 1 -and $selectedIndex -lt $config.ScriptPaths.Count) { - Remove-ScriptPath $config.ScriptPaths[$selectedIndex] - if ($selectedIndex -ge $config.ScriptPaths.Count) { - $selectedIndex = $config.ScriptPaths.Count - 1 - } - } - } - 27 { return } # ESC key - Return to main menu } + } catch { + Write-Warning "Failed to fetch GitHub scripts: $_" } } - finally { - if ($cursorSupported) { - Set-CursorVisible $true - } - } + + # Combine local and remote scripts, ensuring no duplicates + $localScripts = $allScripts | Where-Object { $_.isLocal } + $remoteScripts = $allScripts | Where-Object { -not $_.isLocal } + $combinedScripts = $localScripts + $remoteScripts | Sort-Object name -Unique + return $combinedScripts } -# Function to display the script menu -function Show-Menu { - param([array]$Scripts) - - $selectedIndex = 0 - $scrollOffset = 0 - $maxVisibleItems = [Math]::Min($Scripts.Count, $Host.UI.RawUI.WindowSize.Height - 15) - - $cursorSupported = Set-CursorVisible $false - - try { - while ($true) { - Clear-Host - - # Header - $headerTitle = $config.Title - Write-Host (Get-CenteredText -Text $headerTitle) -ForegroundColor $config.Colors.Header - Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '=' -Length $headerTitle.Length)) -ForegroundColor $config.Colors.Header - Write-Host "" - - # Menu box - $helpText = "Use Up/Down arrows or j/k to navigate, Enter to select, P to manage paths, Esc to exit" - Show-TextBox -Title "Available Scripts" -Content @($helpText, "") - - # Calculate visible range - if ($selectedIndex - $scrollOffset -ge $maxVisibleItems) { - $scrollOffset = $selectedIndex - $maxVisibleItems + 1 - } - elseif ($selectedIndex -lt $scrollOffset) { - $scrollOffset = $selectedIndex - } - - $endIndex = [Math]::Min($scrollOffset + $maxVisibleItems - 1, $Scripts.Count - 1) - - # Show scroll indicators - if ($scrollOffset -gt 0) { - Write-Host " ^ More scripts above" -ForegroundColor $config.Colors.Secondary - } - - # Display visible scripts - for ($i = $scrollOffset; $i -le $endIndex; $i++) { - $scriptName = $Scripts[$i].name - - # Use if-else instead of ternary operator for PowerShell 5.1 compatibility - if ($i -eq $selectedIndex) { - $scriptSymbol = $config.Symbols.Selected - $scriptColor = $config.Colors.Highlight - Write-Host " $scriptSymbol $scriptName" -ForegroundColor $scriptColor - Write-Host " Location: $($Scripts[$i].location)" -ForegroundColor DarkGray - } else { - $scriptSymbol = $config.Symbols.Bullet - $scriptColor = $config.Colors.Text - Write-Host " $scriptSymbol $scriptName" -ForegroundColor $scriptColor - } - } - - # Show scroll indicators - if ($endIndex -lt $Scripts.Count - 1) { - Write-Host " v More scripts below" -ForegroundColor $config.Colors.Secondary - } - - Write-Host "" - Show-Notification -Message "Script $($selectedIndex + 1) of $($Scripts.Count)" -Color $config.Colors.Secondary -Symbol $config.Symbols.Info - - # Get key press - $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") - - # Process key press - switch ($key.VirtualKeyCode) { - 38 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # Up Arrow - 40 { if ($selectedIndex -lt $Scripts.Count - 1) { $selectedIndex++ } } # Down Arrow - 13 { return $selectedIndex } # Enter Key - 27 { return -1 } # Escape Key - Return to main menu - 75 { if ($selectedIndex -gt 0) { $selectedIndex-- } } # K key (vim-style) - 74 { if ($selectedIndex -lt $Scripts.Count - 1) { $selectedIndex++ } } # J key (vim-style) - 36 { $selectedIndex = 0 } # Home key - 35 { $selectedIndex = $Scripts.Count - 1 } # End key - 33 { $selectedIndex = [Math]::Max(0, $selectedIndex - $maxVisibleItems) } # Page Up - 34 { $selectedIndex = [Math]::Min($Scripts.Count - 1, $selectedIndex + $maxVisibleItems) } # Page Down - 80 { Show-PathManager; return -2 } # 'P' key - Show path manager - } - } - } - finally { - if ($cursorSupported) { - Set-CursorVisible $true - } - } +# Function to create a horizontal line +function New-HorizontalLine { + param ( + [char]$Char = '-', + [int]$Length = ($Host.UI.RawUI.WindowSize.Width - 2) + ) + return ($Char * $Length) } # Function to execute a script @@ -485,129 +152,87 @@ function Invoke-SelectedScript { } Clear-Host - Write-Host (Get-CenteredText -Text "Executing Script") -ForegroundColor $config.Colors.Header - Write-Host (Get-CenteredText -Text (New-HorizontalLine -Char '=' -Length 16)) -ForegroundColor $config.Colors.Header - Write-Host "" - - $loadingMessage = if ($Script.isLocal) { "Preparing to execute..." } else { "Downloading and preparing to execute..." } - Show-TextBox -Title $Script.name -Content @($loadingMessage) + Show-Notification -Message "Executing: $($Script.name)" -Color $config.Colors.Header try { $scriptContent = if ($Script.isLocal) { Get-Content -Path $Script.download_url -Raw } else { - Show-LoadingAnimation -LoadingText "Downloading script" -ScriptBlock { - try { - Invoke-RestMethod -Uri $using:Script.download_url -ErrorAction Stop - } - catch { - return @{ Error = $_ } - } - } + Invoke-RestMethod -Uri $Script.download_url -ErrorAction Stop } - # Check if we got an error object back - if ($scriptContent -is [hashtable] -and $scriptContent.ContainsKey('Error')) { - throw $scriptContent.Error - } - - Write-Host "" - Show-Notification -Message "Script downloaded successfully!" -Color $config.Colors.Highlight -Symbol $config.Symbols.Success - Write-Host "" + Write-Host "Script downloaded successfully!" -ForegroundColor $config.Colors.Highlight # Create a temporary script file $tempFile = [System.IO.Path]::GetTempFileName() + ".ps1" $scriptContent | Out-File -FilePath $tempFile -Encoding UTF8 - # Execute it in a new scope - Show-Notification -Message "Executing script..." -Color $config.Colors.Warning - Write-Host "" - Write-Host (New-HorizontalLine) -ForegroundColor $config.Colors.Secondary - Write-Host "" - + # Execute it & $tempFile - Write-Host "" - Write-Host (New-HorizontalLine) -ForegroundColor $config.Colors.Secondary - Write-Host "" - Show-Notification -Message "Script execution completed!" -Color $config.Colors.Highlight -Symbol $config.Symbols.Success - - # Clean up + # Cleanup Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue + Show-Notification -Message "Script execution completed!" -Color $config.Colors.Success } catch { - Write-Host "" - Show-Notification -Message "Error: $_" -Color $config.Colors.Error -Symbol $config.Symbols.Error + Show-Notification -Message "Error executing script: $_" -Color $config.Colors.Error } - Write-Host "" - Show-Notification -Message "Press any key to return to the menu..." -Color $config.Colors.Warning + Write-Host "Press any key to return to the menu..." -ForegroundColor $config.Colors.Warning $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') } -# Function to handle exiting the application -function Exit-Application { - Clear-Host - Write-Host (Get-CenteredText -Text "Thank you for using PowerShell Script Manager") -ForegroundColor $config.Colors.Header - Write-Host "" - exit -} - -# Main application loop -function Start-Application { +# Function to display the menu +function Show-ScriptMenu { + param([array]$Scripts) + while ($true) { - # Clear screen and set cursor position Clear-Host + Write-Host "`n==== PowerShell Script Manager ====" -ForegroundColor $config.Colors.Header + Write-Host "Select a script to execute:`n" - # Fetch scripts - $scripts = Get-Scripts - if (-not $scripts) { - # Check if user wants to exit - Clear-Host - Show-Notification -Message "No scripts available. Press any key to retry or Esc to exit..." -Color $config.Colors.Warning - $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') - if ($key.VirtualKeyCode -eq 27) { - Exit-Application - } - continue + for ($i = 0; $i -lt $Scripts.Count; $i++) { + Write-Host "[$i] $($Scripts[$i].name) ($($Scripts[$i].location))" } - # Show menu and get user selection - $selectedIndex = Show-Menu -Scripts $scripts + Write-Host "`n[R] Refresh | [X] Exit" + $choice = Read-Host "Enter selection" - if ($selectedIndex -eq -2) { - # User accessed path manager, refresh files - continue - } - - # Check if user pressed Esc to exit - if ($selectedIndex -eq -1) { - Exit-Application + if ($choice -match "^\d+$") { + $index = [int]$choice + if ($index -ge 0 -and $index -lt $Scripts.Count) { + Invoke-SelectedScript -Script $Scripts[$index] + } else { + Write-Host "Invalid selection." -ForegroundColor Red + Start-Sleep -Seconds 1 + } + } elseif ($choice -eq "R") { + return + } elseif ($choice -eq "X") { + Exit + } else { + Write-Host "Invalid input. Please enter a number." -ForegroundColor Red + Start-Sleep -Seconds 1 } - - # Get selected script and execute it - $selectedScript = $scripts[$selectedIndex] - Invoke-SelectedScript -Script $selectedScript } } -# Initialize configuration -Load-ScriptPaths - -# Start the application -try { +# Main function +function Start-Application { while ($true) { - $result = Start-Application - if ($result -eq "exit") { - break + Clear-Host + $scripts = Get-Scripts + if (-not $scripts) { + Show-Notification -Message "No scripts available. Press Enter to retry or type X to exit..." -Color $config.Colors.Warning + $key = Read-Host + if ($key -eq "X") { Exit } + continue } + + Show-ScriptMenu -Scripts $scripts } } -catch { - Clear-Host - Write-Host "An unexpected error occurred:" -ForegroundColor $config.Colors.Error - Write-Host $_.Exception.Message -ForegroundColor $config.Colors.Error - Write-Host "" - Write-Host "Press any key to exit..." -ForegroundColor $config.Colors.Warning - $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') -} + +# Initialize and start application +Load-ScriptPaths +Start-Application