Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

Get the Total Parameter Count

Update: There was an update to this post on May 29, 2018. See below.

It’s been a littler longer than normal for me to have not written. My entire week last week didn’t include a post; it’s so weird. Well, I’m taking a quick minute — seriously — to discuss something I wanted to do recently.

In a new project I’m on, I wanted to know how many parameters a function has from inside the function itself. I didn’t want to know how many parameter were used when the function was invoked. Had I, I would’ve used $PSBoundParameters. Again, I wanted to know how many parameter(s) the function had, whether they were used or not.

I’ll tell you what I opted to do. On that note, do however, let me know if you come up with a better idea. I didn’t give this a ton of time. That said, it doesn’t even have to be better; I’d still like to hear about it. For me, I opted to execute a Get-Help command against the function, from inside the function. I’m making this a quick post, so let’s jump to some code.

Function Check-ParameterCount {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$Param01,

        [Parameter()]
        [string]$Param02,

        [Parameter()]
        [ValidateSet('Source','Destination')]
        [string]$Param03
    )

    $ParameterCount = (Get-Help -Name Check-ParameterCount).Parameters.Parameter.Count
    "There are $ParameterCount possible parameter(s) in this function not including the common parameters."
}
PS > Check-ParameterCount
There are 3 possible parameter(s) in this function.

The next example is the same example as above, however, we’ve removed the third parameter. Sure enough, the value is now two. As you may have noticed, Get-Help gets its parameter count from the actual parameter(s) themselves. Neither function has any comment-based help. Therefore, we can determine that it doesn’t use any static help we might include in regard to the parameter(s).

Function Check-ParameterCount {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$Param01,

        [Parameter()]
        [string]$Param02
    )

    $ParameterCount = (Get-Help -Name Check-ParameterCount).Parameters.Parameter.Count
    "There are $ParameterCount possible parameter(s) in this function not including the common parameters."
}
PS > Check-ParameterCount
There are 2 possible parameter(s) in this function.

That was it. Again, let me know if there’s a better way.

Update: I had a thought. Because my function uses the CmdletBinding attribute, it’s possible that my function can have more than the number of parameters returned by Get-Help. Therefore, I’ve only slightly modified the above strings my function returns. It was “There are $ParameterCount possible parameter(s) in this function.”, and now it’s “There are $ParameterCount possible parameter(s) in this function not including the common parameters.” Now, it indicates it returns X number of parameters, not to include the command parameters.

PS > Check-ParameterCount
There are 2 possible parameter(s) in this function not including the common parameters.

The Other $PROFILEs

If you weren’t aware, I did a great deal of writing and posting recently due to my idea to complete chapter reviews of The Pester Book. I think it was a success, and if anything, it helped force me to ensure I fully understood the content for the things I covered here. I’m not confident with things until I fully understand them, and so even though I finished that book, I’m still after additional content.

Well, after all the writing, I did a couple other posts, but the last one I left off on was about an old HTA I wrote. An HTA is an HTML application. It’s old, but it allowed (back when I used and wrote it), people like me to create GUIs for VBS scripts. Yes, VBS. I hate that it’s the first post on the front page, so I had to write something new in relation to PowerShell.

Today, I wanted to take a moment and remind everyone about the $PROFILE variable. If you enter this variable into your PowerShell host program you’ll see a path to the current user’s, current host’s, PowerShell profile script. A couple things: One, the host here is the PowerShell host, as in the ConsoleHost, the ISE, Visual Studio Code, etc. It has nothing to do with a computer host.

Two, a profile script –once it’s been created and edited– runs when you open the host program, and allows you to set up the environment, or PowerShell session, the way you want it. You can create aliases, variables, and define functions, and ensure they’re available when you open that PowerShell host program.

Here’s how you can check the currently used host program.

PS > $Host.Name
ConsoleHost

When I enter $PROFILE, it returns to me the below path. Do notice that I’m using Windows PowerShell in this example. With PowerShell 6.0 running, it would’ve indicated “PowerShell” in the path, and not “WindowsPowerShell.”

PS > $PROFILE
C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Now, it should be noted that there’s actually more profile scripts that can execute when you open a PowerShell host program. Take a look here.

PS > $PROFILE | Select-Object -Property *

AllUsersAllHosts       : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost    : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts    : C:\Users\tommymaynard\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Length                 : 78

The above Select-Object command returns five properties, and four of them are paths. In addition to the CurrentUserCurrentHost property, that we saw when we didn’t use Select-Object, we also have entries for CurrentUserAllHosts, and two for all users: AllUsersAllHosts and AllUsersCurrentHost. It’s too bad that in Windows PowerShell the desire to use an AllUsers profile script required work in the C:\Windows\System32 directory. That’s changed in PowerShell Core, where AllUsers profile scripts default to C:\Program Files\PowerShell\<version>. Just because we have a path in these properties, does not mean there’s a file with these indicated names. The files have to be created with New-Item in order to make use of these profile scripts.

Keep this in mind as you use profile scripts. While I suspect most people don’t use it, CurrentUserAllHosts, is better, than using CurrentUserCurrentHost if you want the same thing in every profile. There was a time I used CurrentUserCurrentHost in the Console and then had all my other hosts dot source that script in their own profile scripts. Dumb. If you need something in all, use CurrentUserAllHosts, but if you need something in one host vs. another, then use CurrentUserCurrentHost.

Now, back to that project I’m working. It has everything to do with profile scripts, and with this post I can be certain that I’ve provided myself some reminders about the $PROFILE variable. Additionally, I’ve gotten that HTA post moved down one; it’s no longer the first post on my site.

Functions Finding Variable Values

Every once in awhile I forget something I know, I know. Not sure why, but for a quick moment, I suddenly couldn’t remember which can access what. Can a nested, or child, function, access the variable in the containing, or parent, function, or is it the other way around? That is, can a containing, or parent, function access the variable in the nested, or child, function? Even as I write this, I still can’t believe that for a moment I forgot what I’ve known for so long.

Duh. A nested or child function can access a variable assigned in the containing or parent function. If the child function cannot find a declared variable inside itself, it’ll go upward in scope to its mom or dad, and ask if they have the variable assigned, and if so, they can borrow it. Being good parents, they offer it if they have it.

It’s so obnoxious that in a quick moment, I wasn’t sure, even though I’ve pictured it in my own head multiple times, as well as relied on it. Anyway, here’s an example that does prove that a nested function will go upward to find a variable, if it’s not been assigned inside itself.

Function AAA {
    'In function AAA.'
    Function BBB {
        'In function BBB.'
        $x = 5
    }
    $x
    BBB
}
AAA

Function CCC {
    'In function CCC.'
    $x = 5
    Function DDD {
        'In function DDD.'
        $x
    }
    DDD
}
CCC
In function AAA.
In function BBB.
In function CCC.
In function DDD.
5

This got me wondering, how far up will it go!? I assume it’ll go up and up and up, and well I was right. Take a look at this example. I guess if I had to forget something so simple that at least I got an opportunity to build this example. Enjoy the weekend!

Function Top {
    $x = 10
    "0. Assigned `$x with $x."
    "1. Inside the $($MyInvocation.MyCommand.Name) function."
 
    Function MidTop {
        "2. Inside the $($MyInvocation.MyCommand.Name) function."
 
        Function MidBottom {
            "3. Inside the $($MyInvocation.MyCommand.Name) function."
 
            Function Bottom {
                "4. Inside the $($MyInvocation.MyCommand.Name) function."
                If (Get-Variable -Name x -Scope Local -ErrorAction SilentlyContinue) {
                    '5. Can find the variable in the local scope.'
                } Else {
                    '5. Cannot find the variable in the local scope.'
                }
                "6. The value of `$x is $x."
            } # End Function: Bottom.
            Bottom
 
        } # End Function: MidBottom.
        MidBottom
 
    } # End Function: MidTop.
    MidTop
 
} # End Function: Top.
Top
0. Assigned $x with 10.
1. Inside the Top function.
2. Inside the MidTop function.
3. Inside the MidBottom function.
4. Inside the Bottom function.
5. Cannot find the variable in the local scope.
6. The value of $x is 10.

Update: I made a few modifications to the above function. Now, it indicates that it cannot find the $x variable in the local scope of the Bottom function. That’s one way to help prove it goes upward through the parent functions looking for a value for $x.

No Prompt Plaster

I took a few hours last week and familiarized myself with Plaster. And no, not the walls and ceiling home improvement project Plaster — Microsoft’s Plaster. The one that requires you search for it alongside the search term PowerShell, otherwise you’ll never get close to what I’m going to briefly discuss.

Plaster allows you to create the folders and files needed for a project, in a repeatable way in order that… well… you don’t have to do it manually. I never quite enjoyed the manual process of most things PowerShell, but especially the process of creating PowerShell script modules: create a folder, rename it, create a .psm1, rename it, create a… Why should I create all the necessary folders and files, if an automated process can do it for me? That’s what this is all about — saving seconds. They add up.

For every video I watched, and post I read, I couldn’t seem to find what I wanted. You see, Plaster by default will prompt you the information needed to create your project. That’s nice and all, and especially because it prompts for the information I’ve determined I need, but how does one run this in an automated fashion already? No one blogged on that (that I read originally), and that’s why we’re here today.

In reading about the Plaster project on GitHub, I read this: “Or you could provide all the required template parameters as dynamic parameters and not be prompted for any of them.” Say what!?

I just knew it had to exist, and there it was — in bold even, in just the right place. For those concerned, I would’ve eventually read the help in full. It’s just what I found first. Honestly though, what I expected to find first was a mention of this in the blog posts and videos I chose to read, and watch. I should note that Mike F. Robbins does mention it here: http://mikefrobbins.com/2018/02/15/using-plaster-to-create-a-powershell-script-module-template. I’d hadn’t quite gotten to that post, as I wondered why I hadn’t found my answer in the first few things I inspected. Do read the link if you have an interest, as Mike includes a full Plaster introduction.

Just in case someone does what I did there for a moment, you don’t have to use Plaster in a manual, you’re-going-to-get-prompted-for-everything manner. Instead, you can do something like the below example, where you provide values to the Plaster parameters you’ve included. Do notice, as Mike mentions, that there’s no tab completion on these parameters. Annoying, but worth it for the automated aspect of Plaster.

Invoke-Plaster -TemplatePath $PSScriptRoot\Tests\TemplateRoot1 -DestinationPath $PSScriptRoot\Tests\Out -ModuleName CoolModule -Version 2.1.1 -License MIT -Options Pester,PSake,Git

The Pester Book Chapter Review (13)

Table of ContentsAdding Tests for a Function

In this chapter, the overall idea was to take an existing function, such as you might have sitting around right now, and write tests for it. There are likely functions and tools that we already have that don’t have a tests, and with the help of this chapter can start thinking through the logic of writing tests. This chapter makes perfect sense, and for me, lines up well. That’s what’s I’ve been doing — writing tests for an existing function I’ve authored. That said, I can’t wait to start my next tool from the beginning and experiment with TDD, and getting those tests in place before I start writing my solutions to pass the tests.

In regard to writing tests, as that’s what we’re after here, I highlighted the below two sentences. It basically says this: “It’s okay if you don’t incorporate all the test possibilities right away. You can come back and add more later.” I really needed to hear this.

As we moved through the chapter, we walked and talked though various things we would test against Warren Frame‘s Invoke-Parallel function. I won’t spend anytime going over the tests, or the function, but I really did appreciate the walk though experimentation and out loud thinking of this chapter. I picked up a few hints, and I’ll share one before wrapping up. It’s okay to include more than a single Should comparison in the same It block, providing they make sense together. If including multiple assertions makes sense to your It block’s purpose, then do so freely. Good to know, and great chapter.

Return to Home with cd

I’ve been working in Git Bash a decent amount of my time recently, and I’ve found myself becoming used to using the cd command to return to my home directory (/c/Users/tommymaynard). Did you know you could do that? In that shell, as well as my Cygwin shell, it’s possible. I even fired up my Ubuntu Linux VM and tried it in the Terminal there, as well, and as expected, it works there too.

The cd alias in Windows PowerShell (that’s 5.1), which resolves to the Set-Location cmdlet, doesn’t do the same thing. Therefore, I made a quick modification to the Set-Location function I keep in my $PROFILE script. I’ll include it below, and then discuss it.

# Set-Location runs Push-Location, creates cd <blank> to $HOME (~).
Function Set-Location {
    Param (
        [string]$Path
    )

    If ($Path) {
        Push-Location -Path $Path
    } Else {
        Push-Location -Path ~
    }
}

My Set-Location function, just like the original cmdlet, accepts a path in order to know where in the file system to move the user. If the function is invoked without a path, however, it’ll now move the user to their $HOME directory (C:\Users\<username>\). So you’re aware, Push-Location puts the current location on the stack and then changes location to that which was specified. Again, it’ll move to $HOME, or ~, or C:\Users\<username>, which are all the same, if nothing is specified for the path parameter.

Here’s the a small portion of the help notes from the Push-Location cmdlet: “The Push-Location cmdlet adds, or pushes, the current location onto a location stack. If you specify a path, this cmdlet pushes the current location onto a location stack and then changes the current location to the location specified by the path.” Using my Set-Location function allows me to always use Pop-Location, or the popd alias, to return to my previous location in the file system.

In closing, I do want to mention that using cd by itself in PowerShell 6.0.2 on Windows, and PowerShell 6.0.0-alpha on Linux, works just like it does in Git Bash, Cygwin, and the Terminal on Ubuntu. Without an argument, it’ll automatically move you to your home directory, just like it does now, in my, Windows PowerShell 5.1.

Someone was paying attention.

Add Measure-Command into a Function

During an at work demonstration of my Advanced Function Template recently, I heard an interesting idea from a teammate. It all started when I mentioned how many lines of code my function template required, without having even added any non template code to it. In version 1.0, the template required around 75 lines, which felt high. In subsequent versions it crept up past 100 — maybe 120. That also felt high, until I ran some tests and stopped caring. It’s probably past 120 by now, but I’ve stopped paying attention, as it no longer matters to me.

In my testing, a function that includes only a single string has an execution time of around 5,000 ticks. With 10,000 ticks per millisecond, you’re right to think that’s fast. With my most current version of the Advanced Function Template, with no additional code, it comes in at 3 – 4 milliseconds. Personally, I can’t tell the difference between a 1/2 of a millisecond and a few, and so the number of lines required by the template stopped meaning much to me. Especially since it only includes relevant and necessary code anyway. It’s worth it, especially since we now have region support in Visual Studio Code and can collapse any code we don’t want to have to constantly view anyway.

The interesting idea that was brought up, was to add a way to measure the execution of the Advanced Function Template (and its included, user based code [once that’s been added]), from within the function that’s been built using the template itself. I had never thought of such a thing. This is to say, wrap the Measure-Command cmdlet inside the template, in order to be able to run it against itself. If that’s confusing, no worries, the following code example should help explain.

Function Get-TheAlias {
    [CmdletBinding()]
    Param (
        [string]$Definition,
        [switch]$Measure
    )
    
    If ($Measure) {
        [System.Void]($PSBoundParameters.Remove('Measure'))
        Measure-Command -Expression {Get-TheAlias @PSBoundParameters}
    } Else {
        Get-Alias @PSBoundParameters
    }
}

The first two below examples do not use the Measure switch parameter offered in the above function. Therefore, they’ll head down the Else path of the If-Else statement. The first example will internally run Get-Alias with no parameters or parameter values (not all results are displayed). The second example also uses Get-Alias internally, however, it does so using the Definition parameter and a parameter value. The Definition parameter allows us to determine if there’s an alias, or aliases, for the given cmdlet or function. As there is, an alias for Get-Command is displayed.

PS > Get-TheAlias

CommandType     Name                                Version    Source
-----------     ----                                -------    ------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ?: -> Invoke-Ternary                3.2.0.0    Pscx
Alias           ?? -> Invoke-NullCoalescing         3.2.0.0    Pscx
Alias           ac -> Add-Content
Alias           And -> GherkinStep                  4.1.0      Pester
...
Alias           gwmi -> Get-WmiObject
Alias           h -> Get-History
Alias           hib -> Suspend-Computer
Alias           hibernate -> Suspend-Computer
Alias           history -> Get-History
Alias           i -> powershell_ise.exe

PS > Get-TheAlias -Definition Get-Command

CommandType     Name                                Version    Source
-----------     ----                                -------    ------
Alias           gcm -> Get-Command

The next two examples of the Get-TheAlias function do use the Measure parameter. In the first example, we only use the Measure parameter. Because the code dictates, we’ll head down the If portion of our If-Else language construct. It will strip the Measure parameter, and then measure the time it take to run a second, internal instance* of the Get-TheAlias function (which again, simply runs Get-Alias). When we also include the Definition parameter and a value, we’ll again strip the Measure parameter, and internally run a measured copy of Get-TheAlias function. This will again, execute Get-Alias with the Definition parameter and the submitted parameter value.

* Update: I do want to mention here, that there’s really no internal instance of the function. When a function calls itself, it looks for the function in it’s own scope (the child scope, inside the function). When it cannot be located there, it goes upward, to the parent scope, where it will find the declared function. Maybe this helps.

PS > Get-TheAlias -Measure

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 6681
TotalDays         : 1.75694444444444E-08
TotalHours        : 4.21666666666667E-07
TotalMinutes      : 2.53E-05
TotalSeconds      : 0.001518
TotalMilliseconds : 1.518

PS > Get-TheAlias -Definition Get-Command -Measure

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 1
Ticks             : 10287
TotalDays         : 7.28819444444444E-09
TotalHours        : 1.74916666666667E-07
TotalMinutes      : 1.0495E-05
TotalSeconds      : 0.0006297
TotalMilliseconds : 0.6297

It’s weird and different sure, but most of all, I need some thoughts on whether or not this will actually work long term. In my mind it does. It tries to run a function from its own function scope, when it can find that function, it goes up to the global scope and runs itself without the measure switch parameter. Sorry, if you’re still confused, but I’m finding it difficult to be 100% pleased with my ability to describe things.

If you’re not confused by this, then walk thought the function a time or two in your own head, or maybe even on a computer! Can you create or think of scenarios where adding a way to measure how long a function takes to complete can’t, or shouldn’t, be added to itself?

Write Functions, Not Scripts – Part V

I like how my assumption that Part IV of Write Functions, Not Scripts wouldn’t be a worthy contender to the previous three parts (P1, P2, P3). Seriously, I didn’t think it compared, and if I’m not mistaken, it’s been the most popular post in this series thus far.

It’s a simple concept though: short, simple, single-purposed is sufficient for writing functions. Think small, easier to troubleshoot code. Say you have five functions that run in succession — one right after the other. The point at which things break will likely remove 4 of the 5 functions as areas you even need to review. That means you’re troubleshooting a fifth of the code the scriptwriter would have to review. Functions are a no-brainer. Well, they ought to be, anyway.

Time for some more thoughts on why functions over scripts. Hey, ever see one of these in a script?

$UserCount = 10
# When using in production, please comment out the above line.
# Then, uncomment the below line.
# $UserCount = 20

This is why we have a Read-Host. This, would be better.

$UserCount = Read-Host -Prompt 'Enter the preferred User Count'

Before blogging was my main gig, you might find me scouring various forums more often and helping others (and secretly learning more, too). A good number of people were getting their input via the Read-Host cmdlet. It’s nice and all, but by itself, there’s a bit of mystery as to what a user might enter. While I’m expecting 10, or 20, I could end up with all kinds of obnoxious values.

$UserCount = Read-Host -Prompt 'Enter the preferred User Count'
Enter the preferred User Count: DogCatMom5
# Huh!?

Inside a Do loop construct, we can better control the acceptable input.

Do {
    $UserCount = Read-Host -Prompt 'Enter the preferred User Count'
} Until ($UserCount -eq 10 -or $UserCount -eq 20)
Enter the preferred User Count: 1
Enter the preferred User Count: 2
Enter the preferred User Count: 3
Enter the preferred User Count: 4
Enter the preferred User Count: 5
Enter the preferred User Count: 6
Enter the preferred User Count: DogCatMom5
Enter the preferred User Count: 10

But there’s still a better way. Function’s allow for parameters and so, as absolutely as best as we can, we need to separate our lives — and our function writing — from Read-Host. If it’s ever in a function, and I’ll agree that it can happen, there ought to be really great reason as to why. We’ll close out today’s post with a simple function example that uses parameters. This is such a huge benefit.

Function Test-ParamsInAFunction {
    Param (
        $Number,
        $Letter
    )
 
    If ($Number -and $Letter) {
        Write-Output -InputObject "You have entered the number $Number, and the letter $Letter."
    } ElseIf ($Number) {
        Write-Output -InputObject "You have entered the number $Number."
    } ElseIf ($Letter) {
        Write-Output -InputObject "You have entered the letter $Letter."
    } Else {
        Write-Output -InputObject "You have not entered a number or letter."
    }
}
Test-ParamsInAFunction
Test-ParamsInAFunction -Number 5
Test-ParamsInAFunction -Letter X
Test-ParamsInAFunction -Number 10 -Letter D

You have not entered a number or letter.
You have entered the number 5.
You have entered the letter X.
You have entered the number 10, and the letter D.

Functions can handle run time delivered values. There’s no more opening up scripts and adding and removing static comments and variable assignments. There’s no using Read-Host inside our scripts and functions (unless, again, you’ve got a really great reason), even if, you’re using a language construct to protect the acceptable values. Before we full on wrap up, let me show you how to make a parameter only accept the 10 and 20 values.

Function Test-ParamsInAFunction {
    Param (
        [ValidateSet(10,20)]
        $Number
    )
    ...
}

See you again next time.

Write Functions, Not Scripts – Part IV

If you haven’t been with us in the previous three posts (this one, this one, and this one), we’ve been discussing why functions, over scripts. If you haven’t already, it’s time to change your mindset.

The idea is to write small, concise, single-purposed, chunks of code. We call these functions. What we don’t need… is long, overly complex, multi-purposed scripts. We’ve got to stop the scripting mentality. You know the one… you’re writing a script and your next thought is, now I need to do this…and then you write lines and lines beneath that which has already written. Now you have a multipurpose script and no clear end in sight.

It’s pretty simple: the longer the script, the more pieces to troubleshoot and when adding new features, the more potential areas to make errors. You’re creating more confusion. The whole process of script writing can easily and quickly get away from you. In one moment you add one solution, and then in another, you add more solutions to other problems you want to solve, and it’s all in a single file. Ugh.

That was it. Start writing in a single purposed methodology. Even if you’re not going not to write in functions beginning tomorrow morning, like you should, start scripting in single purposed scripts. If you need it, make that your first step. I’m not writing for my own benefit this evening; I’m writing for yours. You’re going to love being a function writer. You just need to make this the week you start, if you haven’t already. Here, I’ll get you started:

Function Get-ItDone {
    # Add your code here.
}
Get-ItDone

Alright, see you next time.

Write Functions, Not Scripts – Part III

First off, I have to say, the first two posts in this series have been very successful. Nearly 200 visitors on a Saturday and then a Sunday, too — that’s unheard of during the weekend. And to think, these two posts: Part I and Part II, have felt like some of the most distracted and unpredictable writing of mine to date, and I’ve been at this for almost four years straight, without a missed month.

Okay, to continue with a distraction, in Write Functions, Not Scripts — Part II, I showed a picture of the fog on my cul-de-sac in Tucson, AZ (the desert) last Friday. Here’s that picture now. That fog, is not typical for us. Maybe it happens once a year.

I mentioned that I’d include the typical view, and so here’s a picture from Saturday — just one day later. And that’s, what it normally looks like around here.

Moving along, as if there’s a PowerShell related topic at hand. So, here’s one reason for functions, over scripts: it’s variables. Let’s move out the fog, and explain.

When we’re developing scripts, we often create variables and assign them values at the top of our code. Normal stuff. The problem is that when we go to execute the script a second (or third, or fourth) time, the variables in our script have already been assigned a value, and that may make our results incorrect. This can lead to confusion and wasted time, incorrect results, and extra lines of unnecessary commands to initialize variables. Stop initializing, and then re-initializing, variables.

Let’s take a look at an example.

In each pass though the below ForEach-Object cmdlet, we set the $User variable to my name with the number of the current pass at the end. Then, we increase the $Count variable by 1. Take a look at the below results. Everything looks perfect. My name includes the current iteration through the loop, and the count matches, as it should.

1..5 | ForEach-Object {
    $User = "tommymaynard$_"
    $Count++

    "Count: $($Count)"
    "User: $User"
}

Count: 1
User: tommymaynard1
Count: 2
User: tommymaynard2
Count: 3
User: tommymaynard3
Count: 4
User: tommymaynard4
Count: 5
User: tommymaynard5

Now, let’s run it again.

1..5 | ForEach-Object {
    $User = "tommymaynard$_"
    $Count++

    "Count: $($Count)"
    "User: $User"
}

Count: 6
User: tommymaynard1
Count: 7
User: tommymaynard2
Count: 8
User: tommymaynard3
Count: 9
User: tommymaynard4
Count: 10
User: tommymaynard5

We didn’t want to continue to increase the $Count variable past five, but because we’re scripting, we did it anyway. Writing something like this would required a Remove-Variable, or Clear-Variable command, when the final loop was done. Annoying, and without it, when you’re scripting, you’re going to have inaccuracies.

With functions, we don’t ever need to reinitialize variables to their default values. And of course, we don’t need to remove them, or clear them, before our code is run a second, or third time. This isn’t to say you’ll never change the value of a variable inside a function, it’s to say that variables in a function don’t typically exist before, or after, a function is executed.

Function Show-CountAndUser {
    1..5 | ForEach-Object {
        $User = "tommymaynard$_"
        $Count++

        "Count: $($Count)"
        "User: $User"
    }
}

PS > Show-CountAndUser
Count: 1
User: tommymaynard1
Count: 2
User: tommymaynard2
Count: 3
User: tommymaynard3
Count: 4
User: tommymaynard4
Count: 5
User: tommymaynard5

Okay, that was the first execution. One through five: it looks good. And here’s, the second execution.

PS > Show-CountAndUser
Count: 1
User: tommymaynard1
Count: 2
User: tommymaynard2
Count: 3
User: tommymaynard3
Count: 4
User: tommymaynard4
Count: 5
User: tommymaynard5

Ah, perfect! Write functions, and stop worrying about having to set, and reset, variables. Use the variable scope provided by functions — it wants to help you. It’s one reason for functions over scripts. Back again later.

Part IV is now available.