C#「NTFSのADSでファイルにコメントをつける」

コンピュータ

実行環境構築

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new winforms
code .

ソースプログラム

ファイル名:Form1.cs

using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Security;
using System.Diagnostics;

namespace EditComment;

public partial class Form1 : Form
{
    // CreateFileW
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern IntPtr CreateFileW(
        [MarshalAs(UnmanagedType.LPWStr)] string filename,
        [MarshalAs(UnmanagedType.U4)] FileAccess access,
        [MarshalAs(UnmanagedType.U4)] FileShare share,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
        IntPtr templateFile);
    
    // CloseHandle
    [DllImport("kernel32.dll", SetLastError=true)]
    [SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    // WriteFile
    [DllImport("kernel32.dll")]
    static extern bool WriteFile(IntPtr hFile, byte [] lpBuffer,
        uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten,
        [In] ref System.Threading.NativeOverlapped lpOverlapped);

    // ReadFile
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer,
        uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
    
    // GetFileSizeEx
    [DllImport("kernel32.dll")]
    static extern bool GetFileSizeEx(IntPtr hFile, out long lpFileSize);

    public Form1()
    {
        InitializeComponent();
        
        string[] args = System.Environment.GetCommandLineArgs();

        if (2 > args.Length) return;

        string file = args[1];
        if (!File.Exists(file)) return;

        string msg = String.Format("「{0}」のコメント", file);
        string comment = "";

        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

        var handle = CreateFileW(file+":Comment", FileAccess.ReadWrite, FileShare.Read, IntPtr.Zero, FileMode.OpenOrCreate, FileAttributes.Normal, IntPtr.Zero);
        if (handle != IntPtr.Zero)
        {
            Debug.Print("Success");

            long len = 0;
            GetFileSizeEx(handle, out len);
            if (len > 0)
            {
                UInt32 sz = 0;
                UInt32 l = (UInt32)len;
                byte[] buf = new byte[len];
                ReadFile(handle, buf, l, out sz, IntPtr.Zero);
                comment = (System.Text.Encoding.GetEncoding("Shift_JIS")).GetString(buf);
            }
        }
        else
        {
            Debug.Print("Failed");
            return;
        }


        Size = new Size(640, 360);
        Text = "コメント";

        var panel = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            Padding = new Padding(16),
        };

        var label = new Label
        {
            Dock = DockStyle.Fill,
            Margin = new Padding(16),
            Text = msg,
        };
        panel.Controls.Add(label, 0, 0);

        var tbox = new TextBox
        {
            Dock = DockStyle.Fill,
            Margin = new Padding(16),
            Text = comment,
        };
        panel.Controls.Add(tbox, 0, 1);

        var panel2 = new FlowLayoutPanel
        {
            Margin = new Padding(16),
            Anchor = AnchorStyles.None,
            Dock = DockStyle.Fill,
        };
        panel.Controls.Add(panel2, 0, 2);

        var okBtn = new Button
        {
            Margin = new Padding(16),
            Size = new Size(100, 40),
            Text = "OK",
        };
        panel2.Controls.Add(okBtn);

        var cancelBtn = new Button
        {
            Margin = new Padding(16),
            Size = new Size(100, 40),
            Text = "Cancel",
        };
        panel2.Controls.Add(cancelBtn);

        Controls.Add(panel);
        Action<Form> okAction = (Form form) =>
        {
            string str = tbox.Text;
            byte[] bytes = (System.Text.Encoding.GetEncoding("Shift_JIS")).GetBytes(str.ToCharArray());
            UInt32 length = (UInt32)bytes.Length;
            UInt32 sz = 0;
            System.Threading.NativeOverlapped lpOverlapped = new();
            WriteFile(handle, bytes, length, out sz, ref lpOverlapped);

            CloseHandle(handle);
            form.Close();
        };
        Action<Form> cancelAction = (Form form) =>
        {
            CloseHandle(handle);
            form.Close();
        };
        okBtn.Click += (s, e) => okAction(this);
        tbox.KeyDown += (s, e) =>
        {
            if (e.KeyCode == Keys.Return)
            {
                okAction(this);
            }
        };
        cancelBtn.Click += (s, e) => cancelAction(this);
    }
}

使い方

コマンドライン引数にコメントをつけたいファイルのパスをセットして起動します。
エクスプローラーの「送る」で使うことを想定しています。

ファイルのプロパティぽい使い方になりますが、エクスプローラーではコメントの一覧が表示できないので使い勝手は今一つ。
文字コードはShift_JISにしていますが何が正解かは理解していません。

コメント