“Logon” Processing Code

I’m working on something new and there’s not much on this topic in relation to PowerShell. Compared to some other topics, there’s not much on it anyway. I want to use PowerShell to authenticate with Shibboleth, and I want to use its ECP profile. Shibboleth is typically implemented with a browser and its SSO profile. My goal, while I’m not sure if I’ll get there or not, is to authenticate with Shibboleth from a non-browser-based client: my PowerShell ConsoleHost.

As a part of this effort, I wrote some Proof of Concept (PoC) code that requires a logon. There’s nothing special about this code, but I’m going to dump it here, right on my website, just in case anyone wants to read through it. It’s mostly straightforward, but it does present a momentary challenge as you walk through what does what. You might just use it for that — a challenge — while I’ll use this post for the storage of the code itself. Feel free to read over the code and then the short information section beneath it. Again, just locking the ability to use a specific function until I’m “logged” on.

Function Get-Process5 {
	$Script:OriginalPrompt = prompt
	If ($Script:LoggedOn) {
		Get-Process | Select-Object -First 5
		Disconnect-Shibboleth
	} Else {
		$Script:CommandToInvoke = $MyInvocation.MyCommand.Name
		Invoke-UserIDPassPrompt
	} # End If-Else.
} # End Function: Get-Process5.

Function Invoke-UserIDPassPrompt {
	$Script:UserIDUserName = Read-Host -Prompt "$Script:OriginalPrompt   UserID"
	$Script:UserIDPassword = Read-Host -Prompt "$Script:OriginalPrompt Password" -AsSecureString
	Connect-Shibboleth
} # End Function: Invoke-UserIDPassPrompt.

Function Connect-Shibboleth {
	If ($Script:UserIDUserName -and $Script:UserIDPassword) {
		$Script:LoggedOn = $true
		& $Script:CommandToInvoke
	} # End If.
} # End Function: Connect-Shibboleth.

Function Disconnect-Shibboleth {
	$Message = 'Do you want to disconnect from Shibboleth [Y/N]'
	Do {
		$Response = Read-Host -Prompt $Message
		If ($Response -eq 'y') {$Script:LoggedOn = $false} # End If.
	} Until ($Response -eq 'y' -or $Response -eq 'n') # End Do-Until.
} # End Function: Disconnect-Shibboleth.

There are four separate functions: Get-Process5, Invoke-UserIDPassPrompt, Connect-Shibboleth, and Disconnect-Shibboleth. When the above code is executed, it will first attempt to invoke the Get-Process5 function. If you’re “logged on,” it will return the first five processes running on the computer and then prompt you to disconnect (think “logoff”). I put quotes around “logged on” and “logoff” because there is no true log on/off going on here. It really is just PoC code that runs regardless of the UserID and or password that is entered. If this is of interest, then have a peek. For me, I doubt I’ll actually be back for this, as most of what I needed to write (so far), has been written. Still, a neat little moment in time that produced some PowerShell worth more than being fully discarded.

AWS PowerShell Command Count

Back in the day, I was astonished by the number of PowerShell commands that AWS (Amazon Web Services) had written. It was a huge number—I believe it was around 5,000. There’s probably a post or two on this site where it’s mentioned. Based on the number, it was clear that AWS had made a commitment to (wrapping their APIs with) PowerShell. Back then, it was easy to calculate the number of commands because of that single, PowerShell module. Install the module, count the commands (programmatically of course), and done. Over the last two or three years—I’m not 100% sure—-they moved to a model where modules are separated out by service. I greatly suspect that the single-module model was no longer suitable. Who wants to import a few thousand commands by default, when they only needed maybe one or two? Over time, I suspect that it probably wasn’t the most efficient way in which to handle the high number of commands.

Now, AWS has modules with names that begin with AWS.Tools. In the below example, I have assigned the $AWSModules variable with all the AWS modules located in the PowerShell Gallery that begin with the name AWS.Tools.*.

$AWSModules = Find-Module -Name AWS.Tools.*
$AWSModules.Count
241
$AWSModules | Select-Object -First 10 -Property Name,Version
Name                              Version
----                              -------
AWS.Tools.Common                  4.1.10.0
AWS.Tools.EC2                     4.1.10.0
AWS.Tools.S3                      4.1.10.0
AWS.Tools.Installer               1.0.2.0
AWS.Tools.SimpleSystemsManagement 4.1.10.0
AWS.Tools.SecretsManager          4.1.10.0
AWS.Tools.SecurityToken           4.1.10.0
AWS.Tools.IdentityManagement      4.1.10.0
AWS.Tools.Organizations           4.1.10.0
AWS.Tools.CloudFormation          4.1.10.0

Once I determined that there were over 240 individual modules, I piped the $AWSModule variable to the Get-Member command to view its methods and properties. I didn’t know what I was after, but out of the returned properties, I was intrigued by the Includes property. I used that property as a part of the below command. Each of the 241 entries includes a Command, DscResource, Cmdlet, Workflow, RoleCapability, and Function nested property inside the Includes property.

$AWSModules.Includes | Select-Object -First 3
Name Value
---- -----
Command {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
DscResource {}
Cmdlet {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
DscResource {}
Cmdlet {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
DscResource {}
Cmdlet {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
Workflow {}
RoleCapability {}
Function {}

After some more inspection, I decided that each command populated the Command property and the Cmdlet or the Function property, as well, depending on what type of commands were included in the module. Next, I just returned the commands using dot notation. This isn’t all of them, but you get the idea.

$AWSModules.Includes.Command
Clear-AWSHistory
Set-AWSHistoryConfiguration
Initialize-AWSDefaultConfiguration
Clear-AWSDefaultConfiguration
Get-AWSPowerShellVersion
...
Remove-FISExperimentTemplate
Remove-FISResourceTag
Start-FISExperiment
Stop-FISExperiment
Update-FISExperimentTemplate

If you’re curious, as I was, Update-FISExperimentTemplate, “Calls the AWS Fault Injection Simulator UpdateExperimentTemplate API operation.” I have no idea.

With a little more dot notation, I was able to get the count.

$AWSModules.Includes.Command.Count
8942

And now, I know what I used to know. I also know that AWS has been busy. And, that they’ve continued to make a huge effort in the PowerShell space. If you don’t go straight to the PowerShell Gallery, and I would recommend you do, you can always start at the AWS PowerShell webpage.

PowerShell Approved Verb Synonyms

One of the best design decisions, when PowerShell was initially being created, was using approved verbs in naming a command. When people use those, we can guarantee some consistency between command names. As commands — both cmdlets and functions — we expect to see an approved verb followed by a dash and then a singular noun or nouns. Sometimes there’s a prefix after the dash, but before the noun(s). Here are a few examples of some random commands: Import-Module, Get-VMGroup, Add-AzVhd, Export-AzDataLakeStoreItem, Set-WsusProduct, and Stop-SSMCommand. While these commands display a few of the approved verbs, it’s not all of them. Use the Get-Verb command to view the full list of the approved verbs, from which you should choose when writing a new function or cmdlet.

There may not have always been this many, but in 7.2.0-preview4, there are 100 approved verbs.

[PS7.2.0-preview.4][C:\] Get-Verb | Measure-Object

Count             : 100
Average           :
Sum               :
Maximum           :
Minimum           :
StandardDeviation :
Property          :

Even though there’s a good number of options from which to choose, there may not always feel like there’s one for your newest command. While I’ve brought this up to the community at least a couple of times before, it’s time to do it again. It’s the Get-TMVerbSynonym function. I authored it in June 2016, I updated it in 2017, and I used it earlier today, in 2021. It can be found in the Powershell Gallery and installed using the below command:

[PS7.2.0-preview.4][C:\] Install-Script -Name Get-TMVerbSynonym

The file it downloads is a function inside of a script file — a .ps1 file. In order to add a function inside a script file to the current PowerShell session, you need to dot source the file. That’s what the mystery dot is doing between my prompt and the path to the .ps1 file below.

[PS7.2.0-preview.4][C:\] . C:\Users\tommymaynard\Documents\PowerShell\Scripts\Get-TMVerbSynonym.ps1

Once it’s dot sourced, you can use the function. In closing, here are few examples of the function in action. Each of these commands uses the Format-Table -AutoSize command and parameter in order that the results are easier to read.

[PS7.1.3][C:\] Get-TMVerbSynonym -Verb change | Format-Table -AutoSize

Verb   Synonym     Group  Approved Notes
----   -------     -----  -------- -----
Change Alter                 False
Change Commute               False
Change Convert     Data       True
Change Deepen                False
Change Dress                 False Generic Term
Change Exchange              False
Change Get Dressed           False Generic Term
Change Go                    False Generic Term
Change Interchange           False
Change Locomote              False Generic Term
Change Modify                False
Change Move        Common     True Generic Term
Change Replace               False Generic Term
Change Shift                 False
Change Stay                  False Antonym
Change Switch      Common     True
Change Transfer              False
Change Transfer              False Generic Term
Change Travel                False Generic Term
Change Vary                  False

[PS7.1.3][C:\] Get-TMVerbSynonym -Verb change -Approved | Format-Table -AutoSize

Verb   Synonym Group  Approved Notes
----   ------- -----  -------- -----
Change Convert Data       True
Change Move    Common     True Generic Term
Change Switch  Common     True

[PS7.1.3][C:\] 

If there’s ever a verb you want to use, but it’s not approved, then try this function. You’ll likely be able to choose something that’s close enough, and is approved.

Encoding and Decoding PowerShell Strings

Every few months Base64 comes up and I have to go looking for that one post I saw that one time. It’s because that code, on that one site, hasn’t been memorized — not by me, anyway. So, here it is. The below example shows how to encode and decode a string using Base64. Keep in mind that different things, such as HTTP Headers, might require different character sets. In this example, I’m using UTF-8, but it could have been ASCII, or Unicode, or something else.

Clear-Host
$UserName = 'tommymaynard'
$Password = 'password'

"The UserName is '$UserName' and the password is '$Password'."
"Encoding as $($UserName):$($Password)"

$Text = "$($UserName):$($Password)"
$Bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
$EncodedText =[Convert]::ToBase64String($Bytes)
"Encoded text: $EncodedText"

$DecodedText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($EncodedText))
"Decoded text: $DecodedText"

When the above code is executed it produces the below results.

The UserName is 'tommymaynard' and the password is 'password'.
Encoding as tommymaynard:password
Encoded text: dG9tbXltYXluYXJkOnBhc3N3b3Jk
Decoded text: tommymaynard:password

And, now that’s that. When I need this next, it’ll be right here on my own blog. Now back to that Python to PowerShell project where I need Base64 encoding. By the way, this is a great online encode/decode tool that you might find helpful: https://www.base64decode.org.

Dynamic PowerShell Version in Windows Terminal Tabs Part II

Maybe no one noticed. Or, maybe they did and just didn’t say anything. As you’ll see, I’ve written a quick fix to a post I wrote last week.

I shouldn’t have, but back in Part I, I put reliance on my prompt function for the Windows Terminal tab titles. This means that I have to ensure that I never change my prompt function. If I do, it’s very possible that adding the version of PowerShell to the tab titles will break. This isn’t good. That dependence, while it works just fine now, could become a problem in time.

Knowing that, let’s remove it. Let’s get the version in the Windows Terminal tab titles from something other than my prompt. Let’s put it where it belongs, on the version itself — on the $PSVersionTable automatic variable. My prompt Itself uses that, so why shouldn’t this Windows Terminal tab title code, too? This makes much more sense than coupling the tab titles with my prompt function.

$Host.UI.RawUI.WindowTitle = "$($Host.UI.RawUI.WindowTitle) $($PSVersionTable.PSVersion.ToString())"

The tabs look exactly like they did before, but now my prompt function doesn’t play into the versions making their way to the tabs at all. Altering the prompt doesn’t matter now, or now, at any time in the future.

Dynamic PowerShell Version in Windows Terminal Tabs

There’s a Part II to this post now, be sure to read it after you’ve read this post.

I find myself inside the Window Terminal’s JSON Settings file each time I update the version of PowerShell on my system. That includes non-preview and preview versions of PowerShell. Well, as of today, that’s changed. Previously I would install an updated version, jump into the JSON Settings file, and modify the Name and TabTitle properties of the corresponding profile. I’ve long wanted the version number on the Tab and so I was hard coding the values. No longer.

Let’s take a look at my prompt function. It’s mostly the default one, with some slight changes. Here’s the prompt code from my $PROFILE. Preview version or not, they both use the same $PROFILE script; therefore, they use the same prompt function.

Function Prompt {
    "$("[PS$($PSVersionTable.PSVersion.ToString())][$($executionContext.SessionState.Path.CurrentLocation)]" * ($nestedPromptLevel + 1)) ";
    # .Link
    # https://go.microsoft.com/fwlink/?LinkID=225750
    # .ExternalHelp System.Management.Automation.dll-help.xml
} # End Function: Prompt.

Here’s how the prompt renders in both the non-preview and preview versions of PowerShell.



What I do, and you’ll see it momentarily is extract the version number — “7.1.3” and “7.2.0-preview.4” from the above prompts and write them to the WindowTitle along with what was already there (the word “PowerShell”). The below code is beneath the Prompt function in my $PROFILE script.

$Prompt = prompt
$Host.UI.RawUI.WindowTitle = "$($Host.UI.RawUI.WindowTitle) $(($Prompt.Split('][').TrimStart('[PS')[0]))"

First, it captures the prompt into a variable called Prompt. Then it manipulates the contents of the variable until we only have what we want. This first thing it does consists of splitting the prompt string at the back-to-back closing and opening square brackets: “][“. In the case of the non-preview prompt, this leaves me with two strings: [PS7.1.3 and C:\]. Next, it trims off the [PS from the start of the first of the two strings. As you can tell, we only have an interest in our first string — the value in the zero index: [0]. Once’s the parsing is complete, our value is appended to the existing value in the WindowTitle property and it is all written back to the WindowTitle property.

It’s from here forward that the title includes not just the word “PowerShell,” but also the version. That’s it. A little extra work now to remove all the work from later on.

There’s a Part II to this post now, be sure to read it.

Simple Simple Microsoft Crescendo Example Part II

The Microsoft.PowerShell.Crescendo module is mostly brand new. It’s still early on in its development. It’s currently at version 0.4.1. This isn’t my first discussion on this topic, so please read Part I on this topic. That’ll give you an idea of my first experience working with the module. In that post, we created a PowerShell module that contained a single command that wrapped the mstsc.exe Remote Desktop Connection executable. Although there are several, I only wrote it with a single parameter: a stand-in for /v. This parameter expects the name of the computer or IP address, such as mstsc /v Server01.

In that example I copied, pasted, and edited an existing *.Crescendo.json file. Today, we’ll wrap the same command (even though I alluded to using something else), however, we’ll do it using other cmdlets from the module. These will include New-CrescendoCommand, New-ParameterInfo, New-UsageInfo, and New-ExampleInfo. I had no idea what these were for as the help is limited, but I think I figured it out for now. If you know more, then of course you’re welcome to comment and participate.

In the first example, we invoked New-CrescendoCommand. The available parameters at the time of this writing are -Verb and -Noun. Knowing those parameters, it’s straightforward what this was about. This is the naming for our new PowerShell command. As you can see, I supplied Connect and RemoteComputer, respectively. These are the same values that we supplied in Part I of this series. As you can likely tell, the command creates an object (a [TypeName] Command object to be exact). We send that object to ConvertTo-Json, as we’ll need everything in JSON before creating our mstsc.Crescendo.json file. As you can also tell, the object — both the PowerShell object and JSON object — has several other properties. As best as I could determine, there was no way to include these when invoking the New-CrescendoCommand. Do notice that the OriginalName property is blank and that there wasn’t a way to include that when invoking the command. We’ll be back to discuss this later.

[PS7.1.0] $Command = New-CrescendoCommand -Verb Connect -Noun RemoteComputer
[PS7.1.0] $Command

Verb : Connect
Noun : RemoteComputer
OriginalName :
OriginalCommandElements :
Aliases :
DefaultParameterSetName :
SupportsShouldProcess : False
SupportsTransactions : False
NoInvocation : False
Description :
Usage :
Parameters : {}
Examples : {}
OriginalText :
HelpLinks :
OutputHandlers :

[PS7.1.0] $Command = $Command | ConvertTo-Json
[PS7.1.0] $Command
{
  "Verb": "Connect",
  "Noun": "RemoteComputer",
  "OriginalName": null,
  "OriginalCommandElements": null,
  "Aliases": null,
  "DefaultParameterSetName": null,
  "SupportsShouldProcess": false,
  "SupportsTransactions": false,
  "NoInvocation": false,
  "Description": null,
  "Usage": null,
  "Parameters": [],
  "Examples": [],
  "OriginalText": null,
  "HelpLinks": null,
  "OutputHandlers": null
}

In this code example, we essentially do the same thing. We create a PowerShell object and then convert it to JSON. This is the addition of a new parameter.

[PS7.1.0] $Parameter = New-ParameterInfo -Name ComputerName -OriginalName /v
[PS7.1.0] $Parameter

ParameterType                   : object
Position                        : 2147483647
Name                            : ComputerName
OriginalName                    : /v
OriginalText                    :
Description                     :
DefaultValue                    :
DefaultMissingValue             :
AdditionalParameterAttributes   :
Mandatory                       : False
ParameterSetName                :
Aliases                         :
OriginalPosition                : 0
ValueFromPipeline               : False
ValueFromPipelineByPropertyName : False
ValueFromRemainingArguments     : False
NoGap                           : False

[PS7.1.0] $Parameter = $Parameter | ConvertTo-Json
[PS7.1.0] $Parameter
{
  "ParameterType": "object",
  "Position": 2147483647,
  "Name": "ComputerName",
  "OriginalName": "/v",
  "OriginalText": null,
  "Description": null,
  "DefaultValue": null,
  "DefaultMissingValue": null,
  "AdditionalParameterAttributes": null,
  "Mandatory": false,
  "ParameterSetName": null,
  "Aliases": null,
  "OriginalPosition": 0,
  "ValueFromPipeline": false,
  "ValueFromPipelineByPropertyName": false,
  "ValueFromRemainingArguments": false,
  "NoGap": false
}

And in this code example, we essentially do the same thing, too. This PowerShell object converted to JSON has to do with Usage. I’m still working that one over in my mind, but we’ll take a closer look.

[PS7.1.0] $Usage = New-UsageInfo -usage 'Runs Remote Desktop Connection'
[PS7.1.0] $Usage

Synopsis                       SupportsFlags HasOptions
--------                       ------------- ----------
Runs Remote Desktop Connection False         False

[PS7.1.0] $Usage = $Usage | ConvertTo-Json
[PS7.1.0] $Usage
{
  "Synopsis": "Runs Remote Desktop Connection",
  "SupportsFlags": false,
  "HasOptions": false,
  "OriginalText": null
}

Finally, we create a comment-based help example and ensure it’s in the JSON format.

[PS7.1.0] $Example = New-ExampleInfo -command Connect-RemoteComputer -originalCommand 'C:\Windows\System32\mstsc.exe' -description 'Wraps Remote Desktop Connection' 
[PS7.1.0] $Example

Command                OriginalCommand               Description
-------                ---------------               -----------
Connect-RemoteComputer C:\Windows\System32\mstsc.exe Wraps Remote Desktop Connection

[PS7.1.0] $Example = $Example | ConvertTo-Json
[PS7.1.0] $Example
{
  "Command": "Connect-RemoteComputer",
  "OriginalCommand": "C:\\Windows\\System32\\mstsc.exe",
  "Description": "Wraps Remote Desktop Connection"
}

Next, we have to manually put these pieces of JSON together into our single, mstsc.Crescendo.json file. Let’s do that next. We’ll begin with the Parameter JSON we created and add to it the Command JSON. In the Command JSON section, we had an entry that said "Parameters": [], In between the square brackets, we have to add the Parameter JSON. This has been done below.

{
    "Verb": "Connect",
    "Noun": "RemoteComputer",
    "OriginalName": null,
    "OriginalCommandElements": null,
    "Aliases": null,
    "DefaultParameterSetName": null,
    "SupportsShouldProcess": false,
    "SupportsTransactions": false,
    "NoInvocation": false,
    "Description": null,
    "Usage": null,
    "Parameters": [
        {
            "ParameterType": "object",
            "Position": 2147483647,
            "Name": "ComputerName",
            "OriginalName": "/v",
            "OriginalText": null,
            "Description": null,
            "DefaultValue": null,
            "DefaultMissingValue": null,
            "AdditionalParameterAttributes": null,
            "Mandatory": false,
            "ParameterSetName": null,
            "Aliases": null,
            "OriginalPosition": 0,
            "ValueFromPipeline": false,
            "ValueFromPipelineByPropertyName": false,
            "ValueFromRemainingArguments": false,
            "NoGap": false
        }
    ],
    "Examples": [],
    "OriginalText": null,
    "HelpLinks": null,
    "OutputHandlers": null
}

Next, we’ll add our Usage JSON to its place in our mstsc.Crescendo.json file

{
    "Verb": "Connect",
    "Noun": "RemoteComputer",
    "OriginalName": null,
    "OriginalCommandElements": null,
    "Aliases": null,
    "DefaultParameterSetName": null,
    "SupportsShouldProcess": false,
    "SupportsTransactions": false,
    "NoInvocation": false,
    "Description": null,
    "Usage": {
        "Synopsis": "Runs Remote Desktop Connection",
        "SupportsFlags": false,
        "HasOptions": false,
        "OriginalText": null
    },
    "Parameters": [
        {
            "ParameterType": "object",
            "Position": 2147483647,
            "Name": "ComputerName",
            "OriginalName": "/v",
            "OriginalText": null,
            "Description": null,
            "DefaultValue": null,
            "DefaultMissingValue": null,
            "AdditionalParameterAttributes": null,
            "Mandatory": false,
            "ParameterSetName": null,
            "Aliases": null,
            "OriginalPosition": 0,
            "ValueFromPipeline": false,
            "ValueFromPipelineByPropertyName": false,
            "ValueFromRemainingArguments": false,
            "NoGap": false
        }
    ],
    "Examples": [],
    "OriginalText": null,
    "HelpLinks": null,
    "OutputHandlers": null
}

And finally, we’ll add our Example JSON.

{
    "Verb": "Connect",
    "Noun": "RemoteComputer",
    "OriginalName": null,
    "OriginalCommandElements": null,
    "Aliases": null,
    "DefaultParameterSetName": null,
    "SupportsShouldProcess": false,
    "SupportsTransactions": false,
    "NoInvocation": false,
    "Description": null,
    "Usage": {
        "Synopsis": "Runs Remote Desktop Connection",
        "SupportsFlags": false,
        "HasOptions": false,
        "OriginalText": null
    },
    "Parameters": [
        {
            "ParameterType": "object",
            "Position": 2147483647,
            "Name": "ComputerName",
            "OriginalName": "/v",
            "OriginalText": null,
            "Description": null,
            "DefaultValue": null,
            "DefaultMissingValue": null,
            "AdditionalParameterAttributes": null,
            "Mandatory": false,
            "ParameterSetName": null,
            "Aliases": null,
            "OriginalPosition": 0,
            "ValueFromPipeline": false,
            "ValueFromPipelineByPropertyName": false,
            "ValueFromRemainingArguments": false,
            "NoGap": false
        }
    ],
    "Examples": [
        {
            "Command": "Connect-RemoteComputer",
            "OriginalCommand": "C:\\Windows\\System32\\mstsc.exe",
            "Description": "Wraps Remote Desktop Connection"
        }
    ],
    "OriginalText": null,
    "HelpLinks": null,
    "OutputHandlers": null
}

I don’t think it’s there yet, but one day there will likely be a command that does all this for us. Maybe there already is and somehow I’ve overlooked it. With this JSON created and saved as mstsc.Crescendo.json, we can attempt to use it with Export-CrescendoModule as we did in Part I.

[PS7.1.0] Export-CrescendoModule -ConfigurationFile 'C:\Users\tommymaynard\Documents\PowerShell\Modules\Microsoft.PowerShell.Crescendo\0.4.1\Samples\mstsc.Crescendo.json' -ModuleName 'RemoteComputer.psm1'

If you try this, you’ll notice it fails.

This is because the New-CrescendoCommand didn’t include an OriginalName parameter and therefore our JSON didn’t include a much-needed value. Without it, the command doesn’t point to an existing command on the computer, which becomes the reason behind the above error message. Even though it throws an error, it still creates the file (although it’s missing much of the good stuff). You’ll have to remove the file or you’ll get another error about the file already existing.

Our JSON ends up with this: “OriginalName”: null, instead of this: “OriginalName”:”/Windows/System32/mstsc.exe”,. As you can see below, I’ve added this in, so the code here is complete.

{
    "Verb": "Connect",
    "Noun": "RemoteComputer",
    "OriginalName": "/Windows/System32/mstsc.exe",
    "OriginalCommandElements": null,
    "Aliases": null,
    "DefaultParameterSetName": null,
    "SupportsShouldProcess": false,
    "SupportsTransactions": false,
    "NoInvocation": false,
    "Description": null,
    "Usage": {
        "Synopsis": "Runs Remote Desktop Connection",
        "SupportsFlags": false,
        "HasOptions": false,
        "OriginalText": null
    },
    "Parameters": [
        {
            "ParameterType": "object",
            "Position": 2147483647,
            "Name": "ComputerName",
            "OriginalName": "/v",
            "OriginalText": null,
            "Description": null,
            "DefaultValue": null,
            "DefaultMissingValue": null,
            "AdditionalParameterAttributes": null,
            "Mandatory": false,
            "ParameterSetName": null,
            "Aliases": null,
            "OriginalPosition": 0,
            "ValueFromPipeline": false,
            "ValueFromPipelineByPropertyName": false,
            "ValueFromRemainingArguments": false,
            "NoGap": false
        }
    ],
    "Examples": [
        {
            "Command": "Connect-RemoteComputer",
            "OriginalCommand": "C:\\Windows\\System32\\mstsc.exe",
            "Description": "Wraps Remote Desktop Connection"
        }
    ],
    "OriginalText": null,
    "HelpLinks": null,
    "OutputHandlers": null
}

Have fun and keep watching for newer versions. I suspect I will.

Invoke-RestMethod with a SOAP API

Did it again. I’m back here this evening to write another post after authoring some PoC code for a project. I’ve got to put it somewhere for later. In doing that, there’s no reason it shouldn’t be content in which others can consume.

In the past, I have absolutely written PowerShell to interact with REST (REpresentational State Transfer) APIs. What I haven’t done until now, is interact with SOAP (Simple Object Access Protocol) APIs. The below example is a full-featured PowerShell function. Its purpose, with the help of W3Schools public SOAP API, is to convert Fahrenheit temperatures to Celsius and vice versa. Typical normal stuff, except that I’m not doing the calculations. Instead, I’m sending off a temperature to an API to be converted. One of the big differences between REST and SOAP is the content of the payload. With SOAP we’re sending XML, whereas, with REST, we’d likely send JSON.

While you can inspect the code yourself, I will at minimum tell you how the function can be invoked. Its name is Convert-Temperature and it includes two parameters: Temperature and TemperatureScale. Enter an integer for the Temperature parameter and either Fahrenheit or Celsius for the TemperatureScale parameter. It’ll send off the information as XML to the API. The returned value will be extracted from the returned XML and rounded to two decimal places. I was going to make the rounding optional but I obviously changed my mind. I didn’t find any value in allowing a user to make this determination.

Here are a couple of invocation examples first, and then, the code. Maybe this will be helpful for you. I suspect it will be for me in time, as I’m going to likely have to make use of a SOAP API.

[PS7.1.0] [C:\] Convert-Temperature -Temperature 212 -TemperatureScale Fahrenheit

TemperatureScale Temperature ConvertedTemperature
---------------- ----------- --------------------
Fahrenheit               212                  100

[PS7.1.0] [C:\] Convert-Temperature -Temperature 100 -TemperatureScale Celsius

TemperatureScale Temperature ConvertedTemperature
---------------- ----------- --------------------
Celsius                  100                  212
Function Convert-Temperature {
	[CmdletBinding()]
	Param (
		[Parameter(Mandatory)]
		[int]$Temperature,

		[Parameter(Mandatory)]
		[ValidateSet('Fahrenheit','Celsius')]
		[string]$TemperatureScale
	) # End Param

	Begin {
		$Headers = @{'Content-Type' = 'text/xml'}
	} # End Begin.

	Process {
		If ($TemperatureScale -eq 'Fahrenheit') {
			$Body = @"
<soap12:Envelope xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`" xmlns:soap12=`"http://www.w3.org/2003/05/soap-envelope`">
	<soap12:Body>
		<FahrenheitToCelsius xmlns=`"https://www.w3schools.com/xml/`">
			<Fahrenheit>$Temperature</Fahrenheit>
		</FahrenheitToCelsius>
	</soap12:Body>
</soap12:Envelope>
"@
		} Else {
			$Body = @"
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
	<soap12:Body>
		<CelsiusToFahrenheit xmlns="https://www.w3schools.com/xml/">
			<Celsius>$Temperature</Celsius>
		</CelsiusToFahrenheit>
	</soap12:Body>
</soap12:Envelope>
"@
		} # End If-Else.
	} # End Process.

	End {
		$Response = Invoke-RestMethod -Uri 'https://www.w3schools.com/xml/tempconvert.asmx' -Method 'POST' -Headers $Headers -Body $Body
		If ($TemperatureScale -eq 'Fahrenheit') {
			$ConvertedTemperature = $([System.Math]::Round($Response.Envelope.Body.FahrenheitToCelsiusResponse.FahrenheitToCelsiusResult,2))
		} Else {
			$ConvertedTemperature = ([System.Math]::Round($Response.Envelope.Body.CelsiusToFahrenheitResponse.CelsiusToFahrenheitResult,2))
		} # End If-Else.

		[PSCustomObject]@{
			TemperatureScale = $TemperatureScale
			Temperature = $Temperature
			ConvertedTemperature = $ConvertedTemperature
		}
	} # End End.
} # End Function: Convert-Temperature.

Create Self-Signed Certificate and Export

Yesterday, I found myself walking through the usage of a couple of the cmdlets in the PKIClient (or PKI) module. Due to a project I’m working on, I may soon find myself needing and creating a self-signed certificate. Because I’m not yet ready to implement this code, I thought it made sense to use my blog to store it, until I am. While it’s for me, this could be helpful for you too.

$NewCertCreateParameters = @{
	Subject = 'powershell.functiontemplate.mydomain.com'
	CertStoreLocation = 'Cert:\CurrentUser\My'
	NotAfter = (Get-Date -Date 03/03/2021 -Hour 17).AddYears(10)
	KeyExportPolicy = 'Exportable'
	OutVariable = 'Certificate'
} # End.
New-SelfSignedCertificate @NewCertCreateParameters | Out-Null

In this first code section seen above, I used splatting. This allows me to create a hash table full of parameters and parameter values — key-value pairs*. Once created, it can be used — or splatted — as a part of a command’s invocation. In this instance, we’re splatting the hash table we created and stored in the $NewCertCreateParameters variable on the New-SelfSignedCertificate cmdlet. Noticed that we’re piping our command to Out-Null to keep the default output of this command from displaying. There’s still a way to see it, however.

This bit of PowerShell creates a new self-signed certificate on the computer, which is associated with the current user. Since we included the OutVariable parameter in our hash table, we have the data returned by our cmdlet invocation stored in the $Certificate variable, even though we used Out-Null. While the below output only shows three properties by default, there’s plenty more that can be reviewed by piping $Certificate to Select-Object -Property *.

[PS7.1.0] C:\> $Certificate

   PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject              EnhancedKeyUsageList
----------                                -------              --------------------
56DA4F187DF396CCCB67B5C93F6F0CA7848C5E66  CN=powershell.funct… {Client Authentication, Server Authentication}

Using the code in the second section, we can view our self-signed certificate after it’s been created by the previous command.

Get-ChildItem -Path $NewCertCreateParameters.CertStoreLocation |
	Where-Object Thumbprint -eq $Certificate.Thumbprint |
	Select-Object -Property *

The final section, has a few things happening. First, we create a secure string that holds a password. This isn’t a real password, so don’t bother. You get the idea, though, and really, for me, this is just about storing some code I may, or may not, use. The reason for the password is because we’re about to export the certificate to a Personal Information Exchange file — a PFX file, and we want it to be secure.

Second, we create a second parameter hash table and splat it onto the Export-PfxCertifcate cmdlet. Just as we did before, we pipe this command to Out-Null to suppress its output. Lastly, I’ve included three commands. The first one shows the .pfx file in the file system, the second one removes the .pfx file from the file system, and the third one removes the self-signed certificate from the system completely.

$CertPassword = ConvertTo-SecureString -String 'canoN Beach 44$09' -Force -AsPlainText

$NewCertExportParameters = @{
	Cert = "Cert:\CurrentUser\My\$($Certificate.Thumbprint)"
	FilePath = "$env:USERPROFILE\Documents\PowerShellFunctionTemplate.pfx"
	Password = $CertPassword
} # End.
Export-PfxCertificate @NewCertExportParameters | Out-Null

Get-Item -Path $NewCertExportParameters.FilePath
Remove-Item -Path $NewCertExportParameters.FilePath
Remove-Item -Path $NewCertExportParameters.Cert

And finally, here’s all the code in a single code block.

$NewCertCreateParameters = @{
	Subject = 'powershell.functiontemplate.arizona.edu'
	CertStoreLocation = 'Cert:\CurrentUser\My'
	NotAfter = (Get-Date -Date 03/03/2021 -Hour 17).AddYears(10)
	KeyExportPolicy = 'Exportable'
	OutVariable = 'Certificate'
} # End.
New-SelfSignedCertificate @NewCertCreateParameters | Out-Null

Get-ChildItem -Path $NewCertCreateParameters.CertStoreLocation |
	Where-Object Thumbprint -eq $Certificate.Thumbprint |
	Select-Object -Property *

$CertPassword = ConvertTo-SecureString -String 'canoN Beach 44$09' -Force -AsPlainText

$NewCertExportParameters = @{
	Cert = "Cert:\CurrentUser\My\$($Certificate.Thumbprint)"
	FilePath = "$env:USERPROFILE\Documents\PowerShellFunctionTemplate.pfx"
	Password = $CertPassword
} # End.
Export-PfxCertificate @NewCertExportParameters | Out-Null

Get-Item -Path $NewCertExportParameters.FilePath
Remove-Item -Path $NewCertExportParameters.FilePath
Remove-Item -Path $NewCertExportParameters.Cert

* Before closing, unless you got down here sooner, I wanted to mention a couple of the parameters I opted to use in the first command invocation. The New-SelfSignedCertificate cmdlet included the NotAfter and the KeyExportPolicy parameters, as shown below. The NotAfter parameter allowed us to include the preferred expiration of the self-signed certificate. Mine used March 3, 2031, as I added 10 years to my date value. If this parameter isn’t included, it will default to a one-year expiration. The KeyExportPolicy parameter allowed us to make the private key exportable. This is not a default, so it must be included if you suspect it’ll need to be exported, which is something we did.

...
	NotAfter = (Get-Date -Date 03/03/2021 -Hour 17).AddYears(10)
	KeyExportPolicy = 'Exportable'
...

Simple Simple Microsoft Crescendo Example

Edit: There’s a Part II now!

There’s a newer module that been discussed a few times in the written form, as well as in at least one podcast I listened to recently. Jason Helmick, an MVP turned Microsoft employee, has been notifying the PowerShell community about the Microsoft Crescendo PowerShell module. And he should be. It’s a unique idea for wrapping native system commands as PowerShell commands. I went looking for the easiest possible example and instead of finding that, I ended up here, writing about my first experience with the module.

Before I show you what I did, let me link a few posts about Crescendo. There was this one from Jason himself and a couple from Jim Truher: Part 1 and Part 2. Jim did the development of this module. At the time of this writing they aren’t taking PRs, but here’s the project on Github, too. And then somehow, I ended up watching this on YouTube with Jason and Jim.

The first, first thing I did was install the module from the PowerShell Gallery using the below command. I did that some time ago actually, but you know. I did, however, ensure that there wasn’t a newer version before beginning using Find-Module .

[PS7.1.0] C:\> Install-Module -Name Microsoft.PowerShell.Crescendo

The second, first thing I did was go to “C:\Users\tommymaynard\Documents\PowerShell\Modules\Microsoft.PowerShell.Crescendo\0.4.1\Samples” and copy and paste one of the JSON file examples. I renamed it to mstsc.Crescendo.json. I don’t believe this is the traditional way this is done, but… it was me experimenting with the module. The mstsc.exe executable is used for RDC or Remote Desktop Connection. If you’re like me, you probably call it RDP. I replaced everything in the file with what’s included below. I don’t recall which of the examples I copied from, but I removed one of the parameters, as that one had two and I was only interested in including one for now. Based on the structure of the below JSON you can get an idea of what’s happening.

{
    "$schema" : "./Microsoft.PowerShell.Crescendo.Schema.json",
    "Verb": "Connect",
    "Noun": "RemoteComputer",
    "OriginalName":"/Windows/System32/mstsc.exe",
    "Parameters": [
        {
            "Name": "ComputerName",
            "OriginalName": "/v",
            "ParameterType": "string"
        }
    ]
}

The schema file is used to ensure what’s entered into this JSON file, my mstsc.Crescendo.json file, is correct. The verb is, well the verb I wish to use. Make sure you use an approved verb. It checks against the Schema.json file for approved verb compliance. There’s a noun that’s needed, as well as the path to the native file we’re wrapping. After that is the single parameter I made available for use with this command. There’s plenty of mstsc switches, but I only ever use /v. Perhaps it’s an odd choice for testing, I don’t know, but it was the first to come to me for something simple, simple to try.

In the above JSON, and still in regards to the single parameter I included, I’ve used ComputerName for the parameter name which will stand in for /v. Additionally, I’ve indicated that the parameter type accepts a string. Therefore, if there’s a parameter value included with the parameter it should expect it’s a string value.

Once that portion was complete, I saved and closed my file and ran the Export-CrescendoModule command to create my module file — a .psm1 file. I didn’t see a way to avoid this, but this command will create the module file inside your current directory. Keep that in mind. I didn’t test with Out-File, but perhaps that would be an option for directing the output.

[PS7.1.0] C:\> Export-CrescendoModule -ConfigurationFile 'C:\Users\tommymaynard\Documents\PowerShell\Modules\Microsoft.PowerShell.Crescendo\0.4.1\Samples\mstsc.Crescendo.json' -ModuleName 'RemoteComputer.psm1'

Once the module file has been created, it’s time to import it and use it. Here’s my first go using my new module after my copy, paste, edit, and export method. Notice what happens when I don’t include the ComputerName parameter and value. It simply opens RDP with the last computer and user name. Helpful, but not exactly what I was after.

Here’s my second go at using the module’s Connect-RemoteComputer command. In this example, I included the ComputerName parameter and a value. As it’s not a computer that I’ve ever connected to, it’s prompting me to ensure I trust it. If you use this command with computers that you’ve already trusted, it’ll begin the RDP connection immediately. Perfect — just as I had expected.

A couple of things. This wasn’t a typical example. I think the idea behind the Crescendo module is to make command-line tools — like, strictly command-line tools — act more like PowerShell. I’ve been running mstsc from my command line for so long that it was one of the first command that came to mind. Also, I think this is going to be a Part I of at least one more post. I’d like to try another command — look at this list! Additionally, based on the other command names in the Crescendo module, there appears to be a better way to start a new project that doesn’t include copying and pasting a sample file. I’m going to do a little more experimentation and get back to this if I can. Working with the other cmdlets in the module hasn’t been as straightforward as I had hoped, but I’ll know more as the weekend progresses.

Edit: There’s a Part II now!