(PowerShell 7)
My solution is to create utility functions in profile script to make the call-site shorter.
I have two solution variants, solution 1 is more flexible and solution 2 is more succint. You may use one, or both!
Solution 1
function ConvertTo-ArrayIndexed {
<#
.SYNOPSIS
Indexed variant of `ForEach-Object`.
.PARAMETER Transform
ScriptBlock function: ([Int]$Index, [Object]$Element) -> [Object]$TransformedElement
.EXAMPLE
'a', ('b','c'), 'd' | ConvertTo-ArrayIndexed {"Element #$($args[0])`: $($args[1]) | Type: $($args[1].getType())"}
Element #0: a | Type: string
Element #1: b c | Type: System.Object[]
Element #2: d | Type: string
#>
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)][AllowNull()]
[Object]$InputObject,
[Parameter(Mandatory, Position = 0)]
[ScriptBlock]$Transform
)
begin {
$index = 0
}
process {
. $Transform ($index++) $InputObject
}
}
Example 1
"a", ("b","c"), "d" | %i{ param($i, $e) "Element $i is $e" }
Output:
Element 0 is a
Element 1 is b c
Element 2 is d
Solution 2
function ConvertTo-ArrayIndexed2 {
<#
.SYNOPSIS
Indexed variant of `ForEach-Object`.
.PARAMETER Transform
ScriptBlock function with context: ([Int]$Index, [Object]$Element) -> [Object]$TransformedElement
Two local variables are defined: `$i` for index, and `$_` for element.
.EXAMPLE
'a', ('b','c'), 'd' | ConvertTo-ArrayIndexed2 {"Element #$i`: $_ | Type: $($_.getType())"}
Element #0: a | Type: string
Element #1: b c | Type: System.Object[]
Element #2: d | Type: string
.EXAMPLE
ConvertTo-ArrayIndexed2 -InputObject 'a',('b','c'),'d' {"Element #$i`: $_ | Type: $($_.getType())"}
Element #0: a System.Object[] d | Type: System.Object[]
#>
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)][AllowNull()]
[Object]$InputObject,
[Parameter(Mandatory, Position = 0)][ArgumentCompletions('{}')]
[ScriptBlock]$Transform
)
begin {
$index = 0
}
process {
$variables = @([PSVariable]::new('i', $index++), [PSVariable]::new('_', $InputObject))
$Transform.InvokeWithContext($null, $variables)
}
}
Example 2
"a", ("b","c"), "d" | %i{ "Element $i is $_" }
Output:
Element 0 is a
Element 1 is b c
Element 2 is d
Notes
I added function aliases:
New-Alias -Name %i -Value ConvertTo-ArrayIndexed
New-Alias -Name mapIndexed -Value ConvertTo-ArrayIndexed
Modify the function and alias as you wish.
I came from a background where I understand such a function as mapIndexed, but %i is more succinct and it goes well with the existing % alias for ForEach-Object.
Also note that "ForEach" is a "reserved verb", but if you don't mind I think an alias named ForEach-ObjectIndexed is pretty nice as well.