Search code examples
powershellscopingstrict

Why does this script only work when run directly from powershell but not from another script


I am deploying a Logic App that triggers on event grid. After deployment I need to authenticate the API connection. I found this script https://github.com/logicappsio/LogicAppConnectionAuth and it works fine. But only if I run it manually from Powershell. But I want to call that script from another script, from the one that deploys the Logic App, after the New-AzResourceGroupDeployment command.

Doing that does not work. I get the error:

The variable '$Scope' cannot be retrieved because it has not been set.
At C:\location\LogicAppConnectionAuth.ps1:16 char:113
+ ... Browser -Property @{Width=580;Height=780;Url=($url -f ($Scope -join " ...
+                                                            ~~~~~~
    + CategoryInfo          : InvalidOperation: (Scope:String) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : VariableIsUndefined

What is that $scope variable? It is not defined in the script, still it exists when run separately, but not when I run the script from another script.


Solution

  • As you've discovered, in your specific case the problem was a normally benign bug that surfaced due to the caller enabling strict mode. However, the problem is a fundamental one, as discussed below.


    The underlying problem is that, as of v6.2, PowerShell's strict mode is dynamically scoped, not lexically.

    This means that using Set-StrictMode doesn't just affect the function or script in which it is called, but also other functions and scripts called from that function or script that live in the same scope domain, that is, if both the caller and callee are either defined outside of a module or defined in the same module.

    In other words: A script or function may unwittingly inherit a strict-mode setting and may break, if it wasn't designed for that mode.

    For instance, the callee may assume that the strict mode is at its default, -Off, and rely on (loosely) testing the non-existence of a variable $var with if (-not $var), for instance. If a caller in the same scope domain has Set-StrictMode -Version 1 or higher set, the callee will break.

    Workarounds:

    • If you control the script or function being invoked:

      • Explicitly call Set-StrictMode at the start of your script or function with the mode that your code is designed for.

        • Given the behavior described, it is worth doing that as a matter of habit in non-module scripts and functions.
      • Note: Bear in mind that this mode is then also in effect for scripts/functions in the same scope domain called from within the script or function at hand, as well as scripts/functions they call. (By contrast, code higher up the call stack is not affected).

    • Otherwise:

      • Temporarily disable strict mode in order to call a script or function that expects strict mode to be off (or at a lower version):

        # Temporarily turn strict mode off.
        Set-StrictMode -Off
        
        # Call the script or function that breaks with (a higher) strict mode in effect
        ...
        
        # Re-enable strict mode for your code.
        # Note that there's no way to *get* (and save for later restoring)
        # the specific mode in effect.
        Set-StrictMode -Version <n> 
        

    Future outlook:

    This RFC suggests introducing a lexically scoped strict mode to avoid the problems with the current behavior.