Understanding Scope


Because PowerShell is built on the .NET Framework, it inherits some of the more interesting aspects of that Framework including the concept of scope. This is something we touched on very briefly in Chapter 1, but it's time to really dive in and understand how it affects your scripts.

As shown in Figure 4-2, PowerShell offers many different scopes. You can think of a scope as a kind of container in which code runs. You start with the global scope, which is where PowerShell itself runs. If you run a script within PowerShell, your script receives its own child scope called a script scope-and each script you run gets its own scope. If a scope contains one or more functions, then the functions also get their own child scopes.

image from book
Figure 4-2: Scopes in PowerShell

Now consider this PowerShell script:

 $var = "A" function foo {   write-host "2: $var"   $var = "B"   write-host "3: $var" } write-host "1: $var" foo write-host "4: $var" 

What output do you suppose you'd get? Let's walk through it one line at a time to find out.

  • First, a variable named $var is created and assigned the value "A." This variable exists in the script's local scope.

  • Then the script defined a function named foo - we'll come to that in a moment.

  • Next, the script outputs the contents of $var, calls the foo function, and outputs the contents of $var again.

  • Inside the foo function, the value of $var is output, and then reassigned the new value "B," and then output again.

The output looks like this:

 1: A 2: A 3: B 4: A PS C:\> 

Why? First, the script creates $var and assigns it the value "A." This is reflected in the first line of output. Then, the function named foo is called. It has its own unique scope, but it inherits the variables of its parent scope-specifically, the script. So as shown on the second line of the output, $var carries in the same value of "A." But then foo assigns "B" to $var. However, child scopes only have read access to parent variables; they can't change them. So when foo tries to change $var, a new $var is created inside foo's function scope, and "B" is put into that new copy of $var. That's what's shown in the third line of output. As confirmed by the fourth line of output, the script scope's copy of $var never changed from "A."

Admittedly, this can be very confusing. And there's still more to come. Check out this modified script:

 $var = "A" function foo {   write-host "2: $var"   $var = "B"   write-host "3a: $var"   $script:var = "C"   write-host "3b: $script:var" } write-host "1: $var" foo write-host "4: $var" 

Notice that we've added $script:var. This is a specific reference to "the variable named $var from the script scope." Using this special naming convention, our function foo can modify variables from its parent scope, as shown in this output:

 1: A 2: A 3: B 3b: C 4: C PS C:\> 

The script sets $var to "A," and then runs foo. Foo inherits that value of "A." When it tries to assign "B" to $var, a new local copy of $var is created. However, $script:var assigns the value "C." As shown in the last line of output, that changed the script's copy of $var, rather than creating a whole new variable.

The same applies to the shell's global scope. Try this - save the following script as ScopeTest.ps1:

ScopeTest.ps1

image from book
 write-host "1 (var): $var" $var = "SCRIPT" $global:var = "GLOBAL" write-host "2a (var): $var" write-host "2b (global): $global:var" function foo {   write-host "3a (var): $var"   write-host "3b (global): $global:var"   $var = "LOCAL"   $script:var = "SCRIPT!"   write-host "4a (var): $var"   write-host "4b (global): $global:var"   write-host "4c (script): $script:var" } foo write-host "5a (var): $var" write-host "5b (global): $global:var" write-host "5c (script): $script:var" 
image from book

Now, run the following in PowerShell:

 $var = "HELLO" 

Finally, run the script. You'll get output somewhat like this:

 PS C:\> $var = "HELLO" PS C:\> test\scopetest 1 (var): HELLO 2a (var): SCRIPT 2b (global): GLOBAL 3a (var): SCRIPT 3b (global): GLOBAL 4a (var): LOCAL 4b (global): GLOBAL 4c (script): SCRIPT! 5a (var): SCRIPT! 5b (global): GLOBAL 5c (script): SCRIPT! PS C:\> 

What's happening? First, we created a global variable named $var with the value "Hello." This exists in the shell's scope, or the global scope. Then we ran the script. Line 1 proves that the script's scope inherited the value of "Hello" for $var.

The script then assigns "Script" to $var. However, since $var already exists in the parent scope, a new copy is created as shown on line 2a of the output. The script also changes $global:var to "GLOBAL," successfully modifying the variable in the parent scope as shown on line 2b of the output.

The script goes on to call function foo. That starts by proving that $var was inherited from the parent scope (the script) on line 3a, and the function scope has access to the global scope as shown on line 3b. The function then tries to change $var-creating a separate local copy of the variable with the value "Local" inside as shown on line 4a of the output. Line 4b shows that the shell's global copy of $var is unchanged, and line 4c shows that the script's copy of $var was changed by assigning "Script!" to $script:var. So, at this point there are three copies of $var running around: 1) function-local, 2) script-local, and 3) shell-global. When function foo exits, its local copy of $var ceases to exist.

Line 5a confirms that the script-local copy of $var has been changed to "Script!" a change that is also shown on line 5c. 5b of the output confirms that the global copy is still "Global."

All of this can become even more complicated. For example, if one function calls another, then the second function is a child of the first function. The second function can read all of the first function's variables, but it can't directly change them, just as in the above examples.

That, in fact, is a good summary of how variables work across scopes: Any variable declared in a given scope is visible and read-only in all child scopes. However, if a child changes a variable, a new copy is created that's owned by the child, unless the child specifically refers to the parent variable through a fully-qualified reference like $script:var.

What's the moral of this story? Don't reuse variable names inside child scopes! Your life will be much easier if you never try to change a variable that has already been used at a higher-level (parent) scope. If you do, then keep in mind that casual (non-qualified) reference to a parent's variable is read-only. Any attempt to change the variable will result in a new, local copy being created. For example, qualified references to parent variables such as $global:var or $script:var permit changes to the variables' values.

However, you can keep a variable private to a scope, which prevents any child scopes from seeing it. Make the variable private as follows:

 $private:var = "You can't see me!" 

The scope in which this is declared will be able to access the variable using the name $var. However, no other scope will have access to the variable no matter how they try and refer to it.

Scoping also applies to functions. If you declare a function, it's only available within the scope in which you declared it and within child scopes. So if a script declares a function, that function can't be used within the global shell. If a function is declared in the global shell, then it's accessible to all scripts run from that shell since those scripts run in child scopes. You can have nested functions, which are functions that are declared within other functions. The nested functions are only available from within the function that encloses them. Here's an example:

 function outer {   function inner1 {     #code goes here   }   function inner2 {     #code goes here   } } 

In this example, only code inside the function Outer can call functions Inner1 or Inner2. If Outer were inserted into a script, the script itself couldn't see Inner1 or Inner2. In other words, the script would only be able to call Outer. Nesting functions in this fashion allows you to encapsulate code, which makes the function completely self-contained and allows code within the function to be further modularized into nested "sub-functions."



Windows PowerShell. TFM
Internet Forensics
ISBN: 982131445
EAN: 2147483647
Year: 2004
Pages: 289

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net