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

8. Build with Manageability in Mind

Adam Bertram1  
(1)
Evansville, IN, USA
 

You can write code all day to solve all the things, but if you can’t manage it over time, you’re sunk. It’s important to not only solve the problems of today but think about how those solutions will be maintained over time.

DRY: Don’t Repeat Yourself

Notice when you’re repeating the same code snippets. Be cognizant you’re following the same patterns over and over again. Being great at coding is about pattern recognition and improving efficiency.

Don’t type out the same command ten times to process ten different parameter values. Use a loop. Write “helper” functions that can be called from other functions to eliminate writing that same code again.

Let’s say you have a script that checks for Windows features, and if they don’t exist, installs various Windows features. Currently, your code looks like this to install a single feature.
if (-not (Get-WindowsFeature -Name 'Web-Server').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
You eventually need to install other features. You think that since you already have the code to install one feature, it’d be OK to just copy/paste this code for each new feature.
if (-not (Get-WindowsFeature -Name 'Web-Server').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
if (-not (Get-WindowsFeature -Name 'SNMP-Server').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
if (-not (Get-WindowsFeature -Name 'SNMP-Services').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
if (-not (Get-WindowsFeature -Name 'Backup-Features').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
if (-not (Get-WindowsFeature -Name 'XPS-Viewer').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
if (-not (Get-WindowsFeature -Name 'Wireless-Networking').Installed) {
    Install-WindowsFeature -Name 'Web-Server'
}
Although the preceding code gets the job done, you can tell this practice isn’t sustainable. What if you must change how to detect the feature or install it? You’d have to do a lot of find/replace behavior. There’s a better way to do this by separating the elements that do change (the feature name) with the code that will not typically change (the code to check for an install the feature).
$featureNames = @(
    'Web-Server'
    'SNMP-Server'
    'SNMP-Services'
    'Backup-Features'
    'XPS-Viewer'
    'Wireless-Networking'
)
foreach ($name in $featureNames) {
    if (-not (Get-WindowsFeature -Name $name).Installed) {
        Install-WindowsFeature -Name $name
    }
}

Not only is the code much shorter, it’s also much more manageable in the future. To add new features, simply add them to the $featureNames array. To change the behavior of find the status or installing a new feature, just change the code inside of the foreach loop.

The preceding example is only one way to implement the DRY mentality. Be constantly looking for repeating patterns and address them using logic.

Further Learning

Don’t Store Configuration Items in Code

Always treat configuration items that are required in your code as separate entities. Items like usernames, passwords, API keys, IP addresses, hostnames, etc. are all considered configuration items. Configuration items should then be pointed to in your code. These artifacts are static values that should be injected in your code, not stored in the code.

Separating out configuration items from your code allows you the code to be more flexible. It allows you to make a change at a global level and that change be immediately consumed by the code.

Whenever you write PowerShell code, you should always build with reuse in mind. Storing configuration items outside of the code is one way to do that. Taking the previous tip “DRY: Don’t Repeat Yourself” to the next level, let’s build that code using a configuration file.

Instead of defining the items that may change over time (Windows feature names) inside of the code as is, store them in an external data store. This kind of data store can be anything like a JSON, XML, YAML, or even a SQL database. The point is to separate the data from the code logic itself.

Let’s use a PowerShell data file as the data store called ScriptConfiguration.psd1 in the same directory as the Windows feature script as shown here:
@{
    'WindowsFeatures' = @(
        'Web-Server'
        'SNMP-Server'
        'SNMP-Services'
        'Backup-Features'
        'XPS-Viewer'
        'Wireless-Networking'
    )
}
The original script would then read the PowerShell data file and use it as input as shown in the following code snippet:
$configuration = Import-PowerShellDataFile "$PSScriptRootScriptConfiguration.psd1"
foreach ($name in $configuration.WindowsFeatures) {
    if (-not (Get-WindowsFeature -Name $name).Installed) {
        Install-WindowsFeature -Name $name
    }
}

This practice sets up your script for other configuration values down the road and even allow non-developers to easily change the behavior of the script simply by adding or removing Windows features in the configuration file.

Further Learning

Always Remove Dead Code

Although not critical, leaving code in scripts that will never be executed is bad practice. It clutters up the important code and makes it harder to understand and troubleshoot scripts. Remove it.

Look into using the code coverage options in Pester to discover all of the unused code in your scripts. If you’re using Visual Studio Code with the PowerShell extension, you can also spot and remove dead code by paying attention to unused variables.

Perhaps you have a script that connects to a remote computer. At the time you built it, your test computer was not in an Active Directory domain and you had to pass a PSCredential object to the remote computer. To support that use case, you added a Credential parameter. But things have changed, and this script is only used in a domain environment without needing an alternate credential.

You also had a variable inside of the script that served a purpose at one time, but you had changed the value.
[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [string]$ComputerName,
    [Parameter()]
    [pscredential]$Credential
)
$somelostForgottenVariable = 'bar'
$service = Get-Service -Name 'foo' -ComputerName $ComputerName
if ($service.Status -eq 'Running') {
    Write-Host 'Do something here'
} elseif ($someLostForgottenVariable -eq 'baz') {
    Write-Host 'do something else here'
}

The preceding code has two pieces of “dead” code: the Credential parameter and the $someLostForgottenVariable variable . Why is this code “dead”? Because it will never be used in the script. You can invoke the script using the Credential parameter, but it’s not going to be used. Also, as is, with the value of $someLostForgottenVariable set statically to bar, the Write-Host 'do something else here' line will never execute because it’s depending on a value that will never be set.

Tip Source: https://twitter.com/JimMoyle

Further Learning

..................Content has been hidden....................

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