Using DeleteDosDevice Safely — Step-by-Step Guide and ExamplesWindows provides several legacy APIs for managing DOS device names (also called symbolic link names or MS-DOS device names) that let programs create, modify, or remove mappings between short device names (like “COM1”, “LPT1”, or custom names such as “MYDRIVE:”) and kernel objects (device namespaces, volumes, or symbolic links). One such API is DeleteDosDevice. Although largely superseded by newer mechanisms, DeleteDosDevice still appears in legacy code and may be used in low-level utilities, drivers, installer scripts, and migration tools. Because misuse can break drive mappings, interfere with services, or leave the system in an inconsistent state, it’s important to understand how to call it correctly and safely.
This article covers:
- What DeleteDosDevice does and how it differs from related APIs
- When and why you might need it
- Permissions, limitations, and risk factors
- A step‑by‑step safe procedure for using DeleteDosDevice
- Practical examples in C++ and PowerShell (with fallbacks and safe checks)
- Troubleshooting and alternatives
What DeleteDosDevice does
DeleteDosDevice removes an MS‑DOS device name from the calling process’s namespace or, if appropriate flags are used, from the global namespace. In practice, this means it severs the association between a symbolic DOS name (for example, “Z:”) and the kernel object it pointed to. After a successful call, attempts to use the removed DOS name will fail until the name is recreated.
Related APIs:
- DefineDosDevice — creates or modifies DOS device names.
- QueryDosDevice — enumerates or examines the target of a DOS name.
- CreateSymbolicLink / DeviceIoControl — other kernel/driver mechanisms for device namespaces.
Key differences:
- DefineDosDevice creates or modifies name mappings; DeleteDosDevice removes them.
- DeleteDosDevice affects the namespace in which the name was defined: per-process vs. global depending on flags.
Permissions, flags, and scope
- DeleteDosDevice requires the calling process to run with sufficient privileges to modify the namespace where the name exists. Removing a mapping created by another process or the system may fail unless appropriate rights are granted (for example, elevated token / admin privileges).
- The function accepts flags (same flags used by DefineDosDevice) such as DDD_REMOVE_DEFINITION and DDD_EXACT_MATCH_ON_REMOVE, and DDD_RAW_TARGET_PATH. These flags change matching behavior and how the remove operation interprets the target string. Use them carefully:
- DDD_EXACT_MATCH_ON_REMOVE ensures only exact name matches are removed.
- DDD_REMOVE_DEFINITION controls whether the definition is removed even if multiple links exist.
- DDD_RAW_TARGET_PATH affects how the path is treated (raw NT path vs. DOS path).
- The namespace affected can be the current process namespace or the global namespace; inadvertent global removals can affect other processes.
Risks and safety considerations
- Removing a drive letter or device mapping that other processes depend on may break running applications, services, scheduled tasks, or mounted volumes.
- Removing system-created entries (for example, mappings the system uses for devices) can destabilize device access until Windows recreates them or the system is rebooted.
- Race conditions: another process may re-create or remove the mapping concurrently.
- Incorrect string formatting or mismatched flags can result in DeleteDosDevice silently failing or removing the wrong mapping.
Principles for safe use:
- Always verify the current mapping using QueryDosDevice before removing.
- Prefer removing only definitions you created (track creation calls, use exact-match flags).
- Run elevated only when necessary and avoid blanket global deletions.
- If possible, perform operations during maintenance windows or while the target device is offline/unmounted.
- Provide clear logging and recovery paths (for example, code to re-create mappings on failure).
Step‑by‑step safe procedure
- Identify the exact DOS name you want to remove (e.g., “Z:”) and confirm why it exists.
- Query the current mapping:
- Use QueryDosDevice to retrieve the target(s) of the DOS name and ensure the target matches expectations.
- If QueryDosDevice fails to find the name, abort—nothing to remove.
- Check ownership and context:
- Determine whether the mapping was created by your process, a service, or the system. Removing mappings created by other entities should be avoided unless you have a clear plan.
- Decide flags:
- Use DDD_EXACT_MATCH_ON_REMOVE to prevent accidental wildcard removals.
- Use DDD_RAW_TARGET_PATH only if you are working with raw NT paths and you know the exact format.
- Elevate only if necessary:
- If QueryDosDevice shows the mapping is global or owned by another user or system component, request elevation / run as administrator and warn the user.
- Notify / quiesce dependent components:
- If possible, stop services or close applications that might be using the mapping (or dismount the volume).
- Call DeleteDosDevice and check the return code:
- On failure, call GetLastError and handle specific error codes (access denied, not found, invalid parameter). Log errors.
- Verify removal:
- Re-run QueryDosDevice to ensure the name is gone.
- Provide rollback:
- If something breaks, be ready to recreate the mapping using DefineDosDevice (or by restarting the affected service) and document any manual recovery steps.
Example: C++ (Win32) — safe pattern
Note: This is a conceptual example. Build with a Windows SDK and test in a safe environment (VM or non-production machine).
#include <windows.h> #include <string> #include <vector> #include <iostream> std::wstring QueryDosDeviceSafe(const std::wstring& name) { std::vector<wchar_t> buffer(1024); DWORD len = QueryDosDeviceW(name.c_str(), buffer.data(), (DWORD)buffer.size()); if (len == 0) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { buffer.resize(65536); len = QueryDosDeviceW(name.c_str(), buffer.data(), (DWORD)buffer.size()); } } if (len == 0) return L""; return std::wstring(buffer.data(), buffer.data() + len); } bool RemoveDosNameSafe(const std::wstring& name) { // 1. Query current mapping std::wstring target = QueryDosDeviceSafe(name); if (target.empty()) { std::wcerr << L"No mapping found for " << name << L" "; return false; } std::wcout << L"Current mapping for " << name << L": " << target << L" "; // 2. Use exact-match flag to avoid accidental deletions const DWORD flags = DDD_EXACT_MATCH_ON_REMOVE; BOOL ok = DeleteDosDeviceW(flags, name.c_str()); if (!ok) { DWORD err = GetLastError(); std::wcerr << L"DeleteDosDevice failed, error: " << err << L" "; return false; } // 3. Verify std::wstring verify = QueryDosDeviceSafe(name); if (!verify.empty()) { std::wcerr << L"Verify failed; mapping still present: " << verify << L" "; return false; } std::wcout << L"Successfully removed " << name << L" "; return true; } int main() { // Example: remove Z: if (!RemoveDosNameSafe(L"Z:")) { return 1; } return 0; }
Important notes:
- This example uses DDD_EXACT_MATCH_ON_REMOVE to reduce accidental removals.
- It queries the mapping first and rechecks afterwards.
- In production, add more robust error handling, logging, and permission checks.
Example: PowerShell — check then remove via P/Invoke
PowerShell doesn’t expose DeleteDosDevice directly. You can either:
- Use DefineDosDevice with the REMOVE flag via P/Invoke, or
- Use a small signed native helper executable that performs the removal.
Example using Add-Type to P/Invoke DeleteDosDevice (requires running PowerShell as admin for global removals):
$signature = @" using System; using System.Runtime.InteropServices; public static class DosDev { [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool DeleteDosDeviceW(uint lpFlags, string lpDeviceName); [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern uint QueryDosDeviceW(string lpDeviceName, System.Text.StringBuilder lpTargetPath, uint ucchMax); } "@ Add-Type $signature function Get-DosMapping { param([string]$Name) $sb = New-Object System.Text.StringBuilder 65536 $res = [DosDev]::QueryDosDeviceW($Name, $sb, $sb.Capacity) if ($res -eq 0) { return $null } return $sb.ToString() } $name = "Z:" $map = Get-DosMapping -Name $name if (-not $map) { Write-Host "No mapping for $name" exit 1 } Write-Host "Mapping: $map" # Use exact match flag (0x2) $flags = 0x2 $result = [DosDev]::DeleteDosDeviceW($flags, $name) if (-not $result) { $err = [Runtime.InteropServices.Marshal]::GetLastWin32Error() Write-Error "DeleteDosDevice failed: $err" } else { Write-Host "Removed $name" }
Be careful: running the snippet with admin rights can affect global state.
Alternatives and modern approaches
- Use DefineDosDevice and QueryDosDevice carefully to create/manage mappings only for your process instead of global modifications.
- For drive letter management, use the Volume Mount Points API (SetVolumeMountPoint, DeleteVolumeMountPoint) or WMI/CIM classes such as Win32_Volume which are higher-level and safer for volume-to-drive-letter operations.
- Use symbolic links and junctions via CreateSymbolicLink, CreateHardLink, or newer Windows Storage APIs for file-system redirection instead of DOS device names where possible.
Troubleshooting common errors
- ERROR_ACCESS_DENIED (5): You lack permissions. Try elevated context or determine which component owns the mapping.
- ERROR_FILE_NOT_FOUND (2): The name wasn’t found — confirm with QueryDosDevice.
- ERROR_INVALID_PARAMETER (87): Flags or string formatting wrong — check exact match flag use and ensure the name string is a valid DOS device name (e.g., “Z:”).
- Mapping reappears after removal: Some system services or drivers re-create mappings. Identify the creator (services, startup scripts) and update that source.
Final recommendations
- Prefer non-destructive queries first (QueryDosDevice) and use exact-match flags.
- Limit global changes; prefer per-process definitions when possible.
- Test thoroughly in isolated environments before using DeleteDosDevice in production.
- When available, prefer modern volume and file-system APIs for drive and mount management.
If you want, I can:
- Provide a compact ready-to-run utility (C++ or Rust) that safely removes one DOS name with logging and rollback, or
- Help translate the examples into VB.NET, Rust, or Go.
Leave a Reply