Files
2021-02-06 14:52:41 +01:00

448 lines
13 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
namespace DesktopNotifications.Windows
{
// Modified from http://smdn.jp/programming/tips/createlnk/
// Originally from http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects
// /Creating_and_Modifying_Shortcuts/article.asp
// Partly based on Sending toast notifications from desktop apps sample
public class ShellLink : IDisposable
{
#region Win32 and COM
// IShellLink Interface
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
private interface IShellLinkW
{
uint GetPath([Out] [MarshalAs(UnmanagedType.LPWStr)]
StringBuilder pszFile,
int cchMaxPath, 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 cchMaxName);
uint SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
uint GetWorkingDirectory([Out] [MarshalAs(UnmanagedType.LPWStr)]
StringBuilder pszDir,
int cchMaxPath);
uint SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
uint GetArguments([Out] [MarshalAs(UnmanagedType.LPWStr)]
StringBuilder pszArgs,
int cchMaxPath);
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 cchIconPath, 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 readonly uint dwFileAttributes;
public readonly FILETIME ftCreationTime;
public readonly FILETIME ftLastAccessTime;
public readonly FILETIME ftLastWriteTime;
public readonly uint nFileSizeHigh;
public readonly uint nFileSizeLow;
public readonly uint dwReserved0;
public readonly uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
public readonly string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public readonly 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
#endregion
#region Public Properties
public Guid FormatId { get; }
public int PropertyId { get; }
#endregion
#region Constructor
public PropertyKey(Guid formatId, int propertyId)
{
FormatId = formatId;
PropertyId = propertyId;
}
public PropertyKey(string formatId, int propertyId)
{
FormatId = new Guid(formatId);
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)] private ushort valueType; // Value type
// [FieldOffset(2)]
// ushort wReserved1; // Reserved field
// [FieldOffset(4)]
// ushort wReserved2; // Reserved field
// [FieldOffset(6)]
// ushort wReserved3; // Reserved field
[FieldOffset(8)] private readonly IntPtr ptr; // Value
#endregion
#region Public Properties
// Value type (System.Runtime.InteropServices.VarEnum)
public VarEnum VarType
{
get => (VarEnum) valueType;
set => valueType = (ushort) value;
}
// Whether value is empty or null
public bool IsNullOrEmpty =>
valueType == (ushort) VarEnum.VT_EMPTY ||
valueType == (ushort) VarEnum.VT_NULL;
// Value (only for string value)
public string? Value => 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);
#endregion
#region Fields
private IShellLinkW? shellLinkW;
// Name = System.AppUserModel.ID
// ShellPKey = PKEY_AppUserModel_ID
// FormatID = 9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3
// PropID = 5
// Type = String (VT_LPWSTR)
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 STGM_READ = 0x00000000; // STGM constants
private const uint SLGP_UNCPRIORITY = 0x0002; // SLGP flags
#endregion
#region Private Properties (Interfaces)
private IPersistFile PersistFile
{
get
{
if (!(shellLinkW is IPersistFile persistFile))
{
throw new COMException("Failed to create IPersistFile.");
}
return persistFile;
}
}
private IPropertyStore PropertyStore
{
get
{
if (!(shellLinkW is IPropertyStore PropertyStore))
{
throw new COMException("Failed to create IPropertyStore.");
}
return PropertyStore;
}
}
#endregion
#region Public Properties (Minimal)
// Path of loaded shortcut file
public string ShortcutFile
{
get
{
string shortcutFile;
PersistFile.GetCurFile(out shortcutFile);
return shortcutFile;
}
}
// Path of target file
public string TargetPath
{
get
{
// No limitation to length of buffer string in the case of Unicode though.
StringBuilder targetPath = new StringBuilder(MAX_PATH);
var data = new WIN32_FIND_DATAW();
VerifySucceeded(shellLinkW!.GetPath(targetPath, targetPath.Capacity, ref data,
SLGP_UNCPRIORITY));
return targetPath.ToString();
}
set => VerifySucceeded(shellLinkW!.SetPath(value));
}
public string Arguments
{
get
{
// No limitation to length of buffer string in the case of Unicode though.
StringBuilder arguments = new StringBuilder(INFOTIPSIZE);
VerifySucceeded(shellLinkW!.GetArguments(arguments, arguments.Capacity));
return arguments.ToString();
}
set => VerifySucceeded(shellLinkW!.SetArguments(value));
}
// AppUserModelID to be used for Windows 7 or later.
public string AppUserModelID
{
get
{
using (PropVariant pv = new PropVariant())
{
VerifySucceeded(PropertyStore.GetValue(AppUserModelIDKey, pv));
if (pv.Value == null)
{
return "Null";
}
return pv.Value;
}
}
set
{
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()
{
string file = ShortcutFile;
if (file == null)
{
throw new InvalidOperationException("File name is not given.");
}
Save(file);
}
public void Save(string file)
{
if (file == null)
{
throw new ArgumentNullException(nameof(file));
}
PersistFile.Save(file, true);
}
// Load shortcut file.
public void Load(string file)
{
if (!File.Exists(file))
{
throw new FileNotFoundException("File is not found.", file);
}
PersistFile.Load(file, STGM_READ);
}
// Verify if operation succeeded.
public static void VerifySucceeded(uint hresult)
{
if (hresult > 1)
{
throw new InvalidOperationException("Failed with HRESULT: " +
hresult.ToString("X"));
}
}
#endregion
}
}