The more you write code, the more you notice patterns in the code you write. This goes for PowerShell, C#, Ruby, and any other programming or scripting language under the sun. Patterns are opportunities. Opportunities to generalize them and define them as design patterns. Opportunities to isolate blocks of reusable code in such a way that they can be reused so that you can follow the DRY principle in your work. This article is about the latter of those two.
In recent months I have been working on trying to reduce duplication in my own work by keeping my modules and functions as DRY as possible. One challenge with keeping code DRY in PowerShell is in deciding which is the most appropriate method to do so. There are many opportunities to keep your code DRY in PowerShell. You can create:
- advanced functions
- basic functions
- unnamed functions (aka script blocks)
- script files
- type extensions for the Extended Type System (ETS)
- classes with properties and methods, either in a .NET assembly that is imported into PowerShell, or if you’re using PowerShell 5.0 or later, in PowerShell itself
- Despite each of these extension points being available to PowerShell, they don’t always fit the scenarios you need them to, perhaps because they are not appropriate for the intended purpose, because they have some limitation that you can’t work around, or perhaps for some other reason. For example, I find myself writing all of these, and there are certain pieces of code that I want to set up for easy sharing in many of these types of extensions without being an extension point itself. When you have logic that you might use anywhere that you could write PowerShell, how do you set that up in such a way that you can consume it in all of those locations easily regardless of the machine you are running on, without taking a dependency on physical file paths? That last point is important, because standalone ps1 files may be one possible answer to this need, except invoking them requires knowing where they are, and when you invoke them you must decide whether to dot source them or call them with the call operator, which in turn means you must know the implications of such a decision. Plus, when their use spans all of PowerShell (any script, any module, any function), where do you put them without having to burden the consumer with extra setup work? And how can you create more of these while making them discoverable, and able to be added to or removed from a system with ease?
- Snippets are a great answer to these questions. Snippet is a programming term that has been around for many years, and it generally refers to a small region of re-usable code. They are also a lot more than a fancied-up bit of copy/paste functionality. They have parameters, or fields, that control how they run. They can surround text you have selected, or simply insert into the current cursor location. Most importantly however, for me at least, is that snippets can be invocable, and that, my friends, is key because when you’re trying to maintain a DRY code base, you don’t want to inject boilerplate code blocks in many locations…you want to invoke them.
With snippets being a great solution to this problem, I decided to try to build a snippet-based solution that would allow for discoverable, invocable snippets. I wanted this solution to be able to find snippets regardless of what computer you were running on, as long as you followed a few simple rules. I wanted this solution to keep snippet definitions as simple as the creation of a ps1 file. And I wanted this solution to allow for snippets to be invoked in the current scope by default, and in a child scope as an option.
Enter SnippetPx. SnippetPx is a module that provides two very simple commands, plus a handful of snippets. The two commands are Get-Snippet, which retrieves the snippets that are discoverable on the local computer, and Invoke-Snippet, which allows a snippet to be invoked by name, with or without parameters, in the current scope or in a child scope. With this module, any ps1 file that is stored in a “snippets” folder inside of the current user’s Documents\WindowsPowerShell folder, the system %ProgramFiles%\WindowsPowerShell folder, or as a subfolder under the root of any module that is discoverable via PSModulePath will be discoverable as a snippet. You can see some examples by looking in the “snippets” folder in the SnippetPx module itself, or by looking in another module that includes some snippets such as the TypePx module. Also, any snippet that is discoverable by this module is invocable by the Invoke-Snippet cmdlet.
Since creating this module, it has quickly become a core module that my other modules take a dependency on in order to keep my code DRY. That effort has already paid off for myself because it has allowed me to update a block of code that is defined in a snippet and only have to make that change once, while all other locations where that snippet is invoked simply run with the new code change. I encourage you to give it a try and see if it helps you remove what might otherwise be repetitive code that is more difficult to maintain than it should be.
If you would like to give SnippetPx a try, you can download Snippet from GitHub (yes, it is open source, along with the binary module where Invoke-Snippet and Get-Snippet are implemented) or from the PowerShell Resource Gallery (aka the PowerShellGet public repository). Feedback is more than welcome through any means by which you want to communicate with me: the Issues page for this project on GitHub, social media channels, comments on this blog, etc. In the meantime, I will continue to identify opportunities to create more snippets that will be useful to others and push them out either as updates to SnippetPx or in the modules where those snippets are used.
One more thing: if you do decide that you want to create some snippets of your own, there are some useful details in the Notes section of the help documentation for the Get-Snippet and Invoke-Snippet cmdlets. I strongly recommend giving that a read before you dive into creating invocable snippets, as it provides some additional detail on how snippets are discovered as well as recommendations on the naming of your snippet ps1 files.
Thanks for listening!