Fun With PowerShell Providers

Introduction

Providers are an interesting concept in PowerShell. A provider allows us to use a standard method to communicate with different areas of your computer using the same set of commands. For example, the file system is accessed using a provider. When you use Get-ChildItem it goes through the file system provider to return a list of the files on your computer.

We’ll take a deeper look in a moment, but first let me mention that for all of the examples we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.5, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Listing the Providers

To start with, let’s get a list of the available providers. To do that, PowerShell has the Get-PSProvider cmdlet.

Get-PSProvider

Result:

Name         Capabilities                        Drives
----         ------------                        ------
Registry     ShouldProcess                       {HKLM, HKCU}
Alias        ShouldProcess                       {Alias}
Environment  ShouldProcess                       {Env}
FileSystem   Filter, ShouldProcess, Credentials  {C, D, E, H…}
Function     ShouldProcess                       {Function}
Variable     ShouldProcess                       {Variable}
Certificate  ShouldProcess                       {Cert}

As you can see, there are a variety of providers available out of the box. The default provider is the FileSystem. As stated in the introduction, when you use Get-ChildItem it goes through the FileSystem provider to get a list of files, or more specifically file system objects.

In the right most column of the output, you can see a list of drives. Using the drives we can navigate the provider. To see a detailed list of drives, you can use the Get-PSDrive cmdlet.

Get-PSDrive

Result:

Name           Used (GB)     Free (GB) Provider      Root                 CurrentLocation
----           ---------     --------- --------      ----                 ---------------
Alias                                  Alias
C                 898.79         53.83 FileSystem    C:\                  \Demo\PSCore-QuickStart
Cert                                   Certificate   \
D                 953.72          0.02 FileSystem    D:\
E                 875.38         56.00 FileSystem    E:\
Env                                    Environment
Function                               Function
HKCU                                   Registry      HKEY_CURRENT_USER
HKLM                                   Registry      HKEY_LOCAL_MACHINE
Variable                               Variable
WSMan                                  WSMan

The Name column shows the name we will use when we want to change our location. First look at the column Provider, then at the rows with FileSystem. It shows three drives, C, D, and E. These are the three hard drives on my computer.

The CurrentLocation shows where we are in the provider tree. In the FileSystem provider, these locations are folders. For the C drive, I happen to be in the \Demo\PSCore-QuickStart folder.

Let’s look at how to use some of the other providers.

The Environment Provider

To change to a different provider, you simply use Set-Location followed by the name to move to, just as you would to change to a different hard drive.

Set-Location env
Get-ChildItem

Result:

Name                           Value
----                           -----
__COMPAT_LAYER                 DetectorsAppHealth
ALLUSERSPROFILE                C:\ProgramData
APPDATA                        C:\Users\arcan\AppData\Roaming
CHROME_CRASHPAD_PIPE_NAME      \\.\pipe\crashpad_7700_VQTNLEXXNDEMJHTN
COLORTERM                      truecolor
CommonProgramFiles             C:\Program Files\Common Files
CommonProgramFiles(x86)        C:\Program Files (x86)\Common Files
CommonProgramW6432             C:\Program Files\Common Files
...list truncated for brevity...

This time Get-ChildItem recognized it was operating in the context of the Environment provider, and returned a list of environment variables and their values. (Note I’ve shortened the output for brevity.)

We can access these like any other collection.

$envVars = Get-ChildItem
$envVars[1].Name
$envVars[1].Value

Result:

ALLUSERSPROFILE
C:\ProgramData

Let’s look at another provider.

The Variable Provider

Another useful provider is the Variable provider. It gives access to a list of all variables and their values currently in memory. This includes both user defined variables and built in PowerShell variables. In this example I’ll create a new variable, $aVariable. I’ll then move to the variable provider and list them.

$aVariable = 'Some Value'
Set-Location variable
Get-ChildItem

Result:

Name                           Value
----                           -----
?                              True
^                              Set-Location
$                              Get-ChildItem
args                           {}
aVariable                      Some Value
ConfirmPreference              High
DebugPreference                SilentlyContinue
...list truncated for brevity...

This returns both the names and values for all of the variables. This can be an excellent debugging tool, for example logging all values when an error occurs.

Other Providers

In addition to the built in list of providers you have seen, many modules will add their own providers that you can navigate. On of my favorites is the SQL Server module.

I’ll go over it more in a future post, but know that through it you can use Get-ChildItem to navigate your list of servers, instances, databases, tables, and columns all as if they were your file system.

You will find many modules supply providers you’ll find useful.

Conclusion

In this post we took a brief look at providers, and how to navigate them. In addition, you are now aware there is a big world of providers outside the few default ones you saw in this post.

The demos in this series of blog posts came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight . At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With VSCode Snippets for Markdown and PowerShell

Introduction

I have a confession. I love Markdown. There I said it, my confession is out in the open for all to see.

Seriously though, I do find this documentation language very useful, and easy to use. With just a few commands I can produce a nicely formatted document that can be displayed in my code editor, as well as on platforms like GitHub. I’ve even begun authoring these blog posts in Markdown.

A big reason for me is the ability to integrate it into my projects. VSCode, as well as the full blow Visual Studio, support Markdown (with of course the proper extensions installed). When I create a new PowerShell project in VSCode, I can store the projects documentation in Markdown format right alongside the PowerShell code.

Speaking of VSCode…

VSCode Snippets

A great thing about VSCode is the ability to create your own code snippets. A snippet is a text replacement system where I can enter a few characters of text and VSCode will then replace it with the full snippet. Snippets are activated using the Intellisense feature of VSCode.

One example, I have a standard header I put at the top of my PowerShell scripts. When I defined this header as a snippet, I named it psheader. Now I can enter pshead into my PowerShell script. VSCode’s intellisense feature will prompt me with the list of snippets I have that being with pshead. I can then pick the one I want (psheader), press enter and the snippet of psheader will be replaced with the full text of my file header.

By default, pretty much every language in VSCode has the ability to handle snippets.

Except Markdown.

Markdown Snippets in VSCode

Shocking right? How could such an awesome language like Markdown not have snippets? (And yes, Markdown may not be a language in the strictest sense, but it’s a close enough word for now.)

Well it’s possible to enable Markdown snippets in PowerShell. Sometime back I created a GitHub project that shows you how to enable and use snippets for Markdown. In addition, I included my snippets for both PowerShell and Markdown.

Rather than reiterating everything here, I’ll just point you to that repository.

https://github.com/arcanecode/VSCode_User_Snippets

The main file in the repository, ReadMe.md gives a brief overview and explanation on how to use snippets.

The file Enable_Intellisense_in_Markdown.md does just what is says, shows you how to enable intellisense for Markdown in VSCode.

In VSCode, you can use certain replacements in your snippets. For example, you can embed the $CURRENT_YEAR snippet variable in your snippet (no matter what language) and when the snippet is generated into your code, it will replace the $CURRENT_YEAR with the actual current year.

I included a file, Snippet_Reference.md that lists the various snippet variables and gives a bit more explanation on how to use them.

If you aren’t familiar with Markdown, or don’t use it very often, you’ll find the file Markdown_Cheatsheet.md useful. It has a list of the most often used Markdown formatting commands.

Finally I included two .json files. These are the snippets I use for PowerShell and Markdown on my system. You can use these as a guide in creating your own snippets, or copy the ones you find useful onto your VSCode installation.

If you use the VSCode sync settings feature, they you will be happy to know snippets are included as part of the sync process. You can modify your snippet files on one computer and they will be copied to all the other computers you sync VSCode on.

Conclusion

This was a brief post that primarily served to bring awareness to snippets, as well as the ability to use them with Markdown files. Go take a look at the repository and with just a little effort I believe you’ll find yourself becoming much more productive with the addition of snippets to your toolbox.

Fun With PowerShell Write-Debug

Introduction

In my previous post, Fun With PowerShell Write-Verbose, I introduced the use of the built in -Verbose switch. In this post we’ll dive into its counterpart, the -Debug switch and its companion Write-Debug cmdlet.

In covering Write-Verbose, I mentioned verbose messages are typically targeted at the average user. The -Debug messages are meant to target the developer. These messages are meant to assist the PowerShell developer in trouble shooting and debugging their code.

We’ll take a deeper look at Debug momentarily, but first let me mention that for all of the examples we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.4, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

$DebugPreference – An Introduction

Before we start discussing the Write-Debug cmdlet, we need to cover a PowerShell built in variable, $DebugPreference. If you want to see the value currently there, just type $DebugPreference into your terminal window and hit ENTER.

Depending on how your system is configured, this will usually be set to either Continue or SilentlyContinue. (If it is set to something else then likely you previously ran a script which changed it).

Usually you want it set to Continue. This will display the message provided to the Write-Debug cmdlet, after doing so it will continue to the next line of code.

If it is set to SilentlyContinue, the message is suppressed. Even if Write-Debug is executed, no text is displayed.

For these demos to work, you need to ensure your $DebugPreference is set to Continue, at least to start with. To set it, just assign it.

$DebugPreference = 'Continue'

Later we’ll see some more options when it comes to $DebugPreference, but for now let’s dig into the Write-Debug cmdlet.

Write-Debug

Just like the Verbose switch, the Debug switch is built in. You don’t have to explicitly declare it. Within your code simply use the Write-Debug cmdlet. If PowerShell detects the -Debug switch is passed in, it will display any messages from the Write-Debug cmdlet.

For this demo, we’ll reuse the Show-FileInfo from the previous blog post, Fun With PowerShell Write-Verbose.

function Show-FileInfo ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline)]
          $file
        )

  begin
  {
    $fn = "$($PSCmdlet.MyInvocation.MyCommand.Name)"
    $st = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Starting at $($st.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }

  process
  {
    $dbgMsg = @"
`r`n  Function.......: $fn
  File Name......: $($file.FullName)
  File Length....: $("{0:N0} bytes." -f $file.Length)
  DebugPreference: $DebugPreference
  PS Version.....: $($PSVersionTable.PSVersion.ToString())
"@

    Write-Debug -Message $dbgMsg

    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

  end
  {
    $et = Get-Date
    $rt = $et - $st  # Run Time

    # Format the output time
    if ($rt.TotalSeconds -lt 1)
      { $elapsed = "$($rt.TotalMilliseconds.ToString('#,0.0000')) Milliseconds" }
    elseif ($rt.TotalSeconds -gt 60)
      { $elapsed = "$($rt.TotalMinutes.ToString('#,0.0000')) Minutes" }
    else
      { $elapsed = "$($rt.TotalSeconds.ToString('#,0.0000')) Seconds" }

    Write-Verbose @"
  `r`n  Function: $fn
  Finished at $($et.ToString('yyyy-MM-dd hh:mm:ss tt'))
  Elapsed Time $elapsed
"@
  }
}

The only modifications made are to the process block. We created a variable, $dbgMsg to hold the debugging message we wish to display. Since this is a multiline string, I’m going to do the same thing I did with the Verbose messages and begin with a carriage return / line feed escape sequence ( `r`n ). This will let the word DEBUG appear on a line by itself. Also note I indented the remaining lines two spaces, and used periods so the colons could be aligned. It’s a simple bit of formatting, but it makes the provided information much easier to read.

On the first line I have the name of the function. You’d be surprised how useful this can be. When developing a module or a large complex script with many functions, there may be times when you think your script is calling one function when it is actually calling another.

In this example I then show the file name, including the full path to the file. This can be a useful bit of information to the developer, as it lets us know exactly which directory we are in. Especially useful as the main function only displays the file name, not the full path to the file. The line after shows the file length.

I then display some environmental information. First, I show the value in the previously mentioned $DebugPreference variable. This, perhaps, could help me understand why some debug messages are not getting displayed.

On the next line I show the version of PowerShell this code is running in. Again, a potentially valuable piece of information. There’s always the possibility the developer is running a different version of PowerShell than the user having issues, and this may be a clue to an issue.

Finally, we call the Write-Debug cmdlet. The remaining code is the same as the original function from my previous blog post.

After you highlight the function and run it to put it in memory, let’s call it. For this demo I created a folder C:\Demo and copied a few random files into it.

First, let’s call it without the -Debug switch.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo

Result:

05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 11,200 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
SQLServer2019-x64-ENU-Dev.iso is 1,433,974,784 bytes long.
ubuntu-20.10-desktop-amd64.iso is 2,942,003,200 bytes long.
WideWorldImporters-2021-03-29.zip is 731,246,592 bytes long.
WideWorldImportersDW-2021-03-29.zip is 237,830,144 bytes long.

OK, so far so good. Since we didn’t use the -Debug switch, the Write-Debug statement is ignored. Now let’s call it again, only this time we’ll use -Debug when calling Show-FileInfo.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Debug

Result:

DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\06 - Logic Branching and Looping.ps1
  File Length....: 4,315 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\07 - Functions.ps1
  File Length....: 11,200 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
07 - Functions.ps1 is 11,200 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\08 - Classes.ps1
  File Length....: 6,210 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
08 - Classes.ps1 is 6,210 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\09 - Examples.ps1
  File Length....: 3,125 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
09 - Examples.ps1 is 3,125 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\SQLServer2019-x64-ENU-Dev.iso
  File Length....: 1,433,974,784 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
SQLServer2019-x64-ENU-Dev.iso is 1,433,974,784 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\ubuntu-20.10-desktop-amd64.iso
  File Length....: 2,942,003,200 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
ubuntu-20.10-desktop-amd64.iso is 2,942,003,200 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\WideWorldImporters-2021-03-29.zip
  File Length....: 731,246,592 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
WideWorldImporters-2021-03-29.zip is 731,246,592 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\WideWorldImportersDW-2021-03-29.zip
  File Length....: 237,830,144 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
WideWorldImportersDW-2021-03-29.zip is 237,830,144 bytes long.

As you can see, our debugging messages are now displayed. For each file it shows the debug message, then shows the normal output of the function, the file name and size.

While a simple example, seeing not just the file name but the full file name including its path could supply valuable information to the developer, not to mention the environmental information.

What about the verbose messages? Well there’s no problem using both switches to see all the messages.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Verbose -Debug

Result (note a few files were omitted for brevity):

VERBOSE:
  Function: Show-FileInfo
  Starting at 2021-08-17 05:48:44 PM
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.

*** Some rows removed here for brevity ***

DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\WideWorldImportersDW-2021-03-29.zip
  File Length....: 237,830,144 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
WideWorldImportersDW-2021-03-29.zip is 237,830,144 bytes long.
VERBOSE:
  Function: Show-FileInfo
  Finished at 2021-08-17 05:48:44 PM
  Elapsed Time 24.4972 Milliseconds

As you can see, we get the starting and ending messages from the use of -Verbose, as well as the debugging messages.

$DebugPreference – Digging Deeper

Inquire

In an earlier section of this post we introduced the built-in variable, $DebugPreference, and discussed two of its possible settings, Continue and SilentlyContinue.

What if, though, we wanted PowerShell to stop every time it encounters a Write-Debug?

To do that, we can set the $DebugPreference value to Inquire, as we’ll do in our process block.

  process
  {
    $DebugPreference = 'Inquire'

    $dbgMsg = @"
`r`n  Function.......: $fn
  File Name......: $($file.FullName)
  File Length....: $("{0:N0} bytes." -f $file.Length)
  DebugPreference: $DebugPreference
  PS Version.....: $($PSVersionTable.PSVersion.ToString())
"@

    Write-Debug -Message $dbgMsg

    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

Run the function to get the new version into memory, then let’s call Show-FileInfo again.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Debug

This time, it displays our first debug message, but then displays a prompt in the terminal window.

Result:

DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Inquire
  PS Version.....: 7.1.4
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Yes"):

You now have the option to hit Y to continue (or just press ENTER), or stop by pressing H to stop execution. In this case, using S to suspend has the same affect as halting.

What about A, for Yes to All? You might think it will just run the rest of the script without stopping. For this demo though, that’s not the case. The A option is meant for the rare situations where you are piping data through the Write-Debug cmdlet.

In our demo, as I suspect in most situations you’ll encounter, pressing A has the same effect as pressing Y.

Stop

Using Inquire we can prompt the developer (or the person running the script) as to what they want to do once they hit the Write-Debug. Let’s imagine a situation though where want our script to stop running. In a moment we’ll look at a situation where this can be useful, but for now let’s just see stop in action.

Here we’ll just change our $DebugPreference to Stop.

  process
  {
    $DebugPreference = 'Stop'

    $dbgMsg = @"
`r`n  Function.......: $fn
  File Name......: $($file.FullName)
  File Length....: $("{0:N0} bytes." -f $file.Length)
  DebugPreference: $DebugPreference
  PS Version.....: $($PSVersionTable.PSVersion.ToString())
"@

    Write-Debug -Message $dbgMsg

    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

As usual, re-run the function to refresh the version in memory, then call Show-FileInfo again.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Debug

Result:

DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Stop
  PS Version.....: 7.1.4
Write-Debug:
Line |
  30 |      Write-Debug -Message $dbgMsg
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The running command stopped because the preference variable "DebugPreference" or common parameter is set to Stop:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Stop
  PS Version.....: 7.1.4

You can see our debug message was displayed, then we got a message to let us know the debug preference was set to stop, which is why code execution stopped.

Conditional Stopping

The stop command becomes more useful when we make the stop conditional. We want our code to keep running until we find a certain condition. Once we find it, we’ll display debugging information then stop our script.

While Write-Debug doesn’t have a built in method for this, we can construct the code ourselves using a simple if statement. For this demo we’ll display our debug message until the point we hit a specified condition.

  process
  {
    if ($file.Length -gt 100000)
      { $DebugPreference = 'Stop' }
    else
      { $DebugPreference = 'Continue' }

    $dbgMsg = @"
`r`n  Function.......: $fn
  File Name......: $($file.FullName)
  File Length....: $("{0:N0} bytes." -f $file.Length)
  DebugPreference: $DebugPreference
  PS Version.....: $($PSVersionTable.PSVersion.ToString())
"@

    Write-Debug -Message $dbgMsg

    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

In this example, pretend we know something goes wrong when our file length exceeds 100 KB (100,000 bytes). We use an if statement to check the length. If it is greater than 100,000 bytes, it will set the $DebugPreference to Stop, otherwise it will set it to Continue.

Note we could have also chosen not to call Write-Debug at all if the length is less then 100,000 bytes, or any other number of things. I chose to go ahead and display the debug info in this case, as there’s always the possibility it may provide useful information. Let’s update our function in memory by running it, then we can execute it.

Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Debug

Result:

DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\05 - Arrays and Hashtables.ps1
  File Length....: 8,486 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\06 - Logic Branching and Looping.ps1
  File Length....: 4,315 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\07 - Functions.ps1
  File Length....: 11,200 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
07 - Functions.ps1 is 11,200 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\08 - Classes.ps1
  File Length....: 6,210 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
08 - Classes.ps1 is 6,210 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\09 - Examples.ps1
  File Length....: 3,125 bytes.
  DebugPreference: Continue
  PS Version.....: 7.1.4
09 - Examples.ps1 is 3,125 bytes long.
DEBUG:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\SQLServer2019-x64-ENU-Dev.iso
  File Length....: 1,433,974,784 bytes.
  DebugPreference: Stop
  PS Version.....: 7.1.4
Write-Debug:
Line |
  33 |      Write-Debug -Message $dbgMsg
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The running command stopped because the preference variable "DebugPreference" or common parameter is set to Stop:
  Function.......: Show-FileInfo
  File Name......: C:\Demo\SQLServer2019-x64-ENU-Dev.iso
  File Length....: 1,433,974,784 bytes.
  DebugPreference: Stop
  PS Version.....: 7.1.4

As you can see, the first file we encounter with a length of 100,000 bytes or greater causes execution to stop.

Bear in mind, using Stop has a dramatic effect on your code. For my normal day to day development I may use a Stop, but when I send this script to production I’d either remove the stop logic all together, or at least replace it with the previously discussed Inquire setting.

Also note that if you use Stop, the script STOPS. The end block will never execute. This could potentially leave your script, as well as the scripting environment in an unstable state. So as cool as the Stop setting is, I recommend you use it sparingly and as a general rule do not include it in production code.

Resetting $DebugPreference

Once your script is done, it’s a good idea to restore the $DebugPreference setting so it is in a default state for next time. Of course, in the end block you could simply add $DebugPreference = 'SilentlyContinue' (or Continue), but what if that wasn’t the state when your script began?

It’s a better idea to capture the state of $DebugPreference when your script begins. So in the ‘begin’ block, we’ll add a line at the very top, $currentDebugPreference = $DebugPreference.

Then in the end block we’ll add a line to reset the debug preference variable to its value from the start of the script, $DebugPreference = $currentDebugPreference.

Below is the completed script with one other alteration, which you’ll find in the process block. I changed the Stop to Inquire and the Continue to SilentlyContinue. The first change will give us the option to stop or continue. The second will suppress the debug message for the files we (in our fictional scenario) know are OK, those below 100,000 bytes. This was done just for demo purposes, so you could see the effect of the various settings for $DebugPreference.

function Show-FileInfo ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline)]
          $file
        )

  begin
  {
    $currentDebugPreference = $DebugPreference
    $fn = "$($PSCmdlet.MyInvocation.MyCommand.Name)"
    $st = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Starting at $($st.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }

  process
  {
    if ($file.Length -gt 100000)
      { $DebugPreference = 'Inquire' }
    else 
      { $DebugPreference = 'SilentlyContinue' }

    $dbgMsg = @"
`r`n  Function.......: $fn
  File Name......: $($file.FullName)
  File Length....: $("{0:N0} bytes." -f $file.Length)
  DebugPreference: $DebugPreference
  PS Version.....: $($PSVersionTable.PSVersion.ToString())
"@

    Write-Debug -Message $dbgMsg

    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

  end
  {
    $DebugPreference = $currentDebugPreference
    $et = Get-Date
    $rt = $et - $st  # Run Time

    # Format the output time
    if ($rt.TotalSeconds -lt 1)
      { $elapsed = "$($rt.TotalMilliseconds.ToString('#,0.0000')) Milliseconds" }
    elseif ($rt.TotalSeconds -gt 60)
      { $elapsed = "$($rt.TotalMinutes.ToString('#,0.0000')) Minutes" }
    else
      { $elapsed = "$($rt.TotalSeconds.ToString('#,0.0000')) Seconds" }

    Write-Verbose @"
  `r`n  Function: $fn
  Finished at $($et.ToString('yyyy-MM-dd hh:mm:ss tt'))
  Elapsed Time $elapsed
"@
  }
}

Now let’s run it, after highlighting the function and executing it so it is refreshed in memory. First I’ll set the $DebugPreference to Continue, so it will be different from either the Inquire or SilentlyContinue. Then I’ll call it, using both the Verbose and Debug switches. Finally I’ll display the $DebugPreference again, to prove it was reset at the end of the function. Note too when the Inquire kicks in I will just answer Y so the script can complete normally.

$DebugPreference = 'Continue'
Get-ChildItem -Path 'C:\Demo' | Show-FileInfo -Verbose -Debug
$DebugPreference

Result:

VERBOSE:   
  Function: Show-FileInfo
  Starting at 2021-08-17 06:25:46 PM
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 11,200 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
DEBUG: 
  Function.......: Show-FileInfo
  File Name......: C:\Demo\SQLServer2019-x64-ENU-Dev.iso
  File Length....: 1,433,974,784 bytes.
  DebugPreference: Inquire
  PS Version.....: 7.1.4
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Yes"):
SQLServer2019-x64-ENU-Dev.iso is 1,433,974,784 bytes long.
DEBUG: 
  Function.......: Show-FileInfo
  File Name......: C:\Demo\ubuntu-20.10-desktop-amd64.iso
  File Length....: 2,942,003,200 bytes.
  DebugPreference: Inquire
  PS Version.....: 7.1.4
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Yes"):
ubuntu-20.10-desktop-amd64.iso is 2,942,003,200 bytes long.
DEBUG: 
  Function.......: Show-FileInfo
  File Name......: C:\Demo\WideWorldImporters-2021-03-29.zip
  File Length....: 731,246,592 bytes.
  DebugPreference: Inquire
  PS Version.....: 7.1.4
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Yes"):
WideWorldImporters-2021-03-29.zip is 731,246,592 bytes long.
DEBUG: 
  Function.......: Show-FileInfo
  File Name......: C:\Demo\WideWorldImportersDW-2021-03-29.zip
  File Length....: 237,830,144 bytes.
  DebugPreference: Inquire
  PS Version.....: 7.1.4
Confirm
Continue with this operation?
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Yes"):
WideWorldImportersDW-2021-03-29.zip is 237,830,144 bytes long.
VERBOSE:   
  Function: Show-FileInfo
  Finished at 2021-08-17 06:25:57 PM
  Elapsed Time 11.7336 Seconds
Continue

As you would probably expect by this point in the post, we show our verbose information. We then see a list of files from the normal code in the script. The debug messages for files under 100,000 bytes were suppressed because we’d set the $DebugPreference to SilentlyContinue.

We then see the debug messages, along with inquiries as to our wish to continue. Since we said yes to all of them, the code in the end block was able to execute and display the closing verbose message.

The very last line of the result is Continue, which came from the last line we used to run the script, $DebugPreference. This confirms the value was indeed reset to the value that was present when the script started.

Conclusion

Well this was a monster sized post, so thanks for sticking with it to the end. In it, we saw how the built in -Debug switch works along with the companion Write-Debug. We also covered the built-in PowerShell variable $DebugPreference, and saw how it can affect the behavior of the Write-Debug cmdlet.

As you can see, the use of Debug can provide valuable feedback to you as a PowerShell developer, supplying critical information about the internal state of your script as well as the environment it is running in.

If you want to learn more about PowerShell, check out my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight . At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses or any other course on the site.

Fun With PowerShell Write-Verbose

Introduction

In my previous post, Fun With the PowerShell Switch Parameter, I introduced the use of switches with PowerShell functions. We learned how they work, and how to create your own. For all functions, PowerShell creates a set of switches that are just “built in” to PowerShell. You do not have to explicitly create these yourself, you can simply use them and implement them within your own functions.

Two of the most used are -Verbose and -Debug. When used with the Write-Verbose and Write-Debug cmdlets they will display informational messages to the user of your functions. In this post, we’ll focus on the Verbose switch. The next post in the series will examine the Debug switch.

We’ll take a deeper look at Verbose in a moment, but first let me mention that for all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.4, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Write-Verbose

Messages displayed using the -Verbose switch are intended for the users. It can advise a user of long running processes, or let the user know a function is indeed being executed. Let’s take a look at a function we’ll use for this demo.

function Show-FileInfo ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline)]
          $file
        )

  process
  {
    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

}

The purpose of this function is to take a stream of files from the pipeline, then display the file name and the file size. It’s extremely simple but will serve for this blog post. If you need more info on how pipelined functions work, please see my recent post Fun With PowerShell Pipelined Functions. That post will explain the structure of the function, including the begin, process and end blocks.

Let’s run this function. First, highlight the function and execute it to get it in memory. Then let’s call the function.

Get-ChildItem | Show-FileInfo

Result:

01 - Cmdlets.ps1 is 3,732 bytes long.
02 - Providers.ps1 is 1,819 bytes long.
03 - Variables.ps1 is 4,717 bytes long.
04 - Strings.ps1 is 7,970 bytes long.
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 9,574 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
Company.csv is 9,694 bytes long.
Company.json is 19,479 bytes long.
ReadMe.md is 1,115 bytes long.

So far so good, you can see the files I have in my current folder (obviously your list of files will vary). Now let’s say we want to let the user know when the function starts and ends, so the user can get an idea of how long it will take to execute. Most of the time our function runs in an automated fashion so we don’t need this, however there may be times when a user wants to manually run it so they can get execution times.

We can modify our function to use the Write-Verbose cmdlet to display this message when the built in -Verbose switch is used. First, we’ll add a begin block to our function to show the start time.

begin
  {
    $fn = "$($PSCmdlet.MyInvocation.MyCommand.Name)"
    $st = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Starting at $($st.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }

The first thing we do is get the function name. PowerShell has a built in variable called $PSCmdlet which has information about the code currently being executed. It has an object property called MyInvocation, which has an object property named MyCommand. This has a property called Name which holds the name of the function currently being executed.

While this seems a bit more complicated than just keying in $fn = 'Show-FileName', it actually leverages code reuse. I can paste this into any function and continue, without the need to update anything.

Next I capture the current date and time into a variable called $st, short for start time.

The call to Write-Verbose comes next. By default, when a Write-Verbose is executed, it displayes the text VERBOSE: followed by the message, such as:

VERBOSE: Your Message Here

In my example though I want to display a multi line message. The first line will hold the name of the function, the second will be the start time. To accomplish this I am using a here string. If you aren’t familiar with here strings, see my post from July 12, 2021 named Fun With PowerShell Strings.

In the here string I start with the characters `r`n. This will send a carriage return – line feed to the here string, so the VERBOSE: will appear on a line by itself. I then add two spaces so the line with the function name will be indented two spaces. Not necessary, but I think it makes it more readable.

The next line is the starting time, also indented two spaces. Here I take the start time and convert it to a string. Into the ToString function I pass in a date format string to display the current date and time. This will produce year-month-day hour:minute:second, with the tt becoming AM or PM. Finally I close out the here string as well as the process block.

As is, this code could be copy and pasted into any function without modification, ready to use. You could even go so far as to make it a snippet in VSCode, I have a project on GitHub, VSCode_User_Snippets that explains how to create user snippets in VSCode. It was written to target implementing MarkDown snippets, but I also included my default PowerShell snippets. Note that the PowerShell IDE used with PowerShell 5.1 also allows for reusable code snippets.

OK, we’ve handled the message we want to show when the function starts. Now we’ll add an end block to display the ending time.

end
  {
    $et = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Finished at $($et.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }

Since I already had the function name in the $fn variable I didn’t have to get it again. I just grab the current time into the variable $et (for end time) and display it as I did in the opening.

For reference here is the function again with the new blocks added.

function Show-FileInfo ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline)]
          $file
        )

  begin
  {
    $fn = "$($PSCmdlet.MyInvocation.MyCommand.Name)"
    $st = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Starting at $($st.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }

  process
  {
    $retVal = "$($file.Name) is {0:N0} bytes long." -f $file.Length
    $retVal
  }

  end
  {
    $et = Get-Date
    Write-Verbose @"
  `r`n  Function: $fn
  Finished at $($et.ToString('yyyy-MM-dd hh:mm:ss tt'))
"@
  }
}

If I run this, you'll see I get the exact same results I did the first time.

Get-ChildItem | Show-FileInfo

Result:

01 - Cmdlets.ps1 is 3,732 bytes long.
02 - Providers.ps1 is 1,819 bytes long.
03 - Variables.ps1 is 4,717 bytes long.
04 - Strings.ps1 is 7,970 bytes long.
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 9,574 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
Company.csv is 9,694 bytes long.
Company.json is 19,479 bytes long.
ReadMe.md is 1,115 bytes long.

So how do I get the verbose messages to display? Well all I have to do is add the -Verbose switch to the call.

Get-ChildItem | Show-FileInfo -Verbose

Result:

VERBOSE:   
  Function: Show-FileInfo
  Starting at 2021-08-15 07:28:26 PM
01 - Cmdlets.ps1 is 3,732 bytes long.
02 - Providers.ps1 is 1,819 bytes long.
03 - Variables.ps1 is 4,717 bytes long.
04 - Strings.ps1 is 7,970 bytes long.
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 9,574 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
Company.csv is 9,694 bytes long.
Company.json is 19,479 bytes long.
ReadMe.md is 1,115 bytes long.
VERBOSE:   
  Function: Show-FileInfo
  Finished at 2021-08-15 07:28:26 PM

Simply by adding the -Verbose switch, it now displays the text passed into any Write-Verbose cmdlets you coded. If you look at our param block, you won’t see the Verbose switch declared, as we had to in the previous article Fun With the PowerShell Switch Parameter.

PowerShell automatically adds the Verbose switch to every advanced function you author. You don’t have to do anything special. If PowerShell sees you’ve added the switch when you (or a user) runs your function, it will automatically execute any Write-Verbose cmdlets for you.

Taking It to the Next Level

As is, this function requires our user to manually calculate the run time by comparing the start and end times. But PowerShell developers are a courteous bunch, and so we’ll take care of this for them.

Let’s update the end block to perform the calculation.

  end
  {
    $et = Get-Date

    $rt = $et - $st  # Run Time

    # Format the output time
    if ($rt.TotalSeconds -lt 1)
      { $elapsed = "$($rt.TotalMilliseconds.ToString('#,0.0000')) Milliseconds" }
    elseif ($rt.TotalSeconds -gt 60)
      { $elapsed = "$($rt.TotalMinutes.ToString('#,0.0000')) Minutes" }
    else
      { $elapsed = "$($rt.TotalSeconds.ToString('#,0.0000')) Seconds" }


    Write-Verbose @"
  `r`n  Function: $fn
  Finished at $($et.ToString('yyyy-MM-dd hh:mm:ss tt'))
  Elapsed Time $elapsed
"@
  }

After getting my end time, I subtract the start time from the end time, and place it in the $rt variable, short for run time. This will produce a variable that is a datetime datatype.

A PowerShell datetime datatype has some very useful methods. The first we’ll use is TotalSeconds, which indicates how many seconds are in our variable. In the if statement, check to see if the run time seconds is less than 1. If so, it uses another property TotalMilliseconds that (obviously) returns the total number of milliseconds in the run time variable. It converts it to a string, and we pass in a string format so we get a nice output. Finally it appends the text Milliseconds so the user will now what time unit they are dealing with, and places it all in a variable $elapsed.

The elseif is similar. If the total seconds exceeds 60, we’ll display the run time in minutes. The else script block covers the case when the elapsed time is between 1 and 60 seconds.

Finally we add a third line to the here string passed into Write-Verbose.

Highlight the entire function and execute it so the new version is in memory. Then call the function using the Verbose switch.

Get-ChildItem | Show-FileInfo -Verbose

Result:

VERBOSE:   
  Function: Show-FileInfo
  Starting at 2021-08-15 07:59:39 PM
01 - Cmdlets.ps1 is 3,732 bytes long.
02 - Providers.ps1 is 1,819 bytes long.
03 - Variables.ps1 is 4,717 bytes long.
04 - Strings.ps1 is 7,970 bytes long.
05 - Arrays and Hashtables.ps1 is 8,486 bytes long.
06 - Logic Branching and Looping.ps1 is 4,315 bytes long.
07 - Functions.ps1 is 9,977 bytes long.
08 - Classes.ps1 is 6,210 bytes long.
09 - Examples.ps1 is 3,125 bytes long.
Company.csv is 9,694 bytes long.
Company.json is 19,479 bytes long.
ReadMe.md is 1,115 bytes long.
VERBOSE:   
  Function: Show-FileInfo
  Finished at 2021-08-15 07:59:39 PM
  Elapsed Time 19.9018 Milliseconds

As you can see, these few extra lines of code provides a more professional looking output, not to mention accurate. The user will not be forced to manually calculate times.

And don’t forget you can place these into code snippets for fast and easy use. As constructed both the opening and closing sections can simply be inserted without modification. Again, see my GitHub project, VSCode_User_Snippets for examples on using code snippets in Visual Studio Code.

Conclusion

In this post we saw how the built in -Verbose switch works along with the companion Write-Verbose. This can provide a useful tool to keep users appraised of extra, “meta” information such as run times without obscuring the output of the function when it is used in normal circumstances.

In the next blog post we’ll look at the counterpart for verbose, -Debug and it’s Write-Debug cmdlet.

If you want to learn more about PowerShell, check out my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight . At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With the PowerShell Switch Parameter

Introduction

Over the last few posts I’ve been covering PowerShell functions, both Basic Functions and Advanced Functions. In this post I had originally intended to cover two switches available to all advanced functions, Verbose and Debug.

But then it occurred to me, not everyone may know what a switch parameter is. And to be clear, I’m not talking about the switch statement. I covered that in my post Fun With PowerShell Logic and Branching.

Here, I’m talking about the ability to use what PowerShell calls a switch parameter.

We’ll take a deeper look in a moment, but first let me mention that for all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

A Simple Switch Example

If you’ve been following my series, you know that you can pass in values, in other words parameters, to a function by name. For example:

Get-AValue -one 33 -two 42

-one and -two were the parameter names, and 33 and 42 the values passed in for them.

A switch is similar, you list the name of the switch on call to the function, but unlike a regular parameter you pass in no value. The presence of the switch is enough to tell the function what you want to do.

Let’s look at an example using the common Write-Host cmdlet.

Write-Host 'Hi Mom' -NoNewline -ForegroundColor Green
Write-Host ' and Dad' -ForegroundColor Yellow

Result:
Hi Mom and Dad Image

Normally, Write-Host displays its text, then automatically moves the cursor to the next line. However, Write-Host has a -NoNewLine switch. Including the switch will keep Write-Host from adding the line feed at the end, and allows us do fun things like having two different colors of text on the same line.

Note that we didn’t have to pass in any value, didn’t have to set it to true or false. Just including the switch was enough to say “hey don’t wrap to a new line”.

Implementing Your Own Switches

Switches wouldn’t be any fun if we couldn’t use them too! And it’s actually quite easy. I started off the whole PowerShell series on my blog with a post Fun With PowerShell Get-Random. In it I described (among other things) how to use the Get-Random cmdlet to return a random value from an array. We’ll borrow on that idea for this function.

function Get-RandomSouthAmericanCountry()
{
[CmdletBinding()]
param(
[switch] $UpperCase
)

$array = (
'Argentina', 'Bolivia', 'Brazil',
'Chile', 'Columbia', 'Ecuador',
'Guyana', 'Paraguay', 'Peru',
'Suriname', 'Uruguay', 'Venezuela'
)

# Get an item from the array and convert from
# a generic object to a string
$retVal = $($array | Get-Random).ToString()

# If user passed in upper case switch,
# upper case return value
if ($UpperCase.IsPresent)
{
$retVal = $retVal.ToUpper()
}

return $retVal

}

I’ll give a shout of sorts to my wonderful geek friends who live on the South American continent with this example, our Get-RandomSouthAmericanCountry function. Of course we start with [CmdletBinding()] to indicate this is an advanced function. Then we have our param block.

param(
[switch] $UpperCase
)

We have one parameter named $UpperCase. But instead of having a traditional data type in front, we have [switch]. This will indicate to PowerShell that $UpperCase is a switch. We’ll see how to use it within our code in a moment, but first let’s take a quick look at the rest of the function.

After the param block we have an array which lists the countries in South America (according to the countries of the world website I found).

I then fall to this line to get the country:

$retVal = $($array | Get-Random).ToString()

First, I use $array | Get-Random to get a random country. This will return an element from the array, but I need it to be a datatype of string rather than a generic object. So I wrap the result of Get-Random in $( ) to make PowerShell evaluate it as an object. Then I can call the .ToString() method of the object to convert it to a string. Finally it gets assigned to my return variable, $retVal.

The next part is where I look to see if my switch was used.

if ($UpperCase.IsPresent)
{
$retVal = $retVal.ToUpper()
}

Here, you use the name of the switch and access its .IsPresent property. This returns $true if the switch was passed in. Thus if it was passed in, the if statement will take affect and call the .ToUpper() method on our $retVal string. This is why we had to convert to a string, string data types have a .ToUpper() method, generic objects don’t.

As the final step we return the value held in $retVal, which sticks to the rule of having one exit point for a function.

After highlighting the function and running it using F8/F5 to get it in memory, we’ll call it. First, we’ll so so using it without the switch.

Get-RandomSouthAmericanCountry

Result:
Venezuela

Great, now let’s call it with our switch.

Get-RandomSouthAmericanCountry -UpperCase

Result:
URUGUAY

In this call, just including the switch caused the output to display in upper case.

Switches Work with Basic Functions Too

In the above example I used an advanced function. Be aware that if you are using a basic function, as I described in a previous article, you can still use a switch.

Here is the previous example rewritten as a basic function. (Note the function declaration should all be on one line, the blog layout will probably wrap it.)

function Get-RandomSouthAmericanCountryBasic([switch] $UpperCase)
{
$array = (
'Argentina', 'Bolivia', 'Brazil',
'Chile', 'Columbia', 'Ecuador',
'Guyana', 'Paraguay', 'Peru',
'Suriname', 'Uruguay', 'Venezuela'
)

# Get an item from the array and convert from
# a generic object to a string
$retVal = $($array | Get-Random).ToString()

# If user passed in upper case switch,
# upper case return value
if ($UpperCase.IsPresent)
{
$retVal = $retVal.ToUpper()
}

return $retVal

}

The only real difference is the removal of the [CmdletBinding()} statement and the param block. I then declared the [switch] $UpperCase inside the parenthesis like you would do with a basic function.

By the way, like most things in PowerShell casing isn’t important. These are all the same to PowerShell:

[switch] $UpperCase
[SWITCH] $UpperCase
[Switch] $UpperCase

When you strongly type your variables, for example using [int] or [string], it’s common to use lower case, so I generally stick to lower for [switch], but it’s not mandatory. Just pick a standard for you and your team, then stick with it.

Naming Switches

When creating your switches, be sure to use clear names. In our example, UpperCase was a good switch name. Especially when using the Get- verb in our function name, as it implied the results should be in upper case. It doesn’t occur to me that I need to pass in some value to make it happen.

Contrast that with a switch name like MaxValue. Here, I wonder if I want to return a maximum value, or if I need to pass in a value that I want to be the maximum value, or something else.

A Note on $switchName -eq $true

You may see examples on the web such as:

if ($UpperCase -eq $true)

to check to see if a switch is present. This is a much older, and very much out of date method. Microsoft recommends you use the .IsPresent method that you saw in these examples, you should stick to it in your code.

Conclusion

In this post we covered the useful switch parameter. It’s very easy to use, and can add a lot of flexibility to your functions. This also gives a good foundation for discussion of the Verbose and Debug switches built into all Advanced Functions in our next blog post.

The demos in this series of blog posts came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Pipelined Functions

Introduction

In my previous post, I covered the use of PowerShell Advanced Functions. I highly suggest you read it if you haven’t, it provides some foundational knowledge that will be important to understand for this post.

In this post, we’ll see how to pipeline enable your functions. Just like a cmdlet, you’ll be able to take input from the pipeline, work with it, then send it out your function back into the pipeline.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Pipelining Your Advanced Functions

Pipelining is what gives PowerShell it’s real power. The ability to have small, focused cmdlets (or functions) that you can chain together to produce a useful output. Let’s see this simple, common example:

Get-ChildItem | Sort-Object -Property Length

Result:
Mode  LastWriteTime       Length Name
----  -------------       ------ ----
la--- 11/24/2020  3:37 PM   1115 ReadMe.md
la--- 11/24/2020  3:42 PM   1819 02 - Providers.ps1
la--- 11/24/2020  3:42 PM   3125 09 - Examples.ps1
la--- 11/24/2020  3:42 PM   3732 01 - Cmdlets.ps1
la---  7/15/2021  4:48 PM   4315 06 - Logic Branching and Looping.ps1
la--- 11/24/2020  3:42 PM   4717 03 - Variables.ps1
la--- 11/24/2020  3:42 PM   6210 08 - Classes.ps1
la---  7/20/2021  4:35 PM   6843 07 - Functions.ps1
la---  7/18/2021  8:18 PM   7970 04 - Strings.ps1
la---  7/9/2021   8:00 PM   8486 05 - Arrays and Hashtables.ps1
la--- 11/20/2020 12:58 AM   9694 Company.csv
la--- 11/20/2020 12:58 AM  19479 Company.json

As you can see, Get-ChildItem‘s output was piped into the Sort-Object cmdlet using the pipe symbol, the vertical bar | . Sort-Object took a parameter of a property name, in this case Length, and sorted its output based on that property name.

So how fun would it be to write your own functions that could work with the pipeline? Well as it turns out it’s not only fun but easy too. Here’s an example of a simple pipeline enabled function.

function Get-PSFiles ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline) ]
          $file
        )

  begin  { }

  process
  {
    if ($file.Name -like "*.ps1")
    {
      $retval = "  PowerShell file is $($file.Name)"
      # This is the equivalent of: return $retval
      $retval
    }
  }

  end { }
}

The purpose of Get-PSFiles is to examine each file being passed in from the pipeline. If it finds the file name ends in .ps1, it will pass it onto the next item in the pipeline. Otherwise it gets ignored.

First off is the [CmdletBinding()], which you know by now is needed to let Powershell know this is an advanced function.

Now we have our param block. Note that the parameter block has an attribute of (ValueFromPipeline). When an object comes in from the pipeline it will get copied into the variable with the (ValueFromPipeline) attribute, in this case $file.

The next block in the function is the begin { } block. The begin block executes once, and only once, when the function is first called. Here you could do things like set variables or perform a calculation that will be used later in the function.

In my experience the begin block isn’t used a lot, and if yours is empty you can actually omit it completely from your function.

The next section is the process block. This is where all the fun happens! Here we can work with the objects as they are passed in, one by one, from the pipeline.

In this simple script, I look at the Name property of the item being passed in, which is stored in the variable $file. If it ends in .ps1, I set the return value ($retVal) to a string which has some text including the file name. If it doesn’t end in .ps1, I just don’t do anything with it.

I then have a simple line of $retVal. Since it’s not consumed by any other code PowerShell pushes it out into the next item in the pipeline. If there is no next item, it is displayed to the screen.

Once all the incoming items from the pipeline have been processed, the function continues to the end { } block. This is where you would do any cleanup. Similar to the begin block, end does not get used a lot, and you can omit it entirely if it is empty.

To use our Get-PSFiles function, we first need to highlight it and use F5/F8 to load it into memory. Once done we’ll be ready to call it. In this case when we call it we’ll save the output into a variable then display the contents of the variable.

$output = Get-ChildItem | Get-PSFiles
$output

Result:
  PowerShell file is 01 - Cmdlets.ps1
  PowerShell file is 02 - Providers.ps1
  PowerShell file is 03 - Variables.ps1
  PowerShell file is 04 - Strings.ps1
  PowerShell file is 05 - Arrays and Hashtables.ps1
  PowerShell file is 06 - Logic Branching and Looping.ps1
  PowerShell file is 07 - Functions.ps1
  PowerShell file is 08 - Classes.ps1
  PowerShell file is 09 - Examples.ps1

The directory I’m running from has other files in it besides PowerShell scripts, but the function we wrote filtered those out and only returns my PowerShell script files.

You may wonder, what datatype gets returned? We can find out by checking the type of the variable using the GetType() method built into all PowerShell variables.

$output.GetType()

Result:
IsPublic IsSerial Name     BaseType
-------- -------- ----     --------
True     True     Object[] System.Array

As you can see, it returns an array of objects.

So this function is useful, but limited in one way. It can only work with file type objects such as ones retrieved using Get-ChildItem. What if instead we wanted a function that only worked on a single property?

Line Continuation

Before we get into the next section, I want to bring up the topic of line continuation. Sometimes a line of PowerShell code will get very long. It can be useful to break it up across multiple lines for readability.

PowerShell uses the backtick character, the ` as its line continuation character. On US keyboard this is typically to the left of the number 1. If you are outside the US you’ll have to hunt around to find it, I’ll have to claim ignorance of keyboard layouts for other areas of our globe.

The line continuation character simply tells PowerShell “hey, this set of commands continues on the next line, treat it all as one line of code until you no longer find another backtick character”.

Here’s a simple example:

Write-Host 'Here are the names!' `
  -ForegroundColor Green

As you can see I have a simple Write-Host which displays information to the screen. At the very end of the line I have the backtick character. Then the Write-Host cmdlet continues, passing in the parameter of ForegroundColor.

I encourage you to use line continuation characters whenever you have a long line of code as it will make it much easier to read. It’s also useful to folks like myself who write blog posts or give presentations where my display area is limited. If you go look at some examples on my GitHub site, you’ll see I use them quite a bit.

On a large monitor such as the one I normally use it’s not as needed. But when I write a blog post like this one, or go present at a user group, the amount of space I have is much more limited.

It’s also worth mentioning that the pipe symbol | has line continuation built right into it. I could have taken my earlier example and entered into my editor like so:

Get-ChildItem |
  Sort-Object -Property Length

The pipe symbol has to be the very last character on the line. There cannot be any spaces, comments, or other code after it. On the next line I chose to indent the Sort-Object command by two spaces, this is strictly to make the code easier to read and is not required, but is a good habit.

OK that little detour complete, let’s continue our fun by using just a single property from the pipeline in our function.

Using a Single Property in your Pipeline Function

One way we can make our functions more reusable is to work with a single property. For example, almost all cmdlets return a set of objects that have a Name property. We could create a function that works with just that property, and thus use it with all cmdlets that return objects with a Name property.

This next example is a bit contrived, to show you the concepts, and isn’t something I’d normally code in “real life” but it will serve to show you how this concept works.

function Show-BName ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipelineByPropertyName)]
          $Name
        )

  begin
  {
    Write-Host 'Here are the names!' `
      -ForegroundColor Green
    $names = @()
  }

  process
  {
    $names += $name
    "  Here is the name: $name"
  }

  end
  {
    Write-Host "Those were the names you passed in:" `
      -ForegroundColor Green
    foreach ($n in $names)
    {
       Write-Host "  You passed in " `
         -ForegroundColor White -NoNewline
      Write-Host $n -ForegroundColor Yellow
    }
  }
}

In the parameter block, we use a slightly different attribute, ValueFromPipelineByPropertyName. This tells PowerShell to take each object being passed in, and copy it’s Name property into our variable, in this case $Name.

Note that the name of the parameter must match the name of the property you want to work with. Thus the Name property is copied into the $Name parameter. The rest of the object will be discarded, and not available to you in the function.

The function falls into our begin block, in which I’ve included a Write-Host statement to display a message our function is beginning. Let me stress, you normally do not include Write-Host messages in functions. Typically the script that calls the function will take the output, and display the results. I’m doing this here only to demonstrate how each piece of the advanced function works.

I then do something you might do, I create a variable called $names. I initialize it as an empty array.

Now the function moves into the process block. In it, I take the name that came in from the pipeline and add it to the array. I then embed the name in a string. Since I don’t do anything with the string, it is now passed out to return to the pipeline.

Finally we hit the end block. Here, I’m using some Write-Host statements to show we’re done and what names were processed. In a real world situation you might use this for logging or some other reason that fits your needs.

So let’s see this in action. As before, highlight the function and run it using F8/F5 to get it into memory. Then we can call it.

Get-ChildItem |
  Show-BName

Result:
Here are the names!
  Here is the name: 01 - Cmdlets.ps1
  Here is the name: 02 - Providers.ps1
  Here is the name: 03 - Variables.ps1
  Here is the name: 04 - Strings.ps1
  Here is the name: 05 - Arrays and Hashtables.ps1
  Here is the name: 06 - Logic Branching and Looping.ps1
  Here is the name: 07 - Functions.ps1
  Here is the name: 08 - Classes.ps1
  Here is the name: 09 - Examples.ps1
  Here is the name: Company.csv
  Here is the name: Company.json
  Here is the name: ReadMe.md
Those were the names you passed in:
  You passed in 01 - Cmdlets.ps1
  You passed in 02 - Providers.ps1
  You passed in 03 - Variables.ps1
  You passed in 04 - Strings.ps1
  You passed in 05 - Arrays and Hashtables.ps1
  You passed in 06 - Logic Branching and Looping.ps1
  You passed in 07 - Functions.ps1
  You passed in 08 - Classes.ps1
  You passed in 09 - Examples.ps1
  You passed in Company.csv
  You passed in Company.json
  You passed in ReadMe.md

The first set of “Here is the names…” is what came out of the function into the pipeline. Since we didn’t do anything with them, they were displayed on the screen. The second set, with “You passed in…” is from the end block.

We can have even more fun, let’s take the output of our function and pipe it into another cmdlet. We’ll use Sort-Object to display our results in descending order.

Get-ChildItem |
  Show-BName |
  Sort-Object -Descending

Result:
Here are the names!
Those were the names you passed in:
  You passed in 01 - Cmdlets.ps1
  You passed in 02 - Providers.ps1
  You passed in 03 - Variables.ps1
  You passed in 04 - Strings.ps1
  You passed in 05 - Arrays and Hashtables.ps1
  You passed in 06 - Logic Branching and Looping.ps1
  You passed in 07 - Functions.ps1
  You passed in 08 - Classes.ps1
  You passed in 09 - Examples.ps1
  You passed in Company.csv
  You passed in Company.json
  You passed in ReadMe.md
  Here is the name: ReadMe.md
  Here is the name: Company.json
  Here is the name: Company.csv
  Here is the name: 09 - Examples.ps1
  Here is the name: 08 - Classes.ps1
  Here is the name: 07 - Functions.ps1
  Here is the name: 06 - Logic Branching and Looping.ps1
  Here is the name: 05 - Arrays and Hashtables.ps1
  Here is the name: 04 - Strings.ps1
  Here is the name: 03 - Variables.ps1
  Here is the name: 02 - Providers.ps1
  Here is the name: 01 - Cmdlets.ps1

There are a couple of things to notice with this version. First, the result of the end block, the “You passed in…” comes first. That’s because the function ended, but the data it produced is now being consumed by Sort-Object.

Second, you can see Sort-Object took the output and sorted in reverse (descending) order.

As they say on TV, but wait, there’s more!

Because our function only acts on the Name property, it could care less what type of object is coming in as long as it has a Name property. Thus we could use it with any cmdlet as long as that cmdlet produces objects with a Name property!

Get-Process returns objects with a Name property, so let’s use it with our new function.

Get-Process | Show-BName

Result:
  Here is the name: 5KPlayer
  Here is the name: aesm_service
  Here is the name: Airplay
  Here is the name: AppleMobileDeviceProcess
  Here is the name: ApplicationFrameHost
  Here is the name: CepstralLicSrv
  Here is the name: Code
  (many more here, truncated for brevity)
Those were the names you passed in:
  You passed in 5KPlayer
  You passed in aesm_service
  You passed in Airplay
  You passed in AppleMobileDeviceProcess
  You passed in ApplicationFrameHost
  You passed in CepstralLicSrv
  You passed in Code
  (many more here too, again truncated for brevity)

Because we were able to keep the needs of our function tightly scoped to just the Name property, we were able to create a function that was highly flexible and reusable.

Conclusion

In this post we saw how to create a function that would work in the pipeline. We did it two ways, first by passing in entire objects, then by passing in a specific property of an object.

But there’s still more fun to be had! In a future post we’ll see how to implement two switches common to all cmdlets and advanced functions, Verbose and Debug.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Advanced Functions

Introduction

In my previous post, I covered the use of PowerShell Basic Functions. In this post we’ll cover Advanced Functions.

Advanced Functions provide several abilities over basic ones. First, you can create a detailed parameter list, including the ability to include optional parameters.

Second, you can pipeline enable your functions. Just like a cmdlet, you’ll be able to take input from the pipeline, work with it, then send it out your function back into the pipeline. This will be the subject of our next post.

Finally, you can use features such as Verbose and Debug, which will be the subject of an upcoming blog post.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

A Simple Advanced Function

In my Basic Functions post, I created a simple function Get-AValue that multiplied two values together and returned the result. Let’s take a look at the advanced version, Get-BValue.

function Get-BValue()
{
  [CmdletBinding()]
  param (
          [Parameter( Mandatory = $true
                    , HelpMessage = 'Enter value one.'
                    )
          ]
          [int] $one
        , [Parameter( Mandatory = $false
                    , HelpMessage = 'Enter value two.'
                    )
          ]
          [int] $two = 42
        )

  return $one * $two

}

We start with the function keyword, followed by the name we’ve chosen for the function, in this case Get-BValue. Note the parenthesis are empty, unlike a basic function advanced ones declare their parameters in a different spot.

The first line you’ll see in the function is [CmdletBinding()]. This is the indicator to PowerShell that this is an advanced function. It must be included in the first line for PowerShell to treat this as advanced.

Next up are the parameters, inside a param () block. Within we list each parameter, separated by commas.

A useful feature is the ability to add parameter attributes. For the first parameter, $one, we have the following block:

[Parameter( Mandatory = $true
          , HelpMessage = 'Enter value one.'
          )
]

The first attribute is Mandatory. This indicates that this is a required parameter, and can be set to either $true or $false. When $true, if the user does not enter it they will be prompted to supply a value.

The HelpMessage is text that will be displayed when the user gets help on your function. We won’t do much with it in this demo, but I’ve included it to show there are a variety of attributes you can use with parameters.

Next up is the actual parameter declaration, [int] $one. In this demo we’ve strongly typed our variable. Only integer values will be allowed, any string our decimal value (such as 3.14) will be rejected by PowerShell.

Note that for readability I’ve chosen to split the code onto separate lines, you could have condensed all this into a single line if you’d wanted.

Strong typing has its pros and cons. It can be very helpful in cases where the data must be of a specific type. On the other hand, it can prevent other uses. In this demo for example, we could just have easily multiplied a decimal value.

If you want a flexible variable, then you can leave off the type declaration. In this example we’ll leave it in, so you can see how it works.

Next up is the second variable declaration.

, [Parameter( Mandatory = $false
             , HelpMessage = 'Enter value two.'
             )
  ]
  [int] $two = 42

Here, we’ve set Mandatory to $false. This means if the user does not supply a value for $two, it will use the default value. Where does the default value come from?

If you look you’ll see [int] $two = 42. The 42 is the default value, and is used when the user does not supply a value.

After the parameter block ends we have the actual code for the function, in this case a simple return $one * $two.

So let’s see some examples of how this works. In your development environment highlight the function and press F8 (for VSCode) or F5 (for the PowerShell IDE). Now that it’s in memory you can call it.

Get-BValue -one 33 -two 42

Result:
1386

Here I called the function, passing in the parameters by name. I could have also left off the names and called it by position, as we did in the tutorial on basic functions.

Now lets call it only passing in a value for -one.

Get-BValue -one 33

Result:
1386

In this case, it took our input for -one, but since no value for -two was passed in, the function used the default value of 42.

So what happens if we call it and pass in no values?

Get-BValue

Result:
cmdlet Get-BValue at command pipeline position 1
Supply values for the following parameters:
one: 33
1386

As you can see in the Result: output area, we were prompted for a value for one. I entered 33 and hit enter, then got the expected result of 1386.

To illustrate how the strong typing works, let’s call passing in a string for the -one parameter.

Get-BValue -one "x"

Result:
Get-BValue: Cannot process argument transformation on parameter 'one'. Cannot convert value "x" to type "System.Int32". Error: "Input string was not in a correct format."

As you can see, PowerShell rejects our input with the resulting error message.

Conclusion

In this post we got a start in PowerShell Advanced Functions. We covered a simple advanced function, including parameters and how to use attributes with them.

But there’s still more fun to be had! In the next post we’ll see how to pipeline enable your advanced functions. Then in a future post we’ll implement two switches common to all cmdlets and advanced functions, Verbose and Debug.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Basic Functions

Introduction

Like most languages, PowerShell supports the use of functions. Functions are reusable bits of code with a name wrapped around them. This lets you make multiple calls to your function name allowing you code reuse.

PowerShell actually has two types of functions, Basic and Advanced. Basic functions are a lot like the 1974 VW SuperBeetle I owned back in college. No frills but useful, gets you where you are going. Did 0 to 60 mph in under 5 minutes. Most of the time.

Advanced functions have a lot more capabilities, and are more akin to a Tesla. Lots of fancy design, can do cool things, and depending on your need might be worth the extra investment.

In this post we will focus on Basic functions, saving Advanced functions for a later post.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Basic Functions

To declare a Basic Function you begin with the keyword function. You then follow it with the name of the function. After that are a set of parenthesis, in which you put in any values you want to pass into the function (if any) that the function can use internally. The values passed in are known as parameters.

function Get-AValue($one, $two)
{
  return $one * $two
}

This function is simple. You pass in two values, which it then multiplies by each other. The return keyword is used to return the result. So how to use it?

Well, first you need to highlight the function and use F8 in VSCode (or F5 in the IDE). This will put the function in memory so it can be used.

To call it, just use the name of the function followed by the values to pass in.

Get-AValue 33 42

Result:
1386

The 33 will go into the $one variable, then 42 will go into the $two variable. Note that when calling the function no commas are needed to separate the values. In addition, unlike other languages they don’t have to be placed in parenthsis.

You can also take a function and assign it to a variable which will then hold the results.

$returnValue = Get-AValue 33 42
"Returned value is $returnValue"

Result:
Returned value is 1386

You can also skip placing the result into a variable, and place the function call right into the string.

"Returned value is $(Get-AValue 33 42)"

Result:
Returned value is 1386

The $() will force PowerShell to evaluate the expression, then string interpolation will replace it with the value before returning the final string. (For more on string interpolation, see my recent article Fun With PowerShell Strings.)

Passing Parameters by Name

PowerShell also supports passing in the parameters by name.

$returnValue = Get-AValue -one 11 -two 13
"Returned value is $returnValue"

Result:
Returned value is 1386

With this, you use the names of the variables from the function declaration. Instead of a $ though, you use a - (dash) to show this is a parameter and not a variable, -one and -two.

The great thing about passing in by name is that the order doesn’t matter. You can list the parameters in any order you want.

$returnValue = Get-AValue -two 13 -one 11
"Returned value is $returnValue"

Result:
Returned value is 1386

As you can see in this example we listed the second parameter, -two, first. Because we used names, PowerShell knew which parameter to assign which value to.

No Return

Strictly speaking, the return keyword is not required. Whenever PowerShell finds a value that isn’t otherwise consumed, that is, used by assigning that value to a variable or used in some other way, PowerShell returns that value from the function.

function Get-AValue($one, $two)
{
  $one * $two
}

$returnValue = Get-AValue 33 42
"Returned value is $returnValue"

Result:
Returned value is 1386

As you can see, the result of the $one * $two calculation isn’t otherwise used in the function it is returned as you can see in the result.

This can lead to some interesting side effects. Look at this example.

function Get-AValue($one, $two)
{
  $one * $two
  "Hello from Get-AValue"
}

$returnValue = Get-AValue 33 42
"Returned value is $returnValue"

Result:
Returned value is 1386 Hello from Get-AValue

In this example, there are two things that aren’t consumed. First is the calculation results, second is the string Hello from Get-AValue.

So how to get around this? Using the return keyword has another use. When PowerShell sees return, it exits the function immediately returning the result, as in this example.

function Get-AValue($one, $two)
{
  if ($one -eq 33)
    { return $one + $two }
  else
    { return $one + $two }

  "Hello from Get-AValue"
}

$returnValue = Get-AValue 33 42
"Returned value is $returnValue"

Result:
Returned value is 75

The function saw the value for $one is 33, so took the first branch in the if statement, adding the two. It then exited the function immediately. The Hello from Get-AValue is never executed.

There is one problem with this demo. A basic rule of functions is that they should have one, and only one exit point (barring any error handling). Having two return statements violates this rule. It’s easy enough to fix though.

function Get-AValue($one, $two)
{
  if ($one -eq 33)
    { $retVal = $one + $two }
  else
    { $retVal = $one + $two }

  return $retVal
}

$returnValue = Get-AValue 33 42
"Returned value is $returnValue"

Result:
Returned value is 75

Here I assign the result of the equations to the variable $retVal (short for return value). At the end of the function I have a single return statement that returns the value.

The Case for Return

I have met some PowerShell professionals who say you should never use the return statement. I have to respectfully disagree.

First, using return speaks of clear intent. I didn’t get to a certain spot and my code and just stopped, forgetting to complete the function. Using return clearly says “this is the value I meant to return”.

Second, I have explicit control over my code. Using it I clearly control where my code exits. Typically the return is the last statement in my function, but it’s not required as I may want some error handling.

Third, it improves readability. When I read through the code the return statement clearly shows what value I intend to return.

For these reasons I use the return keyword in all my functions. The choice is of course up to you, but I wanted to make the case why I like it. You will see many code samples without the return keyword, so I wanted you to be aware of the difference and why some people use it and others don’t.

Using Multiple Functions

It’s possible to have multiple functions in the same script, and even have those functions call each other. Let’s look at this example.

function Format-FileOutput($files)
{
  # Find the max length of a file name
  $maxLength = Get-MaxFileNameLength $files

  Write-Host "`r`nHere is the file output`r`n"
  $padding = (' ' * ($maxLength + 1))
  Write-Host "File Name $($padding) Size"
  Write-Host ('-' * ($maxLength + 16))

  foreach($file in $files)
  {
    Format-FileLine $file $maxLength
  }
}

function Get-MaxFileNameLength($files)
{
  $maxLength = 0

  foreach($file in $files)
  {
    if ($file.Name.Length -gt $maxLength)
      { $maxLength = $file.Name.Length }
  }

  return $maxLength
}

function Format-FileLine($file, $maxFileNameLength)
{
  # +1 will ensure there is always at least one space between
  # the file name and the size
  $spaces = ' ' * ($maxFileNameLength - $file.Name.Length + 1)
  $retVal = "$($file.Name)$($spaces){0,15:N0}" -f $file.Length

  return $retVal
}

Let’s break this down. The first function is Format-FileOutput. It takes one parameter, $files, which is intended to hold a list of file objects such as one returned by the Get-ChildItem cmdlet.

The first thing it does is call another function, Get-MaxFileNameLength and assign the result to a variable. This function will loop over all the file objects passed in and determine the length of the longest file name. This will be used later in formatting the output.

This also highlights another feature of PowerShell, the order you declare functions is not important. In some languages, you cannot declare a function that is called, in this case Get-MaxFileNameLength after a function it’s called from, here Format-FileOutput. Any called functions must be listed before the function they are called from.

Again, PowerShell doesn’t care, you can declare the functions in any order you wish.

Looking at Get-MaxFileNameLength, it sets a max length of zero, then loops over the list of file objects passed in. Each file object has a Name property, the name of the file. But the Name property has it’s own set of properties, one of which is Length.

This is not the length (or size) of the file, but the length of the file name. If the file name were MyScript.ps1, the length would be 12.

As it loops it compares the length of the file name, and if it’s greater than the max length already found it replaces it. Finally it returns that value.

Returning to Format-FileOutput, it uses some Write-Host statements to create a nice header to display in the output.

Note that some people say not to put Write-Host statements inside a function, that anything like Write-Host should be done in the main script that calls the function. Normally I might agree, however this is a simple demo so we’ll go with it.

After creating a nice header, it then loops over the list of file objects that were passed in, calling the Format-FileLine function. It passes in two parameters, the file object from the passed in array, and the maximum file name length.

The output will be in the format of:

FileName.ps1       12,345

We want the file sizes to line up neatly, so we first calculate the number of spaces we need to put between the end of the file name and the start of our file sizes. PowerShell lets you repeat a character by using the syntax char * numberoftimes, such as ' ' * 20 to get 20 spaces.

Here we use the length of the maximum file name, then subtract the length of the current file name, and finally add one extra space at the end.

We create a return value of the file name, plus the spaces to put at the end. We then use a string formatting command to put the file size at the end. For more on PowerShell’s string formatting syntax, see my post Fun With PowerShell String Formatting.

This formatted string is returned to the Format-FileOutput function, where it is displayed on the screen.

So how to use this? First, highlight all three functions then use F8/F5 to get them into memory. Then, just set your file location in the terminal to a spot you want to list the files in, and call the main function.

$myfiles = Get-ChildItem
Format-FileOutput $myfiles

Result:
File Name                                       Size
----------------------------------------------------
01 - Cmdlets.ps1                               3,732
02 - Providers.ps1                             1,819
03 - Variables.ps1                             4,717
04 - Strings.ps1                               7,970
05 - Arrays and Hashtables.ps1                 8,486
06 - Logic Branching and Looping.ps1           4,315
07 - Functions.ps1                             6,907
08 - Classes.ps1                               6,210
09 - Examples.ps1                              3,125
Company.csv                                    9,694
Company.json                                  19,479
ReadMe.md                                      1,115

As you can see it lists my header, then the list of files. The sizes are neatly right aligned.

You can use these same functions with other directories on your drive. I’m going to change my current folder to one that has some ISOs in it. Then I’ll run my same functions.

$myfiles = Get-ChildItem
Format-FileOutput $myfiles

Result:
File Name                                         Size
------------------------------------------------------
2018-11-13-raspbian-stretch-full.img     5,297,405,952
2018-11-13-raspbian-stretch-full.zip     1,978,611,497
Camtasia and Snagit MVP Keys.docx               63,229
Camtasia-2018-Key.txt                               71
Keys.txt                                           798
MediaCreationTool21H1.exe                   19,463,448
Office 2016 Professional Plus Keys.txt              77
SQLServer2019-x64-ENU-Dev.iso            1,433,974,784
Win10ProMultiActivationKey.txt                      31
win32diskimager-1.0.0-install.exe           12,567,188
Windows10-20H2.iso                       4,899,078,144
Windows10-21H1.iso                       4,556,128,256

The longer file names and sizes made the output slightly wider than the original example, but as you see the functions adapted easily to this. All without any changes.

This gives me a set of functions I can reuse over and over. And I don’t have to reuse them together, I could if I needed to have code that calls just the Get-MaxFileNameLength function.

Keep Functions Small and Focused

This brings up another rule of functions. Keep the function small and focused. Each function should do one thing and return the result. This will make it much easier to reuse your functions in other projects.

The Get-MaxFileNameLength function in the above demo is a good example. It does one thing, gets the maximum file name length, and returns it.

I could call this from my main script, but I could also write a second function similar to Format-FileOutput but perhaps it could include additional information such as the last modified date. The new function could also reference Get-MaxFileNameLength, providing good code reuse.

It also reduces the amount of code you need to write in a new function, as well as reduces what you need to test, assuming Get-MaxFileNameLength has already been tested of course.

Parameter Names versus Variable Names

I want to call out something you may not have noticed. When we called Format-FileName we passed in:

Format-FileLine $file $maxLength

The variable $maxLength was used for the second parameter. But look at the declaration for the function:

function Format-FileLine($file, $maxFileNameLength)

In the function, the second parameter is named $maxFileNameLength. The point in this demo was to show the variable named being passed in does not need to match the variable used in the function declaration. PowerShell can happily take $maxLength and copy its value into $maxFileNameLength.

Good Function Names

You probably know by now that PowerShell uses the Verb-Noun naming method for its cmdlets. In these demos we could have used any names we wanted for the functions. function Adam(), function John(), or function Robert() are all valid names. Well almost.

It is generally a best practice to use the Verb-Noun naming convention when creating your own function names. Additionally, PowerShell likes you to use an approved verb. You don’t have to, but PowerShell can display a warning if you don’t.

So what are the list of approved verbs? The easiest way is to let PowerShell tell you.

Get-Verb | Sort-Object Verb

The screen shot below shows a short sample of what PowerShell has (the list is too long to reprint here). Click on it for a larger view.

You can then select an appropriate verb from the list for your function.

Conclusion

In this post we covered the use of Basic Functions in PowerShell. You saw how to declare a function, how parameters worked, as well as the pros and cons of the return keyword.

We then got into the use of multiple functions in a PowerShell script, and how they can call one another.

In a future post we’ll dive into the concepts of Advanced Functions.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun with PowerShell Loops

Introduction

Continuing my series on PowerShell basics, in this installment we’ll talk bout looping in PowerShell. Looping is a mechanism for repeating a set of instructions multiple times, ending once a condition is met. Looping is also known as iterating, as in “iterating over an array”.

Loops continue until one of two things happen. In one type of loop, the loop continues until a certain condition is true. In the second type, the loop will iterate over a collection (also called an array) of objects until the entire collection has been gone through.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

While

The while loop continues as long as a condition is true. Let’s take a look at an example.

$i = 1
while ($i -le 5)
{
  "`$i = $i"
  $i = $i + 1
}

Result:
$i = 1
$i = 2
$i = 3
$i = 4
$i = 5

In a while loop, you start with the keyword while, then pass in a condition. In this case, it will keep looping as long as $i is less than or equal to 5. Once $i is greater than 5, it will exit the loop.

Inside the loop we print out the value of $i, then increment the value of $i by 1.

So what happens if $i had been set to the value of 6, as in:



$i = 6
while ($i -le 5)
{
  "`$i = $i"
  $i = $i + 1
}

Result:
(Nothing is displayed)

In this case, the while keyword checked the value at the start of the loop. Since $i was already greater than 5, the loop never executed. So when using a while loop, be sure to understand there may be conditions under which the while loop will never execute.

Fun with Unary Operators

Before we see the next type of loop, I just want to mention unary operators. In the previous section you saw $i = $i + 1. Obviously this increased the value of $i by one.

There is an alternative syntax, the unary operator. With the unary operator, you use ++ either before or after the variable, as in $i++ or ++$i, to increment it. You can also use -- to decrement the value by one.

So what is the difference between $i++ and ++$i? It has to do with when the value is incremented. Let’s look at this example.

$i = 1
"`$i is $(($i++))"
"`$i is now $i"

Result:
$i is 1
$i is now 2

In this case we start by assigning the value of 1 to $i. We then have a string, "`$i is $(($i++))". In the results, it prints out $i = 1.

In this case, when ++ follows a variable the value is returned, then the variable is incremented. As PowerShell evaluates the string, it replaces $i with the value of 1, prints out $i is 1, and then it increments the value $i to 2.

In the final statement it displays the result of the incremented value.

What if the ++ occurs before the variable?

$i = 1
"`$i is $((++$i))"
"`$i is now $i"

Result:
$i is 2
$i is now 2

With ++ before the variable, the value in $i is incremented first, then the result is returned. So when PowerShell evaluated $((++$i)) it incremented the value of $i by one, now making it 2, then returned it to the string.

We took this detour into unary operators because it’s very common to use an expression like $i++ within a loop to increment the value, as you’ll see in the remaining examples.

Do While

With the while loop, you saw that it was possible that it might not execute, if the while condition was already true. What if, though, you always wanted the loop to execute at least once?

That’s where do while comes into play.

$i = 1
do
{
  "`$i = $i"
  $i++
} while($i -le 5)

Result:
$i = 1
$i = 2
$i = 3
$i = 4
$i = 5

This is a similar result to what we experienced with while. But, what if $i was already greater than 5, as in the next example.

$i = 33
do
{
  "`$i = $i"
  $i++
} while($i -le 5)

Result:
$i = 33

Because the while check didn’t occur until the very end of the loop, we are guaranteed that it will execute the code within our do‘s script block at least once, and hence it printed $i = 33.

Do Until

As an alternative to do while, there is do until. With do while it continues to loop as long as a condition is true.

Contrast this with do until, which keeps looping as long as the condition is not true.

$i = 1
do
{
  "`$i = $i"
  $i++
} until($i -gt 5)

Result:
$i = 1
$i = 2
$i = 3
$i = 4
$i = 5

In this case, once $i is greater than 5, the loop exits. There’s not much difference between do while and do until. It’s mostly a matter of how you like to think about your code.

Does it make more sense, or will it increase code readability, to write your code so that it keeps looping while a condition is true, or until it becomes true.

For

Another loop you can have fun with is the for loop. The for loop is ideal when you want to iterate over code a set number of times.

for ($f = 0; $f -le 5; $f++)
{
  "`$f = $f"
}

Result:
$f = 0
$f = 1
$f = 2
$f = 3
$f = 4
$f = 5

After the for, you pass in three expressions, separated by semicolons ; . The first expression you pass in sets the starting value for the variable we’ll increment each time through the loop. In this case, $f = 0 will (obviously) set the variable $f to 0 (zero). $f will be come the value we’ll increment each time through the loop.

The second expression is just like what you would use on a while/do while loop. As long as this condition is true it will keep looping. In this case, the expression $f -le 5 will keep looping as long as $f is less than or equal to 5.

Finally we have an expression that we want to execute at the end of the loop. Here, we’ll use a unary operator to increase the value of $f by 1.

So let’s express this in human terms.

  1. Set the value of $f to zero.
  2. Is $f less than or equal to 5? If yes, go into the script block. If no, exit the loop.
  3. Now execute all the code in the script block.
  4. At the end of the script block, add 1 to $f.
  5. Go back to step 2.

As you can see, the for is very similar to a while loop. If $f is already over 5 (in this example) the loop will not execute at all. Like while, it will keep looping as long as our condition is true.

The for is a little more compact than a while loop as you can include the increment option in the for command rather than having to include it in the script block.

ForEach

By far, the foreach loop is the one I use more than any other. The foreach loop allows you to iterate over an array (or a collection of objects, which is really just an array).

With for each you create a place holder variable then pass in the name of the array. Here, take a look at this example.

$array = 11,12,13,14,15   # Simple Array
foreach ($item in $array)
{
  "`$item = $item"
}

Result:
$item = 11
$item = 12
$item = 13
$item = 14
$item = 15

We create an array of numbers and assign them to the variable $array. Then in the foreach loop, we go over the array item by item. As each item is read from the array it is copied into the first variable in the foreach clause, in this case $item.

It then simply executes the code in the script block, when done it grabs the next item from $array, copies it into $item, and repeats the script block.

In this case we looped over a simple array of integers, but foreach can also loop over something more complex like a collection of objects. In this next example we’ll use the Get-ChildItem cmdlet to get a list of files from the current directory. As it goes over each one it will copy that file object into our $file variable, then we can use it in the script block.

foreach ($file in Get-ChildItem)
{
  $file.Name
}

Result:
01 - Cmdlets.ps1
02 - Providers.ps1
03 - Variables.ps1
04 - Strings.ps1
05 - Arrays and Hashtables.ps1
06 - Logic Branching and Looping.ps1
07 - Functions.ps1
08 - Classes.ps1
09 - Examples.ps1
ReadMe.md

The script block is very simple, it just references the Name property of the $file object and displays it on the screen.

Now many people might have coded this as:

$myFiles = Get-ChildItem
foreach($file in $myFiles)
...

Which is perfectly valid code. I just wanted to show that if the only reason you needed the $myFiles variable was to hold information to iterate over, and you weren’t going to reference it again in your code, you could skip it and just use the cmdlet right in the foreach loop.

Code Formatting

In my previous post I dove into the Code Formatting around PowerShell. In all my examples, I used a space between the loop keyword and parenthesis that enclosed the condition. In addition I put the opening squiggly brace on the next line.

Neither is necessary, this is perfect valid code:]

$i = 1
while($i=1){
  # Do some stuff
}

Conclusion

In this post, we looked at several ways to iterate over your code. This included several variations on while, as well as for and the useful foreach. Now you have several tools at your disposal when you need to run the same piece of code multiple times.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Code Formatting

Introduction

In this post, we’ll take a look at some of PowerShell’s rules around code formatting. Code formatting is how you can layout your code statements.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

In either environment, you can run your entire script by pressing F5.

Code Formatting

Unlike some languages, such as Python, PowerShell is extremely flexible when it comes to how you format your code. There are, in fact, very few rules around it.

Let’s take this first example. When I’m developing a script I tend to do so in steps. I write short pieces at a time, then I use the method of highlighting those lines of code and running using F8/F5 (depending on if I’m in VSCode or the IDE).

Here’s one downside, if you don’t have any lines of code highlighted pressing F5 will run your entire script (in either VSCode or the IDE). I have to admit, even though I’ve been using VSCode for a few years now old habits die hard and I still press F5 sometimes.

To prevent my entire script from running I add the following line at the top of my scripts. Once it is complete can remove it or comment it out.

if ( 1 -eq 1 ) { exit }

Here, 1 will always equal 1, so it will run the code in the script block. This is the exit command, which will cause a script to exit, or in other words stop running.

The real point of this example though isn’t the exit command, but the formatting of the if statement. As you can see, since the script block was so short, I was able to put it all on a single line.

But I didn’t have to. I could have placed it on multiple lines.

if ( 1 -eq 1 )
  { exit }

In this example, I placed my single line script block on the next line. Whenever I have a short script block like this, I usually indent two spaces, just because I think it is more readable. But that’s my personal preference. I could have placed it all the way on the left.

if ( 1 -eq 1 )
{ exit }

I could also have spread it out over multiple lines, as I did in the previous section, even though the code (the exit statement) was still only a single line.

if ( 1 -eq 1 )
{
  exit
}

Many people have a preference for putting the opening squiggly brace at the end of the line with the if statement, and PowerShell supports that as well.

if ( 1 -eq 1 ) {
  exit
}

I tend to prefer the former method as it makes lining up your opening and closing squiggly braces much easier, but fortunately PowerShell supports what ever method you wish to use to format your code.

More Fun with Code Formatting

Next, I’d like I to mention two small things that fall into the code formatting realm.

First, it is possible to compact multiple lines of code into a single line in PowerShell. All you have to do is use a semicolon ; to separate each one.

$a = 'Hi'; $b = 'Mom'; "$a $b"

Result:
Hi Mom

The second thing I’ll mention is comments. Comments begin with a # (pound sign, or as some call it a hash tag).

# This is a comment

Comments can go after a section of code.

$var = 2   # Assign 2 to the variable $var

PowerShell also supports multi-line comments. You start and end them with <# and #>.

<#
   This is a multi line comment.
   You can also put blank lines in here.

   See, easy.
#>

The opening and closing <# #> don’t have to be at the beginning of the line.

<#--------------------------------------------
  You don't have to indent either.
--------------------------------------------#>

Documenting Your Code Formatting Standards

If you work in an organization, even one with just two of you doing PowerShell development, it’s incredibly important to document your coding standards. This will ensure the code produced by all developers in your organization has a consistent look and feel.

So what types of things should go into the document? Well how about something as simple as indenting your code. When you indent, for example as part of an if statement, should it be two spaces? Four? More?

And should it be spaces, or should you use the TAB character? Personally I hate the TAB character, leads to formatting issues when going between platforms. Remember, PowerShell runs on Windows, macOS and Linux. But as I like to say “hey you do you”.

How about the opening squiggly brace of a script block. Should that be at the end of (for example) your if statement, or should it go on the next line?

How about comments? Let’s say you decide each script (PS1 file) should have a header. What should go in it? A copyright notice? The author name? A description of the script? What order should it be in?

There is one rule that is almost universally accepted, never use the short version of names for cmdlets, always spell them out. For example, gci is short for Get-ChildItem. When you are in the terminal just looking around, it’s perfectly valid to use gci, but in a script always spell out the full Get-ChildItem cmdlet name.

These are just a few ideas to get the ball rolling. You should keep your standards document as brief as possible, but cover as many things as necessary to create a consistent look and feel when coding new PowerShell scripts.

There are hundreds of good books and blog sites that discuss and have examples of coding standards. In the end go with what works best for you, but the important part, stick with it!

Make it a group decision as much as possible. Your entire coding team should have input into the standards. Even more important, when a new member comes into the team before they write the first line of code they should be introduced to your coding standards.

I would also avoid using something like a Word document to store the standards. Instead, put them online in a place that is easy to search and index. If your team has it’s own internal Wiki, that’s a great place. Or, author them in Markdown format and store them in a GitHub repository.

Wherever you put them, accessibility is key. Standards should be easy to reference, and to find the item you need.

Conclusion

This post covered the flexibility of code formatting in PowerShell, as well as touched on a few items such as commenting. More importantly, it touched on the importance of documenting and standardizing your code formatting rules.

With this info I hope you jump right on creating your own set of coding standards!

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Logic Branching

Introduction

In this post, we’ll see how some of PowerShell’s logic branching works. Logic branching is where you execute certain lines of code only when a condition is true.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

In either environment, you can run your entire script by pressing F5.

Logic Branching – if

No, I don’t mean “if” we want to do logic branching. if is a keyword in PowerShell. With it, you can pass in a condition, then execute code based on the value of the condition. This example will make it a bit more clear.

$var = 2
if ($var -eq 1)
{
  Clear-Host
  'If branch'
}
else
{
  Clear-Host
  'else branch'
}

Result:
else branch

Beside the if keyword are a set of parenthesis ( ). In it, you create some kind of expression, but, and this is important, the result of the equation must be either True or False.

In this example, I created a variable $var and set the value to 2. Then, inside the parenthesis I used $var - eq 1.

Following this I have a set of squiggly braces { } with code inside it. In PowerShell, this is known as a script block.

If my condition is true, it will execute the first script block. But what if you want to do something if it evaluates to false? That’s where the next keyword comes in, else.

Immediately after the script block associated with the if, you add the else keyword, followed by a second script block to be executed.

In the example above, the condition will evaluate to false, so the code in the else script block will run, displaying the result of “else branch“.

PowerShell also supports another keyword, elseif. I almost hesitate to provide a demo, because most developers say if you need to fall into an elseif, you should then change your code to use another branching method, which we’ll cover next. However for completeness let’s see an example.

$var = 2
if ($var -eq 1)
{
  Clear-Host
  'If branch'
}
elseif ($var -eq 33)
{
  Clear-Host
  'elseif branch'
}
else
{
  Clear-Host
  'else branch'
}

Result:
else branch

As before, we start with an if ($var -eq 1), but following that is our elseif. It will then check the condition in the elseif, ($var -eq 33). If it doesn’t find a match for it, it flows to the next section, the else.

Obviously if it finds a match, it executes the script block associated with the elseif. Once done it skips over any following elseif or else statements and goes on to the following our entire if construct.

A few things, first you can have elseif statements without an else. However it’s generally a good idea to have a default code handler should you not execute any of the other if / elseif statements.

Second, you can have as many elseif statements as you want. But as the old saying goes, just because you can do something, doesn’t mean you should. Most PowerShell developers (myself included) say if you have to resort to an elseif, you should really be using a switch.

Speaking of the switch….

More Fun with Logic Branching – switch

In addition to the if statement, there is another way to branch your code, the switch statement.

$var = 42
switch  ($var)
{
  41 {'Forty One'}
  42 {'Forty Two'}
  43 {'Forty Three'}
  default {'default'}
}

Result:
Forty Two

With the switch statement, you pass in a value. Unlike the if, the value does not need to evaluate to True or False, it can be any value.

It will then execute the script block that follows, evaluating each value in the list. If it finds a match, it will execute the code in the script block that follows.

In the example above, it finds a match on 42, and runs the code in the script block which prints out the text ‘Forty Two‘.

One issue you may encounter is multiple matches.

$var = 42
switch  ($var)
{
  41   {'Forty One'}
  42   {'Forty Two'}
  '42' {'Forty Two'}
  43   {'Forty Three'}
  default {'default"}
}

Result:
Forty Two
Forty Two

Here, it went down the list and found the first match of integer 42. It executed the script block, and continued down the list.

It next found the string of '42'. It converted the string to the same datatype as what was passed in, an integer 42, found a match, and executed that script block.

So how to stop execution after the first match? That’s where break comes in. When PowerShell finds break it will stop code execution and exit to the next line after the switch.

$var = 42
switch  ($var)
{
  41   {'Forty One'; break}
  42   {'Forty Two'; break}
  '42' {'Forty Two'; break}
  43   {'Forty Three'; break}
  default {'default'; break}
}

'Exit to this point'

Result:
Forty Two
Exit to this point

As you can see from the results, the switch found the first match at the int 42. The break statement said “skip the rest and go to the next line after”, which prints out Exit to this point.

This brings up the subject of type coercion. This is the converting of one data type before comparing to another. In PowerShell, anytime it compares values of two different datatypes, it converts the datatype on the right to match the datatype on the left.

For example, if you had an equation if ( 33 -eq '33') it will convert the string '33', on the right side of the -eq, to the datatype on the left side of the -eq, an integer 42.

With the switch statement, the left value is the one being passed in, in this case the variable $var. The right side are the values switch is looking at, here 41, 42, 43. This is something to keep in mind whenever you are writing your code.

In this example, it won’t make a difference.

$var = '42'
switch  ($var)
{
  41 {'Forty One'}
  42 {'Forty Two'}
  43 {'Forty Three'}
  default {'default'}
}

Result:
Forty Two

The code went down the list, converting each value to a string. It took the int, 42 and converted it to a string. It then found a match with the original string of 42 in the $var variable and printed out the ‘Forty Two‘ as found in the script block.

Let’s take a slightly different example.

$var = '042'
switch  ($var)
{
  41 {'Forty One'}
  42 {'Forty Two'}
  43 {'Forty Three'}
  default {'default'}
}

Result:
default

In this case it compared each value, but the string 042 doesn’t match the converted int of '42' so it didn’t match.

So what’s with the output of default? If the switch statement doesn’t find a match, it will execute in the script block associated with default.

The default is optional, if you omit it and no match is found and the switch simply exits and continues to the next line after the switch.

As a final point, I want to mention you don’t have to contain everything in the switch’s script blocks to a single line. This is perfectly valid code.

$var = 42
switch  ($var)
{
  41   {
         'Forty One'
         break
       }
  42   {
         'Forty Two'
         break
       }
  '42' {
         'Forty Two'
         break
       }
  43   {
         'Forty Three'
         break
       }
  default {
            'default'
            break
          }
}

While this example is a bit harder to read, you will encounter situations where trying to condense your script block to a single line will be difficult so don’t hesitate to use multiple lines if it improves readability.

Conslusion

This post focused on the logic branching mechanisms built into PowerShell, if and switch. Hopefully this will give you a better understanding of your options for handling conditional code execution in PowerShell.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Hash Tables

Introduction

Hash tables are powerful, flexible items. In some languages, hash tables are also called dictionaries. In this post we’ll start with a few basics, then get into some fun things you can do with hash tables.

A quick note, some documentation calls them a hash table, others read hashtable, one word. For this post I’ll use the two word hash table, but it’s the same thing no matter what documentation or blog you read.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

Hash Table Basics

To create a hash table you will need to use the @{} syntax to create a set of key / value pairs.

$hash = @{'Key'                 = 'Value'
          'PowerShell'          = 'PowerShell.com'
          'Arcane Code'         = 'arcanecode.com'
          'Pluralsight'         = 'pluralsight.com'
          'GitHub - ArcaneCode' = 'github.com/arcanecode'
         }
$hash                  # Display all values

Result:
Name                           Value
----                           -----
Arcane Code                    arcanecode.com
Key                            Value
Pluralsight                    pluralsight.com
PowerShell                     PowerShell.com
GitHub - ArcaneCode            github.com/arcanecode

In this example I lined up the equal signs because, in my humble opinion, it’s easier to read. But it’s not required.

Similar to an array, you can use positional notation to retrieve a value, only instead of a numeric value you pass in the key.

$hash['PowerShell']

Result:
PowerShell.com

The hash table manifests each key as a property. This means instead of positional notation you can instead use . (dot) notation.

$hash.PowerShell

Result:
PowerShell.com

But Arcane, you may ask, some of those keys have spaces in them. How does that work?

Well all you have to do is put the key in quotes.

$hash.'Arcane Code'

Result:
arcanecode.com

Well that was fun, but what about updating a value for a key? All you have to do is assign the new value.

$hash.'Arcane Code' = 'ArcaneCode.me'
$hash

Result:
Name                           Value
----                           -----
GitHub - ArcaneCode            github.com/arcanecode
Key                            Value
PowerShell                     PowerShell.com
Pluralsight                    pluralsight.com
Arcane Code                    arcanecode.me

As you can see the value for the 'Arcane Code' key has changed from arcanecode.com to arcanecode.me.

In addition to updating, you will have times you want to add a new item to an array. Doing so isn’t intuitive, but it’s easy. Just use the hash table, provide the new key and assign the value to it.

$hash.Ubuntu = 'Ubuntu.com'
$hash

Result:
Name                           Value
----                           -----
GitHub - ArcaneCode            github.com/arcanecode
Key                            Value
Ubuntu                         Ubuntu.com
PowerShell                     PowerShell.com
Pluralsight                    pluralsight.com
Arcane Code                    arcanecode.me

Fun Properties and Methods of Hash Tables

Hash tables have a variety of useful properties and methods you can call. The first, Keys, will return a list of all the keys.

$hash.Keys

Result:
GitHub - ArcaneCode
Key
PowerShell
Pluralsight
Arcane Code
Ubuntu

As you might guess, there’s a corresponding property to return all the values.

$hash.Values

Result:
github.com/arcanecode
Value
PowerShell.com
pluralsight.com
arcanecode.me
Ubuntu.com

Just like the array data type, hash tables also have a Count property to let you know how many key/value pairs are in the table.

$hash.Count

Result:
6

Earlier in this post I showed how to add a new key/value pair. Before adding, it would probably be a good idea to check and see if it is already there. There is a ContainsKey method that will do that.

$hash.ContainsKey('Arcane Code')

Result:
True

This method will return either True or False depending on the presence of the key.

Likewise, there is a method to check to see if a value is present.

$hash.ContainsValue('pluralsight.com')

Result:
True

What if we’d wanted to use ContainsKey to see if a key was present, and if so we wanted to remove it? There is a Remove method that will delete a key/value pair from our hash table.

$hash.Remove('Ubuntu')
$hash

Result:
Name                           Value
----                           -----
GitHub - ArcaneCode            github.com/arcanecode
Key                            Value
PowerShell                     PowerShell.com
Pluralsight                    pluralsight.com
Arcane Code                    arcanecode.me

As you can see in the results, the key / value pair for the Ubuntu entry is now gone.

The final method we’ll look at in this section will allow us to remove all key / value pairs from the hash table.

$hash.Clear()
$hash

Result:
(Nothing is displayed because the hash table is empty)

Creating an Empty Hash Table

Much as with arrays, there are times you will want to create an empty hash table. You could then enter a loop that added new key / value pairs to the hash table.

To declare an empty hash table, you simply assign @{} to a variable.

$empty = @{}

Now you can assign values to it. Here we’ll do it in individual lines, but as I indicated this technique is most useful in a loop.

$empty['Pluralsight'] = 'pluralsight.com'
$empty['DataFabricator'] = 'datafabricator.com'
$empty

Result:
Name                           Value
----                           -----
Pluralsight                    pluralsight.com
DataFabricator                 datafabricator.com

Ordered Hash Tables

Let’s reset our hash table, then look at the output.

$hash = @{'Key'                 = 'Value'
          'PowerShell'          = 'PowerShell.com'
          'Arcane Code'         = 'arcanecode.com'
          'Pluralsight'         = 'pluralsight.com'
          'GitHub - ArcaneCode' = 'github.com/arcanecode'
         }
$hash

Result:
Name                           Value
----                           -----
Arcane Code                    arcanecode.com
Key                            Value
Pluralsight                    pluralsight.com
PowerShell                     PowerShell.com
GitHub - ArcaneCode            github.com/arcanecode

Look at the output carefully. The returned list is not in the same order we added them to the hash table. Most of the time you are referencing the hash table via a specific key, so the ordering isn’t a concern.

What if you needed them to be returned in the same order they were added in? Fortunately PowerShell has an [ordered] instruction you can place before declaring the hash table.

$hash = [ordered]@{
  'Key'                 = 'Value'
  'PowerShell'          = 'PowerShell.com'
  'Arcane Code'         = 'arcanecode.com'
  'Pluralsight'         = 'pluralsight.com'
  'GitHub - ArcaneCode' = 'github.com/arcanecode'
}
$hash

Result:
Name                           Value
----                           -----
Key                            Value
PowerShell                     PowerShell.com
Arcane Code                    arcanecode.com
Pluralsight                    pluralsight.com
GitHub - ArcaneCode            github.com/arcanecode

As you can see, the results are returned in the exact same order they were added to the hash table. This example also demonstrates you can begin the list of key / value pairs on the next line after the single squiggly bracket {.

Iterating (Looping) Over a Hash Table

It is possible to loop over the contents of a hash table. But it isn’t quite as intuitive as it is with arrays. If you were to use foreach($item in $hash) you’d get an error, because PowerShell isn’t sure if you want to loop over the keys, values, or something else.

Instead, the hash table has a special method called GetEnumerator which will allow you to iterate over the table, returning one key / value pair each time through the loop.

foreach($item in $hash.GetEnumerator())
{
  "$($item.Name) has a value of $($item.Value)"
}

Result:
Key has a value of Value
PowerShell has a value of PowerShell.com
Arcane Code has a value of arcanecode.com
Pluralsight has a value of pluralsight.com
GitHub - ArcaneCode has a value of github.com/arcanecode

This is the way I prefer to iterate over a hash table. Just my personal opinion, but I think the code is cleaner and easier to read. That said, there is a second method you may like better.

In it you loop over the Key values, then reference the value from the hash table, using the key as the position. Do notice that instead of using the key[] notation, you have to reference the Item method then pass the key into it.

foreach($key in $hash.Keys)
{
  "$key value is $($hash.Item($key))"
}

Result:
Key value is Value
PowerShell value is PowerShell.com
Arcane Code value is arcanecode.com
Pluralsight value is pluralsight.com
GitHub - ArcaneCode value is github.com/arcanecode

As you can see, the result is the same either way. Use which ever method makes the most sense for you.

Splatting

Right now you are probably asking yourself “Splatting? Hey Arcane, are you trying to confuse us with silly names?”

Fear not, splatting is a real thing. First, let’s start with an example. Here, we are going to take the output of GetChildItem and pipe it to a cmdlet called Join-String. If you aren’t familiar with it, Join-String will take a list of objects and join them to a single string.

Get-ChildItem -Directory D:\ |
  Join-String -Property Name -Separator ', '

Result:
Backups_Temp, CamtasiaWorkspace, Hyper-V, ISOs, Mobile Report Publisher

With splatting, we create a hash table, Each key is the name of a parameter that needs to be passed in, and the value is what we want to use for that parameter.

$splat = @{Property = 'Name'
           Separator = ', '
          }

Here you can see the Property and Separator parameter names were passed in as keys, and the values are what we want to pass in.

Now we can call our cmdlet, passing in the name of our hash table. Only, and this is important, instead of a $ we use an @ at the front of the name of our hash table.

Get-ChildItem -Directory D:\ | Join-String @splat

Result:
Backups_Temp, CamtasiaWorkspace, Hyper-V, ISOs, Mobile Report Publisher

Whenever PowerShell sees a @ in front of a name that corresponds to a hash table, it understand you are using a splat and expands each key / value pair into parameters. As you can see, it shortens the call to Join-String significantly.

What if we want to change the value of one of our parameters? That’s easy, we just assign a new value. The rest of the hash table remains the same.

$splat.Separator = ' - '
Get-ChildItem -Directory D:\ | Join-String @splat

Result:
"Backups_Temp" - "CamtasiaWorkspace" - "Hyper-V" - "ISOs" - "Mobile Report Publisher"

All of the parameters don’t have to be included in the splat. For example, Join-String has a switch that, when included, will wrap each item in single quotes.

$splat = @{Property = 'Name'
           Separator = ', '
          }

Get-ChildItem -Directory D:\ |
  Join-String @splat -SingleQuote

Result:
'Backups_Temp', 'CamtasiaWorkspace', 'Hyper-V', 'ISOs', 'Mobile Report Publisher'

Here I used a switch, but it could be another parameter with a value added after the splat.

What if you wanted to include the switch in the hash table though? Switches don’t have a value that is passed in.

That turns out to be a simple answer, just include the switch as a key, and for the value use either $True or $False.

$splat = @{Property = 'Name'
           Separator = ', '
           SingleQuote = $True
          }
Get-ChildItem -Directory D:\ | Join-String @splat

Result:
'Backups_Temp', 'CamtasiaWorkspace', 'Hyper-V', 'ISOs', 'Mobile Report Publisher'

You could also include the switch in the splat, but set the value to $False if you didn’t want to use single quoted every time.

While this example only had a few parameters, I’ve worked with some cmdlets that had a dozen or more parameters that were required to pass in. SQL Server’s cmdlets come to mind.

I often had scripts that had a dozen parameters, but the only one that changed with each call was the actual SQL command I was sending to the server. Using a splat let me reduce the amount of code significantly.

Conclusion

In this post, we saw a lot of fun things you can do with hash tables. A few were creating, updating, adding and removing values. We also covered iterating over a hash table, and even the very useful concept of splatting.

Now you can use these features in your own scripts where a hash table comes into play.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell Arrays

Introduction

In this article, we’ll look at the different ways to use Arrays in PowerShell. We’ll cover the basics, then move onto more advanced topics.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

A quick note for my non-native English speaking friends, I use the term aka a few times. AKA is shortcut for “also known as“, it’s just a quick way to say “a is another word for b”.

Array Basics

The formal array to declare an array is to use @() around the contents, assigning it to a variable.

$array = @('Robert', 'Cain')
$array

Result:
Robert
Cain

Note that in the output, each item (aka element) in the array is displayed on a single line.

There is a little easier way to create an array, you can omit the @() . If you are passing in more than one item, separated by a comma, PowerShell converts it to an array.

$array = 'Robert', 'Cain'

If you have a long list of items, you can break the list over multiple lines. As long as a comma is the last thing on the line PowerShell will assume the rest of the array contents continue on the next line.

$array = 'Robert',
         'Cain'

You can access individual items in the array by using what is called positional notation. After the name of the array, use square brackets [] and inside put a number that indicates the position in the array.

Numbering begins at zero and goes to the maximum number of elements. (Well, technically the max number of elements minus one, but more on that in a moment.) If you try to use a number greater than the maximum number of elements, you will get an error.

$array = 'Robert', 'Cain'
$array[0]

Result:
Robert

$array[1]

Result:
Cain

Of course you can use variables in place of the numeric values for positional notation.

$x = 1
$array[$x]

Result:
Cain

So how do you know how many items are in an array? The array variable type has a Count property that can tell you that.

$array.Count

Result:
2

There is also a property called Length, which returns the same thing. They can be used interchangeably, most people however use the Count property.

One thing to be careful of with Count, is that it returns the number of items in the array. But arrays start with position 0 (zero), so the last item in an array will be Count - 1, not Count.

$array[$array.Count - 1]

Result:
Cain

PowerShell does provide a shortcut to get the last element. You can pass in a -1 (negative one) into the position, and it will get the last item.

$array[ -1 ]

Result:
Cain

Fun Tricks with Positional Notation

There are a few fun tricks you can do with positional notation. So far, we’ve only used a single value in the square brackets to get a single value from the array.

Did you know you can pass in multiple numbers and get back multiple elements from the array in one command?

$array = 'Zero', 'One', 'Two', 'Three',
         'Four', 'Five', 'Six', 'Seven'
$array[2, 4, 6]

Result:
Two
Four
Six

If you pass in the same number twice, you’ll get that array item back twice in the results.



$array = 'Zero', 'One', 'Two', 'Three',
         'Four', 'Five', 'Six', 'Seven'
$array[2, 4, 2]

Result:
Two
Four
Two

Finally, you can use PowerShell’s range operator to return a series of values.



$array = 'Zero', 'One', 'Two', 'Three',
         'Four', 'Five', 'Six', 'Seven'
$array[1..4]

Result:
'One'
'Two'
'Three'
'Four'

Adding Items to an Array

Adding items is pretty easy, you can use the += (plus equal) to add a new item into the array. In this example, we’ll add two new items, then display the new array, and finally the count of items.

$array = 'Robert', 'Cain'

$array += 'Arcane'
$array += 'Code'
$array

$array.Count

Result:
Robert
Cain
Arcane
Code
4

Here you can see are my original two items, followed by the new ones we just added. Finally the count, 4, is displayed.

Updating an Array Element

What if we want to update a value in the array? Turns out that is as easy as adding a new one. Simply address the item to replace with positional notation, then assign a new value.

$array[0] = 'Mr.'
$array

Result:
Mr.
Cain
Arcane
Code

Empty Arrays

When you need to create an empty array, you use the formal array declaration syntax we saw in the first example, only you put nothing in it.

$empty = @()
$empty

Result:
(Nothing is displayed because the array is empty)

So why would you need an empty array?

Let’s say you were looping over a collection of some type, and were building a new array to output. It might be a collection (aka array) of files, or records return from a SQL Server query.

You would create the empty array, enter the loop, extract and format the information you want then add it to the empty array using the += syntax.

Iterating Over Arrays

There are a few methods we can use to iterate, or loop over the contents of our arrays. The most common, and one I use the most, is a foreach loop.

foreach($item in $array)
{
  "The current item is $item"
}

Result:
The current item is Mr.
The current item is Cain
The current item is Arcane
The current item is Code

The next option is to pipe our array into the ForEach-Object cmdlet, passing a script block to the cmdlet with our instructions.

$array | ForEach-Object { "Current Value: $PSItem"}

Result:
Current Value: Mr.
Current Value: Cain
Current Value: Arcane
Current Value: Code

The variable $PSItem is a built in PowerShell variable, meant to represent the current item in a loop.

Finally, an Array datatype has a ForEach method built right in. You call the method, then as you did with the ForEach-Object cmdlet pass in a script block with the instructions.

$array.ForEach({ "Current Value: $_"})

Result:
Current Value: Mr.
Current Value: Cain
Current Value: Arcane
Current Value: Code

In this example we used the variable $_ to represent the current item. The $_ was the original way to create a placeholder, later PowerShell added the $PSItem variable. $PSItem was introduced way back in version 3, so unless you are on a really really REALLY old version of PowerShell, you can use $_ and $PSItem interchangeably.

Erasing the Contents of an Array

Clearing out the contents of an array is easy, just call the array’s Clear method.

$array.Clear()
$array

Result:
(Nothing is displayed because the array is empty)

Mixed Data Types

Unlike a lot of other languages, the values going into an array can be a mix of data types. In the example below I’m going to load an array with some strings, integers, and a floating point type.

$array = 'Arcane', 'Code', 33, 42,
         'Alabama', 3.14, 'In the Morning'
$array

Result:
Arcane
Code
33
42
Alabama
3.14
In the Morning

Removing Items From Arrays

We now move to a topic that doesn’t have a happy answer. You cannot remove an item from an array. That said, you could create a brand new array, filtering out the unwanted elements.

In this example, we pipe our array into the Where-Object cmdlet. We pass in a script block, in which we say only pass out items where the value of the current item is not equal ( -ne ) to the state of Alabama. After that we display the results.

Note too we are using another built in line continuation feature of PowerShell. If the pipe symbol, a | , is the last thing on the line PowerShell assumes what is on the next line is a continuation of the line above. (I like to indent the second line to the right of my equal sign, but it’s not required.)



$array = 'Arcane', 'Code', 33, 42,
         'Alabama', 3.14, 'In the Morning'
$newArray = $array |
            Where-Object { $PSItem -ne 'Alabama' }
$newArray

Result:
Arcane
Code
33
42
3.14
In the Morning

As you can see, the state of Alabama is no longer in our list.

Fun Operators – In and Compare

There are other operators in PowerShell that can let us do fun things with arrays.

First, what if we need to know if an item is already in the array? There’s actually two methods to handle that. In the first, we’ll see if the item is in the array.

$array = 'Arcane', 'Code', 33, 42,
         'Alabama', 3.14, 'In the Morning'
'Arcane' -in $array

Result:
True

In the second method we’ll use the contains operator.

$array -contains 'Code'

Result:
True

More Fun Operators – Join

What if you wanted to convert all of the elements in the array into a single string? To handle that we have the join operator.

After the join, you pass in a character to use to separate each element. You also have the option to pass in an empty string if you don’t want anything separating your results.

In the last example, you’ll see that you can pass in more than one character to separate the results.

$array = 1, 3, 5, 7, 9
$result = $array -join ' '
$result

Result: 1 3 5 7 9

$array = 1, 3, 5, 7, 9
$result = $array -join '-'
$result

Result: 1-3-5-7-9

$array = 1, 3, 5, 7, 9
$result = $array -join ','
$result

Result: 1,3,5,7,9

$array = 1, 3, 5, 7, 9
$result = $array -join ''
$result

Result: 13579

$result = $array -join ', '
$result

Result: 1, 3, 5, 7, 9

Even More Fun Operators – Replace, Match, Split

There are three more operators we’ll cover here. The first is Replace, which will search through the array and replace one string with another.

$servers = 'HOLD-001', 'HOLD-002', 'SQL-001', 'SQL-002'
$newServers = $servers -replace 'HOLD', 'TEST'
$newServers

Result:
TEST-001
TEST-002
SQL-001
SQL-002

You might use this to develop a script to run against your servers. When designing the script you might use a string like HOLD-001, HOLD-002, etc where HOLD is just a place holder.

At runtime you pass a parameter into your script such as DVLP (development), TEST, or PROD (production) so you can use the same script in multiple environments.

The next operator we’ll look at is match. Match can be used as a filter. Let’s take a look at this example:

$servers = 'SRV-001', 'SRV-002', 'SQL-001', 'SQL-002'
$newServers = $servers -match 'SQL'
$newServers

Result:
SQL-001
SQL-002

As you can see, only servers with SQL in the name were returned. This might be useful if you had a full list of all servers you wanted to iterate over, but in certain sections of your script you only wanted to do things to your SQL Servers.

The final operator to look at is split. You can use split to further divide the elements in your array into smaller parts.

In this example we’ll reuse our list of servers, but split the items based on a dash (or hyphen).

$servers = 'SRV-001', 'SRV-002', 'SQL-001', 'SQL-002'
$newServers = $servers -split '-'
$newServers

Result:
SRV
001
SRV
002
SQL
001
SQL
002

Here you can see each server name was split into two parts, each part becoming a new element in our output array. Also note that the character we split on, the dash, is discarded from the result set.

Iterating Over an Array of Objects

Just like arrays of simple items like strings and integers, you can also loop over an array of objects.

Here, we’ll get a list of files in my temp directory using the Get-ChildItem cmdlet. We’ll also use the empty array technique you saw earlier. A foreach loop will be used to go over each file object in the array and format a string with some of its properties, then add that string to our empty $newFiles array.

Set-Location "C:\Temp"
$files = Get-ChildItem
$newFiles = @()
foreach($file in $files)
{
  $newFiles += "$($file.Name) is $($file.Length) in size"
}
$newFiles

Result:
2019-ARRL-FieldDay-Rules-RevA.pdf is 111434 in size
ARRL-FIELD-DAY.adi is 76110 in size
azuredatastudio-windows-user-setup-1.13.1.exe is 91789368 in size
curl-7.74.0.zip is 6068670 in size
standard-notes-3.6.14-win.exe is 105761936 in size
SysinternalsSuite.zip is 41889407 in size

Within my loop $file represents the current item in my $files array. Inside I access the properties of my file object. I could, for example, have used if ($file.Length -gt 1) and done something useful.

In this case I’m using string interpolation to display the value of a property. Note that because I want it to access the property, within the string I have to wrap it in $(), such as $($file.Name).

This forces PowerShell to first evaluate what is inside the $(), then return that into the string.

Conclusion

This post turned out longer than I’d intended, but there are so many fun things you can do with a PowerShell Array it was hard to stop at just a few.

With luck you’ll have found some new techniques you can employ with arrays in your own PowerShell scripts.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Fun With PowerShell String Formatting

Introduction

In last Monday’s blog post, Fun With PowerShell Strings, I covered a lot of the basics around PowerShell Strings. One thing we didn’t get into though was some of the special formatting commands you can use with PowerShell.

Specifically, this can control the output when we embed a numeric value inside a string. Passing in special formatting instructions will make it easy to display values with commas, as currency, or even as hexidecimal.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of an array variable (or any variable) by highlighting it and using F8/F5.

How it Works

PowerShell supports the C# / C / C++ style of string formatting. Let’s take a look at this example.

[string]::Format("There are {0} items.", $items)

Inside our string we have {0}. This is a placeholder, when a C style language sees this it will look for the first variable after the string and put that into placeholder 0.

PowerShell of course always wants to make life easier on us, so we can use this much simpler format.

"There are {0} items." -f $items

In PowerShell we have our string, including the placeholder. After it we have a -f to let PowerShell know the following items will be copied into the placeholders. In this case, the value in $items will be inserted into the {0} placeholder.

It’s not necessary to use a variable, we could have just hardcoded a value.

"There are {0} items." -f 33

What if we had multiple items? Well we’d just use multiple placeholders (all on one line).

"There are {0} items in the location {1}." -f $items, $loc

In this example, $items will go into the {0} and the value in $loc will get inserted into placeholder {1}.

You can even repeat a placeholder.

"There's {0} items in {1}. {0} is a lot of items!" -f $items, $loc

Now, if you’ve read my previous post you’re probably thinking “wait, wouldn’t string interpolation make all these unnecessary?”

And in these examples you are correct, with string interpolation you could have used the much easier to read syntax.

"There are $items items."

So why use the placeholder style? Well it turns out the placeholder syntax includes some extra syntax which will allow you to format values passed in. Let’s take a look at some examples.

Number

In the placeholder, after the placeholder position ( 0 in these examples ) you can put a colon : , then a letter that indicates the formatting style. Then another number which indicates the decimal places to retain.

"N0 {0:N0} formatted" -f 12345678.119    

Result:
N0 12,345,678 formatted

In this example, after the colon we have the letter N. This lets PowerShell know we want this to be formatted as a numeric value, and to include commas to separate the numbers.

In this example after the letter N we have another number, a 0. This indicates how many decimal places to retain in the output.

Here’s another example where we’ll include two decimal places.

"N2 {0:N2} formatted" -f 12345678.119

Result:
N2 12,345,678.12 formatted

As you can see, it follows the normal rules for rounding up the output. The next to the last last number, 1, was rounded up to 2 since the number after it was a 9.

Often we want to display numbers as right aligned, so the last digit will line up in our output. This means we want to display a number of spaces in front of it. PowerShell includes a feature for this too.

"N0 {0,8:N0} formatted" -f 123.119

Result:
N0      123 formatted

After the place holder we have a comma then a number ( here 0,8 ). This tells PowerShell we want the value for this placeholder to be eight characters in width.

In the output, PowerShell saw the final value, 123, was three characters wide. So it subtracted three from eight and put five spaces in front of the 123 so the entire output became eight characters in width.

A common question is “what if the output is longer than the width passed in?”. For example, you used 0,3 but your input to the placeholder was a value of 123456? In that case PowerShell will still format it as 123,456 but will not add any spaces before the number. This could result in some misaligned output in your display, but at least the accurate value will still be shown.

Currency

Using the C format instruction, PowerShell can display the results in currency format. It will use the default currency symbol for your computer’s language / location. Since I’m in the United States it will use a dollar sign.

"C0 {0:C0} formatted" -f 12345678.1234
"C2 {0:C2} formatted" -f 12345678.1234
"C2 {0,20:C2} formatted" -f 12345678.1234

Result:
C0 $12,345,678 formatted
C2 $12,345,678.12 formatted
C2       $12,345,678.12 formatted

Currency acts very similar to the Number format, the only real difference is the addition of your location’s currency symbol in the formatted output.

Percentage

PowerShell also has the ability to display values in a percentage format.

"P0 {0:P0} formatted" -f 0.1234
"P2 {0:P2} formatted" -f 0.1234
"P2 {0,8:P2} formatted" -f 0.1234

Result:
P0 12% formatted
P2 12.34% formatted
P2   12.34% formatted

Make sure to notice that PowerShell will take the decimal amount and automatically convert the percentage for you. Thus, if you want to display 12%, you have to pass in 0.12.

Hex

Next up on the list is Hexadecimal notation. PowerShell supports two methods for doing Hex values. In the first, it will take a standard number (base 10) and convert it to hexadecimal then display it.

"X0 0x{0:X0} formatted" -f 1234

Result:
X0 0x4D2 formatted

In this case we passed in the number 1234. PowerShell converted it to hex then displayed it.

What if the value were already in hex format though, and we just wanted to display it? In PowerShell, we indicate a number is in hex by placing a 0x in front of it. So we just pass in our number with 0x in front to the string.

"X0 0x{0:X0} formatted" -f 0x4D2

Result:
X0 0x4D2 formatted

When PowerShell saw the 0x on the front of 0x4D2, it knew this value was already in hex format and didn’t try to convert it, it simply displayed our value.

Decimal

The name of the next formatter we’ll look at is Decimal. It’s a bit misnamed though, because it’s really just for formatting integer based numbers. As a matter of fact, if you try use with a non integer value, such as 33.42, it will error out.

Let’s look at the most basic form of Decimal.

"D0 {0:D0} formatted"   -f 123

Result:
D0 123 formatted

Pretty simple, it just displays the value. So you can guess D is the formatter for decimal, but what is D0? The number after the D indicates how many spaces we want to zero pad to.

In other words, we want the string to be a certain number of characters long, so the number indicates that length. It will then put zeros to the left to pad it out. Let’s look at this example to make it clear.

"D8 {0:D8} formatted"   -f 123

Result:
D8 00000123 formatted

In this case, the D8 indicated we wanted eight characters in decimal formatting. PowerShell converted 123 to a string that was three characters long, so it put five zero’s in front to make the total length eight.

You can also add the placeholder comma value formatting to make a value a certain width wide. In these two examples, the first will act like a Number format. The second though will let you use leading zeros, but still set the total space.

"D0 {0,9:D0} formatted" -f 123
"D0 {0,9:D6} formatted" -f 123

Result:
D0       123 formatted
D0   0000123 formatted

In the first example, it made the output nine characters wide by placing six spaces in front of the 123. In the second example, it first made the 123 six characters by placing 000 on front, resulting in 000123.

It then took the resulting 000123 and made that nine wide by placing three spaces in front. As you can see, PowerShell is very flexible when formatting this way.

A reminder, as stated earlier Decimal is only for integers. If I were to try using -f 123.1 as an input it would error out.

Custom Date Formatting

In addition to the other formats, PowerShell provides the ability to create custom date outputs. You do so using these characters, note they are case sensitive.

M / MMOne or Two character month number
d / ddOne or two character day of the month number.
yy / yyyyTwo or four digit year number.
h / hhOne or two digit hour of the day in 12 hour format
H / HHOne or two digit hour of the day in 24 hour format
m / mmOne or two digit minute of the hour
s / ssOne or two digit second of the minute

Let’s see these used in a series of examples.

"Today is {0:M/d/yy}."                 -f $(Get-Date)
"Today is {0,10:MM/dd/yyyy}."          -f $(Get-Date)
"Today is {0,10:dd/MM/yyyy}."          -f $(Get-Date)
"Today is {0,10:yyyyMMdd}."            -f $(Get-Date)
"Today is {0,10:MM/dd/yyyy hh:mm:ss}." -f $(Get-Date)
"Today is {0,10:MM/dd/yyyy HH:mm:ss}." -f $(Get-Date)

Result:
Today is 7/18/21.
Today is 07/18/2021.
Today is 18/07/2021.
Today is   20210718.
Today is 07/18/2021 04:59:56.
Today is 07/18/2021 16:59:56.

In all of these, I passed in the current date (as I’m writing this) using Get-Date, but any date based variable would have worked.

In the output I’ve arranged the date parts in a variety of common manners. You can use these or create your own custom formats. For example you may wish to use a dash instead of a slash to separate the date parts.

Custom Number Formatting

Let’s say you lived in London, where the pound was your default currency. But you are part of a multinational company and need to produce output to send to your coworkers in the United States. You don’t want to have to change your computer to a US language just to get the $ symbol.

You can fix this by using custom formatting. In these examples, when it sees a character such as the $ it will just keep it in the output. When it sees #, (a pound sign followed by a comma) it will know to use commas in the output.

When it sees just a # sign it will put a number there if it has one, otherwise a space, and when it sees 0 it will put a number from the passed in data, or a zero if there is no number there. These examples will clarify it.

Note each one is on it’s own line, WordPress is wrapping the -f part to the next line for space reasons. Additionally, in this and other examples I’ve chosen to add spaces to line up the demos for readability, this isn’t a requirement and you can format however you need.

"Custom 0, 25 $ #,##0.0000  = {0,25:$ #,##0.0000} " -f 123456789.012000005
"Custom 0, 25 $ #,##0.0000  = {0,25:$ #,##0.00} "   -f 123456789.012000005
"Custom 0, 25 $ ##0.0000    = {0,25:$ ##0.00} "     -f 123456789.012000005

Result:
Custom 0, 25 $ #,##0.0000  =        $ 123,456,789.0120
Custom 0, 25 $ #,##0.0000  =          $ 123,456,789.01
Custom 0, 25 $ ##0.0000    =            $ 123456789.01

Using Formatting with Variables

It’s possible to create a formatted value, then assign that to a variable. Let’s take a look at this slightly more complex example.

$vTday = "{0,8:N0}" -f 134567
$vYest = "{0,8:N0}" -f 23546

$tday = "{0:MM/dd/yyyy}" -f $(Get-Date)
$yest = "{0:MM/dd/yyyy}" -f $((Get-Date).AddDays(-1))

$output = "Visitors to ArcaneCode.com`r`n"
$output += "$tday had $vTday visitors`r`n"
$output += "$yest had $vYest visitors"
$output

Result:
Visitors to ArcaneCode.com
07/18/2021 had  134,567 visitors
07/17/2021 had   23,546 visitors

The first two lines you’ve seen a few times by now. I’m simply taking a number, formatting it with commas and making it eight spaces wide.

The next line you’ve seen as well, I take the current date, format to MM/dd/yyyy format and assign to $tday.

The $yest line is slightly more complex, but only because I’m doing some date math. The (Get-Date) will get the current date, but create a date object from it. Essentially I have a date variable type but without a variable name.

Because I have a date datatype I can employ a method of the date datatype, AddDays. This will let me add a number of days to the date, or if you pass in a negative value subtract days. Thus AddDays(-1) will give me yesterday’s date. I then have to wrap it all in $() so PowerShell will evaluate the entire expression before passing it back to the placeholder.

Finally I’m using the += operand to concatenate several strings together for a nice output. Note the formatted dates, and how the visitors number right aligns nicely.

Conclusion

While string interpolation makes creating output strings easy, knowing how to use placeholders can make creating formatted output even easier, allowing you to create output with numeric values that align neatly, or creating custom date formatting.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to https://pluralsight.com. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site. Once there just search for my name, Robert Cain, to see all the courses I have.

Introduction to the SQL Server Mobile Report Publisher – Now Live on Pluralsight

My latest Pluralsight course, Introduction to the SQL Server Mobile Report Publisher, just went live!

The Mobile Report Publisher is a powerful, under-appreciated tool that lets you quickly and easily create reports for not just the web, but for a variety of platforms such as Apple’s iOS and Google Android.

In this course, Introduction to the SQL Server Mobile Report Publisher, you’ll learn to quickly and easily create dashboards for the web, as well as mobile devices such as phones and tablets.

First, you’ll explore how to use the Report Publisher designer to create the layout of the dashboard.

Next, you’ll see how to create datasets and bind them to the dashboard.

Finally, you’ll learn advanced features such as filters to limit the data on the dashboard, as well as drillthroughs to launch a detailed report or web page.

When you’re finished with this course, you’ll have the skills and knowledge of the Mobile Report Publisher needed to create dashboards on multiple platforms.

What’s that you say? You don’t have a subscription but want to watch my course? Hey, no problem. Pluralsight has a 10 day free trial. Just go to https://www.pluralsight.com/pricing/free-trial and sign up!