diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs index 5ef046b61a0..9399ee5fc2a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs @@ -743,6 +743,42 @@ private void ProcessCachedGroupOnWide(WideViewHeaderInfo wvhi, List + /// In cases like implicit remoting, there is no console so reading the console width results in an exception. + /// Instead of handling exception every time we cache this value to increase performance. + /// + static private bool _noConsole = false; + + /// + /// Tables and Wides need to use spaces for padding to maintain table look even if console window is resized. + /// For all other output, we use int.MaxValue if the user didn't explicitly specify a width. + /// If we detect that int.MaxValue is used, first we try to get the current console window width. + /// However, if we can't read that (for example, implicit remoting has no console window), we default + /// to something reasonable: 120 columns. + /// + static private int GetConsoleWindowWidth(int columnNumber) + { + const int defaultConsoleWidth = 120; + + if (columnNumber == int.MaxValue) + { + if (_noConsole) + { + return defaultConsoleWidth; + } + try + { + return Console.WindowWidth; + } + catch + { + _noConsole = true; + return defaultConsoleWidth; + } + } + return columnNumber; + } + /// /// base class for all the formatting hints /// @@ -910,23 +946,7 @@ internal override void Initialize() columnWidthsHint = tableHint.columnWidths; } - int columnsOnTheScreen = this.InnerCommand._lo.ColumnNumber; - // Tables need to use spaces for padding to maintain table look even if console window is resized. - // For all other output, we use int.MaxValue if the user didn't explicitly specify a width. - // If we detect that int.MaxValue is used, first we try to get the current console window width. - // However, if we can't read that (for example, implicit remoting has no console window), we default - // to something reasonable: 120 columns. - if (columnsOnTheScreen == int.MaxValue) - { - try - { - columnsOnTheScreen = Console.WindowWidth; - } - catch - { - columnsOnTheScreen = 120; - } - } + int columnsOnTheScreen = GetConsoleWindowWidth(this.InnerCommand._lo.ColumnNumber); int columns = this.CurrentTableHeaderInfo.tableColumnInfoList.Count; if (columns == 0) @@ -1132,10 +1152,12 @@ internal override void Initialize() // get the header info and the view hint WideFormattingHint hint = this.InnerCommand.RetrieveFormattingHint() as WideFormattingHint; + int columnsOnTheScreen = GetConsoleWindowWidth(this.InnerCommand._lo.ColumnNumber); + // give a preference to the hint, if there if (hint != null && hint.maxWidth > 0) { - itemsPerRow = TableWriter.ComputeWideViewBestItemsPerRowFit(hint.maxWidth, this.InnerCommand._lo.ColumnNumber); + itemsPerRow = TableWriter.ComputeWideViewBestItemsPerRowFit(hint.maxWidth, columnsOnTheScreen); } else if (this.CurrentWideHeaderInfo.columns > 0) { @@ -1155,7 +1177,7 @@ internal override void Initialize() alignment[k] = TextAlignment.Left; } - this.Writer.Initialize(0, this.InnerCommand._lo.ColumnNumber, columnWidths, alignment, false); + this.Writer.Initialize(0, columnsOnTheScreen, columnWidths, alignment, false); } /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 index 8b805c08870..f10f9f38649 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 @@ -1,33 +1,32 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. Describe "Format-Wide" -Tags "CI" { - - It "Should have the same output between the alias and the unaliased function" { - $nonaliased = Get-ChildItem | Format-Wide - $aliased = Get-ChildItem | fw - - $($nonaliased | Out-String).CompareTo($($aliased | Out-String)) | Should -Be 0 + BeforeAll { + 1..2 | ForEach-Object { New-Item -Path ("TestDrive:\Testdir{0:00}" -f $_) -ItemType Directory } + 1..2 | ForEach-Object { New-Item -Path ("TestDrive:\TestFile{0:00}.txt" -f $_) -ItemType File } + $pathList = Get-ChildItem $TestDrive } It "Should be able to specify the columns in output using the column switch" { - { Get-ChildItem | Format-Wide -Column 3 } | Should -Not -Throw + { $pathList | Format-Wide -Column 3 } | Should -Not -Throw } It "Should be able to use the autosize switch" { - { Get-ChildItem | Format-Wide -Autosize } | Should -Not -Throw + { $pathList | Format-Wide -Autosize } | Should -Not -Throw + { $pathList | Format-Wide -Autosize | Out-String } | Should -Not -Throw } It "Should be able to take inputobject instead of pipe" { - { Format-Wide -InputObject $(Get-ChildItem) } | Should -Not -Throw + { Format-Wide -InputObject $pathList } | Should -Not -Throw } It "Should be able to use the property switch" { - { Format-Wide -InputObject $(Get-ChildItem) -Property Mode } | Should -Not -Throw + { Format-Wide -InputObject $pathList -Property Mode } | Should -Not -Throw } It "Should throw an error when property switch and view switch are used together" { - { Format-Wide -InputObject $(Get-ChildItem) -Property CreationTime -View aoeu } | - Should -Throw -ErrorId "FormatCannotSpecifyViewAndProperty,Microsoft.PowerShell.Commands.FormatWideCommand" + { Format-Wide -InputObject $pathList -Property CreationTime -View aoeu } | + Should -Throw -ErrorId "FormatCannotSpecifyViewAndProperty,Microsoft.PowerShell.Commands.FormatWideCommand" } It "Should throw and suggest proper input when view is used with invalid input without the property switch" { @@ -36,74 +35,74 @@ Describe "Format-Wide" -Tags "CI" { } Describe "Format-Wide DRT basic functionality" -Tags "CI" { - It "Format-Wide with array should work" { - $al = (0..255) - $info = @{} - $info.array = $al - $result = $info | Format-Wide | Out-String - $result | Should -Match "array" - } + It "Format-Wide with array should work" { + $al = (0..255) + $info = @{} + $info.array = $al + $result = $info | Format-Wide | Out-String + $result | Should -Match "array" + } - It "Format-Wide with No Objects for End-To-End should work"{ - $p = @{} - $result = $p | Format-Wide | Out-String - $result | Should -BeNullOrEmpty - } + It "Format-Wide with No Objects for End-To-End should work" { + $p = @{} + $result = $p | Format-Wide | Out-String + $result | Should -BeNullOrEmpty + } - It "Format-Wide with Null Objects for End-To-End should work"{ - $p = $null - $result = $p | Format-Wide | Out-String - $result | Should -BeNullOrEmpty - } + It "Format-Wide with Null Objects for End-To-End should work" { + $p = $null + $result = $p | Format-Wide | Out-String + $result | Should -BeNullOrEmpty + } - It "Format-Wide with single line string for End-To-End should work"{ - $p = "single line string" - $result = $p | Format-Wide | Out-String - $result | Should -Match $p - } + It "Format-Wide with single line string for End-To-End should work" { + $p = "single line string" + $result = $p | Format-Wide | Out-String + $result | Should -Match $p + } - It "Format-Wide with multiple line string for End-To-End should work"{ - $p = "Line1\nLine2" - $result = $p | Format-Wide | Out-String - $result | Should -Match "Line1" - $result | Should -Match "Line2" - } + It "Format-Wide with multiple line string for End-To-End should work" { + $p = "Line1\nLine2" + $result = $p | Format-Wide | Out-String + $result | Should -Match "Line1" + $result | Should -Match "Line2" + } - It "Format-Wide with string sequence for End-To-End should work"{ - $p = "Line1","Line2" - $result = $p |Format-Wide | Out-String - $result | Should -Match "Line1" - $result | Should -Match "Line2" - } + It "Format-Wide with string sequence for End-To-End should work" { + $p = "Line1", "Line2" + $result = $p |Format-Wide | Out-String + $result | Should -Match "Line1" + $result | Should -Match "Line2" + } - It "Format-Wide with complex object for End-To-End should work" { - Add-Type -TypeDefinition "public enum MyDayOfWeek{Sun,Mon,Tue,Wed,Thu,Fri,Sat}" - $eto = New-Object MyDayOfWeek - $info = @{} - $info.intArray = 1,2,3,4 - $info.arrayList = "string1","string2" - $info.enumerable = [MyDayOfWeek]$eto - $info.enumerableTestObject = $eto - $result = $info|Format-Wide|Out-String - $result | Should -Match "intArray" - $result | Should -Match "arrayList" - $result | Should -Match "enumerable" - $result | Should -Match "enumerableTestObject" - } + It "Format-Wide with complex object for End-To-End should work" { + Add-Type -TypeDefinition "public enum MyDayOfWeek{Sun,Mon,Tue,Wed,Thu,Fri,Sat}" + $eto = New-Object MyDayOfWeek + $info = @{} + $info.intArray = 1, 2, 3, 4 + $info.arrayList = "string1", "string2" + $info.enumerable = [MyDayOfWeek]$eto + $info.enumerableTestObject = $eto + $result = $info|Format-Wide|Out-String + $result | Should -Match "intArray" + $result | Should -Match "arrayList" + $result | Should -Match "enumerable" + $result | Should -Match "enumerableTestObject" + } - It "Format-Wide with multiple same class object with grouping should work"{ - Add-Type -TypeDefinition "public class TestGroupingClass{public TestGroupingClass(string name,int length){Name = name;Length = length;}public string Name;public int Length;public string GroupingKey;}" - $testobject1 = [TestGroupingClass]::New('name1',1) - $testobject1.GroupingKey = "foo" - $testobject2 = [TestGroupingClass]::New('name2',2) - $testobject1.GroupingKey = "bar" - $testobject3 = [TestGroupingClass]::New('name3',3) - $testobject1.GroupingKey = "bar" - $testobjects = @($testobject1,$testobject2,$testobject3) - $result = $testobjects|Format-Wide -GroupBy GroupingKey|Out-String - $result | Should -Match "GroupingKey: bar" - $result | Should -Match "name1" - $result | Should -Match " GroupingKey:" - $result | Should -Match "name2\s+name3" - } + It "Format-Wide with multiple same class object with grouping should work" { + Add-Type -TypeDefinition "public class TestGroupingClass{public TestGroupingClass(string name,int length){Name = name;Length = length;}public string Name;public int Length;public string GroupingKey;}" + $testobject1 = [TestGroupingClass]::New('name1', 1) + $testobject1.GroupingKey = "foo" + $testobject2 = [TestGroupingClass]::New('name2', 2) + $testobject1.GroupingKey = "bar" + $testobject3 = [TestGroupingClass]::New('name3', 3) + $testobject1.GroupingKey = "bar" + $testobjects = @($testobject1, $testobject2, $testobject3) + $result = $testobjects|Format-Wide -GroupBy GroupingKey|Out-String + $result | Should -Match "GroupingKey: bar" + $result | Should -Match "name1" + $result | Should -Match " GroupingKey:" + $result | Should -Match "name2\s+name3" + } }