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/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/Scripts/Registry/OptionsRegionales 7.reg b/Scripts/Registry/OptionsRegionales 7.reg deleted file mode 100644 index bb24452..0000000 Binary files a/Scripts/Registry/OptionsRegionales 7.reg and /dev/null differ 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 30e18f7..0000000 Binary files "a/Scripts/Registry/Rendre l'appareil visible sur tous les r\303\251seaux.reg" and /dev/null differ diff --git a/Scripts/Registry/exxxxx.reg b/Scripts/Registry/exxxxx.reg deleted file mode 100644 index 22d61c1..0000000 Binary files a/Scripts/Registry/exxxxx.reg and /dev/null differ 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..e2fda34 100644 --- a/menu.ps1 +++ b/menu.ps1 @@ -1,13 +1,34 @@ -# 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" -# Configuration +# 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 $baseDir "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" + RemoteSource = "GitHub" + ScriptPaths = @( + $scriptsDir # Default local path + ) Colors = @{ Primary = [System.ConsoleColor]::Cyan Secondary = [System.ConsoleColor]::DarkCyan @@ -18,309 +39,106 @@ $config = @{ Header = [System.ConsoleColor]::Magenta } Symbols = @{ - Loading = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" - Selected = "►" - Bullet = "○" - Success = "✓" - Error = "✗" - Info = "ℹ" + Loading = "-\|/" + Selected = ">" + Bullet = "*" + Success = "+" + Error = "x" + Info = "i" } WindowTitle = "PowerShell Script Manager" } -# Set window title -$Host.UI.RawUI.WindowTitle = $config.WindowTitle +# Function to load script paths from configuration file +function Load-ScriptPaths { + if (Test-Path $configFilePath) { + 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 safely manage cursor visibility -function Set-CursorVisible { - param([bool]$Visible) +# Function to save script paths to configuration file +function Save-ScriptPaths { try { - $Host.UI.RawUI.CursorVisible = $Visible - return $true + $configToSave = @{ ScriptPaths = $config.ScriptPaths } + $configToSave | ConvertTo-Json -Depth 10 | Set-Content $configFilePath } catch { - return $false + Write-Warning "Error saving script paths to ${configFilePath}: $_" } } -# 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 -} +# Set window title +$Host.UI.RawUI.WindowTitle = $config.WindowTitle -# Function to show a notification +# Function to display a notification function Show-Notification { - param ( + 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" + [string]$Symbol = "*" ) - - $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 - } - } + 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 "Fetching scripts" -ScriptBlock { - try { - $apiUrl = $using:config.ApiUrl - $scripts = Invoke-RestMethod -Uri $apiUrl -ErrorAction Stop - return $scripts | Where-Object { $_.name -match '\.ps1$' } - } - 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 + $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 } - $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 in the repository." -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 + } + + # 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 + } + } } - $retryCount = 0 # Reset retry count if user wants to try again - } - else { - # Success - return the scripts - return $result + } catch { + Write-Warning "Failed to fetch GitHub scripts: $_" } } - - # If we've exhausted all retries and user chose not to continue - return $null + + # 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 draw a box around text -function Show-TextBox { +# Function to create a horizontal line +function New-HorizontalLine { param ( - [string]$Title, - [string[]]$Content + [char]$Char = '-', + [int]$Length = ($Host.UI.RawUI.WindowSize.Width - 2) ) - - $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 display the script menu with keyboard navigation -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 ↑↓ or j/k to navigate, Enter to select, 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 - } else { - $scriptSymbol = $config.Symbols.Bullet - $scriptColor = $config.Colors.Text - } - - 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 "" - 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 -1 instead of exit - 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 - } - } - } - finally { - if ($cursorSupported) { - Set-CursorVisible $true - } - } + return ($Char * $Length) } # Function to execute a script @@ -334,112 +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 "" - - Show-TextBox -Title $Script.name -Content @("Downloading and preparing to execute...") + Show-Notification -Message "Executing: $($Script.name)" -Color $config.Colors.Header try { - $scriptContent = Show-LoadingAnimation -LoadingText "Downloading script" -ScriptBlock { - try { - Invoke-RestMethod -Uri $using:Script.download_url -ErrorAction Stop - } - catch { - return @{ Error = $_ } - } - } - - # Check if we got an error object back - if ($scriptContent -is [hashtable] -and $scriptContent.ContainsKey('Error')) { - throw $scriptContent.Error + $scriptContent = if ($Script.isLocal) { + Get-Content -Path $Script.download_url -Raw + } else { + Invoke-RestMethod -Uri $Script.download_url -ErrorAction Stop } - 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') } -# Handles 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 +# Function to display the menu +function Show-ScriptMenu { + param([array]$Scripts) + + while ($true) { + Clear-Host + Write-Host "`n==== PowerShell Script Manager ====" -ForegroundColor $config.Colors.Header + Write-Host "Select a script to execute:`n" + + for ($i = 0; $i -lt $Scripts.Count; $i++) { + Write-Host "[$i] $($Scripts[$i].name) ($($Scripts[$i].location))" + } + + Write-Host "`n[R] Refresh | [X] Exit" + $choice = Read-Host "Enter selection" + + 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 + } + } } -# Main application loop +# Main function function Start-Application { while ($true) { - # Clear screen and set cursor position Clear-Host - - # 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 - } + 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 menu and get user selection - $selectedIndex = Show-Menu -Scripts $scripts - - # Check if user pressed Esc to exit - if ($selectedIndex -eq -1) { - Exit-Application - } - - $selectedScript = $scripts[$selectedIndex] - - # Execute the selected script - Invoke-SelectedScript -Script $selectedScript + Show-ScriptMenu -Scripts $scripts } } -# Start the application -try { - Start-Application -} -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