有人可以解释为什么以下微小的PowerShell脚本的行为与它一样吗?

时间:2015-03-29 02:38:48

标签: powershell

请注意以下小型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>

2 个答案:

答案 0 :(得分:2)

我将Out-StringFormat-* 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-StringFormat-Table肯定会有效,但如果您希望能够将输出作为对象使用,那么这可能不是解决方案