Libs/ShellLink/ShellLink.cs
using System;
using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using ComTypes = System.Runtime.InteropServices.ComTypes; // Original code is https://emoacht.wordpress.com/2012/11/14/csharp-appusermodelid/ public class ShellLink : IDisposable { #region Win32 and COM // IShellLink Interface [ComImport] [Guid("000214F9-0000-0000-C000-000000000046")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellLinkW { uint GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cch, ref WIN32_FIND_DATAW pfd, uint fFlags); uint GetIDList(out IntPtr ppidl); uint SetIDList(IntPtr pidl); uint GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cch); uint SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); uint GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cch); uint SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); uint GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cch); uint SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); uint GetHotKey(out ushort pwHotkey); uint SetHotKey(ushort wHotKey); uint GetShowCmd(out int piShowCmd); uint SetShowCmd(int iShowCmd); uint GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cch, out int piIcon); uint SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); uint SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved); uint Resolve(IntPtr hwnd, uint fFlags); uint SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); } // ShellLink CoClass (ShellLink object) [ComImport] [ClassInterface(ClassInterfaceType.None)] [Guid("00021401-0000-0000-C000-000000000046")] private class CShellLink { } // WIN32_FIND_DATAW Structure [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)] private struct WIN32_FIND_DATAW { public uint dwFileAttributes; public ComTypes.FILETIME ftCreationTime; public ComTypes.FILETIME ftLastAccessTime; public ComTypes.FILETIME ftLastWriteTime; public uint nFileSizeHigh; public uint nFileSizeLow; public uint dwReserved0; public uint dwReserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] public string cFileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public string cAlternateFileName; } // IPropertyStore Interface [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] private interface IPropertyStore { uint GetCount([Out] out uint cProps); uint GetAt([In] uint iProp, out PropertyKey pkey); uint GetValue([In] ref PropertyKey key, [Out] PropVariant pv); uint SetValue([In] ref PropertyKey key, [In] PropVariant pv); uint Commit(); } // PropertyKey Structure // Narrowed down from PropertyKey.cs of Windows API Code Pack 1.1 [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct PropertyKey { #region Fields private Guid formatId; // Unique GUID for property private Int32 propertyId; // Property identifier (PID) #endregion #region Public Properties public Guid FormatId { get { return formatId; } } public Int32 PropertyId { get { return propertyId; } } #endregion #region Constructor public PropertyKey(Guid formatId, Int32 propertyId) { this.formatId = formatId; this.propertyId = propertyId; } public PropertyKey(string formatId, Int32 propertyId) { this.formatId = new Guid(formatId); this.propertyId = propertyId; } #endregion } // PropVariant Class (only for string value) // Narrowed down from PropVariant.cs of Windows API Code Pack 1.1 // Originally from http://blogs.msdn.com/b/adamroot/archive/2008/04/11 // /interop-with-propvariants-in-net.aspx [StructLayout(LayoutKind.Explicit)] private sealed class PropVariant : IDisposable { #region Fields [FieldOffset(0)] ushort valueType; // Value type // [FieldOffset(2)] // ushort wReserved1; // Reserved field // [FieldOffset(4)] // ushort wReserved2; // Reserved field // [FieldOffset(6)] // ushort wReserved3; // Reserved field [FieldOffset(8)] IntPtr ptr; // Value #endregion #region Public Properties // Value type (System.Runtime.InteropServices.VarEnum) public VarEnum VarType { get { return (VarEnum)valueType; } set { valueType = (ushort)value; } } // Whether value is empty or null public bool IsNullOrEmpty { get { return (valueType == (ushort)VarEnum.VT_EMPTY || valueType == (ushort)VarEnum.VT_NULL); } } // Value (only for string value) public string Value { get { return Marshal.PtrToStringUni(ptr); } } #endregion #region Constructor public PropVariant() { } // Construct with string value public PropVariant(string value) { if (value == null) throw new ArgumentException("Failed to set value."); valueType = (ushort)VarEnum.VT_LPWSTR; ptr = Marshal.StringToCoTaskMemUni(value); } #endregion #region Destructor ~PropVariant() { Dispose(); } public void Dispose() { PropVariantClear(this); GC.SuppressFinalize(this); } #endregion } [DllImport("Ole32.dll", PreserveSig = false)] private static extern void PropVariantClear([In, Out] PropVariant pvar); [DllImport("Shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] private static extern void SHGetNameFromIDList(IntPtr pidl, uint sigdnName, [Out, MarshalAs(UnmanagedType.LPTStr)] out string ppszName); [DllImport("Shell32.dll")] private static extern void ILFree(IntPtr pidl); #endregion #region Private private IShellLinkW shellLinkW = null; private bool readOnly = false; private readonly PropertyKey AppUserModelIDKey = new PropertyKey("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", 5); private const int MAX_PATH = 260; private const int INFOTIPSIZE = 1024; private const int SW_SHOWNORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private const int SW_SHOWMAXIMIZED = 3; private const int SW_SHOWMINNOACTIVE = 7; private const int STGM_READ = 0x00000000; private const int STGM_READWRITE = 0x00000002; private const uint SLGP_UNCPRIORITY = 0x0002; private const uint SLGP_RAWPATH = 0x0004; private const uint SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000; private IPersistFile PersistFile { get { IPersistFile PersistFile = (IPersistFile)shellLinkW; if (PersistFile == null) throw new COMException("Failed to create IPersistFile."); else return PersistFile; } } private IPropertyStore PropertyStore { get { IPropertyStore PropertyStore = (IPropertyStore)shellLinkW; if (PropertyStore == null) throw new COMException("Failed to create IPropertyStore."); else return PropertyStore; } } #endregion #region Public // Path of loaded shortcut file public string FilePath { get { string shortcutFile; PersistFile.GetCurFile(out shortcutFile); return shortcutFile; } } // Path of target file public string TargetPath { get { StringBuilder targetPath = new StringBuilder(MAX_PATH); WIN32_FIND_DATAW data = new WIN32_FIND_DATAW(); VerifySucceeded(shellLinkW.GetPath(targetPath, targetPath.Capacity, ref data, SLGP_RAWPATH)); return targetPath.ToString(); } set { VerifyReadOnly(); VerifySucceeded(shellLinkW.SetPath(value)); } } // Shell item id list public string IDList { get { string idList; System.IntPtr pidl = IntPtr.Zero; try { VerifySucceeded(shellLinkW.GetIDList(out pidl)); SHGetNameFromIDList(pidl, SIGDN_DESKTOPABSOLUTEPARSING, out idList); return idList; } finally { ILFree(pidl); } } } // Description public string Description { get { StringBuilder description = new StringBuilder(INFOTIPSIZE); VerifySucceeded(shellLinkW.GetDescription(description, description.Capacity)); return description.ToString(); } set { VerifyReadOnly(); VerifySucceeded(shellLinkW.SetDescription(value)); } } // Arguments public string Arguments { get { StringBuilder arguments = new StringBuilder(INFOTIPSIZE); VerifySucceeded(shellLinkW.GetArguments(arguments, arguments.Capacity)); return arguments.ToString(); } set { VerifyReadOnly(); VerifySucceeded(shellLinkW.SetArguments(value)); } } // WorkingDirectory public string WorkingDirectory { get { StringBuilder workingDirectory = new StringBuilder(MAX_PATH); VerifySucceeded(shellLinkW.GetWorkingDirectory(workingDirectory, workingDirectory.Capacity)); return workingDirectory.ToString(); } set { VerifyReadOnly(); VerifySucceeded(shellLinkW.SetWorkingDirectory(value)); } } // IconLocation public string IconLocation { get { StringBuilder iconLocation = new StringBuilder(MAX_PATH); int iconIdx; VerifySucceeded(shellLinkW.GetIconLocation(iconLocation, iconLocation.Capacity, out iconIdx)); iconLocation.Append(","); iconLocation.Append(iconIdx.ToString()); return iconLocation.ToString(); } set { VerifyReadOnly(); int idx = value.LastIndexOf(","); string iconLocation; string strIdx; int iconIdx; if (idx >= 0) { strIdx = value.Substring(idx + 1); if (Int32.TryParse(strIdx, out iconIdx)) { iconLocation = value.Substring(0, idx); } else { iconLocation = value; iconIdx = 0; } } else { iconLocation = value; iconIdx = 0; } VerifySucceeded(shellLinkW.SetIconLocation(iconLocation, iconIdx)); } } // WindowStyle public int WindowStyle { get { int windowStyle; VerifySucceeded(shellLinkW.GetShowCmd(out windowStyle)); switch (windowStyle) { case SW_SHOWMINIMIZED: case SW_SHOWMINNOACTIVE: return SW_SHOWMINNOACTIVE; case SW_SHOWMAXIMIZED: return SW_SHOWMAXIMIZED; case SW_SHOWNORMAL: return SW_SHOWNORMAL; default: return 0; } } set { VerifyReadOnly(); int windowStyle; switch (value) { case 0: case 1: windowStyle = SW_SHOWNORMAL; break; case 3: windowStyle = SW_SHOWMAXIMIZED; break; case 7: windowStyle = SW_SHOWMINNOACTIVE; break; default: throw new ArgumentException("Unsupported value."); } VerifySucceeded(shellLinkW.SetShowCmd(windowStyle)); } } // Hotkey public ushort Hotkey { get { ushort hotKey; VerifySucceeded(shellLinkW.GetHotKey(out hotKey)); return hotKey; } set { VerifyReadOnly(); VerifySucceeded(shellLinkW.SetHotKey(value)); } } // AppUserModelID // https://docs.microsoft.com/en-us/windows/win32/shell/appids public string AppUserModelID { get { using (PropVariant pv = new PropVariant()) { VerifySucceeded(PropertyStore.GetValue(AppUserModelIDKey, pv)); if (pv.Value == null) return string.Empty; else return pv.Value; } } set { VerifyReadOnly(); using (PropVariant pv = new PropVariant(value)) { VerifySucceeded(PropertyStore.SetValue(AppUserModelIDKey, pv)); VerifySucceeded(PropertyStore.Commit()); } } } #endregion #region Constructor public ShellLink() : this(null) { } // Construct with loading shortcut file. public ShellLink(string file) { try { shellLinkW = (IShellLinkW)new CShellLink(); } catch { throw new COMException("Failed to create ShellLink object."); } if (file != null) Load(file); } #endregion #region Destructor ~ShellLink() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (shellLinkW != null) { // Release all references. Marshal.FinalReleaseComObject(shellLinkW); shellLinkW = null; } } #endregion #region Methods // Save shortcut file. public void Save() { VerifyReadOnly(); string file = FilePath; if (file == null) throw new InvalidOperationException("File name is not given."); else Save(file); } public void Save(string file) { if (file == null) { throw new ArgumentNullException("File name is required."); } else { VerifyReadOnly(); PersistFile.Save(file, true); } } // Load shortcut file. public void Load(string file) { Load(file, STGM_READWRITE); } public void Load(string file, int flags) { if (!File.Exists(file)) { throw new FileNotFoundException("File is not found.", file); } else { PersistFile.Load(file, flags); if ((flags & 0x0000000f) == 0) readOnly = true; } } // Verify if operation succeeded. private static void VerifySucceeded(uint hresult) { if (hresult > 1) throw new InvalidOperationException("Failed with HRESULT: " + hresult.ToString("X")); } // Verify if operation as read only. private void VerifyReadOnly() { if (readOnly) throw new UnauthorizedAccessException("This object is read-only."); } #endregion } |