March 31, 2015

A Grumpy Module

So following on from my blog post about Hashes, we are going to make generate a hash code in to something more reusable in PowerShell. It makes me grumpy when I can’t reuse code… at the end of the day scripting is there to make life faster right! Grumpy Admin is lazy! So let make that HASH generating code more reusable by the means of a PowerShell Module.

Firstly a PowerShell module has a file extension of .psm1. There don’t say Grumpy Admin never parts wisdom to people!

There are a few components of a PowerShell module, this provides some wonderful features, which means you don’t have to do so much code yourself. Now I want my module to be called grumpy – mainly because I like the idea that we can do the

import-module .\grumpy.psm1

This is good, and now is the starting place for me. So let’s do a quick Google and get up some documentation to help us.

https://msdn.microsoft.com/en-us/library/dd878340(v=vs.85).aspx

Wow, lots of information there! So the basic structure of a module is the function and the meat of what the module does, and then you have to export command.  This makes PowerShell aware of what commands are in the module.

I just as I was typing this up decided this to call our function/cmdlet – get-hash.  You can see I am using the verb GET. It is quite important to the concept of PowerShell to use the right sort of verbs.  If you want to get a list of all the processes you type get-process.  So you….. think and do…. In PowerShell This is perhaps what has helped with the adoption of PowerShell.  So to expose our new function get-hash at the end of the module we can do

export-modulemember -function get-hash

While we are thinking about it lets put the basic framework of our get-hash function in the grumpy module.

Function get-hash
{
}grump1

import-module .\grumpy.psm1

And now let’s see if our function is now exposed to PowerShell….

get-hash

Yep it works, excellent… now let’s cut and paste our code from the other blog in to this function and see how it fits. The first thing we should do is make it so it accepts a different file – so let’s create a param for our function. There are lots of way you can use param. Here are a few examples ( well I got bored at work today ). To show you how to do different things with param and it started to feel like I should done a blog post on param on its own!

Function test {
[CmdletBinding()]
Param()
}

 

The above example, show you the basic layout of the function and you can see that I have included the [cmdletbinding()], this give you access to the default cmdlet parameters such as debug or verbose. Very useful and helps make your cmdlet feel more like a PowerShell. Best of all very little effort required.

Function test2 {
[CmdletBinding()]
Param(
[string]$test
)
}

This example show you how to present a parameter to the cmdlet you have to declare what type it is. So for this string $test I used [string]$test this allows me to do a cmdlet such as

test2 -test “hello”

I think you get the idea with the param to be honest, however, as Grumpy Admin took the time to write the test functions to show you. You can have them! Here are a few additonals as well which you might what to research and learn how to use in your own code in the future J

Function test2 {
[CmdletBinding()]Param(
[Parameter(Mandatory=$true)][string]$test,
[string]$test1,
[Parameter(Mandatory=$true,Position=1)][string]$test2
)
}

This one allows you to say a parameter is mandatory

function test3 {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)][string]$test,
[string]$test1,
[Parameter(Mandatory=$true,Position=1)][string]$test2,
[Parameter(ValueFromPipeline=$true)][string]$test3)
}

this one allows you to say the value can come from the pipeline

function test4 {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,HelpMessage=”Enter Test”)][string]$test,
[string]$test1,
[Parameter(Position=1,HelpMessage=”Enter Test2″)][string]$test2,
[Parameter(ValueFromPipeline=$true,HelpMessage=”Enter Test3″)][string]$test3
)
}

This one allows you to present a help message if they put a !?

function test5 {
param(
[ValidateCount(1,5)][int[]]$test
)
}

this one does a validation check, to make sure there are no more than 5 int values

function test6 {
param(
[ValidatePattern(“[0-9]”)][string]$test
)
}

This one makes it that while the parameter is a string – you can only enter a number 0-9 – very useful perhaps?

function test7 {
param(
[ValidateRange(1-255)][int]$test
)
}

This one validates a range

function test8 {
param(
[DateTime][ValidateScript({$_ -ge (get-date)})]$date = (get-date)
)
}

This one is a great example, that checks the date is greater than or equal to the current date allowing you to do some validation early on in your script.

function test9 {
Param
(
[ValidateSet(“test1”, “test2”, “test3″)][String]$test=”test1”
)
}

So the first thing you notice from all these test examples, is that Grumpy Admin has no life!!! This you will be glad to know was the last test, as I got bored of doing them. Test 9 validates a set, makes sure that $test is either test1 test2 test3 or if it is blank puts in test1 for you. Again validation of user input is great.

Now we have finished with that detour lets go back to our grumpy function. We want to be able to say what file we want it to create a hash of so let’s create a mandatory parameter called file!

[CmdletBinding()]param(
[Parameter(Mandatory=$true)][string]$file
)

And let’s redo out code to reflect the fact the file is now a variable not a static string so

$filebytes=[system.io.file]::ReadAllBytes($file)

Now let give that run through, you might have to

remove-module grumpy and reimport it!grumpy3

grumpy2To get your changes to the module to be reflected correctly. So there we have it – already this has increased the usefulness of our code by 10X. Now this works, but it isn’t perfect yet, but we can start to do all sorts of do things with it like this

gci |%{get-hash $_.FullName}

grumpy5

It looks like it has work, but it generates error and a sea of red, on directories and other things that it can’t process like files larger than 2gig files.  Let’s investigate if it really has worked, though. Let’s add in a quick

write-host $file $hash

Yes I know, I really shouldn’t use write-host etc., but I’m just messing around here, I just want to prove it working. Excellent, it is kind of working….. not very pretty and well is bad code! Which makes me grumpy at myself but our module is getting better and hopefully over the next few blog posts we can keep improving on it. So lots of work to do, like selection of what hash we want to generate, but I guess we can work on that in the future. We can also link this to the public API on virus total, how useful will that be?

So let’s recap, first I am certain other people have done this before and perhaps done it better!

We looked at how to create a module, how to export the function to make it visible to PowerShell and we also had a reasonable look at the param() features. We have made a good start in making our get-hash cmdlet working correctly and being a module worthy of sharing.  Still miles to go to get to be a brilliant cmdlet but we made a start. Grumpy Module has been born!

Hazzy