15分タイマー - @ledsun blog AppUserModelID を設定したショートカットをつくるPoweShellスクリプトを使いました。
スクリプトの中には明らかにC#と思われるコードが含まれています。 C#へ変換すると理解が進みそうです。 ChatGPTに変換して貰いました。
プロジェクトファイル
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- Windows 10 (19041) 以降のWindows専用APIを使うためのTFM --> <TargetFramework>net9.0-windows10.0.19041.0</TargetFramework> <!-- x64固定(win-x64) --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <OutputType>Exe</OutputType> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <PublishAot>true</PublishAot> </PropertyGroup> </Project>
Program.cs
internal static class Program { static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine("使い方: ShortcutAumidSetter <ショートカット名(拡張子なし)> <実行ファイルの実パス> <AppUserModelID>"); return; } string rawName = args[0]; string shortcutName = Path.GetFileNameWithoutExtension(rawName); // .lnk が付いていても削除 string exePath = args[1]; string appUserModelId = args[2]; string startMenu = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu); string programs = Path.Combine(startMenu, "Programs"); Directory.CreateDirectory(programs); string lnkPath = Path.Combine(programs, shortcutName + ".lnk"); // 既存ショートカットは削除 if (File.Exists(lnkPath)) { var attr = File.GetAttributes(lnkPath); if (attr.HasFlag(FileAttributes.ReadOnly)) File.SetAttributes(lnkPath, attr & ~FileAttributes.ReadOnly); File.Delete(lnkPath); } // ヘルパーに処理を委譲 LnkAumidHelper.WriteShortcutWithAumid(lnkPath, exePath, appUserModelId); Console.WriteLine($"OK: AppUserModelID set -> {lnkPath}"); } }
LnkAumidHelper.cs
using System.Runtime.InteropServices; public static class LnkAumidHelper { /// <summary> /// lnk を新規作成または上書きし、AppUserModelID を設定して保存する /// </summary> public static void WriteShortcutWithAumid(string lnkPath, string exePath, string appUserModelId) { var link = (IShellLinkW)new CShellLink(); var persist = (IPersistFile)link; // 必須情報を設定 link.SetPath(exePath); link.SetWorkingDirectory(Path.GetDirectoryName(exePath) ?? ""); // AUMID を設定 var store = (IPropertyStore)link; SetAumidOnStore(store, appUserModelId); // 一度だけ保存 persist.Save(lnkPath, true); persist.SaveCompleted(lnkPath); Marshal.ReleaseComObject(store); Marshal.ReleaseComObject(persist); Marshal.ReleaseComObject(link); } // === 以下は内部実装なので private === [ComImport, Guid("00021401-0000-0000-C000-000000000046")] private class CShellLink { } [ComImport, Guid("000214F9-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellLinkW { int GetPath(IntPtr pszFile, int cch, IntPtr pfd, uint fFlags); int GetIDList(out IntPtr ppidl); int SetIDList(IntPtr pidl); int GetDescription(IntPtr pszName, int cch); int SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); int GetWorkingDirectory(IntPtr pszDir, int cch); int SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); int GetArguments(IntPtr pszArgs, int cch); int SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); int GetHotkey(out short wHotkey); int SetHotkey(short wHotkey); int GetShowCmd(out int iShowCmd); int SetShowCmd(int iShowCmd); int GetIconLocation(IntPtr pszIconPath, int cch, out int iIcon); int SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); int SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved); int Resolve(IntPtr hWnd, uint fFlags); int SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); } [ComImport, Guid("0000010b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IPersistFile { int GetClassID(out Guid pClassID); int IsDirty(); int Load([MarshalAs(UnmanagedType.LPWStr)] string pszFileName, int dwMode); int Save([MarshalAs(UnmanagedType.LPWStr)] string pszFileName, bool fRemember); int SaveCompleted([MarshalAs(UnmanagedType.LPWStr)] string pszFileName); int GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName); } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] private interface IPropertyStore { int GetCount(out uint cProps); int GetAt(uint iProp, out PROPERTYKEY pkey); int GetValue(ref PROPERTYKEY key, out PROPVARIANT pv); int SetValue(ref PROPERTYKEY key, ref PROPVARIANT pv); int Commit(); } [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct PROPERTYKEY { public Guid fmtid; public uint pid; } private static readonly PROPERTYKEY PKEY_AppUserModel_ID = new PROPERTYKEY { fmtid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), pid = 5 }; [StructLayout(LayoutKind.Sequential)] private struct PROPVARIANT { public ushort vt, w1, w2, w3; public IntPtr p; public int i1, i2; } private const ushort VT_LPWSTR = 31; [DllImport("ole32.dll")] private static extern int PropVariantClear(ref PROPVARIANT pvar); private static void SetAumidOnStore(IPropertyStore store, string appId) { PROPVARIANT pv = new PROPVARIANT { vt = VT_LPWSTR, p = Marshal.StringToCoTaskMemUni(appId) }; var key = PKEY_AppUserModel_ID; store.SetValue(ref key, ref pv); store.Commit(); PropVariantClear(ref pv); } }
これがちゃんと動くのです。すごい。
おまかせで書かせると結構余計な機能を入れがちです。 前提条件を厳しくしてエラー処理を省かせる必要があります。 また、コードの設計はあまり上手くないので、クラス分けの仕方は明確に指示する必要があります。 とはいえ対話だけでリファクタリングできました。
動かしたらエラーは出ました。 が、一箇所だけでした。 随分、賢くなったように思います。
AIが書いてくれなかったらCOMを使うプログラムをビルドして実行していませんでした。 最初の一歩を踏み出すための障壁が減るのがとても良いです。