Category Archives: Quick Learn

Push-Location’s Two for One

Sometimes, my life only has time for these short, little lessons.

Today, I learned something new, and without even thinking on it long, I went straight to my blog in order to share it. More or less, anyway. Before we get to the interesting part, let’s quickly discuss three PowerShell cmdlets: Set-Location, Push-Location, and Pop-Location.

Set-Location allows us to relocate ourselves within the file system. This is to say, that we can use this cmdlet to move around from folder to folder, and drive to drive. If I’m at the root of the C:\ drive, I can move to C:\Users, and if I’m in C:\Users and want to move to C:\Windows, I can also use Set-Location, or one of the aliases (cd, chdir, and sl), to get myself there. Here’s a quick example, before we move on.

PS C:\> Set-Location -Path C:\Users
PS C:\Users> Set-Location -Path C:\Windows
PS C:\Windows> Set-Location -Path \
PS C:\>

Push-Location’s purpose is to take our current location in the file system and add it to the location stack. We won’t delve into this too deeply, but picture it this way: It takes our current location in the file system — C:\, or C:\Users, or wherever — and puts it on a piece of paper, on top of a stack of other papers. Now we can reference our paper on top of the stack, to find our previous location the next time we need it.

And that, brings us to Pop-Location. Pop-Location gets the most recent entry on the location stack — that top piece of paper, if you will, and moves us to that location in the file system. Here’s an example of both Push-Location and Pop-Location.

PS C:\> # Our current location is the C:\ drive.
PS C:\> Push-Location
PS C:\> Set-Location -Path C:\Users
PS C:\Users> Pop-Location
PS C:\>

That introduction brings us to something I would’ve thought, I would have already known. Today I learned that Push-Location offers us a two-for-one. Not only will it place the current location on the location stack, as we’d expect, but it can also move us to a different location, such as Set-Location does. Watch.

PS C:\> # Back on the C:\ drive.
PS C:\> Push-Location -Path C:\Windows
PS C:\Windows> Pop-Location
PS C:\> Push-Location -Path C:\Users\tommymaynard
PS C:\Users\tommymaynard> Pop-Location
PS C:\>

With this tidbit of new information, I set out to replace the Set-Location cmdlet in my $PROFILE. Now when I use Set-Location — my new function — I’ll really be using Push-Location. Therefore, I can always return to the previous location in the filesystem with Pop-Location. Always.

Function Set-Location {
    Param (
       [string]$Path
    )
    Push-Location -Path $Path
}

PS C:\> # As you can see, I'm at the root of the C:\ drive.
PS C:\> Set-Location -Path C:\Windows
PS C:\Windows> Pop-Location
PS C:\>

Silent Install from an ISO

In the last several weeks, I’ve been having a great time writing PowerShell functions and modules for new projects moving to Amazon Web Services (AWS). I’m thrilled with the inclusion of UserData as a part of provisioning an EC2 instance. Having developed my PowerShell skills, I’ve been able to leverage them in conjunction with UserData to do all sorts of things to my instances. I’m reaching into S3 for installers, expanding archive files, creating folders, bringing down custom written modules in UserData and invoking the contained functions from them there, too. I’m even setting the timezone. It’s seems so straight forward sure, but getting automation and logging wrapped around that need, is rewarding.

As a part of an automated SQL installation — yes, the vendor told me they don’t support AWS RDS — I had a new challenge. It wasn’t overly involved by any means, but it’s worthy of sharing, especially if someone hits this post in a time of need, and gets a problem solved. I’ve said it a millions times: I often write, so I have a place to put things I may forget, but truly, it’s about anyone else I can help, as well. I’ve been at that almost three years now.

Back to Microsoft SQL: It’s on an ISO. I’ve been pulling down Zip files for weeks, in various projects, with CloudFormation, and expanding them, but this was a new one. I needed to get at the files in that ISO to silently run an installation. Enter the Mount-DiskImage function from Microsoft’s Storage module. Its help synopsis says this: “Mounts a previously created disk image (virtual hard disk or ISO), making it appear as a normal disk.” The command to pull just that help information is listed below.

PS > (Get-Help -Name Mount-DiskImage).Synopsis

As I typically do, I started working with the function in order to learn how to use it. It works as described. Here’s the command I used to mount my ISO.

PS > Mount-DiskImage -ImagePath 'C:\Users\tommymaynard\Desktop\SQL2014.ISO'

The above example doesn’t produce any output by default, and I rather like it that way. After a dismount — it’s the same above command with Dismount-DiskImage instead of Mount-DiskImage — I tried it with the -PassThru parameter. This parameter returns an object with some relevant information.

PS > Mount-DiskImage -ImagePath 'C:\Users\tommymaynard\Desktop\SQL2014.ISO' -PassThru

Attached          : False
BlockSize         : 0
DevicePath        :
FileSize          : 2606895104
ImagePath         : C:\Users\tommymaynard\Desktop\SQL2014.ISO
LogicalSectorSize : 2048
Number            :
Size              : 2606895104
StorageType       : 1
PSComputerName    :

The first thing I noticed about this output is that it didn’t provide the drive letter used to mount the ISO. I was going to need that drive letter in PowerShell, in order to move to that location and run the installer. Even if I didn’t move to that location, I needed the drive letter to create a full path. The drive letter was vital, and this, is why we’re here today.

Update: See the below post replies where Get-Volume is used to discover the drive letter.

Although the warmup here seemed to take a bit, we’re almost done here for today. I’ll drop the code below, and we’ll do a quick, line-by-line walk through.

# Mount SQL ISO and run setup.exe.
PS > $DrivesBeforeMount = (Get-PSDrive).Name
PS >
PS > Mount-DiskImage -ImagePath 'C:\Users\tommymaynard\Desktop\SQL2014.ISO'
PS >
PS > $DrivesAfterMount = (Get-PSDrive).Name
PS >
PS > $DriveLetterUsed = (Compare-Object -ReferenceObject $DrivesBeforeMount -DifferenceObject $DrivesAfterMount).InputObject
PS >
PS > Set-Location -Path "$DriveLetterUsed`:\"

Line 2: The first command in this series, stores the name property of all the drives in our current PowerShell session in a variable named $DrivesBeforeMount. That name should offer some clues.

Line 4: This line should look familiar; it mounts our SQL 2014 ISO (to a mystery drive letter).

Line 6: Here, we run the same command as in Line 2, however, we store the results in $DrivesAfterMount. Do you see what we’re up to yet?

Line 8:  This command compares our two recently created Drive* variables. We want to know which drive is there now, that wasn’t when the first Get-PSDrive command was run.

Line 10: And finally, now that we know the drive letter used for our newly mounted ISO, we can move there in order to access the setup.exe file.

Okay, that’s it for tonight. Now back to working on a silent SQL install on my EC2 instance.

PSMonday #48: March 27, 2017

Topic: The (Online) End of PowerShell Monday

For those following along online, as opposed to those at my workplace, this is the end of the road for PowerShell Monday. The following five weeks of PSMondays are work only, as they describe an advanced function template I’ve written, for those writing PowerShell tools in the office. The reason I’ve decided to end here, is that the posts may be a bit more work specific, and that I’ve actually already offered up this function for public consumption.

At work, we always wanted a standard logging function. Instead of writing that, I incorporated the logging ability into a function template that’s usable for any PowerShell tool we write. This means, that there’s no separate logging function for our functions. It’s built in, if you need it. Before I decided this is something we really should use at work, I actually shared it here first. You can already learn about it, see it for yourself, download it, and use it all you want. The way I accomplished this logging, is by using Write-Verbose to write to the host program, and to a log file, too.

Thanks to those that read the PSMonday series, or any of my posts, really.
http://tommymaynard.com/function-logging-via-write-verbose-2016/

Get the Verbs

So, earlier tonight, my wife, daughter, and I were on the couch. My wife was cleaning out her purse as my daughter helped her organize her change. Me, I was briefly on my phone and looking for a quick list of approved PowerShell verbs. As you likely know, if we create our own cmdlets and functions, we should use an approved verb for the verb, dash, singular noun naming convention: Get-Process, Set-ADUser, Checkpoint-Computer… okay, it’s not always a verb, but you know what I mean.

I couldn’t believe it; I couldn’t just find a simple list of all the verbs, close to one another, and without any explanations. You know the list, the one you basically get when you run Get-Verb. I suppose I should mention that all I had was my phone. There wasn’t a computer close by, and that’s, why I’m putting up a list of the approved verbs (as of PowerShell 5.1 on Windows 8.1), on my website.

Here’s the standard, Get-Verb output that includes the Name and Group. After that, I’ll include the same list, a few more ways. Maybe, just maybe it’ll be here when you, or I, need it next.

PS > Get-Verb

Verb        Group
----        -----
Add         Common
Clear       Common
Close       Common
Copy        Common
Enter       Common
Exit        Common
Find        Common
Format      Common
Get         Common
Hide        Common
Join        Common
Lock        Common
Move        Common
New         Common
Open        Common
Optimize    Common
Pop         Common
Push        Common
Redo        Common
Remove      Common
Rename      Common
Reset       Common
Resize      Common
Search      Common
Select      Common
Set         Common
Show        Common
Skip        Common
Split       Common
Step        Common
Switch      Common
Undo        Common
Unlock      Common
Watch       Common
Backup      Data
Checkpoint  Data
Compare     Data
Compress    Data
Convert     Data
ConvertFrom Data
ConvertTo   Data
Dismount    Data
Edit        Data
Expand      Data
Export      Data
Group       Data
Import      Data
Initialize  Data
Limit       Data
Merge       Data
Mount       Data
Out         Data
Publish     Data
Restore     Data
Save        Data
Sync        Data
Unpublish   Data
Update      Data
Approve     Lifecycle
Assert      Lifecycle
Complete    Lifecycle
Confirm     Lifecycle
Deny        Lifecycle
Disable     Lifecycle
Enable      Lifecycle
Install     Lifecycle
Invoke      Lifecycle
Register    Lifecycle
Request     Lifecycle
Restart     Lifecycle
Resume      Lifecycle
Start       Lifecycle
Stop        Lifecycle
Submit      Lifecycle
Suspend     Lifecycle
Uninstall   Lifecycle
Unregister  Lifecycle
Wait        Lifecycle
Debug       Diagnostic
Measure     Diagnostic
Ping        Diagnostic
Repair      Diagnostic
Resolve     Diagnostic
Test        Diagnostic
Trace       Diagnostic
Connect     Communications
Disconnect  Communications
Read        Communications
Receive     Communications
Send        Communications
Write       Communications
Block       Security
Grant       Security
Protect     Security
Revoke      Security
Unblock     Security
Unprotect   Security
Use         Other

As you may have noticed in the previous results, the default output is sorted by the Group. Here comes a second list; however, this list will be sorted by the Verb. I can already see how this may be helpful. In fact, this is the list I was probably after.

PS > Get-Verb | Sort-Object -Property Verb

Verb        Group
----        -----
Add         Common
Approve     Lifecycle
Assert      Lifecycle
Backup      Data
Block       Security
Checkpoint  Data
Clear       Common
Close       Common
Compare     Data
Complete    Lifecycle
Compress    Data
Confirm     Lifecycle
Connect     Communications
Convert     Data
ConvertFrom Data
ConvertTo   Data
Copy        Common
Debug       Diagnostic
Deny        Lifecycle
Disable     Lifecycle
Disconnect  Communications
Dismount    Data
Edit        Data
Enable      Lifecycle
Enter       Common
Exit        Common
Expand      Data
Export      Data
Find        Common
Format      Common
Get         Common
Grant       Security
Group       Data
Hide        Common
Import      Data
Initialize  Data
Install     Lifecycle
Invoke      Lifecycle
Join        Common
Limit       Data
Lock        Common
Measure     Diagnostic
Merge       Data
Mount       Data
Move        Common
New         Common
Open        Common
Optimize    Common
Out         Data
Ping        Diagnostic
Pop         Common
Protect     Security
Publish     Data
Push        Common
Read        Communications
Receive     Communications
Redo        Common
Register    Lifecycle
Remove      Common
Rename      Common
Repair      Diagnostic
Request     Lifecycle
Reset       Common
Resize      Common
Resolve     Diagnostic
Restart     Lifecycle
Restore     Data
Resume      Lifecycle
Revoke      Security
Save        Data
Search      Common
Select      Common
Send        Communications
Set         Common
Show        Common
Skip        Common
Split       Common
Start       Lifecycle
Step        Common
Stop        Lifecycle
Submit      Lifecycle
Suspend     Lifecycle
Switch      Common
Sync        Data
Test        Diagnostic
Trace       Diagnostic
Unblock     Security
Undo        Common
Uninstall   Lifecycle
Unlock      Common
Unprotect   Security
Unpublish   Data
Unregister  Lifecycle
Update      Data
Use         Other
Wait        Lifecycle
Watch       Common
Write       Communications

In this example, all I want to return is the verbs. Nine times out of 10, the group doesn’t make a difference to me.

PS > (Get-Verb | Sort-Object -Property Verb).Verb

Add
Approve
Assert
Backup
Block
Checkpoint
Clear
Close
Compare
Complete
Compress
Confirm
Connect
Convert
ConvertFrom
ConvertTo
Copy
Debug
Deny
Disable
Disconnect
Dismount
Edit
Enable
Enter
Exit
Expand
Export
Find
Format
Get
Grant
Group
Hide
Import
Initialize
Install
Invoke
Join
Limit
Lock
Measure
Merge
Mount
Move
New
Open
Optimize
Out
Ping
Pop
Protect
Publish
Push
Read
Receive
Redo
Register
Remove
Rename
Repair
Request
Reset
Resize
Resolve
Restart
Restore
Resume
Revoke
Save
Search
Select
Send
Set
Show
Skip
Split
Start
Step
Stop
Submit
Suspend
Switch
Sync
Test
Trace
Unblock
Undo
Uninstall
Unlock
Unprotect
Unpublish
Unregister
Update
Use
Wait
Watch
Write

This next example uses Format-Wide so that my results are in columns. Notice how the sorting goes across the rows, as opposed to down the columns. There’s should probably be a built-in way to handle that behavior. Yuck.

PS > Get-Verb | Sort-Object -Property Verb | Format-Wide -Column 4

Add                           Approve                       Assert                        Backup
Block                         Checkpoint                    Clear                         Close
Compare                       Complete                      Compress                      Confirm
Connect                       Convert                       ConvertFrom                   ConvertTo
Copy                          Debug                         Deny                          Disable
Disconnect                    Dismount                      Edit                          Enable
Enter                         Exit                          Expand                        Export
Find                          Format                        Get                           Grant
Group                         Hide                          Import                        Initialize
Install                       Invoke                        Join                          Limit
Lock                          Measure                       Merge                         Mount
Move                          New                           Open                          Optimize
Out                           Ping                          Pop                           Protect
Publish                       Push                          Read                          Receive
Redo                          Register                      Remove                        Rename
Repair                        Request                       Reset                         Resize
Resolve                       Restart                       Restore                       Resume
Revoke                        Save                          Search                        Select
Send                          Set                           Show                          Skip
Split                         Start                         Step                          Stop
Submit                        Suspend                       Switch                        Sync
Test                          Trace                         Unblock                       Undo
Uninstall                     Unlock                        Unprotect                     Unpublish
Unregister                    Update                        Use                           Wait
Watch                         Write

In the next example, I’ll try the -join operator.

PS > (Get-Verb | Sort-Object -Property Verb).Verb -join ', '
Add, Approve, Assert, Backup, Block, Checkpoint, Clear, Close, Compare, Complete, Compress, Confirm, Connect, Convert, C
onvertFrom, ConvertTo, Copy, Debug, Deny, Disable, Disconnect, Dismount, Edit, Enable, Enter, Exit, Expand, Export, Find
, Format, Get, Grant, Group, Hide, Import, Initialize, Install, Invoke, Join, Limit, Lock, Measure, Merge, Mount, Move,
New, Open, Optimize, Out, Ping, Pop, Protect, Publish, Push, Read, Receive, Redo, Register, Remove, Rename, Repair, Requ
est, Reset, Resize, Resolve, Restart, Restore, Resume, Revoke, Save, Search, Select, Send, Set, Show, Skip, Split, Start
, Step, Stop, Submit, Suspend, Switch, Sync, Test, Trace, Unblock, Undo, Uninstall, Unlock, Unprotect, Unpublish, Unregi
ster, Update, Use, Wait, Watch, Write

Also yuck. So yeah, I thought that might turn out better. Well, I’ve got it now — a place to find all the current verbs as of this writing, if I’m ever sitting on the couch, watching two of my favorite people, and unable to decide what verb to use for a new PowerShell tool.

PSMonday #47: March 20, 2017

Topic: Reusable Code V

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.

I know, I know, I said this would be a four part series, but we’re adding a fifth installment; it’s going to be worth it.

Now that we have a function we want to use whenever it makes sense, we need a place to put it. While we could dot source our Process.ps1 file every time we want to add the function to our scope (like we did last week), there’s two better options. The first option is the profile script. This is a .ps1 file that runs every time you open a new PowerShell ConsoleHost. There’s actually one for the ISE, too.

The below example shows how to determine where the profile script should be located for the current host program (the ConsoleHost).

$PROFILE

C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Now, just because we know the path and file, doesn’t mean the file actually exists. The next thing we should do is test the path to determine if the file exists, or not. Test-Path is a helpful cmdlet, in that it can determine if files and folders exist.

Test-Path -Path $PROFILE

False

If you received False when you run the above command, you don’t yet have a profile script. If you do have one, it’ll indicate True. For those with False, let’s quickly create a profile script and then open it.

New-Item -Path $PROFILE -ItemType File -Force

Once this command is run, it’ll indicate that it’s created the profile script file. It’s a simple text file, so it can be opened and edited a number of different ways. As of now, I tend to modify my profile script in the PowerShell ISE. To edit yours in the ISE, use the first below example. To use Notepad, use the second one. For those already using Visual Studio Code, you can open it there as well.

ise $PROFILE

notepad $PROFILE

With your profile script opened, you can add the function we finished with last Monday. It’s included below.

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."
    }
}

Now, if you’re following along, enter the below command.

Get-TMService -Service powershell_ise

This command should fail, whether or not, the PowerShell ISE is running. It’s because you added the function to your profile script after the profile script was last run. Close the PowerShell ConsoleHost and then open a new one. Doing this will ensure the recently added function is available in your PowerShell session. At this point, the above Get-TMService command should run successfully.

The second option is to use a PowerShell module. Creating a module can get much more complex, as can most things, but there’s just a few steps to make it work at its simplest form. A module is typically created to hold several related functions; however, it can only hold one function, if that’s all you have for now.

First, copy the above function into a text file, giving it an appropriate name, and saving it as a .psm1 file. For instance, call the new file MyFunctions.psm1 and paste the function code inside of it. Now, create a folder with the same name as the module file (without the file extension). This means your folder would be called MyFunctions. The name of the file and the folder must be the same. Now, move the file into the folder.

Next, we need to relocate this folder to a location in the filesystem where PowerShell will look for modules automatically. You can see those locations using the below example.

$env:PSModulePath -split ';'

C:\Users\tommymaynard\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

You’re welcome to use “C:\Program Files\WindowsPowerShell\Modules\” if you want all users on the computer to use the module. If you’re happy with just you being able to use your module, then place it in your profile folder. The final above path, in System32, shouldn’t be bothered if you can help it.

With this completed, the next time you open the PowerShell ConsoleHost you’re module, and that contained function, will be available. If you write some more functions, you can always add them to this same module file, just like you could your profile script.

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.

A Variable’s Current and Previous Value

How to Store a Variable’s Previous Value with the New Value

There was a recent post on the PowerShell Facebook group that said this: “Is there a function or option to check previous $Global:variable -ne Current $Global:variable?” The person that created the post wanted to know if there’s a way to recall the last value a variable stored, once it had already been assigned something new. Here’s my answer, and potential solution.

You have to think about a variable’s previous assignment, as if it never existed, even though you know that variable held something, or was assigned something, previously. Once its value is gone, it’s gone. Now, all that said, I came up with an option. The option, is to use a variable’s description property to store its previous value.

Let’s begin by checking the value of an uninitialized variable, and then assign it something.

PS > $x
PS > # No value.
PS > $x = 'first value'
PS > $x
first value

Now that our variable has a value, let’s take a look at all the variable’s properties using the Get-Variable cmdlet. Notice the Description property; we’re going to use this in a less than conventional way.

PS > Get-Variable -Name x | Select-Object -Property *

Name        : x
Description :
Value       : first value
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

Using the Set-Variable cmdlet, we’ll make two modifications to our variable, at nearly the same time. First, we’ll update the Description property, so that it’s holding the original variable assignment (the string ‘first value’). In the same, Set-Variable command, we’ll modify the current value of the variable to the string ‘second value.’ Notice in the final below command, that our Description property has a value now, too.

PS > Set-Variable -Name x -Value 'second value' -Description $x
PS > $x
second value
PS > Get-Variable -Name x | Select-Object -Property *

Name        : x
Description : first value
Value       : second value
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

In the next example, you’ll see how we can return the original value and the variable’s updated value. Beneath that, I’ve included a couple ways to compare these values. That gets back to what the person on Facebook was trying to determine. Again, this is fairly unconventional use of the Description property, but it does avoid the need for a second variable to hold the first variable’s original value. That said, one of my examples uses a second, comparison variable.

PS > (Get-Variable -Name x).Description
first value
PS > (Get-Variable -Name x).Value
second value
PS > $x
second value

PS > (Get-Variable -Name x).Description -ne (Get-Variable -Name x).Value
True
PS > (Get-Variable -Name x).Description -ne $x
True
PS > $y = (Get-Variable -Name x).Description
PS > $y -ne $x
True

In these last examples, we’re running into a bit of a problem, we are going to have to keep in mind (if anyone even dares use this approach). When we take a value that isn’t a string, and place it into the Description property, it becomes a string. That means, that when we take it back out, we’ll need to cast it back to its proper type. Take a look at the next series of examples for some assistance with this concept.

PS > $z = 5
PS > Set-Variable -Name z -Value 10 -Description $z
PS > Get-Variable -Name z | Select-Object -Property *

Name        : z
Description : 5
Value       : 10
Visibility  : Public
Module      :
ModuleName  :
Options     : None
Attributes  : {}

PS > (Get-Variable -Name z).Description | Get-Member |
>>> Select-Object -Property TypeName -Unique

TypeName
--------
System.String

PS > [int](Get-Variable -Name z).Description
5
PS > [int](Get-Variable -Name z).Description | Get-Member |
>>> Select-Object -Property TypeName -Unique

TypeName
--------
System.Int32

This goes for decimals values, too. If you use the Description property to store something that’s not a string, then you’re going to have to properly cast it when you’re taking it out, or you’re going to have a string. In these next examples, I used the GetType method to determine the value’s type, as opposed to Get-Member and Select-Object used above.

PS > $m = 2.5
PS > $m.GetType().Name
Double
PS > Set-Variable -Name m -Value 3.5 -Description $m
PS > $m
3.5
PS > $m.GetType().Name
Double
PS > (Get-Variable -Name m).Description
2.5
PS > (Get-Variable -Name m).Description.GetType().Name
String
PS > [double](Get-Variable -Name m).Description
2.5
PS > ([double](Get-Variable -Name m).Description).GetType().Name
Double

And that’s it. It’s may not be the first choice for saving a previous variable’s value, but it’s a choice. I rather liked the array option that was recommended; however, like the $Error array, I’d be tempted to put the newest/current value of a variable into index 0 and not at the end of the array. Anyway, back to real life now.

PSMonday #45: March 6, 2017

Topic: Reusable Code III

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.

Back again.

We’ve made two new changes to the below code. One, we replaced powershell_ise, inside of our Get-Process command, with $Service. Two, we added a new, first line to our code. This command will prompt the user to enter a value. Read-Host will then assign that value to the $Service variable. That value will be used in the remainder of the PowerShell code every place $Service is found (there’s only the one). Now, the code is no longer only good for checking for the powershell_ise process; it’ll check for whatever process is entered.

$Service = Read-Host -Prompt 'Enter a Process Name'
    Get-Process -Name $Service |
        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"}}

If you were to open notepad, you could use this code to return its process information. So let’s do that; let’s say we have notepad open and we run our code.

Enter a Process Name:

Enter a Process Name: notepad

Process Name        : notepad
Description         : Notepad
Company             : Microsoft Corporation
Shared Memory       : 9 MB
Private Memory Size : 1 MB

Our few lines of code just became useable against any running process on the computer. To be as thorough as possible, we need to consider what happens when we enter a process that isn’t actually running — what’s it going to do? Let’s close notepad and find out.

Enter a Process Name: notepad

Get-Process : Cannot find a process with the name "notepad". Verify the process name and call the cmdlet again.
At line:3 char:1
+ Get-Process -Name $Service |
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (notepad:String) [Get-Process], ProcessCommandException
+ FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

Get-Process doesn’t handle this gracefully, so let’s make a few more changes. We’ll first add a try-catch. The try portion will wrap the Get-Process and Select-Object commands. While it isn’t always necessary, we need to add the ErrorAction parameter name, with the Stop parameter value to Get-Process. This is required in order to prevent this error from being displayed. Forcing a terminating error isn’t always required, so don’t add it to commands when it’s not necessary. You try it without -ErrorAction. If it doesn’t work, you try -ErrorAction SilentlyContinue, and if that doesn’t work, you use -ErrorAction Stop, as we’ve done here.

The catch portion of our try-catch wraps a newly added, Write-Warning command that will gracefully indicate when a process isn’t running, or perhaps, just wasn’t spelled correctly.

$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."
}


Here’s what happens now, when you enter a process that isn’t running, or doesn’t even exist.


Enter a Process Name: notepad
WARNING: Cannot locate the notepad process.

Enter a Process Name: asdf
WARNING: Cannot locate the asdf process.

That’s it for this Monday. Keep paying attention; it’s about to get good.

PSMonday #44: February 27, 2017

Topic: Reusable Code II

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.

In today’s PSMonday, we’ll jump right back into calculated properties, as we continue to create some reusable code. Last week our final addition was to use a calculated property to modify the name of a property, as can be seen below. We took its default value of “Name,” and changed it to “Process Name.”

Get-Process -Name powershell_ise |
    Select-Object -Property @{Name='Process Name';Expression={$_.Name}},Description,Company,WorkingSet,PrivateMemorySize

Today we’ll add two more calculated properties, but before we do, let’s reformat this code a bit, so it fits better. First, we’re going to use “N” instead of Name and “E” instead of Expression, for the keys in our calculated property hash table. Second, we’ll take advantage of the commas between our  properties which allows each of them to sit on their own line. We could have put a space after each comma and left them on the same line as the first property, and it would have wrapped them for us; however, we’re about to add those other calculated properties.

Get-Process -Name powershell_ise |
    Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
        Company,
        WorkingSet,
        PrivateMemorySize

The WorkingSet and PrivateMemorySize values are being reported in bytes. Let’s modify that, beginning with the WorkingSet property. Take a look at the newest changes, and we’ll discuss them after the example and its results.

Get-Process -Name powershell_ise |
    Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
         Company,
        @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
        PrivateMemorySize

Process Name      : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
Shared Memory     : 420 MB
PrivateMemorySize : 405536768

As you can see in the above example, we first changed the term “WorkingSet” to “Shared Memory.” This time, however, we also modify the value it displays using the Expression key-value pair. We won’t go into this too deep, other than to say we first, divided our Working Set value by 1 MB, as we wanted to report our values in megabytes. Second, we directly accessed .NET to round our value using a Round method, and finally, we added the string “MB” to the end of the value to make it clear what measurement we’re using.

Next, we’ll add a third calculated property, but this time to the PrivateMemorySize property. This will allow it to function just like the modified WorkingSet property. As you can likely tell, I’ve used more lines than is necessary, in order that this best fits.

Get-Process -Name powershell_ise |
    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"}}

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

Okay, let’s stop here and pick up from this point next Monday.

PSMonday #43: February 20, 2017

Topic: Reusable Code I

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.

After I type the same thing a few times, and suspect I’ll need, or want, to continue to do that, I’ll typically write a function for it, so that I never have to type it again. That’s the purpose of a function. It’s a container for a series of commands that I can run, or invoke, simply by entering its name. Today we’ll start a four-part PSMonday on how to reuse code from beginning to end. As we close up PSMonday, these last several are going to be the ones in which to really pay attention.

Let’s start by entering a simple, Get-Process command, in order to determine whether or not the PowerShell ISE — the PowerShell Integrated Scripting Environment — is currently running.

Get-Process -Name powershell_ise | Select-Object -Property ProcessName,Id

ProcessName      Id
-----------      --
powershell_ise 5732

In the above example, we used Select-Object to filter the properties that are returned by Get-Process. The ISE is running, otherwise we would’ve received an error, indicating that the process could not be found.

Next, let’s pipe the results of an unfiltered, Get-Process command to a mildly modified Get-Member command, and take a look at all the properties of the returned object.

Get-Process -Name powershell_ise | Get-Member -MemberType Property

Here’s another filtered Get-Process command, that only includes the properties that hold information in which I am interested.

Get-Process -Name powershell_ise |
    Select-Object -Property Name,Description,Company,WorkingSet,PrivateMemorySize

Name              : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
WorkingSet        : 162627584
PrivateMemorySize : 153522176

Let’s make a change to the Name property using what’s called a calculated property. Among other things, a calculated property allows us to rename a property’s default name to something else.

Get-Process -Name powershell_ise |
    Select-Object -Property @{Name='Process Name';Expression={$_.Name}},Description,Company,WorkingSet,PrivateMemorySize

Process Name      : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
WorkingSet        : 162324480
PrivateMemorySize : 153812992

The calculated property looks like this, when it’s all by itself.

@{Name='Process Name';Expression={$_.Name}}

Notice in the above results that Name property is now named, Process Name. The calculated property consists of a hash table, as signified by @{}, that contains two, key-value pairs. One pair includes the Name key, and the second key-value pair includes an Expression key. We’ll continue next week with some additional examples of calculated properties, as we continue to learn about creating reusable code.