PowerShellの基本的なところがわかってなかった恥を晒すエントリです。
本エントリの内容はWindows 8.1上で動作させ確認しています。
[2015/05/11追記]
内容に間違いがあったので修正。
さらに恥を晒すことになってしまいました。
はじめに
PowerShellを起動してGet-Location
(またはpwd
コマンド)を実行すると以下の様な感じでカレントディレクトリのパスを取得することができます。
Windows PowerShell Copyright (C) 2014 Microsoft Corporation. All rights reserved. PS C:\Users\stknohg> Get-Location Path ---- C:\Users\stknohg
また、System.IO.Directory.GetCurrentDirectory
メソッドを実行しても同様の結果を得ることができます。
PS C:\Users\stknohg> [System.IO.Directory]::GetCurrentDirectory()
C:\Users\stknohg
ここでSet-Location
(またはcd
コマンド)で適当なディレクトリに移動して再度カレントディレクトリのパスを取得してみると、
PS C:\Users\stknohg> cd C:\Tmp PS C:\Tmp> Get-Location Path ---- C:\Tmp PS C:\Tmp> [System.IO.Directory]::GetCurrentDirectory() C:\Users\stknohg
となり、System.IO.Directory.GetCurrentDirectory
メソッドを実行した場合にカレントディレクトリが更新されない事象に遭遇しました。
この事象によってPowerShellから.NET Frameworkの機能を呼んで相対パスを使用する場合(例えばSystem.IO.Compression
あたりの機能を使う場合など)に予期しない結果になることがあります。
PowerShellのドライブとロケーション
個人的にはGet-Location
とSystem.IO.Directory.GetCurrentDirectory
メソッドの結果は常に同期するものと思ってて何故こうなるのかわかっていませんでした。
結論から先に書くと、この様な結果になるのはPowerShellで扱う"ロケーション"がディレクトリだけではないためになります。
上記の内容は間違ってて、[System.IO.Directory]::GetCurrentDirectory()
メソッドは現在のプロセス(PowerShell.exe)のカレントディレクトリを取得するものであり、PowerShellのシェル内でのロケーションとは無関係であるのが理由の様です。
MSDNフォーラムの以下のスレッドでこの件と類似の現象について触れられていました。
[IO.Directory]::GetCurrentDirectory() gives different results on different PCs
いちおう、以下のドライブとロケーションの関係については間違ってはいないのでそのままにしておきます...
PowerShellのドライブ
Managing Windows PowerShell Drives | Microsoft Docs で細かい説明がされていますが、PowerShellにおいてドライブはファイルシステム以外も管理対象としています。
Get-PSDrive
コマンドを使うとドライブの一覧を取得することができます。
実行例
PS C:\Tmp> Get-PSDrive Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- Alias Alias C 12.58 51.08 FileSystem C:\ Cert Certificate \ D FileSystem D:\ Env Environment Function Function HKCU Registry HKEY_CURRENT_USER HKLM Registry HKEY_LOCAL_MACHINE Variable Variable WSMan WSMan
この結果のProvider
の部分がドライブの種類とみなすことができ、ファイルシステム以外のものがあることがわかります。
主なプロバイダーには、
- Alias - エイリアス
- Certificate - 証明書
- Environment - 環境変数
- FileSytem - ファイルシステム
- Function - ファンクション
- Registry - レジストリ
- WSMan - WS-Management構成情報
- Variable - システム定義、独自定義の変数
等があります。各プロバイダーの詳細はこの辺やこの辺に載っています。
PowerShellのロケーション
そして、これらのプロバイダーの中で階層構造を持つもの、例えばレジストリについてもディレクトリと同様にSet-Location
でカレントのキーを移動することができる様になっています。*1
実行例
PS C:\Tmp> cd HKCU:\Software\Microsoft PS HKCU:\Software\Microsoft> ls Hive: HKEY_CURRENT_USER\Software\Microsoft Name Property ---- -------- Active Setup ActiveMovie Assistance AuthCookies Command Processor PathCompletionChar : 9 EnableExtensions : 1 CompletionChar : 9 DefaultColor : 0 ・・・(後略)・・・
当然、上記の例を実行した時点でGet-Location
した場合に得られるのはディレクトリではなくレジストリキーのパスになります。
PS HKCU:\Software\Microsoft> Get-Location Path ---- HKCU:\Software\Microsoft PS HKCU:\Software\Microsoft> Get-Location | fl Drive : HKCU Provider : Microsoft.PowerShell.Core\Registry ProviderPath : HKEY_CURRENT_USER\Software\Microsoft Path : HKCU:\Software\Microsoft
この様にPowerShellにおいて"ロケーション"は単純なディレクトリを指すだけではなくより抽象度の高いものであるため、Get-Location
とSystem.IO.Directory.GetCurrentDirectory
メソッドの結果が同期しない様になっている様です。
PowerShellにおいて"ロケーション"は単純なディレクトリを指すだけではなくより抽象度の高いものでありましたが今回の件とはあまり関係ありませんでした...
カレントディレクトリを同期させるには
とはいえGet-Location
とSystem.IO.Directory.GetCurrentDirectory
メソッドの結果が同期してないと困る場合もあったりするので、同期させたい場合は以下の様にロケーションのProvider
がFileSystem
の場合にSystem.IO.Directory.SetCurrentDirectory
メソッドでカレントディレクトリを更新してやればOKです。
$Current = Get-Location if( $Current.Provider.Name -eq "FileSystem" ){ [System.IO.Directory]::SetCurrentDirectory($Current.Path) }
【2016/01/08追記】
[System.IO.Directory]::SetCurrentDirectory((Get-Location -PSProvider FileSystem).Path)
と、Providerを指定したGet-Location(pwd)
でも良いですね。
*1:一応補足しておくと、階層構造をもたないプロバイダーに対してもドライブのルートに対してはSet-Locationを行うことができます。