如何确定映射驱动器的实际路径?

50

如何确定映射驱动器的实际路径?

如果我在名为“Z”的计算机上有一个映射的驱动器,如何使用.NET确定映射文件夹的计算机和路径?

代码可以假定它正在运行具有映射驱动器的计算机上。

我查看了PathDirectoryFileInfo对象,但似乎找不到任何东西。

我还寻找现有的问题,但找不到我要找的内容。


1
请查看@Nick的答案,其中提供了一种不使用PInvoke或任何特殊库的方法。 - tehDorf
14个回答

43

我对 ibram 的回答进行了扩展并创建了这个类(根据评论反馈已更新)。我可能过度文档化了它,但它应该是自说明的。

/// <summary>
/// A static class to help with resolving a mapped drive path to a UNC network path.
/// If a local drive path or a UNC network path are passed in, they will just be returned.
/// </summary>
/// <example>
/// using System;
/// using System.IO;
/// using System.Management;    // Reference System.Management.dll
/// 
/// // Example/Test paths, these will need to be adjusted to match your environment. 
/// string[] paths = new string[] {
///     @"Z:\ShareName\Sub-Folder",
///     @"\\ACME-FILE\ShareName\Sub-Folder",
///     @"\\ACME.COM\ShareName\Sub-Folder", // DFS
///     @"C:\Temp",
///     @"\\localhost\c$\temp",
///     @"\\workstation\Temp",
///     @"Z:", // Mapped drive pointing to \\workstation\Temp
///     @"C:\",
///     @"Temp",
///     @".\Temp",
///     @"..\Temp",
///     "",
///     "    ",
///     null
/// };
/// 
/// foreach (var curPath in paths) {
///     try {
///         Console.WriteLine(string.Format("{0} = {1}",
///             curPath,
///             MappedDriveResolver.ResolveToUNC(curPath))
///         );
///     }
///     catch (Exception ex) {
///         Console.WriteLine(string.Format("{0} = {1}",
///             curPath,
///             ex.Message)
///         );
///     }
/// }
/// </example>
public static class MappedDriveResolver
{
    /// <summary>
    /// Resolves the given path to a full UNC path if the path is a mapped drive.
    /// Otherwise, just returns the given path.
    /// </summary>
    /// <param name="path">The path to resolve.</param>
    /// <returns></returns>
    public static string ResolveToUNC(string path) {
        if (String.IsNullOrWhiteSpace(path)) {
            throw new ArgumentNullException("The path argument was null or whitespace.");
        }

        if (!Path.IsPathRooted(path)) {
            throw new ArgumentException(
                string.Format("The path '{0}' was not a rooted path and ResolveToUNC does not support relative paths.",
                    path)
            );
        }

        // Is the path already in the UNC format?
        if (path.StartsWith(@"\\")) {
            return path;
        }

        string rootPath = ResolveToRootUNC(path);

        if (path.StartsWith(rootPath)) {
            return path; // Local drive, no resolving occurred
        }
        else {
            return path.Replace(GetDriveLetter(path), rootPath);
        }
    }

    /// <summary>
    /// Resolves the given path to a root UNC path if the path is a mapped drive.
    /// Otherwise, just returns the given path.
    /// </summary>
    /// <param name="path">The path to resolve.</param>
    /// <returns></returns>
    public static string ResolveToRootUNC(string path) {
        if (String.IsNullOrWhiteSpace(path)) {
            throw new ArgumentNullException("The path argument was null or whitespace.");
        }

        if (!Path.IsPathRooted(path)) {
            throw new ArgumentException(
                string.Format("The path '{0}' was not a rooted path and ResolveToRootUNC does not support relative paths.",
                path)
            );
        }

        if (path.StartsWith(@"\\")) {
            return Directory.GetDirectoryRoot(path);
        }

        // Get just the drive letter for WMI call
        string driveletter = GetDriveLetter(path);

        // Query WMI if the drive letter is a network drive, and if so the UNC path for it
        using (ManagementObject mo = new ManagementObject()) {
            mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter));

            DriveType driveType = (DriveType)((uint)mo["DriveType"]);
            string networkRoot = Convert.ToString(mo["ProviderName"]);

            if (driveType == DriveType.Network) {
                return networkRoot;
            }
            else {
                return driveletter + Path.DirectorySeparatorChar;
            }
        }           
    }

    /// <summary>
    /// Checks if the given path is a network drive.
    /// </summary>
    /// <param name="path">The path to check.</param>
    /// <returns></returns>
    public static bool isNetworkDrive(string path) {
        if (String.IsNullOrWhiteSpace(path)) {
            throw new ArgumentNullException("The path argument was null or whitespace.");
        }

        if (!Path.IsPathRooted(path)) {
            throw new ArgumentException(
                string.Format("The path '{0}' was not a rooted path and ResolveToRootUNC does not support relative paths.",
                path)
            );
        }

        if (path.StartsWith(@"\\")) {
            return true;
        }

        // Get just the drive letter for WMI call
        string driveletter = GetDriveLetter(path);

        // Query WMI if the drive letter is a network drive
        using (ManagementObject mo = new ManagementObject()) {
            mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter));
            DriveType driveType = (DriveType)((uint)mo["DriveType"]);
            return driveType == DriveType.Network;
        }
    }

    /// <summary>
    /// Given a path will extract just the drive letter with volume separator.
    /// </summary>
    /// <param name="path"></param>
    /// <returns>C:</returns>
    public static string GetDriveLetter(string path) {
        if (String.IsNullOrWhiteSpace(path)) {
            throw new ArgumentNullException("The path argument was null or whitespace.");
        }

        if (!Path.IsPathRooted(path)) {
            throw new ArgumentException(
                string.Format("The path '{0}' was not a rooted path and GetDriveLetter does not support relative paths.",
                path)
            );
        }

        if (path.StartsWith(@"\\")) {
            throw new ArgumentException("A UNC path was passed to GetDriveLetter");
        }

        return Directory.GetDirectoryRoot(path).Replace(Path.DirectorySeparatorChar.ToString(), "");
    }
}

1
Convert.ToUInt32(mo["DriveType"]) 会导致 The type initializer for 'System.Management.ManagementPath' threw an exception,你知道这段代码在Windows7上是否可行,或者可能是组策略的问题吗? - Jeremy Thompson
1
@JeremyThompson 我也遇到了这个异常的 InnerException,它是 [System.Threading.ThreadAbortException] {"Exception of type 'System.Threading.ThreadAbortException' was thrown."}。我还不知道原因,但仍在寻找解决方案。我正在运行 Win7 x64。 - Hydronium
+1; 我最终使用了这段代码,并进行了一些小改动:1. 将类和方法设为静态;2. mo["DriveType"] 可以直接转换为 uint,然后再转换为 System.IO.DriveType。这样我就不必处理那些神奇的数字了。3. 将 mo 放在 using 语句中。mo = null 对 GC 没有实际帮助 - Kabbalah
此解决方案似乎无法解决使用“subst”映射的驱动器(其中System.IO.DriveType为“Fixed”)的问题。 - AlainD

36

我忘记了在哪里找到这个,但是它可以在不使用p/invoke的情况下正常工作。这就是之前rerun发布的内容。

你需要引用System.Management.dll

using System.IO;
using System.Management;

代码:

public void FindUNCPaths()
{
   DriveInfo[] dis = DriveInfo.GetDrives();
   foreach( DriveInfo di in dis )
   {
      if(di.DriveType == DriveType.Network)
      {
         DirectoryInfo dir = di.RootDirectory;
         // "x:"
         MessageBox.Show( GetUNCPath( dir.FullName.Substring( 0, 2 ) ) );
      }
   }
}

public string GetUNCPath(string path)
{
   if(path.StartsWith(@"\\")) 
   {
      return path;
   }

   ManagementObject mo = new ManagementObject();
   mo.Path = new ManagementPath( String.Format( "Win32_LogicalDisk='{0}'", path ) );

   // DriveType 4 = Network Drive
   if(Convert.ToUInt32(mo["DriveType"]) == 4 )
   {
      return Convert.ToString(mo["ProviderName"]);
   }
   else 
   {
      return path;
   }
}

更新: 明确以管理员身份运行将不显示映射的驱动器。以下是此行为的解释: https://dev59.com/dmXWa4cB1Zd3GeqPOJLJ#11268410 (简而言之:管理员具有不同的用户上下文,因此无法访问普通用户的映射驱动器)


这对我的需求完美地起作用,似乎是最简单的解决方案。我很惊讶为什么我在其他地方没有看到过这个。 - JimDel
1
path = "C:\\" 时,在 Windows 8 上会出现 ManagementException 找不到的错误。 - Loathing
2
@Loathing,你找到ManagementException的解决方案了吗?我也遇到了这个错误。谢谢。 - Scott Lin
1
@kurifodo 解决方案是去掉反斜杠,只使用 C:。我将在下面的新答案中发布我实际使用的代码。 - Loathing
1
@ibram 当程序以管理员身份运行时,在Windows 10中这不起作用。如果您想在程序以管理员身份运行并跨不同平台工作时查找信息,可能需要使用PInvoke。 - David Bentley
@DavidBentley 感谢您的反馈。这在Windows 7中也不起作用。我已将此添加到我的答案中。(DriveInfo.GetDrives()仅作为管理员返回固定驱动器) - ibram

27

以下是一些代码示例:

所有的魔法都来自于Windows函数:

    [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int WNetGetConnection(
        [MarshalAs(UnmanagedType.LPTStr)] string localName, 
        [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, 
        ref int length);

示例调用:

var sb = new StringBuilder(512);
var size = sb.Capacity;
var error = Mpr.WNetGetConnection("Z:", sb, ref size);
if (error != 0)
    throw new Win32Exception(error, "WNetGetConnection failed");
 var networkpath = sb.ToString();

我已确认链接中的C#代码可用。我宁愿有一个非dll导入版本,但总比没有强。 - user117499
3
除了提供链接之外,您能否在实际回答中提供一些背景信息,以防链接不可用?谢谢。 - Deanna
4
如果链接失效,你需要知道的主要内容是它使用了 WNetGetConnection 函数(你可以在 MSDN 上找到)。 - eselk
以下是代码的保存Github Gist,以防网站停止工作: https://gist.github.com/LuciferSam86/ea047f07ff95aa976b058848396f1be1 - LuciferSam

23

我已经写了一个方法来实现这个功能。如果是映射的驱动器,它会返回一个UNC路径,否则就返回原路径。

public static string UNCPath(string path)
{
    using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + path[0]))
    {
        if (key != null)
        {
            path = key.GetValue("RemotePath").ToString() + path.Remove(0, 2).ToString();
        }
    }
    return path;
}

编辑

现在您甚至可以使用该方法来处理已经是 UNC 路径的路径。上面版本的方法会在给定 UNC 路径时抛出异常。

public static string UNCPath(string path)
{
    if (!path.StartsWith(@"\\"))
    {
        using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Network\\" + path[0]))
        {
            if (key != null)
            {
                return key.GetValue("RemotePath").ToString() + path.Remove(0, 2).ToString();
            }
        }
    }
    return path;
}

2
我发现这个非常好用。整洁、简短和简单。 - JustBaron
1
在注册表中是否有其他路径可以找到这些值?因为我发现所有的值,只有一个找不到(请参见截图):链接 - kamp
1
这似乎是一个最简单的解决方案,适用于早期的 .Net 框架(如 2.0 版本),其中还没有 "System.Management" 命名空间,并且它不需要使用任何额外的库。它只需要使用 "Microsoft.Win32" 命名空间。 - Vlad Gonchar
此解决方案不适用于Windows 10。 - XtianGIS

8

我认为您可以在注册表中的“当前用户”中使用“网络(Network)”键,与其共享路径一起列出映射驱动器。

如果系统中没有映射驱动器,则在“当前用户”中没有“网络(Network)”键。

现在,我正在使用这种方式,无需外部dll或其他任何东西。


不适用于Windows 10。 - XtianGIS
这看起来是最干净的方法,但不准确。我正在查看HKEY_CURRENT_USER\Network,它只显示了我的15个映射网络驱动器中的一个。不要相信这种方法。 - Bryce Wagner

5

QueryDosDevice函数可以将一个驱动器号转换为其对应的路径。

需要注意的是,它会将所有驱动器号都翻译出来,而不仅仅是映射到网络连接的那些。您需要已经知道哪些是网络路径,或者解析输出以查看哪些是网络路径。

下面是VB签名:

Declare Function QueryDosDevice Lib "kernel32" Alias "QueryDosDeviceA" (
       ByVal lpDeviceName    As String, 
       ByVal lpTargetPath As String, 
       ByVal ucchMax As Integer) As Integer 

还有C#的部分

[DllImport("kernel32.dll")]
static extern uint QueryDosDevice(string lpDeviceName, IntPtr lpTargetPath, uint ucchMax);

我无法让这个工作起来,而且看起来它也不会提供文件夹,映射的驱动器是连接到服务器和文件夹的。 - user117499
如果您想了解路径在服务器上的实际情况,那么您需要向服务器发出请求。这些信息对客户端不可用。 - John Knoeller
如果驱动器在运行代码的机器上被映射,则应该可以工作。 - user117499
你将在映射驱动器的计算机上获得\server\share。服务器可能会将共享别名设置为c:\foo\bar\whatever,如果你需要这个,则必须向服务器提出请求。 - John Knoeller

5
我无法复制ibram'sVermis'的答案,因为我在Vermis'的答案下评论中提到了类型初始化异常的问题。
相反,我发现我可以查询当前计算机上的所有驱动器,然后通过循环遍历它们,如下所示:
using System.IO; //For DirectoryNotFound exception.
using System.Management;


/// <summary>
/// Given a local mapped drive letter, determine if it is a network drive. If so, return the server share.
/// </summary>
/// <param name="mappedDrive"></param>
/// <returns>The server path that the drive maps to ~ "////XXXXXX//ZZZZ"</returns>
private string CheckUNCPath(string mappedDrive)
{
    //Query to return all the local computer's drives.
    //See http://msdn.microsoft.com/en-us/library/ms186146.aspx, or search "WMI Queries"
    SelectQuery selectWMIQuery = new SelectQuery("Win32_LogicalDisk");
    ManagementObjectSearcher driveSearcher = new ManagementObjectSearcher(selectWMIQuery);

    //Soem variables to be used inside and out of the foreach.
    ManagementPath path = null;
    ManagementObject networkDrive = null;
    bool found = false;
    string serverName = null;

    //Check each disk, determine if it is a network drive, and then return the real server path.
    foreach (ManagementObject disk in driveSearcher.Get())
    {
        path = disk.Path;

        if (path.ToString().Contains(mappedDrive))
        {
            networkDrive = new ManagementObject(path);

            if (Convert.ToUInt32(networkDrive["DriveType"]) == 4)
            {
                serverName = Convert.ToString(networkDrive["ProviderName"]);
                found = true;
                break;
            }
            else
            {
                throw new DirectoryNotFoundException("The drive " + mappedDrive + " was found, but is not a network drive. Were your network drives mapped correctly?");
            }
        }
    }

    if (!found)
    {
        throw new DirectoryNotFoundException("The drive " + mappedDrive + " was not found. Were your network drives mapped correctly?");
    }
    else
    {
        return serverName;
    }
}

这适用于.NET 4的x64 Windows 7。如果您遇到上述异常,它应该是可用的。

我使用了MSDN提供的内容和ibramVermis答案中的部分内容,尽管在MSDN上找到特定示例有点困难。使用的资源:

MSDN:Win32_LogicalDisk类

MSDN:System.Management命名空间

MSDN:WMI查询示例

using System;
using System.Management;
class Query_SelectQuery
{
    public static int Main(string[] args) 
    {
        SelectQuery selectQuery = new 
            SelectQuery("Win32_LogicalDisk");
        ManagementObjectSearcher searcher =
            new ManagementObjectSearcher(selectQuery);

        foreach (ManagementObject disk in searcher.Get()) 
        {
            Console.WriteLine(disk.ToString());
        }

        Console.ReadLine();
        return 0;
    }
}

5
你可以使用WMI来查询计算机上的Win32_LogicalDrive集合。这里有一个使用脚本的例子。将其转换为C#也在其他地方得到了很好的解释。
从文章中稍作修改的VB.NET代码:
Public Class Form1

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim strComputer = "."

        Dim objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

        Dim colDrives = objWMIService.ExecQuery("Select * From Win32_LogicalDisk Where DriveType = 4")

        For Each objDrive In colDrives
            Debug.WriteLine("Drive letter: " & objDrive.DeviceID)
            Debug.WriteLine("Network path: " & objDrive.ProviderName)
        Next
    End Sub

End Class

获取每个映射驱动器的网络共享路径的方法非常简单,无需使用任何特殊库。在VS Express 2012 for Desktop Windows Form应用程序中,这可以直接使用,非常方便。 - tehDorf
尽管这样做可以运行,但在至少在Windows 10上的网络驱动器上会失败。如果您删除'DriveType = 4',则会获得所有本地驱动器但没有映射的网络驱动器列表。 - user2958328

3

类似于ibram的回答,稍作修改:

public static String GetUNCPath(String path) {
    path = path.TrimEnd('\\', '/') + Path.DirectorySeparatorChar;
    DirectoryInfo d = new DirectoryInfo(path);
    String root = d.Root.FullName.TrimEnd('\\');

    if (!root.StartsWith(@"\\")) {
        ManagementObject mo = new ManagementObject();
        mo.Path = new ManagementPath(String.Format("Win32_LogicalDisk='{0}'", root));

        // DriveType 4 = Network Drive
        if (Convert.ToUInt32(mo["DriveType"]) == 4)
            root = Convert.ToString(mo["ProviderName"]);
        else
            root = @"\\" + System.Net.Dns.GetHostName() + "\\" + root.TrimEnd(':') + "$\\";
    }

    return Recombine(root, d);
}

private static String Recombine(String root, DirectoryInfo d) {
    Stack s = new Stack();
    while (d.Parent != null) {
        s.Push(d.Name);
        d = d.Parent;
    }

    while (s.Count > 0) {
        root = Path.Combine(root, (String) s.Pop());
    }
    return root;
}

2

2
看起来这允许你通过代码映射或解除映射驱动器,但我没有看到任何有关从映射路径获取路径的内容。 - user117499

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接