ただ、ここにあるもの

でんねこが骨を埋める場所

NAS上の画像をエクスプローラーでタグ付けしたら「アクセス許可が必要」って出て地獄になった話(ネタバレ:犯人は『偽装拡張子』)

えー、なんか最近ブログも何もせずに色々やってました。
何をしてたかと言うと、AIを使った色んな改良ですね。
そんな中でふざけたことが次々と起こりまして、そんなわけで疲れ切った私はAIと殴り合い、寝て、殴り合い、寝てを繰り返してました。

そんないわゆる『AIと殴り合いシリーズ』を「もうネタにしちまおう!」ってわけでしばらくやっていきます。ネタにするっきゃねえ!!!

注意: このシリーズはAIにある程度まとめさせています。
「なんかAIっぽさあるな……」と思っても勘弁してくださいね。

環境

  • Windows 11(Home)
  • NAS:Synology DS223j(SMB共有)
  • ネットワークドライブ割り当て:Uドライブの直下にある『画像フォルダ』
  • 画像管理:当初は Windows エクスプローラーの「プロパティ > 詳細 > タグ」で付与

症状(起きたこと)

アクセス許可が必要ってエラーが出てしまったぜ!

  • U:\... 配下の特定フォルダ(例:大空スバル)で 複数ファイルを選択してタグ付け(CTRL+A → タグ追加) すると、
    • <管理者権限を持ったユーザー>からアクセス許可を得る必要がある」系のダイアログが出る。
  • ただし タグ自体は一部のファイルに付与されている(=完全に失敗ではない)。
  • 単体ファイルに対してタグ付与すると、ダイアログが出ないケースがある。
  • 最終的に “そのフォルダ内の特定の1ファイル”だけが原因だと切り分けできた。

はい。この画像フォルダ、とりわけホロライブ系の画像整理が今回のリングです。
Eagleも使いにくく、どうしようもこうしようもなかったため、Windowsエクスプローラーを主軸にした運用を試みたわけです。
でも、このままだと複数コラボのイラストやスクショとかはわからず、検索もできず、実に愚かじゃありませんか? ってなるわけです。ハァ、ハァ…敗北者?

というわけで、エクスプローラーのプロパティを使って管理することにしたのです。
早速全選択してタグを付与…しようと思ったのですが、できませんでした。

ラウンド1

まずここで大きく躓き、AIとのバトル開始。
最初に出したAIの提案は『NASの権限が誤っている』というもの。要するに『画像フォルダの権限上プロパティが作れないのでは?』というもの。
しかし、権限を見ても管理者権限、ほぼフルコントロールでした。

次に出してきたのは『thumbs.db / desktop.ini が原因説』いわゆるシステムファイルですね。
「thumbs.dbを見つけたので、これを誤ってタグつけようとしたんじゃね? 隠しファイルを全部表示してみなさい」って解釈でした。
ですが、これもハズレ。しかし『thumbs.db』自体はネットワークフォルダで生成されると困るので『DisableThumbsDBOnNetworkFolders』で止めました。
(なお、ここでもChatGPTはWin11Homeで使えない『gpedit』を提案していた)

さて、ここにきて でんねこ選手がとんでもない事実に気づきます。
『1ファイルだけ、タグが付いていない』
そこでAIの返した答えは『NTFSの所有権が誤っている』というもの。
ですがこれもハズレ。所有権に問題はありませんでした。そもそもこのPC自体うちしかアカウントを作ってないからアカウント設定の誤りはないんだよ!たぶん!

その後もコピーして作り直したりと色々試みましたが、状況は一向に変わりません。
色々考えた結果、1つの結論に達します。

『これ、拡張子が間違っている???』

jpg(実はwebP)

正解でした。拡張子はjpgなのに、実はwebpだったのです。これがタイトルの『偽装拡張子』ってやつですね。おそらく『jpg.webp』となっていたものを自分が迂闊に『.jpg』に書き換えたのが原因かも知れませんが、真相は闇の中です。

ただ、答えはわかりました。あとはこれを直せばいいだけ。 おそらくこの画像ファイルだけではないと考え、拡張子が誤っているものを洗い出して変更することにしました。

これがChatGPTがだしてきたプログラム。ですが動きませんでした。
そこでGeminiに作り直させ、Cludeに精査を依頼したところ、ようやく動くようになりました。

$root = "U:"
Get-ChildItem $root -Recurse -File -Include *.jpg,*.jpeg | ForEach-Object {
    $fs = $null
    $newExt = $null
    try {
        # ファイルサイズチェック
        if ($_.Length -lt 12) { return }
        
        $bytes = [byte[]]::new(12)
        $fs = [System.IO.File]::OpenRead($_.FullName)
        $readCount = $fs.Read($bytes, 0, $bytes.Length)
        $fs.Dispose()  # ここで即座に閉じる
        $fs = $null
        
        # 読み込みバイト数の確認
        if ($readCount -lt 12) { return }
        
        # シグネチャ判定
        $sig4  = [System.Text.Encoding]::ASCII.GetString($bytes,0,4)
        $sig6  = [System.Text.Encoding]::ASCII.GetString($bytes,0,6)
        $riff  = ($sig4 -eq "RIFF")
        $webp  = ([System.Text.Encoding]::ASCII.GetString($bytes,8,4) -eq "WEBP")
        $png   = ($bytes[0] -eq 0x89 -and $bytes[1] -eq 0x50 -and 
                  $bytes[2] -eq 0x4E -and $bytes[3] -eq 0x47)
        $gif   = ($sig6 -eq "GIF87a" -or $sig6 -eq "GIF89a")
        
        if ($riff -and $webp) { $newExt = ".webp" }
        elseif ($png)         { $newExt = ".png"  }
        elseif ($gif)         { $newExt = ".gif"  }
        
        if ($newExt) {
            $newPath = [System.IO.Path]::ChangeExtension($_.FullName, $newExt)
            
            # 同名衝突を連番で回避
            $counter = 1
            while (Test-Path $newPath) {
                $base = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
                $dir  = $_.DirectoryName
                $newPath = Join-Path $dir ("{0}_fix{1}{2}" -f $base, $counter, $newExt)
                $counter++
            }
            
            Rename-Item -LiteralPath $_.FullName -NewName (Split-Path $newPath -Leaf)
        }
    } catch {
        # 読めないファイルはスキップ
    } finally {
        if ($null -ne $fs) { $fs.Dispose() }
    }
}

※ AIが作ったものなので動作は保証しません。

これをPowershellで動かして、偽装拡張子は『一応』なくなったようです。これにて一件落着!
……と行きたかったですが、まだ本題が残っています。
そう、タグつけです。

でも、長くなりすぎたので、また次回お話しましょう。
後半へ続く。