Be careful with that scope

I did some more reading on Powershell variable scope definition today and thought I’d write up what I found.  I’m undecided on the best practice for writing functions especially given the patchy documentation on passing variables to a function.  One way is to create some variables in the main script and just use them in the function although there are some caveats to doing that which I’ll cover in a second and this is definitely not best practice.  The other way is to explicitly pass the variables to the script, even ones you are not going to change within the function itself.  The benefit of the latter option is when reusing the function in another script you can see all the variables that need to be available in the parameter definition.  This approach can lead to long parameter lists and for simplicity I think most people tend to use the former strategy and this is where a discussion on variable scope is useful because it can have a big impact on the functionality of the script.

Variables have the following scope: GLOBAL, SCRIPT , LOCAL and PRIVATE

  • Global – available everywhere in the run space – so even without dot sourcing the script the variable will be available at the command line. ( dot sourcing is this  . .\mypowershellscript.ps1 the period followed by a space makes the script stay loaded in memory after it completes and all script variables are available at the command line or to any subsequent scripts you run in the same run space).
  • Script – available everywhere inside the script and inside any scripts called within the parent script.  As far as the script is concerned this is the same as Global because it’s available in every function too.  When the script finished though the variable is destroyed and you cannot reference it from the command line.  NOTE: this does not apply within an ISE which will often make every variable available as if it’s a global variable.  This means you need to test your script at least once from the command line if it calls other scripts.  What works in the ISE may not when you run it as a standalone script.
  • Local – Is this really a scope?  Local really means that the variable is accessible in the current scope.  So a script scope variable is local to the script and also local in any called function available within the script.
  • Private – is only available within the local scope where it was created and does not bleed into any other scope, even if you create the variable at the beginning of the main script the variable cannot be seen inside any of the called functions or scripts.

Pretty complex then!

here’s a little script that demonstrates this and it’s a modified one taken from this blog posting: http://www.jsnover.com/blog/2013/03/17/variable-scopes-and-private/

But before I get into the script just some quick notes for you……got your pens ready?

  1. You can create a variable by simply assigning it a value, even if that value is NULL, e.g. $MyVariable = $null. It’s quicker to create a variable by just declaring it and assigning a value which is why most people do it this way.
  2. A variables scope is assigned implicitly based on where it is created, e.g. if you create it in the main script is it ‘Script Scope’ and if you create it in a function it is effectively ‘Private  Scope’ to that function or more commonly referred to as ‘Local Scope’, i.e. it cannot be seen outside of the function.
  3. You can specify a variables scope when creating it by using the ‘new-variable’ cmdlet and specify the scope using the -scope parameter or just use Scope “Prefixing”, i.e. $<scope>:<variableName>.
    It’s not recommended to create variables of ‘Global Scope’ in case they interact with other scripts running at the same time.
  4. A variable in the main script, if its not inside a function is is ‘script scope’ unless you explicitly define it as global or Private using $global:MyVariable or $private:MyVariable.
  5. All child functions have ‘read access’ to the variables created by the parent or grandparent or great gran parent or …. you get the idea.
  6. If you don’t prefix the variable then PowerShell will search for the variable in the ‘Local Scope’ first , then ‘Script Scope’ and then finally ‘Global Scope’ before giving up and sulking.  Don’t forget point 4 if the variable is not in the ‘Local Scope’ then it’s a read only variable but read ahead to point 8 before you draw any conclusions 🙂 ( this is a bit like Bill and Teds excellent Adventure – just not as interesting)
  7. When assigning a value to a variable inside a child function, e.g. $MyVariable = 2, PowerShell will search the ‘Local Scope’ to see if it can find the variable and if it does then it updates the value if it can’t then it creates a new ‘Local Scope’ variable to store the value.  Read that carefully!  If the variable was instantiated in the main script then it assumes that you want to create a new ‘Local Scope’ variable as it can’t update the ‘Script Scope’ variable this is read only see point 5, e.g. Setting $myVariable = 1 in the main script and then setting it’s value to 2 inside the function like this $MyVariable = 2 is probably not doing what you expected it to do.  PowerShell creates a ‘Local Scope’ variable in the child function and when you return to the ‘Parent Scope’  calling line, $MyVariable is 1 not 2.  The two variables are in different scopes and now you are back in the main script you are looking at the ‘script scope’ variable which was set to 1.
  8. You can still update a parent scope variable inside a child scope function if you use Scope “Prefixing”, i.e. $script:MyVariable = 1 will update the ‘script scope’ variable.
  9. Point 7 can mean a world of pain when you apparently overwrite variables in child scopes ( functions called from the main script)
  10. It’s far too easy to be sloppy when programming and naming variables – and even easier to get spaghetti sauce on a white shirt.

#start with a clean slate

Remove-Variable -Name ScriptBlock -ErrorAction Ignore  

$ScriptBlock = “top”  

$Private:onlyinmain = “only in the main”

“======================================================”

“In main,ScriptBlock = “ + $ScriptBlock  

“In main, middlevariable = “ + $middlevariable

“In main, onlyinmain = “ + $onlyinmain

“======================================================”
function child 

{  

    “======================================================”

    “In child, ScriptBlock = “ + $ScriptBlock

    “In child, Script:ScriptBlock = “ + $Script:ScriptBlock

    “In child, onlyinmain = “ + $onlyinmain 

    “In child, middlevariable = “ + $middlevariable

    “======================================================”

}  

function middle  

{  

    $Private:ScriptBlock = “middle”  

    $middlevariable = “Middle”  

    “======================================================”

    “In middle, ScriptBlock = “ + $ScriptBlock  

    “In middle, Script:ScriptBlock = “ + $Script:ScriptBlock

    “In middle, onlyinmain = “ + $onlyinmain 

    “In middle, middlevariable = “ + $middlevariable

    “======================================================”

    child  

}  

middle

“======================================================”

“In main,ScriptBlock = “ + $ScriptBlock  

“In main, middlevariable = “ + $middlevariable

“In main, onlyinmain = “ + $onlyinmain

“======================================================”

. middle

“======================================================”

“In main, middlevariable = “ + $middlevariable

“======================================================”

There are two functions middle and child.  Child is nested in the middle function so is only available to the middle function.  You can’t call it from the main script.  At the start of the script I create a private variable just to prove you can’t see it anywhere but in the main script body.  Then I call the middle function and this prints to screen the values of the variables – then middle calls child and does the same.  Notice the variable scriptblock in middle is of private scope to middle and is not available in child – so Powershell searches back through the scopes and finds the scriptblock variable we set in the main script.   the variable we create in middle, middlevariable is available in middle and child but not in the main script.

======================================================
In main, ScriptBlock = top
In main, middlevariable =
In main, onlyinmain = only in the main
======================================================
======================================================
In middle, ScriptBlock = middle
In middle, Script:ScriptBlock = top
In middle, onlyinmain =
In middle, middlevariable = Middle
======================================================
======================================================
In child, ScriptBlock = top
In child, Script:ScriptBlock = top
In child, onlyinmain =
In child, middlevariable = Middle
======================================================
======================================================
In main, ScriptBlock = top
In main, middlevariable =
In main, onlyinmain = only in the main
======================================================
======================================================
In middle, ScriptBlock = middle
In middle, Script:ScriptBlock = middle
In middle, onlyinmain = only in the main
In middle, middlevariable = Middle
======================================================
======================================================
In child, ScriptBlock = middle
In child, Script:ScriptBlock = middle
In child, onlyinmain =
In child, middlevariable = Middle
======================================================
======================================================
In main, middlevariable = Middle
======================================================

So what’s the best strategy for calling functions and using variable scope…… Hmmmm I’ll need to think about this some more before I can post my opinions on this one now that I have a better understanding of scope I may  change my current script writing style to utilise this knowledge.

If your still with me and interest why I suddenly started researching this – it’s because I’ve been handed over the development of an application built on a bunch of powershell scripts written by someone else.  Their style was to use . sourcing every where and call global variable scope which works but it’s very hard to follow and debug.  We need to maintain some variable naming standards and always think about the scope of variables.  I’ve grown to hate . sourcing unless it’s for a quick and dirty script when is when that technique is brilliant.  In a production level script I’d advise you stay away from it.  The other thing I encountered in these scripts was using dos command and paths based on using dots to navigate to different directories – wow, like I don’t have enough things to worry about now we just added folder location of the running script and all the .sourced scripts running in different locations.

One last gripe with . sourcing before I go, when you get an error it give you a line number that nears no relation to the line number in any of the scripts and the actual line of code could be in any one of the . sourced scripts called in the main script.  ARRRRGHHHHHH  – nothing a few beers wont fix though – well you wont care about fixing it, for a while at least and to be fair us scripters could probably do with being more sociable so maybe that’s not such a bad thing after all 🙂 .

Here are some more links discussing variable scope – if I haven’t already bored you to death with this post.

http://blogs.technet.com/b/heyscriptingguy/archive/2010/02/11/hey-scripting-guy-february-11-2010.aspx

http://www.jsnover.com/blog/2013/03/17/variable-scopes-and-private/

http://www.sapien.com/blog/2013/03/06/first-rule-of-powershell-scoping-rules/

Sadly I’ve just broken rule No. one from the link above…..

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.