Warren F bio photo

Warren F

Systems Engineer with a penchant for PowerShell, science, cooking, information security, family, cookies, and the Oxford comma.

Connect

@pscookiemonster LinkedIn Github Stackoverflow TechNet RSS Feed My old blog

My GitHub Repos

AppVReporting BuildHelpers Citrix.NetScaler Git-Presentation InfoBlox Invoke-Parallel PowerShell PSDepend PSDeploy PSDiskPart PSExcel PSHTMLTable PSRabbitMQ PSSlack PSSQLite PSStash RabbitMqTools SecretServer

Overview

I spend a good deal of time wrapping common tasks into PowerShell functions. Here are a few best practices I’ve picked up along the way. My apologies if I miss any attributions!

Suggested Best Practices

Write your function with one purpose

  • Write your function with one purpose. Don’t build in everything but the kitchen sink.
    • This is one of PowerShell’s strengths – there are existing functions to work with input or output objects, or you can write your own set of functions to work together.

Follow naming conventions

  • Follow naming conventions.
    • Use the Verb-Noun format, use an approved verb, and ensure that your Noun is unique and will not collide with another author’s function now or in in the future.
      • Approved Verbs
      • Example: I’m writing commands to work with a Hyper-V lab. Set-Lab and Get-Lab are generic and may be used by another author. I can add a prefix like HV to avoid this – Set-HVLAB and Get-HVLab
    • Use common parameter names and types as appropriate.
      • Example: Use ComputerName to specify systems. Do not use ComputerNames, Computer, PC, or any other non-standard parameter name. If desired, provide an alias for the parameter.

Use the built in comment-based help system

Let PowerShell do the work for you

Use advanced parameters

Provide flexibility with your parameters

  • Provide flexibility with your parameters. Provide default values, allow arrays instead of single objects, allow wildcards, and provide other helpful parameter features.
    • Example: [string[]]$ComputerName = $env:computername is more helpful than [string]$ComputerName

Document your code for yourself, readers, and users

  • Document your code for yourself, readers, and users.
    • Use write-verbose, write-debug and write-error to provide insight at the shell
    • Comment your code in everyday language for readers. If you used a specific command or logic for a reason, explain why it was necessary and why changing it could break things.
    • Use full command names and full named parameters. This makes the code more readable. It also prevents issues that could arise if you rely on aliases or positional parameters.

Avoid dependencies

  • Avoid dependencies. This includes external scripts and modules, binaries, or features exclusive to PowerShell or .NET Framework versions. If you must include dependencies, be sure to indicate this and provide appropriate error handling.
    • Example: Get-ADGroupMember requires the ActiveDirectory module. Instead of relying on this, include or write your own function.
    • Example: To create a new object, use New-Object -TypeName PSObject -Property @{A=1; B="Two" } | Select-Object A, B instead of [PSCustomObject]@{A=1; B=”Two”} to provide compatibility with PowerShell 2.

Provide error handling with helpful messages

Do not break the user’s environment

  • Do not break the user’s environment. Don’t touch the global scope.

Test, Test, Test!

  • Test, Test, Test! Test any reasonable scenario your function might run under.
    • Test with and without a profile. Test with 64 and 32 bit PowerShell hosts. Test with the ISE and Console Host. Test with a single-threaded apartment and multi-threaded apartment. Test with and without the administrative token, with and without actual administrative authority.

If your function provides output, use objects

  • If your function provides output, use objects.
    • Do not output strings. Do not use Write-Host. Do not format the results. You and your users will get the most out of PowerShell when you provide output in objects, that can be passed down the pipeline to other commands.
    • Creating Custom Objects

Why bother?

  • Following these best practices will help you and the greater PowerShell community if you chose to share your code.
  • Your function will fit into the PowerShell world, enabling integration with the many technologies PowerShell can work with.
  • Your function will be usable by wider audiences, who may even provide suggestions and tweaks to help improve it.
  • Your function will be flexible and gracefully handle various scenarios you throw at it.
  • Your function will last. If you avoided or accounted for dependencies, your function should withstand changes to PowerShell, the .NET Framework, and the user’s environment.
  • These practices apply to scripts as well. You can use the majority of these best practices when writing scripts, rather than functions.

Illustrating the best practices

We will look at Get-InstalledSoftware, a quick function that extracts installed software details from the registry.

Write your function with one purpose

This function does one thing: get installed software.

Follow naming conventions

Get-InstalledSoftware follows the Verb-Noun naming format, uses an approved verb, and uses typical parameter names such as ComputerName… but the function name is not unique. In fact, there is another script out there with the same name. Perhaps I should have chosen a better example!

Use the built in comment-based help system

The help system provides a synopsis, a description that points out prerequisites, describes each parameter, provides two examples, and provides a link that will take the user to the Technet Gallery page if they use Get-Help Get-InstalledSoftware –Online

Let PowerShell do the work for you

The function uses [cmdletbinding()] and many of the features it enables.

Use advanced parameters

This function uses advanced parameters for computername. This allows input from the pipeline (e.g. an array of strings), input from the pipeline by property name (e.g. an array of objects with a computername property), and validates that the argument is not null or empty.

Provide flexibility with your parameters

ComputerName is given a default value of the local machine and allows an array of strings rather than a single string. The Publisher and DisplayName parameters are used with the –Match operator and can thus take in regular expressions.

Document your code for yourself, readers, and users

The code uses Write-Verbose and Write-Error. Comments explain what is happening. The ‘help’ information describes prerequisites, and if connectivity fails, verbose output suggests where to start troubleshooting. Aliases are not used in the function.

Avoid dependencies

This code does depend on certain factors – privileges, connectivity, and the Remote Registry service. This is detailed in the help information and in the verbose output. Language, including the output objects we create, is compatible with PowerShell v2.

Provide error handling with helpful messages

Try/Catch blocks are used to capture errors where they would likely occur, and are used in a way that will allow continued processing if errors occur. For example, if multiple computers are specified and one fails, we move to the next computer (continue), rather than breaking execution of the command.

Do not break the user’s environment

The global scope is not altered by this function

Test, Test, Test!

This script was tested in a limited number of expected scenarios. With and without a profile. In the ISE and console host. With and without the administrative token.

One scenario that illustrates the importance of testing is this command’s behavior in a 32 bit session on a 64 bit machine. In this scenario, the script will miss 64 bit items, and will pull double copies of everything else (the native and Wow6432Node keys will point to the same location). I added this to the description. Ideally I should test for and handle this, but doing so would add undue overhead to a lightweight function for what I consider a niche scenario.

If your function provides output, use objects.

This function provides object based output. Not text. Not a CSV. You can use the output with any number of built in or custom commands.

Get-InstalledSoftware in action

  • The end user can use the built in Get-Help command for help. The online switch takes you right to the TechNet gallery site.

    Help

    Help Online

  • We can pass in multiple computers and filter Publisher and DisplayName using regular expressions

    flexible parameters

    flexible parameters 2

Helpful resources

The following resources will provide further help and suggestions for best practices when writing PowerShell.

Good luck! If you do end up writing advanced functions, please consider posting them to websites like PoshCode, TechNet Script Gallery, or GitHub!