One question for you is here 😉
I have this function:
function Set-DbFile {
param(
[Parameter(ValueFromPipeline=$true)]
[System.IO.FileInfo[]]
$InputObject,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[scriptblock]
$Properties
)
process {
$InputObject | % {
Write-Host `nInside. Storing $_.Name
$props = & $Properties
Write-Host ' properties for the file are: ' -nonew
write-Host ($props.GetEnumerator()| %{"{0}-{1}" -f $_.key,$_.Value})
}
}
}
Look at the $Properties. It should be evaluated for each file and then the file and the properties should be processed further.
Example how to use it might be:
Get-ChildItem c:\windows |
? { !$_.PsIsContainer } |
Set-DbFile -prop {
Write-Host Creating properties for $_.FullName
@{Name=$_.Name } # any other properties based on the file
}
When I copy & paste function Set-dbFile to command line and run the example snippet, everything is fine.
However, when I store the function in a module, import it and run the example, the $_ variable is empty. Does anybody know why? And how to solve it? (other solutions are welcome as well)
Results for function defined in a script/typed in commandline:
Inside. Storing adsvw.ini
Creating properties for C:\windows\adsvw.ini
properties for the file are: Name-adsvw.ini
Inside. Storing ARJ.PIF
Creating properties for C:\windows\ARJ.PIF
properties for the file are: Name-ARJ.PIF
....
Results for function defined in module:
Inside. Storing adsvw.ini
Creating properties for
properties for the file are: Name-
Inside. Storing ARJ.PIF
Creating properties for
properties for the file are: Name-
....
Looks like
GetNewClosure()is as good a work around as any, but it changes the way the script block sees those variables. Passing$_to the scriptblock as an argument works, too.It has nothing to do with normal scope issues (e.g., global vs local), but it appears like that at first. Here’s my very simplified reproduction and some explanation following:
script.ps1for normal dot-sourcing:Module\MyTest\MyTest.psm1for importing:Calls and output:
So I started hunting around since this piqued my curiosity, and I found a few interesting things.
This Q&A, which also features a link to this bug report is pretty much the exact same topic, as are some other blog articles I ran across. But while it was reported as a bug, I disagree.
The about_Scopes page has this to say (w:
Now I understand the behavior, but it was the above and a few more experiments that led me to it:
$messagein the scriptblock to$local:messagethen all 3 tests have a blank space, because$messageis not defined in the scriptblock’s local scope.$global:message, all 3 tests printoutside.$script:message, the first 2 tests printoutsideand the last printsinside.Then I also read this in
about_Scopes:$((get-variable -name message -scope 1).value)in order to attempt getting the value from the immediate parent scope, what happens? We still getoutsiderather thaninside.At this point it was clear enough to me that sessions and modules have their own declaration scope or context of sorts, at least for script blocks. The script blocks act like anonymous functions in the environment in which they’re declared until you call
GetNewClosure()on them, at which point they internalize copies of the variables they reference of the same name in the scope whereGetNewClosure()was called (using locals first, up to globals). A quick demonstration:I hope this helps.
Addendum: Regarding design.
JasonMArcher’s comment made me think about a design issue with the scriptblock being passed into the module. In the code of your question, even if you use the
GetNewClosure()workaround, you have to know the name of the variable(s) where the scriptblock will be executed in order for it to work.On the other hand, if you used parameters to the scriptblock and passed
$_to it as an argument, the scriptblock does not need to know the variable name, it only needs to know that an argument of a particular type will be passed. So your module would use$props = & $Properties $_instead of$props = & $Properties.GetNewClosure(), and your scriptblock would look more like this:See CosmosKey’s answer for further clarification.