Skip to content

Instantly share code, notes, and snippets.

@davidair
Last active June 22, 2020 22:24
Show Gist options
  • Save davidair/9acba4b66b2669f6faa542f880e47ed6 to your computer and use it in GitHub Desktop.
Save davidair/9acba4b66b2669f6faa542f880e47ed6 to your computer and use it in GitHub Desktop.
Example on how to save a shortcut with PKEY_AppUserModel_ID
/**
* Copyright 2020 Google LLC.
* SPDX-License-Identifier: MIT
*/
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApp1
{
internal static class UnsafeNativeMethods
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WIN32_FIND_DATAW
{
internal uint dwFileAttributes;
// ftCreationTime was a by-value FILETIME structure
internal uint ftCreationTime_dwLowDateTime;
internal uint ftCreationTime_dwHighDateTime;
// ftLastAccessTime was a by-value FILETIME structure
internal uint ftLastAccessTime_dwLowDateTime;
internal uint ftLastAccessTime_dwHighDateTime;
// ftLastWriteTime was a by-value FILETIME structure
internal uint ftLastWriteTime_dwLowDateTime;
internal uint ftLastWriteTime_dwHighDateTime;
internal uint nFileSizeHigh;
internal uint nFileSizeLow;
internal uint dwReserved0;
internal uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
internal string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
internal string cAlternateFileName;
}
/// <summary>IShellLink.Resolve fFlags</summary>
[Flags()]
internal enum SLR_FLAGS
{
/// <summary>
/// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set,
/// the high-order word of fFlags can be set to a time-out value that specifies the
/// maximum amount of time to be spent resolving the link. The function returns if the
/// link cannot be resolved within the time-out duration. If the high-order word is set
/// to zero, the time-out duration will be set to the default value of 3,000 milliseconds
/// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out
/// duration, in milliseconds.
/// </summary>
SLR_NO_UI = 0x1,
/// <summary>Obsolete and no longer used</summary>
SLR_ANY_MATCH = 0x2,
/// <summary>If the link object has changed, update its path and list of identifiers.
/// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine
/// whether or not the link object has changed.</summary>
SLR_UPDATE = 0x4,
/// <summary>Do not update the link information</summary>
SLR_NOUPDATE = 0x8,
/// <summary>Do not execute the search heuristics</summary>
SLR_NOSEARCH = 0x10,
/// <summary>Do not use distributed link tracking</summary>
SLR_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
/// Universal Naming Convention (UNC) path to track remote file systems whose drive letter
/// has changed. Setting SLR_NOLINKINFO disables both types of tracking.</summary>
SLR_NOLINKINFO = 0x40,
/// <summary>Call the Microsoft Windows Installer</summary>
SLR_INVOKE_MSI = 0x80
}
[Flags()]
internal enum SLGP_FLAGS
{
/// <summary>Retrieves the standard short (8.3 format) file name</summary>
SLGP_SHORTPATH = 0x1,
/// <summary>Retrieves the Universal Naming Convention (UNC) path name of the file</summary>
SLGP_UNCPRIORITY = 0x2,
/// <summary>Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded</summary>
SLGP_RAWPATH = 0x4
}
}
/// <summary>
/// This is the CoClass that impliments the shell link interfaces.
/// </summary>
[ComImport, Guid("00021401-0000-0000-C000-000000000046") ]
internal class ShellLinkCoClass {}
/// <summary>The IShellLink interface allows Shell links to be created, modified, and resolved</summary>
[ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
interface IShellLinkW
{
/// <summary>Retrieves the path and file name of a Shell link object</summary>
void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out UnsafeNativeMethods.WIN32_FIND_DATAW pfd, UnsafeNativeMethods.SLGP_FLAGS fFlags);
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
void GetIDList(out IntPtr ppidl);
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
void SetIDList(IntPtr pidl);
/// <summary>Retrieves the description string for a Shell link object</summary>
void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
/// <summary>Sets the name of the working directory for a Shell link object</summary>
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
/// <summary>Sets the command-line arguments for a Shell link object</summary>
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
/// <summary>Retrieves the hot key for a Shell link object</summary>
void GetHotkey(out short pwHotkey);
/// <summary>Sets a hot key for a Shell link object</summary>
void SetHotkey(short wHotkey);
/// <summary>Retrieves the show command for a Shell link object</summary>
void GetShowCmd(out int piShowCmd);
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
void SetShowCmd(int iShowCmd);
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
int cchIconPath, out int piIcon);
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
/// <summary>Sets the relative path to the Shell link object</summary>
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
void Resolve(IntPtr hwnd, UnsafeNativeMethods.SLR_FLAGS fFlags);
/// <summary>Sets the path and file name of a Shell link object</summary>
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000010B-0000-0000-C000-000000000046")]
internal interface IPersistFile
{
#region Methods inherited from IPersist
void GetClassID(out Guid pClassID);
#endregion
[PreserveSig]
int IsDirty();
void Load(
[MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
int dwMode);
void Save(
[MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
[MarshalAs(UnmanagedType.Bool)] bool fRemember);
void SaveCompleted(
[MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
void GetCurFile(
out IntPtr ppszFileName);
}
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCount([Out] out uint cProps);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAt([In] uint iProp, out PropertyKey pkey);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetValue([In] ref PropertyKey key, out object pv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetValue([In] ref PropertyKey key, [In] ref object pv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void Commit();
}
class Program
{
static void Main(string[] args)
{
var shellLinkClass = new ShellLinkCoClass();
IShellLinkW shellLink = (IShellLinkW)shellLinkClass;
shellLink.SetPath(@"C:\WINDOWS\system32\notepad.exe");
IPropertyStore propertyStore = (IPropertyStore)shellLinkClass;
IPersistFile persistFile = (IPersistFile)shellLinkClass;
shellLink.SetArguments("run");
// https://docs.microsoft.com/en-us/windows/win32/properties/props-system-appusermodel-id
propertyStore.SetValue(new PropertyKey("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3", 5), "MyAppTest");
propertyStore.Commit();
persistFile.Save(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.lnk"), true);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment