DEV Community

Cover image for Windows 上利用 VBScript 取得 junction point 的真實路徑
codemee
codemee

Posted on • Edited on

Windows 上利用 VBScript 取得 junction point 的真實路徑

這幾天因為測試改用 scoop 當成軟體管理工具, 發現透過 scoop 安裝的 pyenv 無法幫我安裝 Python, 都會出現以下的錯誤訊息:

:: [Error] :: error installing "core" component MSI.
Enter fullscreen mode Exit fullscreen mode

根據善心人士的研究, 發現 msi 格式的安裝套件需要以 msiexe 執行, 但是 msiexe 考量跳轉到其它資料夾的安全性, 所以如果遇到指定安裝的資料夾是 junction point 時, 並不會取得真正的路徑, 所以安裝就會失敗。scoop 為了讓軟體更新時不會動到執行軟體的路徑, 所以每個軟體都會有一個 current 的 junction point, 連結到實際版本的資料夾, pyenv 會把 Python 安裝到透過 current 連結的資料夾下, 因此遇到 msiexe 不取得真正路徑的問題。

pyenv 的 Windows 版本實際上使用了 VBScript 撰寫安裝 Python 的工作, 因此這就引起了我的好奇心, 要怎樣才能在 VBScript 中取得 junction point 的實際路徑, 這樣就可以再執行 msiexe 的時候, 改用真實路徑, 就可以避免前面提到的問題了。

取得 junction point 實際路徑的指令

VBScript 時實際上沒辦法取得 junction point 的真實路徑, 因此就要看看是不是有限成的指令可用, 再由 VBScript 去執行指令後取得輸出結果。

要在 Windows 上取得 junction point 的真實路徑, 有以下三種指令:

  • 使用內建工具 fsutil

    # fsutil reparsepoint query "current"
    重新分析標記值 : 0xa0000003
    標記值: Microsoft
    標記值: Name Surrogate
    標記值: Mount Point
    取代名稱位移: 0
    取代名稱長度: 80
    列印名稱位移: 82
    列印名稱長度: 0
    取代名稱:       \??\C:\Users\meebo\scoop\apps\7zip\23.01
    
    重新分析資料長度:0x5c
    重新分析資料:
    0000:  00 00 50 00 52 00 00 00  5c 00 3f 00 3f 00 5c 00  ..P.R...\.?.?.\.
    0010:  43 00 3a 00 5c 00 55 00  73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
    0020:  5c 00 6d 00 65 00 65 00  62 00 6f 00 5c 00 73 00  \.m.e.e.b.o.\.s.
    0030:  63 00 6f 00 6f 00 70 00  5c 00 61 00 70 00 70 00  c.o.o.p.\.a.p.p.
    0040:  73 00 5c 00 37 00 7a 00  69 00 70 00 5c 00 32 00  s.\.7.z.i.p.\.2.
    0050:  33 00 2e 00 30 00 31 00  00 00 00 00              3...0.1.....
    

    『取代名稱』後面就是 current 這個 junction point 實際的路徑, 不過這個指令會依據系統選用的語系顯示對應語言的訊息, 像是剛剛看到的『取代名稱』在英文語系下就會是 "Substitute Name", 如果試想要透過程式從中取得實際路徑, 就要小心語系的問題。

  • 使用需要額外下載的外部工具 junction

    # junction current
    
    Junction v1.07 - Creates and lists directory links
    Copyright (C) 2005-2016 Mark Russinovich
    Sysinternals - www.sysinternals.com
    
    C:\Users\meebo\scoop\apps\7zip\current: JUNCTION
       Substitute Name: C:\Users\meebo\scoop\apps\7zip\23.01
    

它固定使用英文訊息, 所以不會有語系的問題。不過這個工具程式並沒有考慮到中文, 所以如果連結到中文資料夾的 junction point, 就會出現亂碼:

   # junction test

    Junction v1.07 - Creates and lists directory links
    Copyright (C) 2005-2016 Mark Russinovich
    Sysinternals - www.sysinternals.com

    C:\Users\meebo\code\test\test: JUNCTION
       Substitute Name: C:\Users\meebo\code\test\??
Enter fullscreen mode Exit fullscreen mode
  • 使用 PowerShell 的 get-item 指令

    這應該是最理想的方案:

    # (get-item "current").Target
    C:\Users\meebo\scoop\apps\7zip\23.01
    

    即使是中文資料夾也沒問題:

    # (get-item "test").Target
    C:\Users\meebo\code\test\測試
    

使用 VBScript 取得 junction point 的實際路徑

VBScript 其實沒有真正取得 junction point 的方法, 所以必須執行剛剛介紹的工具間接取得:

foldername = "current"

Set sh = CreateObject("WScript.Shell")
Set fsutil = sh.Exec("fsutil reparsepoint query """ & foldername & """")

Do While fsutil.Status = 0
 WScript.Sleep 100
Loop

If fsutil.ExitCode <> 0 Then
 WScript.Echo "An error occurred (" & fsutil.ExitCode & ")."
 WScript.Quit fsutil.ExitCode
End If

Set re = New RegExp
re.Pattern = "取代名稱:\s+(.*)"

For Each m In re.Execute(fsutil.StdOut.ReadAll)
 targetPath = m.SubMatches(0)
Next

WScript.Echo targetPath
Enter fullscreen mode Exit fullscreen mode

要記得 VBScript 是 Big5 編碼, 存檔時要注意。執行結果如下:

# cscript //U //nologo c:\users\meebo\code\test\test2.vbs
\??\C:\Users\meebo\scoop\apps\7zip\23.01
Enter fullscreen mode Exit fullscreen mode

請留意實際的路徑之前還會有垃圾資料, 也要處理掉。

如同之前所說, 因為 fsutil 的輸出會因為語系而採用不同語言, 程式碼中以規則樣式找尋真實路徑位置的地方也要跟著改。如果想要避免這個麻煩, 也可以改用 junction 外部工具:

foldername = "current"

Set sh = CreateObject("WScript.Shell")
Set fsutil = sh.Exec("junction """ & foldername & """")

Do While fsutil.Status = 0
 WScript.Sleep 100
Loop

If fsutil.ExitCode <> 0 Then
 WScript.Echo "An error occurred (" & fsutil.ExitCode & ")."
 WScript.Quit fsutil.ExitCode
End If

Set re = New RegExp
re.Pattern = "Substitute Name:\s+(.*)"

For Each m In re.Execute(fsutil.StdOut.ReadAll)
 targetPath = m.SubMatches(0)
Next

WScript.Echo targetPath
Enter fullscreen mode Exit fullscreen mode

執行結果如下:

# cscript //U //nologo c:\users\meebo\code\test\test.vbs 
C:\Users\meebo\scoop\apps\7zip\23.01
Enter fullscreen mode Exit fullscreen mode

不過因為 fsutil 和 junction 各有缺點, 所以最理想的還是透過 PowerShell 指令:

foldername = "test"

Set sh = CreateObject("WScript.Shell")
Set fsutil = sh.Exec("powershell -command  (get-item """ & foldername & """).target")

Do While fsutil.Status = 0
 WScript.Sleep 100
Loop

If fsutil.ExitCode <> 0 Then
 WScript.Echo "An error occurred (" & fsutil.ExitCode & ")."
 WScript.Quit fsutil.ExitCode
End If

targetPath = fsutil.StdOut.ReadAll
WScript.Echo targetPath
Enter fullscreen mode Exit fullscreen mode

執行結果如下:

# cscript //U //nologo c:\users\meebo\code\test\test1.vbs
C:\Users\meebo\scoop\apps\7zip\23.01
Enter fullscreen mode Exit fullscreen mode

即使是連結到中文資料夾也沒有問題:

# cscript //U //nologo c:\users\meebo\code\test\test1.vbs
C:\Users\meebo\code\test\測試
Enter fullscreen mode Exit fullscreen mode

Top comments (0)