50

How do I determine a mapped drive's actual path?

So if I have a mapped drive on a machine called "Z", how can I use .NET to determine the machine and path for the mapped folder?

The code can assume it's running on the machine with the mapped drive.

I looked at Path, Directory, and FileInfo objects, but can't seem to find anything.

I also looked for existing questions, but could not find what I'm looking for.

jordanz
  • 367
  • 4
  • 12

14 Answers14

42

I expanded on ibram's answer and created this class (which has been updated per comment feedback). I've probably over documented it, but it should be self-explanatory.

/// <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(), "");
    }
}
Vermis
  • 2,238
  • 2
  • 17
  • 13
  • 1
    `Convert.ToUInt32(mo["DriveType"])` causes a *The type initializer for 'System.Management.ManagementPath' threw an exception*, do you know if this code works on Windows7 or could it be Group Policy? – Jeremy Thompson Jun 04 '13 at 05:52
  • 1
    @JeremyThompson The InnerException for this exception (which I am also getting) is [System.Threading.ThreadAbortException] {"Exception of type 'System.Threading.ThreadAbortException' was thrown."}. I do not know the cause of this yet, but am still looking for a solution. I'm running Win7 x64. – Hydronium Jul 10 '13 at 20:14
  • +1; I ended using this code with some small changes: 1. Made the class and methods static; 2. mo["DriveType"] can be casted directly to uint and then to System.IO.DriveType. This way I don't have to deal with those magic numbers. 3. Put mo in an using statement. [mo = null doesn't really help the GC](http://stackoverflow.com/a/574659/2504607) – Kabbalah Mar 20 '14 at 10:56
  • This solution does not appear to resolve drives mapped using `subst` (for which the System.IO.DriveType is `Fixed`) – AlainD Mar 23 '22 at 16:05
36

I can't remember where I found this, but it works without p/invoke. It's what rerun posted before.

you need to reference System.Management.dll:

using System.IO;
using System.Management;

code:

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;
   }
}

Update: Explicitly running as administrator will not show mapped drives. Here is an explanation of this behaviour: https://stackoverflow.com/a/11268410/448100 (in short: administrator has a different user context, so no access to mapped drives of normal user)

ibram
  • 4,414
  • 2
  • 22
  • 34
  • This worked perfectly for my needs and seems to be the simplest solution. I'm surprised I haven't seen this anywhere else. – JimDel Jan 11 '12 at 01:17
  • 1
    Fails on Windows 8 when `path = "C:\\"` with a `ManagementException` not found. – Loathing Nov 30 '14 at 07:06
  • 2
    @Loathing Did you find a solution for the `ManagementException`? I'm also hitting this error. Thanks. – Scott Lin Feb 24 '15 at 20:12
  • 1
    @kurifodo The solution is to trim off the backslashes and just use `C:`. I'll post the code I actually use below in a new answer. – Loathing Feb 24 '15 at 23:00
  • 1
    @ibram This is not working in windows 10 when the program is running as admin. Might have to pinvoke if you want to find info when program is running as admin and work across different platforms. – David Bentley Mar 09 '18 at 22:04
  • @DavidBentley thank you for your feedback. This is also not working in windows 7. I added that to my answer. (DriveInfo.GetDrives() returns only fixed drives as admin) – ibram Sep 20 '18 at 10:35
26

Here are some code samples:

All of the magic derives from a Windows function:

    [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);

Example invocation:

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();
BitSchupser
  • 432
  • 3
  • 13
Mike Marshall
  • 7,788
  • 4
  • 39
  • 63
  • I have confirmed the C# code from the link works. I would rather have a non-dll import version, but better than nothing at all. –  Jan 15 '10 at 21:40
  • 3
    Rather than just providing a link, can you please provide some context in your actual answer in case the link becomes unavailable? Thanks. – Deanna Mar 13 '13 at 16:42
  • 4
    In case that link is invalid some day, the main thing you need to know is that it uses WNetGetConnection (you can find that on MSDN). – eselk Sep 04 '13 at 19:19
  • Here the saved github gist for the code in case the site stop working: https://gist.github.com/LuciferSam86/ea047f07ff95aa976b058848396f1be1 – LuciferSam Sep 21 '18 at 07:31
23

I've written a method for this. It returns a UNC path if it is a mapped drive, otherwise it returns the path unchanged.

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;
}

EDIT

You now can use the Method even with already UNC paths. The above version of the method throws an exception if given a UNC path.

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;
}
cramopy
  • 3,459
  • 6
  • 28
  • 42
  • 2
    I found this to work really well. Neat, short and simple. – JustBaron May 18 '16 at 09:34
  • 1
    Is there an other path in the Registry to find this values? Because i found all but one (please see screenshot): [link](http://imgur.com/AxA9FJN) – kamp Jun 15 '16 at 10:19
  • 1
    It looks as simplest solution, that works for earlier .Net framework (like 2.0) where no "System.Management" namespace yet and it works with no additional libraries. It needs only namespace used "Microsoft.Win32". – Vlad Gonchar Jun 08 '17 at 16:03
  • This solution is not valid for Windows 10 – XtianGIS Oct 07 '21 at 11:54
8

I think you can use the "Network" key From the "Current User" Hive, In the Registry. The Mapped Drives Are Listed There With Their Shared Path On Server.

If there is no mapped drive in the system, so there is no "Network" Key In The "Current User" Hive.

Now, I'm using this way, no external dll nor anything else.

Farzad Karimi
  • 770
  • 1
  • 12
  • 31
5

I could not replicate ibram's or Vermis' answer due to the problem I mentioned in a comment under Vermis' answer, about a type initializer exception.

Instead, I discovered I could query for all the drives currently on the computer and then loop through them, like so:

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;
    }
}

This works for x64 Windows 7, for .NET 4. It should be usable in case you're getting that exception that was mentioned above.

I did this using the stuff given from MSDN and bits from ibram's or Vermis' answers, though it was a bit difficult to find specific examples on the MSDN. Resources used:

MSDN : Win32_LogicalDisk Class

MSDN : System.Management namespace

MSDN : WMI Queries example:

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;
    }
}
Community
  • 1
  • 1
Hydronium
  • 793
  • 1
  • 11
  • 28
5

QueryDosDevice translates a drive letter into the path that it expands to.

Note that this will translate ALL drive letters, not just those that are mapped to network connections. You need to already know which are network paths, or to parse the output to see which are network.

Here's the VB signature

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

And the C# one

[DllImport("kernel32.dll")]
static extern uint QueryDosDevice(string lpDeviceName, IntPtr lpTargetPath, uint ucchMax);
John Knoeller
  • 33,512
  • 4
  • 61
  • 92
  • I can't get this to work, also this look like it will not give the folder, mapped driver are to a server and a folder... –  Jan 15 '10 at 21:37
  • If you mean you want to know that path as it appears to the server, then you will need to ask the server. That information isn't available to the client. – John Knoeller Jan 15 '10 at 21:39
  • If the drive is mapped on the machine the code is running on then it should work. –  Jan 15 '10 at 21:46
  • You will get back \\server\share on the machine that the drive is mapped on. The server may have share aliased to c:\foo\bar\whatever, If you want that then you have to ask the server. – John Knoeller Jan 15 '10 at 21:51
5

You can use WMI to interrogate the Win32_LogicalDrive collection on your machine. Here is an example of how to do it with scripting. Changing this over to C# is pretty well explained in other places.

Slightly modified VB.NET code from the article:

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
tehDorf
  • 775
  • 1
  • 11
  • 32
Nick
  • 5,875
  • 1
  • 27
  • 38
  • Dead simple way of getting the network share of each mapped drive without using any special libraries. This works straight out of the box in a VS Express 2012 for Desktop Windows Form application. – tehDorf Jun 05 '13 at 15:32
  • While this does work it fails at least on Windows 10 on network drives. If you remove the 'DriveType = 4' you will get a list of all local drives but no mapped network drives. – user2958328 Nov 30 '19 at 15:57
3

Similar to ibram's answer with a few modifications:

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;
}
Loathing
  • 5,109
  • 3
  • 24
  • 35
2

Seems it's need a P/Invoke: Converting a mapped drive letter to a network path using C#

This guy built a managed class to deal with it: C# Map Network Drive (API)

Rubens Farias
  • 57,174
  • 8
  • 131
  • 162
  • 2
    Looks like this allows you to map or un-map drives via code, I don't see anything on obtaining a path from a mapped path. –  Jan 15 '10 at 21:43
2

You can also use WMI Win32_LogicalDisk to get all the information you need. use the ProviderName from the class to get the UNC path.

rerun
  • 25,014
  • 6
  • 48
  • 78
0

As far as Windows cares, what's needed is a call to WNetGetConnection. I don't know of a front-end for that in .NET, so you may have to call it via P/Invoke (fortunately, it has only one parameter, the P/Invoke code isn't too awful).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
0

This post describe how to get the absolute path of a drive which is mapped to a local folder?

For example, I have a "c:\test" folder and an "x:" drive which is mapped to c:\test.

I'm looking for a function which will return "c:\test" when I pass in "x:"

The answer is:

SUBST uses DefineDosDevice (XP and later) to create the drive/path mapping. You can use the QueryDosDevice to get the path of a SUBSTed drive:

[DllImport("kernel32.dll")]

private    static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);

static String GetPhysicalPath(String path)

{

    if (String.IsNullOrEmpty(path))

    {

        throw new ArgumentNullException("path");

    }

    // Get the drive letter

    string pathRoot = Path.GetPathRoot(path);

    if(String.IsNullOrEmpty(pathRoot))

    {

        throw new ArgumentNullException("path");

    }

    string lpDeviceName = pathRoot.Replace("\\", "");



    const String substPrefix = @"\??\";

    StringBuilder lpTargetPath = new StringBuilder(260);



    if (0 != QueryDosDevice(lpDeviceName, lpTargetPath, lpTargetPath.Capacity))

    {

        string result;



        // If drive is substed, the result will be in the format of "\??\C:\RealPath\".

        if (lpTargetPath..ToString().StartsWith(substPrefix))

        {

            // Strip the \??\ prefix.

            string root = lpTargetPath.ToString().Remove(0, substPrefix.Length);



            result = Path.Combine(root, path.Replace(Path.GetPathRoot(path), ""));

        }

        else

        {

            // TODO: deal with other types of mappings.

            // if not SUBSTed, just assume it's not mapped.

            result = path;

        }

        return result;

    }

    else

    {

        // TODO: error reporting

        return null;

    }

}
Carlos Liu
  • 2,348
  • 3
  • 37
  • 51
0

Here is a solution that doesn't care if it is local or remote

    private string uncpath_check(string path)
    {
        string rval = path;
        string driveprefix = path.Substring(0, 2);
        string unc;

        if (driveprefix != "\\")
        {
            ManagementObject mo = new ManagementObject();
            try
            {
                mo.Path = new ManagementPath(String.Format("Win32_LogicalDisk='{0}'", driveprefix));
                unc = (string)mo["ProviderName"];
                rval = path.Replace(driveprefix, unc);
            }
            catch
            {
                throw;
            }
        }

        if (rval == null)
        { rval = path; }

        return rval;
    }