Public/Start-SoftwareManagement.ps1

<#
.SYNOPSIS
    Interactive tool for managing installed software on Windows systems.
 
.COMPONENT
    QuickSoft
     
.DESCRIPTION
    Provides a text-based user interface for listing, searching, and uninstalling software
    installed on Windows systems. Supports both 32-bit and 64-bit applications, and handles
    both MSI and executable-based uninstallers.
 
.EXAMPLE
    Start-SoftwareManagement
    Launches the interactive software management interface.
 
.OUTPUTS
    None. This function provides an interactive interface and displays results directly.
 
.NOTES
    Name: Start-SoftwareManagement
    Author: AutomateSilent
    Version: 1.0.0
    Last Updated: 2025-02-03
     
    Requires -RunAsAdministrator
 
 
#>

function Start-SoftwareManagement {
    [CmdletBinding()]
    param()

    begin {
        # Check for admin privileges
        $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
        if (-not $isAdmin) {
            throw "This function requires administrator privileges. Please run PowerShell as Administrator."
        }

        function Get-InstalledSoftware {
            [CmdletBinding()]
            param(
                [Parameter(Position = 0)]
                [SupportsWildcards()]
                [string]$DisplayName = "*"
            )

            try {
                Write-Verbose "Searching for software matching pattern: $DisplayName"
                
                $uninstallKeys = @(
                    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
                    "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
                )

                $results = @()
                foreach ($key in $uninstallKeys) {
                    Write-Verbose "Scanning registry key: $key"
                    
                    Get-ItemProperty -Path $key -ErrorAction SilentlyContinue | 
                    Where-Object { $_.DisplayName -like $DisplayName -and $_.UninstallString } |
                    ForEach-Object {
                        $GUID = if ($_.UninstallString -match '{[A-Z0-9-]+}') { $matches[0] }
                        $results += [PSCustomObject]@{
                            DisplayName = $_.DisplayName
                            Publisher = $_.Publisher
                            Architecture = if($key -like "*Wow6432Node*") {"32-bit"} else {"64-bit"}
                            GUID = $GUID
                            Version = $_.DisplayVersion
                            UninstallString = $_.UninstallString
                            QuietUninstallString = $_.QuietUninstallString
                        }
                    }
                }
                
                Write-Verbose "Found $($results.Count) matching software entries"
                return @($results)
            }
            catch {
                Write-Error "Failed to retrieve software list: $_"
                return @()
            }
        }

        function Invoke-SoftwareUninstall {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory = $true)]
                [array]$SelectedSoftware
            )
            
            $total = $SelectedSoftware.Count
            $current = 0
            
            foreach ($software in $SelectedSoftware) {
                $current++
                Write-Host "`nUninstalling ($current/$total): $($software.DisplayName)..." -ForegroundColor Yellow
                Write-Verbose "Processing uninstall for: $($software.DisplayName)"
                
                try {
                    $uninstallCmd = if ($software.QuietUninstallString) { 
                        $software.QuietUninstallString 
                    } else { 
                        $software.UninstallString 
                    }

                    if ($software.GUID) {
                        Write-Verbose "Using MSI uninstall with GUID: $($software.GUID)"
                        Write-Host " [MSI] Using product code: $($software.GUID)" -ForegroundColor Cyan
                        $process = Start-Process "msiexec.exe" -ArgumentList "/x $($software.GUID) /quiet" -Wait -PassThru
                        
                        if ($process.ExitCode -ne 0) {
                            throw "MSI uninstall failed with exit code: $($process.ExitCode)"
                        }
                    }
                    else {
                        Write-Verbose "Parsing executable uninstall string: $uninstallCmd"
                        Write-Host " [EXE] Parsing uninstall string" -ForegroundColor Cyan
                        
                        if ($uninstallCmd -match '^"(.+?)"') {
                            $exe = $matches[1]
                            $args = $uninstallCmd.Substring($exe.Length + 2)
                        }
                        else {
                            $exe = ($uninstallCmd -split ' ')[0]
                            $args = $uninstallCmd.Substring($exe.Length)
                        }

                        Write-Verbose "Launching uninstaller: $exe $args"
                        Write-Host " Executing: $exe" -ForegroundColor DarkCyan
                        Write-Host " Arguments: $args" -ForegroundColor DarkCyan
                        
                        $process = Start-Process -FilePath $exe -ArgumentList $args -PassThru
                        
                        # Timeout handling
                        $timeout = New-TimeSpan -Minutes 5
                        $sw = [System.Diagnostics.Stopwatch]::StartNew()
                        
                        while (-not $process.HasExited -and $sw.Elapsed -lt $timeout) {
                            Write-Host "." -NoNewline -ForegroundColor Yellow
                            Start-Sleep -Seconds 1
                        }
                        
                        if (-not $process.HasExited) {
                            Write-Warning "Uninstall process timed out after 5 minutes"
                            $process.Kill()
                            throw "Uninstall process timed out"
                        }
                    }
                    
                    Write-Host "`n Uninstall completed successfully!" -ForegroundColor Green
                    Write-Verbose "Successfully uninstalled: $($software.DisplayName)"
                }
                catch {
                    Write-Error "Failed to uninstall $($software.DisplayName): $_"
                    $choice = Read-Host " Retry with MSI? (Y/N)"
                    if ($choice -eq 'Y' -and $uninstallCmd -match '{[A-Z0-9-]+}') {
                        Write-Verbose "Attempting MSI fallback with GUID: $($matches[0])"
                        Start-Process "msiexec.exe" -ArgumentList "/x $($matches[0]) /quiet" -Wait
                    }
                }
                
                Start-Sleep -Seconds 1
            }
        }
    }

    process {
        try {
            # Main menu loop
            while ($true) {
                Clear-Host
                Write-Host "=== SOFTWARE MANAGEMENT TOOL ===" -ForegroundColor Cyan
                Write-Host " 1. List All Installed Software" -ForegroundColor Yellow
                Write-Host " 2. Search Software" -ForegroundColor Yellow
                Write-Host " 3. Exit`n" -ForegroundColor Yellow
                $choice = Read-Host "Enter your choice (1-3)"

                switch ($choice) {
                    '1' { 
                        :listLoop while ($true) {
                            Clear-Host
                            Write-Host "=== ALL INSTALLED SOFTWARE ===" -ForegroundColor Cyan
                            Write-Verbose "Retrieving complete software list"
                            $software = Get-InstalledSoftware
                            
                            if ($software.Count -eq 0) {
                                Write-Host "No software found!" -ForegroundColor Yellow
                            }
                            else {
                                for ($i = 0; $i -lt $software.Count; $i++) {
                                    Write-Host " $($i+1)." -NoNewline -ForegroundColor Cyan
                                    Write-Host " $($software[$i].DisplayName)" -NoNewline
                                    Write-Host " [$($software[$i].Version)]" -ForegroundColor DarkGray
                                }
                            }

                            Write-Host "`n=== OPTIONS ===" -ForegroundColor Cyan
                            Write-Host " [Numbers] Select items to uninstall (e.g., 1,3)"
                            Write-Host " R Refresh list"
                            Write-Host " B Back to main menu`n"
                            $selection = Read-Host "Enter choice"

                            switch -Regex ($selection.ToUpper()) {
                                '^\d+(,\d+)*$' {
                                    $indices = $selection -split ',' | ForEach-Object { [int]$_ - 1 }
                                    $selected = $software[$indices]
                                    if (-not $selected) { 
                                        Write-Warning "Invalid selection!"
                                        Start-Sleep -Seconds 2
                                        continue 
                                    }
                                    
                                    $confirmation = Read-Host "Uninstall $($selected.Count) items? (Y/N)"
                                    if ($confirmation -eq 'Y') { 
                                        Write-Verbose "Initiating uninstall for $($selected.Count) items"
                                        Invoke-SoftwareUninstall $selected 
                                    }
                                }
                                'R' { continue }
                                'B' { break listLoop }
                                default { 
                                    Write-Warning "Invalid input!"
                                    Start-Sleep -Seconds 1 
                                }
                            }
                        }
                    }

                    '2' {
                        :searchLoop while ($true) {
                            Clear-Host
                            Write-Host "=== SOFTWARE SEARCH ===" -ForegroundColor Cyan
                            $searchTerm = Read-Host "Enter search term (or 'B' to cancel)"
                            if ($searchTerm -eq 'B') { break }

                            Write-Verbose "Searching for software matching: $searchTerm"
                            $software = @(Get-InstalledSoftware -DisplayName "*$searchTerm*")
                            
                            if ($software.Count -eq 0) {
                                Write-Host "No matches found!" -ForegroundColor Yellow
                                Start-Sleep -Seconds 2
                                continue
                            }

                            Clear-Host
                            Write-Host "=== SEARCH RESULTS ===" -ForegroundColor Cyan
                            for ($i = 0; $i -lt $software.Count; $i++) {
                                Write-Host " $($i+1)." -NoNewline -ForegroundColor Cyan
                                Write-Host " $($software[$i].DisplayName)" -NoNewline
                                Write-Host " [$($software[$i].Version)]" -ForegroundColor DarkGray
                            }

                            Write-Host "`n=== OPTIONS ===" -ForegroundColor Cyan
                            Write-Host " [Numbers] Select items to uninstall (e.g., 1,3)"
                            Write-Host " S New search"
                            Write-Host " B Back to main menu`n"
                            $selection = Read-Host "Enter choice"

                            switch -Regex ($selection.ToUpper()) {
                                '^\d+(,\d+)*$' {
                                    $indices = $selection -split ',' | ForEach-Object { [int]$_ - 1 }
                                    $selected = $software[$indices]
                                    if (-not $selected) { 
                                        Write-Warning "Invalid selection!"
                                        Start-Sleep -Seconds 2
                                        continue 
                                    }
                                    
                                    $confirmation = Read-Host "Uninstall $($selected.Count) items? (Y/N)"
                                    if ($confirmation -eq 'Y') { 
                                        Write-Verbose "Initiating uninstall for $($selected.Count) items"
                                        Invoke-SoftwareUninstall $selected 
                                    }
                                }
                                'S' { continue }
                                'B' { break searchLoop }
                                default { 
                                    Write-Warning "Invalid input!"
                                    Start-Sleep -Seconds 1 
                                }
                            }
                        }
                    }

                    '3' { 
                        Write-Host "Goodbye!" -ForegroundColor Cyan
                        return 
                    }

                    default {
                        Write-Warning "Invalid choice!"
                        Start-Sleep -Seconds 1
                    }
                }
            }
        }
        catch {
            Write-Error "An unexpected error occurred: $_"
        }
    }

    end {
        Write-Verbose "Software Management session ended"
    }
}