以编程方式查找网络位置
介绍
上周,我在互联网上正在努力寻找一个解决方案来解决一个有趣的问题,我正在构建一个“Windows资源管理器”的应用程序时遇到的问题。我无法访问通常在Windows资源管理器的“此PC”节点下看到的网络位置。
在环顾一个好时机之后,我终于找到了我需要的,并为那些有兴趣以编程方式浏览的人创建一个快速帮助库。
背景
为此,我们将只需要的C#的工作知识,它可能是有帮助的如何浏览目录在C#中的一些知识System.IO.DriveInfo
和System.IO.DirectoryInfo
类,静态方法和SHELL32
DLL。
走过这个过程
我遇到的第一个挑战是试图找到这些网络位置甚至存储的位置。他们必须是SOMEWHERE,最后。看了一会儿后,我终于发现他们实际上是存储在以下目录中的快捷方式:“ %AppData%/Microsoft/Windows/Network
Shortcuts
”
大!我们找到当前用户的“网络快捷方式”!现在,使用C#以编程方式来访问这个位置。首先,我们不能简单地输入,new
System.IO.DirectoryInfo("%AppData%/Microsoft/Windows/Network Shortcuts")
因为System.IO.DirectoryInfo
类(我们将用于浏览这些快捷方式)没有环境变量的任何概念。我们实际上必须输入new
System.IO.DirectoryInfo("C:\Users\<<current_user>>\AppData\Roaming\Microsoft\Windows\Network Shortcuts")
(<<current_user>>
在Windows中替换为您的用户),尽管这将导致我们进入正确的位置,但这并不能补偿存储此数据的更改。我们实际想要使用的是枚举System.Environment.SpecialFolder
。要完成这个,我们会进入 System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData)
; 这是%AppData%
我们在Windows资源管理器中使用的“ ”环境变量的编程相当的。使用System.IO.Path.Combine
,我们可以创建完整的目录路径到我们的网络快捷方式:
-
private static string NetworkLocationsPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Network Shortcuts");
现在我们已经保存了NetworkShortcuts的位置并准备使用,接下来,我们需要遍历此目录中的每个快捷方式。为此,如上所述,我们将使用System.IO.DirectoryInfo
该类。
-
DirectoryInfo networkShortcuts = new DirectoryInfo(NetworkLocationsPath);
这些快捷方式不是文件快捷方式,而是文件夹快捷方式,因此,而不是使用System.IO.DirectoryInfo.GetFiles
,我们将改为使用System.IO.DirectoryInfo.GetDirectories
。现在用快速foreach
语句,我们将遍历这个文件夹中的每个“目录”。
在查看DirectoryInfo类的一个实例中的其中一个快捷方式的每个属性和方法之后,遇到两个问题。首先,我不知道目录是否是快捷方式,其次,如果实例引用快捷方式,我无法获取快捷方式的目标。
看了一会儿后,我找到了两个解决方案来解决我的问题:
我发现的第一个解决方案是一个精采编码的自定义类,它将从快捷方式中提取目标路径(下面的代码),这个类的好处是我不需要引用Shell32
dll。我将类稍微操作为一个静态类,并隐藏了一些暴露的类和属性,我觉得不一定需要暴露。以下不是我的代码,我已经寻找原始的来源,我把代码从而找不到
- 如果你碰巧知道,请让我知道,我会相应地更新这篇文章。
-
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();
-
}
- }
我发现的第二个解决方案是通过Shell32
在Microsoft
Shell控件和自动化 COM类型库中引用该库。要在Visual
Studio中添加此项目,请在项目中右键单击“ 引用”,单击“ 添加引用”,展开“ 参考管理器 ”左侧的COM选项,单击“ 类型库”,向下滚动到“ Microsoft
Shell控件和自动化”,检查复选框,然后单击确定。
现在,要访问这些信息,我们将使用下面的代码。我设置了这个代码来使用静态方法和变量,以便将它并入到稍后将要介绍的助手类中。
-
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
使用外推路径信息,我可以System.IO.DirectoryInfo
使用该路径创建一个新的实例。
以上是方便的,但是,我想把所有这一切都包装成一个简单的类,它将类似于System.IO.DriveInfo
类,这将允许我获取所有的网络位置,并处理所有的名称解析,快捷方式目标提取和有效为我提供一站式服务。
我决定在System.IO
命名空间中创建这个类,尽管你可以随意改变,但是你认为合适。
-
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 a 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 }
- }
有了这个助手类,我们现在可以通过简单的调用浏览我们的网络位置快捷方式 NetworkLocationInfo.GetNetworkLocations()
对于我的用途,我正在创建一个具有所有网络位置的树,下面是我如何实现这个帮助类。
-
// 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 { }
- }
享受快乐编码!