如何正确使用PowerShell $args[]

问题描述

有时使用包含空格并用双引号括起来的参数调用 PowerShell 脚本, 以确保字符串被解释为一个单一的参数。当参数为空并且传递的双引号不包含任何内容时,就会出现问题。 PS 将命令行视为非空,并尝试从中形成参数值。除了双引号被去掉,留下一个空字符串作为参数值。当然 PS 不能实际使用空值,所以我们要验证命令行参数以禁止空字符串。

查看调用 Cmd 脚本和 PS 源代码,两者都进行了注释。测试 4 和 5 显示了扫描 $args[] 数组并返回第一个有效/非空参数的函数的好处。

问题:为什么不能只用一个 ForEach 循环对函数进行编码?

Args.CMD:

@Echo Off
CD /D "%~dp0" & Rem Change to the folder where this script lives
CLS
Echo.

Rem PS script name is the same as this Cmd script
Set Script=%~n0.PS1

Rem Parameters of each call here are TestNumber,ExpectedExitCode,Argument1,Argument2

Rem Test 0: Try with zero       arguments;                 expected exit code is 1
Call :TryMe 0 1

Rem Test 1: Try with non-useful arguments;                 expected exit code is 2
Call :TryMe 1 2 ""        ""

Rem Test 2: Try with valid      arguments,single   words; expected exit code is 0
Call :TryMe 2 0 ArgOne     ArgTwo

Rem Test 3: Try with valid      arguments,multiple words; expected exit code is 0
Call :TryMe 3 0 "Arg One" "Arg Two"

Rem Test 4: Try with mixed      arguments,single   words; expected exit code is 0
Call :TryMe 4 0 ""         ArgTwo

Rem Test 5: Try with mixed      arguments,multiple words; expected exit code is 0
Call :TryMe 5 0 ""        "Arg Two"

Echo All Done.
Pause
GoTo :EOF

:TryMe
Set TstNbr=%1
Set Expcod=%2
Set Args12=%3 %4
Echo Test %TstNbr% of 5: PowerShell.EXE -F %script% %Args12%
                         PowerShell.EXE -F %script% %Args12%
Echo ErrorLevel is [%ErrorLevel%],expected was [%Expcod%]
Pause
Echo.
GoTo :EOF

Args.ps1:

Set-StrictMode -Version 3.0                     # disallow implicit variables

function ArgNonEmtFstGet( $arrArgs ) {          # get the first non-empty argument
  ForEach( $strArgs In $arrArgs ) {             # <=== why is this double-indirection needed?
    ForEach( $strArg In $strArgs ) {            # scan all argument strings
      $intArgLen = $strArg.Length               # get length of current argument-string
      If( $intArgLen -gt 0 ) { Return $strArg } # first time length is greater than zero,quit scanning and pass that argument back to caller
    }                                           # done scanning
  }                                             # <=== why is this double-indirection needed?
  ""                                            # when we finished scanning and did not return early,return a non-crashing value
} # ArgNonEmtFstGet

# Step 0: show we are alive
"Arg count: " + $args.Length                    # emit how many strings exist that *Could be* arguments

# Step 1: look for any argument at all
If( $args.Length -eq 0 ) {                      # when there are zero strings that look like arguments
  Write-Output "Putative argument required."    # emit error message for 'no strings found'
  Exit( 1 )                                     # abort with exit code
}                                               # done with no strings found that *Could be* arguments
"First putative argument: [" + $args[0] + "]"   # emit the first argument-looking string

# Step 2: look for an argument that is actually meaningful
$strArg = ArgNonEmtFstGet( $args )              # get the first non-empty argument; that is our input
If( $strArg -eq "" ) {                          # when it is not a usable argument
  Write-Output "Non-empty argument required."   # emit error message for 'no real arguments found'
  Exit( 2 )                                     # abort with exit code
}                                               # done with no usable arguments found
"First non-empty argument: [$strArg]"           # emit the first valid argument; this is our input

解决方法

我无法使用嵌套的 foreach 语句重现您的问题。 当我删除内部循环时,我得到了相同的结果。

但我建议您应该避免使用 $args 变量,因为它接受任何输入并且不允许命名绑定(仅限位置)。

只需在您的 PowerShell 脚本中声明一个 param 块,如下所示,然后您就不需要遍历您的参数,此外,您已经命名了可以在批处理脚本中使用的参数。 (位置绑定仍然是可能的)。

带有 param 块和“ValidateScript”属性的示例脚本,用于验证输入以避免空输入、空输入或空白输入。

param(
    # first mandatory parameter. Default Position = 1 (if using positional binding)
    [Parameter(Mandatory=$true)]
    [ValidateScript(
        {
            if([String]::IsNullOrWhiteSpace($_)) {
                throw [System.ArgumentException]'Your string is null,empty or whitespace!'
            } else {
                $true
            }
        }
    )]
    [string]
    $varA,# second mandatory parameter. Default Position = 2 (if using positional binding)
    [Parameter(Mandatory=$true)]
    [ValidateScript(
        {
            if([String]::IsNullOrWhiteSpace($_)) {
                throw [System.ArgumentException]'Your string is null,empty or whitespace!'
            } else {
                $true
            }
        }
    )]
    [string]
    $varB,# You can just use this parameter if you want your arguments being absolutely dynamic
    [Parameter(ValueFromRemainingArguments)]
    [ValidateScript(
        {
            if([String]::IsNullOrWhiteSpace($_)) {
                throw [System.ArgumentException]'Your string is null,empty or whitespace!'
            } else {
                $true
            }
        }
    )]
    [string[]]
    $remainingArgs
)


# create ordered dictionary of mandatory arguments used for returning object
$properties= [ordered]@{
    'Arg0' = "'$varA'"
    'Arg1' = "'$varB'"
}


# add any optional remaining argument (if any)
$index = 1 # start index
foreach ($remainingArg in $remainingArgs) {
    $index++
    $properties.Add("Arg$index","'$remainingArg'")
}

# return as object
[pscustomobject]$properties

如脚本中所述,如果您希望脚本在参数方面绝对动态,您也可以仅使用第三个参数。

关于属性“ValidateScript”的更多信息:https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-5.1#validatescript-validation-attribute

附注: 批量使用命名参数时的示例命令:

PowerShell.EXE -F %Script% -varA "%3" -varB "%4"