From 2179219c84d585170ce27598169c71feeadb7cc9 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 17 Sep 2026 14:27:16 -0700 Subject: [PATCH] Allow user to force the default implicit formatting to use `AutoSize` (#28018) When `$PSStyle.AutoSizeDefaultFormatting = $true`, the default implicit formatting applied by PowerShell (via `Out-Default`) will enforce `AutoSize` when the chosen formatting view is `table` or `wide`, similar to running `Format-Table -AutoSize` and `Format-Wide -AutoSize` explicitly. The `List` and `Complex` views formatting won't be buffered though -- they will continue to be streamed. This allows PowerShell to generate less verbose output when used by an agent. It also makes PowerShell works better with small-window terminal. --- .../PowerShellCore_format_ps1xml.cs | 1 + .../common/FormatViewGenerator.cs | 6 + .../FormatAndOutput/common/PSStyle.cs | 5 + .../format-default/format-default.cs | 15 ++ .../engine/Formatting/PSStyle.Tests.ps1 | 167 ++++++++++++++++++ 5 files changed, 194 insertions(+) diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 24d99d4dd..9feb5cbbf 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -2157,6 +2157,7 @@ namespace System.Management.Automation.Runspaces .AddItemScriptBlock(@"""$($_.StrikethroughOff)$($_.StrikethroughOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "StrikethroughOff") .AddItemScriptBlock(@"""$($_.Strikethrough)$($_.Strikethrough.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Strikethrough") .AddItemProperty(@"OutputRendering") + .AddItemProperty(@"AutoSizeDefaultFormatting") .AddItemScriptBlock(@"""$($_.Formatting.FormatAccent)$($_.Formatting.FormatAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FormatAccent") .AddItemScriptBlock(@"""$($_.Formatting.ErrorAccent)$($_.Formatting.ErrorAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.ErrorAccent") .AddItemScriptBlock(@"""$($_.Formatting.Error)$($_.Formatting.Error.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Error") diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index 963b5a0f8..38daef9cb 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -135,6 +135,12 @@ namespace Microsoft.PowerShell.Commands.Internal.Format private void InitializeAutoSize() { + // Only Table and Wide views support autosize. + if (this is not (TableViewGenerator or WideViewGenerator)) + { + return; + } + // check the autosize flag first if (parameters != null && parameters.autosize.HasValue) { diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs index 534f95411..0c3f88040 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -641,6 +641,11 @@ namespace System.Management.Automation /// public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; + /// + /// Gets or sets whether to force the default implicit formatting to use 'AutoSize'. + /// + public bool AutoSizeDefaultFormatting { get; set; } + /// /// Gets value to turn off all attributes. /// diff --git a/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs b/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs index 7d7a993d5..a33272a3a 100644 --- a/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs +++ b/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs @@ -20,5 +20,20 @@ namespace Microsoft.PowerShell.Commands { this.implementation = new InnerFormatShapeCommand(FormatShape.Undefined); } + + /// + /// Hook up the AutoSize override, if requested, before handing off to the + /// implementation. + /// + protected override void BeginProcessing() + { + if (PSStyle.Instance.AutoSizeDefaultFormatting) + { + var parameters = new FormattingCommandLineParameters { autosize = true }; + ((InnerFormatShapeCommand)this.implementation).SetCommandLineParameters(parameters); + } + + base.BeginProcessing(); + } } } diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 index 18baed17d..fd91e3f41 100644 --- a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 +++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 @@ -89,6 +89,10 @@ Describe 'Tests for $PSStyle automatic variable' -Tag 'CI' { $PSStyle.OutputRendering | Should -BeExactly 'Host' } + It '$PSStyle default view includes AutoSizeDefaultFormatting' { + $PSStyle | Out-String | Should -Match 'AutoSizeDefaultFormatting' + } + It '$PSStyle has correct defaults for style ' -TestCases (Get-TestCases $styleDefaults) { param($key, $value) @@ -603,3 +607,166 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } } + +Describe 'Default formatting AutoSize override' -Tag 'CI' { + BeforeAll { + $formatFile = Join-Path $TestDrive 'AutoSizeDefaultFormatting.format.ps1xml' + @' + + + + AutoSizeWide + + Test.AutoSizeWide + + + + + + Name + + + + + + + AutoSizeComplex + + Test.AutoSizeComplex + + + + + + Item + + + + + + + + +'@ | Set-Content -Path $formatFile + } + + It "Forces default table formatting to use 'AutoSize'" { + $oldOutputRendering = $PSStyle.OutputRendering + $oldAutoSizeDefaultFormatting = $PSStyle.AutoSizeDefaultFormatting + + try { + $PSStyle.OutputRendering = 'PlainText' + $PSStyle.AutoSizeDefaultFormatting = $false + $defaultOutput = Get-Command Import-Module | Out-String + $PSStyle.AutoSizeDefaultFormatting = $true + $autoSizedOutput = Get-Command Import-Module | Out-String + + $defaultOutput = $defaultOutput.Trim().Split("`n") + $autoSizedOutput = $autoSizedOutput.Trim().Split("`n") + + $defaultOutput.Count | Should -BeExactly $autoSizedOutput.Count + for ($i = 0; $i -lt $defaultOutput.Count; $i++) { + $defaultOutput[$i].Length | Should -BeGreaterThan $autoSizedOutput[$i].Length + $elements_1 = $defaultOutput[$i].Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) + $elements_2 = $autoSizedOutput[$i].Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) + $elements_1 -join ' ' | Should -BeExactly ($elements_2 -join ' ') + } + } + finally { + $PSStyle.AutoSizeDefaultFormatting = $oldAutoSizeDefaultFormatting + $PSStyle.OutputRendering = $oldOutputRendering + } + } + + It "Forces default wide formatting to use 'AutoSize'" { + $powerShell = [PowerShell]::Create() + + try { + $result = $powerShell.AddScript({ + param($Path) + + Update-FormatData -PrependPath $Path + $oldOutputRendering = $PSStyle.OutputRendering + $oldAutoSizeDefaultFormatting = $PSStyle.AutoSizeDefaultFormatting + + try { + $PSStyle.OutputRendering = 'PlainText' + $items = 1..6 | ForEach-Object { + [pscustomobject]@{ PSTypeName = 'Test.AutoSizeWide'; Name = "Item$_" } + } + + $PSStyle.AutoSizeDefaultFormatting = $false + $defaultOutput = $items | Out-String -Width 40 + $PSStyle.AutoSizeDefaultFormatting = $true + $autoSizedOutput = $items | Out-String -Width 40 + + [pscustomobject]@{ + DefaultOutput = $defaultOutput + AutoSizedOutput = $autoSizedOutput + } + } + finally { + $PSStyle.AutoSizeDefaultFormatting = $oldAutoSizeDefaultFormatting + $PSStyle.OutputRendering = $oldOutputRendering + } + }).AddArgument($formatFile).Invoke() + + $powerShell.HadErrors | Should -BeFalse + $defaultLines = $result.DefaultOutput.Trim().Split("`n") + $autoSizedLines = $result.AutoSizedOutput.Trim().Split("`n") + $defaultLines | Should -HaveCount 3 + $autoSizedLines | Should -HaveCount 1 + $autoSizedLines[0].Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries) | Should -HaveCount 6 + } + finally { + $powerShell.Dispose() + } + } + + It 'Does not buffer default formatting until pipeline completion' -TestCases @( + @{ ViewType = 'List' } + @{ ViewType = 'Complex' } + ) { + param($ViewType) + + $powerShell = [PowerShell]::Create() + + try { + $producerFinished = $powerShell.AddScript({ + param($Path, $ViewType) + + Update-FormatData -PrependPath $Path + $oldAutoSizeDefaultFormatting = $PSStyle.AutoSizeDefaultFormatting + + try { + $PSStyle.AutoSizeDefaultFormatting = $true + $inputObject = $ViewType -eq 'List' ? $PSStyle : [pscustomobject]@{ PSTypeName = 'Test.AutoSizeComplex'; Name = 'Item' } + + $script:producerFinished = $false + & { + $inputObject + $script:producerFinished = $true + } | + Out-String -Stream | + Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ## Ignore the newline written out by 'Out-String' + Select-Object -First 1 | + Out-Null + + $script:producerFinished + } + finally { + $PSStyle.AutoSizeDefaultFormatting = $oldAutoSizeDefaultFormatting + } + }).AddArgument($formatFile).AddArgument($ViewType).Invoke() + + $powerShell.HadErrors | Should -BeFalse + # When rendering streams, the first non-whitespace string reaches 'Select-Object' before '$script:producerFinished = $true' runs. 'Select-Object -First 1' then stops + # the upstream script, so that assignment is never evaluated and '$producerFinished' remains false. + # If rendering is buffered, 'Out-String' emits no non-whitespace payload until the upstream script finishes and the assignment runs, making '$producerFinished' true. + $producerFinished | Should -BeFalse + } + finally { + $powerShell.Dispose() + } + } +}