Windowsの環境変数をバックアップして不測の事態に備えよう


TL;DR (Too Long, Didn’t Read)

Windowsの環境変数一覧をテキスト形式でバックアップするバッチファイルを書いた。それをタスクスケジューラに登録した。

前置き

新しいCLI ツールが環境変数を用意して設定を変更するタイプのものだったので、Windows システムの環境変数を編集して専用の環境変数を設定したところ、どうも様子がおかしい。今まで動いていたツールが動かなくなった。

色々調べた結果、「システムの環境変数」のPath が消失しているのが原因と気付き青ざめた。消した自覚は全くなかったのだが消してしまっていたらしい。そう、Windows の環境変数設定GUIは右下のボタンを何らかのはずみでクリックしてしまってそれに気が付かないままOKしてしまうと何の確認もなく簡単に削除されしかも後からそれを取り戻すのは難しい。

今日の悲しみを繰り返さないように環境変数を定期的にバックアップすることにした。
週に一度程度、テキストファイルの形式で環境変数の状態をバックアップするのだ。

テキスト形式でバックアップしたい

環境変数は $ reg save <reg-key> <file-path> コマンドで保存できるようだが、これだとバイナリ形式なので中身を確認できない。そりゃあリストアだけを考えたらバイナリが最も手軽だが、モニタリングするのならテキスト形式で保存していきたい。

幸い、$ reg query <reg-key> [OPTION] でレジストリキーと値の情報が文字列で取れるようだ。綺麗な形式ではないが整形すれば何とかなりそう。

バッチファイル

タスクスケジューラに登録して、一週間ごとにバックアップをさせるつもりだ。バッチファイルは嫌いなのだが、タスクスケジューラにそのまま登録できるという点で手軽なので今回はバッチファイルに処理させよう。

以下のファイルを適当な場所に作ろう。バッチファイルは改行コードはdos形式(CRLF)で、文字コードはCP932(Shift_JISの拡張)じゃないとちゃんと動かないと思われるので注意。

backup-envvar.bat
@echo off
setlocal enabledelayedexpansion

REM バックアップ先のディレクトリ 環境変数 HOME が定義されている前提
set BAK_BASE_DIR=%HOME%\.backup\win-envvar


REM 改行コードCRLF を得る: http://itdiary.info/コマンドプロンプト/post-861/
FOR /f "delims=" %%i IN ('cmd /u /c ECHO;名') DO (
  SET CR=%%i
  SET CR=!CR:~0,1!
)
REM LFの下の空行2行は意味があってやっているので詰めないこと
SET LF=^


set CRLF=!CR!!LF!


REM すでにバックアップされているファイルの中で最新のファイルを得る
REM ISSUE: ファイルが存在しないときには警告が出る
for /f %%a in ('DIR /b /O-d /a-d %BAK_BASE_DIR%\*.txt') do (
  set utdfile=%%a
  goto break
)
:break


REM %%A が環境変数名で %%B が環境変数の値。
REM 私は両者の間を空白(タブ)で結んだが、好みに応じて適宜変更してください。
set sysenvs=
for /f "tokens=1,3 skip=2" %%A in ('reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"') do (
  set sysenvs=!sysenvs!!CRLF!%%A    %%B
)

set usrenvs=
for /f "tokens=1,3 skip=2" %%A in ('reg query "HKEY_CURRENT_USER\Environment"') do (
  set usrenvs=!usrenvs!!CRLF!%%A    %%B
)


set qtime=%time:~,2%-%time:~3,2%
set DATETIME=%date:/=-%_%qtime: =0%

if not exist %BAK_BASE_DIR% mkdir %BAK_BASE_DIR%

REM ファイル中身フォーマットはここを書き換えたら変更できる
echo [ユーザー環境変数]:!usrenvs!!CRLF!!CRLF!!CRLF![システム環境変数]:!sysenvs! > %BAK_BASE_DIR%\%DATETIME%.txt


REM 前回と差分がない(=環境変数が変更されていない)なら今作ったファイルを削除する
if [%utdfile%] == [] (
  exit /b
)
fc %BAK_BASE_DIR%\%DATETIME%.txt %BAK_BASE_DIR%\%utdfile% > nul
IF %errorlevel%==0 (
  del %BAK_BASE_DIR%\%DATETIME%.txt
)

for 文の中では変数の展開に % ではなく ! を使うとか、改行コードが提供されていないので得るのに何か変な呪文を書かないといけないとか、そういう面倒くさいことがあったがまだ許容範囲でしょう。バッチファイルは一本道の手続きを書くためのものであってプログラミングするためのものではない(思想)。

それはともかく、これを実行したら現在の環境変数が記録されたテキストが指定ディレクトリに作成されるはずだ。

日付_時_分.txt の形式の名前のファイルが保存されていく。前回保存時から変更がなければ作ったばかりのファイルを削除することで、変更があったときだけファイルが作られていくようにした。

タスクスケジューラにバッチファイルを登録する

タスクスケジューラはWindowsメニューの Windows 管理ツールの中にある。「ファイル名を指定して実行」から「taskschd.msc」と打っても呼び出せるはずだ。

起動時はトップの階層である「タスクスケジューラライブラリ」で開かれるが、自分の作ったタスクを勝手に作られるタスクと区別する意味で、新しいフォルダーを作ってそこに自分のタスクを置いていくといいだろう。ちなみに一度作ったタスクはリネームすることも階層を移動させることもできないのが微妙に使いにくいところ。

「基本タスクの作成」から「名前」と「説明」(マルチバイト文字OK)、「トリガー」(あとから変更できるから深く考えなくてOK、1回限りにして適当に時間を入れてOK、手動でも起動できるので)、「操作」プログラムの開始、「プログラム/スクリプト」には先ほどのバッチファイルを指定、「開始(オプション)」は作業ディレクトリ(空でOK)、これで「完了」を押すとタスクが作られる。

右側の「操作」の「実行」ボタンを押したらタスクが起動する(ラグがあるかもしれない)ので、それでバックアップディレクトリにバックアップが生成されていることが確認出来たら成功。あらためてタスクを編集してトリガーを自分の好きなように変更しよう。

とりあえず私は週一で土曜日に起動させるようにした。週二でもいいかもしれない。

薄氷踏み抜いて地固める

こうして環境変数バックアップ環境を整えた。今回の失敗は中規模の痛手だったが何とかなってもっと大きな事故への備えもできたので結果的にはよかったのだろう。
あと、環境変数のログを取ることで、アプリがこっそり環境変数をいじっても差分を比べることで気づけるようになった。
ていうか、環境変数って割と致命的なものなのにカジュアルに破壊することができて、失ったら元に戻らないのにほとんどバックアップ手段が提供されていないってよく考えたらおかしいよね。