Tag Archives: Set-Alias

Put the Alias Description Property to Work II

In part I of Put the Alias Description Property to Work, I showed and explained how to add a description property to each of my personal, profile script-created aliases. This allowed me to use a basic function, that filtered the Get-Alias results to return only my aliases. In this post, we will take this one step further and we will add descriptions to the built-in aliases in PowerShell. If you remember, we determined that most of the built-in aliases do not include a source property. Therefore, sorting or filtering on that property is of little value.

The source property is not a settable property, meaning that no matter how hard I try, I cannot edit it. The Description property, as we learned, is open for me to make changes. This is also true with built-in aliases and not just the aliases I create. Here is what we are going to do. All the aliases resolve to a command, and all but four of those commands have a source property. We are going to acquire a command’s source and set it as the description of its alias. This might make more sense with some examples, so let’s start there.

But before we do, let’s look at the default properties returned by Get-Alias.

Get-Alias | Get-Random
CommandType     Name                Version    Source
-----------     ----                -------    ------
Alias           pwd -> Get-Location 

Get-Alias returns CommandType, Name, Version, and Source. Kind of. Name is really a property called DisplayName. It may be better to know that now. In the first, real example, we are also going to use Get-Random to randomly select an alias for us. Then, we will return its ResolvedCommandName, DisplayName, Description, and Options properties.

$Alias = Get-Alias | Get-Random
Get-Alias -Name $Alias |
    Select-Object ResolvedCommandName,DisplayName,Description,Options
ResolvedCommandName DisplayName               Description  Options
------------------- -----------               -----------  -------
Connect-PSSession   cnsn -> Connect-PSSession             ReadOnly

Notice that currently, its Description property is blank. In the next example, we will use the value stored in $Alias and run it through a ForEach-Object loop. Take a look at the PowerShell now, and I will do that recently experimented with, line-by-line explanation further below.

Get-Alias -Name $Alias | ForEach-Object {
    if ($_.Description -eq '') {
        $Params = @{
            Name = $_.Name
            Value = $_.ResolvedCommandName
            Description = if ((Get-Command -Name $_.ResolvedCommandName).Source) {
                ((Get-Command -Name $_.ResolvedCommandName).Source)
            } else {'None'}
            Option = $_.Options
            Force = $true
        }
        Set-Alias @Params -Verbose
    }
}

VERBOSE: Performing the operation "Set Alias" on target "Name: cnsn Value: Connect-PSSession".

Line 1: Pipe our single alias into a ForEach-Object loop.
Line 2: Continue into an if construct if the alias’ description is empty. If it is not, we would move on to the next alias if there were more than just one.
Line 3: Create a hash table and store it in the $Params variable.
Line 4, 5: Add the name and the resolved command name into the Name and Value keys, respectively.
Line 6 – 8: Add a description to the Description key based on whether or not there is a source. If there is a source, add it, if there is not a source add the string None.
Line 9, 10: Add the options and $true value into the Option and Force keys, respectively.
Line 12: Invoke the Set-Alias command, splatting the $Params hash table as parameters and associated parameter values.

Let’s rerun the command we did just a minute ago.

Get-Alias -Name $Alias |
    Select-Object ResolvedCommandName,DisplayName,Description,Options
ResolvedCommandName DisplayName               Description                Options
------------------- -----------               -----------                -------
Connect-PSSession   cnsn -> Connect-PSSession Microsoft.PowerShell.Core ReadOnly

We have taken the source of the command Connect-PSSession and placed its value into the description of an alias that resolves to Connect-PSSession. It is not genius, but it is something!

Moving on, the next command pipes out all the known aliases and the values in each of the included properties. I will only include the first ten, as there are almost 150.

Get-Alias |
    Select-Object ResolvedCommandName,DisplayName,Description,Options
ResolvedCommandName DisplayName           Description              Options
------------------- -----------           -----------              -------
Where-Object        ? -> Where-Object                   ReadOnly, AllScope
ForEach-Object      % -> ForEach-Object                 ReadOnly, AllScope
Add-Content         ac -> Add-Content                             ReadOnly
Clear-Host          c -> Clear-Host        tommymaynard               None
Get-Content         cat -> Get-Content                                None
Set-Location        cd -> Set-Location                            AllScope
Set-Location        chdir -> Set-Location                             None
Clear-Content       clc -> Clear-Content                          ReadOnly
Clear-Host          clear -> Clear-Host                               None
Clear-History       clhy -> Clear-History                         ReadOnly

This should look familiar. There are a couple of differences in this code block compared to the one above. One, we are piping in all of the aliases (not just one), and two, there is no Verbose parameter included when the Set-Alias command is invoked. We do not need to output the change made to each alias. Yeah, no thanks.

Get-Alias | ForEach-Object {
    if ($_.Description -eq '') {
        $Params = @{
            Name = $_.Name
            Value = $_.ResolvedCommandName
            Description = if ((Get-Command -Name $_.ResolvedCommandName).Source) {
                ((Get-Command -Name $_.ResolvedCommandName).Source)
            } else {'None'}
            Option = $_.Options
            Force = $true
        }
        Set-Alias @Params
    }
}

This is the same command we saw earlier. I have only included the first ten here, as well. Notice the changes made to the Description property for each alias. Now I can easily see which alias goes with which source.

Get-Alias |
    Select-Object ResolvedCommandName,DisplayName,Description,Options
ResolvedCommandName DisplayName           Description                                Options
------------------- -----------           -----------                                -------
Where-Object        ? -> Where-Object     Microsoft.PowerShell.Core       ReadOnly, AllScope
ForEach-Object      % -> ForEach-Object   Microsoft.PowerShell.Core       ReadOnly, AllScope
Add-Content         ac -> Add-Content     Microsoft.PowerShell.Management           ReadOnly
Clear-Host          c -> Clear-Host       tommymaynard                                  None
Get-Content         cat -> Get-Content    Microsoft.PowerShell.Management               None
Set-Location        cd -> Set-Location    Microsoft.PowerShell.Management           AllScope
Set-Location        chdir -> Set-Location Microsoft.PowerShell.Management               None
Clear-Content       clc -> Clear-Content  Microsoft.PowerShell.Management           ReadOnly
Clear-Host          clear -> Clear-Host   None                                          None
Clear-History       clhy -> Clear-History Microsoft.PowerShell.Core                 ReadOnly

Like all worthy PowerShell code, I am going to wrap this in a function, and for the foreseeable future, copy this into my profile script. There is one change in this version that should be mentioned. The Scope parameter is now being included inside the $Params hash table with the Global value. Like the other parameters and parameters values in this hash table, it will be splatted onto the Set-Alias cmdlet when it is invoked. While the Scope parameter is not required with Get-Alias, it absolutely is with Set-Alias. We want the aliases that exist outside the function to be the ones we modify.

function Set-AliasDescription
Get-Alias | ForEach-Object {
    if ($_.Description -eq '') {
        $Params = @{
            Name = $_.Name
            Value = $_.ResolvedCommandName
            Description = if ((Get-Command -Name $_.ResolvedCommandName).Source) {
                ((Get-Command -Name $_.ResolvedCommandName).Source)
            } else {'None'}
            Option = $_.Options
            Force = $true
            Scope = 'Global'
        }
        Set-Alias @Params
    }
}
Set-AliasDescription

With the descriptions set, we can filter such as in the next two examples. Remember, the Get-Alias Description property is not displayed by default.

Get-Alias | Where-Object -Property Description -eq 'None'
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           clear -> Clear-Host
Alias           cls -> Clear-Host
Alias           man -> help
Alias           md -> mkdir
Get-Alias | Where-Object -Property Description -like '*core'
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           ? -> Where-Object
Alias           % -> ForEach-Object
Alias           clhy -> Clear-History
Alias           cnsn -> Connect-PSSession
Alias           dnsn -> Disconnect-PSSession
Alias           etsn -> Enter-PSSession
Alias           exsn -> Exit-PSSession
Alias           foreach -> ForEach-Object
Alias           gcm -> Get-Command
Alias           ghy -> Get-History
Alias           gjb -> Get-Job
Alias           gmo -> Get-Module
Alias           gsn -> Get-PSSession
Alias           h -> Get-History
Alias           history -> Get-History
Alias           icm -> Invoke-Command
Alias           ihy -> Invoke-History
Alias           ipmo -> Import-Module
Alias           nmo -> New-Module
Alias           nsn -> New-PSSession
Alias           oh -> Out-Host
Alias           r -> Invoke-History
Alias           rcjb -> Receive-Job
Alias           rcsn -> Receive-PSSession
Alias           rjb -> Remove-Job
Alias           rmo -> Remove-Module
Alias           rsn -> Remove-PSSession
Alias           sajb -> Start-Job
Alias           spjb -> Stop-Job
Alias           where -> Where-Object
Alias           wjb -> Wait-Job

Put the Alias Description Property to Work

I use a small handful of my own PowerShell aliases and of course some of those included with PowerShell natively. There is a best practice around aliases in PowerShell, and that is to not use them in anything that is going to live longer than a one-time use. Therefore, do not use them in scripts, functions, and modules (except in the case where your module exports aliases). I would recommend not even using them in forum posts or emails or work-only documentation. Keep them away from your own personal documentation, too. You might know what spsv, ndr, rcjb, and shcm mean today, at this moment, but you may not a few months from now. They have a place, however. Use them in your console, or shell, to speed up what would otherwise be a longer, manually entered command. Let’s check my $PROFILE script and see how many of my own PowerShell aliases I have.

Get-Content -Path $PROFILE | Select-String -Pattern '-alias'
Set-Alias -Name c -Value Clear-Host
Set-Alias -Name psrh -Value Open-PSReadLineHistoryFile
Set-Alias -Name sel -Value Select-Object
Set-Alias -Name wa -Value Watch-Apartment
Set-Alias -Name psgal -Value Show-PSGalleryProjectJob
'l','link' | ForEach-Object {Set-Alias -Name $_ -Value Find-Link}
Set-Alias -Name cts -Value Convert-TextToSpeech

Based on the above command and its results, I have seven. Even that is too many to remember when they are not being used often. Well, I forgot one recently and I was annoyed I had to open my profile script file and search for -alias. Sure, I could have used the above Get-Content command I wrote and tried out a minute ago, but why? Why not avoid ever having to search for my aliases from inside a source file again. They exist in the PowerShell session; why not search there? I am already in the session.

My first thought was, why does Microsoft not indicate which aliases are built-in? There is a source property. It is just too bad that not many aliases include that information. It would be much simple to filter aliases that way, removing those that are not a part of a Microsoft.<something>.<something> module. But, if you return this property there is often nothing. To begin with, there are currently 147 aliases on my machine.

(Get-Alias).Count
147

Only seven include a value in their source property.

Get-Alias | Where-Object Source -ne ''
CommandType     Name                    Version    Source
-----------     ----                    -------    ------
Alias           fhx -> Format-Hex       7.0.0.0    Microsoft.PowerShell.Utility
Alias           gcb -> Get-Clipboard    7.0.0.0    Microsoft.PowerShell.Management
Alias           gin -> Get-ComputerInfo 7.0.0.0    Microsoft.PowerShell.Management
Alias           gtz -> Get-TimeZone     7.0.0.0    Microsoft.PowerShell.Management
Alias           scb -> Set-Clipboard    7.0.0.0    Microsoft.PowerShell.Management
Alias           stz -> Set-TimeZone     7.0.0.0    Microsoft.PowerShell.Management

Weird right? One command alias is from the Microsoft.PowerShell.Utility module and six are from the Microsoft.PowerShell.Management module. It is not like there are only one and six aliases, respectively, from each entire module. There are plenty more commands and aliases, as well. Take a look; I have included both modules. We will begin with the Microsoft.PowerShell.Utility module.

((Get-Command -Module Microsoft.PowerShell.Utility).Name |
    ForEach-Object {Get-Alias -Definition $_ -ErrorAction SilentlyContinue}).Count
43
(Get-Command -Module Microsoft.PowerShell.Utility).Name |
    ForEach-Object {Get-Alias -Definition $_ -ErrorAction SilentlyContinue}
CommandType     Name                        Version    Source
-----------     ----                        -------    ------
Alias           clv -> Clear-Variable
Alias           compare -> Compare-Object
Alias           diff -> Compare-Object
Alias           dbp -> Disable-PSBreakpoint
Alias           ebp -> Enable-PSBreakpoint
Alias           epal -> Export-Alias
Alias           epcsv -> Export-Csv
Alias           fc -> Format-Custom
Alias           fhx -> Format-Hex           7.0.0.0    Microsoft.PowerShell.Utility
Alias           fl -> Format-List
Alias           ft -> Format-Table
Alias           fw -> Format-Wide
Alias           gal -> Get-Alias
Alias           gerr -> Get-Error
Alias           gm -> Get-Member
Alias           gbp -> Get-PSBreakpoint
Alias           gcs -> Get-PSCallStack
Alias           gu -> Get-Unique
Alias           gv -> Get-Variable
Alias           group -> Group-Object
Alias           ipal -> Import-Alias
Alias           ipcsv -> Import-Csv
Alias           iex -> Invoke-Expression
Alias           irm -> Invoke-RestMethod
Alias           iwr -> Invoke-WebRequest
Alias           measure -> Measure-Object
Alias           nal -> New-Alias
Alias           nv -> New-Variable
Alias           ogv -> Out-GridView
Alias           rbp -> Remove-PSBreakpoint
Alias           rv -> Remove-Variable
Alias           select -> Select-Object
Alias           sls -> Select-String
Alias           sal -> Set-Alias
Alias           sbp -> Set-PSBreakpoint
Alias           set -> Set-Variable
Alias           sv -> Set-Variable
Alias           shcm -> Show-Command
Alias           sort -> Sort-Object
Alias           sleep -> Start-Sleep
Alias           tee -> Tee-Object
Alias           echo -> Write-Output
Alias           write -> Write-Output

And continue with the Microsoft.PowerShell.Management module.

((Get-Command -Module Microsoft.PowerShell.Management).Name |
    ForEach-Object {Get-Alias -Definition $_ -ErrorAction SilentlyContinue}).Count
62
(Get-Command -Module Microsoft.PowerShell.Management).Name |
    ForEach-Object {Get-Alias -Definition $_ -ErrorAction SilentlyContinue}
CommandType     Name                         Version    Source
-----------     ----                         -------    ------
Alias           ac -> Add-Content
Alias           clc -> Clear-Content
Alias           cli -> Clear-Item
Alias           clp -> Clear-ItemProperty
Alias           cvpa -> Convert-Path
Alias           copy -> Copy-Item
Alias           cp -> Copy-Item
Alias           cpi -> Copy-Item
Alias           cpp -> Copy-ItemProperty
Alias           dir -> Get-ChildItem
Alias           gci -> Get-ChildItem
Alias           ls -> Get-ChildItem
Alias           gcb -> Get-Clipboard         7.0.0.0    Microsoft.PowerShell.Management
Alias           gin -> Get-ComputerInfo      7.0.0.0    Microsoft.PowerShell.Management
Alias           cat -> Get-Content
Alias           gc -> Get-Content
Alias           type -> Get-Content
Alias           gi -> Get-Item
Alias           gp -> Get-ItemProperty
Alias           gpv -> Get-ItemPropertyValue
Alias           gl -> Get-Location
Alias           pwd -> Get-Location
Alias           gps -> Get-Process
Alias           ps -> Get-Process
Alias           gdr -> Get-PSDrive
Alias           gsv -> Get-Service
Alias           gtz -> Get-TimeZone          7.0.0.0    Microsoft.PowerShell.Management
Alias           ii -> Invoke-Item
Alias           mi -> Move-Item
Alias           move -> Move-Item
Alias           mv -> Move-Item
Alias           mp -> Move-ItemProperty
Alias           ni -> New-Item
Alias           mount -> New-PSDrive
Alias           ndr -> New-PSDrive
Alias           popd -> Pop-Location
Alias           pushd -> Push-Location
Alias           del -> Remove-Item
Alias           erase -> Remove-Item
Alias           rd -> Remove-Item
Alias           ri -> Remove-Item
Alias           rm -> Remove-Item
Alias           rmdir -> Remove-Item
Alias           rp -> Remove-ItemProperty
Alias           rdr -> Remove-PSDrive
Alias           ren -> Rename-Item
Alias           rni -> Rename-Item
Alias           rnp -> Rename-ItemProperty
Alias           rvpa -> Resolve-Path
Alias           scb -> Set-Clipboard         7.0.0.0    Microsoft.PowerShell.Management
Alias           si -> Set-Item
Alias           sp -> Set-ItemProperty
Alias           cd -> Set-Location
Alias           chdir -> Set-Location
Alias           sl -> Set-Location
Alias           stz -> Set-TimeZone          7.0.0.0    Microsoft.PowerShell.Management
Alias           saps -> Start-Process
Alias           start -> Start-Process
Alias           sasv -> Start-Service
Alias           kill -> Stop-Process
Alias           spps -> Stop-Process
Alias           spsv -> Stop-Service

In the end, the Microsoft.PowerShell.Utilityhas 43 aliases and the Microsoft.PowerShell.Management module has 63 aliases. Maybe there is a good reason for the source, not being included most of the time. But, we are here to make my aliases easier to find. The source property is not a settable property, therefore, we are going to use the Description property to indicate when an alias is one of mine. Here are my aliases from earlier, each with a new addition to their Description property. It is my name.

Set-Alias -Name c -Value Clear-Host -Description 'tommymaynard'
Set-Alias -Name psrh -Value Open-PSReadLineHistoryFile -Description 'tommymaynard'
Set-Alias -Name sel -Value Select-Object -Description 'tommymaynard'
Set-Alias -Name wa -Value Watch-Apartment -Description 'tommymaynard'
Set-Alias -Name psgal -Value Show-PSGalleryProjectJob -Description 'tommymaynard'
'l','link' | ForEach-Object {Set-Alias -Name $_ -Value Find-Link -Description 'tommymaynard'}
Set-Alias -Name cts -Value Convert-TextToSpeech -Description 'tommymaynard'

Now, after my profile script runs, I can invoke a modified Get-Alias command to return just my aliases from the current PowerShell session.

Get-Alias | Where-Object -Property Description -eq 'tommymaynard'
CommandType     Name                               Version    Source
-----------     ----                               -------    ------
Alias           c -> Clear-Host
Alias           cts -> Convert-TextToSpeech
Alias           l -> Find-Link
Alias           link -> Find-Link
Alias           psgal -> Show-PSGalleryProjectJob
Alias           psrh -> Open-PSReadLineHistoryFile
Alias           sel -> Select-Object
Alias           wa -> Watch-Apartment

This post would not be complete if there was no new function to add to my $PROFILE script. So, with that, here is that new function and alias. Notice I added the description to the new alias, too!

Set-Alias -Name gmal -Value Get-MyAlias -Description 'tommymaynard'
function Get-MyAlias {
    Get-Alias | Where-Object -Property Description -eq 'tommymaynard'
}
gmal
CommandType     Name                              Version    Source
-----------     ----                               -------    ------
Alias           c -> Clear-Host
Alias           cts -> Convert-TextToSpeech
Alias           gmal -> Get-MyAlias
Alias           l -> Find-Link
Alias           link -> Find-Link
Alias           psgal -> Show-PSGalleryProjectJob
Alias           psrh -> Open-PSReadLineHistoryFile
Alias           sel -> Select-Object
Alias           wa -> Watch-Apartment

Part II has been published!

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
    }
    "&gt;&gt; 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.

An Alias and Function Change

At current, my site has 230 published posts. It’s 231 when this one goes live. Those are all written by me since mid 2014. I’m not sure of my per month average, but I can say for sure that I’ve never missed a month.

Outside of those 230 published posts, are 40 some drafts. Those are posts I started and never finished for one reason or another. Sometimes, I realized what I thought worked really didn’t, and therefore wasn’t able to complete the post. Well, in an effort to clean up the drafts, I’m publishing my first stupid draft turned post. Here’s goes… It’s learning from my failure — enjoy.

For just about as long as I can remember, I’ve always created an alias for my functions, just before the function is defined. This is of course when a function is defined outside of a module, such as when it’s defined in my $PROFILE script. In the below example, I do just that. After the alias is created, I define the function for which my alias can be used to invoke the function.

Set-Alias -Name saw -Value Show-AWord
Function Show-AWord {
    '!! A Word !!'
}

PS > saw
!! A Word !!

There’s another way to do this, as well. You can create the alias after the function’s been defined. You just swap the commands.

Function Show-EWord {
    '** E Word **'
}
Set-Alias -Name sew -Value Show-EWord

PS > sew
** E Word **

And here’s where the post went stupid.

I’ve always been mildly annoyed that I needed to have the code outside of the function, whether it’s before, or after, the function definition. I always wished there was a way to create aliases from inside the function.

Well there is, and there’s always been. I’ve just never given it much thought until about five minutes ago. This might be why I started this post; I didn’t think about it long enough. Here’s nearly the same function as above; however, now we’ll create the alias for the function, within the function. Because the Set-Alias cmdlet has a scope parameter, we can create a global alias from inside the function.

Function Show-GWord {
    Set-Alias -Name sgw -Value Show-GWord -Scope Global
    '$$ G Word $$'
}

PS > sgw
PS > # Nothing

Here’s about the time, I realized my problem. If you create an alias inside the function (using the Global scope [that’s how it “works”]), the alias is not going to exist until after the function has been invoked for the first time. Therefore, the function would have to be run like the below example. I pretty much removed a line outside the function, put it into the function, and then added another line outside the function. Ugh, not what I was after all.

PS > Show-GWord | Out-Null
PS > sgw
$$ G Word $$

So yeah, this post didn’t go as planned. No wonder it made its home in my drafts. It makes you wonder though. Why isn’t there a way to run some code inside a function when the function is being defined? Maybe because functions belong in modules and modules give you this ability when they’re imported, via their module manifest, and potentially, their ScriptsToProcess.

There you have it. A stupid draft I published.

Script Sharing – Functions and Code from My Profile

Over the last couple of years, my profile ($PROFILE) has filled up with a bunch of little functions that I use to help shave seconds off my day. I’ve shared a few in other posts, but here’s a few more that I’ve yet to share, that might be interesting. As a forewarning, in many of these, I understand that there may be a better way to do things.

This first one uses Google to show me the definition of a word. It isn’t written to return results to the Windows PowerShell console, but instead will launch the definition in a web browser. It was good enough at the time, and still is on a occasion (when I don’t actually know what a word means ;)) .

Set-Alias -Name lookup -Value Get-Definition
Function Get-Definition {
    Param (
        [Parameter(Mandatory = $true)]
        [string]$Word
    )
        Start-Process "https://www.google.com/?gws_rd=ssl#q=define:$Word"
}

Here’s an example I just used today, when I read Don Jones’ post about Jeffrey Snover’s promotion to Technical Fellow: http://donjones.com/2015/09/02/congratulations-jsnover-a-well-earned-honor.

Functions and Code from my Profile ($PROFILE)01

And, because why not, here’s a photo of Jeffrey Snover and me. For those of you new to PowerShell, I’m on the right; Snover, PowerShell’s inventor, is on the left. Thanks to PowerShell.org and Will for the photo.

Me and Snover (front)

This next inclusion isn’t a function, but has turned out to be quite helpful. I recently posted it on Twitter. By entering the alias “snip” into the console, I’m able to quickly open the snipping tool. In fact, I just used the alias to grab the screen capture, for the Get-Definition function, above.

Set-Alias -Name snip -Value "$env:SystemRoot\system32\SnippingTool.exe"

Here’s a screen capture of the Snipping Tool program.

Functions-and-Code-from-my-Profile-PROFILE02

This next one is also not a function, but extremely helpful, and likely to be some of the oldest code in my profile. This, while quite small, saves me all the time. Here’s how it works: When the PowerShell console opens, it checks the title bar — the WindowsTitle — for any left or right square brackets: this [, or this ]. If it doesn’t find them, it then modifies the WindowsTitle to include my computer’s name inside square brackets. It is extremely handy to be able to quickly verify that I’m typing in to the console on my machine and not inside a console on a RDP session. Yes, I still RDP for some things, and yes, I will also, at times, open a PowerShell console inside the RDP session (as strange as that may be).

# Set Console Window Title
If (-not($Host.UI.RawUI.WindowTitle -like "*`[*`]*")) {
    $Host.UI.RawUI.WindowTitle += " [$($env:COMPUTERNAME)]"
}

Since I’ll occasionally take and upload screen captures (see the first example, above), I wanted a quick way to remove my edited WindowsTitle, so it didn’t show my computer’s name. That gave way to this micro function to return the WindowsTitle to its default (yes, it’s hard coded) — my console is always elevated (however, the log on to my computer is not). I suppose what I should do, is assign the default text, when the console is first opened, and before the WindowsTitle is changed, into a variable for later use in this function.

# Set Default Console Window Title
Function Set-WindowTitleDefault {
    $Host.UI.RawUI.WindowTitle = 'Administrator: Windows PowerShell'
}

The last function I’ll share today is called Get-PSRemotingSession and it allows me to see if anyone is actively using PowerShell Remoting on a specific computer, or computers. It’s basically a wrapper function for Get-WSManInstance (without having to remember the required parameters).

Function Get-PSRemotingSession([string[]]$ComputerName) {
    Foreach ($C in $ComputerName) {
        Get-WSManInstance -ComputerName $C -ResourceURI Shell -Enumerate | Select-Object -Property Owner,ClientIP
    }
}

That’s all I’ve got for today. I hope that some of these might have been helpful enough to incorporate into your profile. If there’s things you can’t live without in your profile, that you want to share, then comment below, or share them on Twitter.

Update: The last function I introduced, Get-PSRemotingSession, has been updated. Only returning the Owner and ClientIP was fine when I only ran it against a single computer. The problem, when I ran it against multiple computers, is that I didn’t know the name of the computer that had the remote session — it wasn’t included in the output. Therefore, I updated the function, as seen below, so that it’ll indicate the computer name. It can’t get the computer name as part of what’s returned from Get-WSManInstance (without resolving the ClientIP), but it can based on which computer it’s checking to begin with (the value in $C). Here’s the updated version:

Function Get-PSRemotingSession([string[]]$ComputerName) {
    Foreach ($C in $ComputerName) {
        Get-WSManInstance -ComputerName $C -ResourceURI Shell -Enumerate | Select-Object -Property @{N='ComputerName';E={$C}},Owner,ClientIP
    }
}

Twitter Reply – Rebel Doesn’t Mean Rebel to Everyone

Twitter Reply to: no link

I saw a recent Twitter #PowerShell post and while I wanted to respond to it on Twitter, I didn’t. I was opposed to what it said, but I didn’t feel it was my place to call anyone out in that fashion. That’s why there isn’t a link above, where they might’ve been otherwise.

The twitter post in which I’m referring indicated that a person was a “rebel” by not using an approved PowerShell verb. I saw the included :P, and that’s fine, but only if the author is joking about the whole thing — I didn’t get that they were.

To use an unapproved verb, especially while knowing you shouldn’t, is everything that PowerShell is trying to stay away from. Part of the success of PowerShell has been the consistency in which it’s been developed by Microsoft, and others. While you might consider yourself a rebel, I consider what you’re doing, a problem.

I will admit, there has been a time or two that I’ve struggled with the best option for my verb, but I’ve always found one. Here’s my recommendation if you are not satisfied with the list of approved verbs: Use an alias; don’t be a “rebel.”

Let’s say I have a function I want to invoke using the name Smack-Yourself, and it looks like this:

Function Smack-Yourself {
    Write-Output -Verbose "I've been smacked!"
}

I can tell you without looking; smack is not an approved verb. We’ll check if it exists anyway.

PS C:\> Get-Verb -Verb Smack
PS C:\>

Nope, this verb hasn’t (yet?) been approved by Microsoft.

Instead of using an unapproved verb, let’s give our function an approved verb and then make an alias we can use instead. There’s no best practice for aliases; name them whatever you want. As far as your function name, please stay consistent and follow the verb dash noun naming convention, using an approved verb.

Set-Alias -Name Smack-Yourself -Value Pop-Yourself

Function Pop-Yourself {
    Write-Output -Verbose "I've been smacked!"
}

In the example above, we’ve used Set-Alias to modify (or create) an alias, called Smack-Yourself. When run, this alias will ultimately execute the Pop-Yourself function, where Pop is an approved verb. Even if the approved verb you choose doesn’t perfectly align with the verb you wanted to use, you can use whatever you want, if you’re using an alias.

Determine if the Alias, or Function Name was Used

As a Windows PowerShell enthusiast, I am often thinking about PowerShell. Seems logical. Can I do this? What will happen if I do that? Why doesn’t this work!? Well, I had one of those thoughts recently. Can I determine if a function was called using an alias, or if it was called using the function name?

This thought occurred to me because of a change I considered adding to one of my functions. The idea was, that I wanted to prompt the user for confirmation to run a function (within the function), if an alias was used, and not prompt the user when the full function name was used. This lined up with the assumption that an alias could be accidentally entered more easily than the function name, and that we may want to take precautions when the alias was used.

My first consideration was to get the last command entered by the user, by using: (Get-History -Count 1).CommandLine. The problem with that “option,” is that the history isn’t updated until after the function has ended. This means that I wouldn’t know what was entered by the user while the function was running. Take a look at the example below. When I run the function Get-TMHistory the first time, nothing is displayed (in a fresh console), but something is displayed after the second run.

Function Get-TMHistory {
	Get-History -Count 1
}

PS C:\> Get-TMHistory
PS C:\> Get-TMHistory

  Id CommandLine
  -- -----------
   1 Get-TMHistory

I came up with a another idea. Let’s start by creating a new function called Get-TMHowCalled. In addition, we’ll need to create an alias for the function – let’s use ghc. I’ve included the New-Alias cmdlet’s -Force parameter in case there’s already an alias with that name. If there is, then New-Alias acts like Set-Alias, and updates the alias (instead of throwing an error). Note: This option could be problematic if it replaced ghc, and ghc was used for a different purpose.

New-Alias -Name ghc -Value Get-TMHowCalled -Force

Function Get-TMHowCalled {

}

The next thing to do is to construct the logic that will check and determine if the alias was used, or if the full function name was used. This is simple function: check, and echo what was entered by the user.

Function Get-TMHowCalled {
    If () {
        Write-Output -Verbose 'Alias was used.'
    } ElseIf () {
        Write-Output -Verbose 'Function name was used.'
    }
}

The secret here is the $MyInvocation automatic variable and its Line property. This stores what was entered, and so it can be used to determine how the function was called – by alias or by name. The best part, is that the variable is populated when the function is called and can, therefore, be queried inside the function. This variable can only be used in “scripts, functions, and script blocks.” You can read more about it using this command: Get-Help about_Automatic_Variables.

Function Get-TMHowCalled {
    If ($MyInvocation.Line -eq 'ghc') {
        Write-Output -Verbose 'Alias was used.'
    } ElseIf ($MyInvocation.Line -eq 'Get-TMHowCalled') {
        Write-Output -Verbose 'Function name was used.'
    }
}

Hope this proves to be helpful for someone. It might be for me.

Add CMD’s ver to PowerShell

One of the great things about Windows PowerShell is that it can run Windows native command line tools, such as ping, ipconfig, and others. There are times, however, when there are exceptions. While recently working in the console, I brainlessly entered ‘ver’ (without the quotes), and it didn’t return what I expected. Instead of printing ‘Microsoft Windows [Version 6.3.9600]’ to the console, it reported that ver wasn’t recognized.

PS C:\> ver
ver : The term 'ver' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ ver
+ ~~~
    + CategoryInfo          : ObjectNotFound: (ver:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

While I wouldn’t recommend opening CMD to run the command, because seriously get out of that habit already, you can switch to CMD from inside the PowerShell console, as demonstrated below.

PS C:\> cmd
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\>ver

Microsoft Windows [Version 6.3.9600]

C:\>exit
PS C:\>

Still, this wasn’t quite as native as I wanted. For whatever reason, I wanted to type ver, and get the identical output I was used to seeing,… regardless of the fact that the ver output doesn’t really tell me much.

The first thing I did was launch the ISE and create an alias for ver. Keep in mind, that you may need to add -ErrorAction SilentlyContinue to the New-Alias cmdlet, if you run the script more than once inside the ISE. The problem here is that you’ll receive an error if you try and create an alias that is already being used. Either that, or you can add the -Force parameter and make New-Alias act like Set-Alias (and overwrite the alias even though it’s not really changing it).

New-Alias -Name ver -Value Get-TMVersion

Next, I constructed a function called Get-TMVersion that the alias ver would use. Inside the function, I created a single variable, $OS, and assigned it the results of a Get-CimInstance command. If for some reason you’re still using PowerShell 2.0, this can be replaced by the Get-WmiObject equivalent: Get-WmiObject -Class Win32_OperatingSystem.

Function Get-TMVersion {
    $OS = Get-CimInstance -ClassName Win32_OperatingSystem
}

Once I have this data stored in a variable, I can begin checking for a value in the variable, and then, building out my results to match the native Windows command. The If statement checks to see if the Name property of $OS begins with the string Microsoft Windows. Providing it does, it sets a second variable, $Version, as seen in the example below. Once that’s complete, it then echos a blank line, the $Version variable, and then echos a second blank line. At this point, $Version should be identical to the ver command’s standard output.

Function Get-TMVersion {
    $OS = Get-CimInstance -ClassName Win32_OperatingSystem
    If ($OS.Name -like 'Microsoft Windows*') {
        $Version = "Microsoft Windows [Version $($OS.Version)]"
    }
    Write-Output -Verbose `r`n$Version`r`n
}

Here’s the function in action.

PS C:\> ver

Microsoft Windows [Version 6.3.9600]

PS C:\>

Boring, but it works. I should note that this function would probably be best served to include some error checking, in case setting the $OS variable errors out. In addition, I suspect there are probably other ways to produce the same resul–… (pause, keyboard keys clicking) …ugh, here’s a couple variations of yet another, simpler way.

PS C:\> cmd /c ver

Microsoft Windows [Version 6.3.9600]
PS C:\> cmd /c ver;echo ''

Microsoft Windows [Version 6.3.9600]

PS C:\>

It seems I could have just dropped a tiny bit of text into my function and called it a day. If you’ve ever read anything else I’ve written and posted, then you may have noticed a pattern. I seem to do things the hard way, long before I figure out a simpler way. I like it that way, though. If anything, it keeps me thinking, and therefore, improving my PowerShell skills overall.

Function Get-TMVersion {
    cmd /c ver
    Write-Output -Verbose ''
}

about_Profiles

This post is the help rewrite for about_Profiles. While the help files for Windows PowerShell are invaluable, the idea behind a rewrite is so true beginners might even better understand the help file concepts. At times, some things discussed in the Windows PowerShell help file will not be included in a help rewrite. Therefore, it is always best to read the actual help file after reading this post. (PS3.0)

Once you start using your profile ($PROFILE), you’ll have a hard time not using it and not adding new things to it. When you have a profile (script), it runs each time you open a new Windows PowerShell session. My current profile does a number of things – it sets my location to the C:\ drive, sets various variables, set aliases, modifies the console’s window title, and creates several functions. Some of those functions allow me to connect remotely to Exchange servers, load the VMWare PowerCLI PSSnapin (without having to remember its name), use Wake-on-LAN to wake my home computer, and several others.

There are several different profiles based on the host and the user. To see your profile, type $PROFILE and press Enter in the console or Integrated-Scripting Environment (ISE). As seen in the example below, this will display the path to the profile. Line 1 is typed in the standard PowerShell console and Line 3 in the ISE.

PS C:\> $PROFILE
C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
PS C:\> $PROFILE
C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1

There are more profiles than these two and they are active under different circumstances. To see all the profiles, pipe the $PROFILE variable to the Select-Object cmdlet with the wildcard character.

PS C:\> $PROFILE | Select-Object -Property *

AllUsersAllHosts       : C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
AllUsersCurrentHost    : C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
CurrentUserAllHosts    : C:\Users\tommymaynard\Documents\WindowsPowerShell\profile.ps1
CurrentUserCurrentHost : C:\Users\tommymaynard\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Length                 : 82

Based on the information on the left hand side, there are different profiles based on who is using what host. While it’s not recommended to modify the profiles in the System32 directory, you should be able to tell what these do. The one on line1 is run for any user, regardless of what host (console/ISE) they use. The second one on line 2 is for any user in the current host. The third is for the current user, me, in all the hosts, and the final one is for me in the current host. These last two are the one’s you can modify.

But just by having a path stored in $PROFILE, doesn’t actually mean the file, or profile, actually exists. Use the Test-Path cmdlet to determine if the file/profile exists.

PS C:\> Test-Path $PROFILE
False

If this returns False, then you do not have a profile and so it will need to be created. You can create this file but using the New-Item cmdlet.

PS C:\> New-Item -Type File -Path $PROFILE -Force

    Directory: C:\Users\tommymaynard\Documents\WindowsPowerShell

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         8/20/2014  12:39 PM          0 Microsoft.PowerShell_profile.ps1

These two commands can be put together in an If-Else statement that will test for the file, and if not found, will create the file. Once you have a profile file created, you can open it up and start adding to it. Remember that every time you open the matching console, the profile script will run.

If (-not(Test-Path $PROFILE)) {
    New-Item -Type File -Path $PROFILE -Force
}

Here’s some quick examples of things that could be added to a profile. The first example  changes the prompt location from its default to the root of the C:\ drive.

Set-Location \

The next example sets aliases for c and gh. The first example, in line 1, will allow me to use the letter c to run the Clear-Host cmdlet and in line 2, will allow me to use the alias gh in place of typing out Get-Help.

Set-Alias -Name c -Value Clear-Host
Set-Alias -Name gh -Value Get-Help

These next example allow me to set variables inside my profile. This will allow me to use $DCs to return DC01, DC02, DC03, allow me to use $hosts to return the path of my hosts file, and allow me to return all my web and data servers by using the $AppServers variable. Noticed that $AppSevers is a mulit-dimentional array. Use $AppServers[0] to return the web servers and $AppServers[1] to return the data servers.

Set-Variable -Name DCs -Value 'DC01','DC02','DC03'
Set-Variable -Name hosts -Value "$env:SystemRoot\System32\drivers\etc\hosts"
Set-Variable -Name AppServers -Value @(('web01','web02','web03'),('data01','data02','data03'))

You can also create functions. This function allows me to type Add-VMC to load the VMware PSSnapin. For me, it’s easier to remember this short function name than remembering the PSSnapin name or having to type out Get-PSSnapin -Registered to find the name.

Function Add-VMC {
	Add-PSSnapin VMware.VimAutomation.Core
}

Start using your $PROFILE today and everything you can to help personalize your PowerShell experience. Keep in mind that profiles do not exist in remote sessions.

Learn More

This information, and more, is stored in the help file about_Profile that comes with Windows PowerShell. This information can be read by typing any of the commands below. The first example will display the help file in the Windows PowerShell console, the second example will open the full help in it’s own window, the third example will send the contents of the help file to the clipboard (so it can be pasted into Word, Notepad, etc.), and the fourth example will open the help file in Notepad.

PS C:\> Get-Help about_variables
PS C:\> Get-Help about_variables -ShowWindow
PS C:\> Get-Help about_variables| clip
PS C:\> Notepad $PSHOME\en-us\about_Variables.help.txt