Reputation: 903
Powershell can add custom properties with somewhat messy syntax, as described in the about_Calculated_Properties help topic; e.g.:
Get-Date | Select-Object Year, @{name='foo'; expression={123}}
Year foo
---- ---
2024 123
I want to clean it up a bit, so I can write something more like this:
Get-Date | Select-Object Year, (prop 'foo' 123)
How can I implement a prop
function that enables this simplified syntax?
Upvotes: -6
Views: 590
Reputation: 439812
Santiago's helpful answer shows an alternative to what you're asking for, by overriding the built-in Select-Object
cmdlet:
@{ name='foo'; expression={123} }
, to the - still hashtable literal-based - form @{ foo = { 123 } }
, but it also limits you to only using this simplified syntax; that is, the original calculated-property syntax is then no longer supported.Your (first) own helpful answer mostly addresses your question as asked, except for the - syntactically still awkward - need to enclose a static property value in { ... }
, i.e. in a script block; that is, you asked for (prop 'foo' 123)
, but the prop
implementation in your answer requires (prop 'foo' { 123 })
instead.
Fully implementing what your question asks for is possible, assuming that you're willing to forgo the normal (albeit rarely seen in practice) shorthand syntax of providing a property name of the input objects as a string, e.g. 'foo'
instead of the verbose equivalent of using a script block in which the property is explicitly accessed on the input object at hand, via the automatic $_
variable, { $_.foo }
function prop {
param([Parameter(Mandatory)] $name, [Parameter(Mandatory)] $val)
if ($val -isnot [scriptblock]) {
# Wrap the original value in a script block and
# use a closure to capture it, so it is also available when
# executed in the caller's scope.
$val = { $val }.GetNewClosure()
}
# Otherwise: If already a script block, use as-is.
# Create and output a hashtable using the prescribed format
# for serving as a calculated property.
@{ name = $name; expression = $val }
}
The above lets you pass literal values as-is, while also supporting script block-based values; e.g.:
Get-Date |
Select-Object Year,
(prop foo 123), # Static value
(prop MyDay { $_.Day + 1 }) # Value based on input object
Sample output:
Year foo MyDay
---- --- -----
2024 123 19
Upvotes: 0
Reputation: 903
An alternative to the -NotePropertyMembers
approach in y y's answer is the combination of
-NotePropertyName
and -NotePropertyValue
Get-Date |
Add-Member -Force -PassThru -NotePropertyName foo -NotePropertyValue bar |
Select-Object Year, Foo
or simply
date | Add-Member foo bar -Force -PassThru | select Year, Foo
The same output in either case
Year foo
---- ---
2024 123
-Force may be required to avoid name collision errors even if the name isn't present in the original object
Upvotes: 0
Reputation: 349
Get-Date |
Add-Member -NotePropertyMembers @{ 'foo' = 123 } -PassThru |
Select-Object Year, foo
Upvotes: 0
Reputation: 903
Define the function as follows
function prop($name,$val) {@{name=$name; expression=$val}}
It gives the expected results below. The curly braces are not optional.
Get-Date | Select-Object Year, (prop 'foo' {123})
Year foo
---- ---
2024 123
Upvotes: 0
Reputation: 60838
An alternative to using a function to create calculated properties for you is to proxy the cmdlet itself to handle this use case. The proxy command is created via ProxyCommand.Create
Method:
[System.Management.Automation.ProxyCommand]::Create((Get-Command Select-Object))
And from there you can add additional logic to it, as an example to handle the use where Select-Object
could accept @{ Key = Value }
and transform it to @{ N=Key; E=Value }
, the proxy could look something like this (I'm reducing it a lot from the original output however this cmdlet's param
block is very extensive. The relevant code is in the begin
block):
function Select-Object {
[CmdletBinding(
DefaultParameterSetName = 'DefaultParameter',
HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2096716',
RemotingCapability = 'None')]
param(
[Parameter(ValueFromPipeline = $true)]
[psobject]
${InputObject},
[Parameter(ParameterSetName = 'SkipLastParameter', Position = 0)]
[Parameter(ParameterSetName = 'DefaultParameter', Position = 0)]
[System.Object[]]
${Property},
[Parameter(ParameterSetName = 'DefaultParameter')]
[Parameter(ParameterSetName = 'SkipLastParameter')]
[string[]]
${ExcludeProperty},
[Parameter(ParameterSetName = 'SkipLastParameter')]
[Parameter(ParameterSetName = 'DefaultParameter')]
[string]
${ExpandProperty},
[switch]
${Unique},
[Parameter(ParameterSetName = 'DefaultParameter')]
[ValidateRange(0, 2147483647)]
[int]
${Last},
[Parameter(ParameterSetName = 'DefaultParameter')]
[ValidateRange(0, 2147483647)]
[int]
${First},
[Parameter(ParameterSetName = 'DefaultParameter')]
[ValidateRange(0, 2147483647)]
[int]
${Skip},
[Parameter(ParameterSetName = 'SkipLastParameter')]
[ValidateRange(0, 2147483647)]
[int]
${SkipLast},
[Parameter(ParameterSetName = 'DefaultParameter')]
[Parameter(ParameterSetName = 'IndexParameter')]
[switch]
${Wait},
[Parameter(ParameterSetName = 'IndexParameter')]
[ValidateRange(0, 2147483647)]
[int[]]
${Index}
)
begin {
# if the function was called with `-Property`
if ($PSBoundParameters.ContainsKey('Property')) {
# reconstruct from @{ Key = Value } to @{ N=Key; E=Value }
$PSBoundParameters['Property'] = foreach ($prop in $Property) {
if ($prop -isnot [hashtable]) {
# nothing to do here,
# just output the value as-is and go next
$prop
continue
}
# here we assume the property is calculated
foreach ($keyvalue in $prop.GetEnumerator()) {
@{ Name = $keyvalue.Key; Expression = $keyvalue.Value }
}
}
}
$steppablePipeline = { Microsoft.PowerShell.Utility\Select-Object @PSBoundParameters }.
GetSteppablePipeline($MyInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($InputObject)
}
end {
$steppablePipeline.End()
}
}
Then using the proxy function, this syntax would work just fine:
Get-Date | Select-Object Year, @{ Month = { $_.Month }}, @{ Day = 'Day' }
Also this syntax becomes valid:
Get-Date | Select-Object @{
New = 'Month'
Property = { $_.Day }
}
Upvotes: 3