如何显示使用扩展参数调用的命令行

San*_*oli 3 powershell

我发现在 PowerShell (.ps1) 脚本中我可以显示启动该脚本的命令行:

Write-Information $MyInvocation.Line -InformationAction Continue
Run Code Online (Sandbox Code Playgroud)

但是,当我在命令行上传递变量时,使用上面的命令显示这些变量时不会展开它们。

为了方便起见,我需要扩展它们,因为我需要任何人都能够在没有定义的变量的情况下复制粘贴命令行。

我尝试过类似的东西:

# Display command
$fullCommand = $MyInvocation.MyCommand.Name
$MyInvocation.BoundParameters.Keys | ForEach {
    $fullCommand += " -$($_) $($PSBoundParameters.Item($_))"
}
Write-Information $fullCommand -InformationAction Continue
Run Code Online (Sandbox Code Playgroud)

这无法按预期工作,因为标志未正确显示。

参数块:

[CmdletBinding(DefaultParameterSetName="noUploadSet")]
param(
    [switch] $SkipGetList,
    [switch] $DisableHistory,
    [string] $RefVersion,
    [datetime] $ComputationDate,
    [string] $RefEnv,
    [string] $NewEnv,
    [Parameter(Mandatory=$true)][string] $Perimeter,
    [string] $ComputationType = "Test",
    [string] $WorkingDir,
    [string] $NewServiceName,
    [Parameter(ParameterSetName="noUploadSet")][switch] $DebugMode,
    [Parameter(ParameterSetName="uploadSet")][switch] $UploadNewService,
    [string] $ScenarioPath,
    [string] $User
)
Run Code Online (Sandbox Code Playgroud)

Mat*_*sen 5

这无法按预期工作,因为标志未正确显示。

如果“标志”指的是[switch]参数,那么您所需要的只是将:显式值紧密绑定到参数名称:

$fullCommand += " -$($_):$($PSBoundParameters.Item($_))"
Run Code Online (Sandbox Code Playgroud)

但这并不能解决你的所有问题。这些[datetime]值可能默认为包含空格的区域性特定格式,字符串值需要加引号(并正确转义),并且参数值需要在放入字符串时[switch]添加前缀:$

function Get-InvocationQuote
{
  param([System.Management.Automation.InvocationInfo]$Invocation)

  $cmdText = $Invocation.InvocationName
  foreach($param in $Invocation.BoundParameters.GetEnumerator()){
    $name = $param.Key
    $value = switch($param.Value){
      {$_ -is [string]} {
        # Quote and escape all string values as single-quoted literals
        "'{0}'" -f [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($_)
      }

      {$_ -is [datetime]} {
        # Quote datetime using a culture-independent format
        "'{0}'" -f $_.ToString('o')
      }

      {$_ -is [bool] -or $_ -is [switch]} {
        # Map booleans to their respective automatic variables
        '${0}' -f "$_"
      }

      {$_ -is [enum] -or $_.GetType().IsPrimitive} {
        # Leave numerals
        $_
      }

      default {
        throw "Unable to quote '{0}' of type '{1}'" -f [System.Management.Automation.LanguagePrimitives]::ConvertTypeNameToPSTypeName($_.GetType.FullName)
        return
      }
    }

    $cmdText += " -${name}:${value}"
  }

  return $cmdText
}
Run Code Online (Sandbox Code Playgroud)

现在我们有一个很好的独立实用程序,可以将$MyInvocation带有简单参数的命令转换为可执行引用,我们只需要一个命令来测试:

function Test-Quoting {
  param(
    [int]$Number,
    [string]$String,
    [switch]$Flag,
    [datetime]$Date
  )

  Get-InvocationQuote $MyInvocation
}
Run Code Online (Sandbox Code Playgroud)

我们得到如下结果:

PS ~> Test-Quoting -Number 123 -String "this will'need escaping" -Date 1/1/1970 -Flag
Test-Quoting -Number:123 -String:'this will''need escaping' -Date:'1970-01-01T00:00:00.0000000' -Flag:$True
Run Code Online (Sandbox Code Playgroud)

为了测试引用的命令确实重现原始调用参数,让我们测试连续引用是否生成重复结果:

PS ~> $quote = Test-Quoting -Number 123 -String "this will'need escaping" -Date 1/1/1970 -Flag
PS ~> $quoteEval = $quote |Invoke-Expression
PS ~> $quote -eq $quoteEval
True
Run Code Online (Sandbox Code Playgroud)


mkl*_*nt0 5

为了补充Mathias R. Jessen 的有用答案,提供了一个解决方案:

  • 也支持数组类型的参数
  • 采用更熟悉的语法,通过:
    • 仅在必要时引用参数
    • 仅在必要时才使用:分隔参数名称和值,即仅用于传递[switch]到的 -type 参数$false
      • 表示-Switch参数,其隐含值为$true, as just-Switch而不是 as-Switch:$true

笔记

  • 如果遇到一个在重新调用结果字符串时可能无法工作的值(例如 a[hashtable][pscustomobject]实例),则会发出警告。

  • [datetime][datetimeoffset]实例由其区域性不变的字符串表示形式表示,因为 PowerShell 在除 之外的字符串操作中-f使用不变的区域性进行格式化(有关背景信息,请参阅此答案)。因此,无论当前文化是什么,这种表达都应该有效。

    • 然而,这些表示仅限于的粒度 (例如,12/31/2020 10:57:35);如果您需要保留亚秒值,请使用.ToString('o')字符串化,如 Mathias 的答案所示;这也可能更好地避免默认字符串化的月份优先格式(您也可以选择较短的自定义格式作为'o'往返格式的替代)。

    • 顺便说一句:如果使用日期的字符串表示形式调用已编译的 cmdlet (而不是用 PowerShell 编写的代码),则解析会意外地对文化敏感- 不幸的是,由于向后兼容性问题,这种不一致不会得到修复 - 请参阅GitHub 问题 #6989

function Get-Foo {

  [CmdletBinding()]
  param(
      [switch] $SkipGetList,
      [switch] $DisableHistory,
      [datetime] $ComputationDate,
      [string] $RefVersion,
      [string] $WorkingDir,
      [int[]] $Indices
  )

  # Get this function's invocation as a command line 
  # with literal (expanded) values.
  '{0} {1}' -f `
    $MyInvocation.InvocationName, # the function's own name, as invoked
    ($(foreach ($bp in $PSBoundParameters.GetEnumerator()) { # argument list
      $valRep =
        if ($bp.Value -is [switch]) { # switch parameter
          if ($bp.Value) { $sep = '' } # switch parameter name by itself is enough
          else { $sep = ':'; '$false' } # `-switch:$false` required
        }
        else { # Other data types, possibly *arrays* of values.
          $sep = ' '
          foreach ($val in $bp.Value) {
            if ($val -is [bool]) { # a Boolean parameter (rare)
              ('$false', '$true')[$val] # Booleans must be represented this way.
            } else { # all other types: stringify in a culture-invariant manner.
              if (-not ($val.GetType().IsPrimitive -or $val.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint])) {
                Write-Warning "Argument of type [$($val.GetType().FullName)] will likely not round-trip correctly; stringifies to: $val"
              }
              # Single-quote the (stringified) value only if necessary
              # (if it contains argument-mode metacharacters).
              if ($val -match '[ $''"`,;(){}|&<>@#]') { "'{0}'" -f ($val -replace "'", "''") }
              else { "$val" }
            }
          }
        }
      # Synthesize the parameter-value representation.
      '-{0}{1}{2}' -f $bp.Key, $sep, ($valRep -join ', ')
    }) -join ' ') # join all parameter-value representations with spaces

}

# Sample call:
Get-Foo `
  -SkipGetList `
  -DisableHistory:$false `
  -RefVersion 1.0b `
  -WorkingDir "C:\dir A\files'20" `
  -ComputationDate (Get-Date) `
  -Indices (1..3)
Run Code Online (Sandbox Code Playgroud)

上面的代码生成以下单行字符串(为了便于阅读,仅在此处进行了注释并分成多行):

Get-Foo `
  -SkipGetList `  # switch syntax was preserved
  -DisableHistory:$false # negated switch (rare) was preserved
  -RefVersion 1.0b `  # string parameter NOT quoted, because not needed
  -WorkingDir 'C:\dir A\files''20' ` # quoting needed, ' escaped as ''
  -ComputationDate '12/31/2020 10:40:50' ` # culture-invariant date string
  -Indices 1, 2, 3  # array preserved
Run Code Online (Sandbox Code Playgroud)