When Invoke-Expression wrapped in function then Import-Module leads to weird variable scope leaking
See original GitHub issueHello! During debugging legacy code I faced with weird behaviour. I have a module without Export-ModuleMember statements. And two nested invoked scripts. And it occurs that variables defined in imported module are not available in script imported the module - this is ok, because no Export-ModuleMember . But! but these variables become visible in next nested script! And even after I imported one more module there with redefining variables with same name. And again, imported module variables become visible in one more nested invoked script 🤔
But the main point that this is a case only when I wrapped Invoke-Expression command into function, which resides in that imported module.
Steps to reproduce
How to reproduce - download enclosed files into folder and run
& {.\MainScript.ps1}
. This will results in what shown in Actual behavior part - unexported (leaked) variable $WAT1 is visible from nested scripts scope (btw it does not have (Get-Variable WAT1).Module
property value!). But it should not be visible, since it is not exported from the module.
ImportModuleScopeLeakExample.zip
MainScript.ps1
Import-Module -Name ${PSScriptRoot}\ModuleTEST_wo_Export_1.psm1 -Force
try {
Get-Variable WAT1 -ErrorAction Stop
Write-Host "I don't want to read that line, must be alredy catched due to error in previous line." -ForegroundColor Red
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Host "I awaits this catch, cause WAT1 is not exported."
}
Write-Host "Here WAT1 should not be visible here $WAT1 ***"
invoke-exp -scriptfile "${PSScriptRoot}\Level1ChildScript.ps1"
# Invoke-Expression '.\Level1ChildScript.ps1'
try {
$(Get-Variable WAT1 -ErrorAction Stop | select Value )
Write-Host "I don't want to read that line, must be alredy catched due to error in previous line." -ForegroundColor Red
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Host "I awaits this catch, cause WAT1 is not exported."
Write-Host "Still no WAT1 variable awaited here."
}
Level1ChildScript.ps1
Import-Module -Name ${PSScriptRoot}\ModuleTEST_wo_Export_2.psm1 -Force
try {
Get-Variable WAT1 -ErrorAction Stop
Write-Host " I don't want to read that line, must be alredy catched due to error in previous line." -ForegroundColor Red
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Host " I awaits this catch cause WAT1 is not exported."
}
Write-Host " Here WAT1 should not be visible here - $WAT1 ***"
# Invoke-Expression ${PSScriptRoot}\Level2ChildScript.ps1
# . ${PSScriptRoot}\Level2ChildScript.ps1
invoke-exp -scriptfile ${PSScriptRoot}\Level2ChildScript.ps1
try {
$(Get-Variable WAT1 -ErrorAction Stop | select Value )
Write-Host " I don't want to read that line, must be alredy catched due to error in previous line." -ForegroundColor Red
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Host " I awaits this catch, cause WAT1 is not exported."
Write-Host " Still no WAT1 variable awaited here."
}
Level2ChildScript.ps1
try {
Get-Variable WAT1 -ErrorAction Stop
Write-Host " I don't want to read that line, must be alredy catched due to error in previous line." -ForegroundColor Red
}
catch [ItemNotFoundException] {
Write-Host " I awaits this catch cause WAT1 is not exported."
}
Write-Host " Here WAT1 should not be visible here - $WAT1 ***"
ModuleTEST_wo_Export_1.psm1
$WAT1 = 'SUPER PUPER ONE FROM MODULE 1!'
function send-message_wait_color(){
param([string] $functionName)
write-host "Start processing function:$functionName.." -ForegroundColor Yellow
}
function send-message_complete_color(){
param([string] $functionName)
write-host $functionName is Done -ForegroundColor Green
}
function invoke-exp(){
param([string] $scriptfile,[string] $filepath='')
send-message_wait_color -functionName "deployment of: $scriptfile"
Invoke-Expression $scriptfile
send-message_complete_color -functionName "deployment of: $scriptfile"
}
ModuleTEST_wo_Export_2.psm1
$WAT1 = 'SUPER PUPER ONE FROM MODULE 2!'
function invoke-exp(){
param([string] $scriptfile,[string] $filepath='')
send-message_wait_color -functionName "deployment of: $scriptfile"
Invoke-Expression $scriptfile
send-message_complete_color -functionName "deployment of: $scriptfile"
}
Expected behavior
PS C:\dev\PowerShell\BUG> & {.\MainScript.ps1 }
I awaits this catch, cause WAT1 is not exported.
Here WAT1 should not be visible here ***
I awaits this catch cause WAT1 is not exported.
Here WAT1 should not be visible here - ***
I awaits this catch cause WAT1 is not exported.
Here WAT1 should not be visible here - ***
I awaits this catch, cause WAT1 is not exported.
Still no WAT1 variable awaited here.
I awaits this catch, cause WAT1 is not exported.
Still no WAT1 variable awaited here.
Actual behavior
PS C:\dev\PowerShell\BUG> & {.\MainScript.ps1 }
I awaits this catch, cause WAT1 is not exported.
Here WAT1 should not be visible here ***
Start processing function:deployment of: C:\dev\PowerShell\BUG\Level1ChildScript.ps1..
Name Value
---- -----
WAT1 SUPER PUPER ONE FROM MODULE 1!
I don't want to read that line, must be alredy catched due to error in previous line.
Here WAT1 should not be visible here - SUPER PUPER ONE FROM MODULE 1! ***
Start processing function:deployment of: C:\dev\PowerShell\BUG\Level2ChildScript.ps1..
WAT1 SUPER PUPER ONE FROM MODULE 2!
I don't want to read that line, must be alredy catched due to error in previous line.
Here WAT1 should not be visible here - SUPER PUPER ONE FROM MODULE 2! ***
deployment of: C:\dev\PowerShell\BUG\Level2ChildScript.ps1 is Done
Value : SUPER PUPER ONE FROM MODULE 1!
I don't want to read that line, must be alredy catched due to error in previous line.
deployment of: C:\dev\PowerShell\BUG\Level1ChildScript.ps1 is Done
I awaits this catch, cause WAT1 is not exported.
Still no WAT1 variable awaited here.
Environment data
Name Value
PSVersion 7.2.0-preview.1 PSEdition Core GitCommitId 7.2.0-preview.1 OS Microsoft Windows 10.0.17763 Platform Win32NT PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 WSManStackVersion 3.0
Issue Analytics
- State:
- Created 3 years ago
- Comments:12
Top GitHub Comments
Wow! Thank you! Now I can see that. Please close the ticket! I must be careful with all that scope. Seems better not to use module variables in functions at all. All other functions from module will behave similar 🤔 like this
Returning:
I’d more recommend avoiding
Invoke-Expression
tbh. The scriptblock method of managing callbacks described in this comment avoids all those issues as scriptblocks are bound to the module they’re created in.Invoke-Expression
has a lot of other issues as well. Potential security issues if used with user input, no error/verbose/etc streams, limited$MyInvocation
info, nobegin
orprocess
blocks, and poor performance to name a few.