皆さん、PC のバックアップはしてますか?
何が起こるかわからないので、定期的なバックアップは大切です。
Microsoft としては、OneDrive を使ったバックアップを推しているように思われます。
新しく PC を買ってきて立ち上げると、Windows 10 の初期セットアップ中に、OneDrive バックアップを構成することを勧められますし。
今回は、そんな OneDrive バックアップと、PowerShell の相性のお話。
つまり?
PowerShellGet を使っている場合、Get-InstalledModule で、インストール済みのモジュールの一覧が取得できますね。
ところが、PowerShell Core 6 を使っていて、ドキュメント フォルダーを OneDrive にバックアップしていると、このコマンドは何も結果を返しません。
#PowerShell Core 6.2.1 + PowerShellGet 2.1.5 の環境で Get-InstalledModule が何も結果を返さない。Document フォルダをうっかり OneDrive にマップしてしまったせいか。
— あえとす 7/28 和光市ボドゲ会 (@aetos382) July 4, 2019
何故なの。
OneDrive フォルダはリパース ポイント
「シンボリック リンク」とか「ジャンクション」とか、聞いたことありますか。
平たく言うと、ファイルやフォルダーの実体を、そのパスが指し示す場所とは違う場所に置くという NTFS の機能です。
これらは、より包括的な「リパース ポイント」という仕組みで実現されています。
リパース ポイントという仕組みは汎用的なものであり、シンボリック リンクやジャンクション以外にも、様々なところで使われています。
OneDrive もその一つです。
あるフォルダがリパース ポイントかどうかは、fsutil reparsepoint query というコマンドで調べられます。
ここで表示される「再解析タグ値」というのが、リパースポイントの種類を示します。
シンボリック リンクとジャンクションが代表的な例ですが、(レガシーなものも含めれば)30 種類以上が定義されています。*2
Get-InstalledModule は内部で Get-ChildItem を使っている
PowerShellGet でモジュールをインストールできる場所は 2 つです。
Install-Module の -Scope パラメーターで間接的に指定でき、PowerShell Core 6 の場合は
- AllUsers → Program Files 下の PowerShell\Modules
- CurrentUser → ドキュメント フォルダ下の PowerShell\Modules
となります。
上記のリンクにある Install-Module コマンドの説明では、CurrentUser の場合は $env:HOME から取るように見えますが、これは嘘で、実際は Environment.GetFolderPath("MyDocument") で取得されます。
このメソッドが返すパスは、ドキュメント フォルダを OneDrive にバックアップしている場合、OneDrive 内のパスになります。
つまり、Install-Module がモジュールをインストールするパスも、OneDrive 内になるわけです。
Get-InstalledModule コマンドは、これらのフォルダ内を、Get-ChildItem コマンドを使って再帰的に検索し、PowerShellGet でインストールされたモジュールを探します。*3
PowerShell Core 6 からの仕様変更
PowerShell Core 6 から、Get-ChildItem に -FollowSymLink というパラメーターが追加されています。
再帰的にフォルダー階層を検索していくときに、リパース ポイントを見つけると、-FollowSymLink パラメーターが指定されていない限り、Get-ChildItem はそこより下の階層を見に行きません。
そして、現状、Get-InstalledModule は、その内部で Get-ChildItem を呼び出すときに、このパラメーターをつけていないのです。
Windows PowerShell 5.1 にはこのパラメーターはなく、既定でリパース ポイントを辿るようになっているので、この問題は起きません。
まとめると
- OneDrive フォルダはリパース ポイント
- OneDrive バックアップを構成していると、PowerShellGet がモジュールをインストールしたり探索したりするフォルダは OneDrive 下、つまり、リパース ポイントの先になる
- Get-InstalledModule は Get-ChildItem を使ってモジュールを探索する
- PowerShell Core 6 から、Get-ChildItem は -FollowSymLink パラメーターをつけないと、リパース ポイントの先を見ない仕様になった
- Get-InstalledModule は、その内部で Get-ChildItem を呼ぶ際に、-FollowSymLink パラメーターをつけていない
というのが、この問題の原因です。
そのため、Get-InstalledModule が、内部で Get-ChildItem を呼ぶ際に、-FollowSymLink パラメーターをつけるようにしてくれれば解決するはずです。
Windows 10 が推奨する構成でやるとちゃんと機能しないということで、早めに修正して欲しいなあ…と思っています。