Skip to content

Add -ExcludeProperty parameter to Format-* cmdlets#26514

Merged
iSazonov merged 11 commits into
PowerShell:masterfrom
yotsuda:issue-25043-format-table
Dec 5, 2025
Merged

Add -ExcludeProperty parameter to Format-* cmdlets#26514
iSazonov merged 11 commits into
PowerShell:masterfrom
yotsuda:issue-25043-format-table

Conversation

@yotsuda
Copy link
Copy Markdown
Contributor

@yotsuda yotsuda commented Nov 23, 2025

PR Summary

Add -ExcludeProperty parameter to all Format-* cmdlets (Format-Table, Format-List, Format-Wide, and Format-Custom).

PR Context

Implements #25043

The -ExcludeProperty parameter allows users to exclude specific properties from formatting output without requiring an intermediate Select-Object call, providing consistency with Select-Object's ExcludeProperty functionality.

Before this PR:

# Users had to use Select-Object to exclude properties
$obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' }
$obj | Select-Object -ExcludeProperty Age | Format-Table

After this PR:

# Can now exclude properties directly in all Format-* cmdlets
$obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' }
$obj | Format-Table -ExcludeProperty Age

PR Checklist

Description

Implementation Overview

This PR implements the -ExcludeProperty parameter for all four Format-* cmdlets across 2 commits:

  • Commit 1 (1368c4d): Format-Table & Format-List
  • Commit 2 (36939d8): Format-Wide & Format-Custom

Files Changed: 14 files (10 product code, 4 test files)

Key Features

  1. Wildcard Support: Supports wildcard patterns (e.g., -ExcludeProperty Prop*)
  2. Auto-Property Expansion: When used without -Property, automatically applies -Property *
  3. Conflict Detection: Prevents usage with -View parameter
  4. Consistent Behavior: Matches Select-Object -ExcludeProperty functionality
  5. Case-Insensitive Matching: Uses WildcardOptions.IgnoreCase

Cmdlet Behaviors

  • Format-Table & Format-List: Display all properties except excluded ones
  • Format-Wide: Display first remaining property after exclusion
  • Format-Custom: Display all remaining properties in custom format

Usage Examples

$obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' }

# Basic exclusion
$obj | Format-Table -ExcludeProperty Age
# Output: Name, City columns

# Wildcard patterns
Get-Process | Format-List -ExcludeProperty *Memory*, *Time*
# Excludes all properties containing 'Memory' or 'Time'

# Format-Wide shows first remaining property
$obj | Format-Wide -ExcludeProperty Name
# Output: 30 (Age value)

# Error case: conflict with -View
Get-Process | Format-Table -View Priority -ExcludeProperty Name
# Error: FormatCannotSpecifyViewAndProperty

Technical Implementation

  • Reusable Design: PSPropertyExpressionFilter class defined once in BaseFormattingCommand.cs, reused by all four cmdlets for wildcard pattern matching
  • Consistent Filtering: Same LINQ-based filtering logic (activeAssociationList.Where()) applied across all view generators
  • Tab Completion: Added completion support via CompletionCompleters.cs
  • Auto-Expansion: Automatically applies -Property * when -ExcludeProperty is used without -Property
  • Performance: Minimal impact (filtering only when parameter is specified)
  • Code Cleanup: Removed unused inputParameters field during implementation

Testing

Test Coverage:

  • Added 20 new tests (5 per cmdlet)
  • All 184 tests pass (184 passed, 0 failed)
  • Tests cover: basic exclusion, wildcard patterns, auto-expansion, property combination, multiple exclusions

Test Results:

Tests Passed: 184, Failed: 0, Skipped: 0, Pending: 3

Breakdown by cmdlet:
- Format-Table: 61 tests (56 existing + 5 new)
- Format-List: 28 tests (23 existing + 5 new)
- Format-Wide: 19 tests (14 existing + 5 new)
- Format-Custom: 16 tests (11 existing + 5 new)

Additional Notes

No Breaking Changes

  • All existing functionality remains unchanged
  • New parameter is optional
  • Fully backward compatible

Implementation Quality

  • Consistent naming with Select-Object -ExcludeProperty
  • Uses existing error reporting infrastructure
  • No BOM, proper file endings, no trailing whitespace
  • Comprehensive test coverage

Implements PowerShell#25043 for Format-Table and Format-List cmdlets.

Changes:
- Add PSPropertyExpressionFilter class for wildcard pattern matching
- Add -ExcludeProperty parameter to OuterFormatShapeCommandBase
- Implement property filtering in TableViewGenerator and ListViewGenerator
- Add tab completion support for -ExcludeProperty parameter
- Add conflict check for -View and -ExcludeProperty parameters
- Auto-apply '-Property *' when -ExcludeProperty is used without -Property
- Change ListViewGenerator to use 'parameters' instead of 'inputParameters'

Test Results:
- Format-Table: 61 tests passed (56 existing + 5 new)
- Format-List: 28 tests passed (23 existing + 5 new)
- All Format-* tests: 174 tests passed

Note: Format-Wide and Format-Custom will be implemented in subsequent commits.
Completes the implementation of ExcludeProperty parameter for all
Format-* cmdlets (Table, List, Wide, Custom) to provide consistency
with Select-Object's ExcludeProperty functionality.

Changes:
- Add ExcludeProperty parameter to Format-Wide and Format-Custom cmdlets
- Implement property filtering in FormatViewGenerator_Wide and FormatViewGenerator_Complex
- Add conflict check for -View and -ExcludeProperty parameters
- Auto-apply '-Property *' when -ExcludeProperty is used without -Property
- Change to use 'parameters' instead of 'inputParameters' for consistency
- Remove unused 'inputParameters' field from base class
- Add comprehensive test coverage (5 tests per cmdlet)

Test Results:
- All Format-* tests: 184 passed, 0 failed
- Format-Wide: 19 tests (14 existing + 5 new)
- Format-Custom: 5 new tests added
@yotsuda yotsuda force-pushed the issue-25043-format-table branch from 36939d8 to c7e04d8 Compare November 23, 2025 10:34
- Fix XML documentation to follow 'Gets or sets' convention
- Add blank lines before single-line comments
- Complete AutoSize property documentation summary
@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Nov 24, 2025
@iSazonov iSazonov requested a review from Copilot November 24, 2025 05:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the -ExcludeProperty parameter to all four Format-* cmdlets (Format-Table, Format-List, Format-Wide, and Format-Custom), providing a convenient way to exclude specific properties from formatted output without requiring an intermediate Select-Object call. The implementation follows a consistent pattern across all cmdlets, using wildcard pattern matching and automatically expanding to -Property * when used without an explicit -Property parameter.

Key Changes

  • Introduced PSPropertyExpressionFilter class for wildcard-based property filtering
  • Added -ExcludeProperty parameter to all Format-* cmdlets with proper validation and conflict detection
  • Integrated tab completion support for the new parameter
  • Comprehensive test coverage with 20 new tests (5 per cmdlet)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
BaseFormattingCommand.cs Defines PSPropertyExpressionFilter class for wildcard matching and adds -ExcludeProperty parameter to base cmdlet classes
BaseFormattingCommandParameters.cs Adds excludePropertyFilter field to store the filter in command line parameters
Format-Wide.cs Implements -ExcludeProperty parameter for Format-Wide cmdlet with auto-expansion logic
Format-Object.cs Implements -ExcludeProperty parameter for Format-Custom cmdlet with auto-expansion logic
FormatViewGenerator.cs Removes unused inputParameters field from base ViewGenerator class
FormatViewGenerator_Table.cs Applies exclude filter at all property expansion points in table view generation
FormatViewGenerator_List.cs Applies exclude filter after property setup; changes inputParameters to parameters
FormatViewGenerator_Wide.cs Applies exclude filter at all property expansion points; removes unused inputParameters field
FormatViewGenerator_Complex.cs Applies exclude filter in object display logic; refactors parameter handling
CompletionCompleters.cs Adds tab completion support for -ExcludeProperty parameter on all Format-* cmdlets
Format-Table.Tests.ps1 Adds 5 test cases covering basic exclusion, wildcards, auto-expansion, property combination, and multiple exclusions
Format-List.Tests.ps1 Adds 5 test cases mirroring Format-Table coverage
Format-Wide.Tests.ps1 Adds 5 test cases verifying first-property-after-exclusion behavior
Format-Custom.Tests.ps1 Adds 5 test cases with custom format-specific assertions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs Outdated
Comment thread test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 Outdated
Comment thread test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs Outdated
ParameterProcessor processor = new ParameterProcessor(def);
TerminatingErrorContext invocationContext = new TerminatingErrorContext(this);

parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for the PR.
We have huge places in our code base where we allocate such temp arrays. We could review this and use modern C# feature params ReadOnlySpan<YYY> to pass arguments in methods without allocation.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs Outdated
Comment thread src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs Outdated
/// <summary>
/// Filter for excluding properties from formatting.
/// </summary>
internal PSPropertyExpressionFilter excludePropertyFilter = null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question about the design. We don't keep property filter but only exclude filter. Is this really necessary? Looking how ApplyExcludePropertyFilter is inserted a question is - wouldn't it be more correct to apply the filter exactly at the time of adding new property in the list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, storing the filter is necessary to pass it from the cmdlet to ViewGenerator. Here's the class structure:

Format-Table cmdlet
    └── InnerFormatShapeCommand
            └── _viewManager: FormatViewManager
                    └── _viewGenerator: ViewGenerator (TableViewGenerator, etc.)
                            └── activeAssociationList (property list)
  • ExcludeProperty parameter is defined in the cmdlet
  • Property list is built in ViewGenerator.Initialize() using the input object (for wildcard expansion, DefaultPropertySet, etc.)
  • ApplyExcludePropertyFilter() is defined in ViewGenerator (the base class of TableViewGenerator, ListViewGenerator, etc.)

Since the cmdlet and ViewGenerator are separate classes, we need a way to pass ExcludeProperty between them. ViewGenerator.Initialize() already receives FormattingCommandLineParameters, so adding excludePropertyFilter there was a natural way to pass it - same as -Property which is stored as mshParameterList in the same class.

Even with add-time filtering, we would still need to add a member to FormattingCommandLineParameters because that's the bridge between the cmdlet and ViewGenerator.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main concern is that we call ApplyExcludePropertyFilter() 8 times. It is looks as not reliable, it is easy to make an error.
Also we have some places where we access activeAssociationList and this rises a question should it return already filtered list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. You're right - calling ApplyExcludePropertyFilter() at multiple exit points is error-prone.

I've refactored both methods to eliminate multiple return statements and call ApplyExcludePropertyFilter() exactly once at the end:

  • Table Initialize(): 3 calls → 1 call
  • Wide SetUpActiveProperty(): 4 calls → 1 call

Regarding activeAssociationList access: The existing design doesn't prevent access to activeAssociationList before Initialize(), but I've verified no such access exists. All external access (e.g., GeneratePayload(), GenerateHeaderInfo()) happens after Initialize() completes, so they always see the filtered list.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good step forward. Thanks!
Nevertheless, can we think about making it more generic? Can it be in base.Initialize() for example? It would be great.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the positive feedback and the suggestion!

I explored moving ApplyExcludePropertyFilter() into base.Initialize(), but found some structural challenges:

Current Structure:

ViewGenerator When activeAssociationList is built
Table During Initialize()
List During Initialize()
Wide Lazily in GeneratePayload() via SetUpActiveProperty()
Complex Not used (uses ComplexViewObjectBrowser)

Challenges with base.Initialize():

  1. Timing: activeAssociationList is built by derived classes after base.Initialize() returns, so it would be null at that point
  2. Wide's lazy initialization: Wide builds the list during GeneratePayload(), not during Initialize()
  3. Complex doesn't use it: Uses a different rendering path

Possible approach - Template Method pattern:

// ViewGenerator (base)
internal virtual void Initialize(...) {
    InitializeHelper();
    BuildActiveAssociationList(so);  // virtual, overridden by derived classes
    ApplyExcludePropertyFilter();
}

However, this wouldn't work for Wide's lazy initialization pattern without additional refactoring.

Current approach:
Each Initialize method calls ApplyExcludePropertyFilter() once at the end, which handles all cases including Wide's lazy pattern.

Would you prefer me to explore the Template Method approach further, or is the current single-call-per-Initialize approach acceptable given these constraints?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally it would be great to have clean class design. But if it requires a lot of refactoring, then it's better to postpone it, i.e. I can accept the PR as it is now, and you can do the refactoring in a subsequent PR if you want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the flexibility and understanding!

I agree that a cleaner centralized approach would require some additional refactoring. The current approach—calling ApplyExcludePropertyFilter() once at the end of each Initialize method—keeps the changes minimal and focused on the -ExcludeProperty feature.

I've opened #26568 to track the potential refactoring for the future. Please feel free to assign it to me if you'd like.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to grab the work. We are in beginning of next version dev cycle so we can do formatting and refactoring that way there's enough time to stabilize.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I'll take on #26568.
Since the refactoring depends on the code introduced in this PR, I'll start working on it after this PR is merged.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…ies/Mshexpression.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@iSazonov iSazonov enabled auto-merge (squash) December 5, 2025 04:28
@iSazonov iSazonov merged commit 6311c7c into PowerShell:master Dec 5, 2025
36 checks passed
@yotsuda yotsuda deleted the issue-25043-format-table branch December 5, 2025 05:05
SIRMARGIN pushed a commit to SIRMARGIN/PowerShell that referenced this pull request Dec 12, 2025
kilasuit pushed a commit to kilasuit/PowerShell that referenced this pull request Jan 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants