A Series to Help You Improve Your PowerShell Scripts
Many Practical 365 articles and posts from other websites include snippets of PowerShell code alongside information explaining how to solve a particular problem. For the sake of brevity and improved readability, many elements are often left out of the code, such as connectivity and perhaps error handling. These code snippets are not full-fledged scripts; administrators must take the code and customize it to match their needs in interactive scripts or Azure Automation runbooks.
Practical 365 is frequently asked by administrators and consultants how to increase PowerShell proficiency by incorporating and enhancing published code snippets into their work. It is a fact that administrators often are expected to be more of a developer than they bargained for. Usually, they know how to apply one-liners and can read scripts but might have difficulties when it comes to creating something themselves. Often, related questions to support their requirements are asked, such as how to make a bare-bones code snippet more resilient by handling errors more elegantly than simply saying “We hit a problem.” Sometimes, the question is more task-oriented, such as how to incorporate logging.
While GitHub Copilot might be helpful when making changes to scripts, it still requires a human to validate Copilot-generated code. For that, humans need to possess certain skills. This is the first installment in a monthly PowerShell Scripting series that’s designed to help part-time PowerShell developers. We expect readers to have some PowerShell knowledge and be comfortable with loading and using the Microsoft 365 PowerShell modules. We start with some essential talking points, such as functions and flow control. Eventually, we will talk about how to incorporate snippets in a self-contained script.
The goal is to help the target audience improve their skills using practical examples. Practical is the keyword. I am not interested in theoretical or aesthetic discussions on code layout or which style is better. I leave that debate to the PowerShell community, who can often have semi-religious discussions on why method X is better. I want code and examples to be readable and understandable. Also, layout and style preferences, which PowerShell doesn’t care about, are usually dictated by communities or organizations. PascalCase (typical for PowerShell) and Kernighan & Ritchie indentation styles are commonly used. I cannot promise that other Practical365 authors will follow my guidance.
Lastly, when publishing code, it’s always better to expand aliases, including the shorthand for ForEach-Object (%) and Where-Object (?). Spelling cmdlets out improves readability. This is less of an issue when using the command line or drafting some code, but it’s critical if you expect people to read and understand your code.
One-liners Versus Scripts
The question might come to mind about defining the difference between a one-liner and a script. A one-liner is a continuous set of commands tied together to form a stream of operations using the pipeline. Administrators commonly use one-liners for less frequent or ad-hoc tasks, where the command is copied from a OneNote or text file containing specific commands tailored for the environment.
However, a PowerShell one-liner may consist of more than one ‘physical line,’ and some smaller ‘scripts’ of two or three lines are usually more of an expanded one-liner with improved readability. This is especially true for nested commands, as shown in the following example:
Get-Mailbox | ForEach-Object { $_.EmailAddresses | ForEach-Object { Write-host $_ } }
Versus
$Mailboxes= Get-ExoMailbox ForEach( $Mailbox in $Mailboxes) { ForEach( $Email in $Mailbox.EmailAddresses) { Write-host $Email } }
Before the comments start rolling in: Yes, the last example can be written in many ways, and yes, Select-Object -ExpandProperty could have been used, and yes, this is less efficient and performant, but that is not the point here. The point is that both commands perform the same task, but only the first is a one-liner according to the definition. Using the pipeline may, in theory, bring performance benefits because it can process objects sent through the pipeline in parallel. I say in theory because, for example, the Sort-Object needs to collect all input before it can perform its magic.
The second example is better in terms of readability, but it also uses variables to store objects. Putting data into variables enables other options, such as the ability to perform other operations on the objects, such as setting values. But then the code is already starting to look more script-ish and less of a one-liner.
In my mind, everything depends on the intended use of the code. Also, since you can separate cmdlets in PowerShell using semi-colons, you can have your own PowerShell obfuscated code contest, like the International Obfuscated C Code Contest. While this technique might be great to show off, is that code readable and maintainable? Not so much.
Friends Don’t Let Friends Use Backticks
Earlier I mentioned a one-liner can consist of more than one line, which is where the backtick might come into play. PowerShell one-liners can become very long. For readability, you might want to break statements from the same one-liner or cmdlet and put them on multiple lines. Also, backticks can be used to add manual line-breaks for improved readability, instead of having automatic line-breaks inserted right after a parameter dash, such as in this example:
Set-Mailbox -Identity olrik@contoso.com – MessageCopyForSentAsEnabled $True
The backtick (`) is a line continuation character, allowing you to write a command using multiple lines. However, the consensus in the community is that backticks are terrible for readability. Depending on the situation, techniques such as splatting using hash tables might be more appropriate, as shown in the example below:
Backtick
Set-CalendarProcessing -Identity Ohio.Conference ` -AllRequestInPolicy $True -AllBookinPolicy $False ` -BookInPolicy 'Conference Organizers'
Splatting
$CPSettings= @{ Identity= 'Ohio.Conference' AllRequestInPolicy= $True AllBookinPolicy= $False BookInPolicy= 'Conference Organizers' } Set-CalendarProcessing @CPSetting
A quick glance at the second example shows why it is easier on the eye and more maintainable than the single-line version. This becomes even more apparent as you add more parameters to the command.
A small bonus is that splatting removes the need to check whether parameters are switches or (Boolean) values when handling. For example, in the following example, IncludeActiveMailbox is a switch:
Get-Mailbox -RecipientTypeDetails UserMailbox –IncludeInactiveMailbox
The presence or absence of IncludeInactiveMailbox determines what Get-Mailbox should fetch; there is no need to pass a value the usual way. Therefore, this will not work:
Get-Mailbox -RecipientTypeDetails UserMailbox -IncludeInactiveMailbox $True
A trick is available for parameters: assign values using a colon. This also works for switches. You might already use this when turning off confirmation prompts for cmdlets by specifying -Confirm:$False:
Get-Mailbox -RecipientTypeDetails UserMailbox -IncludeInactiveMailbox:$True
In splatting, you do not have to worry about switches:
$filter= @{ RecipientTypeDetails='UserMailbox' IncludeInactiveMailbox=$True } Get-Mailbox @filter
If you do not need to change the command, the backtick is fine, but as soon as you need to change a parameter or two, splatting makes the code easier to maintain. Not using backticks also prevents misreading, as the only indicator that a line is a continuation of the previous line is at the end, which is easy to miss. Note that if you paste the splatting example in the PowerShell command window, you will notice it will wait on the following line for more input. This is because of the opening accolade of the hash table ({). Command input will be completed after you send a closing accolade. The same principle applies to other brackets and quotes. PowerShell detects when you enter something incomplete, such as a missing closing quote. It will show the continuation prompt and wait for additional input until input is completed unless the operator cancels input using ctrl-break.
Summarizing Our Start
I hope this series will help people learn more about PowerShell by using and understanding commands so that you can create your own scripts with added bells and whistles. Feel free to reach out and tell me where you struggle with PowerShell in the comments. If not, wait for the next article, where I discuss functions and parameterization.
Great content. Thank you for starting this.
I have been using powershell windows and core for over 10 years. I piciked a bit that helps me. Read — there is something he knows you do not know.
Great content, I am trying to create runbooks using Microsoft graph to call users with employeetype set to “regular” but the script only works when calling a specific user and not when looping through all users
There are examples of using the Get-MgUser cmdlet with various types of filters in https://practical365.com/find-azure-ad-users-powershell/