バッチ実行中にファイル選択やフォルダ選択を行うGUI

# はじめに
これは、[Visual Basic Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vb)の19日目の記事となります。
先日、Twitterを眺めていたら、下記サイトの紹介ツイートを見つけました。

https://ar-ray.hatenablog.com/entry/2021/12/15/214447

Windowsならバッチで、ファイル参照やフォルダ参照が出来たら便利かも知れないと、早速ググりました。

https://stackoverflow.com/questions/15885132/file-folder-chooser-dialog-from-a-windows-batch-script

PowerShellやC#やWSH(JScript)を使用したものが見られます。
この記事は、Visual Basic Advent Calendarですから、WSH(VBScript)で作り直すことにしました。

【2025/03/30追記】
PowerShell版を作成しました。

https://qiita.com/yaju/items/98ec88701e47298ec6ef

# ファイル選択
下記サイトを参考にWSH(JScript)から、WSH(VBScript)に書き換えました。

http://scripting.cocolog-nifty.com/blog/2009/05/post-25ec.html

## アップロード向けファイル選択
HTMLアプリケーション用のファイル選択を利用しているので、ネットに関連したフィルターが用意されています。

|項目|内容|
|:--|:--|
|初期値|指定は出来ません。初期はデスクトップですが別フォルダを選択して閉じた場合、次から同じ親プロセス上なら別フォルダが指定されます。|
|拡張子フィルター|すべてのファイル(*.*)、画像(*.gif,*.jpg,*.jpeg,*.png)、HTML(*.htm;*.html)|
|タイトル|アップロードするファイルの選択|

```bat:fileRef.bat
@echo off
set dialog="about:<input type=file id=FILE><script language=vbscript>FILE.Click:
set dialog=%dialog%CreateObject("Scripting.FileSystemObject").GetStandardStream(1).WriteLine(FILE.value):
set dialog=%dialog%Close:resizeTo 0,0</script>"

set file=
for /f "tokens=* delims=" %%p in ('mshta.exe %dialog%') do set "file=%%p"
echo selected  file is : "%file%"
pause
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/e1004b31-99e4-f096-f0df-39f78f8e14f9.png)

```bat:結果
c:\Temp>fileRef.bat
selected  file is : "C:\Temp\江戸時代の円周率.txt"
続行するには何かキーを押してください . . .
```

## 汎用向けファイル選択
初期フォルダ、フィルタ、タイトルを指定することが出来ます。
拡張子フィルターで指定するファイルが２種類以上の場合、パイプ(|)を使用します。
参照:[【C#】オープンファイルダイアログ-ファイル指定(フィルター)](http://movie-memo.bookmarks.jp/blog/2017/12/07/%E3%80%90c%E3%80%91%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%8C%87%E5%AE%9A/)

|項目|内容|
|:--|:--|
|初期値|set "File=(初期値を指定)"|
|拡張子フィルター|set "Filter=(フィルターを指定)"|
|タイトル|set "Title=（タイトル名を指定)"|

```bat:fileRef2.bat
@echo off
set "File=C:\Temp\*.txt"
set "Filter=テキスト (*.txt)|*.txt|すべてのファイル (*.*)|*.*"
set "Title=ファイルの選択"

set dialog="about:<object id=HtmlDlgHelper classid=CLSID:3050f4e1-98b5-11cf-bb82-00aa00bdce0b></object>
set dialog=%dialog%<script language=vbscript>resizeTo 0,0:Sub window_onload():
set dialog=%dialog%Set reg=CreateObject("VBScript.RegExp"):reg.Pattern="\0.*":
set dialog=%dialog%Set Env=CreateObject("WScript.Shell").Environment("Process"):
set dialog=%dialog%ret=HtmlDlgHelper.object.openfiledlg(Env("File"),,Env("Filter"),Env("Title")):
set dialog=%dialog%CreateObject("Scripting.FileSystemObject").GetStandardStream(1).Write reg.Replace(ret,""):
set dialog=%dialog%Close:End Sub</script><hta:application caption=no showintaskbar=no />"

set file=
for /f "delims=" %%p in ('MSHTA.EXE %dialog%') do set "file=%%p"
echo selected  file is : "%file%"
pause
```
【2025/03/30追記】
~~注意点として、初回は拡張子フィルターに`テキスト (*.txt)`を指定しているが、全ての拡張子が表示されてしまう。
一旦、拡張子フィルターを`すべてのファイル (*.*)`にしてから`テキスト (*.txt)`を指定すると指定した拡張子のみが表示されるようになる。
次からは同じ親プロセス上なら、`テキスト (*.txt)`が指定された状態になっている。~~

原因が判明しました。最後のパイプ記号(|)が不要でした。
```
set "Filter=テキスト (*.txt)|*.txt|すべてのファイル (*.*)|*.*|"
                                                        ↓　
set "Filter=テキスト (*.txt)|*.txt|すべてのファイル (*.*)|*.*"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/7038d22e-d3cf-fb19-3edd-0a2e8c12fa1c.png)

```bat:結果
c:\Temp>fileRef.bat
selected  file is : "C:\Temp\江戸時代の円周率.txt"
続行するには何かキーを押してください . . .
```

### 苦労した点
最初は参考サイトではなく別の方法で検証していたのですが、バッチファイルではパイプ記号(|)のエスケープ処理が難しいかったです。ハット（^)記号をパイプ記号の前に付けてもエラーになり動いてくれない。

参考サイトにて下記のように引数にして渡せば、パイプ記号(|)のエスケープ処理なしで動作できることが分かって、採用しました。

```vb
set "Filter=テキスト (*.txt)|*.txt|すべてのファイル (*.*)|*.*"
Set Env=CreateObject("WScript.Shell").Environment("Process")
ret=HtmlDlgHelper.object.openfiledlg(Env("File"),,Env("Filter"),Env("Title")):
```

# フォルダ選択
|項目|内容|
|:--|:--|
|ルートフォルダ|set "RootFolder=(ルートフォルダを指定)"|
|メッセージ|set "Title=（メッセージ名を指定)"|

```bat:folderRef.bat
@echo off
set "RootFolder=C:\Temp"
set "Title=読込みフォルダを選択してください"

set dialog="about:<script language=vbscript>resizeTo 0,0:Sub window_onload():
set dialog=%dialog%Set Shell=CreateObject("Shell.Application"):
set dialog=%dialog%Set Env=CreateObject("WScript.Shell").Environment("Process"):
set dialog=%dialog%Set Folder=Shell.BrowseForFolder(0, Env("Title"), 1, Env("RootFolder")):
set dialog=%dialog%If Folder Is Nothing Then ret="" Else ret=Folder.Items.Item.Path End If:
set dialog=%dialog%CreateObject("Scripting.FileSystemObject").GetStandardStream(1).Write ret:
set dialog=%dialog%Close:End Sub</script><hta:application caption=no showintaskbar=no />"

set folder=
for /f "delims=" %%p in ('MSHTA.EXE %dialog%') do  set "folder=%%p"
echo selected  folder is : "%folder%"
pause
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/f4d2ede2-59c6-e704-60d1-98c4e48f6adc.png)

```bat:結果
c:\Temp>folderRef.bat
selected  file is : "C:\Temp\Test"
続行するには何かキーを押してください . . .
```

# 最後に
これまでは、フォルダ固定にしていたりファイルやフォルダをドラッグ＆ドロップして引数から処理してました。自分が使用するだけならいいのですが、他の人に操作を依頼するなら、GUIでファイル選択やフォルダ選択できれば、操作性は向上します。

例として、`江戸時代の円周率.txt`ファイルを指定していました。実は江戸時代の寺子屋では、円周率は3.16で教えていたのです。明治になって円周率は3.14に訂正されました。そんな記事をnoteに書きました。

https://note.com/yaju/m/m80a5546f6bf4