Classy Powershell

Using classes in PowerShell

So I've recently come to fall in love with PowerShell, despite all its strange quirks and odd behaviors. For those not in the know, some time ago, Microsoft decided that they needed a more fully functional scripting language then the command line syntax that existed. So they spent a bit, and before you know it they had spun up PowerShell. So, being one of those programmers who aren't just satisfied with if/then loops in my languages, I wondered if given its object-oriented nature, PowerShell supported classes. It turns out, that with the introduction of PowerShell 5.0, they had actually introduced the concept of classes; however, as I was searching around for how to use them, the documentation was a bit more... sparse.

More than one file? 

If you're like me, your programming is usually organized with handy dandy folders, I usually have something like this

main.ps1 - The actual "Executable" code that the user runs
- \lib\ - A series of functions that the program needs to run that I'll use a lot
- \bin\ - The actual "Code" that makes the program unique
- \str\ - Storage for whatever the program might need
- \cfg\ - Configuration settings
- \tmp\ - Temporary files
So with all this in mind, I wondered if I could call all the .ps1 scripts I had so politely nested in my /lib/ folder with one single call, to simply iterate through each of them and then for each one I imported run import module, something like 

foreach $item in $libfolder{
 import-module $item
}
And as it turns out that would have worked fantastically, if it wasn't for one small little detail. If I put a class in those library files? Well, Powershell can't use them, at least not calling them from main.ps1.

 Classes vs Module 

Before Powershell introduced a class based design, it was quite happy living off another implementation. You see, there is this wonderful little concept in PowerShell called modules. A module was intended to be basically like a .dll file, a series of functions that one could run, and then the ability to set which functions from it could and couldn't be extracted, and honestly in the use case I was looking at a module probably would have been just fine. Ever the one to want to try and be fancy in my code, however, I opted for a different route.

To label something a "Module" you literally only need to change the file type from a .ps1 to a psm1, and then run the import-module command. This makes things fairly simple in theory, and for getting things off the ground fairly quickly it can be handy

Now you can further specify the things you do and don't want to be shared from the module, as by default PowerShell will simply treat it as little more than a glorified .ps1 file and import everything. To do this you simply add these somewhere near the bottom 
Export-ModuleMember -function 'RegeX goes here'
Export-ModuleMember -variable 'AnotherRegexHere' 
If you have administrator rights on the machine, you can even skip the whole import-module bit entirely  by simply storing the modules wherever $ENV:PSModulePath happens to be looking, as PowerShell will simply load up all the .psm1 files in there that it feels the need to load up 

This is great and all, but this still fails to answer the question of what to do about classes. Now if you're like me you probably stumbled across "Using" 

Using 

So a few things about the "using" command. First and let's get this out of the way now, if you're going to go about loading up your classes in this way, you can't do what I had originally hoped and dynamically iterate through a folder. The using command must be at the absolute top of the script. The only thing that can be above it is comments. You call it like this
Using module '/myPath/myModule.psm1'
Again awesome, though we lose our ability to summon up these psm1 files dynamically, it seems like a small price to pay when it comes to being able to use those classes we've been meaning to get at. Now when you run
[myClass]::new() 
Powershell won't look at you like you've just vomited up a baby.  But there is one TINY little hitch in this plan.

So if you're like me, you are probably calling on some namespaces, like the presentationframework which by the way you can find with all of it's namespace cousins nested in C:\Windows\Microsoft.NET\assembly\GAC_64 if you were ever curious just what the namespaces actually were referring to.

The thing is that if I call my .psm1 file and it relies upon a namespace, even if I call that namespace with something akin to
using namespace PresentationFramework
using module './lib/MyModulethatNeedsPresentationFramework' 
It's still going to fail. This is where we have to introduce something called the Powershell Manifest file. Now what this means is where you had myModule.psm1 before you're now going to need
myModule.psm1
myModule.psd1 
And then to invoke the module you're going to run the following
using './myPath/myModule.psd1' 

The PSD1

The PSD1 filetype is actually fairly useful. This is the file that we use to define plenty of little features for our developed module without cluttering it. Instead of putting those functions to export at the bottom of yoru PSM1 you can just put it here. They basically look like giant configuration files so you end up with something like this
@{
RootModule = 'myModule.psm1'
ModuleVersion = '1.0'
Author = 'Alnarra'
CompanyName = 'Alnarra Media LLC'
Copyright = '(c) 2018 Alnarra Media LLC. All rights reserved'
}
Which is simple enough, but the awesome thing here is that we can also tell it a few other things, including any required modules (like say you've developed an error handling module or a file browsing module, and you want to use those everywhere). You can simply specify them with
RequiredModules = 'error.psd1' 
But what we're looking for is actually the assemblies, and again it's pretty simple, simply call them like this 
RequiredAssemblies = 'presentationFramework' 
So, in the end, your .psd1 file looks something like

@{
RootModule = 'myModule.psm1'
ModuleVersion = '1.0'
Author = 'Alnarra'
CompanyName = 'Alnarra Media LLC'
Copyright = '(c) 2018 Alnarra Media LLC. All rights reserved'
RequiredAssemblies = 'presentationFramework'
RequiredModules = 'error.psd1'
}
Now for the whole kit and kaboodle, we have

main.ps1

using module './lib/myModule.psd1'
function someFunction{
[myClass]::new()
}
Then we'd have our /lib/ folder inside of which would be two files
myModule.psm1
myModule.psd1 
Mymodule.psm1 would look something like
class MyClass {
   myClass() {
     #Do Something that requires the presentation framework
  }
And MyModule.psd1 would appear as something like
@{
RootModule = 'myModule.psm1'
RequiredAssemblies = 'presentationFramework'
}
Admittedly this is probably overcomplicating what would otherwise be a simple thing, and this will only work in Powershell 5.0, but for me, it makes things a lot easier to manage and keep track of. The code's always easier to maintain when it's not a jumbled mess and a single file. I feel like spreading it out across a few files while adding a bit of read overhead in terms of IO helps keep things consistent.

Here in a couple of weeks, I'll try to walk through an ACTUAL and useful powershell script I used to work with ipInfo.io in order to quickly gather information on a .csv file full of IP addresses and format them into an easily readable output 

Comments

Popular Posts