PSMonday #46: March 13, 2017

Topic: Reusable Code IV

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

The below example is the same one we ended on last Monday. What I need you to imagine is that this code now exists in a file called Process.ps1, on the desktop of your own computer. You can even put it there if you want to follow along.

$Service = Read-Host -Prompt 'Enter a Process Name'

try {
    Get-Process -Name $Service -ErrorAction Stop |
        Select-Object -Property @{N='Process Name';E={$_.Name}},
            Description,
            Company,
           @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
           @{N='Private Memory Size';
               E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}
} catch {
    Write-Warning -Message "Cannot locate the $Service process."
}

In the next example, we’ll begin by changing directories to the desktop. Once there, we’ll run our Process.ps1 file that contains the code we’ve been constructing over the last few weeks. As you’re likely aware, changing directories — the first below command — isn’t necessary, had we simply supplied the full path to the Process.ps1 file in the second command.

PS > Set-Location -Path C:\Users\tommymaynard\Desktop

PS > .\Process.ps1
Enter a Process Name: powershell_ise

Process Name : powershell_ise
Description : Windows PowerShell ISE
Company : Microsoft Corporation
Shared Memory : 181 MB
Private Memory Size : 159 MB

PS > # FYI: Notepad is not currently running.
PS > .\Process.ps1
Enter a Process Name: notepad
WARNING: Cannot locate the notepad process.

After instructing the .ps1 to execute, it immediately prompts us to enter a process name just as we’ve seen in the past. If we enter something that’s running, it returns our modified information according to the command we’ve written. If it’s not running, it displays the Write-Warning message as opposed to throwing the red, and sometimes intimidating, error message we saw last week. What an easy way to return only what you want, and without the need to rewrite the code each time.

Here’s the thing: People love the Read-Host cmdlet, but there’s a better way. Let’s use a Param block instead. This next example does just that. As you move into writing functions, you’ll quickly begin to understand why parameters are better. So you can continue to picture this, we’re saving the below modification over what we had in the Process.ps1 file on the desktop. Watch how we run it now!

Param (
    [string]$Service
)
 
try {
    Get-Process -Name $Service -ErrorAction Stop |
        Select-Object -Property @{N='Process Name';E={$_.Name}},
            Description,
            Company,
            @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
            @{N='Private Memory Size';
                E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}
} catch {
    Write-Warning -Message "Cannot locate the $Service process."
}
 
PS > .\Process.ps1 -Service notepad
WARNING: Cannot locate the notepad process.
 
PS > .\Process.ps1 -Service powershell_ise
 
 
Process Name        : powershell_ise
Description         : Windows PowerShell ISE
Company             : Microsoft Corporation
Shared Memory       : 178 MB
Private Memory Size : 147 MB

Now, instead of the code using Read-Host to prompt us for input, we have a -Service parameter we can use. When the code was invoked, it took the parameter value we supplied and used it to set the $Service variable. There’s one problem here, however. If we don’t enter the Service parameter, the function will still try and check for a service that wasn’t even included. Let’s make the Service parameter mandatory; we can do that with one quick addition to our code.

Param (
    [Parameter(Mandatory=$true)]
    [string]$Service
)
 
try {
    Get-Process -Name $Service -ErrorAction Stop |
        Select-Object -Property @{N='Process Name';E={$_.Name}},
            Description,
            Company,
            @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
            @{N='Private Memory Size';
                E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}
} catch {
    Write-Warning -Message "Cannot locate the $Service process."
}

Now, if we forget the Service parameter value, it’ll prompt us to enter it. I had to manually enter “powershell_ise” when it prompted me on the fourth line down, where it says “Service:”.

PS > .\Process.ps1
cmdlet Process.ps1 at command pipeline position 1
Supply values for the following parameters:
Service: powershell_ise
 
 
Process Name        : powershell_ise
Description         : Windows PowerShell ISE
Company             : Microsoft Corporation
Shared Memory       : 188 MB
Private Memory Size : 156 MB

The ultimate goal of someone that really wants to learn PowerShell well, is to lose the whole I-write-scripts mentality, and embrace an I-write-functions mentality. Let’s take our code and make it a function; there’s literally two changes.

We add a new first line that uses the Function keyword. Following that, we give our function a name and add an open, curly brace. Remember as you create functions, to use approved verbs (see Get-Verb for the approved list), a dash, and a singular noun, or singular nouns. You might also add a prefix to the noun, too, such as I’ve done by adding “TM.” This ensures my function won’t take precedence over the Microsoft written and included Get-Service cmdlet (that we actually use inside the Get-TMService function).

Function Get-TMService {
    Param (
        [Parameter(Mandatory=$true)]
        [string]$Service
    )
 
    try {
        Get-Process -Name $Service -ErrorAction Stop |
            Select-Object -Property @{N='Process Name';E={$_.Name}},
                Description,
                Company,
                @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
                @{N='Private Memory Size';
                    E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}
    } catch {
        Write-Warning -Message "Cannot locate the $Service process."
    }
}

So it’s been said, we’ve again taken the above code and saved it over what we had in Process.ps1. Here’s the trick with using functions that are saved in .ps1 files. To add the Get-TMService function to our PowerShell session, we need to dot source the file. This will make the function available for use. The second below command has a dot and a space before the path and the file name (. .\Process.ps1). That dot is vital, so don’t overlook it. Let’s start by ensuring the function doesn’t yet exist.

PS > Get-Command -Name Get-TMService
Get-Command : The term 'Get-TMService' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again...
 
PS > . .\Process.ps1
 
PS > Get-Command -Name Get-TMService
 
CommandType     Name             Version    Source     
-----------     ----             -------    ------     
Function        Get-TMService
 
 
PS > Get-TMService -Service powershell_ise
 
 
Process Name        : powershell_ise
Description         : Windows PowerShell ISE
Company             : Microsoft Corporation
Shared Memory       : 198 MB
Private Memory Size : 174 MB

Had you read this and the previous three lessons, you’ve seen how we started with a simple command and turned it into a tool that we can use whenever it’s needed. If you give PowerShell some time now, you’re going to get your time back in the future. Until next week.

Leave a Reply

Your email address will not be published. Required fields are marked *