为什么运行pwsh.exe时GetCommandLineArgs返回DLL?

问题描述

我正在运行以下命令,作为测试代码的一部分,试图确定脚本是否正在交互式会话(即使用CLI)中运行,还是从无控制台远程会话(SSH,SCP,FTP)中运行。

因此,当使用[Environment]::GetCommandLineArgs()时,在 Powershell Core pwsh.exe)CLI会话中,我得到: C:\Program Files\PowerShell\6\pwsh.dll 。这是令人惊讶的,因为我本来希望得到的是 pwsh.exe 而不是DLL。

为什么我得到DLL而不是EXE?
发生了什么事?

解决方法

这是一个已知问题,截至.NET 5.0(基于PowerShell Core 7.1的构建)仍未得到解决(在撰写本文时均处于预览状态,但是我不希望这修复,以便及时发布这些版本。

请参见GitHub issue #11305

但是,有一个解决方法实际上是 一般首选的:

[System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName

# Shorter,but slower equivalent (works only from PowerShell):
#  (Get-Process -Id $PID).MainModule.FileName

[System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName优于[Environment]::GetCommandLineArgs()[0]的原因有两个:

  • 以前的始终报告了完整路径。

  • 至少在.NET Core 3.1之前,后者可以报告临时位置,即对于将自己提取到幕后此类位置的单文件.NET Core可执行文件。 / p>

请注意, .NET 5.0+将具有专用的[Environment]::ProcessPath属性,它们的性能也会更好-请参见GitHub PR #42768


如果要获得[Environment]::GetCommandLineArgs()修正的版本-即,将实际的可执行文件存储在索引0中,并提供真实的参数在其余元素中:

# Get the original array...
# Note: Modifying the array directly works,but you can enclose 
#       the call in @(...) in order to create a *copy* of it.
$cmdLineArgs = [Environment]::GetCommandLineArgs()
# ... and fix the entry for the executable (index 0)
$cmdLineArgs[0] = [Diagnostics.Process]::GetCurrentProcess().MainModule.FileName

通过cmd.exe调用PowerShell CLI进行演示:

C:>pwsh -noprofile -c "$a=[Environment]::GetCommandLineArgs(); $a[0]=[Diagnostics.Process]::GetCurrentProcess().MainModule.FileName; $a"
C:\Program Files\PowerShell\6\pwsh.exe
-noprofile
-c
$a=[Environment]::GetCommandLineArgs(); $a[0]=[Diagnostics.Process]::GetCurrentProcess().MainModule.FileName; $a