Reputation: 5949
Running the following will create two modules with one calling a function in the other where the called function tries to refer to a variable in the scope of the module containing the calling function:
$ModulePath = $env:PSModulePath.Split(";")[0]
New-Item -ItemType Directory -Path $ModulePath\TestModule1 -Force
@"
`$var = 1
function Write-Stuff {
`$var
}
function Test-WriteStuff {
Write-Stuff
Write-Stuff2
}
"@ | Out-File $ModulePath\TestModule1\TestModule1.psm1
New-Item -ItemType Directory -Path $ModulePath\TestModule2 -Force
@"
function Write-Stuff2 {
`$var
"`r`nGet-Variable -Scope 0`r`n"
Get-Variable -Scope 0
"`r`nGet-Variable -Scope 1`r`n"
Get-Variable -Scope 1
"`r`nGet-Variable -Scope 2`r`n"
Get-Variable -Scope 2
"`r`nGet-Variable -Scope 3`r`n"
Get-Variable -Scope 3
}
"@ | Out-File $ModulePath\TestModule2\TestModule2.psm1
This results in the following output that shows $Var
isn't an available variable in any of the scopes accessible to the called function:
PS C:\Users\user> Test-WriteStuff
1
Get-Variable -Scope 0
Name Value
---- -----
args {}
false False
input System.Collections.ArrayList+ArrayListEnumeratorSimple
MaximumAliasCount 4096
MaximumDriveCount 4096
MaximumErrorCount 256
MaximumFunctionCount 4096
MaximumVariableCount 4096
MyInvocation System.Management.Automation.InvocationInfo
null
PSBoundParameters {}
PSCommandPath C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules\testmodule2\testmodule2.psm1
PSScriptRoot C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules\testmodule2
true True
Get-Variable -Scope 1
args {}
Error {}
false False
MaximumAliasCount 4096
MaximumDriveCount 4096
MaximumErrorCount 256
MaximumFunctionCount 4096
MaximumVariableCount 4096
MyInvocation System.Management.Automation.InvocationInfo
null
PSCommandPath C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules\testmodule2\testmodule2.psm1
PSDefaultParameterValues {}
PSScriptRoot C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules\testmodule2
true True
Get-Variable -Scope 2
$ testmodule2
? True
^ ipmo
args {}
ConfirmPreference High
ConsoleFileName
DebugPreference SilentlyContinue
Error {The scope number '3' exceeds the number of active scopes....
ErrorActionPreference Continue
ErrorView NormalView
ExecutionContext System.Management.Automation.EngineIntrinsics
false False
FormatEnumerationLimit 4
HOME C:\Users\cmagnuson
Host System.Management.Automation.Internal.Host.InternalHost
InformationPreference SilentlyContinue
input System.Collections.ArrayList+ArrayListEnumeratorSimple
MaximumAliasCount 4096
MaximumDriveCount 4096
MaximumErrorCount 256
MaximumFunctionCount 4096
MaximumHistoryCount 4096
MaximumVariableCount 4096
ModulePath C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules
MyInvocation System.Management.Automation.InvocationInfo
NestedPromptLevel 0
null
OutputEncoding System.Text.SBCSCodePageEncoding
PID 9552
profile C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1
ProgressPreference Continue
PSBoundParameters {}
PSCommandPath
PSCulture en-US
PSDefaultParameterValues {}
PSEdition Desktop
PSEmailServer
PSHOME C:\Windows\System32\WindowsPowerShell\v1.0
psISE Microsoft.PowerShell.Host.ISE.ObjectModelRoot
PSScriptRoot
PSSessionApplicationName wsman
PSSessionConfigurationName http://schemas.microsoft.com/powershell/Microsoft.PowerShell
PSSessionOption System.Management.Automation.Remoting.PSSessionOption
PSUICulture en-US
psUnsupportedConsoleApplica... {wmic, wmic.exe, cmd, cmd.exe...}
PSVersionTable {PSVersion, PSEdition, PSCompatibleVersions, BuildVersion...}
PWD C:\Users\cmagnuson
ShellId Microsoft.PowerShell
StackTrace at System.Management.Automation.SessionStateInternal.GetScopeByID(Int32 scopeID)...
true True
VerbosePreference SilentlyContinue
WarningPreference Continue
WhatIfPreference False
Get-Variable -Scope 3
Get-Variable : The scope number '3' exceeds the number of active scopes.
Parameter name: scopeID
Actual value was 3.
At C:\Users\cmagnuson\OneDrive - tervis.com\Documents\WindowsPowerShell\Modules\testmodule2\testmodule2.psm1:21 char:5
+ Get-Variable -Scope 3
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-Variable], PSArgumentOutOfRangeException
+ FullyQualifiedErrorId : ArgumentOutOfRange,Microsoft.PowerShell.Commands.GetVariableCommand
The reason for trying to do this is enable PSTemplateEngine to function without requiring someone to pass in a hash table with the variable names and values or something like -ArgumentsList
and then require them to have a param block in their template.
Invoke-ProcessTemplatePath
specifically could point to a tree structure with many files each containing many variables each. A single imaginary -ArgumentsList
might contain 40 values and each template file would have to have a param block 40 parameters long since the parameters are passed position ally. The hash table would work better but I still want to avoid it if possible.
How can I access the variable $Var
from the calling function's module's scope from the called function without passing a hashtable, the value of $Var
, a parameter with the value of $(Get-Variable)
, etc.?
Upvotes: 3
Views: 2853
Reputation: 1702
Since the OP didn't mention how they actually solved the problem (only alluding to a hash table but not describing how it's done) I'll flesh it out one way to do it since it may not be obvious.
First I create a variable to test with because your environment will contain other variables and I want to demo accessing one of them. The next line loads a hash table with references to all the variables accessible from the current scope. The hash table can then be passed to any function.
$x = 'Some value'
$a = Get-ChildItem variable:|&{begin{$h=@{}}process{$h[$_.name]=$_}end{$h}}
Because the hash table stores references the variable's content can be modified.
$a['x'].value = 'A different value'
So a working copy of the OP's code could look like this:
$ModulePath = $env:PSModulePath.Split(";")[0]
@'
$var = 1
function Write-Stuff {
$var
}
function Test-WriteStuff {
Write-Stuff
$a = Get-ChildItem variable:|&{begin{$h=@{}}process{$h[$_.name]=$_}end{$h}}
Write-Stuff2 $a
Write-Stuff
}
'@ | Out-File $ModulePath\TestModule1\TestModule1.psm1
@'
function Write-Stuff2 {
param ($all)
$all['var'].value
$all['var'].value=101
}
'@ | Out-File $ModulePath\TestModule2\TestModule2.psm1
Where the results would be
1
1
101
Upvotes: 0
Reputation: 47792
Modules exist in their own world; it's not a scope but it sort of acts like one. From about_Scopes:
Sessions, modules, and nested prompts are self-contained environments, but they are not child scopes of the global scope in the session.
The privacy of a module behaves like a scope, but adding a module to a session does not change the scope. And, the module does not have its own scope, although the scripts in the module, like all Windows PowerShell scripts, do have their own scope.
So you're not going to be able to access the variable in one module from another using scope semantics.
You can export a variable from one of the modules using Export-ModuleMember -Variable
.
I don't fully understand what that templating engine is or does, but on the surface, what you're trying to do sounds very wrong.
Passing values as parameters is probably the right way to pass data into functions.
Upvotes: 3