question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

When Invoke-Expression wrapped in function then Import-Module leads to weird variable scope leaking

See original GitHub issue

Hello! 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:closed
  • Created 3 years ago
  • Comments:12

github_iconTop GitHub Comments

1reaction
4mitchcommented, Mar 16, 2021

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

$module1 = New-Module {
    $Internal = 'Module 1'

    function Get-Var {
        param ($name)
        Get-Variable -Name $name -Scope script
    }

    function Invoke-Exp {
        param([string] $ScriptFile)
        Invoke-Expression $ScriptFile
    }
}

$module2 = New-Module {
    $Internal = 'Module 2' 
    function Get-Var {
        param ($name)
        Get-Variable -Name $name -Scope script
    }

    function Invoke-Exp {
        param([string] $ScriptFile)
        Invoke-Expression $ScriptFile
    }
}

Import-Module -ModuleInfo $module1 -Force
$Internal = 'Script'
"Var value is: $((Get-Var -name Internal).Value)"
Invoke-Exp '
    Import-Module -ModuleInfo $module2
    "Var value is: $((Get-Var -name Internal).Value)"
    "Var value is: $((Get-Var -name Internal).Value)"
'

"Var value is: $((Get-Var -name Internal).Value)"

Returning:

Var value is: Module 1
Var value is: Module 2
Var value is: Module 2
Var value is: Module 1
0reactions
SeeminglySciencecommented, Mar 16, 2021

Seems better not to use module variables in functions at all.

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, no begin or process blocks, and poor performance to name a few.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Powershell access caller scope variables across modules
I receive the following error: Invoke-Expression : The variable '$id' cannot be retrieved because it has not been set. The interesting thing is ......
Read more >
Best practice: import functions in a module · Issue #18740
When using Invoke-Expression to run a command that the user enters, verify that the command is safe to run before running it. In...
Read more >
Overusing lambda expressions in Python
I'll call the lambda syntax itself a lambda expression and the function you get back from this I'll call a lambda function. Python's...
Read more >
Invoke-Expression (Microsoft.PowerShell.Utility)
The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command.
Read more >
[AskJS] why are arrow functions used so universally ...
I've never really understood this: why would you use a `const` here and an arrow function when you can just use the function...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found