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

Powershell 如何显示使用扩展参数调用的命令行,powershell,Powershell,我发现,在PowerShell(.ps1)脚本中,我可以使用以下命令显示启动脚本的命令行: Write-Information $MyInvocation.Line -InformationAction Continue 但是,当我在命令行上传递变量时,使用上面的命令显示变量时,这些变量不会展开 为了方便起见,我需要对它们进行扩展,因为我需要任何人都能够在没有定义变量的情况下复制粘贴命令行 我试过这样的方法: # Display command $fullCommand = $MyInvoca

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

Write-Information $MyInvocation.Line -InformationAction Continue
但是,当我在命令行上传递变量时,使用上面的命令显示变量时,这些变量不会展开

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

我试过这样的方法:

# Display command
$fullCommand = $MyInvocation.MyCommand.Name
$MyInvocation.BoundParameters.Keys | ForEach {
    $fullCommand += " -$($_) $($PSBoundParameters.Item($_))"
}
Write-Information $fullCommand -InformationAction Continue
由于未正确显示标志,因此无法正常工作

参数块:

[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
)
由于未正确显示标志,因此无法正常工作

如果“flags”指的是
[switch]
参数,则只需使用
将显式值紧密绑定到参数名称:

$fullCommand += " -$($_):$($PSBoundParameters.Item($_))"

但这并不能解决你所有的问题。
[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
}
现在我们有了一个很好的自包含实用程序,可以将带有简单参数的命令的
$MyInvocation
转换为可执行的引号,我们只需要一个命令来测试:

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

  Get-InvocationQuote $MyInvocation
}
我们得到的结果如下:

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
要测试quoted命令是否确实复制了原始调用参数,让我们测试连续QUOTE是否生成重复结果:

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
以以下解决方案作为补充:

  • 也支持数组类型的参数
  • 通过以下方式使用更为熟悉的语法:
    • 仅在必要时引用论点
    • 使用
      仅在必要时分隔参数名称和值,即仅用于
      [开关]
      -键入传递到
      $false
      的参数。
      • 表示
        -Switch
        参数,其隐含值为
        $true
        ,仅表示
        -Switch
        ,而不是表示
        -Switch:$true
注意

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

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

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

    • 顺便提一下:如果使用日期的字符串表示形式调用已编译的cmdlet(而不是在PowerShell中编写的代码),则解析会意外失败 区域性敏感-遗憾的是,由于向后兼容性问题,此不一致性将无法修复-请参阅


你能给我们看一下你函数的param块吗?如果你说的“flags”是指开关参数,那么把
-$($)$($PSBoundParameters.Item($)
改成
-$($):$($PSBoundParameters.Item($)
做得很好。请注意,如果使用
-f
,则只能获得值的区域性敏感表示形式,例如
[datetime]
;如果使用PowerShell的字符串插值或隐式字符串上下文(如
-join
操作),则表示是区域性不变的。是否约定使用与(此处为“测试引用”)(非反问)相同的大小写样式?@PeterMortensen是,我可以这么说——虽然文档和开发指南仅将其列为cmdlet的基本要求,但PascalCased
动词-名词
样式普遍适用于一般命令,并且这种做法被广泛接受。
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)
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