using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; namespace EmbyToolbox.Interop; internal static class ToastShortcutRegistration { private const int StgmReadwrite = 0x00000002; private static readonly PropertyKey AppUserModelIdKey = new( new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); public static string? LastDiagnostics { get; private set; } public static bool TryEnsureStartMenuShortcut(string appUserModelId) { LastDiagnostics = null; if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 10240)) { LastDiagnostics = "требуется Windows 10 (10240) или новее."; return false; } try { var exePath = Environment.ProcessPath; if (string.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath)) { LastDiagnostics = "не удалось определить путь к exe."; return false; } var programs = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu); var shortcutPath = Path.Combine(programs, "Programs", "Emby Toolbox.lnk"); Directory.CreateDirectory(Path.GetDirectoryName(shortcutPath)!); var shellLinkObject = (object)new CShellLink(); var shellLink = (IShellLinkW)shellLinkObject; shellLink.SetPath(exePath); shellLink.SetArguments(string.Empty); shellLink.SetWorkingDirectory(Path.GetDirectoryName(exePath) ?? AppContext.BaseDirectory); shellLink.SetDescription("Emby Toolbox"); if (File.Exists(Path.Combine(AppContext.BaseDirectory, "Resources", "AppIcon.ico"))) { shellLink.SetIconLocation(Path.Combine(AppContext.BaseDirectory, "Resources", "AppIcon.ico"), 0); } else { shellLink.SetIconLocation(exePath, 0); } using var appId = PropVariant.FromString(appUserModelId); var propertyStore = (IPropertyStore)shellLink; propertyStore.SetValue(AppUserModelIdKey, appId); propertyStore.Commit(); var persistFile = (IPersistFile)shellLink; persistFile.Save(shortcutPath, true); return true; } catch (Exception ex) { LastDiagnostics = $"{ex.GetType().Name}: {ex.Message}"; return false; } } [ComImport] [Guid("00021401-0000-0000-C000-000000000046")] private sealed class CShellLink { } [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214F9-0000-0000-C000-000000000046")] private interface IShellLinkW { void GetPath(IntPtr pszFile, int cchMaxPath, IntPtr pfd, uint fFlags); void GetIDList(out IntPtr ppidl); void SetIDList(IntPtr pidl); void GetDescription(IntPtr pszName, int cchMaxName); void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); void GetWorkingDirectory(IntPtr pszDir, int cchMaxPath); void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); void GetArguments(IntPtr 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(IntPtr 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("00000138-0000-0000-C000-000000000046")] 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(in PropertyKey key, in PropVariant pv); void Commit(); } [StructLayout(LayoutKind.Sequential, Pack = 4)] private readonly struct PropertyKey(Guid formatId, int propertyId) { private readonly Guid _formatId = formatId; private readonly int _propertyId = propertyId; } [StructLayout(LayoutKind.Sequential)] private sealed class PropVariant : IDisposable { private ushort _valueType; private ushort _reserved1; private ushort _reserved2; private ushort _reserved3; private IntPtr _value; private IntPtr _reserved4; public static PropVariant FromString(string value) { return new PropVariant { _valueType = 31, // VT_LPWSTR _value = Marshal.StringToCoTaskMemUni(value) }; } public void Dispose() { PropVariantClear(this); GC.SuppressFinalize(this); } ~PropVariant() { PropVariantClear(this); } [DllImport("ole32.dll")] private static extern int PropVariantClear([In, Out] PropVariant pvar); } }