5.3 ファイル名一括変更ツール

(1/1)
ファイル名一括変更ツールで写真の名前を揃えた陽奈
5.2 はじめてのWPFアプリ」では、ボタンと文字を1つずつ置いただけの小さなウィンドウを作った。本項では、毎日使える道具を1本作る。ファイル名一括変更ツール(プログラム名 RenameKit)である。フォルダーの中にあるファイル名を、決めた規則にしたがってまとめて変え、しかも変更を実行する前に、変更後の名前を一覧で確かめられる
本項の目的は、ツールが1本できることよりも、画面を作ってから配布するまでの流れを、通して体験することにある。画面を縦横に区切るGridStackPanel、WPFの主なコントロール、画面の表示とプログラムの値をつなぐデータバインディング、思ったとおりに動かないときの手がかりを残すログ、実行ファイルを作るdotnet publish、そして誰にでも配れる形にするMSIインストーラーまでを、ひととおり扱う。ここでもコードを書くのはCodexであり、人間が行うのは、日本語で依頼することと、出来上がったアプリを触って確かめることだけである。

これまでのように、「Codexへの依頼文」のプロンプト(マークダウン形式)を Codex に流れば、コンパイルから、テスト、取扱説明書、インストーラーを作成し、配布ファイルをまとめるまで一気通貫に処理してくれる。また、実際に私が作った配布ファイルもダウンロードできる。
その他の項目は、Windowsデスクトップアプリの仕組みや開発工程、配布や保守に関わる作業を学んでもらう目的で書いている。AI(Codex)を正しく動かすために参考にしていただきたいと思う。

目次

作るもの ― ファイル名一括変更ツール

デジタルカメラで撮った写真は、DSC_0182.JPG のような名前で保存される。これを「京都旅行001.jpg」「京都旅行002.jpg」と揃えたい、と考えたことのある人は多いはずである。1枚や2枚なら手作業でよいが、200枚あればそうはいかない。Windowsのエクスプローラーにも複数ファイルの名前をまとめて変える機能はあるが、付けられる名前の形が決まっており、しかも実行してみるまで結果が分からない
本項で作るツールは、この2点を解決する。名前の付け方を自分で指定でき、変更を実行する前に、変更前と変更後の名前が並んだ一覧を見て確かめられる。名前が重複する、Windowsで使えない文字が混ざっているといった問題も、実行する前に一覧の「状態」欄に表示される。
ファイル名一括変更ツールの画面。左にフォルダーツリー、中央にファイル一覧、右に変更前後の一覧が並ぶ
画面は3つの区画(ペインと呼ぶ)に分かれる。使う順番は左から右である。
  1. 左:フォルダーツリー‥‥パソコンの中のフォルダーを、枝分かれした形で表示する。ここでフォルダーを1つ選ぶ。
  2. 中央:ファイル一覧‥‥選んだフォルダーの中身を、名前・種類・サイズ・更新日時の順に並べる。名前を変えたいものをここで選ぶ。まとめて複数選んでもよい。
  3. 右:変更前後のファイル名‥‥選んだものについて、変更前の名前と、規則を当てはめた変更後の名前を並べて表示する。納得できたら「変更」ボタンを押す。
フォルダーを選び、ファイルを選び、変更後の名前を確かめてから実行する流れの図解
選ぶ、選ぶ、確かめる、実行する。左から右へ進む
ファイル名一括変更ツールの機能
区分機能内容
選ぶフォルダーツリードライブとフォルダーを枝分かれ表示する。前回終了時のフォルダーを覚えており、起動すると同じ場所を開く
選ぶ絞り込みa*.txt のような書き方で、表示するファイルを絞り込む
選ぶ複数選択一覧から複数のファイルまたはフォルダーをまとめて選ぶ
決める名前の変更変更後の名前を指定する。? を並べた個所が連番の数字に置き換わる
決める開始番号連番の始まりの数を指定する。上下の矢印ボタンで増減できる。最小値は 0
決める拡張子の変更変更後の拡張子を指定する。空欄なら元の拡張子のまま
確かめる変更後の一覧表示変更前と変更後の名前を並べて表示する。問題があれば「状態」欄に理由を表示する
実行する一括変更一覧のとおりに名前を変更する
実行する取消直前に行った1回分の変更を元に戻す
その他状態の記憶フォルダー、絞り込み、変更ルールを覚え、次回起動時に再現する
その他ヘルプ取扱説明書をWebブラウザーで開く。バージョン情報を表示する

まず仕様書を書く

2.1 プログラム仕様書をつくる」で述べたとおり、ある程度の大きさのプログラムを作るときは、コードを頼む前に仕様書を書く。これまでの小さな題材では、依頼文そのものが仕様書を兼ねていた。しかし本項のツールは、画面が3つに分かれ、変更ルールがあり、設定を保存し、インストーラーまで作る。ここまで来ると、依頼文を書き足すたびに前提が食い違い、Codexが以前の指示を打ち消す修正を加えることが起きる。

そこで、RenameKit.mdという1つのファイルに、決めたことをすべて書いておく。これが本ツールの仕様書である。Codexには「この仕様書を読んでから作業してほしい」と伝えればよい。仕様が変わったときは、まず仕様書を直し、それからコードの修正を依頼する。仕様書が正、コードが従という関係を崩さないことが、途中で行き詰まらないための最大のこつである。
仕様書に書いた項目を下表に示す。第5章の各項で作るツールは、いずれもこの形の仕様書を持つ。
仕様書(RenameKit.md)に書く項目
項目書く内容本ツールの場合
目的何のための道具か複数のファイル名を指定のルールで一括変更する
動作環境OS、実行に必要なものWindows 10以上の64ビット版、.NET 10 Desktop Runtime
技術要件使う技術、使わない技術C#、WPF。外部DLLを可能な限り使わない。外部と通信しない
画面構成区画の分け方と、各区画の中身左:フォルダーツリー、中央:ファイル一覧、右:変更前後の一覧
メニュー構成メニューの項目と動作ファイル、編集、ヘルプの3つ
例外・エラー処理異常時にどう振る舞うか処理エラーは画面に表示する。未処理の例外は詳細を表示して終了する
データ保存何を、どこに覚えるか前回のフォルダーと入力内容を、利用者ごとの保存場所に記録する
テスト方針どう確かめるかダミーデータによる自己テストを5項目行う
バージョン規則番号の付け方評価用の新規作成は 0.1.0、配布用の新規作成は 1.0.0
配布の手順実行ファイルとMSIの作り方dotnet publish の後、WiX 4で64ビットMSIを作る
著作権・使用条件ライセンスと問い合わせ先MIT License
仕様書には、バージョン番号も書いておく。本ツールは評価用の新規作成なので >>3 0.1.0 << から始め、インストーラーの日本語化などを加えた段階で >>3 0.2.0 << とした。番号の付け方は、大きな作り替えなら整数部分、機能の追加なら小数第1位、不具合の修正なら小数第2位を1増やす、と決めておく。番号は、プロジェクトファイル、画面の「バージョン情報」、プログラムの先頭コメント、インストーラーのすべてに現れる。ばらばらになると、利用者が「どれが最新か」を判断できなくなる。仕様書に1か所だけ書いておき、Codexに「すべてへ反映してほしい」と頼むのが確実である。

Codexへの依頼文

仕様書ができたら、Codexへ依頼する。5.2 はじめてのWPFアプリで示した6項目(使う技術とバージョン、作業する場所、ビルドと実行の指示、画面の構成、動作、制約)は、そのまま守る。加えて、本項の依頼文では次の3点を書いている。
  1. 仕様書のファイル名を示す‥‥「RenameKit.md を読んでから作業してほしい」と書く。画面や規則の細部を依頼文へ書き写す必要がなくなる。
  2. 画面の区画の分け方を、幅の数字まで示す‥‥「左は幅280、中央は残り全部、右は幅520」のように書く。曖昧に書くと、区画の境目が使いにくい位置に決められてしまう。
  3. 部品を大きく表示するよう指示する‥‥WPFの標準の文字は小さめである。「入力欄、ボタン、ラベルは大きく見やすく表示する」の1行で、全体の印象が変わる。
以上をまとめた最初の依頼文が次のものである。長く見えるが、書いてあることは仕様書の要約と、作業のしかたの指示だけである。
プロンプト:ファイル名一括変更ツールを作る
# 依頼
ファイル名を一括変更するWindowsデスクトップアプリを作ってほしい。
同じフォルダにある RenameKit.md が仕様書である。必ず読んでから作業すること。

# 開発環境 - .NET 10 - WPF(XAML) - C# - win-x64 - Visual Studioの画面は使わない。dotnetコマンドだけで作業する。
# プロジェクト・フォルダ作成 - RenameKit という名前のサブフォルダを作成し、以降の作業はサブフォルダで行う。 - すでにサブフォルダがあれば、新規に作らず、そのサブフォルダで作業を進める。 - 名前空間は RenameKit とする。
# 画面 - ウィンドウの題名は「ファイル名一括変更ツール」、幅1400、高さ780とする。 - ウィンドウは画面の中央に表示する。 - 入力欄、ボタン、ラベルは大きく見やすく表示する。 - 画面を横に3つの区画へ分ける。区画の境目はマウスで動かせるようにする。 - 左(幅280):フォルダーツリー。上に選択中のフォルダーをフルパスで表示する読み取り専用の入力欄を置く。 - 中央(残り全部):ファイル一覧。上に絞り込みの入力欄と「抽出」ボタンを置く。 - 右(幅520):変更前後のファイル名一覧。上に変更ルールの入力欄、下に「変更」「取消」ボタンを置く。 - 不可視属性のフォルダーとファイルは表示しない。
# 変更ルール - 「名前変更」欄に入力した文字を、変更後の名前とする。 - 名前の中の連続したクエスチョンマーク ? は連番の数字に置き換える。? 1つにつき数字1桁とする。 - 「開始番号」欄に連番の始まりの数を入力する。上下の矢印ボタンで増減でき、最小値は0とする。 - 「拡張子」欄に入力があれば、その拡張子へ変更する。空欄なら元の拡張子のままとする。 - 中央の一覧で選んだものについて、変更後の名前を右の一覧へ即座に表示する。
# 動作 - 「変更」ボタンで一括変更を実行する。 - 「取消」ボタンで直前の変更を元に戻す。取り消せるのは直前の1回だけとする。 - 名前が重複する、Windowsで使えない文字を含むなど、実行できない場合は右の一覧に理由を表示し、変更を実行しない。
# データ保存 - 前回選択したフォルダー、絞り込みの内容、変更ルールの内容を記憶し、次回起動時に再現する。 - 保存先は利用者ごとの領域とし、JSON形式で保存する。
# ビルドと実行 - dotnet build でビルドし、警告とエラーが無いことを確認する。 - エラーが出た場合は内容を読んで修正し、警告とエラーが無くなるまでくり返す。 - dotnet run で起動し、ウィンドウが表示されることを確認する。
# 制約 - 外部ライブラリ(NuGetパッケージ)は使わない。 - 外部と通信しない。 - ソースファイルはUTF-8で保存する。 - 初心者が読むので、要所に日本語のコメントを入れてほしい。 - 作成したファイルの一覧と、それぞれの役割を日本語で説明してほしい。
この依頼で、Codexは7つのファイルを作る。人間がこれらを書き写す必要は無い。どのファイルが何を担当しているかだけ、下表で把握しておけばよい。不具合を伝えるときに「右の一覧の様子がおかしい」と言えば、Codexが自分でどのファイルを直すかを判断する。
Codexが作ったファイルと役割
ファイル名種類役割
RenameKit.csproj設定使う.NETの版、アプリ名、バージョン、アイコンなど、プロジェクト全体の設定
App.xaml / App.xaml.csXAML / C#アプリの起動と終了。想定外のエラーを受け止めて画面に出す
MainWindow.xamlXAML画面の設計図。3つの区画と、その中の部品の配置
MainWindow.xaml.csC#画面の動き。フォルダーを開く、一覧を作る、ボタンが押されたときの処理
Models.csC#扱うデータの形。フォルダーの節、ファイル1件、変更前後の1組、設定
RenameEngine.csC#名前を組み立てる規則と、実際に名前を変える処理。画面とは切り離してある
SettingsStore.csC#設定をJSONファイルへ保存し、読み出す
SelfTest.csC#自分自身の動作を確かめる試験
注目してほしいのは、名前を変える処理(RenameEngine.cs)が、画面の処理(MainWindow.xaml.cs)と別のファイルに分けられている点である。こう分けておくと、画面を操作しなくても名前の組み立てだけを試せる。後の「自己テストで動作を確かめる」で行う試験は、この分離があるからこそ成り立つ。依頼文に「分けてほしい」とは書いていないが、Codexは一般的な作法としてこう分ける。もし1つのファイルに詰め込まれていたら、「名前を組み立てる処理を別のファイルに分けてほしい」と頼めばよい。

完成したツールのダウンロード

ダウンロードしたZIPファイルには、ツールの仕様書 RenameKit.md やインストーラー、取扱説明書、VisualStudioで手動でコンパイル・ビルドするときに必要になるリソース群などが格納されている。

GridとStackPanelで画面を組み立てる

WPFでは、部品の位置を「左から120ピクセル、上から80ピクセル」と直に指定することは、ふつう行わない。代わりにパネルと呼ばれる入れ物を用意し、その中へ部品を入れる。入れ物が、中身をどう並べるかを決める。ウィンドウの大きさを変えても崩れないのは、この仕組みによる(パネル(WPF))。
入れ物にはいくつか種類がある。本ツールで使うのは次の3つである。
  1. Grid(グリッド)‥‥方眼である。あらかじめ何行何列に区切るかを決め、部品に「何行目の何列目」と指定する。3つの区画に分けるのはこれで行う。
  2. StackPanel(スタックパネル)‥‥一列に積む入れ物である。縦か横かを指定すると、入れた順に並ぶ。「変更」「取消」の2つのボタンを横に並べるのはこれで行う。
  3. DockPanel(ドックパネル)‥‥縁に貼り付ける入れ物である。上・下・左・右のどこへ貼るかを指定し、最後に残った部分を1つの部品で埋める。メニューを上、状態表示を下、残りを本体、という配置はこれで行う。
Grid、StackPanel、DockPanelの3つの入れ物が部品を並べる形の違いを示す図解
3つの入れ物。方眼に置く、一列に積む、縁に貼る
MainWindow.xaml(画面の骨組み)
<Window x:Class="RenameKit.MainWindow"
        Title="ファイル名一括変更ツール"
        Width="1400" Height="780"
        MinWidth="1050" MinHeight="600"
        WindowStartupLocation="CenterScreen">
  <!-- 外側の入れ物。上下に貼り付け、残りを中身とします。 -->
  <DockPanel>
    <!-- 上端に貼り付けるメニュー -->
    <Menu DockPanel.Dock="Top" FontSize="15"> … </Menu>

<!-- 下端に貼り付ける状態表示 --> <StatusBar DockPanel.Dock="Bottom"> <TextBlock x:Name="StatusText" Text="準備完了" /> </StatusBar>
<!-- 残りの部分。5つの列に区切ります。 --> <Grid Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition Width="280" MinWidth="180" /> <!-- 左:ツリー --> <ColumnDefinition Width="6" /> <!-- 境目 --> <ColumnDefinition Width="*" MinWidth="350" /> <!-- 中央:一覧 --> <ColumnDefinition Width="6" /> <!-- 境目 --> <ColumnDefinition Width="520" MinWidth="380" /> <!-- 右:変更前後 --> </Grid.ColumnDefinitions>
<GroupBox Header="フォルダーツリー"> … </GroupBox> <GridSplitter Grid.Column="1" Width="6" HorizontalAlignment="Stretch" /> <GroupBox Grid.Column="2" Header="ファイル一覧"> … </GroupBox> <GridSplitter Grid.Column="3" Width="6" HorizontalAlignment="Stretch" /> <GroupBox Grid.Column="4" Header="変更前後のファイル名"> … </GroupBox> </Grid> </DockPanel> </Window>
読み方の要点は3つである。
  1. 列の幅の書き方‥‥ >>3 Width="280" << は280ピクセル固定、 >>3 Width="*" << は残り全部という意味である。左と右を固定にして中央を >>3 * << にすると、ウィンドウを広げたときに中央だけが広がる。 >>3 MinWidth << は、これ以上は狭くしないという下限である。
  2. Grid.Column の指定‥‥部品の側に「自分は何列目か」を書く。 >>3 Grid.Column="2" << は3列目という意味である(0から数える)。書かなければ0列目、つまりいちばん左になる。
  3. GridSplitter‥‥区画と区画のあいだに置く、幅6ピクセルの部品である。これを置くだけで、マウスで境目をつかんで動かせるようになる。区画の幅は人によって好みが違うので、入れておくとよい。
右の区画の中では、GridとStackPanelを組み合わせている。「名前変更」「開始番号」「拡張子」の3つの入力欄は、ラベルと入力欄が縦横に揃っていないと読みにくいのでGridで行と列に配置する。いっぽう「変更」「取消」の2つのボタンは、横に並べて右へ寄せるだけなのでStackPanelでよい。

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="変更" FontSize="17" Padding="24,8" Click="Rename_Click" />
<Button Content="取消" FontSize="17" Padding="24,8" Click="Undo_Click" IsEnabled="False" />
</StackPanel>
Orientation="Horizontal" << が横に積む指定、 >>3 HorizontalAlignment="Right" << が右寄せの指定である。 >>3 Padding="24,8" << は、文字の周囲に左右24ピクセル、上下8ピクセルの余白を取るという意味で、これがボタンの押しやすさを決める。 >>3 IsEnabled="False" << は、起動直後は「取消」ボタンを押せない状態にしておく指定である。まだ何も変更していない時点で押せてしまうと、利用者が迷う。

WPFの主なコントロール

入れ物の中に置く部品をコントロールと呼ぶ。WPFには数十種類が用意されているが、道具を作るうえで使うものは限られる。本ツールで実際に使っているコントロールを下表にまとめる。名前と役割の対応が分かれば、Codexへ「ここをComboBoxにしてほしい」と具体的に頼めるようになる。
本ツールで使ったWPFのコントロール
コントロール読み役割本ツールでの使い所
TextBlockテキストブロック文字を表示する(入力はできない)「名前変更:」などのラベル、画面下の状態表示
TextBoxテキストボックス文字を入力する絞り込み、名前変更、開始番号、拡張子の各入力欄
Buttonボタン押すと処理を行う抽出、変更、取消、開始番号の上下
TreeViewツリービュー枝分かれした階層を表示する左のフォルダーツリー
ListViewリストビュー項目を一覧表示する。列に分けて表にもできる中央のファイル一覧、右の変更前後の一覧
GridViewグリッドビューListViewを表形式で表示する指定名前・種類・サイズ・更新日時の4列
GroupBoxグループボックス枠と見出しで、まとまりを示す3つの区画それぞれの枠
Menu / MenuItemメニュー画面上端のメニューファイル、編集、ヘルプ
StatusBarステータスバー画面下端に状態を表示する「12 項目」「3 項目を変更しました。」など
GridSplitterグリッドスプリッター区画の境目をマウスで動かせるようにする左と中央、中央と右のあいだ
ComboBoxコンボボックスいくつかの選択肢から1つ選ぶ本ツールでは未使用。選択肢を増やすときに使う
CheckBoxチェックボックス入/切を切り替える本ツールでは未使用。「サブフォルダーも対象にする」などに使える
表の下2つは本ツールで使っていないが、覚えておくと役に立つ。たとえば「サブフォルダーの中も対象にしたい」と思ったら、「区画の上にチェックボックスを1つ追加し、入のときはサブフォルダーの中も一覧に含めてほしい」と依頼すればよい。部品の名前を知っていると、依頼が具体的になる

データバインディング ― 画面と値をつなぐ

5.2で作ったアプリでは、画面の文字を変えるとき、C#から >>3 DateTimeTextBlock.Text = "…"; << のように部品を名指しして代入していた。部品が数個ならこれでよい。しかし本ツールでは、フォルダーの中身が数百件になることもある。1件ずつ部品を作って代入していたら、コードは長くなり、表示も遅くなる。
そこでWPFにはデータバインディング(データの結び付け)という仕組みがある。「この一覧の中身は、この入れ物の中身と同じにせよ」と一度結び付けておくと、以後は入れ物の中身を書き替えるだけで画面が変わる。画面を操作するコードを書かなくてよくなる(データ バインディングの概要)。
プログラムの中の入れ物と画面の一覧が結び付けられ、入れ物を書き替えると画面が変わる様子の図解
入れ物と画面を結び付けておくと、入れ物を書き替えるだけで画面が変わる

変更ルールと連番の仕組み

本ツールの中心は、変更後の名前をどう組み立てるかという規則である。入力するのは「名前変更」「開始番号」「拡張子」の3つだけで、決まりは次の3行に尽きる。
  1. 名前変更の欄が空なら、元の名前をそのまま使う
  2. 名前変更の欄に文字があれば、それを新しい名前とする。ただし、連続したクエスチョンマーク ? は連番の数字に置き換わる。 ? 1つにつき数字1桁である。
  3. 拡張子の欄が空なら元の拡張子のまま、文字があればその拡張子に変える。フォルダーには拡張子を付けない。
実際にどうなるかを下表に示す。開始番号を1として、3つのファイルを選んだ場合である。
変更ルールの指定と結果(開始番号は1)
名前変更拡張子変更前変更後説明
(空欄)(空欄)DSC_0182.JPGDSC_0182.JPG何も変わらない。状態欄に「変更なし」と出る
(空欄)jpgDSC_0182.JPGDSC_0182.jpg拡張子だけを変える
京都旅行(空欄)DSC_0182.JPG京都旅行.JPGすべて同じ名前になるため、2件目以降は重複となり実行できない
京都旅行???(空欄)DSC_0182.JPG
DSC_0183.JPG
DSC_0184.JPG
京都旅行001.JPG
京都旅行002.JPG
京都旅行003.JPG
? が3つなので3桁の連番になる
京都旅行??jpgDSC_0182.JPG京都旅行01.jpg連番と拡張子の変更を同時に行う
資料_?(空欄)a.txt資料_1.txt? が1つなので桁を揃えない
2026年度(空欄)旧資料(フォルダー)2026年度フォルダーには拡張子を付けない
この規則を担っているのが、RenameEngine.cs の中の >>3 BuildName << という短い手続きである。全文は次のとおりで、10行ほどしかない
RenameEngine.cs(変更後の名前を組み立てる)
public static string BuildName(string oldName, bool isDirectory,
                               string pattern, int number, string extension)
{
    // 拡張子を除いた部分(フォルダーの場合は名前そのもの)
    string stem = isDirectory ? oldName : Path.GetFileNameWithoutExtension(oldName);
    // 元の拡張子(先頭のピリオドは取り除く)
    string oldExtension = isDirectory ? "" : Path.GetExtension(oldName).TrimStart('.');

// 名前変更の指定があれば、連続した ? を連番に置き換える if (!string.IsNullOrWhiteSpace(pattern)) stem = Regex.Replace(pattern, @"\?+", m => number.ToString($"D{m.Length}"));
// 拡張子の指定があればそちらを、無ければ元の拡張子を使う string ext = isDirectory ? "" : (string.IsNullOrWhiteSpace(extension) ? oldExtension : extension.Trim().TrimStart('.'));
return ext.Length == 0 ? stem : $"{stem}.{ext}"; }
中心は真ん中の1行である。 >>3 Regex.Replace << は正規表現という道具で、文字の並びの中から条件に合う部分を探して置き換える。 >>3 \?+ << が「クエスチョンマークが1個以上続いている部分」を表し、見つかるたびに >>3 number.ToString($"D{m.Length}") << の結果へ置き換える。 >>3 D3 << は「3桁で、足りない分は0で埋める」という書式である。 >>3 m.Length << が見つかった ? の個数なので、? を3つ書けば3桁、2つなら2桁になる、という動きがこの1行で実現している。
正規表現の書き方を覚える必要は無い。「クエスチョンマークが並んだところを、桁数を揃えた連番に置き換えてほしい」と日本語で頼めば、Codexがこの表現を選ぶ。
クエスチョンマークの個数が連番の桁数に対応することを示す図解
クエスチョンマークの個数が、そのまま連番の桁数になる

安全に名前を変える工夫

ファイル名の一括変更には、取り返しがつかないという性質がある。しかも、素直に書くと途中で失敗する場面がある。次の例を考えてほしい。
  1. a.txt を b.txt に変える
  2. b.txt を a.txt に変える
名前を入れ替えたいだけだが、上から順に実行すると、1番目の時点で「b.txt はすでに存在する」となって失敗する。連番を振り直すときにも同じことが起きる。1.jpg を 2.jpg に、2.jpg を 3.jpg に、と変えていくと、必ずどこかでぶつかる。

本ツールは、これを2段階で解決している。まず全部を誰とも重ならない一時的な名前に変え、そのうえで一時的な名前から目的の名前へ変える。途中で失敗したら、一時的な名前から元の名前へ戻す。
いったん一時的な名前に変えてから目的の名前に変える2段階の改名を示す図解
いったん全部を一時的な名前にしてから、目的の名前に変える
RenameEngine.cs(2段階で名前を変える)
public static IReadOnlyList<RenamePair> Execute(IEnumerable<RenamePair> requested)
{
    // 名前が変わらないものは対象から外す
    var pairs = requested.Where(p => !StringComparer.OrdinalIgnoreCase
                                        .Equals(p.OldPath, p.NewPath)).ToList();
    Validate(pairs);   // 実行前の検査(重複、使えない文字、予約名)

var staged = new List<(string Temporary, string Target)>(); try { // 第1段階:すべてを一時的な名前に変える foreach (var pair in pairs) { string temporary = Path.Combine(Path.GetDirectoryName(pair.OldPath)!, $".RenameKit-{Guid.NewGuid():N}.tmp"); Move(pair.OldPath, temporary); staged.Add((temporary, pair.NewPath)); } // 第2段階:一時的な名前から目的の名前に変える foreach (var item in staged) Move(item.Temporary, item.Target); return pairs; } catch { // 失敗したら、後ろから順に元の名前へ戻す for (int i = staged.Count - 1; i >= 0; i--) { string current = Exists(staged[i].Target) ? staged[i].Target : staged[i].Temporary; try { if (Exists(current)) Move(current, pairs[i].OldPath); } catch { } } throw; } }
一時的な名前には >>3 Guid.NewGuid() << が使われている。GUID(グローバル一意識別子)は、世界中で重複しないことを狙って作られる長い識別子である。これをファイル名に使えば、たまたま同じ名前のファイルがあった、という事故を避けられる。
実行の前には >>3 Validate << という検査が入る。下表の3点を調べ、1つでも当てはまれば1件も変更せずに中止する。半分だけ変わってしまう、という最も困る事態を避けるためである。
実行前に行う検査
検査内容画面での見え方
名前として使えるか空欄でないか。 \ / : * ? " < > | を含まないか。末尾が空白やピリオドでないか状態欄に「使用できない文字を含みます」などと表示する
予約名でないかCON、PRN、AUX、NUL、COM1〜COM9、LPT1〜LPT9 はWindowsが特別扱いするため使えない状態欄に「Windowsの予約名です」と表示する
ぶつからないか変更後の名前どうしが重複していないか。変更後の名前と同じものが、すでにフォルダーに無いか実行時に「変更後の名前が重複しています」と表示し、中止する
「取消」ボタンは、直前の変更の変更前と変更後を入れ替えて、もう一度実行するという作りである。同じ Execute を、組を逆にして呼ぶだけでよい。取り消せるのは直前の1回だけとした。何回でも戻せるようにすることもできるが、そのぶん覚えておく情報が増え、途中で利用者が手作業で名前を変えていた場合に食い違いが起きる。できることを増やしすぎないのも、道具の設計である。

前回の状態を覚えさせる

道具として毎日使うものは、前回の続きから始められることが大切である。起動するたびにCドライブの先頭から目的のフォルダーまでたどり直すのでは、使う気が失せる。本ツールは、終了時に次の5つを覚え、次の起動時に再現する。
  1. 最後に選んでいたフォルダー
  2. 絞り込みの入力内容
  3. 名前変更の入力内容
  4. 開始番号
  5. 拡張子の入力内容


保存先は、利用者ごとの領域である。Windowsでは、この用途の置き場所が決まっている。
C:\Users\(利用者名)\AppData\Local\pahoo.org\RenameKit\settings.json
アプリを入れた場所(Program Files の下)に書かないのが要点である。そこは管理者しか書き込めないため、設定の保存に失敗する。また、パソコンを複数人で使う場合、利用者ごとに別の設定を持てるほうがよい。 >>3 AppData\Local << はこの利用者だけの、このパソコン限りの置き場所という意味を持つ。

保存の形式はJSON(ジェイソン)である。「2.6 クラウドAPIを利用する」でも登場した、値に名前を付けて並べる書き方で、人が読める。実際のファイルの中身は次のようになる。
{
"LastFolder": "D:\\photo\\2026kyoto",
"Filter": "*.JPG",
"NamePattern": "京都旅行???",
"StartNumber": "1",
"Extension": "jpg"
}
これを読み書きするコードは、全部で十数行である。
public static AppSettings Load()
{
try { return File.Exists(PathName)
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(PathName)) ?? new()
: new(); }
catch { return new(); }
}
注目すべきは >>3 catch { return new(); } << の1行である。設定ファイルが壊れていたり、手作業で編集して形式が崩れていたりした場合、何もなかったことにして初期状態で起動する。設定ファイルが読めないという理由でアプリが立ち上がらないのは、利用者にとって最悪の振る舞いである。無くても困らないものは、読めなくても止まらない。これは道具を作るときの原則の1つである。

うまく動かないときの調べ方

ここまで順調に進んだように書いてきたが、実際には思ったとおりに動かないことがある。本講座ではVisual Studioの画面を開かない方針を採っているので、画面を止めて変数の中身を見る、といった調べ方はしない。代わりに使うのがログである。
ログとは、プログラムが何をしたかを、時刻とともにファイルへ書き残したものである。人間が見ていない間に起きたことも残るので、「たまに失敗する」「特定のフォルダーだけおかしい」といった、その場で再現しにくい不具合に強い。

ログの出力は、あとから追加できる。次の依頼文をCodexへ渡せばよい。
プロンプト:ログのファイル出力を追加する
# 依頼
RenameKit に、動作の記録(ログ)をファイルへ書き出す機能を追加してほしい。

# 保存先 - %LOCALAPPDATA%\pahoo.org\RenameKit\log\ に保存する。 - ファイル名は RenameKit_yyyyMMdd.log とし、日付ごとに分ける。 - 30日より古いログファイルは、起動時に自動で削除する。
# 記録する内容 - 1行につき「日時 種別 内容」の順に、タブ区切りで書く。 - 日時は yyyy-MM-dd HH:mm:ss.fff 形式とする。 - 種別は INFO(通常)、WARN(注意)、ERROR(異常)の3種類とする。 - 次のできごとを記録する。 - アプリの起動と終了(バージョン番号も記録する) - フォルダーを開いたとき(フォルダーのフルパスと、見つかった件数) - 名前の変更を実行したとき(対象の件数、変更前と変更後の名前) - 変更を取り消したとき(件数) - エラーが起きたとき(種別 ERROR で、エラーの内容と発生個所)
# 条件 - 外部ライブラリ(NuGetパッケージ)は使わない。 - ログの書き込みに失敗しても、アプリの動作は止めない。 - ファイル名やフォルダー名以外の個人情報は記録しない。 - 追加後、dotnet build でビルドし、dotnet run で起動して、ログファイルが作られることを確認してほしい。
この依頼で作られるログは、次のような内容になる。
2026-08-29 11:02:15.318 INFO 起動 RenameKit 0.2.0
2026-08-29 11:02:15.902 INFO フォルダーを開く D:\photo\2026kyoto (212 項目)
2026-08-29 11:03:41.115 INFO 変更 3 件 DSC_0182.JPG → 京都旅行001.JPG ほか
2026-08-29 11:04:07.660 ERROR 名前を変更できません 別のプログラムが使用中です
ログの読み方は簡単である。時刻の新しいものから見て、ERROR と書かれた行を探す。見つかったら、その直前の行を見る。エラーが起きるすぐ前に何をしていたかが、原因を知る最大の手がかりになる。上の例では、名前を変更しようとしたが別のプログラムがそのファイルを開いていた、と読める。

そして、原因を人間が突き止める必要は無い。ログの該当部分をCodexへ貼り付け、「こう表示された」と伝えればよい。第4章までは人間がエラーメッセージを読んで貼り付けていたが、第5章ではCodexがログファイルそのものを読める。「ログを見て原因を調べてほしい」と頼むだけでもよい。
不具合をCodexへ伝えるときの言い方を下表にまとめる。左の列が、人間が言えば十分な内容である。

なお、Visual Studioにはブレークポイントという、プログラムを指定の行で一時停止させて変数の中身を見る機能がある。プログラムを書く人にとっては強力な道具だが、使うにはVisual Studioの画面を開き、コードの行を選び、停止した状態で変数を追う、という操作が必要になる。本講座はコードを書かない人がツールを手に入れることを目的としているため、その役割はログとCodexに任せる。ログのほうが、配布したあとに利用者の環境で起きた不具合にも使える、という利点もある。
不具合の伝え方と、Codexが行うこと
人間が伝えることできれば添えるとよい情報Codexが行うこと
変更ボタンを押しても何も起きないそのときの一覧の状態欄に何が出ていたか状態欄の判定条件を見直す。ログの該当時刻の行を確認する
連番が思った桁数にならない入力した名前変更の文字列連番に置き換える処理を確認し、必要なら直す
起動しても前回のフォルダーが開かない前回どのフォルダーを開いていたか設定ファイルの中身とツリー復元の処理を確認する
ときどき「変更できません」と出るログのERROR行と、その直前の数行他のプログラムがファイルを使用中である可能性を調べ、待って再試行する処理を検討する
ファイルが多いと画面が固まるおおよその件数時間のかかる処理を別に動かす作りへ変更する(5.4で扱う)
アプリが起動直後に消えるログの最後の数行起動時の処理でエラーが起きていないかを確認する

自己テストで動作を確かめる

画面を触って確かめるのは大切だが、それだけでは足りない。名前の入れ替え、重複の検出といった場面は、毎回手作業で試すのが面倒である。そこで本ツールには、自分で自分を試験する仕組みを入れてある。コマンドの後ろに >>3 --self-test << と付けて起動すると、画面を出さずに5つの試験を行い、結果を表示して終了する。
RenameKit.exe --self-test
実行すると、次のように表示される。
PASS 1/5: Sequence
PASS 2/5: Extension
PASS 3/5: Folder
PASS 4/5: RenameUndo
PASS 5/5: Collision
SELF-TEST PASSED (5/5)


5つの試験の内容は下表のとおりである。仕様書に「ダミーデータによる試験を5回行う」と書いておくと、Codexがこの形の試験を用意する。
自己テストの5項目
名前確かめること合格の条件
Sequence連番への置き換えa.jpg を「photo???」開始番号7で変えると photo007.jpg になる
Extension拡張子だけの変更a.jpg の拡張子を png に変えると a.png になる
Folderフォルダーの扱いフォルダーに拡張子 txt を指定しても、拡張子は付かない
RenameUndo変更と取消一時フォルダーで a.txt を b.txt に変え、取り消すと a.txt に戻る
Collision重複の検出すでに b.txt がある状態で a.txt を b.txt に変えようとすると、実行されずエラーになる
4番目と5番目は、一時フォルダーに本物のファイルを作って試している。試験が終われば、作ったファイルもフォルダーも消す。利用者のファイルには一切触れない。
この試験の値打ちは、改良したあとに、壊れていないことを確かめられる点にある。「サブフォルダーも対象にしてほしい」と依頼して改良したあと、もう一度 >>3 --self-test << を実行し、5項目すべてが PASS であれば、少なくとも基本の動作は壊れていない。Codexへは「変更のたびに、--self-test を実行して結果を示してほしい」と伝えておくとよい。人間は、示された結果を見て合格かどうかを判断するだけである。

dotnet publishで実行ファイルを作る

ここまでは >>3 dotnet run << で動かしてきた。これは開発中の起動方法であり、プロジェクトのフォルダーごと相手に渡さないと動かない。人に配るには、必要なファイルだけを1つのフォルダーにまとめる作業が要る。これを発行(publish)と呼び、次のコマンドで行う(dotnet publish コマンド)。
dotnet publish -c Release -r win-x64 --self-contained false -o publish
コマンドの意味を下表に示す。
dotnet publish の指定
指定意味省くとどうなるか
-c Release配布用に、速度を優先して作る開発用(Debug)のまま作られ、動作が遅く、余分な情報を含む
-r win-x64Windowsの64ビット版で動く形にする実行環境が決まらず、単独のexeが作られないことがある
--self-contained false.NET本体は同梱せず、パソコンに入っているものを使うtrue にすると.NET本体を丸ごと同梱し、配布物が70MB以上になる
-o publishpublish という名前のフォルダーへ出力する深い階層の既定のフォルダーへ出力され、探すのに手間がかかる
--self-contained false << は、判断が分かれるところである。.NET 10 デスクトップランタイムが入っていないパソコンでは動かないが、そのぶん配布物が小さくなる。本ツールは、動作環境の欄に「.NET 10 Desktop Runtime が必要」と明記し、入っていない場合はWindows標準のエラー画面が導入を促す作りとした。社内で配るなど相手の環境が分かっている場合は、この選択でよい。不特定多数へ配るなら >>3 true << にして同梱するほうが親切である。

発行すると、publish フォルダーに次のファイルができる。
このフォルダーをそのままZIPにして渡しても、相手は使える。しかし、どこに置けばよいかショートカットをどう作るか要らなくなったらどう消すかを、相手が自分で考えなければならない。次の項で作るインストーラーは、この3つを肩代わりするものである。
発行で作られる主なファイル
ファイル内容
RenameKit.exe起動するためのファイル。これをダブルクリックするとアプリが立ち上がる
RenameKit.dllプログラムの本体。実際の処理はこちらに入っている
RenameKit.deps.json必要な部品の一覧
RenameKit.runtimeconfig.jsonどの版の.NETで動かすかの指定
LICENSE.txt使用条件を記した文書
help フォルダー取扱説明書のHTMLとスタイルシート
ソースコードから発行を経てMSIインストーラーができるまでの流れの図解
ソースコード、発行したフォルダー、インストーラーの3段階

WiX Toolset v4でMSIインストーラーを作る

Windowsで「インストーラー」といえば、ふつうMSIファイル(拡張子 .msi)を指す。MSIは、Windows自身が持つインストールの仕組みに従うファイル形式である。MSIで配ると、インストール済みのアプリの一覧に載り、そこからアンインストールできる。バージョンを上げたときは、古いものを自動で置き換えられる。
MSIを作る道具はいくつかあるが、本講座ではWiX Toolset v4(ウィックス・ツールセット)を使う。無償で、コマンドだけで完結し、設定をXMLの文書で書ける。設定を文書で書けるということは、Codexが書けるということである(WiX Documentation)。

WiXは.NETの道具として配布されているので、導入は1行である。
dotnet tool install --global wix
続いて、何をどこへ入れるかを書いた >>3 installer.wxs << という文書を用意する。Codexが書いたものから、要点を抜き出したのが次のコードである。
installer.wxs(インストーラーの設計図・抜粋)
<Package Name="ファイル名一括変更ツール"
         Manufacturer="studio pahoo"
         Version="0.2.0"
         UpgradeCode="B297165D-C951-43D6-B6AF-9930DC361B69"
         Scope="perMachine" Language="1041" Codepage="932">

<!-- 古い版が入っていたら置き換えます --> <MajorUpgrade DowngradeErrorMessage="新しいバージョンが既にインストールされています。" />
<!-- インストール先を選べる画面を使います --> <ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" /> <WixVariable Id="WixUIDialogBmp" Value="resourceWiX\dialog.bmp" /> <WixVariable Id="WixUIBannerBmp" Value="resourceWiX\banner.bmp" /> <WixVariable Id="WixUILicenseRtf" Value="resourceWiX\license.rtf" />
<!-- 入れる場所:C:\Program Files\pahoo.org\RenameKit --> <StandardDirectory Id="ProgramFiles64Folder"> <Directory Id="PahooFolder" Name="pahoo.org"> <Directory Id="INSTALLFOLDER" Name="RenameKit"> <Directory Id="HelpFolder" Name="help" /> </Directory> </Directory> </StandardDirectory>
<!-- 入れるファイル --> <ComponentGroup Id="ApplicationFiles" Directory="INSTALLFOLDER"> <Component Id="MainExecutable" Guid="*"> <File Id="RenameKitExe" Source="publish\RenameKit.exe" KeyPath="yes" /> </Component> … </ComponentGroup>
<!-- デスクトップとスタートメニューのショートカット --> <Component Id="Shortcuts" Directory="ProgramMenuPahoo" Guid="*"> <Shortcut Id="StartMenuShortcut" Name="ファイル名一括変更ツール" Target="[#RenameKitExe]" Icon="RenameKitIcon" /> <Shortcut Id="DesktopShortcut" Directory="DesktopFolder" Name="ファイル名一括変更ツール" Target="[#RenameKitExe]" Icon="RenameKitIcon" /> <RemoveFolder Id="RemoveProgramMenuPahoo" Directory="ProgramMenuPahoo" On="uninstall" /> </Component> </Package>
初めて見る語が多いが、押さえるのは4か所だけである。
  1. UpgradeCode‥‥このアプリを一意に表す番号である。版を上げても、この番号は変えてはならない。同じ番号を持つ古い版を見つけて置き換える、という判断に使われるからである。逆に、別のアプリを作るときは必ず別の番号にする。
  2. Scope="perMachine"‥‥パソコン全体に入れる、という指定である。Program Files の下に入り、その利用者だけでなく全員が使える。インストール時に管理者の確認画面が出る。
  3. ProgramFiles64Folder‥‥64ビット版の Program Files を指す。ここを間違えると >>3 Program Files (x86) << という32ビット用のフォルダーへ入ってしまう。仕様書にも「(x86) ではない」と明記してある。
  4. Language="1041" Codepage="932"‥‥日本語の指定である。1041が日本語、932が日本語の文字コードを表す番号である。


MSIを作るコマンドは次の1行である。 >>3 -culture ja-JP << を付けると、インストーラーの画面に出る「次へ」「キャンセル」といった文言が日本語になる。付け忘れると英語のままになるので、忘れやすい指定である。
wix build installer.wxs -ext WixToolset.UI.wixext -culture ja-JP -arch x64 -o RenameKit_0.2.0.msi
作成したMSIインストーラーの最初の画面
インストーラーの見た目は、3つの画像・文書を差し替えるだけで整う。dialog.bmpが最初と最後の画面の背景(493×312ピクセル)、banner.bmpが途中の画面の上端の帯(493×58ピクセル)、license.rtfが使用条件の文書である。いずれも仕様書に大きさを書いておき、Codexに位置を指定させる。
これで、RenameKit_0.2.0.msiという1つのファイルができる。相手に渡すのはこれだけでよい。受け取った人は、ダブルクリックして案内に従うだけで、デスクトップとスタートメニューにショートカットが作られ、要らなくなればWindowsの「インストールされているアプリ」から消せる。

取扱説明書とヘルプメニュー

自分だけで使う道具なら説明書は要らない。しかし人に渡すなら、使い方をどこかに書いておく必要がある。本ツールでは、HTMLで書いた取扱説明書をMSIに同梱し、インストール先の help フォルダーへ一緒に入れる。メニューの「ヘルプ」を選ぶと、そのHTMLが既定のWebブラウザーで開く。
private void Help_Click(object sender, RoutedEventArgs e)
=> Open(Path.Combine(AppContext.BaseDirectory, "help", "index.html"));
AppContext.BaseDirectory << は、実行中のexeが置かれているフォルダーを表す。インストール先が C:\Program Files\pahoo.org\RenameKit であっても、別の場所に入れられても、そこからの相対で説明書を探すので迷わない。インターネットに置かないのも要点である。手元にあるので、通信できない環境でも読める。

取扱説明書に載せる項目は、仕様書に決めてある。Codexへは「この構成でHTMLの説明書を作ってほしい」と依頼するだけでよい。
取扱説明書に載せる項目
項目内容
目次各項目への見出しリンク
基本的な使い方画面スナップショットに操作手順の番号を書き入れ、順を追って説明する
変更ルール連番、開始番号、拡張子の指定と、その結果の例
抽出ワイルドカードによる絞り込みの書き方
取消取り消せる範囲(直前の1回のみ)
メニューファイル、編集、ヘルプの各項目
トラブル対応変更できないときに考えられる原因
使用条件MIT License の内容
更新履歴版ごとの変更点
お問い合わせ連絡先
アプリに同梱した取扱説明書をWebブラウザーで開いた画面
同梱した取扱説明書。ヘルプメニューから開く

よくあるつまずきと対処

本項の作業でつまずきやすい個所を下表にまとめる。「症状」の欄は、いずれも人間が見て分かることだけを書いてある。原因の欄は参考であり、人間が調べる必要は無い。対処の欄のとおりCodexへ伝えればよい。
ファイル名一括変更ツールでよくあるつまずき
症状原因対処
ウィンドウは出るが、左のツリーに何も出ないドライブの読み取りで例外が起きている、または不可視の判定が広すぎる「左のツリーが空である」とCodexへ伝える
フォルダーを選んでも中央に何も出ない絞り込みの入力欄に前回の内容が残っている絞り込み欄を * に戻して「抽出」を押す。それでも出なければCodexへ伝える
中央でファイルを選んでも右に何も出ない選択の変化と一覧の作り直しがつながっていない「中央で選んでも右の一覧が変わらない」とCodexへ伝える
変更ボタンを押しても何も起きない状態欄に問題が表示されており、実行が止められている右の一覧の状態欄を読む。「使用できない文字」「重複」の表示があれば、名前の指定を直す
「同名の項目が既にあります」と出る変更後の名前が、フォルダーにすでにあるものと同じである連番の開始番号を変える、または名前の文字を変える
「別のプログラムが使用中です」と出る対象のファイルを、他のアプリが開いたままにしているそのファイルを開いているアプリを閉じてから、もう一度実行する
連番が001から始まらない開始番号の欄が前回の値のままである開始番号を1に直す。上下の矢印ボタンでも変えられる
フォルダー名に拡張子が付いてしまうフォルダーとファイルを区別していない「フォルダーには拡張子を付けないでほしい」とCodexへ伝える
ファイルが多いと画面が固まる時間のかかる処理を、画面と同じ流れで行っている「1000件のフォルダーを開くと固まる」とCodexへ伝える。本格的な対処は5.4で扱う
publishしたexeが起動しない.NET 10 デスクトップランタイムが入っていない表示された案内に従って導入する。または --self-contained true で作り直す
MSIの画面が英語で出るwix build に -culture ja-JP を付け忘れている「インストーラーの画面が英語になっている」とCodexへ伝える
Program Files (x86) に入ってしまう32ビット用の指定になっている「64ビットのMSIとして作り直してほしい」と依頼する
新しい版を入れても古い版が残るUpgradeCodeが前の版と違う「UpgradeCodeを変えずに、古い版を置き換える設定にしてほしい」と依頼する
本項では、画面の組み立て、データバインディング、変更ルール、安全な実行、設定の保存、ログ、自己テスト、発行、MSIの作成までを1本のツールで通した。ここまでで、道具を作って人に渡すまでの一周ができたことになる。次の「5.4 重複ファイル検出ツール」では、時間のかかる処理を扱う。数万件のファイルを調べるあいだ画面を固まらせない方法、進み具合の表示、途中で止める仕組みを学ぶ。本項の最後の表に出てきた「ファイルが多いと画面が固まる」への、本格的な答えである。

参考サイト

(この項おわり)
header