Add -ExcludeProperty parameter to Format-* cmdlets (#26514)

This commit is contained in:
Yoshifumi
2025-12-05 04:54:52 +00:00
committed by GitHub
parent 5c57c3e19c
commit 6311c7c2f6
16 changed files with 404 additions and 127 deletions
@@ -41,6 +41,12 @@ namespace Microsoft.PowerShell.Commands
private object[] _props;
/// <summary>
/// Gets or sets the properties to exclude from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
/// <summary>
/// </summary>
/// <value></value>
@@ -61,6 +67,18 @@ namespace Microsoft.PowerShell.Commands
{
FormattingCommandLineParameters parameters = new();
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if ((_props is not null && _props.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (_props != null)
{
ParameterProcessor processor = new(new FormatObjectParameterDefinition());
@@ -68,15 +86,17 @@ namespace Microsoft.PowerShell.Commands
parameters.mshParameterList = processor.ProcessParameters(_props, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (_props is null || _props.Length == 0)
{
ParameterProcessor processor = new(new FormatObjectParameterDefinition());
TerminatingErrorContext invocationContext = new(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
parameters.groupByParameter = this.ProcessGroupByParameter();
@@ -42,9 +42,14 @@ namespace Microsoft.PowerShell.Commands
private object _prop;
/// <summary>
/// Optional, non positional parameter.
/// Gets or sets the properties to exclude from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to autosize the output.
/// </summary>
/// <value></value>
[Parameter]
public SwitchParameter AutoSize
{
@@ -74,6 +79,18 @@ namespace Microsoft.PowerShell.Commands
{
FormattingCommandLineParameters parameters = new();
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if (_prop is not null || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (_prop != null)
{
ParameterProcessor processor = new(new FormatWideParameterDefinition());
@@ -81,15 +98,17 @@ namespace Microsoft.PowerShell.Commands
parameters.mshParameterList = processor.ProcessParameters(new object[] { _prop }, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (_prop is null)
{
ParameterProcessor processor = new(new FormatWideParameterDefinition());
TerminatingErrorContext invocationContext = new(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
// we cannot specify -column and -autosize, they are mutually exclusive
@@ -12,47 +12,6 @@ using Microsoft.PowerShell.Commands.Internal.Format;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Helper class to do wildcard matching on PSPropertyExpressions.
/// </summary>
internal sealed class PSPropertyExpressionFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="PSPropertyExpressionFilter"/> class
/// with the specified array of patterns.
/// </summary>
/// <param name="wildcardPatternsStrings">Array of pattern strings to use.</param>
internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings)
{
ArgumentNullException.ThrowIfNull(wildcardPatternsStrings);
_wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length];
for (int k = 0; k < wildcardPatternsStrings.Length; k++)
{
_wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase);
}
}
/// <summary>
/// Try to match the expression against the array of wildcard patterns.
/// The first match shortcircuits the search.
/// </summary>
/// <param name="expression">PSPropertyExpression to test against.</param>
/// <returns>True if there is a match, else false.</returns>
internal bool IsMatch(PSPropertyExpression expression)
{
for (int k = 0; k < _wildcardPatterns.Length; k++)
{
if (_wildcardPatterns[k].IsMatch(expression.ToString()))
return true;
}
return false;
}
private readonly WildcardPattern[] _wildcardPatterns;
}
internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition
{
protected override void SetEntries()
@@ -9,6 +9,7 @@ using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell.Commands;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
@@ -729,6 +730,12 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
[Parameter(Position = 0)]
public object[] Property { get; set; }
/// <summary>
/// Optional parameter for excluding properties from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
#endregion
internal override FormattingCommandLineParameters GetCommandLineParameters()
@@ -751,6 +758,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
internal void GetCommandLineProperties(FormattingCommandLineParameters parameters, bool isTable)
{
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if ((Property is not null && Property.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (Property != null)
{
CommandParameterDefinition def;
@@ -765,15 +784,21 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
parameters.mshParameterList = processor.ProcessParameters(Property, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (Property is null || Property.Length == 0)
{
CommandParameterDefinition def = isTable
? new FormatTableParameterDefinition()
: new FormatListParameterDefinition();
ParameterProcessor processor = new ParameterProcessor(def);
TerminatingErrorContext invocationContext = new TerminatingErrorContext(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
}
}
@@ -70,6 +70,11 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
/// Extension mechanism for shape specific parameters.
/// </summary>
internal ShapeSpecificParameters shapeParameters = null;
/// <summary>
/// Filter for excluding properties from formatting.
/// </summary>
internal PSPropertyExpressionFilter excludePropertyFilter = null;
}
/// <summary>
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Internal;
@@ -348,7 +349,19 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
protected DataBaseInfo dataBaseInfo = new DataBaseInfo();
protected List<MshResolvedExpressionParameterAssociation> activeAssociationList = null;
protected FormattingCommandLineParameters inputParameters = null;
/// <summary>
/// Apply ExcludeProperty filter to activeAssociationList if specified.
/// This method filters and updates "activeAssociationList" instance property.
/// </summary>
protected void ApplyExcludePropertyFilter()
{
if (this.parameters is not null && this.parameters.excludePropertyFilter is not null)
{
this.activeAssociationList = this.activeAssociationList
.Where(item => !this.parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression))
.ToList();
}
}
protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex,
FieldFormattingDirective directive)
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Internal;
@@ -16,7 +17,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters)
{
base.Initialize(errorContext, expressionFactory, so, db, parameters);
this.inputParameters = parameters;
}
internal override FormatStartData GenerateStartData(PSObject so)
@@ -40,7 +40,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
private ComplexViewEntry GenerateComplexViewEntryFromProperties(PSObject so, int enumerationLimit)
{
ComplexViewObjectBrowser browser = new ComplexViewObjectBrowser(this.ErrorManager, this.expressionFactory, enumerationLimit);
return browser.GenerateView(so, this.inputParameters);
return browser.GenerateView(so, this.parameters);
}
private ComplexViewEntry GenerateComplexViewEntryFromDataBaseInfo(PSObject so, int enumerationLimit)
@@ -425,17 +425,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
/// of the object.
/// </summary>
/// <param name="so">Object to process.</param>
/// <param name="inputParameters">Parameters from the command line.</param>
/// <param name="parameters">Parameters from the command line.</param>
/// <returns>Complex view entry to send to the output command.</returns>
internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters inputParameters)
internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters parameters)
{
_complexSpecificParameters = (ComplexSpecificParameters)inputParameters.shapeParameters;
_parameters = parameters;
_complexSpecificParameters = (ComplexSpecificParameters)parameters.shapeParameters;
int maxDepth = _complexSpecificParameters.maxDepth;
TraversalInfo level = new TraversalInfo(0, maxDepth);
List<MshParameter> mshParameterList = null;
mshParameterList = inputParameters.mshParameterList;
mshParameterList = parameters.mshParameterList;
// create a top level entry as root of the tree
ComplexViewEntry cve = new ComplexViewEntry();
@@ -513,6 +514,14 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
List<MshResolvedExpressionParameterAssociation> activeAssociationList =
AssociationManager.SetupActiveProperties(parameterList, so, _expressionFactory);
// Apply ExcludeProperty filter if specified
if (_parameters != null && _parameters.excludePropertyFilter != null)
{
activeAssociationList = activeAssociationList
.Where(item => !_parameters.excludePropertyFilter.IsMatch(item.ResolvedExpression))
.ToList();
}
// create a format entry
FormatEntry fe = new FormatEntry();
formatValueList.Add(fe);
@@ -758,6 +767,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
return feFrame.formatValueList;
}
private FormattingCommandLineParameters _parameters;
private ComplexSpecificParameters _complexSpecificParameters;
/// <summary>
@@ -31,7 +31,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
_listBody = (ListControlBody)this.dataBaseInfo.view.mainControl;
}
this.inputParameters = parameters;
this.parameters = parameters;
SetUpActiveProperties(so);
}
@@ -227,10 +227,12 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
List<MshParameter> mshParameterList = null;
if (this.inputParameters != null)
mshParameterList = this.inputParameters.mshParameterList;
if (this.parameters != null)
mshParameterList = this.parameters.mshParameterList;
this.activeAssociationList = AssociationManager.SetupActiveProperties(mshParameterList, so, this.expressionFactory);
ApplyExcludePropertyFilter();
}
}
}
@@ -40,40 +40,45 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
rawMshParameterList = parameters.mshParameterList;
// check if we received properties from the command line
if (rawMshParameterList != null && rawMshParameterList.Count > 0)
if (rawMshParameterList is not null && rawMshParameterList.Count > 0)
{
this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so);
return;
}
// we did not get any properties:
// try to get properties from the default property set of the object
this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory);
if (this.activeAssociationList.Count > 0)
else
{
// we got a valid set of properties from the default property set..add computername for
// remoteobjects (if available)
if (PSObjectHelper.ShouldShowComputerNameProperty(so))
// we did not get any properties:
// try to get properties from the default property set of the object
this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory);
if (this.activeAssociationList.Count > 0)
{
activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null,
new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty)));
// we got a valid set of properties from the default property set..add computername for
// remoteobjects (if available)
if (PSObjectHelper.ShouldShowComputerNameProperty(so))
{
activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null,
new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty)));
}
}
else
{
// we failed to get anything from the default property set
this.activeAssociationList = AssociationManager.ExpandAll(so);
if (this.activeAssociationList.Count > 0)
{
// Remove PSComputerName and PSShowComputerName from the display as needed.
AssociationManager.HandleComputerNameProperties(so, activeAssociationList);
FilterActiveAssociationList();
}
else
{
// we were unable to retrieve any properties, so we leave an empty list
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
return;
}
}
return;
}
// we failed to get anything from the default property set
this.activeAssociationList = AssociationManager.ExpandAll(so);
if (this.activeAssociationList.Count > 0)
{
// Remove PSComputerName and PSShowComputerName from the display as needed.
AssociationManager.HandleComputerNameProperties(so, activeAssociationList);
FilterActiveAssociationList();
return;
}
// we were unable to retrieve any properties, so we leave an empty list
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
ApplyExcludePropertyFilter();
}
/// <summary>
@@ -13,7 +13,6 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters)
{
base.Initialize(errorContext, expressionFactory, so, db, parameters);
this.inputParameters = parameters;
}
internal override FormatStartData GenerateStartData(PSObject so)
@@ -159,39 +158,37 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
private void SetUpActiveProperty(PSObject so)
{
List<MshParameter> rawMshParameterList = null;
if (this.inputParameters != null)
rawMshParameterList = this.inputParameters.mshParameterList;
List<MshParameter> rawMshParameterList = this.parameters?.mshParameterList;
// check if we received properties from the command line
if (rawMshParameterList != null && rawMshParameterList.Count > 0)
if (rawMshParameterList is not null && rawMshParameterList.Count > 0)
{
this.activeAssociationList = AssociationManager.ExpandParameters(rawMshParameterList, so);
return;
}
// we did not get any properties:
// try to get the display property of the object
PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory);
if (displayNameExpression != null)
else
{
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression));
return;
// we did not get any properties:
// try to get the display property of the object
PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory);
if (displayNameExpression is not null)
{
this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>();
this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression));
}
else
{
// try to get the default property set (we will use the first property)
this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory);
if (this.activeAssociationList.Count == 0)
{
// we failed to get anything from the default property set
// just get all the properties
this.activeAssociationList = AssociationManager.ExpandAll(so);
}
}
}
// try to get the default property set (we will use the first property)
this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory);
if (this.activeAssociationList.Count > 0)
{
// we got a valid set of properties from the default property set
return;
}
// we failed to get anything from the default property set
// just get all the properties
this.activeAssociationList = AssociationManager.ExpandAll(so);
ApplyExcludePropertyFilter();
}
}
}
@@ -375,5 +375,42 @@ namespace Microsoft.PowerShell.Commands
private bool _isResolved = false;
#endregion Private Members
}
/// <summary>
/// Helper class to do wildcard matching on PSPropertyExpressions.
/// </summary>
internal sealed class PSPropertyExpressionFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="PSPropertyExpressionFilter"/> class
/// with the specified array of patterns.
/// </summary>
/// <param name="wildcardPatternsStrings">Array of pattern strings to use.</param>
internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings)
{
ArgumentNullException.ThrowIfNull(wildcardPatternsStrings);
_wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length];
for (int k = 0; k < wildcardPatternsStrings.Length; k++)
{
_wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase);
}
}
/// <summary>
/// Try to match the expression against the array of wildcard patterns.
/// The first match short-circuits the search.
/// </summary>
/// <param name="expression">PSPropertyExpression to test against.</param>
/// <returns>True if there is a match, else false.</returns>
internal bool IsMatch(PSPropertyExpression expression)
{
string expressionString = expression.ToString();
return _wildcardPatterns.Any(pattern => pattern.IsMatch(expressionString));
}
private readonly WildcardPattern[] _wildcardPatterns;
}
}
@@ -2504,7 +2504,8 @@ namespace System.Management.Automation
case "Format-Table":
case "Format-Wide":
{
if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase))
if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)
|| parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase))
{
NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]);
}