diff --git a/Sources/EasyExtensions.Windows/ShellLink.cs b/Sources/EasyExtensions.Windows/ShellLink.cs index 49f7d53..cbcf7af 100644 --- a/Sources/EasyExtensions.Windows/ShellLink.cs +++ b/Sources/EasyExtensions.Windows/ShellLink.cs @@ -192,85 +192,132 @@ void GetPath([Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, } + /// + /// IShellLinkW interface for managing shell links (shortcuts) in Windows. + /// [ComImport()] [Guid("000214F9-0000-0000-C000-000000000046")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellLinkW { - //[helpstring("Retrieves the path and filename of a shell link object")] - void GetPath( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, - int cchMaxPath, - ref WIN32_FIND_DATAW pfd, - uint fFlags); - - //[helpstring("Retrieves the list of shell link item identifiers")] + /// + /// Retrieves the path and filename of a shell link object. + /// + /// The buffer that receives the path and filename. + /// The size, in characters, of the buffer pointed to by the pszFile parameter. + /// The WIN32_FIND_DATA structure that receives information about the shell link object. + /// Flags that specify the type of path information to retrieve. + void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, uint fFlags); + + /// + /// Retrieves the list of shell link item identifiers. + /// + /// The address of an ITEMIDLIST pointer that receives the list of item identifiers. void GetIDList(out IntPtr ppidl); - //[helpstring("Sets the list of shell link item identifiers")] + /// + /// Sets the list of shell link item identifiers. + /// + /// The address of an ITEMIDLIST that specifies the list of item identifiers to be set. void SetIDList(IntPtr pidl); - //[helpstring("Retrieves the shell link description string")] - void GetDescription( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, - int cchMaxName); + /// + /// Retrieves the shell link description string. + /// + /// The buffer that receives the description string. + /// The size, in characters, of the buffer pointed to by the pszFile parameter. + void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxName); - //[helpstring("Sets the shell link description string")] - void SetDescription( - [MarshalAs(UnmanagedType.LPWStr)] string pszName); + /// + /// Sets the shell link description string. + /// + /// The new description string. + void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); - //[helpstring("Retrieves the name of the shell link working directory")] - void GetWorkingDirectory( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, - int cchMaxPath); + /// + /// Retrieves the name of the shell link working directory. + /// + /// The buffer that receives the working directory. + /// The size, in characters, of the buffer pointed to by the pszDir parameter. + void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); - //[helpstring("Sets the name of the shell link working directory")] - void SetWorkingDirectory( - [MarshalAs(UnmanagedType.LPWStr)] string pszDir); + /// + /// Sets the name of the shell link working directory. + /// + /// The new working directory. + void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); - //[helpstring("Retrieves the shell link command-line arguments")] - void GetArguments( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, - int cchMaxPath); + /// + /// Retrieves the shell link command-line arguments. + /// + /// The buffer that receives the command-line arguments. + /// The size, in characters, of the buffer pointed to by the pszArgs parameter. + void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); - //[helpstring("Sets the shell link command-line arguments")] - void SetArguments( - [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + /// + /// Sets the shell link command-line arguments. + /// + /// The new command-line arguments. + void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); - //[propget, helpstring("Retrieves or sets the shell link hot key")] + /// + /// Retrieves the shell link hot key. + /// + /// The address of a variable that receives the hot key. void GetHotkey(out short pwHotkey); - //[propput, helpstring("Retrieves or sets the shell link hot key")] + + /// + /// Sets the shell link hot key. + /// + /// The new hot key. void SetHotkey(short pwHotkey); - //[propget, helpstring("Retrieves or sets the shell link show command")] + /// + /// Retrieves the shell link show command. + /// + /// The address of a variable that receives the show command. void GetShowCmd(out uint piShowCmd); - //[propput, helpstring("Retrieves or sets the shell link show command")] + + /// + /// Sets the shell link show command. + /// + /// The new show command. void SetShowCmd(uint piShowCmd); - //[helpstring("Retrieves the location (path and index) of the shell link icon")] - void GetIconLocation( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, - int cchIconPath, - out int piIcon); - - //[helpstring("Sets the location (path and index) of the shell link icon")] - void SetIconLocation( - [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, - int iIcon); - - //[helpstring("Sets the shell link relative path")] - void SetRelativePath( - [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, - uint dwReserved); - - //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] - void Resolve( - IntPtr hWnd, - uint fFlags); - - //[helpstring("Sets the shell link path and filename")] - void SetPath( - [MarshalAs(UnmanagedType.LPWStr)] string pszFile); + /// + /// Retrieves the location (path and index) of the shell link icon. + /// + /// The buffer that receives the icon path. + /// The size, in characters, of the buffer pointed to by the pszIconPath parameter. + /// The address of a variable that receives the icon index. + void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); + + /// + /// Sets the location (path and index) of the shell link icon. + /// + /// The new icon path. + /// The new icon index. + void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); + + /// + /// Sets the shell link relative path. + /// + /// The new relative path. + /// Reserved. Must be zero. + void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved); + + /// + /// Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary). + /// + /// A handle to the window that the system uses as a parent for any dialog boxes that it displays. + /// Flags that control the resolution process. + void Resolve(IntPtr hWnd, uint fFlags); + + /// + /// Sets the shell link path and filename. + /// + /// The new path and filename. + void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); } #endregion @@ -353,12 +400,12 @@ private struct FILETIME } #endregion - #region UnManaged Methods - private class UnManagedMethods + #region Unmanaged Methods + private class UnmanagedMethods { [DllImport("Shell32", CharSet = CharSet.Auto)] internal extern static int ExtractIconEx([MarshalAs(UnmanagedType.LPTStr)] - string lpszFile, int nIconIndex, IntPtr[] phIconLarge, IntPtr[] phIconSmall, int nIcons); + string lpszFile, int nIconIndex, IntPtr[]? phIconLarge, IntPtr[]? phIconSmall, int nIcons); [DllImport("user32")] internal static extern int DestroyIcon(IntPtr hIcon); @@ -380,10 +427,12 @@ public enum EShellLinkResolveFlags : uint /// on ME/2000 or above, use the other flags instead. /// SLR_ANY_MATCH = 0x2, + /// /// Call the Microsoft Windows Installer. /// SLR_INVOKE_MSI = 0x80, + /// /// Disable distributed link tracking. By default, /// distributed link tracking tracks removable media @@ -393,6 +442,7 @@ public enum EShellLinkResolveFlags : uint /// SLR_NOLINKINFO disables both types of tracking. /// SLR_NOLINKINFO = 0x40, + /// /// Do not display a dialog box if the link cannot be resolved. /// When SLR_NO_UI is set, a time-out value that specifies the @@ -403,23 +453,28 @@ public enum EShellLinkResolveFlags : uint /// set to the default value of 3,000 milliseconds (3 seconds). /// SLR_NO_UI = 0x1, + /// /// Not documented in SDK. Assume same as SLR_NO_UI but /// intended for applications without a hWnd. /// SLR_NO_UI_WITH_MSG_PUMP = 0x101, + /// /// Do not update the link information. /// SLR_NOUPDATE = 0x8, + /// /// Do not execute the search heuristics. /// SLR_NOSEARCH = 0x10, + /// /// Do not use distributed link tracking. /// SLR_NOTRACK = 0x20, + /// /// If the link object has changed, update its path and list /// of identifiers. If SLR_UPDATE is set, you do not need to @@ -432,7 +487,7 @@ public enum EShellLinkResolveFlags : uint /// /// Window Show Command enumeration /// - public enum LinkDisplayMode : uint + internal enum LinkDisplayMode : uint { /// /// Show the window in its default state @@ -533,15 +588,15 @@ public string ShortcutFile /// Gets a System.Drawing.Icon containing the icon for this /// ShellLink object. /// - public Icon LargeIcon => GetIcon(true); + public Icon? LargeIcon => GetIcon(true); /// /// Gets a System.Drawing.Icon containing the icon for this /// ShellLink object. /// - public Icon SmallIcon => GetIcon(false); + public Icon? SmallIcon => GetIcon(false); - private Icon GetIcon(bool large) + private Icon? GetIcon(bool large) { StringBuilder iconPath = new StringBuilder(260, 260); // Get icon index and path: @@ -572,24 +627,14 @@ private Icon GetIcon(bool large) IntPtr[] hIconEx = new IntPtr[1] { IntPtr.Zero }; if (large) { - _ = UnManagedMethods.ExtractIconEx( - iconFile, - iconIndex, - hIconEx, - null, - 1); + _ = UnmanagedMethods.ExtractIconEx(iconFile, iconIndex, hIconEx, null, 1); } else { - _ = UnManagedMethods.ExtractIconEx( - iconFile, - iconIndex, - null, - hIconEx, - 1); + _ = UnmanagedMethods.ExtractIconEx(iconFile, iconIndex, null, hIconEx, 1); } // If success then return as a GDI+ object - Icon icon = null; + Icon? icon = null; if (hIconEx[0] != IntPtr.Zero) { icon = Icon.FromHandle(hIconEx[0]); @@ -607,35 +652,37 @@ public string IconPath get { StringBuilder iconPath = new StringBuilder(260, 260); - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetIconLocation(iconPath, iconPath.Capacity, out _); } - else + else if (linkA != null && linkW == null) { linkA.GetIconLocation(iconPath, iconPath.Capacity, out _); } + else + { + throw new InvalidOperationException("Unable to get icon location"); + } return iconPath.ToString(); } set { StringBuilder iconPath = new StringBuilder(260, 260); int iconIndex; - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); + linkW.SetIconLocation(value, iconIndex); } - else + else if (linkA != null && linkW == null) { linkA.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); - } - if (linkA == null) - { - linkW.SetIconLocation(value, iconIndex); + linkA.SetIconLocation(value, iconIndex); } else { - linkA.SetIconLocation(value, iconIndex); + throw new InvalidOperationException("Unable to get icon location"); } } } @@ -649,33 +696,31 @@ public int IconIndex { StringBuilder iconPath = new StringBuilder(260, 260); int iconIndex; - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); } - else + else if (linkA != null && linkW == null) { linkA.GetIconLocation(iconPath, iconPath.Capacity, out iconIndex); } + else + { + throw new InvalidOperationException("Unable to get icon location"); + } return iconIndex; } set { StringBuilder iconPath = new StringBuilder(260, 260); - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetIconLocation(iconPath, iconPath.Capacity, out _); - } - else - { - linkA.GetIconLocation(iconPath, iconPath.Capacity, out _); - } - if (linkA == null) - { linkW.SetIconLocation(iconPath.ToString(), value); } - else + else if (linkA != null && linkW == null) { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out _); linkA.SetIconLocation(iconPath.ToString(), value); } } @@ -689,28 +734,36 @@ public string Target get { StringBuilder target = new StringBuilder(260, 260); - if (linkA == null) + if (linkA == null && linkW != null) { WIN32_FIND_DATAW fd = new WIN32_FIND_DATAW(); linkW.GetPath(target, target.Capacity, ref fd, (uint)EShellLinkGP.SLGP_UNCPRIORITY); } - else + else if (linkA != null && linkW == null) { WIN32_FIND_DATAA fd = new WIN32_FIND_DATAA(); linkA.GetPath(target, target.Capacity, ref fd, (uint)EShellLinkGP.SLGP_UNCPRIORITY); } + else + { + throw new InvalidOperationException("Unable to get target path"); + } return target.ToString(); } set { - if (linkA == null) + if (linkA == null && linkW != null) { linkW.SetPath(value); } - else + else if (linkA != null && linkW == null) { linkA.SetPath(value); } + else + { + throw new InvalidOperationException("Unable to set target path"); + } } } @@ -722,26 +775,34 @@ public string WorkingDirectory get { StringBuilder path = new StringBuilder(260, 260); - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetWorkingDirectory(path, path.Capacity); } - else + else if (linkA != null && linkW == null) { linkA.GetWorkingDirectory(path, path.Capacity); } + else + { + throw new InvalidOperationException("Unable to get working directory"); + } return path.ToString(); } set { - if (linkA == null) + if (linkA == null && linkW != null) { linkW.SetWorkingDirectory(value); } - else + else if (linkA != null && linkW == null) { linkA.SetWorkingDirectory(value); } + else + { + throw new InvalidOperationException("Unable to set working directory"); + } } } @@ -753,26 +814,34 @@ public string Description get { StringBuilder description = new StringBuilder(1024, 1024); - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetDescription(description, description.Capacity); } - else + else if (linkA != null && linkW == null) { linkA.GetDescription(description, description.Capacity); } + else + { + throw new InvalidOperationException("Unable to get description"); + } return description.ToString(); } set { - if (linkA == null) + if (linkA == null && linkW != null) { linkW.SetDescription(value); } - else + else if (linkA != null && linkW == null) { linkA.SetDescription(value); } + else + { + throw new InvalidOperationException("Unable to set description"); + } } } @@ -784,26 +853,34 @@ public string Arguments get { StringBuilder arguments = new StringBuilder(260, 260); - if (linkA == null) + if (linkA == null && linkW != null) { linkW.GetArguments(arguments, arguments.Capacity); } - else + else if (linkA != null && linkW == null) { linkA.GetArguments(arguments, arguments.Capacity); } + else + { + throw new InvalidOperationException("Unable to get arguments"); + } return arguments.ToString(); } set { - if (linkA == null) + if (linkA == null && linkW != null) { linkW.SetArguments(value); } - else + else if (linkA != null && linkW == null) { linkA.SetArguments(value); } + else + { + throw new InvalidOperationException("Unable to set arguments"); + } } } @@ -851,35 +928,32 @@ public void Save() /// Saves the shortcut to the specified file /// /// The shortcut file (.lnk) - public void Save( - string linkFile - ) + public void Save(string linkFile) { // Save the object to disk - if (linkA == null) + if (linkA == null && linkW != null) { ((IPersistFile)linkW).Save(linkFile, true); shortcutFile = linkFile; } - else + else if (linkA != null && linkW == null) { ((IPersistFile)linkA).Save(linkFile, true); shortcutFile = linkFile; } + else + { + throw new InvalidOperationException("Unable to save shortcut"); + } } /// /// Loads a shortcut from the specified file /// /// The shortcut file (.lnk) to load - public void Open( - string linkFile - ) + public void Open(string linkFile) { - Open(linkFile, - IntPtr.Zero, - (EShellLinkResolveFlags.SLR_ANY_MATCH | EShellLinkResolveFlags.SLR_NO_UI), - 1); + Open(linkFile, IntPtr.Zero, EShellLinkResolveFlags.SLR_ANY_MATCH | EShellLinkResolveFlags.SLR_NO_UI, 1); } /// @@ -889,11 +963,7 @@ string linkFile /// The shortcut file (.lnk) to load /// The window handle of the application's UI, if any /// Flags controlling resolution behaviour - public void Open( - string linkFile, - IntPtr hWnd, - EShellLinkResolveFlags resolveFlags - ) + public void Open(string linkFile, IntPtr hWnd, EShellLinkResolveFlags resolveFlags) { Open(linkFile, hWnd, resolveFlags, 1); } @@ -907,12 +977,7 @@ EShellLinkResolveFlags resolveFlags /// The window handle of the application's UI, if any /// Flags controlling resolution behaviour /// Timeout if SLR_NO_UI is specified, in ms. - public void Open( - string linkFile, - IntPtr hWnd, - EShellLinkResolveFlags resolveFlags, - ushort timeOut - ) + public void Open(string linkFile, IntPtr hWnd, EShellLinkResolveFlags resolveFlags, ushort timeOut) { uint flags; @@ -930,13 +995,13 @@ ushort timeOut { ((IPersistFile)linkW).Load(linkFile, 0); //STGM_DIRECT) linkW.Resolve(hWnd, flags); - this.shortcutFile = linkFile; + shortcutFile = linkFile; } else if (linkA != null && linkW == null) { ((IPersistFile)linkA).Load(linkFile, 0); //STGM_DIRECT) linkA.Resolve(hWnd, flags); - this.shortcutFile = linkFile; + shortcutFile = linkFile; } else { diff --git a/Sources/EasyExtensions.Windows/WinApi.cs b/Sources/EasyExtensions.Windows/WinApi.cs index 3ffa922..d468a1c 100644 --- a/Sources/EasyExtensions.Windows/WinApi.cs +++ b/Sources/EasyExtensions.Windows/WinApi.cs @@ -78,5 +78,64 @@ public static bool MoveToRecycleBin(this FileInfo file) } return DeleteFileOrFolder(file.FullName); } + + /// + /// Create a shortcut file. + /// + /// Target file path. + /// Link file path. + /// Icon file path. + /// Working directory path. + /// Thrown when target file or icon file not found. + /// Thrown when working directory not found. + /// Thrown when cannot create link file at specified location. + /// Thrown when link file must have .lnk extension. + public static void CreateLink(string targetFile, string linkFile, string? iconFile = null, string? workingDirectory = null) + { + FileInfo fileInfo = new FileInfo(targetFile); + if (!fileInfo.Exists) + { + throw new FileNotFoundException("Target file not found.", targetFile); + } + if (!linkFile.EndsWith(".lnk", StringComparison.OrdinalIgnoreCase)) + { + throw new FormatException("Link file must have .lnk extension."); + } + try + { + File.WriteAllText(linkFile, string.Empty); + File.Delete(linkFile); + } + catch (Exception ex) + { + throw new IOException("Cannot create link file at specified location.", ex); + } + if (iconFile != null) + { + if (!File.Exists(iconFile)) + { + throw new FileNotFoundException("Icon file not found.", iconFile); + } + } + if (workingDirectory != null) + { + if (!Directory.Exists(workingDirectory)) + { + throw new DirectoryNotFoundException("Working directory not found."); + } + } + + workingDirectory ??= fileInfo.DirectoryName; + iconFile ??= targetFile; + + ShellLink sl = new ShellLink + { + Target = targetFile, + IconPath = iconFile, + ShortcutFile = linkFile, + WorkingDirectory = workingDirectory + }; + sl.Save(); + } } }