Search code examples
powershellclosuresscriptblock

How to return a parameterized code block in PowerShell


Can a PowerShell function return a parameterized function as one would in JavaScript (see below)?

I would like to make a reusable code block which renames files in a case-sensitive way. This should not be a function but a code block which I can pass to ForEach-Object:

$Renamer = {
  Rename-Item $_ $_.name.replace("a", "A", $false, $null);
}
Get-ChildItem *.txt | ForEach-Object $Renamer

This works fine but now I want a function which returns the same code block but with different parameters.

function MakeRenamer($from, $to) {
  # This doesn't work, $from and $to are undefined
  return {
    Rename-Item $_ $_.name.replace($from, $to, $false, $null);
  }
}
$Renamer = MakeRenamer "a" "A"
Get-ChildItem *.txt | ForEach-Object $Renamer
$Renamer = MakeRenamer "b" "B"
Get-ChildItem *.txt | ForEach-Object $Renamer

This fails implying $from is null/undefined and thus is not being passed through.

enter image description here

JavaScript Example

function makeRenamer($from, $to) {
  return function(file) {
    file.name = file.name.replace(from, to);
  }
}
let renamerA = makeRenamer "a" "A";
let renamerB = makeRenamer "b" "B";

let myFile = {name:"aaab.txt"};
console.log(myFile.name); // "aaab.txt

renamerA(myFile);
console.log(myFile.name); // "AAAb.txt

renamerB(myFile);
console.log(myFile.name); // "AAAB.txt

Solution

  • PowerShell supports closures, which is what you're implicitly relying on in javascript - but in PowerShell you need to explicitly request it by calling GetNewClosure:

    function New-Renamer {
        param (
            [string]$From,
            [string]$To
        )
    
        return {
            $_ |Rename-Item -NewName {$_.Name.Replace($from, $to, $false, $null)}
        }.GetNewClosure()
    }
    
    $renamer = New-Renamer -From a -To A
    
    Get-ChildItem -Filter *.txt |ForEach-Object $renamer