$MyInvocation 的替代品

Alternative to $MyInvocation

从我的 RMM (NinjaOne) 部署 PowerShell 脚本时,脚本是从 .bat(批处理文件)调用的。

示例:

@powershell -ExecutionPolicy Bypass -File "test.ps1" -stringParam "testing" -switchParam > "output.txt" 2>&1

我正在调用的脚本需要 PowerShell 7+,因此我需要使用当前参数调用 pwsh 来重新启动脚本。我计划通过以下方式完成此操作:

Invoke-Command { & pwsh -Command $MyInvocation.Line } -NoNewScope

不幸的是,当从批处理文件调用 PowerShell 脚本时,$MyInvocation.Line 没有 return 正确的结果。在这种情况下存在哪些替代方案?

备注:

测试脚本(从批处理调用):

Param(
  [string]$string,
  [switch]$switch
)

if (!($PSVersionTable.PSVersion.Major -ge 7)) {
  Write-Output "`n"
  Write-Output 'Attempting to restart in PowerShell 7'
  if(!$MyInvocation.Line) {
    Write-Output 'Parameters not carried over - cannot restart' 
    Exit 
  } else { Write-Output $MyInvocation.Line }
  Invoke-Command { & pwsh -Command $MyInvocation.Line } -NoNewScope # PowerShell 7
  Exit
}

Write-Output 'Parameters carried over:'
Write-Output $PSBoundParameters

Write-Output "`nSuccessfully restarted"

编辑: 我发现 $MyInvocation / $PSBoundParameters 未正确设置的原因是由于使用了 -File当我的 RMM 提供程序从 .bat 文件调用 PowerShell 脚本时,而不是 -Command。我建议他们实施此更改以解决问题。在他们这样做之前,我仍在寻找替代方案。

我会尝试将名为 powershell.bat 的文件放在您路径的早期(至少,在路径中比 C:\Windows\System32\WindowsPowerShell\v1.0\; 条目更早的目录中)和 assemble 适当的参数(我不知道 $myinvocationline 所需的结构 - 毫无疑问,它可以从传递给 powershell.bat 的参数中导出)。

我的想法是,这应该覆盖 powershell、re-assemble 位并将它们传送到 pwsh

将 RMM 提供商排除在外(他们的参与可能重要也可能无关紧要),以下 test.ps1 内容应该有效:

# Note: Place [CmdletBinding()] above param(...) to make
#       the script an *advanced* one, which then prevents passing
#       extra arguments that don't bind to declared parameters.
param(
  [string] $stringParam,
  [switch] $switchParam
)

# If invoked via powershell.exe, re-invoke via pwsh.exe
if ((Get-Process -Id $PID).Name -eq 'powershell') {
   # $PSCommandPath is the current script's full file path,
   # and @PSBoundParameters uses splatting to pass all 
   # arguments that were bound to declared parameters through.
   # Any extra arguments, if present, are passed through with @args
   pwsh -ExecutionPolicy Bypass -File $PSCommandPath @PSBoundParameters @args
   exit $LASTEXITCODE
}

# Getting here means that the file is being executed by pwsh.exe

# Print the arguments received:

if ($PSBoundParameters.Count) {
  "-- Bound parameters and their values:`n"
  # !! Because $PSBoundParameters causes table-formatted
  # !! output, synchronous output must be forced to work around a bug.
  # !! See notes below.  
  $PSBoundParameters | Out-Host
}

if ($args) {
  "`n-- Unbound (positional) arguments:`n"
  $args
}

exit 0

按照代码注释中的建议,将 [CmdletBinding()] 放在 param(...) 块上方,以使脚本成为 advanced 一个,在这种情况下传递额外的参数(那些不't bind to formally declared parameters) 被主动阻止(在这种情况下 $args 未定义)。

警告:

  • 注意需要管道 $PSBoundParametersOut-Host 以强制 同步 输出 (默认)table-formatted 表示其值。这同样适用于输出任何其他值,这些值会导致不基于预定义格式数据的隐式 Format-Table 格式($args,作为 array 其元素是 strings 不受影响)。 (请注意,虽然 Out-Host 输出通常不适合 data 来自 inside PowerShell 会话的输出,但它确实写入外部来电者的 stdout.)

  • 此需求源于 一个非常不幸的 output-timing 错误 ,存在于 Windows PowerShell 和当前的 PowerShell(核心)中版本,7.2.1;它是 and reported in GitHub issue #13985 中详细说明的行为的变体表现形式, 适用于此,因为 exit 在 300 毫秒内被调用。启动隐式 table-formatted 输出;如果您 省略 最后的 exit 0 语句,问题就不会出现。

另见:


至于你试过的:

  • $MyInvocation.Line 在通过 PowerShell 的 CLI 调用脚本时未定义 ;但是,反映 运行 脚本的完整文件路径的 automatic $PSCommandPath variable 定义的。

    • 即使定义了 $MyInvocation.Line,由于潜在的引用,它也不会启用 robust pass-through 原始参数问题和 - 当从 inside PowerShell 调用时 - 由于反映 unexpanded 参数。 (此外,该值将以可执行文件/脚本名称/路径开头,必须将其删除。)

    • 虽然 [Environment]::CommandLine 确实反映了 CLI 调用的 process 命令行(也以可执行文件名称/路径开头),但引用问题也在那里申请。

  • 此外,使用Invoke-Command for local invocations - see 几乎没有意义。

我把这行放在测试中。ps1:

$MyInvocation | Format-List -Property *

在 output.txt 中找到此内容:



MyCommand             : test.ps1
BoundParameters       : {}
UnboundArguments      : {-stringParam, testing, -switchParam}
ScriptLineNumber      : 0
OffsetInLine          : 0
HistoryId             : 1
ScriptName            : 
Line                  : 
PositionMessage       : 
PSScriptRoot          : 
PSCommandPath         : 
InvocationName        : D:\Temp\Whosebug087897\test.ps1
PipelineLength        : 2
PipelinePosition      : 1
ExpectingInput        : False
CommandOrigin         : Runspace
DisplayScriptPosition : 



然后在测试中尝试了这个。ps1:

[string]$MySelf = $MyInvocation.InvocationName
Write-Host "###$MySelf###"
[string[]]$Params = $MyInvocation.UnboundArguments
foreach ($Param in $Params) {
    Write-Host "Param: '$Param'"
}

并在 output.txt 中找到了这个:

###D:\Temp\Whosebug087897\test.ps1###
Param: '-stringParam'
Param: 'testing'
Param: '-switchParam'

然后在测试中尝试了这个。ps1:

[string]$Line = "$($MyInvocation.InvocationName) $($MyInvocation.UnboundArguments)"
Write-Host "$Line"

并在 output.txt 中找到了这个:

D:\Temp\Whosebug087897\test.ps1 -stringParam testing -switchParam

这会让你到达你需要去的地方吗?