PowerShell 與 .NET 工作目錄不同步是剛開始寫 PowerShell 常會踩到的坑之一。

用下面的例子復習這個問題:

我們在 D:\ 下啟動 PowerShell [1],接著切到 D:\sub 資料夾[2],寫入 .\test.txt,此時用 Get-Content .\test.txt 可順利讀取 [3]。但 [IO.File]::ReadAllText('.\test.txt').\test.txt 則被解析成 'D:\test.txt' [4],理由是 PowerShell 啟動時目錄在 D:\,故 .NET 世界的工作目錄還在 D:\。試著用 Resolve-Path .\test.txt 轉成絕對路徑再傳給 File.ReadAllText(),這次順利讀取。

這不算什麼大問題,多遇幾次便會養成習慣,傳相對路徑給 .NET API 時,要嘛強制 [System.IO.Directory]::SetCurrentDirectory($(PWD)) 強迫兩邊工作目錄同步,要嘛就是用 Resolve-Path 將 PowerShell 相對路徑轉成絕對路徑,我偏好後者。

而 Resolve-Path 有個不好用的地方 - 它只會傳回已存在的路徑,若目錄或檔案不存在,它會觸發錯誤,回報某某絕對路徑不存在。

這在處理"輸出路徑"參數最有困擾,要寫入的路徑或檔案本來就不存在,等下才要建立。可惡,明明都已經解析好了出現在錯誤訊息卻拿不到!

Resolve-Path 能無視路徑是否存在,只幫我解析絕對路徑就好嗎?很遺憾,即便到了 PowerShell 7.4,Resolve-Path 還是沒有內建參數可以無視檔案不存在只傳回解析結果。

爬文後找到解法

  1. 既然錯誤訊息有解析出來的絕對路徑,用 -ErrorVariable 參數把它撈出來
    $fullPath = Resolve-Path .\file-to-save.txt -ErrorAction SilentlyContinue -ErrorVariable err
    if (!$fullPath) {
        $fullPath = $err[0].TargetObject 
        # 想了解 ErrorVariable 傳回變數屬性可用 $err[0] | Format-List -Force 觀察
    }
    $fullPath
    
  2. 借用 GetUnresolvedProviderPathFromPSPath() 函式
    $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\file-to-save.txt")
    
    指令有點長,但一行搞定,不拖泥帶水。

若要方便應用,可將上述做法包成函式,例如:

function Resolve-AbsPath($relPath) {
    return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($relPath)
}
Resolve-AbsPath .\file-to-save.txt

就醬,又解決掉一個日常小問題囉~

This blog post discusses the common PowerShell issue of desynchronization with .NET working directories and offers solutions, including a custom function to resolve relative paths to absolute paths.


Comments

# by nameless

Registry provider 下的路徑回傳路徑格式不是 DriveName:\path\to\item,還需要替換root部分 -replace ('^' + [Regex]::Escape((Get-PSDrive -Name (Split-Path $PWD.Path -Qualifier).TrimEnd(':')).Root) + '($|[\/\\]+)'), ((Split-Path $PWD.Path -Qualifier) + '\')

Post a comment