© Adam Bertram 2020
A. BertramBuilding Better PowerShell Codehttps://doi.org/10.1007/978-1-4842-6388-4_9

9. Be Specific

Adam Bertram1  
(1)
Evansville, IN, USA
 

Us humans tend to take a lot of shortcuts and assume many things and for good reason. We wouldn’t survive in this world if our brain had to pay attention to every input we receive on a daily basis. We naturally and automatically assign context to situations, conversations, and more. This is why a coworker can just begin talking about something that happened the previous day you were involved with and you can quickly pick up context. You don’t have to start a conversation like: “At 7:01 PM, you and I were in our office working late. I then rose from my chair and yelled over the cube….” Your coworker gets it already.

Although artificial intelligence (AI) is trying, computers don’t assume. They aren’t smart. They can’t automatically do things. We humans must tell them exactly what to do. Sure, you can get away with taking shortcuts here and there, but if that random edge case comes along you never coded for, the computer isn’t going to handle it; it’s going to fail miserably.

In this chapter, you’re going to learn some tips on when to be specific in your code. You will learn about opportunities you will find yourself in to choose the more defined, explicit, specific route rather than relying on that off-chance your code will not encounter an edge case you didn’t account for.

Explicitly Type All Parameters

PowerShell is called a loosely typed language. This means that you don’t have to statically assign types to variables. Statically typed languages don’t allow you to simply define a variable or parameter like $var = 2 and automatically assign the $var value as an integer or assign the $var2 variable as a string in the code $var2 = 'string'. PowerShell makes assumptions based on various “patterns” it sees in the value.

Loosely typed languages like PowerShell are handy because they take care of some extra work, but this hand-holding can sometimes backfire. When defining function or script parameters, you should always statically assign a type to each one.

When creating a parameter, you have a lot of control. You can, for example, simply define a parameter name. PowerShell accepts this.
function Get-Thing {
    param(
        $Param
    )
}

I don’t advise you to define parameters like this even though you can. What happens if you pass in a string, how about a Windows service object? Are you handling for those times when someone tries to use the pipeline to pass in a Boolean somehow? You get the point. There are many different types of parameters PowerShell will accept in this situation. You’re probably not accounting for all of them in the code.

To make the parameter better, you should statically type the parameter to whatever type you’re supporting in the function code. Perhaps you expect a pscustomobject with a property of Foo. Your code will reference the Foo property on the Param object.
function Get-Thing {
    param(
        $Param
    )
    Write-Host "The property Foo's value is [$($Param.Foo)]"
}
Instead of assuming this will work, instead, explicitly cast the Param parameter to the type of object you’re expecting. Make this a habit.
function Get-Thing {
    param(
        [pscustomobject]$Param
    )
    Write-Host "The property Foo's value is [$($Param.Foo)]"
}

By explicitly assigning a type, you’re telling PowerShell that this function should only accept this type.

Sometimes explicitly defining the type isn’t as important as others, but it’s a great habit to get into.

Further Learning

Always Use Parameter Validation When Possible

Along the same lines as explicitly typing a parameter, you should also narrow the type of input a parameter will accept by using parameter validation. PowerShell provides many different ways you can limit values passed to parameters.

For example, maybe you have a function that is part of a larger automation routine that provisions servers. One function in that routine comes up with a random server name. The first part of that random server name is supposed to be what department owns that server.

You have defined three departments names that should be part of the server name.
$departments = 'ACCT', 'HR', 'ENG'
You then have a function to come up with the server name which accepts the department label and assigns a random number to the name.
function New-ServerName {
    param(
        [Parameter()]
        [string]$Department
    )
    "$Department-$(Get-Random -Maximum 99)"
}
The New-ServerName function is working as intended for a while because you just know you should only use one of three department labels. You decide to share this script with someone else and they don’t know that and instead try New-ServerName -Department 'Software Engineering' which creates a server name Software Engineering-88. This name doesn’t fit your schema and isn’t even a valid name. You need to ensure everyone only uses the values you want. You need to use a ValidateSet validation in this case.
function New-ServerName {
    param(
        [Parameter()]
        [ValidateSet('ACCT','HR','ENG')]
        [string]$Department
    )
    "$Department-$(Get-Random -Maximum 99)"
}
Now when anyone tries to pass a value that isn’t allowed, PowerShell will deny it and tell them which values they can use.
PS> New-ServerName -Department 'Software Engineering'
New-ServerName: Cannot validate argument on parameter 'Department'. The argument 'Software Engineering' does not below to the set “ACCT,HR,ENG” specified by the ValidateSet attribute. Supply and argument that is in the set and try the command again .

Further Learning

Always Define a Function’s OutputType

The OutputType keyword is a relatively unknown PowerShell construct, but one not only shows, at a glance, what kind of object a function returns but also helps the function user with some handy tab-completion.

Let’s say you have a function that returns a file object in the form of a System.IO.FileInfo object like this:
function Get-File {
    param(
        [Parameter()]
        [string]$FilePath
    )
    Get-Item -Path $FilePath
}
This function returns a single file object as you can see in Figure 9-1.
../images/501963_1_En_9_Chapter/501963_1_En_9_Fig1_HTML.jpg
Figure 9-1

Get-File returning a single object

Now let’s say you need to reference the time that file was created but don’t quite remember what the property name is. You pipe the output to Get-Member and begin looking.
Get-File -Path 'C:myfile.txt' | Get-Member
You wouldn’t have to use Get-Member if you would know what type of object is coming from that function. How do you do that? Using the OutputType keyword like the following:
function Get-File {
    [OutputType([System.String])]
    param(
        [Parameter()]
        [string]$FilePath
    )
    Get-Item -Path $FilePath
}

Not only is this helpful when reading the code, it’s helpful when inspecting the object that it returns. Since PowerShell knows what type of object will be returned when you run it, you don’t even have to run the function to discover the properties and methods on that object.

Enclose the function reference in parens or assign the output to a variable, hit a dot, and start hitting the Tab key. You’ll now see that PowerShell cycles through all of the available members on that object.
(Get-File -FilePath C:Test-PendingReboot.ps1).<tab>

Write Specific Regular Expressions

If you have to match a string with a regular expression (regex), be sure that regular expression is built as specific as possible. Regex can be another book in and of itself, but for this book, just try to build that expression as specific as possible. If not, you’re bound to match strings you didn’t intend.

Perhaps you need to match a globally unique identifier (GUID). An accurate regex to match a GUID would be like the following. That’s a lot of characters!
({){0,1}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(}){0,1}
Compare that long string to something like this that would also match GUIDs.
'^.{8}-.{4}-.{4}-.{4}-.{12}$'

The difference in these two regular expressions is not only the length but the accuracy. The first string match all string patterns a GUID could possibly be represented in and only those. The second example simply matches any characters separated by dashes. The second regex is less specific and could lead to matches you do not intend.

Also, pay attention to case sensitivity. Maybe you have an expression that’s only supposed to match exactly the name of Michael. You decide to hurriedly put in a regex and just use the match operator like the following. It works for now but some scenario down the road, the michael string comes along, is matched and screws up your script.
'michael' -match 'Michael'
Instead of using the case-insensitive match operator, consider using the case-sensitive cmatch operator . Using the cmatch operator ensures the string you intend to match is exactly what you intend.
'michael' -cmatch 'Michael'
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.49.182