Scope - PowerShell scripting and automation - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 3. PowerShell scripting and automation

Chapter 22. Scope

This chapter covers

· Understanding scope

· Using scope

· Using best practices

Scope is one of the most confusing things about PowerShell when you’re a newcomer, and even experienced gurus get tripped up by it from time to time. If you’re just using the shell as a shell—meaning you’re running commands and seeing the results onscreen—then scope won’t affect your life much. It’s when you start writing scripts, functions, and modules that scope comes into play.

22.1. Understanding scope

Scope is a form of containerization. Certain elements in PowerShell are considered scoped elements, and when you create one it exists only within the container, or scope, in which you created it. Generally speaking, it can only be used from within that scope as well. There are obviously a lot of rules and caveats around this, but we’re going to start with the basics and general realities and then diverge from there.

Take a look at figure 22.1, which illustrates the relationship between different kinds of scope.

Figure 22.1. Scopes can have both child and parent scopes, creating a hierarchical relationship.

There are three main types of scope in PowerShell. They all do exactly the same thing—the names just reflect the ways in which these scopes are created.

· A global scope is created when you start a new PowerShell session, such as opening a new console window. PowerShell refers to a shell instance as a runspace. With the normal console application, you can only have one runspace, so an instance of the shell equals one runspace. But other hosting applications can have multiple runspaces within the same application. In the PowerShell ISE, for example, you can select New PowerShell Tab from the File menu and in doing so create an additional runspace within the same application window. Because it’s possible to have multiple runspaces active at the same time, such as when you open two console windows side by side, you can have multiple global scopes. A session created on another machine using PowerShell remoting has its own global scope that’s totally independent of the scope from which the session was created.

· A script scope is created whenever PowerShell runs a new script (with one exception, called dot sourcing, which we cover later in this chapter). As illustrated in figure 22.1, a script can run another script, and the second one (Set-Something.ps1 in the illustration) gets its own scope.

· A function scope is created whenever you define a function. Functions can be defined within the shell itself (meaning within the global scope), or within a script, or even within another function.

Using scope

PowerShell v3 introduced another level of scope called using. It doesn’t fit into the hierarchical model described in figure 22.1, and the rules laid down in this chapter don’t apply, because it’s designed to be used in situations where you want to access a local variable in a remote session. The definition from the help file about_scopes is:

Using is a special scope modifier that identifies a local variable in a remote command. By default, variables in remote commands are assumed to be defined in the remote session.

More on using can be found in about_Remote_Variables. It’s used like this:

PS C:\> $s = New-PSSession -ComputerName W12Standard

PS C:\> $dt = 3

PS C:\> Invoke-Command -Session $s -ScriptBlock {Get-WmiObject -Class

Win32_LogicalDisk -Filter "DriveType=$dt"}

Invalid query "select * from Win32_LogicalDisk where DriveType="

+ CategoryInfo : InvalidArgument: (:)

[Get-WmiObject], ManagementException

+ FullyQualifiedErrorId : GetWMIManagementException,

Microsoft.PowerShell.Commands.GetWmiObjectCommand

+ PSComputerName : W12Standard

PS C:\> Invoke-Command -Session $s -ScriptBlock {Get-WmiObject -Class

Win32_LogicalDisk -Filter "DriveType=$Using:dt"}

DeviceID : C:

DriveType : 3

ProviderName :

FreeSpace : 126600425472

Size : 135994011648

VolumeName :

PSComputerName : W12Standard

Create a remoting session to another machine and define a local variable. If you try to use that variable (which is local) in the remote session, you’ll get the error shown in the first command, but if you employ the using scope, it works.

In PowerShell v2, you could do this:

PS C:\> Invoke-Command -Session $s -ScriptBlock {param($d) Get-

WmiObject -Class Win32_LogicalDisk -Filter "DriveType=$d" }

-ArgumentList $dt

Using is a valuable addition to your toolbox when you’re working with remote machines.

As you can see in figure 22.1, the global, script, and function scopes create a hierarchy: The global scope is always at the top. That contains one or more function scripts (PowerShell’s Help command is actually a function, not an alias, which is defined in the global scope). The global scope will also contain a script scope whenever you run a script. A script or function can create additional scopes by containing functions or running other scripts.

When one scope hosts another, such as when our Test1.ps1 ran Set-Something.ps1 in figure 22.1, the higher-level scope is referred to as a parent and the newly created scope as a child. So, in our illustration, Test1.ps1, Test2.ps1, and the Help function are all children of the global scope. They all refer to the global scope as their parent. A child, such as Test2.ps1, can also be a parent to its own children, which in our illustration is the function Do-This.

As we mentioned earlier, scope is only important in relation to scoped elements. The following elements of PowerShell are scoped:

· PSDrives

· Variables

· Functions

· Aliases

Here are the general rules for scope, along with some examples for each:

· When you create a new scoped element, it exists only within the current scope. That is, if Test1.ps1 defines a new alias named “Fred,” that alias won’t be available in the global scope. Parent scopes can never see anything in a child scope.

· If you try to access an element that doesn’t exist in the current scope, PowerShell will go up the scope relationships from child to parent to see if that item exists somewhere else. For example, let’s say the Set-Something.ps1 function runs Dir, which is an alias to Get-ChildItem. Assuming Set-Something.ps1 didn’t define Dir as an alias, PowerShell would look to see if it was defined in the parent scope, Test1.ps1. Assuming it was also not defined there, PowerShell would look to the next parent, which is the global scope. The alias is definitely defined there, and so it’d run normally.

· You’re allowed to create a scoped element whose name conflicts with a parent scope’s definition. In other words, Test1.ps1 could create a new alias named Dir that pointed to the Get-WmiObject cmdlet. From then on, until the script finished running, executing Dir would run Get-WmiObject, not Get-ChildItem. In effect, the child scope’s local definition would prevent PowerShell from going up to the parent scope to find the normal definition of Dir.

· When a scope is finished, everything in it, including all child scopes, is destroyed and removed from memory. Assume Set-Something created a variable named $x. Once Test1.ps1 finished running, that $x—and everything else defined by Test1.ps1 or one of its children—would go away.

Note

Our example of redefining the Dir alias is strictly theoretical. In practice, PowerShell’s creators have made it more or less impossible to overwrite the built-in aliases. We’ll use a more practical example in the next section of this chapter.

We’re going to cover plenty of examples of these behaviors, because we realize they can be a little confusing. But we have one more thing to point out: Plenty of PowerShell elements are not scoped. For example, if Test1.ps1 were to load a module by using the Import-Module cmdlet, that module would remain loaded even after the script finished running. That’s because modules aren’t scoped elements: Messing with them in any way affects the entire runspace, or global scope.

22.2. Observing scope in action

Perhaps the easiest way to understand scope’s general rules is to see them in action. To do that, you’ll start by creating a little demonstration script, shown in the following listing.

Listing 22.1. Scope demonstration script, test.ps1

function Do-This {

Write-Host "Inside the function" –Foreground Green

Write-Host "Variable contains '$variable'" –Foreground Green

$variable = 'Function'

Write-Host "Variable now contains '$variable'" –Foreground Green

Gw -Class Win32_BIOS

}

Write-Host "Inside the script" –Foreground Red

$variable = "Script"

Write-Host "Variable now contains '$variable'" –Foreground Red

New-Alias -Name gw -Value Get-WmiObject -Force

Gw -Class Win32_ComputerSystem

Do-This

Write-Host "Back in the script" –Foreground Red

Write-Host "Variable contains '$variable'" –Foreground Red

Write-Host "Done with the script" –Foreground Red

Save the script as C:\Test.ps1. Then, open a brand-new PowerShell console window (just to make sure you’re starting fresh) and run the script. Here are the results:

PS C:\> ./test

Inside the script

Variable now contains 'Script'

Domain : company.pri

Manufacturer : VMware, Inc.

Model : VMware Virtual Platform

Name : WIN-KNBA0R0TM23

PrimaryOwnerName : Windows User

TotalPhysicalMemory : 1073209344

Inside the function

Variable contains 'Script'

Variable now contains 'Function'

SMBIOSBIOSVersion : 6.00

Manufacturer : Phoenix Technologies LTD

Name : PhoenixBIOS 4.0 Release 6.0

SerialNumber : VMware-56 4d 47 10 6b f6 d7

9e 4b

Version : INTEL - 6040000

Back in the script

Variable contains 'Script'

Done with the script

PS C:\> gw -class win32_operatingsystem

The term 'gw' is not recognized as the name of a cmdlet, function,

script file, or operable program. Check the spelling of the name, or if

a path was included, verify that the path is correct and try again.

At line:1 char:3

+ gw <<<< -class win32_operatingsystem

+ CategoryInfo : ObjectNotFound: (gw:String) [], Comman

dNotFoundException

+ FullyQualifiedErrorId : CommandNotFoundException

Note that the script used a foreground color with Write-Host to make it clear when scope changes. Let’s walk through exactly what happened, step by step:

1. You run the script.

2. The first executable line of the script displays “Inside the script.”

3. The next line of the script puts Script inside $variable. The $variable element hasn’t been seen before, so PowerShell creates it within this scope.

4. The next line displays the contents of the variable, resulting in Variable now contains 'Script'—all good so far.

5. The script defines an alias, Gw, that points to Get-WmiObject. This alias is created within the script’s own scope.

6. The script runs its Gw alias, creating some output in the shell.

7. Now the script executes the Do-This function, which is located within the script. We’re now in scope #3: Scope #1 is the global scope, scope #2 is the Test.ps1 script, and the third scope is the interior of the Do-This function.

8. The function starts by displaying Inside the function, just so you know where you’re at.

9. The function attempts to display the contents of $variable. But $variable hasn’t been created inside this scope. So the shell goes up a level to the first parent and finds that $variable was defined there. So the shell displays Variable contains 'script' and moves on.

10. The function now puts Function inside $variable. This creates a new element called $variable inside the current scope. The $variable owned by the parent isn’t affected!

11. Next, the function displays Variable now contains 'Function', which is true because $variable now exists inside the local scope and contains Function.

12. Now the function tries to use the Gw alias. It doesn’t exist in this scope, so again the shell goes up to the parent. The alias exists there, and so the command runs successfully. You can see the output with a bit of information about the computer’s BIOS.

13. The function exits and its scope is destroyed. The script displays Back in the script.

14. The script displays Variable contains 'Script', which is true because $variable exists in the script scope, and in this scope it does contain Script. It was never changed by what the function did.

15. Finally, the script displays Done with the script.

16. The script finishes, and its scope is destroyed. Back in the shell, you attempt to use the Gw alias and find that it won’t work. The alias was never defined in the global scope, so you can’t use it here. There’s no parent to the global scope, so the shell has nowhere up to go and find the alias.

That’s a good look at the basics of scope. Just remember the rules we outlined earlier, and you’ll usually be okay. Now for the creative stuff!

22.3. Dot sourcing

As you’ll recall from chapter 20, dot sourcing is a technique in PowerShell that lets you run a script without first creating a new scope for it. In other words, rather than creating a container in which to run the script, PowerShell runs it inside the current scope. The practical upshot of this is that anything created within the script continues to exist after the script is finished. Let’s use our Test.ps1 demonstration from listing 22.1 again. Again open a brand-new PowerShell console window and run the following:

To dot-source the script, you type a period, followed by a space, and then the path and filename of the script. The script’s output looks exactly the same as it did last time. But when the script finishes, you’re able to display the contents of $variable, and you’re able to use the Gw alias. That’s because you’ve essentially created those items within the global scope! The script was never given a scope of its own, so everything it did technically happened inside the global scope. When the script finishes running, the global scope continues to exist, and so those two elements remain accessible.

Even the Do-This function continued to be accessible. That’s because it was defined within the script. But the script didn’t have its own scope, so Do-This was technically defined into the global scope. That means it continues to exist after the script has finished running. All the regular scope rules still apply, though: When Do-This runs, it creates its own $variable and puts Function inside it. Once the function completes, its scope is destroyed. At the global scope, $variable still contains Script, as shown in the output. The dot sourcing only prevented a scope from being created for the script; it didn’t stop any other scope rules, such as the function getting its own scope, from operating.

Some folks will use dot sourcing as a way of including a library script inside another script they want to run. For example, they’ll put a bunch of functions, and nothing else, into a script, perhaps naming it Library.ps1. Then, inside all their other scripts, they’ll dot-source Library.ps1. Doing so makes all of the functions from Library.ps1 available inside the other scripts. That was the only way to accomplish such a thing in PowerShell v1; in v2 and later, that library should be written as a script module and loaded by running Import-Module. That way, it (and its contents) can be easily removed by running Remove-Module. You also save the overhead of continually loading the library of functions.

Tip

Dot sourcing is great when developing scripts. If you’re having problems working out how to do something, run your script using dot sourcing and you’ll have all your variables and functions available. You can experiment at the command line to get the code working and copy the working code into your script.

22.4. Manipulating cross-scope elements

Our general rules from earlier in this chapter boil down to two important basics:

· A parent can never see or change scoped elements that exist within a child. We’re not going to budge on this one—it’s always true and there’s no way around it.

· A child scope can read items from its parent, but it can’t change them. If a child scope tries to change an element from its parent, it winds up creating a new element, having the same name, inside its own local scope.

That second rule is the one that we’re now going to mess with. To do that we’re going to create a slightly simpler demonstration script that only manipulates variables. These same rules apply to other scoped elements, such as aliases and PSDrives, but variables are the easiest to observe in action. The following listing contains our simplified example script, which should be saved as test2.ps1.

Listing 22.2. Demonstrating cross-scope activities

function Do-That {

Write-Host "Inside Do-That `$var is '$var'"

$var = 3

Write-Host "Now, inside Do-That `$var is '$var'"

}

Write-Host "Inside Test2 `$var is '$var'"

$var = 2

Write-Host "Now, inside Test2 `$var is '$var'"

Do-That

Write-Host "Back inside Test2 `$var is '$var'"

As before, open a fresh PowerShell console window to try this out. Begin by setting $var to something in the global scope, running the script, and then checking the contents of $var. Here’s what happens:

PS C:\> $var = 1

PS C:\> .\test2.ps1

Inside Test2 $var is '1'

Now, inside Test2 $var is '2'

Inside Do-That $var is '2'

Now, inside Do-That $var is '3'

Back inside Test2 $var is '2'PS C:\> $var

1

PS C:\>

This is exactly the same behavior that we demonstrated before. At the beginning of each new scope, $var doesn’t exist. When the scope tries to access it, PowerShell pops up a level to that scope’s parent. So at the beginning of the script, the value 1 is coming from your global scope $var; at the beginning of the function, the value of 2 is coming from the script. Each scope then sets its own value for the variable, which in essence creates a new variable that happens to have the same name as a variable in the parent scope. The scopes’ use of $var doesn’t in any way change their parent scopes’ definition of the variable: You can see after the script runs that $var remains 1 in the global scope.

Now let’s change up the rules a bit. You’ll use PowerShell’s variable cmdlets to do this, resulting in the script shown in the next listing.

Listing 22.3. Test2.ps1, which now uses cmdlets to modify out-of-scope elements

function Do-That {

Write-Host "Inside Do-That `$var is '$var'"

Set-Variable -Name var -Value 3 -Scope 2

Write-Host "Now, inside Do-That `$var is '$var'"

}

Write-Host "Inside Test2 `$var is '$var'"

Set-Variable -Name var -Value 2 -Scope 1

Write-Host "Now, inside Test2 `$var is '$var'"

Do-That

Write-Host "Back inside Test2 `$var is '$var'"

Here are the results of running the script in listing 22.3 in a brand-new console window:

PS C:\> $var = 1

PS C:\> .\test2.ps1

Inside Test2 $var is '1'

Now, inside Test2 $var is '2'

Inside Do-That $var is '2'

Now, inside Do-That $var is '3'

Back inside Test2 $var is '3'

PS C:\> $var

3

PS C:\>

Unlike the previous example, in this case there’s only one copy of $var running around, and it’s the one in the global scope. The various –Variable cmdlets, including Set-Variable, all have a –Scope parameter. This parameter lets you explicitly modify an element in a higher-level scope. The parameter accepts a number, and 0 means do it in my local scope, which is the default behavior if you don’t use –Scope at all. A value of 1 means do this in my parent’s scope, a value of 2 means do it in my parent’s parent’s scope (which you could call my grandparent’s scope, but that might be taking the family analogy a bit too far).

So when the function ran this:

Set-Variable -Name var -Value 3 -Scope 2

it was telling the shell to “modify the contents of $var, placing a 3 into the variable. But don’t do this locally. Go up two levels, to my parent’s parent, and make the change there.” Because the function was three levels down—global, script, function—this resulted in the global scope’s copy of $var being modified.

It can be tough to keep track of scopes by number like that. For example, let’s say you wrote another script, test3.ps1, as shown here.

Listing 22.4. Test3.ps1, which runs Test2.ps1 (unmodified)

C:\test2.ps1

Now, open a fresh shell console and try this:

PS C:\> $var = 1

PS C:\> ./test3

Inside Test2 $var is '1'

Now, inside Test2 $var is '2'

Inside Do-That $var is '2'

Now, inside Do-That $var is '3'

Back inside Test2 $var is '3'

PS C:\> $var

1

You get different results. At the end of everything, $var continues to contain 1 in the global scope. That’s because Test3.ps1 created its own scope, which was a child of the global. When the function modified the variable, it went up two levels (that’s what -Scope 2 means). Up one level is Test2.ps1, and up a second level is Test3.ps1. So the function modified $var in Test3.ps1 rather than in the global scope.

To help get more predictable results, you can refer to specific scopes by name instead of by numbers. The following listing contains a modified Test2.ps1.

Listing 22.5. Modifying Test2.ps1 to use named scopes

function Do-That {

Write-Host "Inside Do-That `$var is '$var'"

Set-Variable -Name var -Value 3 -Scope global

Write-Host "Now, inside Do-That `$var is '$var'"

}

Write-Host "Inside Test2 `$var is '$var'"

Set-Variable -Name var -Value 2 -Scope global

Write-Host "Now, inside Test2 `$var is '$var'"

Do-That

Write-Host "Back inside Test2 `$var is '$var'"

Now let’s go run Test3.ps1, from listing 22.4, again:

PS C:\> $var = 1

PS C:\> .\test3.ps1

Inside Test2 $var is '1'

Now, inside Test2 $var is '2'

Inside Do-That $var is '2'

Now, inside Do-That $var is '3'

Back inside Test2 $var is '3'

PS C:\> $var

3

Now the global scope’s $var has again been modified, because Test2.ps1 referred specifically to the global scope by name, rather than by trying to count up a certain number of levels. This is an absolute scope reference, meaning no matter how deeply nested the Set-Variable command becomes, it’ll always modify the global scope when –Scope global is specified.

You can refer to a few scopes by name:

· Global always refers to the global scope.

· Local always refers to the current scope and is the default.

· Script refers to the nearest script scope upward in the hierarchy.

If you can accomplish what you need by using a named scope in that fashion, then you don’t even have to use a –Variable cmdlet. PowerShell recognizes a shortcut syntax that can be used directly with variable names:

· $global:var will always refer to the variable $var in the global scope.

· $local:var will always refer to the variable $var in the current scope.

· $script:var will always refer to the variable $var in the next script scope that’s upward in the hierarchy.

As a best practice, assiduously avoid messing with any scopes that aren’t your own. For example, don’t rely on the global scope containing information that you need, because you don’t know what else might be playing with the global scope and possibly messing you up and causing bugs in your script. We see people use higher-scope variables as a way of passing information between two scripts, or between two functions, or something else. As a rule, that’s a bad idea. It can create complex debugging situations, along with other troubles. It can also cause your script to conflict with other people’s scripts, if those other people are also relying on higher-scope variables and information. As a rule, you should only mess with things in the local scope. To pass information between scopes, rely on parameters for input and the pipeline for output. We cover all of that in upcoming chapters.

22.5. Being private

There’s one more scope we have to cover, and that’s the private scope. When you create a scoped item and give it a scope of private, it exists only inside the current, local scope. As always, it can’t be seen by the current scope’s parent—that’s always impossible. But unlike other elements, a private element also can’t be seen by a scope’s children.

For example, suppose a script creates a variable like this:

$private:computername = 'SERVER1'

Normally, any functions contained within that script—its children—would be able to access the contents of $computername. Not in this case. To them, $computername will appear to be undefined. They’re welcome to create their own copy of $computername, of course, but they can’t see the parent’s. But if a child scope explicitly tried to access $private:computername, it’d be able to do so.

22.6. Being strict

We discussed the Set-StrictMode cmdlet in chapter 16. Because this cmdlet ties in so closely with scope, we’ll cover it in a bit more detail here. Let’s first see it in normal operation: You set a global scope variable named $check and then write a script named Strict.ps1, which contains one line that displays the contents of $check, as shown in the next listing.

Listing 22.6. Strict.ps1

$check

Now let’s see what happens with the various strict modes:

PS C:\> $check = 'Please'

PS C:\> Set-StrictMode -Off

PS C:\> ./strict

Please

PS C:\> Set-StrictMode -Version 1

PS C:\> ./strict

Please

PS C:\> Set-StrictMode -Version 2

PS C:\> ./strict

Please

PS C:\> Set-StrictMode -Version 3

PS C:\> ./strict

Please

PS C:\> Set-StrictMode -Version 4

PS C:\> ./strict

Please

PS C:\>

That’s the same behavior you’ve seen throughout this chapter: In all three modes, with strict set to Off, version 1, version 2, version 3, or version 4, the script’s scope is able to go up the scope hierarchy to access the global $check variable. Now modify Strict.ps1, as shown in the following listing, to display a variable that hasn’t been created in the global scope (or anywhere else).

Listing 22.7. Modifying Strict.ps1

$peace

Now try running that script in the different strict modes:

PS C:\> Set-StrictMode -Off

PS C:\> ./strict

PS C:\> Set-StrictMode -Version 1

PS C:\> ./strict

The variable '$peace' cannot be retrieved because it has not been set.

At C:\Test\strict.ps1:1 char:1

+ $peace

+ ~~~~~~

+ CategoryInfo : InvalidOperation: (peace:String) [],

RuntimeException

+ FullyQualifiedErrorId : VariableIsUndefined

PS C:\> Set-StrictMode -Version 2

PS C:\> ./strict

The variable '$peace' cannot be retrieved because it has not been set.

At C:\Test\strict.ps1:1 char:1

+ $peace

+ ~~~~~~

+ CategoryInfo : InvalidOperation: (peace:String) [],

RuntimeException

+ FullyQualifiedErrorId : VariableIsUndefined

PS C:\> Set-StrictMode -Version 3

PS C:\> ./strict

The variable '$peace' cannot be retrieved because it has not been set.

At C:\Test\strict.ps1:1 char:1

+ $peace

+ ~~~~~~

+ CategoryInfo : InvalidOperation: (peace:String) [],

RuntimeException

+ FullyQualifiedErrorId : VariableIsUndefined

PS C:\> Set-StrictMode -Version 4

PS C:\> ./strict

The variable '$peace' cannot be retrieved because it has not been set.

At C:\Test\strict.ps1:1 char:1

+ $peace

+ ~~~~~~

+ CategoryInfo : InvalidOperation: (peace:String) [],

RuntimeException

+ FullyQualifiedErrorId : VariableIsUndefined

As you can see, with strict off, the $peace variable has a default value of $null. With any of the strict modes engaged, trying to use an undefined variable—undefined both in the local scope and in any parent scope—is an illegal operation, resulting in an error.

Some subtle differences exist between version 1 and 2 (and 3) strict mode. There doesn’t appear to be any difference between using version 2, version 3, or version 4 with Set-StrictMode. Table 22.1 sums up the major differences.

Table 22.1. Differences between Set-StrictMode versions

Strict off

Strict v1

Strict v2, v3, and v4

Uninitialized variable

Variable presumed to be empty

Illegal

Illegal

Uninitialized variable referenced from within a double-quoted string

Variable presumed to be empty

Variable presumed to be empty

Illegal

References to nonexistent properties of an object

Property value presumed to be empty

Property value presumed to be empty

Illegal

Calls to functions that enclose parameters in parentheses, as a method would

Allowed

Allowed

Illegal

Variables with no name, such as ${}

Allowed

Allowed

Illegal

As you can see, the higher strict versions (you can always select the most recent by running Set-StrictMode –Version latest) offers the best protection against common mistakes that can often lead to extensive, difficult debugging sessions.

If you try to set strict mode to a version that doesn’t exist, an error will be generated:

PS C:\> Set-StrictMode -Version 5

Set-StrictMode : Cannot validate argument on parameter 'Version'. The "5.0"

argument does not contain a valid Windows PowerShell version. Supply a

valid version number and then try the command again.

At line:1 char:25

+ Set-StrictMode -Version 5

+ ~

+ CategoryInfo : InvalidData: (:) [Set-StrictMode],

ParameterBindingValidationException

+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft

.PowerShell.Commands.SetStrictModeCommand

Although the default strict mode is Off, we recommend setting it to Latest whenever you’re beginning work on a new script or function.

22.7. Summary

As we mentioned at the outset of this chapter, scope can be a complex topic. Our usual recommendation is to avoid dealing with it as much as possible: Don’t use variables and other scoped elements until you’ve given them an explicit value within the current scope. Don’t mess with out-of-scope elements. That’s the easiest way to keep out of trouble.