Tag Archives: Find-Module

Keeping a Continuous Total


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on July 15, 2019.


There’s a function stored in my $PROFILE (CurrentUserAllHosts). Maybe you’ve read about it before; it’s come up. Its purpose is to go out to the PowerShell Gallery and determine how many downloads I have of each PowerShell script and/or module I’ve published there. In the past, my tool provided a download total for each project along with a single total of all the downloads. Today that changes, but before it does (I seriously haven’t the updated code yet — typical), let’s show the code and results, as it currently stands.

Function Showw-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )

    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project
        }
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
} # End Function: Show-PSGalleryProject.
Show-PSGalleryProject
Name                Version Downloads
----                ------- ---------
TMOutput            1.1     1193
Start-1to100Game3.0 3.0     170
Get-TMVerbSynonym   1.4     163
SinkProfile         1.0     100
Show-PSDriveMenu    1.1     69
Switch-Prompt       1.2.0   84
>> Total downloads: 1779

See how we only have the single Total downloads above (the 1779 value)? I’ve decided I don’t like that, so it’s about to change. I want the total downloads as each of these objects are being returned (, calculated,) and written.

In the newest version of this little $PROFILE-based function — there was an earlier one — the first thing I’ve done is added the [CmdletBinding] Attribute. It’s to add the OutVariable Common Parameter. We’ll see how that’s helpful momentarily. The second thing we’ve done is begun collecting and displaying, what I’ve called the Type (find in the below code: ; $Type = Module or Script). This will differentiate when added to each object, whether one of my projects in the PowerShell Gallery is a module or script. Here’s the updated code with these two additions. Notice also, the object created inside [PSCustomObject] now includes a Type property. This is where the Module or Script determinations are included.

Function Show-PSGalleryProject {
    [CmdletBinding()]
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )

    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project; $Type = 'Module'
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project; $Type = 'Script'
        }
        $TotalDownloads = [int]$TotalDownloads + [int]$TempVar.AdditionalMetadata.downloadCount
        [PSCustomObject]@{
            Name = $TempVar.Name
            Type = $Type
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
            TotalDownloads = $TotalDownloads
        }
    } # End Foreach.
} # End Function: Show-PSGalleryProject.
Show-PSGalleryProject

The other recent addition to this code is the addition of my $TotalDownloads variable. Through each iteration, we update it so it has the current value and then displays said current value with the current object. I may have been a little too generous with my [int] casting; however, do remember that the plus sign (+) is also a concatenation operator. I want a total download count — not a string of individual download counts, strung together.

In closing, there’s something else to remember. By default (and there are ways around this), when we have more than four properties being returned per object, the “table” output becomes a “list” output — have a look.

Name           : TMOutput
Type           : Module  
Version        : 1.1     
Downloads      : 1193    
TotalDownloads : 1193    

Name           : Start-1to100Game3.0
Type           : Script
Version        : 3.0   
Downloads      : 170   
TotalDownloads : 1363  

Name           : Get-TMVerbSynonym
Type           : Script
Version        : 1.4   
Downloads      : 163   
TotalDownloads : 1526  

Name           : SinkProfile
Type           : Module
Version        : 1.0   
Downloads      : 100   
TotalDownloads : 1626  

Name           : Show-PSDriveMenu
Type           : Script
Version        : 1.1   
Downloads      : 69    
TotalDownloads : 1695  

Name           : Switch-Prompt
Type           : Script
Version        : 1.2.0 
Downloads      : 84    
TotalDownloads : 1779

While using the newly added OutVariable is helpful if I want to display the above output and capture the output into a variable at the same time, here’s how I run this now.

$PS = Show-PSGalleryProject
$PS | Format-Table -AutoSize
Name                Type   Version Downloads TotalDownloads
----                ----   ------- --------- --------------
TMOutput            Module 1.1     1193                1193
Start-1to100Game3.0 Script 3.0     170                 1363
Get-TMVerbSynonym   Script 1.4     163                 1526
SinkProfile         Module 1.0     100                 1626
Show-PSDriveMenu    Script 1.1     69                  1695
Switch-Prompt       Script 1.2.0   84                  1779

Next up is likely putting this code into a background job; it’s not the quickest thing I’ve written (although I blame that on the speed of the PowerShell Gallery lookup process, perhaps). Maybe a background job that runs in, or starts at, the end of the profile script. This, in order that these slow-to-obtain results are available sooner and with minimal impact.

Edit: 2/5/2022 –  I never did do that, although this function does still exists in my $PROFILE script. Now that I have seen this post again, perhaps there will be another push to try that. Time will tell. Oh, and here are my updated totals since this post was first written and published.

Name                Type   Version Downloads TotalDownloads
----                ----   ------- --------- --------------
TMOutput            Module 1.1     2980                2980
Start-1to100Game3.0 Script 3.0     266                 3246
Get-TMVerbSynonym   Script 1.4     293                 3539
SinkProfile         Module 1.0     302                 3841
Show-PSDriveMenu    Script 1.1     186                 4027
Switch-Prompt       Script 1.2.0   236                 4263

PowerShellGet Find-Both


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on June 11, 2019.


For years now, I’ve had a handful of personal functions living as a part of my $PROFILE script — the CurrentUserAllHosts version. This specific profile script ensures it’s available for use regardless of which PowerShell host program I use: the ConsoleHost, the ISE, VS Code, etc. One of these functions — Show-PSGalleryProject — goes out to the PowerShell Gallery and returns the current download count of all the modules and scripts I’ve added to the gallery. It also determines the combined download total.

The below image was rescued from an old Twitter post, back when the function worked as expected. Don’t mind the black background color. It was a demo of my TMOutput module. Again, this was the output produced back when the function originally worked. It’s not currently working, but with any luck that’s about to change.

Before we continue, let’s take a quick look at the underlying code that created the above output.

Set-Alias -Name Watch-PSGalleryProject -Value Show-PSGalleryProject
Function Show-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )
 
    Foreach ($Project in $Projects) {
        $TempVar = Find-Module -Name $Project
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
}

At some point, and I’m not sure when the Find-Module function stopped working against scripts in the PowerShell Gallery. Additionally, Find-Script potentially stopped working against modules. Now that said, I’m not even certain when Find-Script was added and if it ever worked against modules hosted in the PowerShell Gallery. Regardless, I need a way now to rewrite this personal function, so that it invokes the correct PowerShellGet function against the proper project: Find-Module against modules and Find-Script against scripts. Before we work on that solution, here’s what my function returns now. Ugh. The Show-PSGalleryProject function’s failed invocation.

I started writing this post prior to writing the solution. Once I dug in, it really only took a few changes to see that this was working again. While it wasn’t difficult, I’ve opted to continue the post and share my results, even if, you’ve already calculated a way in which a single function could check one function (Find-Module), before another (Find-Script). Here’s my updated code and the current, and corrected, results.

Set-Alias -Name Watch-PSGalleryProject -Value Show-PSGalleryProject
Function Show-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )
 
    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project
        }
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
}

It’s not too much slower than it used to be, but it’s always been somewhat slow. There is without question, a noticeable delay doing separate and individual searches against the PowerShell Gallery (and -Filter didn’t seem to return more than a single result the last time I checked). It’s fine for me, however (for now). I have considered doing the search as a part of a background process or when the profile is loaded. Either way, I’m back in business for today.

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.

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!

Compare AWSPowerShell Versions II

Series links: Part I, Part II, Part III (Coming)

I wrote a post a day or two ago that indicated how to compare your currently installed AWSPowerShell module with the offering on the PowerShell Gallery (see Part I above). Well, as I suspected, I’m back with an update.

Before we get there, however, I want to mention that Steve Roberts, at AWS, got in touch with me via Twitter about the post. I suspect we’ll be seeing some updates to Get-AWSPowerShellVersion. What’s interesting, is that I found the ListServiceVersionInfo parameter after my post. It seems that the cmdlet does create objects for some of the results it can return, just not everything. As always, I’m looking forward to Steve’s ideas and implementation.

Today, I’m going to share a quickly written advanced function. This will do the comparison for you and return the results in a custom object. Don’t be surprised if I make some more additions and/or changes to the function and post that as well. It really should have an option to download and install the new module if you want it, right?

Function Compare-AWSPowerShellVersion {
<#
.SYNOPSIS
    This advanced function compares the currently installed version of the AWSPowerShell module and the version on the PowerShell Gallery (http://powershellgallery.com).

.DESCRIPTION
    This advanced function compares the currently installed version of the AWSPowerShell module and the version on the PowerShell Gallery (http://powershellgallery.com).

.EXAMPLE
    PS > Compare-AWSPowerShellVersion
    This example compares the currently installed version of the AWSPowerShell module and the version on the PowerShell Gallery.

.NOTES 
    NAME: Compare-AWSPowerShellVersion
    AUTHOR: Tommy Maynard
    COMMENTS: --
    LASTEDIT: 10/19/2016
    VERSION 1.1:
        - Edited notes.
        - Changed Switch to -regex and made the first option an OR for both side indicators.
    VERSION 1.2:
        - Modified results to use a custom object.
    VERSION 1.3:
        - Removed Switch statement: added version determination logic inside custom object creation.
        - Only returning the highest version number from currently installed if more than one version. This is a leftover due to AWS Toolkit install vs. PowerShell Gallery install.
#>
    [CmdletBinding()]
    Param (
    )

    Begin {
        # Set continuation variable for Process block.
        $Continue = $true

        'PowerShellGet','AWSPowerShell' | ForEach-Object {
            Write-Verbose -Message "Determining if the $_ module is available."
            If (-Not(Get-Module -Name $_ -ListAvailable)) {
                Write-Warning -Message "Unable to locate the required $_ module."
                $Continue = $false
            }
        } # End Foreach.
    } # End Begin.

    Process {
        If ($Continue) {
            Write-Verbose -Message 'Collecting the current and newest AWSPowerShell module versions.'
            $CurrentAWSPSModule = ((Get-Module -Name AWSPowerShell -ListAvailable).Version |
                Sort-Object -Descending | Select-Object -First 1).ToString()
            $NewestAWSPSModule = (Find-Module -Name AWSPowerShell).Version.ToString()

            Write-Verbose -Message 'Comparing the AWSPowerShell version and creating custom results object.'
            [PSCustomObject]@{
                Match = If ($CurrentAWSPSModule -eq $NewestAWSPSModule) {$true} Else {$false}
                Current = $CurrentAWSPSModule
                Newest = $NewestAWSPSModule
            }
        } # End If.
    } # End Process.

    End {
    } # End End.
} # End Function: Compare-AWSPowerShellVersion.

Update: After updating to the newest version, I returned a System.Object[] error for my current version, due to having multiple versions and using the ToString method. I’ve modified the code above to only select the first (and highest) module version from those on my system. I think it’s important to point out that I’ve always downloaded and installed using the .msi until this version, and that when I used Install-Module, I included the Scope parameter name with the CurrentUser parameter value. I suspect this is where the problem is originating. Well, this and the fact I haven’t uninstalled the .msi version from Programs and Features.

I shouldn’t have to wait too long to be on an old build to test some more! It seems as though AWS does at least one, daily build. Here’s the full command as I ran it to install the newest version: Find-Module -Name AWSPowerShell | Install-Module -Scope CurrentUser.

Here’s the usage and results since updating to 3.3.11.0.

Compare-AWSPowerShellVersion

Match Current  Newest  
----- -------  ------  
 True 3.3.11.0 3.3.11.0

Update: As suspected, there’s a new build this evening, and it appears the comparison function is still working, as it’s correctly indicated there isn’t a match between what’s installed and what’s available to install.

Compare-AWSPowerShellVersion

Match Current  Newest
----- -------  ------
False 3.3.11.0 3.3.12.0

I updated to the newest version of the module and ran it again. It’s looking good.

Compare-AWSPowerShellVersion

Match Current  Newest
----- -------  ------
 True 3.3.12.0 3.3.12.0

Write-Output Gets Foreground and Background Colors and More

Every once in a while a forum post comes along that really captures my interest. Take this one for instance.

In this post, the creator wanted to build out a start up menu in the ConsoleHost that has a solid white line, another white line below that, with the words “PowerShell Project 1” in it, and a final white line beneath that. That might be difficult to imagine, so here’s a visual representation, and then what his/hers looked like. I had recommended to use a here-string to fix the problem, but unfortunately we didn’t have the same results.

write-output-gets-foreground-and-background-colors-and-more01
Note: While you can’t see them, in both this and the below image, there are spaces on the lines above, below, and before and after “PowerShell Project 1.” This is how we can ensure the white rectangle around the text.

write-output-gets-foreground-and-background-colors-and-more02

I can’t say for certain why theirs rendered differently, but I suspect version, or PSReadline, or possibly console font/size. That wasn’t important; what was, was trying to figure out a way around this problematic inconsistency. We have the option to change the background color of the area where we type, and so I wondered if I could temporarily do that inside the ConsoleHost. It turns out I could. With that new piece of knowledge, I set out to write a wrapper function around Write-Output. In my version — Write-TMOutput — it includes -ForegroundColor and -BackgroundColor parameters with an option to horizontally and vertically pad the text (object).

First, here’s a few images to show my new function in action. This first image shows the commands included in the TMOutput module.

write-output-gets-foreground-and-background-colors-and-more03

The next example image shows some basic, Write-Output type usage. One where we pipe to the function, and one where we don’t. Fairly standard.

write-output-gets-foreground-and-background-colors-and-more04

Now, the next example is where this starts to get fun: the incorporation of the -ForegroundColor and -BackgroundColor parameters. Prior to running these commands, and those in the last two examples, I removed the PSReadLine module so the colors would be easier to spot.

write-output-gets-foreground-and-background-colors-and-more05

Next is a demo of two additional parameters: -HorizontalPad and -VerticalPad. These will allow you to add spaces before and after the value supplied to the -InputObject parameter (the text), and add lines above and below the text. This is the part that gets back to the request in the Microsoft Technet forum post. I should mention that my solution in the forum post would be different now that I’ve spend some time writing this function — there are better ways to do things that I hadn’t considered at the time I replied to the post.

write-output-gets-foreground-and-background-colors-and-more06

The next example shows some usage with the Get-ADUser cmdlet. Keep in mind that if you combine this function with other cmdlets, that the -HorizontalPad and -VerticalPad parameters cannot be used. In my mind, they’re just a bonus to the option of using colors with Write-Output. I should mention it now, but this function was intentionally written to only work in the ConsoleHost. Perhaps I’ll add the option of using it in the ISE in the future.

write-output-gets-foreground-and-background-colors-and-more07

I’ve written an additional function that will allow you to quickly see the available colors from which you can choose. It’s called Show-TMOutputColor, and produces the results below. This might come in handy as you’re using the modified Write-Output.

write-output-gets-foreground-and-background-colors-and-more08

Now, on to the module. It’s the first time I’ve done it, but I’ve uploaded this to the PowerShell Gallery. I believe publishing to the PowerShell Gallery was one of my PowerShell New Year’s Resolutions for 2016, so there’s one down! You can use this direct link or, one better, download the module from your console, using this command:

Install-Module -Name TMOutput

Thanks for reading this post. Hopefully these function will be a useful way to get around the need for Write-Host’s foreground and background color options while working in the console. That, and maybe the TechNet thread creator can do whatever they wanted, too.

Search PowerShell Gallery Module for #Requires Statement

The first part of this post began on PowerShell.org. Start there, unless you already have: http://powershell.org/wp/2015/11/30/search-powershell-gallery-module-for-requires-statement.

Please use the connect.microsoft.com link here, or at the bottom of this post, to up vote my feedback.

Desired State Configuration (DSC) and Just Enough Administration (JEA) are two topics that have recently piqued my interest. This, after becoming involved in Windows PowerShell constrained endpoints, and writing proxy functions, to better control how cmdlets are used. While working with DSC and xJEA (x, as in experimental), I ran up against an error message in the Event Viewer on my Server 2012 R2 target node running Windows Management Framework (WMF) 4.0 (the installation package that contains PowerShell 4.0). Before I get too deeply involved in JEA in WMF 5.0 on Server 2016 — what appears to be constrained endpoints of the past, with the benefits of JEA, but without the need for DSC, and some additional new features — I wanted to ensure I was able to deploy JEA endpoints with DSC, while running WMF 4.0.

Knowing this, the first error message below, makes perfect sense. I downloaded a version of xJEA that had a PowerShell 5.0 requirement, without even knowing it. Instead of moving up to WMF 5.0 to “fix” this, I opted to first get an older version of xJEA and determine if I was able to make it run with a version that can be used on WMF 4.0. This seemed like a logical progression in my learning and understanding of both DSC and JEA. Here’s the error:

“Error Message = The script ‘MSFT_xJeaToolkit.psm1’ cannot be run because it contained a “#requires” statement for Windows PowerShell 5.0. The version of Windows PowerShell that is required by the script does not match the currently running version of Windows PowerShell 4.0.”

As to be expected, the error message that was reported in the console host, was much less helpful. In fact, I was at a total loss until I went hunting in the Event Viewer, where I turned up the previous error message. Here’s the error I saw in the console:

“Invoke-CimMethod : Failed to extract the module from zip file C:\Windows\TEMP\\635842583367244849\xJea_0.2.16.6.zip
downloaded by Download Manager WebDownloadManager.”

In order to determine which version of xJEA didn’t require PowerShell 5.0, I needed to download all the xJEA versions and inspect them. You see, there isn’t a way to find out if there’s a PowerShell version requirement… more on that shortly. To begin, I ensured that the PowerShell Gallery was a trusted repository, and therefore, wouldn’t prompt me when I ran the Save-Module command in an upcoming example. To do this, I ran the commands below to (1) verify if the PowerShell Gallery was trusted (which it wasn’t, and isn’t by default), (2) trust it as an installation source, and (3) verify it was trusted.

PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Untrusted            https://www.powershellgallery.com/api/v2/


PS> Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Trusted              https://www.powershellgallery.com/api/v2/

When that was completed, what I needed was to download all the xJEA modules from the PowerShell Gallery, at once, and place them into a folder for each version. Luckily, the Save-Module cmdlet will handle the file structure. This means, each module (a DSC resource, in this case) will be saved inside a version folder, nested in the same xJea folder. I gave the cmdlet a base path (C:\), and it did the rest. Take a look below at the example and image.

PS> Find-Module -Name xJea -AllVersions |
>> ForEach-Object {Save-Module -Path C:\ -Name xJea -RequiredVersion "$($_.Version.ToString())"}

Search PowerShell Gallery Module for requires01

Once I had all the files downloaded, I needed to run through all the contained .psm1 files for the requires statement for PowerShell version 5.0. The command below returned 51 files where it located the string “requires -version 5.”

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path
C:\xJea\0.2.10\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.11\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.12\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.13\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.14\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.15\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.1\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.2\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.3\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.4\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.5\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.6\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.8\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.9\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In the next two examples, I haven’t included all the results to save some space. The example below, takes the previous one, and removes the portion of the path to the left of the version number. Compare the last line in the example above with the last line in the example below, and you’ll see how the .Split() method cleaned up the beginning of each result.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1]}
0.2.10\DSCResources\Library\JeaAccount.psm1
0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
0.2.11\DSCResources\Library\JeaAccount.psm1
0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
...
0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In this example, we added a second .Split() method to clean up the path information to the right of the version number. Now, we only have the versions, although we do have duplicates.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> Foreach-Object {$_.Split('\',3)[-1].Split('\')[0]}
0.2.10
0.2.10
0.2.10
0.2.11
0.2.11
0.2.11
..
0.2.9

This example below, shows the full results again; however, each version is only listed one time. This is because we’ve piped the previous results to Select-Object -Unique. This means, for example, we only see 0.2.11 once, even though it appeared three times in the last example.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} |
>> Select-Object -Unique
0.2.10
0.2.11
0.2.12
0.2.13
0.2.14
0.2.15
0.2.16
0.2.16.1
0.2.16.2
0.2.16.3
0.2.16.4
0.2.16.5
0.2.16.6
0.2.5
0.2.6
0.2.7
0.2.8
0.2.9

We now have a complete list of all the versions that require PowerShell 5.0. That means we can determine which versions will run with PowerShell 4.0.

In this final example, we set a variable, $VersionsRequireWMF5, to the results produced by our last command. These are the versions that require PowerShell 5.0. In the middle of the example below, we set a second variable, $AllVersions, to all the versions of xJEA that we downloaded. We did this by running a Get-ChildItem (think, dir or ls) against the C:\xJea directory and returned just the directory names. That gives us two variables that we can supply to the Compare-Object cmdlet. That cmdlet will tell us the differences between the values of the variables, indicating which versions I can use with WMF 4.0.

PS> $VersionsRequireWMF5 = (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' | 
>> Get-Content) -match 'requires -version 5' | 
>> Select-Object PSPath | Convert-Path | 
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} | 
>> Select-Object -Unique
PS>  
PS> $AllVersions = (Get-ChildItem -Path C:\xJea | Select-Object).Name
PS>  
PS> Compare-Object -ReferenceObject $AllVersions -DifferenceObject $VersionsRequireWMF5

InputObject SideIndicator
----------- -------------
0.2         <=
0.2.1       <=
0.2.2       <=
0.2.4       <=

Based on these results, version 0.2, 0.2.1, 0.2.2, and 0.2.4, do not require WMF 5.0 and can be tested with WMF 4.0, before moving to the newer version of WMF. You can expect that I’ll work with 0.2.4 tomorrow morning.

A final note: Microsoft may not yet think they need it, but they should indicate the version of PowerShell that is required by modules on the PowerShell Gallery. That needs to be a part of their approval process, and the results need to added to the web interface, and made a part of the results returned by the Find-Module cmdlet (see the full Find-Module results below). These results should include a Requires property. This isn’t going to be the last time someone comes up against this problem, especially as we move into versions post WMF 5.0.

PS> Find-Module -Name xJea | Select-Object *

Name : xJea
Version : 0.2.16.6
Description : Module with DSC Resources for Just Enough Admin (JEA). Jea makes it simple to create
custom RBAC solutions using PowerShell.
Author : Microsoft Corporation
CompanyName :
Copyright : (c) 2014 Microsoft Corporation. All rights reserved.
PublishedDate : 5/14/2015 7:51:23 PM
LicenseUri :
ProjectUri :
IconUri :
Tags : {PSModule, PSIncludes_DscResource}
Includes : {Function, DscResource, Cmdlet, Command}
PowerShellGetFormatVersion :
ReleaseNotes :
Dependencies : {}
RepositorySourceLocation : https://www.powershellgallery.com/api/v2/
Repository : PSGallery
PackageManagementProvider : NuGet

If you think this should be included, then up vote the feedback I’ve left on connect.microsoft.com.

Cmdlet and Function Alias Best Practice

Aliases are a beautiful thing, really. Even though this may be true, we need to make sure we’re following best practice, when we opt to use them. I’m not sure what it is about me, but I get a little antsy whenever I see someone not conforming to a best practice in PowerShell. I really don’t care how you lace and tie your shoes, or make your peanut butter and jelly sandwiches, but when you use your PowerShell aliases is important to me, and it should be to you, too.

The main idea here is that cmdlet and function aliases shouldn’t exist in something that lasts longer than a one time use. That means we shouldn’t see them in help documentation (although, I believe I’ve seen some), in online articles, and in PowerShell-related forum posts. The only time you’ll see me use a cmdlet or function alias, is if you’re looking over my shoulder and watching me type commands into my console, or the ISE’s console pane. Realistically, aliases do two things: One, they help speed up getting results in a one time use scenario, as I’ve mentioned, and two, they confuse PowerShell newcomers that come across a blog where they’ve been used without consideration to best practice. Perhaps it’s my desire to help people learn PowerShell, that’s driving some of this alias indignation. I should mention, that I think it’s acceptable to include them in cases where you also explain that you’re using an alias, and indicate the full cmdlet or function name.

If you showed up here after seeing one of those blogs, or scripts, where someone littered their work with aliases, keep in mind that we have a Get-Alias cmdlet that can help you determine to which cmdlet an alias resolves — we’ll get back to that momentarily. Let’s say you found this command online:

PS> ls c:\windows | ? {$_ -like 'Win*'} | % {echo "$($_.Name)||$($_.LastWriteTime)"}

I understand this command has the potential to be written better. One such way, would be to not pipe to ? in order to filter on the file or directory name, but instead to use the ls -Filter parameter. Weird… in that last sentence, I used a couple aliases instead of their cmdlet names, and even that was confusing. Imagine having read that as a newcomer. This command, since it’s going to live on this webpage “forever,” should have been written like this (for the most part):

PS> Get-ChildItem -Path c:\windows | Where-Object {$_ -like 'Win*'} | ForEach-Object {Write-Output -InputObject "$($_.Name)||$($_.LastWriteTime)"}

It’s longer, there’s no doubt about it, but it’s much more complete and easier to comprehend, especially had you found it in a lengthy script or function.

Back to Get-Alias: If you find yourself confused by a command you found online, where someone “didn’t think of the next person,” then run though the command, or commands, and check the aliases against the Get-Alias cmdlet. Let’s do that below, for the first example command I wrote. Notice that Get-Alias will accept a comma-separated list of (alias) values.

PS> Get-Alias -Name ls,?,%,echo

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           ls -> Get-ChildItem
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           h -> Get-History
Alias           r -> Invoke-History
Alias           % -> ForEach-Object
Alias           echo -> Write-Output

Now, notice the results. We’ve returned aliases that we didn’t request, such as h for Get-History and r for Invoke-History. Why? A little off topic, but this is because the question mark (?), in regular expressions, or regex, stands in for a single character. The results are not only returning the alias for the question mark, but for any aliases that only have a single character. In order to only return what we want, we’ll need to escape the question mark character and put it in quotes, so that the parser is certain we have supplied the string value, of an actual question mark.

PS> Get-Alias -Name ls,'`?',%,echo

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           ls -> Get-ChildItem
Alias           ? -> Where-Object
Alias           % -> ForEach-Object
Alias           echo -> Write-Output

There, we go.

As a community, we should do things so they better help the current, and future members. Use all the aliases you want, but do so where they won’t exist for someone else to stumble upon, unless, you’ve taken the time to explain that you’ve used an alias, and to which cmdlet it refers.

In closing, I want to mention the PSScriptAnalyzer module. I pulled it down and installed it in PowerShell 5.0, using the Find-Module and Install-Module cmdlets: Find-Module -Name PSScriptAnalyzer | Install-Module. I then copied my alias heavily command above, pasted it into notepad, and saved it as C:\file.ps1. Following that, I ran an Invoke-ScriptAnalyzer command. You can see the command and its results below

PS> Invoke-ScriptAnalyzer -Path C:\file.ps1 -IncludeRule PSAvoidUsingCmdletAliases

RuleName                            Severity     FileName   Line  Message
--------                            --------     --------   ----  -------
PSAvoidUsingCmdletAliases           Warning      file.ps1   1     'echo' is an alias of 'Write-Output'. Alias can
                                                                  introduce possible problems and make scripts hard to
                                                                  maintain. Please consider changing alias to its full
                                                                  content.
PSAvoidUsingCmdletAliases           Warning      file.ps1   1     '%' is an alias of 'ForEach-Object'. Alias can
                                                                  introduce possible problems and make scripts hard to
                                                                  maintain. Please consider changing alias to its full
                                                                  content.
PSAvoidUsingCmdletAliases           Warning      file.ps1   1     '?' is an alias of 'Where-Object'. Alias can
                                                                  introduce possible problems and make scripts hard to
                                                                  maintain. Please consider changing alias to its full
                                                                  content.
PSAvoidUsingCmdletAliases           Warning      file.ps1   1     'ls' is an alias of 'Get-ChildItem'. Alias can
                                                                  introduce possible problems and make scripts hard to
                                                                  maintain. Please consider changing alias to its full
                                                                  content.

The cmdlet was instructed to only return the problems it found with aliases (see the parameter used and the included value). It’s a very powerful cmdlet; not only can you find any alias-related best practice failures, but it’ll help you locate any other areas, where you can improve your code.

Thanks for your time, and… your new dedication to not use cmdlet and function aliases, that last longer than a one time use.

Update: I just saw a post from The Scripting! Guy (http://blogs.technet.com/b/heyscriptingguy/archive/2015/10/25/powertip-group-powershell-cmdlet-count-by-version.aspx) where he used a few aliases and told us what they mean. If you’re going to use them, explain them.