Powershell 如何显示使用扩展参数调用的命令行
我发现,在PowerShell(.ps1)脚本中,我可以使用以下命令显示启动脚本的命令行:Powershell 如何显示使用扩展参数调用的命令行,powershell,Powershell,我发现,在PowerShell(.ps1)脚本中,我可以使用以下命令显示启动脚本的命令行: Write-Information $MyInvocation.Line -InformationAction Continue 但是,当我在命令行上传递变量时,使用上面的命令显示变量时,这些变量不会展开 为了方便起见,我需要对它们进行扩展,因为我需要任何人都能够在没有定义变量的情况下复制粘贴命令行 我试过这样的方法: # Display command $fullCommand = $MyInvoca
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]
实例由其区域性不变字符串表示形式表示,因为PowerShell在除[datetimeoffset]
以外的字符串操作中使用不变区域性进行格式化(请参阅背景)。因此,无论当前的文化是什么,表示都应该起作用-f
- 然而,这些表示仅限于秒的粒度(例如,
);如果需要保留亚秒值,请使用12/31/2020 10:57:35
字符串化,如Mathias的回答所示;为了避免使用默认字符串化的月首格式(您也可以选择一种较短的自定义格式作为往返格式.ToString('o')
)的替代方案),这可能更可取'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