Category Archives: Quick Learn

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

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.

Write Functions, Not Scripts – Part II

In Part I of this series, I was supposed to begin offering reasons as to why someone would want to stop writing scripts, and start writing functions. I got a bit distracted. Instead, we discussed making an easy, and mostly meaningless, function. The reasoning for this, was because I get this feeling that moving from a script writer to a function writer can be a unsettling endeavor for some. I liken it to bicycle training wheels. They’re so easy, and reliable. It’s just not until you take them off, and get your balance, that you suddenly realize all the things they actually kept your from doing. Learn to balance this bike, and then you can start jumping off your makeshift ramp in the middle of the cul-de-sac, instead of just riding around it.

Speaking of cul-de-sacs, here’s another distraction. It’s our view from Friday morning. That’s not what it usually looks like in Tucson, AZ. Fog is really, really rare. In one of these follow ups, I’ll be sure to include our typical desert view, uninterrupted by “winter” type weather events.

Functions can get advanced, sure, but once you know a few things about them, you’ll never look back. You’ll continue to learn more and more about them. There’s a whole new level of being proud of your work, as you transition to function writing. You’re going to sleep better at night knowing you’ve written five singled-purposed functions vs. that one long script you secretly hate to open and troubleshoot. Maybe, it’s not even a secret.

Look, at the end of the day, a function is nothing more than a scriptblock you can invoke by entering its name. You run commands all the time, maybe even a few, one right after the other. Now you can do the same thing, but by only typing a function’s name and pressing enter.

Let’s assume there’s was a point in time I often wanted to know the date, the system drive letter, the current PowerShell host major version, and return a random number between 1 and 20. If I grew tired of doing this manually, I could create a function to do this for me. It’s not likely this would ever be useful outside this post, but it certainly helps highlight what using a function does. It’s essentially four commands in one. Easy.

Function Start-RandomStuff {
    "Date : $(Get-Date)"
    "System Drive : $env:SystemDrive"
    "Host Major Version : $((Get-Host).Version.Major)"
    "Random Number (1-20) : $(Get-Random -Minimum 1 -Maximum 20)"
}
PS > Start-RandomStuff
Date : 02/13/2018 22:58:00
System Drive : C:
Host Major Version : 5
Random Number (1-20) : 3

Think of a function as a wrapper. You can wrap the execution of various (related) commands by running one command. This isn’t to say our example ran related commands so much; it really didn’t. The point is to keep in mind that functions should be single purposed. They’re tight, short, and to the point. If you start wondering if you’re adding too much procedural code to your function, you probably already have. If you keep adding to the function you’re currently writing, then you better be able to explain why.

I’ve done it again! I haven’t really hit those reasons as to why functions, over scripts. Or perhaps I have a little. I do have some specifics topics, and for the second time, let’s hope the next part of this series, gets serious. There really are a few specific things I want to share — reasons why functions are all you want to be writing.

Back soon. And hopefully, with a non winter picture of the cul-de-sac.

Part III is now available.