2

I have an object with spaces in their properties names. I want to Select-Object @{n='NewName';e={$_.'Old Name'}} for every NoteProperty. Since there is a lot of them, I created this function. Running this code will return an array of hash tables but I can't get the old name $col to be replaced with the actual old name. I think it's because it's bound to a new execution context but I can't make it to work.

function Rename-Columns {
    # Array of HashTable splat to rename columns in Select-Object (TSQL SELECT [col with space] as colwithspace)
    param (
        [string[]]$Columns,
        [hashtable]$RenamePattern = @{Replace='\s|\:|\.|\+|\|';With=''} # remove unwanted cars 
    )
    $return = @()
    foreach ($col in $Columns) {
        $newName = $col -replace $RenamePattern['Replace'], $RenamePattern['With']
        $return += @{n="$newName";e={$_.'"$col"'}} # <- can't replace $col 
    }
    $return
}
mklement0
  • 382,024
  • 64
  • 607
  • 775
PollusB
  • 1,726
  • 2
  • 22
  • 31

1 Answers1

2

Your current approach can't work because by the time you pass the {$_."$col"} block to Select-Object, $col no longer resolves to the value it did when you created the scriptblock inside the loop.

In order to bind the current iterator value of $col to the expression block, you need a closure:

function Rename-Columns {
    # Array of HashTable splat to rename columns in Select-Object (TSQL SELECT [col with space] as colwithspace)
    param (
        [string[]]$Columns,
        [hashtable]$RenamePattern = @{Replace='\s|\:|\.|\+|\|';With=''} # remove unwanted cars 
    )

    foreach ($col in $Columns) {
        # calculate new name
        $newName = $col -replace $RenamePattern['Replace'], $RenamePattern['With']
        # close over `{$_.$col}` to bind the current value to `$col`
        @{n=$newName;e={$_.$col}.GetNewClosure()}
    }
}

GetNewClosure() will see that the $col variable exists in the scope where it was called, and so it copies its value and stores it along-side the scriptblock.

As a result, when Select-Object executes the property expression (at a later time), the scriptblock "remembers" the original value and $col resolves correctly/as expected.


With sample data:

$data = [pscustomobject]@{
  'Old Name' = 123
  'Schema   Name' = 456
}

$originalColumnNames = $data |Get-Member -MemberType Properties |ForEach-Object -MemberName Name

$data |Select -Property @(Rename-Columns $originalColumnNames)

It yields the expected result (spaces replaced in all names):

OldName SchemaName
------- ----------
    123        456
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • Still doesn't work. Can you try it with: ```$cols = "Schema Name,Secret Column Names,Singleton Lookups,Total Reads,User Updates" -split ','``` – PollusB Feb 23 '22 at 15:02
  • I have PS 5.1 and I found in doc online that it should work. – PollusB Feb 23 '22 at 15:03
  • @PollusB I've updated the answer with an example at the bottom. If you still can't get it to work: _please [update your post](https://stackoverflow.com/q/71238816/712649) with sample data_ – Mathias R. Jessen Feb 23 '22 at 15:11