11<#
22. SYNOPSIS
33A comprehensive script to manage the Virtual Display Driver.
4- It can install, uninstall, enable, disable, toggle, and check the status of the driver.
4+ It can enable, disable, toggle, and check the status of the driver.
55
66. DESCRIPTION
77This script handles the full lifecycle of the Virtual Display Driver.
88- If no action is specified, it interactively prompts the user to select one.
9- - For install/uninstall, it automatically resolves the correct version of Microsoft's DevCon utility using a nearest-build matching algorithm for maximum compatibility.
109- For enable/disable/toggle/status, it uses fast, built-in PowerShell commands.
1110- It requires Administrator privileges and will self-elevate if needed by re-launching in a new window.
1211- All temporary files are automatically cleaned up unless in Verbose mode for diagnostics.
1312
1413. PARAMETER Action
1514Specifies the operation to perform. If omitted, the script will prompt for a selection.
1615
17- . PARAMETER DriverVersion
18- Used only with the 'install' action to specify a version of the Virtual Display Driver, otherwise defaults to 'latest'.
1916
2017. PARAMETER Json
2118If present, all output will be in JSON format for easy parsing by other programs.
@@ -32,12 +29,6 @@ If present, prints detailed diagnostic information and prevents the temporary fo
3229# Run without an action to get an interactive menu. The window will pause when finished.
3330.\virtual-driver-manager.ps1
3431
35- # Install the driver and pause for review afterwards (default behavior).
36- .\virtual-driver-manager.ps1 -Action install
37-
38- # Uninstall the driver and close the window automatically.
39- .\virtual-driver-manager.ps1 -Action uninstall -Silent
40-
4132. EXAMPLE
4233# --- USAGE FROM CMD.EXE OR ANOTHER PROCESS ---
4334
@@ -50,7 +41,7 @@ powershell.exe -ExecutionPolicy Bypass -File .\virtual-driver-manager.ps1 -Actio
5041[CmdletBinding (SupportsShouldProcess = $true )]
5142param (
5243 [Parameter (Mandatory = $false )]
53- [ValidateSet (' install ' , ' uninstall ' , ' enable' , ' disable' , ' toggle' , ' status' )]
44+ [ValidateSet (' enable' , ' disable' , ' toggle' , ' status' )]
5445 [string ]$Action ,
5546
5647 [Parameter (Mandatory = $false )]
@@ -104,7 +95,7 @@ if (-not $PSBoundParameters.ContainsKey('Action')) {
10495 exit 1
10596 }
10697
107- $options = ' install ' , ' uninstall ' , ' enable' , ' disable' , ' toggle' , ' status'
98+ $options = ' enable' , ' disable' , ' toggle' , ' status'
10899 Write-Host " `n Please select an action to perform:" - ForegroundColor Yellow
109100
110101 for ($i = 0 ; $i -lt $options.Length ; $i ++ ) {
@@ -162,10 +153,6 @@ if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti
162153# ----------------------------------------------------------------------
163154# SECTION 4: SETUP AND GUARANTEED CLEANUP
164155# ----------------------------------------------------------------------
165- # Create a unique temporary directory to avoid conflicts if the script is run multiple times concurrently.
166- $tempDir = Join-Path $env: TEMP " VDD-Manager-$ ( Get-Random ) "
167- New-Item - ItemType Directory - Path $tempDir - Force | Out-Null
168- Write-Verbose " Created temporary directory at $tempDir "
169156
170157# Use a try/catch/finally block to ensure that no matter what happens (success or error),
171158# the 'finally' block will ALWAYS run to clean up temporary files.
@@ -196,135 +183,7 @@ try {
196183 # ----------------------------------------------------------------------
197184 # SECTION 6: MAIN ACTION LOGIC
198185 # ----------------------------------------------------------------------
199- if ($Action -in @ (' install' , ' uninstall' )) {
200-
201- # --- 6a. Automatically Resolve DevCon Hash ---
202- Write-Log - Message " Action '$Action ' requires the DevCon utility. Determining correct version..."
203- $osInfo = Get-CimInstance - ClassName Win32_OperatingSystem
204- $osBuild = [int ]$osInfo.BuildNumber
205-
206- $osMajorVersion = ' Server'
207- if ($osInfo.Caption -match ' Windows 11' ) { $osMajorVersion = ' 11' }
208- elseif ($osInfo.Caption -match ' Windows 10' ) { $osMajorVersion = ' 10' }
209-
210- Write-Log - Message " Detected: Windows $osMajorVersion (Build $osBuild )"
211- Write-Verbose " OS detection complete. Starting DevCon source matching."
212-
213- # This map is the core of the matching logic. It translates the human-readable version names
214- # from the devcon_sources.json file into their corresponding OS build numbers for comparison.
215- $versionNameToBuildMap = @ {
216- " Windows 11 version 24H2" = 26100 ; " Windows 11 version 23H2" = 22631 ; " Windows 11 version 22H2" = 22621 ;
217- " Windows 11 version 21H2" = 22000 ; " Windows Server 2022" = 20348 ; " Windows 10 version 2004" = 19041 ;
218- " Windows 10 version 1903" = 18362 ; " Windows 10 version 1809" = 17763 ; " Windows Server 2019" = 17763
219- }
220-
221- $sourcesUrl = " https://raw.githubusercontent.com/Drawbackz/DevCon-Installer/refs/heads/master/devcon_sources.json"
222- Write-Verbose " Fetching DevCon sources from $sourcesUrl "
223- $devconSources = Invoke-RestMethod - Uri $sourcesUrl
224-
225- # Enrich the downloaded source list with a calculated 'BuildNumber' property to make it sortable.
226- $enrichedSources = $devconSources | ForEach-Object {
227- $source = $_ ; $matchedBuild = 0
228- foreach ($entry in $versionNameToBuildMap.GetEnumerator ()) {
229- if ($source.Name.Contains ($entry.Key )) { $matchedBuild = $entry.Value ; break }
230- }
231- $source | Add-Member - MemberType NoteProperty - Name " BuildNumber" - Value $matchedBuild ; $source
232- } | Where-Object { $_.BuildNumber -gt 0 }
233-
234- $osFamilySources = $enrichedSources | Where-Object { $_.Name -like " *Windows $osMajorVersion *" -or ($osMajorVersion -eq ' Server' -and $_.Name -like " *Server*" ) }
235-
236- # --- NEAREST-BUILD MATCHING LOGIC ---
237- # 1. Try for a perfect match first.
238- $bestMatch = $osFamilySources | Where-Object { $_.BuildNumber -eq $osBuild } | Select-Object - First 1
239-
240- if (-not $bestMatch ) {
241- Write-Log - Message " No exact DevCon match for build $osBuild . Finding nearest available version..." - Status ' Warning'
242- # 2. If no exact match, find the newest version that is still older than (or equal to) the current OS.
243- # This is the safest fallback, as it guarantees API compatibility.
244- $bestOlderMatch = $osFamilySources | Where-Object { $_.BuildNumber -le $osBuild } | Sort-Object BuildNumber - Descending | Select-Object - First 1
245-
246- # 3. If no older versions exist, find the oldest version that is newer than the current OS.
247- # This is a less-safe fallback but better than failing completely.
248- $bestNewerMatch = $osFamilySources | Where-Object { $_.BuildNumber -gt $osBuild } | Sort-Object BuildNumber | Select-Object - First 1
249-
250- if ($bestOlderMatch ) { $bestMatch = $bestOlderMatch } elseif ($bestNewerMatch ) { $bestMatch = $bestNewerMatch }
251- Write-Verbose " Nearest older match: $ ( $bestOlderMatch.Name ) | Nearest newer match: $ ( $bestNewerMatch.Name ) "
252- }
253-
254- if (-not $bestMatch ) { throw " Could not find any compatible DevCon versions for your OS (Build $osBuild )." }
255-
256- $devconHash = ($bestMatch.Sources | Where-Object { $_.Architecture -eq ' X64' }).Sha256
257- Write-Log - Message " Using DevCon Source: $ ( $bestMatch.Name ) (Build $ ( $bestMatch.BuildNumber ) )" - Status ' Success'
258- Write-Verbose " Selected DevCon Hash (X64): $devconHash "
259- if (-not $devconHash ) { throw " Could not find a 64-bit DevCon hash in the selected source: $ ( $bestMatch.Name ) " }
260-
261- # --- 6b. Acquire DevCon Utility ---
262- Write-Log - Message " Acquiring secure DevCon utility..."
263- $devconInstallerUrl = " https://github.com/Drawbackz/DevCon-Installer/releases/download/1.4-rc/Devcon.Installer.exe"
264- $devconInstallerPath = Join-Path $tempDir " Devcon.Installer.exe"
265- Write-Verbose " Downloading DevCon Installer from $devconInstallerUrl "
266- Invoke-WebRequest - Uri $devconInstallerUrl - OutFile $devconInstallerPath
267- Write-Verbose " DevCon Installer downloaded successfully."
268-
269- # Use the hash to ensure the DevCon-Installer utility downloads the correct, secure version of DevCon.
270- $devconArgs = " install -hash $devconHash -update -dir `" $tempDir `" "
271- Write-Verbose " Running DevCon Installer with arguments: $devconArgs "
272- Start-Process - FilePath $devconInstallerPath - ArgumentList $devconArgs - Wait - NoNewWindow
273-
274- $devconExe = Join-Path $tempDir " devcon.exe"
275- if (-not (Test-Path $devconExe )) { throw " Failed to acquire devcon.exe." }
276- Write-Verbose " devcon.exe acquired successfully at $devconExe "
277-
278- # --- 6c. Execute Install or Uninstall ---
279- if ($Action -eq ' install' ) {
280- Write-Log - Message " Starting driver installation..."
281- $downloadUrl = $null
282- if ($DriverVersion -eq " latest" ) {
283- $apiUrl = " https://api.github.com/repos/VirtualDrivers/Virtual-Display-Driver/releases/latest"
284- # Technical Choice: Many APIs, including GitHub's, require a User-Agent header.
285- # Omitting this can lead to connection errors (like 403 Forbidden or 404 Not Found).
286- $headers = @ { " User-Agent" = " PowerShell-VDD-Manager-Script" }
287-
288- Write-Verbose " Querying GitHub API for latest driver release: $apiUrl "
289- $releaseInfo = Invoke-RestMethod - Uri $apiUrl - Headers $headers
290- Write-Verbose " API call successful. Latest release found: $ ( $releaseInfo.tag_name ) "
291-
292- $asset = $releaseInfo.assets | Where-Object { $_.name -match " x64\.zip$" } | Select-Object - First 1
293- if (-not $asset ) { throw " Could not find a 64-bit driver asset (x64.zip) in the latest GitHub release." }
294-
295- $downloadUrl = $asset.browser_download_url
296- Write-Verbose " Found driver asset: $ ( $asset.name ) "
297- }
298- else {
299- $downloadUrl = " https://github.com/VirtualDrivers/Virtual-Display-Driver/releases/download/$DriverVersion /Signed-Driver-v$DriverVersion -x64.zip"
300- Write-Verbose " Using specified driver version: $DriverVersion "
301- }
302-
303- if (-not $downloadUrl ) { throw " Could not determine a valid driver download URL for version '$DriverVersion '." }
304-
305- Write-Verbose " Downloading driver from URL: $downloadUrl "
306- $driverZipPath = Join-Path $tempDir " driver.zip"
307- Invoke-WebRequest - Uri $downloadUrl - OutFile $driverZipPath
308- Write-Verbose " Driver ZIP file downloaded to $driverZipPath "
309-
310- Expand-Archive - Path $driverZipPath - DestinationPath $tempDir - Force
311- Write-Verbose " Driver archive expanded."
312-
313- # Use DevCon to install the driver by pointing to its INF file and specifying its unique Hardware ID.
314- # "Root\MttVDD" identifies this as a root-enumerated virtual device.
315- Write-Verbose " Running DevCon to install the driver..."
316- & $devconExe install (Join-Path $tempDir " MttVDD.inf" ) " Root\MttVDD"
317- }
318- else { # Action must be 'uninstall'
319- Write-Log - Message " Starting driver uninstallation..."
320- if (Get-VirtualDisplayDevice ) {
321- Write-Verbose " Driver found. Preparing to remove."
322- & $devconExe remove " Root\MttVDD"
323- }
324- else { Write-Log - Message " Driver is not currently installed. Nothing to do." }
325- }
326- }
327- elseif ($Action -in @ (' enable' , ' disable' , ' toggle' )) {
186+ if ($Action -in @ (' enable' , ' disable' , ' toggle' )) {
328187 $device = Get-VirtualDisplayDevice
329188 if (-not $device ) { Write-Log - Message " Device not found. Cannot perform '$Action '. Please install the driver first." - Status ' Warning' }
330189 elseif ($Action -eq ' enable' ) { Write-Log - Message " Enabling device: $ ( $device.FriendlyName ) ..." ; $device | Enable-PnpDevice - Confirm:$false }
@@ -383,16 +242,7 @@ finally {
383242 # This block ALWAYS runs, ensuring cleanup happens after success or failure.
384243 # If the user ran with -Verbose, we assume they are debugging.
385244 # We will NOT delete the temporary folder so they can inspect its contents.
386- if ($PSBoundParameters.ContainsKey (' Verbose' )) {
387- Write-Verbose " Verbose mode is active. Temporary directory will not be deleted so you can inspect its contents: $tempDir "
388- }
389- else {
390- if (Test-Path $tempDir ) {
391- # This Write-Verbose message will not be visible without -Verbose, but is good practice.
392- Write-Verbose " Cleaning up temporary directory: $tempDir "
393- # Remove-Item -Path $tempDir -Recurse -Force
394- }
395- }
245+
396246}
397247
398248# Add a final pause unless in Silent or JSON mode so the user can see the output.
0 commit comments