File: DriveMapping.cs
Web Access
Project: ..\..\..\src\UnitTests.Shared\Microsoft.Build.UnitTests.Shared.csproj (Microsoft.Build.UnitTests.Shared)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
 
namespace Microsoft.Build.UnitTests.Shared;
 
internal static class DriveMapping
{
    private const int ERROR_FILE_NOT_FOUND = 2;
    private const int ERROR_INSUFFICIENT_BUFFER = 122;
    private const int DDD_REMOVE_DEFINITION = 2;
    private const int DDD_NO_FLAG = 0;
    // extra space for '\??\'. Not counting for long paths support in tests.
    private const int MAX_PATH = 259;
 
    /// <summary>
    /// Windows specific. Maps path to a requested drive.
    /// </summary>
    /// <param name="letter">Drive letter</param>
    /// <param name="path">Path to be mapped</param>
    [SupportedOSPlatform("windows")]
    public static void MapDrive(char letter, string path)
    {
        if (!DefineDosDevice(DDD_NO_FLAG, ToDeviceName(letter), path))
        {
            NativeMethodsShared.ThrowExceptionForErrorCode(Marshal.GetLastWin32Error());
        }
    }
 
    /// <summary>
    /// Windows specific. Unmaps drive mapping.
    /// </summary>
    /// <param name="letter">Drive letter.</param>
    [SupportedOSPlatform("windows")]
    public static void UnmapDrive(char letter)
    {
        if (!DefineDosDevice(DDD_REMOVE_DEFINITION, ToDeviceName(letter), null))
        {
            NativeMethodsShared.ThrowExceptionForErrorCode(Marshal.GetLastWin32Error());
        }
    }
 
    /// <summary>
    /// Windows specific. Fetches path mapped under specific drive letter.
    /// </summary>
    /// <param name="letter">Drive letter.</param>
    /// <returns>Path mapped under specified letter. Empty string if mapping not found.</returns>
    [SupportedOSPlatform("windows")]
    public static string GetDriveMapping(char letter)
    {
        // since this is just for test purposes - let's not overcomplicate with long paths support
        char[] buffer = new char[MAX_PATH];
 
        while (QueryDosDevice(ToDeviceName(letter), buffer, buffer.Length) == 0)
        {
            // Return empty string if the drive is not mapped
            int err = Marshal.GetLastWin32Error();
            if (err == ERROR_FILE_NOT_FOUND)
            {
                return string.Empty;
            }
 
            if (err != ERROR_INSUFFICIENT_BUFFER)
            {
                NativeMethodsShared.ThrowExceptionForErrorCode(err);
            }
 
            buffer = new char[buffer.Length * 4];
        }
 
        // Translate from the native path semantic - starting with '\??\'
        return new string(buffer, 4, buffer.Length - 4);
    }
 
    private static string ToDeviceName(char letter)
    {
        return $"{char.ToUpper(letter)}:";
    }
 
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [SupportedOSPlatform("windows")]
    private static extern bool DefineDosDevice([In] int flags, [In] string deviceName, [In] string? path);
 
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [SupportedOSPlatform("windows")]
    private static extern int QueryDosDevice([In] string deviceName, [Out] char[] buffer, [In] int bufSize);
}