Click here to Skip to main content
15,867,939 members
Articles / Programming Languages / C#

Finding Network Locations Programmmatically

Rate me:
Please Sign up or sign in to vote.
4.63/5 (12 votes)
21 Apr 2018CPOL4 min read 20.2K   436   26   7
A quick helper library for accessing the network locations on a computer, by creating a helper class that is similar to the System.IO.DriveInfo class.

Introduction

Last week, I was scouring the internet trying to find a solution to an interesting problem that I was encountering while building up a "Windows Explorer"-like application. I was having difficulty accessing the Network Locations that are normally seen under the "This PC" node of the Windows Explorer.

Image 1

After looking around for a good hour, I finally was able to find what I needed and create a quick helper library for those that are interested in browsing these programmatically.

Background

For this, we will just need a working knowledge of C#, and it may be helpful to have some knowledge of how to browse directories in C#, the System.IO.DriveInfo & System.IO.DirectoryInfo classes, static methods, and the Shell32 DLL.

Walking through the Process

The first challenge I ran into was trying to find where these network locations were even stored. They had to be SOMEWHERE, after all. After looking around for a little while, I finally found that they are actually stored as shortcuts in the following directory: "%AppData%/Microsoft/Windows/Network Shortcuts".

Image 2

Great! We found the "Network Shortcuts" for the current user! Now, to get to this location programmatically using C#. First, we cannot simply enter new System.IO.DirectoryInfo("%AppData%/Microsoft/Windows/Network Shortcuts") because the System.IO.DirectoryInfo class (which we will be using to browse these shortcuts) does not have any concept of Environment Variables. We would actually have to enter new System.IO.DirectoryInfo("C:\Users\<<current_user>>\AppData\Roaming\Microsoft\Windows\Network Shortcuts") (replacing <<current_user>> with your user in Windows) although this would take us to the correct location, this would not compensate for any changes to where this data was stored. What we would actually want to use is the enum System.Environment.SpecialFolder. To complete this, we would enter System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData); this is the programmatic equivalent of the "%AppData%" environment variable that we use in the Windows Explorer. Using System.IO.Path.Combine, we can create the full directory path to our network shortcuts:

C#
private static string NetworkLocationsPath { get; } = 
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
"Microsoft", "Windows", "Network Shortcuts");

Now that we have the location of the NetworkShortcuts saved and ready to use, next, we need to iterate through each of the shortcuts in this directory. For this, as stated above, we will be using the System.IO.DirectoryInfo class.

C#
DirectoryInfo networkShortcuts = new DirectoryInfo(NetworkLocationsPath);

These shortcuts are not file shortcuts, but instead are folder shortcuts, so, instead of using System.IO.DirectoryInfo.GetFiles, we would instead want to use System.IO.DirectoryInfo.GetDirectories. Now with a quick foreach statement, we will iterate through each of the "directories" in this folder.

After looking through each of the properties and methods within an instance of the DirectoryInfo class for one of my shortcuts, I ran into two problems. First, I could not tell if the directory was a shortcut or not, and second, I could not get the Target of the shortcut if the instance was referencing a shortcut!

Image 3

After looking around for a while, I found two solutions to my problem:

The first solution I found, was a brilliantly coded custom class that would extract the target path from the shortcut (code for that is below) the benefit of this class was the fact that I would not need to reference the Shell32 DLL. I manipulated the class slightly to be a static class and hid a few of the classes and properties that were exposed that I felt didn't necessarily need to be exposed. The following is NOT my code. I have looked for the original source that I pulled the code from and could not find it - if you happen to know it, please let me know and I will update this article accordingly.

C#
using System;
using System.Runtime.InteropServices;
using System.Text;

internal static class ShortcutResolver
{
    #region Signatures imported from http://pinvoke.net

    [DllImport("shfolder.dll", CharSet = CharSet.Auto)]
    private static extern int SHGetFolderPath
    (IntPtr hwndOwner, int nFolder, IntPtr hToken, int dwFlags, StringBuilder lpszPath);

    [Flags()]
    enum SLGP_FLAGS
    {
        /// <summary>Retrieves the standard short (8.3 format) file name</summary>
        SLGP_SHORTPATH = 0x1,
        /// <summary>Retrieves the Universal Naming Convention (UNC) path name of the file</summary>
        SLGP_UNCPRIORITY = 0x2,
        /// <summary>Retrieves the raw path name. A raw path is something that might not exist
        /// and may include environment variables that need to be expanded</summary>
        SLGP_RAWPATH = 0x4
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    struct WIN32_FIND_DATAW
    {
        public uint dwFileAttributes;
        public long ftCreationTime;
        public long ftLastAccessTime;
        public long ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [Flags()]
    enum SLR_FLAGS
    {
        /// <summary>
        /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set,
        /// the high-order word of fFlags can be set to a time-out value that specifies the
        /// maximum amount of time to be spent resolving the link. The function returns if the
        /// link cannot be resolved within the time-out duration. If the high-order word is set
        /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds
        /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out
        /// duration, in milliseconds.
        /// </summary>
        SLR_NO_UI = 0x1,
        /// <summary>Obsolete and no longer used</summary>
        SLR_ANY_MATCH = 0x2,
        /// <summary>If the link object has changed, update its path and list of identifiers.
        /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine
        /// whether or not the link object has changed.</summary>
        SLR_UPDATE = 0x4,
        /// <summary>Do not update the link information</summary>
        SLR_NOUPDATE = 0x8,
        /// <summary>Do not execute the search heuristics</summary>
        SLR_NOSEARCH = 0x10,
        /// <summary>Do not use distributed link tracking</summary>
        SLR_NOTRACK = 0x20,
        /// <summary>Disable distributed link tracking. By default, distributed link tracking tracks
        /// removable media across multiple devices based on the volume name. It also uses the
        /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter
        /// has changed. Setting SLR_NOLINKINFO disables both types of tracking.</summary>
        SLR_NOLINKINFO = 0x40,
        /// <summary>Call the Microsoft Windows Installer</summary>
        SLR_INVOKE_MSI = 0x80
    }

    /// <summary>The IShellLink interface allows Shell links to be created, modified,
    /// and resolved</summary>
    [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
          Guid("000214F9-0000-0000-C000-000000000046")]
    interface IShellLinkW
    {
        /// <summary>Retrieves the path and file name of a Shell link object</summary>
        void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile,
                      int cchMaxPath, out WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
        /// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
        void GetIDList(out IntPtr ppidl);
        /// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.
        /// </summary>
        void SetIDList(IntPtr pidl);
        /// <summary>Retrieves the description string for a Shell link object</summary>
        void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName,
                     int cchMaxName);
        /// <summary>Sets the description for a Shell link object.
        /// The description can be any application-defined string</summary>
        void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
        /// <summary>Retrieves the name of the working directory for a Shell link object</summary>
        void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir,
                  int cchMaxPath);
        /// <summary>Sets the name of the working directory for a Shell link object</summary>
        void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
        /// <summary>Retrieves the command-line arguments associated with a Shell link object
        /// </summary>
        void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs,
                 int cchMaxPath);
        /// <summary>Sets the command-line arguments for a Shell link object</summary>
        void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
        /// <summary>Retrieves the hot key for a Shell link object</summary>
        void GetHotkey(out short pwHotkey);
        /// <summary>Sets a hot key for a Shell link object</summary>
        void SetHotkey(short wHotkey);
        /// <summary>Retrieves the show command for a Shell link object</summary>
        void GetShowCmd(out int piShowCmd);
        /// <summary>Sets the show command for a Shell link object.
        /// The show command sets the initial show state of the window.</summary>
        void SetShowCmd(int iShowCmd);
        /// <summary>Retrieves the location (path and index) of the icon
        /// for a Shell link object</summary>
        void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
            int cchIconPath, out int piIcon);
        /// <summary>Sets the location (path and index) of the icon for a Shell link object
        /// </summary>
        void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
        /// <summary>Sets the relative path to the Shell link object</summary>
        void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
        /// <summary>Attempts to find the target of a Shell link,
        /// even if it has been moved or renamed</summary>
        void Resolve(IntPtr hwnd, SLR_FLAGS fFlags);
        /// <summary>Sets the path and file name of a Shell link object</summary>
        void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
    }

    [ComImport, Guid("0000010c-0000-0000-c000-000000000046"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IPersist
    {
        [PreserveSig]
        void GetClassID(out Guid pClassID);
    }

    [ComImport, Guid("0000010b-0000-0000-C000-000000000046"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IPersistFile : IPersist
    {
        new void GetClassID(out Guid pClassID);
        [PreserveSig]
        int IsDirty();

        [PreserveSig]
        void Load([In, MarshalAs(UnmanagedType.LPWStr)]
string pszFileName, uint dwMode);

        [PreserveSig]
        void Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
            [In, MarshalAs(UnmanagedType.Bool)] bool fRemember);

        [PreserveSig]
        void SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);

        [PreserveSig]
        void GetCurFile([In, MarshalAs(UnmanagedType.LPWStr)] string ppszFileName);
    }

    const uint STGM_READ = 0;
    const int MAX_PATH = 260;

    // CLSID_ShellLink from ShlGuid.h
    [
        ComImport(),
        Guid("00021401-0000-0000-C000-000000000046")
    ]
    private class ShellLink
    {
    }

    #endregion

    public static string GetPath(string filename)
    {
        ShellLink link = new ShellLink();
        ((IPersistFile)link).Load(filename, STGM_READ);
        // TODO: if I can get hold of the hwnd call resolve first.
        // This handles moved and renamed files.
        // ((IShellLinkW)link).Resolve(hwnd, 0)
        StringBuilder sb = new StringBuilder(MAX_PATH);
        WIN32_FIND_DATAW data = new WIN32_FIND_DATAW();
        ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
        return sb.ToString();
    }
}

The second solution I found was through referencing the Shell32 library in the Microsoft Shell Controls and Automation COM type library. To add this in Visual Studio, in your project, right click on References, click Add Reference, expand the COM option on the left hand side of the Reference Manager, click on Type Libraries, scroll down to Microsoft Shell Controls And Automation, check the checkbox, and click OK.

Image 4

Now, to access the information, we will use the below code. I set up this code to use static methods and variables so that it incorporates into the helper class that I will outline later.

C#
private static string NetworkLocationsPath { get; } =
  System.IO.Path.Combine(System.Environment.GetFolderPath
  (System.Environment.SpecialFolder.ApplicationData),
  "Microsoft", "Windows", "Network Shortcuts");

#region Shell Support

private static Shell32.Folder _networkLocationsFolder;
private static Shell32.Folder NetworkLocationsFolder
{
    get
    {
        if (_networkLocationsFolder == null)
        {
            Shell32.Shell shell = new Shell32.Shell();
            _networkLocationsFolder = shell.NameSpace(NetworkLocationsPath);
        }
        return _networkLocationsFolder;
    }
}
private static string GetShortCutPath(string name)
{
    try
    {
        Shell32.FolderItem folderItem = NetworkLocationsFolder.ParseName(name);

        if (folderItem == null || !folderItem.IsLink)
            return null;

        Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
        return link.Path;
    }
    catch
    {
        return null;
    }
}

#endregion

With the extrapolated path information, I was able to create a new System.IO.DirectoryInfo instance with the path.

The above was convenient, however, I wanted to wrap all of this up into a simple class that would act similarly to the System.IO.DriveInfo class that would allow me to get all of the network locations and handle all of the name parsing, shortcut target extraction and effectively provide me with a one-stop-shop for my needs.

I decided to create this class in the System.IO namespace, although feel free to change that as you see fit.

C#
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security;

namespace System.IO
{
    /// <summary>
    /// Provides access to information on a Network Location.
    /// </summary>
    [ComVisible(true)]
    public sealed class NetworkLocationInfo : ISerializable
    {
        /// <summary>
        /// Gets a value that indicates whether a network location is ready.
        /// </summary>
        public bool IsReady { get { return RootDirectory.Exists; } }
        /// <summary>
        /// Gets a value that indicates whether a network location is mapped.
        /// </summary>
        public bool IsMapped { get { return ShortcutFile.Exists; } }
        /// <summary>
        /// Gets the name of the share, such as \\192.168.100.1\data.
        /// </summary>
        public string Name { get { return RootDirectory.FullName; } }
        /// <summary>
        /// Gets the share name on the server
        /// </summary>
        public string ShareName { get; }
        /// <summary>
        /// Gets the name or IP address of the server
        /// </summary>
        public string ServerName { get; }
        /// <summary>
        /// Gets the share label of a network location
        /// </summary>
        public string ShareLabel
        {
            get { return ShortcutFile.Name; }
            set {
                if (!ShortcutFile.Exists)
                    throw new FileNotFoundException("Cannot find the network location shortcut file");

                ShortcutFile.MoveTo(Path.Combine(ShortcutFile.Parent.FullName, value));
            }
        }
        /// <summary>
        /// Gets the root directory of a network location.
        /// </summary>
        public DirectoryInfo RootDirectory { get; }

        private DirectoryInfo _shortcutFile;
        private DirectoryInfo ShortcutFile {
            get
            {
                if (_shortcutFile == null)
                    _shortcutFile = FindShortCutFile();

                return _shortcutFile;
            }
        }

        /// <summary>
        /// Provides access to information on the specified network location.
        /// </summary>
        [SecuritySafeCritical]
        public NetworkLocationInfo(string networkLocationPath)
            : this(networkLocationPath, null) { }

        private NetworkLocationInfo(string networkLocationPath, DirectoryInfo shortcutFile)
        {
            if (string.IsNullOrWhiteSpace(networkLocationPath))
                throw new ArgumentNullException(nameof(networkLocationPath));

            if (!networkLocationPath.StartsWith("\\\\"))
                throw new ArgumentException("The UNC path should be of the form \\\\server\\share");

            string root = networkLocationPath.TrimStart("\\"); // TODO: This needs to use 
                                // an apostrophe (single quote) instead of a double quote, 
                                // but does not format properly in the article with a single quote.
            int i = root.IndexOf("\\");
            if (i < 0 || i + 1 == root.Length)
                throw new ArgumentException("The UNC path should be of the form \\\\server\\share");

            ServerName = root.Substring(0, i);
            root = root.Substring(i + 1);
            i = root.IndexOf("\\");
            ShareName = i < 0 ? root : root.Substring(0, i);

            if (string.IsNullOrWhiteSpace(ShareName))
                throw new ArgumentException("The UNC path should be of the form \\\\server\\share");

            RootDirectory = new DirectoryInfo(string.Concat("\\\\", ServerName, "\\", ShareName));

            _shortcutFile = shortcutFile ?? FindShortCutFile();
        }

        private DirectoryInfo FindShortCutFile()
        {
            DirectoryInfo network = new DirectoryInfo(NetworkLocationsPath);
            foreach (DirectoryInfo dir in network.EnumerateDirectories())
            {
                string path = GetShortCutPath(dir.Name);

                if (string.Equals(RootDirectory.FullName, path, StringComparison.OrdinalIgnoreCase))
                    return dir;
            }

            return new DirectoryInfo(Path.Combine(NetworkLocationsPath, 
                                     string.Concat(ShareName, " (", ServerName, ")")));
        }

        #region ISerializable methods

        /// <summary>
        /// Creates a new instance through deserialization
        /// </summary>
        internal NetworkLocationInfo(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException(nameof(info));

            ShareName = info.GetString(nameof(ShareName));
            ServerName = info.GetString(nameof(ServerName));
            RootDirectory = new DirectoryInfo(info.GetString(nameof(RootDirectory)));
            _shortcutFile = new DirectoryInfo(info.GetString(nameof(ShortcutFile)));
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
                throw new ArgumentNullException(nameof(info));

            info.AddValue(nameof(ShareName), ShareName);
            info.AddValue(nameof(ServerName), ServerName);
            info.AddValue(nameof(RootDirectory), RootDirectory.FullName);
            info.AddValue(nameof(ShortcutFile), ShortcutFile.FullName);
        }

        #endregion

        #region Object methods

        public override string ToString()
        {
            return Name;
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj is NetworkLocationInfo)
                return string.Equals(((NetworkLocationInfo)obj).Name, 
                                 Name, StringComparison.OrdinalIgnoreCase);

            return false;
        }

        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }

        #endregion

        #region Static Methods

        private static string NetworkLocationsPath { get; } = 
                 Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
                 "Microsoft", "Windows", "Network Shortcuts");

        #region Shell Support

        private static Shell32.Folder _networkLocationsFolder;
        private static Shell32.Folder NetworkLocationsFolder
        {
            get
            {
                if (_networkLocationsFolder == null)
                {
                    Shell32.Shell shell = new Shell32.Shell();
                    _networkLocationsFolder = shell.NameSpace(NetworkLocationsPath);
                }
                return _networkLocationsFolder;
            }
        }
        private static string GetShortCutPath(string name)
        {
            try
            {
                Shell32.FolderItem folderItem = NetworkLocationsFolder?.ParseName(name);

                if (folderItem == null || !folderItem.IsLink)
                    return null;

                Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
                return link.Path;
            }
            catch
            {
                return null;
            }
        }

        #endregion

        /// <summary>
        /// Gets all of the network locations found.
        /// </summary>
        public static NetworkLocationInfo[] GetNetworkLocations()
        {
            if (NetworkLocationsFolder == null)
                return new NetworkLocationInfo[] { };

            DirectoryInfo networkShortcuts = new DirectoryInfo(NetworkLocationsPath);

            DirectoryInfo[] subDirectories = networkShortcuts.GetDirectories();

            NetworkLocationInfo[] locations = new NetworkLocationInfo[subDirectories.Length];
            if (subDirectories.Length == 0)
                return locations;

            int i = 0;
            foreach (DirectoryInfo dir in subDirectories)
            {
                string networkLocationPath = GetShortCutPath(dir.Name);

                if (string.IsNullOrWhiteSpace(networkLocationPath))
                    continue;

                try
                {
                    NetworkLocationInfo info = new NetworkLocationInfo(networkLocationPath, dir);

                    locations[i++] = info;
                }
                catch { continue; }
            }

            if (i < locations.Length)
                Array.Resize(ref locations, i);

            return locations;
        }

        #endregion
    }
}

With this helper class, we can now browse our network location shortcuts by simply calling NetworkLocationInfo.GetNetworkLocations().

For my uses, I was creating a Tree with all of the network locations, below is how I was able to implement this helper class.

C#
// Load Network Locations
foreach (NetworkLocationInfo n in NetworkLocationInfo.GetNetworkLocations())
{
    if (!n.IsReady)
        continue;

    TreeNode aNode = new TreeNode(n.ShareLabel,
                     (int)ImageKeys.NetworkDrive, (int)ImageKeys.NetworkDrive);

    aNode.Tag = n.RootDirectory;
    aNode.ImageKey = "network-location";
    try
    {
        GetDirectories(n.RootDirectory.EnumerateDirectories(), aNode);
        nodeToAddTo.Nodes.Add(aNode);
    }
    catch { }
}

Enjoy & happy coding!

</the_madness>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Ohm Vision, Inc
United States United States
I'm a software consultant who would like to help your company grow.

Need someone to bring a fresh idea to the table?
Want a sounding board for proposed solutions?
Have some projects that "just need to get done"?

I can help.

Architectural Design, or Reviews
Database Design, Development, Optimization, or Administration
Backend Review or Development
Devops Scripting or Implementation
Technical Interviewing

I've worked in both AWS and Azure, have experience in Node and .NETCore stack in the healthcare and IoT. I've worked with small platforms servicing thousands of transactions per second and global corporations servicing 500,000+ transactions per second.

I favor open source solutions but can adopt whatever standards you already have in place. I have extensive knowledge about multiple architectural patterns and can help you sort through which is best for you.

I'd like to help you streamline and optimize your software and processes to take full advantage of the environment you are developing in, enabling you to fully serve your customers.

Comments and Discussions

 
GeneralMy vote of 5 Pin
raddevus28-Sep-17 15:51
mvaraddevus28-Sep-17 15:51 
GeneralRe: My vote of 5 Pin
Komron Nouri28-Sep-17 17:35
Komron Nouri28-Sep-17 17:35 
GeneralRe: My vote of 5 Pin
raddevus29-Sep-17 3:04
mvaraddevus29-Sep-17 3:04 
GeneralRe: My vote of 5 Pin
Komron Nouri29-Sep-17 6:22
Komron Nouri29-Sep-17 6:22 
SuggestionUse Windows API Code Pack... Pin
Paw Jershauge28-Sep-17 0:59
Paw Jershauge28-Sep-17 0:59 
GeneralRe: Use Windows API Code Pack... Pin
Komron Nouri28-Sep-17 10:56
Komron Nouri28-Sep-17 10:56 
GeneralMy vote of 4 Pin
cocis4827-Sep-17 9:47
cocis4827-Sep-17 9:47 
Nice findings

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.