ShellLinkHelper.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Runtime.InteropServices; 7 using System.Runtime.InteropServices.ComTypes; 8 using System.Text; 9 10 using Accessibility; 11 using Wox.Plugin.Logger; 12 13 namespace Wox.Infrastructure 14 { 15 public class ShellLinkHelper : IShellLinkHelper 16 { 17 [Flags] 18 private enum SLGP_FLAGS 19 { 20 SLGP_SHORTPATH = 0x1, 21 SLGP_UNCPRIORITY = 0x2, 22 SLGP_RAWPATH = 0x4, 23 } 24 25 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 26 [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] 27 private struct WIN32_FIND_DATAW 28 { 29 public uint dwFileAttributes; 30 public long ftCreationTime; 31 public long ftLastAccessTime; 32 public long ftLastWriteTime; 33 public uint nFileSizeHigh; 34 public uint nFileSizeLow; 35 public uint dwReserved0; 36 public uint dwReserved1; 37 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 38 public string cFileName; 39 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] 40 public string cAlternateFileName; 41 } 42 43 [Flags] 44 public enum SLR_FLAGS 45 { 46 SLR_NO_UI = 0x1, 47 SLR_ANY_MATCH = 0x2, 48 SLR_UPDATE = 0x4, 49 SLR_NOUPDATE = 0x8, 50 SLR_NOSEARCH = 0x10, 51 SLR_NOTRACK = 0x20, 52 SLR_NOLINKINFO = 0x40, 53 SLR_INVOKE_MSI = 0x80, 54 } 55 56 // Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW 57 58 // The IShellLink interface allows Shell links to be created, modified, and resolved 59 [ComImport] 60 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 61 [Guid("000214F9-0000-0000-C000-000000000046")] 62 private interface IShellLinkW 63 { 64 /// <summary>Retrieves the path and file name of a Shell link object</summary> 65 void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); 66 67 /// <summary>Retrieves the list of item identifiers for a Shell link object</summary> 68 void GetIDList(out IntPtr ppidl); 69 70 /// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary> 71 void SetIDList(IntPtr pidl); 72 73 /// <summary>Retrieves the description string for a Shell link object</summary> 74 void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); 75 76 /// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary> 77 void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); 78 79 /// <summary>Retrieves the name of the working directory for a Shell link object</summary> 80 void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); 81 82 /// <summary>Sets the name of the working directory for a Shell link object</summary> 83 void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); 84 85 /// <summary>Retrieves the command-line arguments associated with a Shell link object</summary> 86 void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); 87 88 /// <summary>Sets the command-line arguments for a Shell link object</summary> 89 void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); 90 91 /// <summary>Retrieves the hot key for a Shell link object</summary> 92 void GetHotkey(out short pwHotkey); 93 94 /// <summary>Sets a hot key for a Shell link object</summary> 95 void SetHotkey(short wHotkey); 96 97 /// <summary>Retrieves the show command for a Shell link object</summary> 98 void GetShowCmd(out int piShowCmd); 99 100 /// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary> 101 void SetShowCmd(int iShowCmd); 102 103 /// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary> 104 void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); 105 106 /// <summary>Sets the location (path and index) of the icon for a Shell link object</summary> 107 void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); 108 109 /// <summary>Sets the relative path to the Shell link object</summary> 110 void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); 111 112 /// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary> 113 void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags); 114 115 /// <summary>Sets the path and file name of a Shell link object</summary> 116 void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); 117 } 118 119 [ComImport] 120 [Guid("00021401-0000-0000-C000-000000000046")] 121 private class ShellLink 122 { 123 } 124 125 // Contains the description of the app 126 public string Description { get; set; } = string.Empty; 127 128 // Contains the arguments to the app 129 public string Arguments { get; set; } = string.Empty; 130 131 public bool HasArguments { get; set; } 132 133 // Retrieve the target path using Shell Link 134 public string RetrieveTargetPath(string path) 135 { 136 var link = new ShellLink(); 137 const int STGM_READ = 0; 138 139 try 140 { 141 ((IPersistFile)link).Load(path, STGM_READ); 142 } 143 catch (System.IO.FileNotFoundException ex) 144 { 145 Log.Exception("Path could not be retrieved " + path, ex, GetType(), path); 146 Marshal.ReleaseComObject(link); 147 return string.Empty; 148 } 149 catch (System.Exception ex) 150 { 151 Log.Exception("Exception loading path " + path, ex, GetType(), path); 152 Marshal.ReleaseComObject(link); 153 return string.Empty; 154 } 155 156 var hwnd = default(_RemotableHandle); 157 ((IShellLinkW)link).Resolve(ref hwnd, 0); 158 159 const int MAX_PATH = 260; 160 StringBuilder buffer = new StringBuilder(MAX_PATH); 161 162 var data = default(WIN32_FIND_DATAW); 163 ((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH); 164 var target = buffer.ToString(); 165 166 // To set the app description 167 if (!string.IsNullOrEmpty(target)) 168 { 169 buffer = new StringBuilder(MAX_PATH); 170 try 171 { 172 ((IShellLinkW)link).GetDescription(buffer, MAX_PATH); 173 Description = buffer.ToString(); 174 } 175 catch (System.Exception e) 176 { 177 Log.Exception($"Failed to fetch description for {target}, {e.Message}", e, GetType()); 178 Description = string.Empty; 179 } 180 181 StringBuilder argumentBuffer = new StringBuilder(MAX_PATH); 182 ((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity); 183 Arguments = argumentBuffer.ToString(); 184 185 // Set variable to true if the program takes in any arguments 186 if (argumentBuffer.Length != 0) 187 { 188 HasArguments = true; 189 } 190 } 191 192 // To release unmanaged memory 193 Marshal.ReleaseComObject(link); 194 195 return target; 196 } 197 } 198 }