Category Archives: Script Sharing

Read-Host with a Previous Value Part II

It’s simply not everyday that Rob Sewell (@sqldbawithbeard) places a link on Twitter to your blog. Yours, as in mine, but here we are. That was yesterday.

Today, knowing this is out there, I decided to add a bit more to this short post with a second installment. In this post, I’ve written a few changes to the single If statement from Part I. While it was a simple statement to see if I could reuse a previously stored value in the first installment, it’s a bit more full featured now.

We’re getting away from statically setting the default user. We’ll actually let the user assign that value. This determination — whether there’s a default user or not — defines our prompt message, which is most of what’s been added.

If ($User) {
    $Prompt = "Press Enter for the last user [$User], or enter a new user"
} Else {
    $Prompt = "Please enter a new user"
}

If (($Result = Read-Host -Prompt $Prompt) -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
    $User = $Result
}

I won’t bother including any examples of the running code, but do run the example yourself if you’re interested. Remember to clear or remove the $User, $Prompt, and $Result variables if you want to start fresh. The following command will remove those variables, if you find you need it before a new run.

Remove-Variable -Name User,Prompt,Result -ErrorAction SilentlyContinue

And that’s it. Thanks for the link to the blog, Rob!

Read-Host with a Previous Value

Update: There’s a Part II, read it next.

Here’s a quick one. For an upcoming project, I decided I may need a way to use Read-Host in combination with a previously set, default value. Here’s the working code I threw together in case it’s needed.

$User = 'tommymaynard'

If (($Result = Read-Host -Prompt "Press Enter for the last user [$User], or enter a new user") -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
}

I set my user to “tommymaynard,” and then the If statement executes. The execution pauses while the Read-Host cmdlet waits for my input. If I press Enter, the If portion runs indicating that I’m using the previously set value of “tommymaynard.” It’ll do that if I enter tommymaynard again, too. If I enter a new value, it indicates I’m using the new value.

While we’re here, let’s run the code with three possible values: nothing entered, tommymaynard entered, and a different user entered.

Press Enter for the last user [tommymaynard], or enter a new user:
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: tommymaynard
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: lsmith
You’re using the previously unused “lsmith” user.

And there it is, a super short post about using a previously set value for Read-Host.

Measure the Comment-Based Help Synopsis

There was a recent tweet — I believe it was a tweet, anyway — that indicated that a comment-based help synopsis, should only be as long as what’s allowable for a tweet. That’s 140 characters. With that in mind and a few minutes, I wrote this little function to check the character count in a string.

Function Measure-CharacterCount {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [string]$String,

        [int]$CharacterCount = 140
    )
    $StringCount = $String.ToCharArray().Count

    If ($StringCount -le $CharacterCount) {
        Write-Verbose -Message "The string is equal to or less than $CharacterCount characters ($StringCount)." -Verbose
    } Else {
        Write-Warning -Message "The string is longer than $CharacterCount characters ($StringCount)."
    }
}

In the example below, I’ve included two commands: one where the character count is less than 140 and one where it’s greater. On that note, let me add a third where the character count equals the default 140 character, and then 141, just to make sure this thing works…

PS > Measure-CharacterCount -String 'Today is awesome, maybe.'
VERBOSE: The string is equal to or less than 140 characters (24).
PS >
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe.'
WARNING: The string is longer than 140 characters (149).
PS > 
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesom'
VERBOSE: The string is equal to or less than 140 characters (140).
PS >
PS > Measure-CharacterCount -String 'Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome, maybe. Today is awesome'
WARNING: The string is longer than 140 characters (141).

Add AccessKey and SecretKey Parameters to AWS Function

I’m spending more, and more, time with AWS. As a PowerShell enthusiast, I’ve therefore kept myself busy during a few of the week and weekend evenings. And sometimes during the day, too, when it makes sense.

As of today, I’ve written four AWS specific functions for work. It’s everything from comparing my version of the AWSPowerShell module with the one in the PowerShell Gallery, returning the AWS service noun prefixes (EC2, CFN, etc.), returning the EC2 instance type counts, and then there’s the newest one.

My most recent function can create an RDP (.rdp) file to connect to an EC2 instance (it gets the EC2’s IP address and places that in an RDP file). As I wrote up an email to share the function with a coworker — the one that mentioned Azure having an option to download RDP files — it occurred to me that none of my functions include an option to provide AccessKey and SecretKey parameters. All of them, that need it (two of them now), rely on persistent credentials having been saved.

Well, I’ll be going to go back through these two functions and add a way to accept an AccessKey and SecretKey. First, however, was to figure out how. I literally typed “PowerShell add AccessKey and SecretKey to function” into Google (because I was being lazy,… and didn’t feel the need to write something that must surely be out there). Well, there wasn’t anything I could use, so I’ve done the work myself. Now, if anyone else types that, or something like that into Google, or Bing, maybe they’ll get this answer.

Before I consider moving this to an existing function, I needed to make it work. I did just that, using parameter sets. I’ve long know about them, but haven’t really had a need for them in more than a few instances. This of course led me to view the parameter sets on an AWS cmdlet. Ugh, they aren’t even parameter sets on something like Get-EC2Instance. Try it yourself: Show-Command -Name Get-EC2Instance. I suspect there’s internal logic in the cmdlet itself to handle whether or not an AccessKey and SecretKey have been provided. Don’t have persistent, shell credentials: error*. Only provide an AccessKey: error*. Only provide a SecretKey: error*. Well, I went with parameter sets anyway.

* “No credentials specified or obtained from persisted/shell defaults.”

Here’s the complete function used to work through this desire. Take a look and continue reading about it further below.

Function Test-AWSParameterSets {
    [CmdletBinding(DefaultParameterSetName='NoAccessKeys')]
    Param(
        [Parameter(ParameterSetName='NoAccessKeys',Mandatory = $true)]
        [Parameter(ParameterSetName='AccessKeys',Mandatory=$true)]
        [String]$InstanceID,
 
        [Parameter(ParameterSetName='AccessKeys',Mandatory=$true)]
        [string]$AccessKey,
 
        [Parameter(ParameterSetName='AccessKeys',Mandatory=$true)]
        [string]$SecretKey

    )

    $PSBoundParameters
    '-----------------------------------------'
    "InstanceID: $InstanceID"
    "AccessKey: $AccessKey"
    "SecretKey: $SecretKey"
    "ParameterSet: $($PSCmdlet.ParameterSetName)"
}

In this first example, we’ll run the function without supplying any parameter names, or values. Because the InstanceID parameter is mandatory, and the NoAccessKeys parameter set is the default, the PowerShell engine prompts me to enter a value for InstanceID. After I entered i-1234554321, the function returns the $PSBoundParameters hash tables. This include the parameter names and values that were supplied to the function at run time. Additionally, it returns the values for the InstanceID, AccessKey, SecretKey, and which of the two parameter sets were used: NoAccessKeys or AccessKeys.

PS > Test-AWSParameterSets
cmdlet Test-AWSParameterSets at command pipeline position 1
Supply values for the following parameters:
InstanceID: i-1234554321

Key        Value       
---        -----       
InstanceID i-1234554321
-----------------------------------------
InstanceID: i-1234554321
AccessKey: 
SecretKey: 
ParameterSet: NoAccessKeys

The next example includes an InstanceID parameter when the function is invoked. Therefore, I’m not prompted to enter any additional information. It produces the same output as it did above.

PS > Test-AWSParameterSets -InstanceID i-1234554321

Key        Value       
---        -----       
InstanceID i-1234554321
-----------------------------------------
InstanceID: i-1234554321
AccessKey: 
SecretKey: 
ParameterSet: NoAccessKeys

This below example includes both an InstanceID parameter and an AccessKey parameter when the function is invoked. The moment the AccessKey parameter name was included, we switched to using the AccessKeys parameter set. Therefore, I was prompted to enter a SecretKey parameter value, as it’s a required parameter in that parameter set.

PS > Test-AWSParameterSets -InstanceID i-1234554321 -AccessKey aacccceesssskkeeyy

cmdlet Test-AWSParameterSets at command pipeline position 1
Supply values for the following parameters:
SecretKey: ssseeecccrrreeetttkkkeeeyyy

Key        Value                      
---        -----                      
InstanceID i-1234554321               
AccessKey  aacccceesssskkeeyy         
SecretKey  ssseeecccrrreeetttkkkeeeyyy
-----------------------------------------
InstanceID: i-1234554321
AccessKey: aacccceesssskkeeyy
SecretKey: ssseeecccrrreeetttkkkeeeyyy
ParameterSet: AccessKeys

This is basically the opposite of the above example. I included the SecretKey parameter and it prompted me to enter the AccessKey parameter — we can’t have one without the other; they’re both mandatory in the AccessKeys parameter set.

PS > Test-AWSParameterSets -InstanceID i-1234554321 -SecretKey ssseeecccrrreeetttkkkeeeyyy

cmdlet Test-AWSParameterSets at command pipeline position 1
Supply values for the following parameters:
AccessKey: aacccceesssskkeeyy

Key        Value                      
---        -----                      
InstanceID i-1234554321               
SecretKey  ssseeecccrrreeetttkkkeeeyyy
AccessKey  aacccceesssskkeeyy         
-----------------------------------------
InstanceID: i-1234554321
AccessKey: aacccceesssskkeeyy
SecretKey: ssseeecccrrreeetttkkkeeeyyy
ParameterSet: AccessKeys

The final example includes values for all the parameter names at the time the function is invoked. If someone wasn’t using a persistent set of credentials, then this is how you might expect an AWS function you’ve written to be used.

PS > Test-AWSParameterSets -InstanceID i-1234554321 -AccessKey aacccceesssskkeeyy -SecretKey ssseeecccrrreeetttkkkeeeyyy

Key        Value                      
---        -----                      
InstanceID i-1234554321               
SecretKey  ssseeecccrrreeetttkkkeeeyyy
AccessKey  aacccceesssskkeeyy         
-----------------------------------------
InstanceID: i-1234554321
AccessKey: aacccceesssskkeeyy
SecretKey: ssseeecccrrreeetttkkkeeeyyy
ParameterSet: AccessKeys

Well, that’s the first part of the challenge. Now to incorporate what I’ve done here, into the functions that need it. Maybe, I’ll be back with that.

Extract Media Folder from PowerPoint Files

On Wednesday, I wrote a response to something on Stack Overflow. So I don’t have to chase it down one day, I though I’d briefly discuss it here and include the updated code I would be more likely to use, providing I ever need to use it.

If you open a PowerPoint .pptx file in something like 7-Zip, you’ll quickly realize that the .pptx is a compressed, or archived, file format. While this brings down the file size (1.65MB file vs. 3.27MB when expanded), this is more more to say that there are a few folders, and several files, that make up a single PowerPoint file.

A person on Stack Overflow wanted to automate the expansion of a series of .pptx files and extract the media folder. This folder holds all the images in a PowerPoint file. I’m not going to bother to explain the code, but let me set the stage. I created a folder on my desktop called pptx. Inside the folder were four PowerPoint files using the .pptx file extension. My code created a new folder for each file in the pptx folder, expanded each PowerPoint file in their own new folders, located the nested media folder, moved it to the top level of their new folder, and then deleted all the other folders and files inside the new folder. If there wasn’t a media folder, it would still create the new folder; however, it would create a single text file in there called “No media folder.txt.” This was to help ensure the user didn’t think the code didn’t work, when they didn’t find the media folder inside the new folder.

I’ll included the original code from the forum post further below, but for now here’s an updated and cleaner version.

$Path = 'C:\users\tommymaynard\Desktop\pptx'
$Files = Get-ChildItem -Path $Path

Foreach ($File in $Files) {
    New-Item -Path $File.DirectoryName -ItemType Directory -Name $File.BaseName | Out-Null
    $NewFolder = $File.FullName.Split('.')[0]

    Add-Type -AssemblyName System.IO.Compression.FileSystem
    [System.IO.Compression.ZipFile]::ExtractToDirectory($File.FullName,$NewFolder)
    
    If (Test-Path -Path "$($NewFolder)\ppt\media") {
        Move-Item -Path "$($NewFolder)\ppt\media" -Destination $NewFolder
        Get-ChildItem -Path $NewFolder | Where-Object {$_.Name -ne 'media'} | Remove-Item -Recurse

    } Else {
        Get-ChildItem -Path $NewFolder | Remove-Item -Recurse
        New-Item -Path $NewFolder -ItemType File -Name 'No media folder.txt' | Out-Null
    }
}

And, here’s the original code and a link to the post itself. This below version didn’t use the $NewFolder variable, so there’s overwhelming inclusion of this piece of code: $File.FullName.Split(‘.’)[0]. It also defaulted to use the Expand-Archive cmdlet, if that was available. As I mentioned in the Stack Overflow post itself, it’s so slow. I’ve removed that so that it’s much faster now, which reminded me, I’ve actually written about this time difference before. It was just last October.

$Path = 'C:\users\tommymaynard\Desktop\pptx'
$Files = Get-ChildItem -Path $Path

Foreach ($File in $Files) {
    New-Item -Path $File.DirectoryName -ItemType Directory -Name $File.BaseName | Out-Null

    If (Get-Command -Name Expand-Archive) {
        Expand-Archive -Path $File.FullName -OutputPath $File.FullName.Split('.')[0]

    } Else {
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory($File.FullName,$File.FullName.Split('.')[0])
    } # End If-Else.

    If (Test-Path -Path "$($File.FullName.Split('.')[0])\ppt\media") {
        Move-Item -Path "$($File.FullName.Split('.')[0])\ppt\media" -Destination $File.FullName.Split('.')[0]
        Get-ChildItem -Path $File.FullName.Split('.')[0] | Where-Object {$_.Name -ne 'media'} | Remove-Item -Recurse

    } Else {
        Get-ChildItem -Path $File.FullName.Split('.')[0] | Remove-Item -Recurse
        New-Item -Path $File.FullName.Split('.')[0] -ItemType File -Name 'No media folder.txt' | Out-Null
    } # End If-Else.
} # End Foreach.

Modify the Time Zone Without tzutil.exe

Note: This post has been updated below (3/9/2017).

Earlier today (2/27/2017), I wanted a purely PowerShell answer to setting the time zone. That meant that I didn’t want to rely on tzutil.exe, as I had forever now. The best I could do was to use PowerShell with the registry, in order to update the TimeZoneKeyName key. So I can refer to the code, as I’ll forget it soon enough, I thought I’d place it here with the possibility it’ll also be helpful for someone else, someday.

I do want to mention that this method would require elevation to a local administrator, as would any modification to HKLM. Using tzutil.exe, however, does not include this same restriction. On that alone, I may actually stick with the Windows Time Zone (command line) Utility.

$RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
$RegistryKeyName = 'TimeZoneKeyName'
$RegistryValue = 'US Mountain Standard Time'
Set-ItemProperty -Path $RegistryPath -Name $RegistryKeyName -Value $RegistryValue


Update
: I saw a recent Tweet from Jeffery Hicks about the newer, PowerShell 5.1 function, Get-ComputerInfo. I ran it, as I have at least once before, and ended up staring right at a TimeZone property. So with that, I jumped back to this post to do an update. I think it’s coming…

Convert Inches to Feet, and Inches

My blog serves several purposes. One of which I’ve mentioned before, is to keep things around I may want later. That’s why we’re back today. While helping on a TechNet forum post, I wrote the below function.

This function’s purpose is to convert inches between 56 and 76 to their <feet>'<inches>” equivalent. This means if you send in 56, you’ll get back 4’8″, if you send in 60 you’ll get 5’0″, if it’s 73 you’ll get 6’1″. The reason it’ll only take 56 to 76 inches is due to the initial requirements in the forum post. If you had a purpose for this function and wanted it to accept more or less inches, you’d have to modify the ValidateRange attribute to different minimum and maximum values, or remove it altogether. If you can use this function, then here it is.

Function Convert-InchToFeetInch {
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateRange(56,76)]
        [int]$Inches
    )

    $TotalFeet = [int](($Inches / 12).ToString().Split('.')[0])
    $RemainingInches = $Inches - ($TotalFeet * 12)

    "$TotalFeet'$RemainingInches`""
}

This first example will loop through all the possible values from 56 to 76 using the ForEach-Object cmdlet. As it does that, it’ll write out the number of inches it’s converting, as well as the feet and remaining inches in the aforementioned format.

56..76 | ForEach-Object {
    "$_ inches is $(Convert-InchToFeetInch -Inches $_)"
}

56 inches is 4'8"
57 inches is 4'9"
58 inches is 4'10"
59 inches is 4'11"
60 inches is 5'0"
61 inches is 5'1"
62 inches is 5'2"
63 inches is 5'3"
64 inches is 5'4"
65 inches is 5'5"
66 inches is 5'6"
67 inches is 5'7"
68 inches is 5'8"
69 inches is 5'9"
70 inches is 5'10"
71 inches is 5'11"
72 inches is 6'0"
73 inches is 6'1"
74 inches is 6'2"
75 inches is 6'3"
76 inches is 6'4"

These next examples simply use the function outside a looping construct. You provide a number of inches to the Inches parameter, and the function will return the converted value. That’s it. Maybe it helps you, and maybe I can find it if I ever need it.

Convert-InchToFeetInch -Inches 56 # 4'8"
Convert-InchToFeetInch -Inches 60 # 5'0"
Convert-InchToFeetInch -Inches 68 # 5'8"
Convert-InchToFeetInch -Inches 73 # 6'1"
Convert-InchToFeetInch -Inches 76 # 6'4"

4'8"
5'0"
5'8"
6'1"
6'4"

Get-TMVerbSynonym 1.4

Notes: Download link at the bottom of this post.

One of my favorite PowerShell community members, June Blender, posted a question on Twitter. While it wasn’t directed at me, I couldn’t resist. She wanted the approved verb options for the verbs “populate”, “fill in”, or “load”. I couldn’t help myself, because I wrote a function for this very task!

The Get-TMVerbSynonym function’s purpose is to find synonyms for verbs and return whether they’re approved, or not. Well, her Tweet was all it took to finally make some overdue changes, to include getting it posted on the PowerShell Gallery. As I called it in one of my follow up tweets, when June asked where it was then published, I said it was a “TechNet leftover.”

Well, not anymore.

It’s since been updated to version 1.4 and placed on the PowerShell Gallery. The above image was what I posted when it was in version 1.3. This means that it doesn’t reflect two of the newest, view able changes. The Verb property now indicates the verb it’s checking, and the old Verb property has been renamed to Synonym. This was included to support some other potential updates for an even newer version. Anyway, this means it’s up to five properties, so pipe to Format-Table -AutoSize in order that the results can be easily read.

Additionally, it now includes something I always wanted it to have: an Approved, switch parameter. This means there’s no piping to Where-Object to filter the function to only show the approved synonyms. Here’s an example of the function in action, both with, and without the Approved, switch parameter.

Download it now manually, or use Install-Script -Name Get-TMVerbSynonym.

The 1 to 100 Game, Updated

Notes: Download link at the bottom of this post.

My just turned, five-year-old daughter was introduced to my PowerShell 1 to 100 game around two years ago. As I watched her play, I always thought I should make some changes to the game. Well today, I have a newer version, that I’d like to briefly share, and discuss.

First off, you may need to know how to play the 1 to 100 game. It was common in my household as a child, but it’s possible it wasn’t in everyone’s. It works this way: The computer — the function, more or less — chooses a random number between 1 and 100 and you have to try and guess the number. If you choose a number lower than the computer’s number, it will tell you to choose a higher number. If you choose a number higher than the computer’s number, it will tell you to choose a lower number. You continue to do this, until you guess the correct number.

What I always thought was necessary for this game was to make it possible to choose whether you even want to use 1 and 100. This 3.0 version of the function will allow you to choose the minimum and maximum numbers. That way, littler kids can play 1 to 10, or 1 to 20. Maybe your kiddo is learning their fifties: You could even play 50 to 59, if you desired. Maybe you have a little downtime while you’re waiting for a server restart; you can play it too. There’s some value here, but not perhaps the value you might typically expect with a PowerShell function. It does have an example of thrice nested Do-While loops. That’s how I pushed the previous version. Skip downloading this 2.0 version, and use the bottom link for version 3.0.

In the older version, it showed the aggregate totals by default. Now, you have to include the ShowTotals switch parameter, if you want to see that collected information. You probably do, if you’re going to play more than once or twice in a row. This information includes nuggets of information like how many games you’ve played, your total number of attempts to guess the right number across all games, and you average attempts per game. These totals have drastically improved, too. In fact, it’ll continue to show the previous totals, and the new totals, after each game. If this is confusing, then take a look at game play images further below, and try it out for yourself.

This first image, is a game from 1 to 10 that doesn’t include the ShowTotals parameter.

This next image is from two back-to-back games from 1 to 3, that does include the ShowTotals parameter.

So it’s been said, the function defaults to 1 and 100, if you don’t supply minimum and maximum numbers. Try it out when you have a minute, and definitely show it to your kiddos, too. If my daughter is any indication, your kids will enjoy it, as well. I should mention, that she was pestering me to update this function, after I first mentioned wanting to make some changes for her. A four-year-old pestering me to write PowerShell: I loved it.

On a final note, there is some redundant code in the function. Since this is just a game, and I have bigger projects, I’m leaving it as is for now. Maybe I’ll come back around some time and clean it up… we’ll see.

You can download it from the PowerShell Gallery, or better yet, from your PowerShell host application using PowerShellGet and the Install-Script function: Install-Script -Name Start-1to100Game3.0

Copy the Last Command to the Clipboard

As a PowerShell blogger, I’ll often write a command in the PowerShell ConsoleHost and then, after it’s been executed, want to copy it out and paste it into a post. What this typically means is that I’ll press the up arrow to recall the last command — the one I want — and then selectively highlight the code I want and right-click to copy. Inevitably, I’ll end up copying my prompt and other unwanted lines, or parts of lines, and have to clean that up once it’s been pasted elsewhere.

Well, with a quick minute, those days are over. Type the function name Copy-LastCommand (once the function is in your PowerShell session), press Enter, and boom, the last command you entered is on the clipboard and ready to go elsewhere. Simple. Now all I do, is paste it where I want it and save myself some seconds. They add up.

Function Copy-LastCommand {
    (Get-History (Get-History | Select-Object -Last 1).Id).CommandLine | clip
}

Update: I made some changes to this little function and need to share those. Something I didn’t consider right away, was what happens if there isn’t a last command to copy? This is to say, what if the Copy-LastCommand is the first command entered into someone’s PowerShell session? Well, it won’t have anything to copy. I’ve added a fix for those times — a simple Write-Warning command. I also edited this to work using the newer, Set-Clipboard, cmdlet that was introduced in PowerShell 5.1 (I believe).

Function Copy-LastCommand {
    If (Get-History) {
        If ($PSVersionTable.PSVersion.ToString() -match '5.1') {
            Set-Clipboard -Value (Get-History (Get-History | Select-Object -Last 1).Id -ErrorAction SilentlyContinue).CommandLine
        } Else {
            (Get-History (Get-History | Select-Object -Last 1).Id).CommandLine | clip
        }
    } Else {
        Write-Warning -Message 'No command history to copy.'
    }
}