Skip to main content

How to Detach All Mounted VHDs in Batch Script

When you've been working with multiple virtual hard disks throughout the day, such as testing, extracting files, or running maintenance, you may have several VHDs mounted simultaneously. Before shutting down your server, performing a backup, or moving VHD files to another location, you need to safely detach all of them. Leaving VHDs mounted while copying or moving their files will result in data corruption or file locks. A Batch script can enumerate all currently mounted VHDs and detach them in one automated sweep.

This guide will explain how to safely release all virtual disk mounts.

Why Detaching Matters

ScenarioVHD Left MountedVHD Properly Detached
Copying the VHDX fileFile locked or copy is corruptClean copy succeeds
Running backup softwareBackup may skip locked file or capture inconsistent stateBackup captures complete, consistent file
Moving VHDX to another hostMove fails with "file in use"Move succeeds
Shutting down the serverPending writes may be lostAll writes flushed, clean shutdown
Data Corruption Risk

Copying, moving, or backing up a VHDX file while it is mounted can produce a corrupt disk image. The copy may contain partial writes, inconsistent metadata, or incomplete file system structures. Always detach before performing file-level operations on VHD/VHDX files.

Method 1: Automatic Bulk Detach

This method discovers all currently mounted VHDs using PowerShell and detaches each one, with reporting on what was found and released.

@echo off
setlocal

:: Verify admin privileges
net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Detaching VHDs requires administrator privileges. >&2
endlocal
exit /b 1
)

set "LogFile=%~dp0vhd_operations.log"

echo [INFO] Scanning for mounted virtual hard disks...

:: Find and detach all mounted VHDs
for /f "tokens=1-2 delims=|" %%a in (
'powershell -NoProfile -Command "$vhds = @(Get-Disk | Where-Object Location -like *.vhd*); Write-Output ([string]$vhds.Count + [char]124 + (($vhds | ForEach-Object Location) -join [char]59))"'
) do (
set "Count=%%a"
set "Paths=%%b"
)

if "%Count%"=="0" (
echo [OK] No mounted VHDs found. Nothing to detach.
endlocal
exit /b 0
)

echo [INFO] Found %Count% mounted VHD(s^).
echo.

:: Show what will be detached
powershell -NoProfile -Command "Get-Disk | Where-Object Location -like '*.vhd*' | ForEach-Object { [PSCustomObject]@{ Disk = $_.Number; 'Size GB' = [math]::Round($_.Size / 1GB, 1); 'Location' = $_.Location } } | Format-Table -AutoSize"

echo.

:: Confirm before detaching
set /p "Confirm=Detach all %Count% VHD(s)? (YES/no): "
if /i not "%Confirm%"=="YES" (
echo [INFO] Cancelled. No VHDs were detached.
endlocal
exit /b 0
)

echo.
echo [ACTION] Detaching all mounted VHDs...

:: Perform the detach
set "Detached=0"
set "Failed=0"

powershell -NoProfile -Command "$vhds = Get-Disk | Where-Object Location -like '*.vhd*'; foreach ($vhd in $vhds) { try { Dismount-VHD -DiskNumber $vhd.Number -Confirm:$false -ErrorAction Stop; Write-Host (' [OK] Detached: ' + $vhd.Location) } catch { Write-Host (' [FAIL] ' + $vhd.Location + ' - ' + $_.Exception.Message) -ForegroundColor Red } }"

:: Verify all are detached
for /f "delims=" %%r in (
'powershell -NoProfile -Command "@(Get-Disk | Where-Object Location -like *.vhd*).Count"'
) do set "Remaining=%%r"

echo.

if "%Remaining%"=="0" (
echo [OK] All VHDs successfully detached.
) else (
echo [WARNING] %Remaining% VHD(s^) could not be detached. >&2
echo These may have open file handles. Close any applications using >&2
echo files on the mounted drives and try again. >&2
)

:: Log the operation
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format s"'
) do echo [%%t] DETACH ALL: %Count% found, %Remaining% remaining on %COMPUTERNAME% by %USERNAME% >> "%LogFile%"

endlocal
exit /b 0

Why Dismount-VHD instead of DiskPart:

  • Dismount-VHD is designed specifically for virtual disks and handles the complete unmount process (flush buffers, remove drive letter, release file lock).
  • Get-Disk | Where-Object { $_.Location -like '*.vhd*' } automatically finds all mounted VHDs regardless of how they were mounted, whether by DiskPart, PowerShell, or double-clicking the file.
  • DiskPart's detach vdisk requires you to specify each VHD file path individually (you must already know which files are mounted). PowerShell discovers them automatically.
Close Applications First

If any application (Explorer window, file manager, database, antivirus scanner) has files open on a mounted VHD drive, the detach will fail for that VHD with a "file in use" error. Close all applications accessing the mounted drives before running this script.

Method 2: Non-Interactive Bulk Detach (for Automation)

For use in scheduled tasks, pre-backup scripts, or shutdown workflows where no interactive confirmation is wanted.

@echo off
setlocal

set "LogFile=%~dp0vhd_operations.log"

net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)

echo [ACTION] Detaching all mounted VHDs (non-interactive^)...

powershell -NoProfile -Command "$vhds = @(Get-Disk | Where-Object Location -like '*.vhd*'); if ($vhds.Count -eq 0) { Write-Host '[OK] No mounted VHDs found.'; exit 0 }; Write-Host ('[INFO] Found ' + $vhds.Count + ' mounted VHD(s). Detaching...'); $detached = 0; $failed = 0; foreach ($vhd in $vhds) { try { Dismount-VHD -DiskNumber $vhd.Number -Confirm:$false -ErrorAction Stop; Write-Host (' [OK] ' + $vhd.Location); $detached++ } catch { Write-Host (' [FAIL] ' + $vhd.Location + ': ' + $_.Exception.Message); $failed++ } }; Write-Host ''; Write-Host ('Detached: ' + $detached + ' Failed: ' + $failed); if ($failed -gt 0) { exit 1 } else { exit 0 }"

set "PSResult=%errorlevel%"

:: Log
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format s"'
) do echo [%%t] BULK DETACH (auto^): Result=%PSResult% on %COMPUTERNAME% >> "%LogFile%"

endlocal
exit /b %PSResult%

Integration with backup workflows:

:: Example: Pre-backup script
call detach_all_vhds.bat
if errorlevel 1 (
echo [WARNING] Some VHDs could not be detached. Backup may be inconsistent. >&2
)

:: Run backup
robocopy D:\VMs \\BackupServer\VMBackups /MIR /R:1 /W:1

Integration with shutdown scripts:

:: Add to Group Policy shutdown script or scheduled task
call detach_all_vhds.bat
shutdown /s /t 30 /c "Scheduled maintenance shutdown"

Method 3: DiskPart Detach for Known VHDs

When you know exactly which VHDs are mounted and want to detach specific ones (not all), use DiskPart with explicit paths.

@echo off
setlocal

net session >nul 2>&1
if errorlevel 1 (
echo [ERROR] Administrator privileges required. >&2
endlocal
exit /b 1
)

:: Define the VHDs to detach
set "VHD[0]=D:\VMs\TestServer.vhdx"
set "VHD[1]=D:\VMs\DevDatabase.vhdx"
set "VHD[2]=D:\Backups\MonthlyArchive.vhdx"
set "VHDCount=3"

echo [ACTION] Detaching specified VHDs...

set "DPScript=%TEMP%\dp_detachall_%RANDOM%.txt"

:: Build the DiskPart script
(
for /L %%i in (0, 1, 2) do (
call :WriteDetach %%i
)
) > "%DPScript%"

diskpart /s "%DPScript%" >nul 2>&1

del "%DPScript%" 2>nul

echo [OK] Detach commands executed. Verify with:
echo powershell -Command "Get-Disk | Where-Object { $_.Location -like '*.vhd*' }"

endlocal
exit /b 0

:WriteDetach
setlocal EnableDelayedExpansion
set "Path=!VHD[%1]!"
if defined Path (
echo select vdisk file="%Path%"
echo detach vdisk
)
endlocal
exit /b 0
When to Use Method 1 vs. Method 3
  • Method 1 (automatic discovery): Use when you want to detach ALL VHDs regardless of which ones are mounted. Best for pre-backup and pre-shutdown cleanup.
  • Method 3 (explicit paths): Use when you want to detach specific VHDs while leaving others mounted. Best for targeted cleanup in development environments.

Method 4: Verification - List Currently Mounted VHDs

Before and after detaching, verify what's mounted.

@echo off
setlocal

echo [INFO] Currently mounted VHDs:
echo --------------------------------------------------

powershell -NoProfile -Command "$vhds = Get-Disk | Where-Object Location -like '*.vhd*'; if (-not $vhds) { Write-Host ' No VHDs are currently mounted.'; exit 0 }; $vhds | ForEach-Object { $partitions = Get-Partition -DiskNumber $_.Number -ErrorAction SilentlyContinue; $letters = ($partitions | Where-Object DriveLetter | ForEach-Object { $_.DriveLetter + ':' }) -join ', '; if (-not $letters) { $letters = '(no letter)' }; [PSCustomObject]@{ Disk = $_.Number; Drive = $letters; 'Size GB' = [math]::Round($_.Size / 1GB, 1); ReadOnly = if ($_.IsReadOnly) { 'Yes' } else { 'No' }; Path = $_.Location } } | Format-Table -AutoSize -Wrap"

echo --------------------------------------------------

endlocal
exit /b 0

What to check:

  • ReadOnly = Yes: This VHD was mounted for inspection only. Safe to detach.
  • ReadOnly = No: This VHD is mounted read-write. Ensure all writes are complete and applications are closed before detaching.
  • (no letter): The VHD is mounted but has no drive letter assigned. It may be mounted to a folder path (mount point) or just attached without any access path.

How to Avoid Common Errors

Wrong Way: Copying VHDX Files While Mounted

:: CORRUPTS the copy - VHDX file is locked and inconsistent during mount
copy D:\VMs\TestServer.vhdx E:\Backup\TestServer.vhdx

A mounted VHDX file has active write caches, pending metadata updates, and file locks. A copy captures an inconsistent snapshot that may be unreadable.

Correct Way: Detach all VHDs first (Methods 1–2), then perform the copy/move/backup.

Problem: "The Disk Is in Use"

If an application, Explorer window, or background process (antivirus, indexing) has files open on a mounted VHD, Dismount-VHD fails.

Solution:

  1. Close all Explorer windows showing the mounted drive.
  2. Close any applications with files open on the drive.
  3. Check for processes using the drive:
powershell -NoProfile -Command "Get-Process | Where-Object { $_.Modules.FileName -like 'V:\*' }"
  1. If the process cannot be closed, use -Force with Dismount-VHD (may cause data loss if writes are pending).

Problem: Dismount-VHD Not Available

Dismount-VHD requires the Hyper-V PowerShell module. On systems without Hyper-V features installed, the cmdlet is not available.

Solution: Use DiskPart as a fallback:

:: DiskPart detach for a specific VHD
(
echo select vdisk file="D:\VMs\TestServer.vhdx"
echo detach vdisk
) > "%TEMP%\detach.txt"
diskpart /s "%TEMP%\detach.txt"
del "%TEMP%\detach.txt"

Or install just the Hyper-V PowerShell module without the full Hyper-V role:

powershell -Command "Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell -NoRestart"

Problem: VHD Mounted by Double-Click Won't Detach

Windows allows mounting VHDs by double-clicking the file. These mounts are handled by the Virtual Disk service and may not appear the same way as DiskPart-mounted VHDs in all tools.

Solution: Get-Disk | Where-Object { $_.Location -like '*.vhd*' } finds VHDs regardless of how they were mounted. Dismount-VHD works for all mount methods.

Best Practices and Rules

1. Detach Before Backup

Add the non-interactive detach script (Method 2) as the first step in any backup workflow that copies VHDX files. This ensures the backup agent captures consistent, unlocked files.

2. Verify After Detaching

After detaching, run Method 4 to confirm no VHDs remain mounted. An empty result means all are cleanly released.

3. Close Applications Before Detaching

Dismount-VHD cannot detach a VHD with open file handles. Close Explorer windows, databases, and other applications using the mounted drives before running the detach script.

4. Use Non-Interactive Mode for Automation

Method 1 (interactive) asks for confirmation, appropriate for manual use. Method 2 (non-interactive) detaches without prompting, appropriate for scheduled tasks, backup workflows, and shutdown scripts.

5. Log Every Bulk Detach

Track when VHDs were detached and by whom. This is especially important in shared environments where multiple administrators may mount and forget to unmount VHDs.

6. Schedule as a Pre-Shutdown Step

Use Group Policy or Task Scheduler to run Method 2 before system shutdown. This ensures all VHDs are cleanly released, preventing "dirty" VHD files that require recovery on the next mount.

Conclusions

Detaching all mounted VHDs is critical hygiene for virtual storage management. By using automatic VHD discovery with Get-Disk, per-VHD error handling with Dismount-VHD, and post-detach verification, you ensure that no virtual disk is left mounted when it shouldn't be, preventing data corruption, backup failures, and file lock conflicts. Integrating this step into your backup and shutdown workflows makes it automatic rather than something that depends on human memory.