#TL;DR;
On Jeffrey Snover’s blog post Variable Scopes and $PRIVATE: | Jeffrey Snover's blog, Niklas Bergius commented with a good question about scoping on parameters.
Does anyone know if there is a good way to scope parameters to the private scope without ruining usage of Get-Help and tab-completion for the function parameters?
#Longer description of the question
First, let’s create a script similar to the one mentioned in Mr Snover’s blog post:
function Test-Scopes { PARAM( [scriptblock]$ScriptBlock ) & $ScriptBlock }
This function only takes a single parameter named ScriptBlock. In an outer scope, which will call the Test-Stuff function, let’s create two script block variables, where the second script block calls the first script block as part of its execution, and the first script block has the same name as the parameter to our Test-Scopes function above.
[scriptblock]$ScriptBlock = { Write-Host "My script block" } [scriptblock]$myScriptBlock = { Write-Host "Outer script block" & $ScriptBlock }
Finally, let’s call the function, providing the second scriptblock as parameter.
Test-Scopes -ScriptBlock $myScriptBlock
Executing that line will end up in an infinite loop, constantly writing the output “Outer script block”. The reason for this is of course that the -ScriptBlock parameter will be the one called during the execution of the script block we passed in, not the $ScriptBlock variable which we created in a scope above.
So, as mentioned in Niklas Bergius’ comment, we can fix this behaviour by changing the scope of the parameter to Private.
function Test-Scopes { PARAM( [scriptblock]$Private:ScriptBlock ) & $Private:ScriptBlock }
If we now, using the same script block variables as previously, try to write Get-Help for the command, it will display the following syntax:
Test-Scopes [[-Private:ScriptBlock] <scriptblock>]
Also, the tab-completion functionality in the PowerShell ISE and the PowerShell console will both want to use the -Private:ScriptBlock parameter name, but trying to do this fails with the following error message:
PS C:> Test-Scopes -Private:ScriptBlock $myScriptBlock Test-Scopes : Cannot process argument transformation on parameter 'Private:ScriptBlock'. Cannot convert the "ScriptBlock" value of type "System.String" to type "System.Management.Automation.ScriptBlock". At line:19 char:22 + Test-Scopes -Private:ScriptBlock $myScriptBlock + ~~~~~~~~~~~ + CategoryInfo : InvalidData: (:) [Test-Scopes], ParameterBindingArgumentTransformationException + FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-Scopes
The error message seems do try to indicate that it is actually trying to pass “ScriptBlock” as a string (the underlined part in the error), not the “$myScriptBlock” variable value, as a parameter value. Almost seems a bit like a PowerShell parser bug, since it seems like it first correctly identifies the parameter name as being “Private:ScriptBlock” (since it correctly identifies the expected type to be ScriptBlock. Another interpretation could be that it is trying to pass the parameter by index instead of name, but that would lead to a lot of other questions regarding parsing of that line) but when it checks for the value to bind to the parameter it tries to use “ScriptBlock” for the name. This could, of course, be a missconception on my part, but leaving out the $myScriptBlock part of that line generates the same error, supporting the hypothesis at least a little.
We can, instead, provide the parameter by index instead, calling the Test-Scopes function like:
Test-Scopes $myScriptBlock
This works correctly and prints the expected “Outer script block” followed by “My script block” lines. In order to use a named parameter, we could set an alias for the parameter, like so:
function Test-Scopes { PARAM( [Alias("ScriptBlock")] [scriptblock]$Private:ScriptBlock ) & $Private:ScriptBlock } Test-Scopes -ScriptBlock $myScriptBlock
This works correctly, executing the functions just as we want. However, the Get-Help syntax shown, as well as the tab-completion, will still be suggesting that we use the parameter named -Private:ScriptBlock, which, as mentioned, fails.
Does anyone know if it is possible to specify the scope on a parameter to a function and still retain a useful Get-Help and tab-completion functionality for the function?