2023/09/07(木)ジャンクション(NTFS)の更新日時を変更する

ジャンクション(NTFS)の更新日時を変更する

ちょっとジャンクション(NTFS)の更新日時を書き換えようとしたら、すんなりできなくて結構手間取ったのでメモ。

通常の方法ではリンク先の更新日時が変更される

まずはディレクトリとジャンクションをセットで作成。

$directory = New-Item -Path "test_directory" -ItemType Directory
$junction = New-Item -Value "test_directory" -Path "test_junction" -ItemType Junction
dir

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2023/09/07      3:48                test_directory
d----l        2023/09/07      3:48                test_junction

そして、ジャンクションの方に更新日時を設定してみると、ジャンクションではなくリンク先のディレクトリの更新日時が書き換えられてしまいました。

$junction.LastWriteTime = "2000-01-02T03:04:05+09:00"
dir

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2000/01/02      3:04                test_directory
d----l        2023/09/07      3:48                test_junction

仕様と言えばそうなのかも知れませんが、それだったら表示される更新日時もリンク先のものでないと一貫性が無いような……。

ジャンクションそのものの更新日時を変更する方法

まずは「Junction LastWriteTime」とかで調べてみると、英語圏で同様の疑問が挙がっていましたが手っ取り早い解決策は無さそうでした。(Windows APIを直接使う必要がありそうな感じ。)

更新日時変更をWindows APIを使って行う場合、CreateFile→SetFileTimeという手順になるのでこっちを調べてみたところ、ジャンクション(リパースポイント)をCreateFileで開くときにOPEN_REPARSE_POINTフラグを付けて開くとリンクを辿らずに開いてくれそうです。

FileTimeコピープログラム作成

OPEN_REPARSE_POINTを指定するにはWindows APIで直接指定するしか無さそうなので、自分でプログラムを作ることにしました。

日時を自由に指定できるようにするには日時文字列の解釈が必要になって面倒なので、他のファイル・フォルダの日時をコピーするプログラムです。これなら、GetFileTimeが使えるので楽。

SetFileTimeがcreationTime/lastAccessTime/lastWriteTimeを扱うので全部コピーしています。(コピーしたくなければNULLを渡せば変更されない。)

手抜きなので失敗してもメッセージとかは出ません(^_^;)

  • ソースコード
    #include <windows.h>
    #include <tchar.h>
    
    int _tmain(int argc, _TCHAR *argv[])
    {
        HANDLE hFrom;
        HANDLE hTo;
    
        FILETIME creationTime, lastAccessTime, lastWriteTime;
    
        if (argc < 3)
        {
            return 1;
        }
    
        //----------------------------------------
        // Get FILETIME from argv[1]
        //----------------------------------------
        hFrom = CreateFile(
            argv[1],
            GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS,
            NULL);
        if (hFrom == INVALID_HANDLE_VALUE)
        {
            return 1;
        }
        if (!GetFileTime(hFrom, &creationTime, &lastAccessTime, &lastWriteTime))
        {
            return 1;
        }
        CloseHandle(hFrom);
    
        //----------------------------------------
        // Set FILETIME to argv[2]
        //----------------------------------------
        hTo = CreateFile(
            argv[2],
            GENERIC_READ | GENERIC_WRITE,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
            NULL);
        if (hTo == INVALID_HANDLE_VALUE)
        {
            return 1;
        }
        if (!SetFileTime(hTo, &creationTime, &lastAccessTime, &lastWriteTime))
        {
            return 1;
        }
        CloseHandle(hTo);
    
        return 0;
    }
    
  • コンパイル
    "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat"
    cl.exe /DUNICODE /D_UNICODE /source-charset:utf-8 CopyFileTime.c
    

ジャンクションへのFileTimeコピー実験

CopyFileTime.exe test_directory test_junction

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2000/01/02      3:04                test_directory
d----l        2000/01/02      3:04                test_junction

ちゃんと日時コピーできてました。

コピー元の方はOPEN_REPARSE_POINTを付けずに開いている(リンク先を辿る)ので、

CopyFileTime.exe test_junction test_junction

としても同じ効果のはず。