From 5f28df120ab95ebb8871d19d3bdb8046c5b9817c Mon Sep 17 00:00:00 2001 From: Michael Klement Date: Tue, 7 Apr 2020 20:37:13 -0400 Subject: [PATCH] In local invocations do not require `-PowerShellVersion 5.1` for `Get-FormatData` in order to see all format data. (#11270) --- .../common/GetFormatDataCommand.cs | 26 +++++++- .../engine/Utils.cs | 20 ++++++ .../Export-FormatData.Tests.ps1 | 5 +- .../Get-FormatData.Tests.ps1 | 62 +++++++++++++++---- 4 files changed, 97 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs index ae9e83bce1..09a6a88693 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/common/GetFormatDataCommand.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Management.Automation; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using Microsoft.PowerShell.Commands.Internal.Format; @@ -97,9 +98,28 @@ namespace Microsoft.PowerShell.Commands /// protected override void ProcessRecord() { - bool writeOldWay = PowerShellVersion == null || - PowerShellVersion.Major < 5 || - (PowerShellVersion.Major == 5 && PowerShellVersion.Minor < 1); + // Remoting detection: + // * Automatic variable $PSSenderInfo is defined in true remoting contexts as well as in background jobs. + // * $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion contains the client version, as a [version] instance. + // Note: Even though $PSVersionTable.PSVersion is of type [semver] in PowerShell 6+, it is of type [version] here, + // presumably because only the latter type deserializes type-faithfully. + var clientVersion = PowerShellVersion; + PSSenderInfo remotingClientInfo = GetVariableValue("PSSenderInfo") as PSSenderInfo; + if (clientVersion == null && remotingClientInfo != null) + { + clientVersion = PSObject.Base((PSObject.Base(remotingClientInfo.ApplicationArguments["PSVersionTable"]) as PSPrimitiveDictionary)?["PSVersion"]) as Version; + } + + // During remoting, remain compatible with v5.0- clients by default. + // Passing a -PowerShellVersion argument allows overriding the client version. + bool writeOldWay = + (remotingClientInfo != null && clientVersion == null) // To be safe: Remoting client version could unexpectedly not be determined. + || + (clientVersion != null + && + (clientVersion.Major < 5 + || + (clientVersion.Major == 5 && clientVersion.Minor < 1))); TypeInfoDataBase db = this.Context.FormatDBManager.Database; diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 6c12255199..a188a091e3 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; using System.Management.Automation.Language; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Management.Automation.Security; using System.Numerics; @@ -2098,6 +2099,25 @@ namespace System.Management.Automation.Internal { return Utils.TryRunAsImplicitBatch(commandPipeline, runspace); } + + /// + /// Constructs a custom PSSenderInfo instance that can be assigned to $PSSenderInfo + /// in order to simulate a remoting session with respect to the $PSSenderInfo.ConnectionString (connection URL) + /// and $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion (the remoting client's PowerShell version). + /// See Get-FormatDataTest.ps1. + /// + /// The connection URL to reflect in the returned instance's ConnectionString property. + /// The version number to report as the remoting client's PowerShell version. + /// The newly constructed custom PSSenderInfo instance. + public static PSSenderInfo GetCustomPSSenderInfo(string url, Version clientVersion) + { + var dummyPrincipal = new PSPrincipal(new PSIdentity("none", true, "someuser", null), null); + var pssi = new PSSenderInfo(dummyPrincipal, url); + pssi.ApplicationArguments = new PSPrimitiveDictionary(); + pssi.ApplicationArguments.Add("PSVersionTable", new PSObject(new PSPrimitiveDictionary())); + ((PSPrimitiveDictionary)PSObject.Base(pssi.ApplicationArguments["PSVersionTable"])).Add("PSVersion", new PSObject(clientVersion)); + return pssi; + } } /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 index 12af02aa27..42b69c6fa1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 @@ -2,7 +2,8 @@ # Licensed under the MIT License. Describe "Export-FormatData" -Tags "CI" { BeforeAll { - $fd = Get-FormatData + $clientVersion = '5.0' # Preliminarily preserve the original test semantics in place before https://github.com/PowerShell/PowerShell/pull/11270 + $fd = Get-FormatData -PowerShellVersion $clientVersion $testOutput = Join-Path -Path $TestDrive -ChildPath "outputfile" } @@ -23,7 +24,7 @@ Describe "Export-FormatData" -Tags "CI" { $runspace.Open() $runspace.CreatePipeline("Update-FormatData -AppendPath $TESTDRIVE\allformat.ps1xml").Invoke() - $actualAllFormat = $runspace.CreatePipeline("Get-FormatData -TypeName *").Invoke() + $actualAllFormat = $runspace.CreatePipeline("Get-FormatData -PowerShellVersion $clientVersion").Invoke() $fd.Count | Should -Be $actualAllFormat.Count Compare-Object $fd $actualAllFormat | Should -Be $null diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1 index baf349e107..04bffe8df8 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1 @@ -3,24 +3,64 @@ Describe "Get-FormatData" -Tags "CI" { Context "Check return type of Get-FormatData" { - It "Should return an object[] as the return type" { $result = Get-FormatData - ,$result | Should -BeOfType System.Object[] + , $result | Should -BeOfType "System.Object[]" } } - It "Can get format data requiring '-PowerShellVersion 5.1'" { - $format = Get-FormatData System.IO.FileInfo -PowerShellVersion 5.1 - $format.TypeNames | Should -HaveCount 2 - $format.TypeNames[0] | Should -BeExactly "System.IO.DirectoryInfo" - $format.TypeNames[1] | Should -BeExactly "System.IO.FileInfo" + # Note: Format data for [System.IO.FileInfo] (among others) is not to be + # returned to v5.0- remoting clients. - $isUnixStatEnabled = $EnabledExperimentalFeatures -contains 'PSUnixFileStat' - $format.FormatViewDefinition | Should -HaveCount ( $isUnixStatEnabled ? 5 : 4) + Context "Local use: Can get format data requiring v5.1+ by default" { + BeforeAll { + $cmds = @( + @{ cmd = { Get-FormatData System.IO.FileInfo } } + @{ cmd = { (Get-FormatData System.IO.FileInfo &) | Receive-Job -Wait -AutoRemoveJob } } + ) + } + It "Can get format data requiring v5.1+ with " -TestCases $cmds { + param([scriptblock] $cmd) + $format = & $cmd + $format.TypeNames | Should -HaveCount 2 + $format.TypeNames[0] | Should -BeExactly "System.IO.DirectoryInfo" + $format.TypeNames[1] | Should -BeExactly "System.IO.FileInfo" + + $isUnixStatEnabled = $EnabledExperimentalFeatures -contains 'PSUnixFileStat' + $format.FormatViewDefinition | Should -HaveCount ($isUnixStatEnabled ? 5 : 4) + } } - It "Should return nothing for format data requiring '-PowerShellVersion 5.1' and not provided" { - Get-FormatData System.IO.FileInfo | Should -BeNullOrEmpty + Context "Can override client version with -PowerShellVersion" { + BeforeAll { + $cmds = @( + @{ shouldBeNull = $true; cmd = { Get-FormatData System.IO.FileInfo -PowerShellVersion 5.0 } } + @{ shouldBeNull = $false; cmd = { Get-FormatData System.IO.FileInfo -PowerShellVersion 5.1 } } + @{ shouldBeNull = $false; cmd = { $PSSenderInfo = [System.Management.Automation.Internal.InternalTestHooks]::GetCustomPSSenderInfo('foo', [version] '5.0'); Get-FormatData System.IO.FileInfo -PowerShellVersion 5.1 } } + ) + } + It " should return for a null-output test" -TestCases $cmds { + param([scriptblock] $cmd, [bool] $shouldBeNull) + $null -eq $(& $cmd) | Should -Be $shouldBeNull + } } + + Context "Remote use: By default, don't get format data requiring v5.1+ for v5.0- clients" { + BeforeAll { + # Simulated PSSenderInfo instances for various PowerShell versions. + $pssiV50 = [System.Management.Automation.Internal.InternalTestHooks]::GetCustomPSSenderInfo('foo', [version] '5.0') + $pssiV51 = [System.Management.Automation.Internal.InternalTestHooks]::GetCustomPSSenderInfo('foo', [version] '5.1') + $pssiV70 = [System.Management.Automation.Internal.InternalTestHooks]::GetCustomPSSenderInfo('foo', [version] '7.0') + $cmds = @( + @{ shouldBeNull = $true; cmd = { $PSSenderInfo = $pssiV50; Get-FormatData System.IO.FileInfo } } + @{ shouldBeNull = $false; cmd = { $PSSenderInfo = $pssiV51; Get-FormatData System.IO.FileInfo } } + @{ shouldBeNull = $false; cmd = { $PSSenderInfo = $pssiV70; Get-FormatData System.IO.FileInfo } } + ) + } + It "When remoting, should return for a null-output test" -TestCases $cmds { + param([scriptblock] $cmd, [bool] $shouldBeNull) + $null -eq $(& $cmd) | Should -Be $shouldBeNull + } + } + }