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

6. Parameterize Everything

Adam Bertram1  
(1)
Evansville, IN, USA
 

One of the key differences between a simple script and a PowerShell tool are parameters. Parameters allow developers to write scripts that are reusable. Parameters don’t force developers to edit their scripts or functions every time they need to run them. They allow users to modify how the script or function works without modifying the code.

Parameters are an integral component of building a reusable PowerShell tool that turns ad hoc scripts into building blocks.

In this chapter, you’ll learn many different tips on how to properly use parameters in your daily life.

Don’t Hardcode. Always Use Parameters

You should make it your mission to reuse as many scripts and functions as possible. There’s no need to re-create the wheel. One of the easiest ways to do that is to define parameters for everything.

Before you finish up a script, think about how it could be reused for other similar purposes. Which components may need to be changed for next time? Is it a script to run against a remote computer? Make the computer name a parameter. How about referencing a file that could be anywhere. Create a FilePath parameter.

If, most of the time, the value is the same, set a default parameter and override it as necessary.

Don’t hardcode values that may change in scripts or functions.

Let’s say that you have a need for a script that restarts some services on one particular server that looks like the following code snippet:
## restart-serverservice.ps1
Get-Service -ComputerName FOO -Name 'service1','service2' | Restart-Service

On the surface, there’s nothing wrong with the preceding line. It does what you need it to do. But one of the most important concepts when coming from the PowerShell kiddie pool to the deep end is thinking ahead. Ask yourself: “Do I ever see a need where I’ll need to restart different services or restart services on a different server?” If you answered yes to that question, you need to provide parameters.

Parameters help you build reusable tools, not just static scripts. Instead of the preceding script, instead, build one that looks like the following:
## restart-serverservice.ps1
[CmdletBinding()]
param(
    [Parameter()]
    [string]$ServerName,
    [Parameter()]
    [string[]]$ServiceName
)
Get-Service -ComputerName $ServerName -Name $ServiceName | Restart-Service
You’d then call this script using parameters.
. estart-serverservice.ps1 -ServerName FOO -ServiceName 'service1','service2'
Perhaps, for now, you know you will always use the same server and services. You don’t have to keep passing in the same server and services every time with parameters. Instead, assign default values to parameters. You can still run . estart-serverservice.ps1 like you did before without parameters, but now you can change up the functionality of the script simply by passing different values to the script instead of modifying the script itself.
## restart-serverservice.ps1
[CmdletBinding()]
param(
    [Parameter()]
    [string]$ServerName = 'FOO',
    [Parameter()]
    [string[]]$ServiceName = @('service1','service2')
)
Get-Service -ComputerName $ServerName -Name $ServiceName | Restart-Service

Further Learning

Use Parameter Sets When All Parameters Should Not Be Used at Once

If you have a script or function with parameters that cannot be used at the same time, create parameter sets. Parameter sets are there to help you control which parameters are used together when you have similar ways of calling a script or function.

For example, perhaps you have a function that takes input via an object or a simple name. Let’s call it Reboot-Server. It looks like the following:
function Reboot-Server {
    param(
        [Parameter(Mandatory)]
        [string]$ServerName
    )
    if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) {
        Restart-Computer -ComputerName $ServerName
    }
}
You call this function by passing a string value to the ServerName parameter as shown in the following:
Reboot-Server -ServerName FOO
All is well but you’ve been getting better at PowerShell and want to create a module called RebootServer that has a function find a server in Active Directory (AD) called Get-Server and use the pipeline to pass that server object to Reboot-Server. You’re creating your own objects now too.
## RebootServer.psm1
#requires -Module ActiveDirectory
function Get-Server {
    param(
        [Parameter()]
        [string]$ServerName
    )
    ## If ServerName parameter was not used, pull computer names from AD
    if (-not ($PSBoundParameters.ContainsKey('ServerName'))) {
        $serverNames = Get-AdComputer -Filter * | Select-Object ​-ExpandProperty Name
    } else {
        $serverNames = $ServerName
    }
    ## send a custom object out to the pipeline for each server name found
    $serverNames | ForEach-Object {
        [pscustomobject]@{
            'ServerName' = $_
        }
    }
}
When you run this function, it will return one or more custom objects with a ServerName property. You then want to pass this object directly to the Reboot-Server function using the pipeline to reboot all servers that are passed to it like the following:
## Reboots all computers in AD. Ruh roh!
Get-Server | Reboot-Server
But this scenario won’t work because, as is, the Reboot-Server function only has a ServerName string parameter that doesn’t accept pipeline input. You want to still be able to run Reboot-Server like so:
Reboot-Server -ServerName FOO
But you also want the ability to use the pipeline so you create another parameter called InputObject that accepts your custom object that Get-Server returns over the pipeline as shown in the following. You then add the necessary logic to test which parameter is used.
## RebootServer.psm1
function Reboot-Server {
    param(
        [Parameter(Mandatory)]
        [string]$ServerName,
        [Parameter(Mandatory, ValueFromPipeline)]
        [pscustomobject]$InputObject
    )
    process {
        if ($PSBoundParameters.ContainsKey('InputObject')) {
            $ServerName = $InputObject.ServerName
        }
        if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) {
            Restart-Computer -ComputerName $ServerName
        }
    }
}

Notice that both parameters are now mandatory. You want to be sure one of them is used, but when you run Get-Server | Reboot-Server, you’ll find that you’re prompted for the ServerName parameter. You’re using the pipeline and it should be using InputObject instead, you think. But PowerShell is confused. It doesn’t know which parameter you want to use. You have two scenarios here, using the ServerName parameter or the InputObject parameter, not both.

To rectify this situation, use parameter sets. Put the ServerName and InputObject parameters in different sets. This way, you can still make each mandatory, but now PowerShell won’t be confused, and you are only allowed to use one parameter. PowerShell won’t let you use both parameters together which is exactly what you want.
## RebootServer.psm1
function Reboot-Server {
    param(
        [Parameter(Mandatory,ParameterSetName = 'ServerName')]
        [string]$ServerName,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
        [pscustomobject]$InputObject
    )
    process {
        if ($PSBoundParameters.ContainsKey('InputObject')) {
            $ServerName = $InputObject.ServerName
        }
        if (Test-Connection -ComputerName $ServerName -Count ​-Quiet) {
            Restart-Computer -ComputerName $ServerName
        }
    }
}

Further Learning

Use a PSCredential Object Rather Than a Separate Username and Password

PowerShell has a type of object called PSCredential. This object stores a username and password with the password securely encrypted.

When you write a new script or function, use a [pscredential]$Credential parameter rather than a UserName and Password. It’s cleaner, ubiquitously common in the PowerShell world, and a more secure way to pass sensitive information to a function.

Perhaps you have a script that authenticates to some services. To do that, the service needs a plaintext username and password, so you oblige by creating two parameters, UserName and Password both as string values.
## somescript.ps1
[CmdletBinding()]
param(
    [Parameter()]
    [string]$UserName,
    [Parameter()]
    [string]$Password
)
.someapp.exe $UserName $Password
You would then call this script like so:
.somescript.ps1 -UserName 'adam' -Password 'MySuperS3ct!pw!'
This works fine but you have to store this sensitive information somewhere and, when you pass the information, it’s all in clear text. If you’re writing PowerShell in Visual Studio Code (which you should), you’ll see a yellow squiggly line underneath the Password parameter telling you this is a no-no.
../images/501963_1_En_6_Chapter/501963_1_En_6_Fig1_HTML.jpg
Figure 6-1

Code linting

Instead of creating two parameters, just create one parameter that will securely hold both the username and password in the form of a PSCredential object. You can then decrypt and extract the username and password from the object using the GetNetworkCredential() method.
## somescript.ps1
[CmdletBinding()]
param(
    [Parameter()]
    [pscredential]$Credential
)
## You'll still have to decrypt the password here but you at least keep it
## secure for as long as you can
$cred = $Credential.GetNetworkCredential()
.someapp.exe $cred.UserName $cred.Password
Once the script has a Credential parameter and the code inside to extract the username and password, you can then create a PSCredential object and pass it securely to the function. In the following, Get-Credential is prompting the user for a username and password preventing the script from holding the sensitive information:
.somescript.ps1 -Credential (Get-Credential)

Tip Source: https://www.reddit.com/user/thedean_801/

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.223.125.219