<# Reset-Webcam.ps1 Purpose: Clear false "camera in use" locks by (1) killing likely processes, (2) restarting the Camera Frame Server, (3) toggling camera drivers. Requirements: Windows 10/11, PowerShell 5+; run as Administrator. #> # Self-elevate script if not running as Administrator if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { Write-Host "Restarting script as Administrator..." Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs exit } [CmdletBinding()] param( [switch]$SkipProcessKill, # Don't kill apps (if you prefer to keep them) [switch]$AggressiveKill, # Also kill browsers (Chrome/Edge/Firefox) [switch]$RestartAudio, # Rarely needed: restarts Windows audio services [switch]$NoPause # Don’t pause at the end ) function Ensure-Admin { $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Write-Warning "This script must run as Administrator. Relaunching elevated..." $argsList = @() if ($SkipProcessKill) { $argsList += "-SkipProcessKill" } if ($AggressiveKill) { $argsList += "-AggressiveKill" } if ($RestartAudio) { $argsList += "-RestartAudio" } if ($NoPause) { $argsList += "-NoPause" } Start-Process -FilePath "powershell.exe" -Verb RunAs -ArgumentList @("-NoProfile","-ExecutionPolicy","Bypass","-File","`"$PSCommandPath`"") + $argsList exit } } function Start-Logging { $global:LogPath = Join-Path $env:TEMP ("Reset-Webcam_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date)) Start-Transcript -Path $LogPath -ErrorAction SilentlyContinue | Out-Null Write-Host "Logging to: $LogPath" } function Stop-Logging { try { Stop-Transcript | Out-Null } catch {} } function Kill-CameraApps { param([switch]$Aggressive) $softList = @( 'Teams','ms-teams','CiscoCollabHost','Webex','Zoom','zoom','zoomptch','Skype','SkypeApp', 'Discord','slack','obs64','ManyCam','SnapCamera','SnapCam','NVIDIA Broadcast','NVBroadcast.Container', 'Lghub','LogiTune','YouCam','Camera','WindowsCamera' ) # Some vendor services keep handles alive: $serviceBackends = @('obs-virtualcam-module','NvContainer','BcastDVRUserService') if ($Aggressive) { # Browsers use WebRTC; sometimes a stray tab holds the camera. $softList += @('chrome','msedge','firefox','brave','opera') } Write-Host "`n-- Stopping common camera-using apps --" foreach ($name in $softList | Select-Object -Unique) { Get-Process -Name $name -ErrorAction SilentlyContinue | ForEach-Object { try { Write-Host ("Stopping {0} (PID {1})..." -f $_.ProcessName,$_.Id) $_ | Stop-Process -Force -ErrorAction Stop } catch { Write-Verbose "Could not stop $name: $($_.Exception.Message)" } } } # Try friendly stop of some services if present foreach ($svc in $serviceBackends | Select-Object -Unique) { $s = Get-Service -Name $svc -ErrorAction SilentlyContinue if ($s -and $s.Status -eq 'Running') { Write-Host "Stopping service $($s.Name)..." try { Stop-Service -Name $s.Name -Force -ErrorAction Stop } catch {} } } } function Restart-CameraFrameServer { $svcName = 'FrameServer' # Windows Camera Frame Server Write-Host "`n-- Restarting Camera Frame Server service ($svcName) --" $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue if (-not $svc) { Write-Warning "Service $svcName not found (older builds may differ). Skipping." return } try { if ($svc.Status -eq 'Running') { Restart-Service -Name $svcName -Force -ErrorAction Stop } else { Start-Service -Name $svcName -ErrorAction Stop } Start-Sleep -Seconds 2 Write-Host "Camera Frame Server status: $((Get-Service $svcName).Status)" } catch { Write-Warning "Failed to restart $svcName: $($_.Exception.Message)" } } function Get-CameraDevices { # Prefer PnPDevice if available (Win10+) $devices = @() try { $devices = @( Get-PnpDevice -Class Camera -ErrorAction SilentlyContinue ) # Some cameras show up under 'Image' class on older drivers $devices += Get-PnpDevice -Class Image -ErrorAction SilentlyContinue } catch {} if (-not $devices -or $devices.Count -eq 0) { # Fallback via WMI $devices = Get-CimInstance Win32_PnPEntity | Where-Object { $_.PNPClass -in @('Camera','Image') } } $devices | Sort-Object -Property Status, FriendlyName -Unique } function Reset-CameraDrivers { Write-Host "`n-- Resetting camera drivers (disable/enable) --" $cams = Get-CameraDevices if (-not $cams -or $cams.Count -eq 0) { Write-Warning "No camera-class devices found." return } foreach ($cam in $cams) { $name = $cam.FriendlyName $id = $cam.InstanceId if (-not $id) { $id = $cam.PNPDeviceID } # WMI fallback Write-Host "Toggling: $name" try { # Try PnP cmdlets first if (Get-Command Disable-PnpDevice -ErrorAction SilentlyContinue) { Disable-PnpDevice -InstanceId $id -Confirm:$false -ErrorAction Stop Start-Sleep -Seconds 2 Enable-PnpDevice -InstanceId $id -Confirm:$false -ErrorAction Stop } else { # Fallback: WMI (less reliable for toggling); advise user if missing PnP cmdlets Write-Warning "PnP cmdlets not available; please run in Windows PowerShell 5+ on Win10/11." } Start-Sleep -Seconds 2 } catch { Write-Warning "Failed to toggle $name ($id): $($_.Exception.Message)" } } } function Restart-AudioStack { Write-Host "`n-- Restarting Windows audio services (optional) --" $audioSvcs = @('Audiosrv','AudioEndpointBuilder') foreach ($svc in $audioSvcs) { try { $s = Get-Service -Name $svc -ErrorAction Stop if ($s.Status -eq 'Running') { Restart-Service -Name $svc -Force -ErrorAction Stop } else { Start-Service -Name $svc -ErrorAction Stop } } catch { Write-Warning "Audio service $svc restart failed: $($_.Exception.Message)" } } } # ------------ MAIN ------------ Ensure-Admin Start-Logging try { Write-Host "=== Webcam Reset Routine ===" if (-not $SkipProcessKill) { Kill-CameraApps -Aggressive:$AggressiveKill } else { Write-Host "-- Skipping process kill step (per -SkipProcessKill) --" } Restart-CameraFrameServer Reset-CameraDrivers if ($RestartAudio) { Restart-AudioStack } Write-Host "`nDone. Try your camera again." Write-Host "If the problem persists, unplug/replug external webcams or reboot as a last resort." Write-Host "Full log: $LogPath" } finally { Stop-Logging if (-not $NoPause) { Write-Host "`nPress Enter to close..." [void][System.Console]::ReadLine() } }