请注意以下小型PowerShell脚本(我们称之为 a.ps1 ):
param([switch]$WithFormat)
[System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe")
"-1-"
if ($WithFormat)
{
@{a=$null} | Format-Table
}
"-2-"
$j = Start-Job { [System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe") }
"-3-"
Wait-Job $j
"-4-"
Receive-Job $j
"-5-"
Remove-Job $j
现在,允许我在没有任何参数的情况下运行一次,然后使用-WithFormat
开关:
PS C:\tmp\1> .\a.ps1
GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-1-
-2-
-3-
HasMoreData : True
StatusMessage :
Location : localhost
Command : [System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe")
JobStateInfo : Completed
Finished : System.Threading.ManualResetEvent
InstanceId : b70f261c-50f7-455f-9169-f200df78dc9d
Id : 168
Name : Job168
ChildJobs : {Job169}
PSBeginTime : 3/28/2015 10:32:10 PM
PSEndTime : 3/28/2015 10:32:12 PM
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
State : Completed
-4-
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-5-
PS C:\tmp\1> .\a.ps1 -WithFormat
GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-1-
Name Value
---- -----
a
-2-
-3-
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
170 Job170 BackgroundJob Completed True localhost [System.Reflection.As...
-4-
RunspaceId : e1a6cac3-80f8-4dd8-8f8b-f687d1dcc8a0
CodeBase : file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe
FullName : MSBuild, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
EntryPoint : Int32 Main()
DefinedTypes : {Microsoft.Build.Shared.AssemblyNameComparer, Microsoft.Build.Shared.CollectionHelpers, Microsoft.Build.Shared.DirectoryGetFiles, Microsoft.Build.Shared.GetDirectories...}
Evidence : {<System.Security.Policy.GacInstalled version="1"/>
, <StrongName version="1"
Key="002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E8
21C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293"
Name="MSBuild"
Version="4.0.0.0"/>
, <System.Security.Policy.Url version="1">
<Url>file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe</Url>
</System.Security.Policy.Url>
, <System.Security.Policy.Zone version="1">
<Zone>MyComputer</Zone>
</System.Security.Policy.Zone>
...}
PermissionSet : {}
SecurityRuleSet : Level2
ManifestModule : MSBuild.exe
ReflectionOnly : False
Location : C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
ImageRuntimeVersion : v4.0.30319
GlobalAssemblyCache : True
HostContext : 0
IsDynamic : False
EscapedCodeBase : file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe
ExportedTypes : {Microsoft.Build.CommandLine.MSBuildApp, Microsoft.Build.CommandLine.MSBuildApp+ExitType}
IsFullyTrusted : True
CustomAttributes : {[System.Diagnostics.DebuggableAttribute((System.Diagnostics.DebuggableAttribute+DebuggingModes)2)], [System.Runtime.CompilerServices.ExtensionAttribute()],
[System.Runtime.CompilerServices.InternalsVisibleToAttribute("MSBuild.Whidbey.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c8
34c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")],
[System.Runtime.CompilerServices.InternalsVisibleToAttribute("MSBuild.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921
eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]...}
Modules : {MSBuild.exe}
-5-
PS C:\tmp\1>
对于我的生活,我不明白为什么两次运行会产生如此截然不同的输出!
有什么想法吗?
修改
伙计们,请注意 -4 - 和 -5 - 行之间的不同输出。该输出对应于后台作业上下文中[System.Reflection.Assembly]::LoadFile
的调用,并指示当我使用-WithFormat
标志调用脚本时,将使用不同的安全策略在后台作业中加载程序集。这就是问题的实质 - 为什么在地球上使用Format-Table
会这样做?
EDIT2
看起来有一个错误,但更无辜,与安全策略无关。通过在我的示例中调用Format-Table
,Powershell的默认对象渲染概念变得搞砸了。
EDIT3
尝试了另一个剧本:
param([switch]$WithFormat,[switch]$WithJob)
$ScriptBlock = { dir "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" }
&$ScriptBlock
"-1-"
if ($WithFormat)
{
@{a=$null} | Format-Table
}
"-2-"
if ($WithJob)
{
$j = Start-Job $ScriptBlock
"-3-"
Wait-Job $j
"-4-"
Receive-Job $j
"-5-"
Remove-Job $j
}
else
{
&$ScriptBlock
}
使用不同的参数集运行它会告诉我,默认对象格式化确实很混乱。
P.S。
PS C:\tmp\1> $PSVersionTable
Name Value
---- -----
PSVersion 3.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.34209
BuildVersion 6.2.9200.17065
PSCompatibleVersions {1.0, 2.0, 3.0}
PSRemotingProtocolVersion 2.2
PS C:\tmp\1> [System.Environment]::OSVersion.Version
Major Minor Build Revision
----- ----- ----- --------
6 2 9200 0
PS C:\tmp\1>
答案 0 :(得分:2)
我将Out-String
与Format-*
cmdlet一起使用以避免混合输出中存在此类差异,即在您的情况下@{a=$null} | Format-Table | Out-String
。关于 Why 部分的答案很难,它是关于PowerShell格式化程序的内部细节。
答案 1 :(得分:2)
这与在输出流中有多个对象类型时PowerShell格式输出的方式有关。它与作业,程序集或安全策略无关。 PowerShell通过尝试在表中的输出流中显示所有对象的相同属性来格式化输出。但是,如果您开始混合具有不同属性的对象,它只是以列表格式开始编写这些对象属性。我不知道为什么,但PowerShell在遇到Format-Table
时会“重置”,并以下一个对象作为格式化输出的当前对象类型开始。也许这是一个错误,我不知道。
我们可以通过一个更简单的例子来演示这种行为。考虑这个输入
gps idle; gsv winrm; gps idle
给出了这个输出
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
0 0 0 4 0 0 Idle
Status : Stopped
Name : winrm
DisplayName : Windows Remote Management (WS-Management)
0 0 0 4 0 0 Idle
和此输入
gps idle; @{a=0} | ft; gsv winrm; gps idle
给出了这个输出
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
0 0 0 4 0 0 Idle
Name Value
---- -----
a 0
Status Name DisplayName
------ ---- -----------
Stopped winrm Windows Remote Management (WS-Manag...
Id : 0
Handles : 0
CPU :
Name : Idle
这两者之间的输出差别很大,即使唯一的区别是对Format-Table
的额外调用。
在第一种情况下,PowerShell会看到Process
并开始格式化输出。它获得一个ServiceController
对象,该对象没有相同的属性,因此它将其作为列表输出。然后它获取另一个进程对象并继续该表(注意没有标题)。
在第二种情况下,调用Format-Table
似乎“重置”PowerShell正在考虑作为输出的当前对象。当它收到第二个Process对象时,它与ServiceController
没有相同的属性,所以它只是以列表格式显示它。
如果您的目标是以某种方式格式化输出,那么将所有内容驱动到Out-String
或Format-Table
肯定会有效,但如果您希望能够将输出作为对象使用,那么这可能不是解决方案