using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; namespace AeroMCSetup; /// /// Verknüpfungen (.lnk) über die Shell-Schnittstellen – inklusive AppUserModelID, damit /// Taskleiste und Windows-Benachrichtigungen AeroMC wie bisher zuordnen (wie beim alten NSIS-Setup). /// public static class Shortcut { public static void Create(string link, string target, string description, string appUserModelId) { Directory.CreateDirectory(Path.GetDirectoryName(link)!); var shellLink = (IShellLinkW)new CShellLink(); try { shellLink.SetPath(target); shellLink.SetWorkingDirectory(Path.GetDirectoryName(target)!); shellLink.SetIconLocation(target, 0); shellLink.SetDescription(description); var store = (IPropertyStore)shellLink; var key = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); // PKEY_AppUserModel_ID var value = new PropVariant { vt = 31 /* VT_LPWSTR */, p = Marshal.StringToCoTaskMemUni(appUserModelId) }; try { store.SetValue(ref key, ref value); store.Commit(); } finally { Marshal.FreeCoTaskMem(value.p); } ((IPersistFile)shellLink).Save(link, true); } finally { Marshal.ReleaseComObject(shellLink); } } /// Ziel einer vorhandenen Verknüpfung, oder null. public static string? Target(string link) { if (!File.Exists(link)) return null; var shellLink = (IShellLinkW)new CShellLink(); try { ((IPersistFile)shellLink).Load(link, 0); var sb = new StringBuilder(1024); shellLink.GetPath(sb, sb.Capacity, IntPtr.Zero, 0); return sb.ToString(); } catch { return null; } finally { Marshal.ReleaseComObject(shellLink); } } // ---------- COM ---------- [ComImport, Guid("00021401-0000-0000-C000-000000000046")] private class CShellLink { } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")] private interface IShellLinkW { void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, uint fFlags); void GetIDList(out IntPtr ppidl); void SetIDList(IntPtr pidl); void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); void GetHotkey(out short pwHotkey); void SetHotkey(short wHotkey); void GetShowCmd(out int piShowCmd); void SetShowCmd(int iShowCmd); void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon); void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved); void Resolve(IntPtr hwnd, uint fFlags); void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] private interface IPropertyStore { void GetCount(out uint cProps); void GetAt(uint iProp, out PropertyKey pkey); void GetValue(ref PropertyKey key, out PropVariant pv); void SetValue(ref PropertyKey key, ref PropVariant pv); void Commit(); } [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct PropertyKey(Guid fmtid, uint pid) { public Guid fmtid = fmtid; public uint pid = pid; } // PROPVARIANT: 16 Byte (32 Bit) bzw. 24 Byte (64 Bit) [StructLayout(LayoutKind.Sequential)] private struct PropVariant { public ushort vt; public ushort r1, r2, r3; public IntPtr p; public IntPtr p2; } }