Public/Remove-OldDrivers.ps1
Function Remove-OldDrivers { <# .SYNOPSIS Remove old third-party drivers from the system. .DESCRIPTION This function identifies and removes old third-party drivers that are no longer needed. It lists all installed third-party drivers, identifies duplicates based on the original file name, and removes older versions while keeping the most recent one. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. Returns a summary of the removal operation. .PARAMETER Interactive If specified, the function will prompt for confirmation before removing each driver. .PARAMETER MinimumVersionsToKeep Specifies the minimum number of driver versions to keep. Default is 1 (keep only the latest). .EXAMPLE PS C:\> Remove-OldDrivers -WhatIf Shows what would happen if the command were to run. The command is not run. .EXAMPLE PS C:\> Remove-OldDrivers -Interactive Prompts for confirmation before removing each driver. .EXAMPLE PS C:\> Remove-OldDrivers -MinimumVersionsToKeep 2 Keeps the 2 most recent versions of each driver. #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] [OutputType([PSCustomObject])] Param( [Parameter()] [Switch]$Interactive, [Parameter()] [ValidateRange(1, 10)] [int]$MinimumVersionsToKeep = 1 ) Begin { Write-Verbose "[BEGIN]: Remove-OldDrivers" # Initialize result object $result = [PSCustomObject]@{ TotalDriversFound = 0 DriversToRemove = 0 DriversRemoved = 0 DriversFailed = 0 Errors = @() } $OriginalFileName = @{ Name = "OriginalFileName" Expression = { ($_.OriginalFileName | Split-Path -Leaf) -replace '\s+', ' ' } } $Date = @{ Name = "Date" Expression = { try { [DateTime]$_.Date } catch { [DateTime]::MinValue } } } } Process { Write-Verbose "[PROCESS]: Remove-OldDrivers" try { # Enhanced user feedback during driver retrieval Write-Host "Starting driver analysis..." -ForegroundColor Yellow Write-Host "This process may take several minutes depending on the number of installed drivers." -ForegroundColor Cyan Write-Host "Please wait while we scan the system..." -ForegroundColor Cyan # Start progress bar for driver retrieval Write-Progress -Activity "Scanning system drivers" -Status "Retrieving driver information..." -PercentComplete 0 # Start background job to show periodic updates $progressJob = Start-Job -ScriptBlock { param($parentId) $counter = 0 while ($true) { Start-Sleep -Seconds 3 $counter++ $dots = "." * (($counter % 4) + 1) Write-Progress -ParentId $parentId -Activity "Driver Scan Progress" -Status "Still scanning$dots" -PercentComplete -1 } } -ArgumentList $PID try { # Get all third-party drivers with progress updates Write-Verbose "Retrieving installed third-party drivers..." $startTime = Get-Date $AllDrivers = Get-WindowsDriver -Online -All -ErrorAction Stop | Where-Object { $_.Driver -like 'oem*.inf' } | Select-Object -Property $OriginalFileName, Driver, ClassDescription, ProviderName, $Date, Version $endTime = Get-Date $duration = $endTime - $startTime } finally { # Clean up progress job if ($progressJob) { Stop-Job -Job $progressJob -ErrorAction SilentlyContinue Remove-Job -Job $progressJob -ErrorAction SilentlyContinue } Write-Progress -Activity "Scanning system drivers" -Completed } $result.TotalDriversFound = $AllDrivers.Count Write-Host "Driver scan completed in $([math]::Round($duration.TotalSeconds, 1)) seconds." -ForegroundColor Green Write-Host "Found $($AllDrivers.Count) third-party drivers to analyze." -ForegroundColor Green if ($AllDrivers.Count -eq 0) { Write-Warning "No third-party drivers found." return $result } Write-Verbose "Found $($AllDrivers.Count) third-party drivers" Write-Verbose ($AllDrivers | Sort-Object ClassDescription | Format-Table -AutoSize | Out-String) # Analyze drivers for duplicates Write-Host "Analyzing drivers for duplicates..." -ForegroundColor Yellow Write-Progress -Activity "Analyzing drivers" -Status "Grouping drivers by name..." -PercentComplete 0 # Group drivers and find duplicates with improved logic $DriverGroups = $AllDrivers | Where-Object { $_.OriginalFileName -and $_.OriginalFileName.Trim() } | Group-Object -Property OriginalFileName | Where-Object { $_.Count -gt 1 } Write-Progress -Activity "Analyzing drivers" -Status "Identifying old versions..." -PercentComplete 50 Write-Host "Found $($DriverGroups.Count) driver groups with multiple versions." -ForegroundColor Green Write-Verbose "Found $($DriverGroups.Count) driver groups with duplicates" # Get drivers to remove (keep the most recent N versions) $DriversToRemove = foreach ($group in $DriverGroups) { $sortedDrivers = $group.Group | Sort-Object Date -Descending $sortedDrivers | Select-Object -Skip $MinimumVersionsToKeep } Write-Progress -Activity "Analyzing drivers" -Completed $result.DriversToRemove = $DriversToRemove.Count if ($DriversToRemove.Count -eq 0) { Write-Host "No old drivers found to remove. All drivers are current versions." -ForegroundColor Green return $result } Write-Host "Identified $($DriversToRemove.Count) old driver versions for removal." -ForegroundColor Yellow Write-Verbose "Drivers to remove:" Write-Verbose ($DriversToRemove | Sort-Object ClassDescription | Format-Table | Out-String) # Show summary before removal Write-Host "`nRemoval Plan:" -ForegroundColor Cyan Write-Host " Total drivers found: $($result.TotalDriversFound)" -ForegroundColor White Write-Host " Driver groups with duplicates: $($DriverGroups.Count)" -ForegroundColor White Write-Host " Old drivers to remove: $($result.DriversToRemove)" -ForegroundColor White Write-Host " Versions to keep per driver: $MinimumVersionsToKeep" -ForegroundColor White if ($WhatIfPreference) { Write-Host "`nWhatIf: The following drivers would be removed:" -ForegroundColor Magenta $DriversToRemove | ForEach-Object { Write-Host " - $($_.Driver) ($($_.ClassDescription))" -ForegroundColor Gray } return $result } # Confirm before proceeding (if not in interactive mode) if (-not $Interactive) { Write-Host "`nThis operation will permanently remove the old driver versions." -ForegroundColor Yellow if (-not $PSCmdlet.ShouldContinue("Do you want to proceed with the removal?", "Remove Old Drivers")) { Write-Host "Operation cancelled by user." -ForegroundColor Yellow return $result } } # Process driver removal with improved progress tracking Write-Host "`nStarting driver removal process..." -ForegroundColor Yellow $totalDrivers = $DriversToRemove.Count $processedCount = 0 foreach ($Driver in $DriversToRemove) { $processedCount++ $percentComplete = [math]::Round(($processedCount / $totalDrivers) * 100) Write-Progress -Activity "Removing old drivers" ` -Status "Processing driver $processedCount of $totalDrivers ($percentComplete%)" ` -CurrentOperation "$($Driver.ClassDescription) - $($Driver.Driver)" ` -PercentComplete $percentComplete $DriverName = Split-Path -Path $Driver.Driver -Leaf $shouldRemove = $true # Handle confirmation logic if ($Interactive) { Write-Host "`nDriver Details:" -ForegroundColor Cyan Write-Host " File: $DriverName" -ForegroundColor White Write-Host " Class: $($Driver.ClassDescription)" -ForegroundColor White Write-Host " Provider: $($Driver.ProviderName)" -ForegroundColor White Write-Host " Date: $($Driver.Date)" -ForegroundColor White do { $confirmation = Read-Host "Remove this driver? [Y/N]" } while ($confirmation -notmatch '^[YyNn]$') $shouldRemove = $confirmation -match '^[Yy]$' } elseif (-not $PSCmdlet.ShouldProcess($DriverName, "Remove driver")) { $shouldRemove = $false } if (-not $shouldRemove) { Write-Host "Skipping removal of driver '$DriverName'" -ForegroundColor Yellow continue } # Remove the driver with better error handling try { Write-Verbose "Removing driver: $DriverName" Write-Host "Removing: $DriverName..." -ForegroundColor Gray if (-not $WhatIfPreference) { $pnpResult = & pnputil.exe /delete-driver $DriverName /uninstall /force 2>&1 if ($LASTEXITCODE -ne 0) { throw "pnputil.exe failed with exit code $LASTEXITCODE. Output: $pnpResult" } } Write-Host " ✓ Driver '$DriverName' removed successfully." -ForegroundColor Green $result.DriversRemoved++ } catch { $errorMsg = "Failed to remove driver '$DriverName': $($_.Exception.Message)" Write-Host " ✗ $errorMsg" -ForegroundColor Red Write-Error $errorMsg $result.DriversFailed++ $result.Errors += $errorMsg } Start-Sleep -Milliseconds 100 } } catch { $errorMsg = "Failed to retrieve driver information: $($_.Exception.Message)" Write-Error $errorMsg $result.Errors += $errorMsg } finally { Write-Progress -Activity "Removing old drivers" -Completed } } End { Write-Verbose "[END]: Remove-OldDrivers" # Display final summary Write-Host "`n" + ("=" * 50) -ForegroundColor Cyan Write-Host "DRIVER REMOVAL SUMMARY" -ForegroundColor Cyan Write-Host ("=" * 50) -ForegroundColor Cyan Write-Host " Total drivers found: $($result.TotalDriversFound)" -ForegroundColor White Write-Host " Drivers to remove: $($result.DriversToRemove)" -ForegroundColor White Write-Host " Drivers removed: $($result.DriversRemoved)" -ForegroundColor Green if ($result.DriversFailed -gt 0) { Write-Host " Drivers failed: $($result.DriversFailed)" -ForegroundColor Red } if ($result.DriversRemoved -gt 0) { Write-Host "`nRecommendation: Consider restarting your computer to complete the driver removal process." -ForegroundColor Yellow } Write-Host ("=" * 50) -ForegroundColor Cyan return $result } } |