using System; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Security.AccessControl; using System.Security.Permissions; using System.Text; using System.Windows.Forms; using Vanara.Extensions; using Vanara.PInvoke; using static Vanara.PInvoke.Macros; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Kernel32; using static Vanara.PInvoke.Shell32; // ReSharper disable UnusedParameter.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global // ReSharper disable SuspiciousTypeConversion.Global // ReSharper disable InconsistentNaming namespace Vanara.Windows.Shell { /// Flags determining how the links with missing targets are resolved. [Flags] public enum LinkResolution : uint { /// No flags set. None = 0, /// /// Do not display a dialog box if the link cannot be resolved. When NoUI is set, a time-out value that specifies /// the maximum amount of time to be spent resolving the link can be specified in milliseconds. The function /// returns if the link cannot be resolved within the time-out duration. If the timeout is not set, the time-out /// duration will be set to the default value of 3,000 milliseconds (3 seconds). /// NoUI = 0x1, /// Allow any match during resolution. Has no effect on ME/2000 or above, use the other flags instead. AnyMatch = 0x2, /// /// If the link object has changed, update its path and list of identifiers. If UPDATE is set, you do not need to /// call IPersistFile::IsDirty to determine whether or not the link object has changed. /// Update = 0x4, /// Do not update the link information. NoUpdate = 0x8, /// Do not execute the search heuristics. NoSearch = 0x10, /// Do not use distributed link tracking. NoTrack = 0x20, /// /// Disable distributed link tracking. By default, distributed link tracking tracks removable media across /// multiple devices based on the volume name. It also uses the UNC path to track remote file systems whose drive /// letter has changed. Setting NoLinkInfo disables both types of tracking. /// NoLinkInfo = 0x40, /// Call the Microsoft Windows Installer. InvokeMSI = 0x80, /// Windows XP and later. Assume same as NoUI but intended for applications without a hWnd. NoUIWithMsgPump = 0x101, /// /// Windows 7 and later. Offer the option to delete the shortcut when this method is unable to resolve it, even /// if the shortcut is not a shortcut to a file. /// OfferDeleteWithoutFile = 0x200, /// /// Windows 7 and later. Report as dirty if the target is a known folder and the known folder was redirected. /// This only works if the original target path was a file system path or ID list and not an aliased known folder /// ID list. /// KnownFolder = 0x400, /// /// Windows 7 and later. Resolve the computer name in UNC targets that point to a local computer. This value is /// used with SLDFKEEPLOCALIDLISTFORUNCTARGET. /// MachineInLocalTarget = 0x800, /// Windows 7 and later. Update the computer GUID and user SID if necessary. UpdateMachineAndSid = 0x1000, /// ?? Assuming this does not update the Object ID NoObjectID = 0x2000 } /// Represents a Shell Shortcut (.lnk) file. public sealed class ShellLink : ShellItem { internal IShellLinkW link; private ShellItem target; /// Initializes a new instance of the class, which acts as a wrapper for a .lnk file. /// The shortcut file (.lnk) to load. /// /// The window that the Shell will use as the parent for a dialog box. The Shell displays the dialog box if it needs to prompt the user for more /// information while resolving a Shell link. /// /// The resolve flags. /// The time out. /// linkFile public ShellLink(string linkFile, LinkResolution resolveFlags = LinkResolution.NoUI, IWin32Window window = null, TimeSpan timeOut = default) : base(linkFile) { LoadAndResolve(linkFile, (SLR_FLAGS)resolveFlags, ShellFolder.IWin2Ptr(window), (ushort)timeOut.TotalMilliseconds); } internal ShellLink(IShellItem iItem) : base(iItem) { LoadAndResolve(iItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH), SLR_FLAGS.SLR_NO_UI); } private ShellLink() { } /*public string AppUserModelID { get { using (PropVariant pv = new PropVariant()) { VerifySucceeded(PropertyStore.GetValue(AppUserModelIDKey, pv)); if (pv.Value == null) return "Null"; else return pv.Value; } } set { using (PropVariant pv = new PropVariant(value)) { VerifySucceeded(PropertyStore.SetValue(AppUserModelIDKey, pv)); VerifySucceeded(PropertyStore.Commit()); } } }*/ /// Gets/sets any command line arguments associated with the link public string Arguments { get => GetStringValue(link.GetArguments, MAX_PATH); set { link.SetArguments(value); Save(); } } /// Gets/sets the description of the link public string Description { get => GetStringValue(link.GetDescription, ComCtl32.INFOTIPSIZE); set { link.SetDescription(value); Save(); } } /// Gets the full path of the link file. /// The full path of the link file. public string FullPath => GetPath(SLGP.SLGP_RAWPATH); /// Gets/sets the HotKey to start the shortcut (if any). public Keys HotKey { get { var hk = link.GetHotKey(); return (Keys)MAKELONG(LOBYTE(hk), HIBYTE(hk)); } set { link.SetHotKey(MAKEWORD((byte)LOWORD((uint)value), (byte)HIWORD((uint)value))); Save(); } } /// Gets the index of this icon within the icon path's resources. public IconLocation IconLocation { get { var iconPath = new StringBuilder(MAX_PATH, MAX_PATH); link.GetIconLocation(iconPath, iconPath.Capacity, out var iconIndex); return new IconLocation(iconPath.ToString(), iconIndex); } set { link.SetIconLocation(value.ModuleFileName, value.ResourceId); Save(); } } /// Get or sets the list of item identifiers for a Shell link. public PIDL IDList { get => link.GetIDList(); set { link.SetIDList(value); Save(); } } /// Gets/sets the relative path to the link's target public string RelativeTargetPath { get => GetPath(SLGP.SLGP_RELATIVEPRIORITY); set { link.SetRelativePath(value, 0); Save(); } } /// Gets or sets a value indicating whether the target is run with Administrator rights. /// true if run as Administrator; otherwise, false. public bool RunAsAdministrator { get => ((IShellLinkDataList)link).GetFlags().IsFlagSet(SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER); set { var dl = (IShellLinkDataList)link; dl.SetFlags(dl.GetFlags().SetFlags(SHELL_LINK_DATA_FLAGS.SLDF_RUNAS_USER, value)); Save(); } } /// Gets/sets the short (8.3 format) path to the link's target public string ShortTargetPath => GetPath(SLGP.SLGP_SHORTPATH); /// Gets or sets the show command for a Shell link object. /// The show command for a Shell link object. public FormWindowState ShowState { get => (FormWindowState)link.GetShowCmd() - 1; set { link.SetShowCmd((ShowWindowCommand)value + 1); Save(); } } /// Gets or sets the target with a instance. public ShellItem Target { get => target ?? (target = new ShellItem(link.GetIDList())); set => link.SetIDList(value.PIDL); } /// Gets/sets the fully qualified path to the link's target public string TargetPath { get => GetPath(SLGP.SLGP_UNCPRIORITY); set { link.SetPath(value); Save(); } } /// Gets/sets the Working Directory for the Link public string WorkingDirectory { get => GetStringValue(link.GetWorkingDirectory, MAX_PATH); set { link.SetWorkingDirectory(value); Save(); } } /// Creates or overwrites a new link file. /// The link filename. /// The full path to the target file. /// The description of the link. /// The working directory for the execution of the target. /// The arguments for the target's execution. /// An instance of a representing the values supplied. public static ShellLink Create(string linkFilename, string targetFilename, string description = null, string workingDirectory = null, string arguments = null) { if (File.Exists(linkFilename)) throw new InvalidOperationException("File already exists."); var lnk = new ShellLink { link = new IShellLinkW(), TargetPath = targetFilename, Description = description, WorkingDirectory = workingDirectory, Arguments = arguments }; lnk.SaveAs(linkFilename); lnk.Init((IShellItem)lnk.link); return lnk; } /// Creates or overwrites a new link file. /// The link filename. /// The ShellItem for the target. /// The description of the link. /// The working directory for the execution of the target. /// The arguments for the target's execution. /// An instance of a representing the values supplied. public static ShellLink Create(string linkFilename, ShellItem target, string description = null, string workingDirectory = null, string arguments = null) { if (File.Exists(linkFilename)) throw new InvalidOperationException("File already exists."); var lnk = new ShellLink { link = new IShellLinkW(), Target = target, Description = description, WorkingDirectory = workingDirectory, Arguments = arguments }; lnk.SaveAs(linkFilename); lnk.Init((IShellItem)lnk.link); return lnk; } /// /// Copies an existing file to a new file, allowing the overwriting of an existing file. /// /// The name of the new file to copy to. /// true to allow an existing file to be overwritten; otherwise false. /// A new file, or an overwrite of an existing file if overwrite is true. If the file exists and overwrite is false, an IOException is thrown. public ShellLink CopyTo(string destShellLink, bool overwrite = false) { File.Copy(FullPath, destShellLink, overwrite); return new ShellLink(destShellLink); } /// Dispose the object, releasing the COM ShellLink object public override void Dispose() { if (link != null) { Marshal.ReleaseComObject(link); link = null; } //Release(link); base.Dispose(); } /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object obj) { var link2 = obj as ShellLink; if (link2 != null) return string.Equals(link2.ToString(), ToString(), StringComparison.InvariantCultureIgnoreCase); return base.Equals(obj); } /// /// Gets a FileSecurity object that encapsulates the specified type of access control list (ACL) entries for the file described by the current FileInfo object. /// /// One of the AccessControlSections values that specifies which group of access control entries to retrieve. /// A FileSecurity object that encapsulates the access control rules for the current file. public FileSecurity GetAccessControl(AccessControlSections includeSections = AccessControlSections.Access | AccessControlSections.Group | AccessControlSections.Owner) => File.GetAccessControl(FullPath, includeSections); /// Returns a hash code for this instance. /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() => ToString().GetHashCode(); /// Gets the icon for this link file. /// if set to true retrieve the large icon; other retrieve the small icon. /// The icon. public Icon GetIcon(bool large) { var loc = IconLocation; if (loc.IsValid) return loc.Icon; // If there are no details set for the icon, then we must use the shell to get the icon for the target var sfi = new ShellFileInfo(TargetPath); return large ? sfi.LargeIcon : sfi.SmallIcon; } /// /// Applies access control list (ACL) entries described by a FileSecurity object to the file described by the current FileInfo object. /// /// A FileSecurity object that describes an access control list (ACL) entry to apply to the current file. public void SetAccessControl(FileSecurity fileSecurity) { File.SetAccessControl(FullPath, fileSecurity); } /*/// Returns a that represents this instance. /// A that represents this instance. // Path and title should be case insensitive. Shell treats arguments as case sensitive because apps can handle // those differently. public override string ToString() => $"{(Properties.GetProperty(PROPERTYKEY.System.Title) ?? "").ToUpperInvariant()} {FullPath.ToUpperInvariant()} {Arguments}";*/ private static string GetStringValue(Action method, int buffSize) { var ret = new StringBuilder(buffSize, buffSize); method(ret, ret.Capacity); return ret.ToString(); } private string GetPath(SLGP value) { var target = new StringBuilder(MAX_PATH, MAX_PATH); var fd = new WIN32_FIND_DATA(); link.GetPath(target, target.Capacity, fd, value); return target.ToString(); } private void LoadAndResolve(string linkFile, SLR_FLAGS resolveFlags, HWND hWin = default, ushort timeOut = 0) { if (string.IsNullOrEmpty(linkFile)) throw new ArgumentNullException(nameof(linkFile)); var fullPath = Path.GetFullPath(linkFile); if (!File.Exists(fullPath)) throw new FileNotFoundException("Link file not found.", linkFile); link = new IShellLinkW(); if (resolveFlags.IsFlagSet(SLR_FLAGS.SLR_NO_UI) && timeOut != 0) resolveFlags = (SLR_FLAGS)MAKELONG((ushort)resolveFlags, timeOut); new FileIOPermission(FileIOPermissionAccess.Read, fullPath).Demand(); ((IPersistFile)link).Load(fullPath, 0); //STGM_DIRECT) link.Resolve(hWin, resolveFlags); } /// Saves the shortcut to ShortCutFile. private void Save() { if (File.Exists(FullPath)) ((IPersistFile)link).Save(null, true); else if (FullPath != null) SaveAs(FullPath); } /// Saves the shortcut to the specified file /// The shortcut file (.lnk) private void SaveAs(string linkFile) { ((IPersistFile)link).Save(linkFile, true); } } }