簡體   English   中英

將參數從 Powershell 傳遞到批處理腳本

[英]Pass argument from Powershell to Batch script

我需要將帶有特殊字符的密碼從 powershell 腳本automation.ps1傳遞給批處理腳本batch_script.bat ,然后將其通過管道傳輸到main.py batch_script.batmain.py的管道工作正常,即身份驗證成功。 但是,當我運行上述整個過程時,身份驗證失敗,但echo密碼顯示正確的密碼字符串。

我的猜測是特殊字符存在問題。 傳遞這些字符串的安全方法是什么?

背景

我想通過 Python 腳本main.py自動從某些外部源進行每日下載。 此過程需要密碼。 因此,我編寫了一個batch_script.bat ,它在提示輸入密碼時將密碼傳送到 Python 腳本。 但是,我不想將密碼作為純文本存儲在批處理腳本中,因此我對密碼進行了加密並編寫了另一層automation.ps1來解密密碼並將其作為純文本傳遞給batch_script.bat

自動化.ps1

# get password
$securePassword = Get-Content "<file_path>" | ConvertTo-SecureString
$credentials = New-Object System.Management.Automation.PsCredential("<user_name>",$securePassword)
$unsecurePassword = ($credentials).GetNetworkCredential().Password

# call script
$script = "<path>\batch_script.bat"
$args = @("`"<user name>`"","`"$unsecurePassword`"")
start-process $script $args

批處理腳本.bat

(我知道在這個例子中我丟棄了傳遞的用戶名,只是想保留我傳遞多個 arguments 的事實,以防與它有任何相關性)

@echo off
SET username=%~1
SET password=%~2
echo(%password%|python main.py

有了以下,所有特殊字符都應該得到很好的處理。 如果需要轉義任何字符,請檢查

$pass 可以是任何字符串,但檢查powershell的特殊字符

$pass="%^&<>|'\``,;=(\)![]\/";
# Wait till it ends with -Wait when using -NoNewWindow.
# It may be comprehensible to use `" instead of "" to denote we are enclosing string in quotes.
(thanks @mklement0 for elaboration).
start-process -Wait -NoNewWindow .\script.cmd "`"$pass`""
script.cmd
    setlocal

    rem Remove Double quotes
    set "arg=%~1"

    rem Test result with base64 encoding
    echo|set/p="%arg%"|openssl base64
    rem echo is used with set/p to prevent trailing new line.
    echo|set/p="%arg%"|python main.py

    rem Test with following, argument is in double quotes
    rem script "%^&<>|'\`,;=(\)![]\/"
    rem Expected result
    rem %^&<>|'\`,;=(\)![]\/



tl;博士

  • 除非您特別需要批處理文件在新的 window中運行,否則請避免Start-Process (其內置別名是startsaps ),並直接調用批處理文件。

  • 為避免$unsecurePassword的特殊字符出現問題,請勿將其作為參數傳遞,而是通過標准輸入(標准輸入流)傳遞,您的批處理文件將通過該標准輸入到您的python腳本:

automation.ps1

# ...
$script = "<path>\batch_script.bat"
# Pass the password via *stdin*
$unsecurePassword | & $script 'userName'

注意: $OutputEncoding首選項變量控制 PowerShell 用於將文本發送到外部程序的標准輸入的字符編碼。 Windows PowerShell中,該變量默認為 ASCII(,) 編碼,這意味着在 Unicode 字符的 7 位 ASCII 范圍之外的任何字符,如轉重音? 幸運的是,PowerShell [Core] v6+ 現在默認為 UTF-8。 根據需要將所需的編碼分配給$OutputEncoding

batch_script.bat

@echo off

SET username=%~1

REM The batch file will pass its own stdin input through to Python.
python main.py

繼續閱讀背景信息。


從 PowerShell 調用批處理文件:

除非您確實需要在新的 window中啟動批處理文件,否則最好的方法是直接從 PowerShell 調用它 這樣,它運行:

  • 在同一個控制台 window 中,同步。

  • 其 output 流連接到 PowerShell(允許您捕獲或重定向輸出)。

因為您的批處理文件路徑存儲在變量中,所以直接調用需要使用& ,即調用運算符

# Note: The " chars. around $unsecurePassword are only needed if the variable
#       value contains cmd.exe metacharacters - see next section.
& $script 'userA' `"$unsecurePassword`"

Start-Process通常是調用控制台應用程序、批處理文件和其他基於控制台的腳本的錯誤工具; 有關更多信息,請參閱此答案

如果您確實需要在新的 window 中運行批處理文件(這只是Windows上的一個選項),請按如下方式使用Start-Process (該命令將異步執行,除非您還通過-Wait ):

# The string with the arguments to pass is implicitly bound 
# to the -ArgumentList parameter. Use only " for embedded quoting.
Start-Process $script "userA `"$unsecurePassword`""

注意:雖然(隱含的) -ArgumentList ( -Args ) 參數是數組值 ( [string[]] ) 並且單獨傳遞 arguments 可以說是更清潔的方法,但這通常不能正常工作,因為可能存在一個長期存在的錯誤不會得到修復; 例如,
Start-Process foo.exe -Args 'one', 'two (2)'通過3 arguments 而不是 2; 也就是說,它將單個字符串'two (2)'作為兩個arguments 傳遞 - 請參閱此 GitHub 問題
因此,將帶有嵌入式引用單個參數傳遞給-ArgumentList最終更簡單且更可預測,但請確保僅使用" (而不是' )進行嵌入式引用:
Start-Process foo.exe -Args "one `"two (2)`""


將 arguments 穩健地傳遞給cmd.exe / 批處理文件:

筆記:

  • cmd.exe (解釋批處理文件的傳統命令處理器)的局限性阻礙了完全穩健的解決方案; 值得注意的是,當您從 PowerShell 調用批處理文件時,您不能阻止將諸如%foo%之類的標記解釋為環境變量引用(至少在不將參數更改為%foo^%的情況下不會,這將保留^ )。

  • 在您的特定情況下,由於您試圖echo顯參數unquoted ,因此此類參數中嵌入的雙引號 ( " ) - 需要轉義為"" - 不被正確支持:它們作為""傳遞。

將不帶引號的參數傳遞給cmd.exe / 如果該參數包含cmd.exe元字符之一,則批處理文件會中斷,即具有特殊語法含義的字符; 在這種情況下,它們是: & | < > ^ " & | < > ^ "

解決方案是將參數用引號 ( "..." ) 括起來,同時還需要將嵌入"字符加倍(值的一部分)。

PowerShell 在執行自己的命令行解析(特別是評估變量引用和表達式)之后,在幕后構造最終用於調用外部目標程序的命令行。

但是,它只會在參數包含空格時自動雙引號,而不是僅包含cmd.exe元字符時; 例如,具有逐字字符串內容two (2)變量通過雙引號傳遞 - $val = 'two 2'; .\foo.bat $val $val = 'two 2'; .\foo.bat $val導致命令行.\foo.bat "two 2" - 而字符串內容a&b不是- $val = 'a&b'.\foo.bat $val導致.\foo.bat a&b -這打破了

解決方案 - 如您的問題所示 - 是將變量引用括在文字中,嵌入"字符,因為這樣的“預引用”值指示 PowerShell 按原樣傳遞值:
$val = 'a&b'; .\foo.bat `"$val`" $val = 'a&b'; .\foo.bat `"$val`"結果為.\foo.bat "a&b"

注意: .\foo.bat "`"$val`""效果相同; 我利用了 PowerShell 在參數(解析)模式下(通常)隱含地將 arguments 視為雙引號的事實; 表達式(解析)模式中,例如在問題中的數組構造語句( @(..., ...) )中,您確實需要"`"$val`""形式。


您的特定批處理文件的問題:

正確的"..."封閉參數(任何嵌入的"字符。轉義為"" )被正確地視為批處理文件中的參數(例如, %1 )。

但是,它帶有封閉的雙引號任何雙倍嵌入的"字符。

如果您將此參數作為參數傳遞給目標程序(在這種情況下python ),那么一切都會按預期工作。

但是,由於您通過 stdin使用echo傳遞值,因此您需要去掉封閉的雙引號,以便它們不會作為value 的一部分傳遞,這是您的批處理文件嘗試的(例如, %~2

但是,傳遞剝離的值會導致echo命令中斷

echo沒有很好的解決這個問題的方法,沒有執行繁瑣的顯式^ -轉義( ^cmd.exe的轉義字符):

$escapedUnsecurePassword = $unsecurePassword -replace '[&|<>^]' -replace '"', '""'
& $script 'userA' `"$escapedUnsecurePassword`"

但是,僅此還不夠 - 您的batch_script.bat文件也需要修改:因為您的SET password=%~2命令中的賦值本身不受雙引號保護,它會與包含元字符的值中斷 有點自相矛盾的是,您必須使用SET "password=%~2"形式才能安全地去除嵌入的封閉"字符。:

@echo off

REM Strip the enclosing "..." from the arguments (%~<n>)
REM !! The *assignment itself* must be in "..."  so that
REM !! it does not break if the value has cmd.exe metacharacters.
set "username=%~1"
set "password=%~2"

echo(%password%|python main.py

請注意,這將按預期對所有元字符起作用,除了- 必須加倍- 嵌入的" ,它們作為""傳遞。


但是,有一種解決方法可以用未引用的元字符來回顯字符串,正如subcoder 的有用答案中所展示的那樣:

如果你定義batch_script.bat如下:

@echo off

set "username=%~1"

REM Do NOT strip quotes from the password argument
set password=%2

REM Using a trick with set /p, echo the password unquoted.
REM Note: Put the "|" directly after the ":" to avoid trailing spaces.
<NUL set /p=%password% & echo:|python main.py

變通方法重新調整了set/p選項的用途,該選項在以交互方式提示用戶輸入值時接受要打印的提示消息,並打印不帶引號的消息; 實際的交互式提示通過<NUL被抑制,因此只打印消息。

echo:打印一個換行符(換行符),這是必要的,因為set /p命令打印其消息時沒有尾隨換行符(如果您不想發送換行符,只需省略& echo: )。

警告:除了嵌入的""問題也在這里應用之外,這種解決方法還有一個副作用:它會修剪前導空格 例如, " foo "導致 output foo (僅保留尾隨空格); 但是,鑒於帶有前導空格的 arguments 很少見,這在實踐中可能無關緊要。


鑒於上述方法的繁瑣/晦澀,頂部顯示的基於標准輸入的方法更可取。

您使用start / saps-argumentlist開關將 arguments 傳遞給 powershell 中的批處理文件。 對你來說,你可以使用:

saps "$script" -argumentlist $args

但我建議首先分解$args ,因為它可能不起作用,因為通過 arguments,您通常希望一次通過 arguments,例如:

saps "$script" -argumentlist "1","2","3"

傳遞$args在大多數情況下都會起作用,但在某些情況下它不起作用。 大多數時候你都很好

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM