1

I have a function written in PowerShell:

function replace([string] $name,[scriptblock] $action) {
    Write-Host "Replacing $name"
    $_ = $name
    $action.Invoke()
}

and would be used as:

$name = "tick"
replace $agentPath\conf\buildAgent.dist.properties {
    (cat templates\buildAgent.dist.properties.tpl) `
        -replace '@@serverurl@@', 'http:/localhost:8080/teamcity' `
        -replace '@@name@@', $name `
        > $_
}

However I've discovered that within the scriptblock the variable $name is overwritten by the $name param from within the replace function.

Is there a way to execute the script block so that only the variable $_ is added to the scope of the scriptblock but nothing else is?

Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217

2 Answers2

1

I preface my answer by claiming that powershell is only for sadists. The trick is that if you put the function into a module the local variables become private and are not passed to the script block. Then to pass in the $_ variable you have to jump some more hoops.

The gv '_' get's the powershell variable $_ and passes it to the context via InvokeWithContext.

Now I know more than I ever wanted to :|

New-Module {
    function replace([string] $name,[scriptblock] $action) {
        Write-Host "Replacing $name"
        $_ = $name
        $action.InvokeWithContext(@{}, (gv '_'))
    }
}

and as before

$name = "tick"
replace $agentPath\conf\buildAgent.dist.properties {
    (cat templates\buildAgent.dist.properties.tpl) `
        -replace '@@serverurl@@', 'http:/localhost:8080/teamcity' `
        -replace '@@name@@', $name `
        > $_
}
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
0

You could use the $global: prefix for the $name variable within your scriptblock:

$name = "tick"
replace $agentPath\conf\buildAgent.dist.properties {
    (cat templates\buildAgent.dist.properties.tpl) `
        -replace '@@serverurl@@', 'http:/localhost:8080/teamcity' `
        -replace '@@name@@', $global:name `
        > $_
}
Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
  • I could but that is asking for me or another developer to forget one day and the problem randomly appears in the future. I'd expect there to be some kind of solution working from the other end where the caller of the script block is polite and doesn't inject all their local variables into the scope. – bradgonesurfing May 03 '17 at 10:25
  • I found a way to do it. It's ugly for sure :) See my answer. – bradgonesurfing May 03 '17 at 12:40
  • It looks ugly but I agree that its better for other developer *using* the function. – Martin Brandl May 03 '17 at 12:43