2017-11-27 13:11:20 -05:00
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 ;
2018-01-20 20:19:18 -05:00
// ReSharper disable UnusedParameter.Global
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
2017-11-27 13:11:20 -05:00
// ReSharper disable SuspiciousTypeConversion.Global
// ReSharper disable InconsistentNaming
namespace Vanara.Windows.Shell
{
/// <summary>Flags determining how the links with missing targets are resolved.</summary>
[Flags]
public enum LinkResolution : uint
{
/// <summary>No flags set.</summary>
None = 0 ,
/// <summary>
/// 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).
/// </summary>
NoUI = 0x1 ,
/// <summary>Allow any match during resolution. Has no effect on ME/2000 or above, use the other flags instead.</summary>
AnyMatch = 0x2 ,
/// <summary>
/// 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.
/// </summary>
Update = 0x4 ,
/// <summary>Do not update the link information.</summary>
NoUpdate = 0x8 ,
/// <summary>Do not execute the search heuristics.</summary>
NoSearch = 0x10 ,
/// <summary>Do not use distributed link tracking.</summary>
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 UNC path to track remote file systems whose drive
/// letter has changed. Setting NoLinkInfo disables both types of tracking.
/// </summary>
NoLinkInfo = 0x40 ,
/// <summary>Call the Microsoft Windows Installer.</summary>
InvokeMSI = 0x80 ,
/// <summary>Windows XP and later. Assume same as NoUI but intended for applications without a hWnd.</summary>
NoUIWithMsgPump = 0x101 ,
/// <summary>
/// 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.
/// </summary>
OfferDeleteWithoutFile = 0x200 ,
/// <summary>
/// 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.
/// </summary>
2018-01-20 20:19:18 -05:00
KnownFolder = 0x400 ,
2017-11-27 13:11:20 -05:00
/// <summary>
/// Windows 7 and later. Resolve the computer name in UNC targets that point to a local computer. This value is
/// used with SLDFKEEPLOCALIDLISTFORUNCTARGET.
/// </summary>
MachineInLocalTarget = 0x800 ,
/// <summary>Windows 7 and later. Update the computer GUID and user SID if necessary.</summary>
UpdateMachineAndSid = 0x1000 ,
/// <summary>?? Assuming this does not update the Object ID</summary>
NoObjectID = 0x2000
}
/// <summary>Represents a Shell Shortcut (.lnk) file.</summary>
2018-01-20 20:19:18 -05:00
public sealed class ShellLink : ShellItem
2017-11-27 13:11:20 -05:00
{
internal IShellLinkW link ;
2018-01-20 20:19:18 -05:00
private ShellItem target ;
2017-11-27 13:11:20 -05:00
/// <summary>Initializes a new instance of the <see cref="ShellLink"/> class, which acts as a wrapper for a .lnk file.</summary>
/// <param name="linkFile">The shortcut file (.lnk) to load.</param>
/// <param name="window">
/// 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.
/// </param>
/// <param name="resolveFlags">The resolve flags.</param>
/// <param name="timeOut">The time out.</param>
/// <exception cref="System.ArgumentNullException">linkFile</exception>
2018-11-19 23:18:50 -05:00
public ShellLink ( string linkFile , LinkResolution resolveFlags = LinkResolution . NoUI , IWin32Window window = null , TimeSpan timeOut = default ) : base ( linkFile )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
LoadAndResolve ( linkFile , ( SLR_FLAGS ) resolveFlags , ShellFolder . IWin2Ptr ( window ) , ( ushort ) timeOut . TotalMilliseconds ) ;
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
internal ShellLink ( IShellItem iItem ) : base ( iItem )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
LoadAndResolve ( iItem . GetDisplayName ( SIGDN . SIGDN_FILESYSPATH ) , SLR_FLAGS . SLR_NO_UI ) ;
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
private ShellLink ( ) { }
2017-11-27 13:11:20 -05:00
/ * 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 ( ) ) ;
}
}
} * /
/// <summary>Gets/sets any command line arguments associated with the link</summary>
public string Arguments
{
2018-01-20 20:19:18 -05:00
get = > GetStringValue ( link . GetArguments , MAX_PATH ) ;
set { link . SetArguments ( value ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
/// <summary>Gets/sets the description of the link</summary>
public string Description
{
2018-01-20 20:19:18 -05:00
get = > GetStringValue ( link . GetDescription , ComCtl32 . INFOTIPSIZE ) ;
set { link . SetDescription ( value ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
/// <summary>Gets the full path of the link file.</summary>
/// <value>The full path of the link file.</value>
public string FullPath = > GetPath ( SLGP . SLGP_RAWPATH ) ;
2017-11-27 13:11:20 -05:00
/// <summary>Gets/sets the HotKey to start the shortcut (if any).</summary>
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 ( ) ; }
}
/// <summary>Gets the index of this icon within the icon path's resources.</summary>
2018-01-20 20:19:18 -05:00
public IconLocation IconLocation
2017-11-27 13:11:20 -05:00
{
get
{
2018-01-20 20:19:18 -05:00
var iconPath = new StringBuilder ( MAX_PATH , MAX_PATH ) ;
link . GetIconLocation ( iconPath , iconPath . Capacity , out var iconIndex ) ;
return new IconLocation ( iconPath . ToString ( ) , iconIndex ) ;
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
set { link . SetIconLocation ( value . ModuleFileName , value . ResourceId ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
/// <summary>Get or sets the list of item identifiers for a Shell link.</summary>
public PIDL IDList
{
2018-01-20 20:19:18 -05:00
get = > link . GetIDList ( ) ;
set { link . SetIDList ( value ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
/// <summary>Gets/sets the relative path to the link's target</summary>
public string RelativeTargetPath
{
2018-01-20 20:19:18 -05:00
get = > GetPath ( SLGP . SLGP_RELATIVEPRIORITY ) ;
set { link . SetRelativePath ( value , 0 ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
/// <summary>Gets or sets a value indicating whether the target is run with Administrator rights.</summary>
/// <value><c>true</c> if run as Administrator; otherwise, <c>false</c>.</value>
public bool RunAsAdministrator
{
2018-01-20 20:19:18 -05:00
get = > ( ( IShellLinkDataList ) link ) . GetFlags ( ) . IsFlagSet ( SHELL_LINK_DATA_FLAGS . SLDF_RUNAS_USER ) ;
2017-11-27 13:11:20 -05:00
set
{
var dl = ( IShellLinkDataList ) link ;
2018-01-20 20:19:18 -05:00
dl . SetFlags ( dl . GetFlags ( ) . SetFlags ( SHELL_LINK_DATA_FLAGS . SLDF_RUNAS_USER , value ) ) ;
2017-11-27 13:11:20 -05:00
Save ( ) ;
}
}
/// <summary>Gets/sets the short (8.3 format) path to the link's target</summary>
public string ShortTargetPath = > GetPath ( SLGP . SLGP_SHORTPATH ) ;
/// <summary>Gets or sets the show command for a Shell link object.</summary>
/// <value>The show command for a Shell link object.</value>
public FormWindowState ShowState
{
2018-01-20 20:19:18 -05:00
get = > ( FormWindowState ) link . GetShowCmd ( ) - 1 ;
set { link . SetShowCmd ( ( ShowWindowCommand ) value + 1 ) ; Save ( ) ; }
}
/// <summary>Gets or sets the target with a <see cref="ShellItem"/> instance.</summary>
public ShellItem Target
{
get = > target ? ? ( target = new ShellItem ( link . GetIDList ( ) ) ) ;
set = > link . SetIDList ( value . PIDL ) ;
2017-11-27 13:11:20 -05:00
}
/// <summary>Gets/sets the fully qualified path to the link's target</summary>
public string TargetPath
{
2018-01-20 20:19:18 -05:00
get = > GetPath ( SLGP . SLGP_UNCPRIORITY ) ;
set { link . SetPath ( value ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
/// <summary>Gets/sets the Working Directory for the Link</summary>
public string WorkingDirectory
{
2018-01-20 20:19:18 -05:00
get = > GetStringValue ( link . GetWorkingDirectory , MAX_PATH ) ;
set { link . SetWorkingDirectory ( value ) ; Save ( ) ; }
2017-11-27 13:11:20 -05:00
}
2018-01-20 20:19:18 -05:00
/// <summary>Creates or overwrites a new link file.</summary>
2017-11-27 13:11:20 -05:00
/// <param name="linkFilename">The link filename.</param>
/// <param name="targetFilename">The full path to the target file.</param>
/// <param name="description">The description of the link.</param>
/// <param name="workingDirectory">The working directory for the execution of the target.</param>
/// <param name="arguments">The arguments for the target's execution.</param>
/// <returns>An instance of a <see cref="ShellLink"/> representing the values supplied.</returns>
2018-01-20 20:19:18 -05:00
public static ShellLink Create ( string linkFilename , string targetFilename , string description = null , string workingDirectory = null , string arguments = null )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
if ( File . Exists ( linkFilename ) ) throw new InvalidOperationException ( "File already exists." ) ;
2017-11-27 13:11:20 -05:00
var lnk = new ShellLink
{
2018-01-20 20:19:18 -05:00
link = new IShellLinkW ( ) ,
2017-11-27 13:11:20 -05:00
TargetPath = targetFilename ,
Description = description ,
WorkingDirectory = workingDirectory ,
Arguments = arguments
} ;
lnk . SaveAs ( linkFilename ) ;
2018-01-20 20:19:18 -05:00
lnk . Init ( ( IShellItem ) lnk . link ) ;
return lnk ;
}
/// <summary>Creates or overwrites a new link file.</summary>
/// <param name="linkFilename">The link filename.</param>
/// <param name="target">The ShellItem for the target.</param>
/// <param name="description">The description of the link.</param>
/// <param name="workingDirectory">The working directory for the execution of the target.</param>
/// <param name="arguments">The arguments for the target's execution.</param>
/// <returns>An instance of a <see cref="ShellLink"/> representing the values supplied.</returns>
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 ) ;
2017-11-27 13:11:20 -05:00
return lnk ;
}
/// <summary>
/// Copies an existing file to a new file, allowing the overwriting of an existing file.
/// </summary>
/// <param name="destShellLink">The name of the new file to copy to.</param>
/// <param name="overwrite"><c>true</c> to allow an existing file to be overwritten; otherwise <c>false</c>.</param>
/// <returns>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.</returns>
public ShellLink CopyTo ( string destShellLink , bool overwrite = false )
{
2018-01-20 20:19:18 -05:00
File . Copy ( FullPath , destShellLink , overwrite ) ;
2017-11-27 13:11:20 -05:00
return new ShellLink ( destShellLink ) ;
}
/// <summary>Dispose the object, releasing the COM ShellLink object</summary>
2018-01-20 20:19:18 -05:00
public override void Dispose ( )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
if ( link ! = null ) { Marshal . ReleaseComObject ( link ) ; link = null ; }
//Release(link);
base . Dispose ( ) ;
2017-11-27 13:11:20 -05:00
}
/// <summary>Determines whether the specified <see cref="System.Object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals ( object obj )
{
var link2 = obj as ShellLink ;
if ( link2 ! = null )
2018-01-20 20:19:18 -05:00
return string . Equals ( link2 . ToString ( ) , ToString ( ) , StringComparison . InvariantCultureIgnoreCase ) ;
2017-11-27 13:11:20 -05:00
return base . Equals ( obj ) ;
}
/// <summary>
/// Gets a FileSecurity object that encapsulates the specified type of access control list (ACL) entries for the file described by the current FileInfo object.
/// </summary>
/// <param name="includeSections">One of the AccessControlSections values that specifies which group of access control entries to retrieve.</param>
/// <returns>A FileSecurity object that encapsulates the access control rules for the current file.</returns>
2018-01-20 20:19:18 -05:00
public FileSecurity GetAccessControl ( AccessControlSections includeSections = AccessControlSections . Access | AccessControlSections . Group | AccessControlSections . Owner ) = >
File . GetAccessControl ( FullPath , includeSections ) ;
2017-11-27 13:11:20 -05:00
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode ( ) = > ToString ( ) . GetHashCode ( ) ;
2018-01-20 20:19:18 -05:00
/// <summary>Gets the icon for this link file.</summary>
/// <param name="large">if set to <c>true</c> retrieve the large icon; other retrieve the small icon.</param>
/// <returns>The icon.</returns>
public Icon GetIcon ( bool large )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
var loc = IconLocation ;
if ( loc . IsValid ) return loc . Icon ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
// 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 ;
2017-11-27 13:11:20 -05:00
}
/// <summary>
/// Applies access control list (ACL) entries described by a FileSecurity object to the file described by the current FileInfo object.
/// </summary>
/// <param name="fileSecurity">A FileSecurity object that describes an access control list (ACL) entry to apply to the current file.</param>
public void SetAccessControl ( FileSecurity fileSecurity )
{
File . SetAccessControl ( FullPath , fileSecurity ) ;
}
2018-01-20 20:19:18 -05:00
/ * /// <summary>Returns a <see cref="System.String"/> that represents this instance.</summary>
2017-11-27 13:11:20 -05:00
/// <returns>A <see cref="System.String"/> that represents this instance.</returns>
// Path and title should be case insensitive. Shell treats arguments as case sensitive because apps can handle
// those differently.
public override string ToString ( ) = >
2018-01-20 20:19:18 -05:00
$"{(Properties.GetProperty<string>(PROPERTYKEY.System.Title) ?? "").ToUpperInvariant()} {FullPath.ToUpperInvariant()} {Arguments}" ; * /
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
private static string GetStringValue ( Action < StringBuilder , int > method , int buffSize )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
var ret = new StringBuilder ( buffSize , buffSize ) ;
method ( ret , ret . Capacity ) ;
return ret . ToString ( ) ;
2017-11-27 13:11:20 -05:00
}
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 ( ) ;
}
2018-10-26 14:24:07 -04:00
private void LoadAndResolve ( string linkFile , SLR_FLAGS resolveFlags , HWND hWin = default , ushort timeOut = 0 )
2017-11-27 13:11:20 -05:00
{
2018-01-20 20:19:18 -05:00
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 ) ;
2017-11-27 13:11:20 -05:00
2018-01-20 20:19:18 -05:00
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 ) ;
2017-11-27 13:11:20 -05:00
}
/// <summary>Saves the shortcut to ShortCutFile.</summary>
private void Save ( )
{
2018-01-20 20:19:18 -05:00
if ( File . Exists ( FullPath ) )
2017-11-27 13:11:20 -05:00
( ( IPersistFile ) link ) . Save ( null , true ) ;
else if ( FullPath ! = null )
SaveAs ( FullPath ) ;
}
/// <summary>Saves the shortcut to the specified file</summary>
/// <param name="linkFile">The shortcut file (.lnk)</param>
private void SaveAs ( string linkFile )
{
( ( IPersistFile ) link ) . Save ( linkFile , true ) ;
}
}
}