Not too long ago we published an article and PowerShell function you could use to copy a PowerShell command. You could use this technique to quickly build your own tools based on existing commands. Let me give you another example that’s a bit more detailed. First, there must be a need. For me, I want a quick way to get command history, but I want to limit my history to unique entries.
For example, here are the last 10 commands I have run using Get-History. Using the Get-History cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
In case you were wondering, the gmb command is an alias for one of my functions to display battery life. You’ll see that I have some duplicate commands. I’d prefer to have a unique list, which I can do like this:
It works.
Creating a unique list with PowerShell's Get-History cmdlet. (Image Credit: Jeff Hicks)
But I don't want to have to always type all of this. I'm happy with Get-History. I just need to add some functionality to it. So I'll use my Copy-Command tool to create a new version of Get-History, where I'll call Get-MyHistory. If you are following along, run this in the PowerShell ISE.
Without making any changes, the Get-MyHistory function is identical to Get-History because at its core, all I am doing is passing parameters to the underlying command.
I am splatting the automatic variable $PSBoundParameters to Get-History. My copy includes all the same parameters as well as complete comment-based help. Eventually, I will go back and modify the help to reference anything new that I add and to remove some things, such as the online help link to Get-History.
Remember, the purpose of creating my own tool is to simplify what I have to type at the prompt. I will also stress that this is a tool for me to use interactively, not something I would include in a script. If I did, I would have to make sure to include the Get-MyHistory function script.
Defining Parameters
Although my initial need was to find unique results, I don't want to hard-code myself into a corner. There might be situations where I want to use my new tools to grab everything. We can do this by adding a switch parameter.
Param(
[Parameter(Position=0, ValueFromPipeline=$true)]
[ValidateRange(1, 9223372036854775807)]
[long[]]$Id,
[Parameter(Position=1)]
[ValidateRange(0, 32767)]
[int]$Count,
#parameters that I added
[switch]$Unique,
[regex]$Pattern
)
I also want an easy way to find previous commands using a regular expression pattern. Depending on my needs, I can remove existing parameters, add validation tests or set default values.
Begin Scriptblock
Code in the Begin block runs once, before any pipelined objects are processed, although that’s not really an issue with this command. What I prefer to do is insert some Write-Verbose commands that will provide some debug and trace information. Some of these commands are auto-generated when you run Copy-Command.
Begin {
#insert verbose messages to provide debugging and tracing information
Write-Verbose "Starting $($MyInvocation.Mycommand)"
Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)"
Write-Verbose ($PSBoundParameters | out-string)
#remove Unique and Pattern parameters if used since they are not part of Get-History
if ($Unique) {
$PSBoundParameters.Remove("Unique") | Out-Null
}
if ($Pattern) {
$PSBoundParameters.Remove("Pattern") | Out-Null
}
} #begin
Assuming you intend to splat $PSBoundParameters to the original command, you have to pay attention to this variable. In this instance, Get-History isn’t designed to use –Unique or –Pattern. If I try to run Get-History with them, PowerShell will complain and throw an exception. That’s why you see me removing them from $PSBoundParameters. The variables still exist, so if I use –Pattern, I can still reference $Pattern in my function. They just aren’t part of $PSBoundParameters.
Process Scriptblock
In the Process scriptblock, I can grab history commands using the same parameters, ID and Count.
If I'm searching for a command based on a pattern, I filter the results.
if ($Pattern) {
#use v4 Where method for performance purposes
Write-Verbose "Searching for commandlines matching $pattern"
$all = $all.where({$_.commandline -match $pattern})
}
Finally, I filter for unique history items if requested.
All that remains is to write the results to the pipeline.
I also decided I might want to know how long a command took to run so I added an additional property that is calculated at run time.
End Scriptblock
Code in the End scriptblock runs once after everything has been processed. In my case, I don't have any cleanup to do, so I display a verbose message.
End {
Write-Verbose "Ending $($MyInvocation.Mycommand)"
} #end
The Final Tool
When I load the function into my PowerShell session, I have a new tool at my disposal. Get-MyHistory Help. (Image Credit: Jeff Hicks)
I can run it like Get-History. Executing Get-MyHistory. (Image Credit: Jeff Hicks)
Or I can use my new parameters. Using parameters with Get-MyHistory. (Image Credit: Jeff Hicks)
The additional property is present but not part of the default display.
Here’s my result. Out-GridView for Get-MyHistory results. (Image Credit: Jeff Hicks)
Without too much difficulty I created a new tool to get command history the way I want it. For the record, here’s the complete command.
#requires -version 4.0
<#
This is a copy of:
CommandType Name ModuleName
----------- ---- ----------
Cmdlet Get-History Microsoft.PowerShell.Core
Created: 5/18/2015
Author : Jeff
****************************************************************
* DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
* THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF *
* YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
* DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. *
****************************************************************
#>
Function Get-MyHistory {
<#
.SYNOPSIS
Gets a list of the commands entered during the current session.
.DESCRIPTION
The Get-MyHistory cmdlet gets session history, that is, the list of commands entered during the current session. This is a modified version of Get-History.
Windows PowerShell automatically maintains a history of each session. The number of entries in the session history is determined by the value of the $MaximumHistoryCount preference variable. Beginning in Windows PowerShell 3.0, the default value is 4096.
You can save the session history in XML or CSV format. By default, history files are saved in the home directory, but you can save the file in any location.
For more information about the history features in Windows PowerShell, see about_History (http://go.microsoft.com/fwlink/?LinkID=113233).
.PARAMETER Count
Displays the specified number of the most recent history entries. By, default, Get-MyHistory gets all entries in the session history. If you use both the Count and Id parameters in a command, the display ends with the command that is specified by the Id parameter.
In Windows PowerShell 2.0, by default, Get-MyHistory gets the 32 most recent entries.
.PARAMETER Id
Specifies the ID number of an entry in the session history. Get-MyHistory gets only the specified entry. If you use both the Id and Count parameters in a command, Get-MyHistory gets the most recent entries ending with the entry specified by the Id parameter.
.PARAMETER Pattern
A regular expression pattern to match something in the commandline.
.EXAMPLE
PS C:>Get-MyHistory -unique
This command gets the unique entries in the session history. The default display shows each command and its ID, which indicates the order of execution as well as the command's start and stop time.
.EXAMPLE
PS C:>Get-MyHistory -pattern "service"
This command gets entries in the command history that include "service".
.NOTES
The session history is a list of the commands entered during the session. The session history represents the order of execution, the status, and the start and end times of the command. As you enter each command, Windows PowerShell adds it to the history so that you can reuse it. For more information about the command history, see about_History (http://go.microsoft.com/fwlink/?LinkID=113233).
Beginning in Windows PowerShell 3.0, the default value of the $MaximumHistoryCount preference variable is 4096. In Windows PowerShell 2.0, the default value is 64. For more information about the $MaximumHistoryCount variable, see about_Preference_Variables (http://go.microsoft.com/fwlink/?LinkID=113248).
Learn more about PowerShell:
****************************************************************
* DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
* THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF *
* YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
* DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. *
****************************************************************
.INPUTS
Int64
.OUTPUTS
Microsoft.PowerShell.Commands.HistoryInfo
.LINK
Add-History
Clear-History
Invoke-History
.LINK
about_History
#>
[CmdletBinding()]
Param(
[Parameter(Position=0, ValueFromPipeline=$true)]
[ValidateRange(1, 9223372036854775807)]
[long[]]$Id,
[Parameter(Position=1)]
[ValidateRange(0, 32767)]
[int]$Count,
#parameters that I added
[switch]$Unique,
[regex]$Pattern
)
Begin {
#insert verbose messages to provide debugging and tracing information
Write-Verbose "Starting $($MyInvocation.Mycommand)"
Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)"
Write-Verbose ($PSBoundParameters | out-string)
#remove Unique and Pattern parameters if used since they are not part of Get-History
if ($Unique) {
$PSBoundParameters.Remove("Unique") | Out-Null
}
if ($Pattern) {
$PSBoundParameters.Remove("Pattern") | Out-Null
}
} #begin
Process {
#splat bound parameters to Get-History
$all = Get-History @PSBoundParameters
if ($Pattern) {
#use v4 Where method for performance purposes
Write-Verbose "Searching for commandlines matching $pattern"
$all = $all.where({$_.commandline -match $pattern})
}
if ($Unique) {
Write-Verbose "Selecting unique items"
$all = $all | Select-Object -Unique
}
#write results to pipeline and add a new property
$all | Add-Member -MemberType ScriptProperty -Name "Runtime" -value {$this.EndExecutionTime - $this.StartExecutionTime} -PassThru -force
} #process
End {
Write-Verbose "Ending $($MyInvocation.Mycommand)"
} #end
} #end function Get-MyHistory
#define an optional alias
Set-Alias -Name gmh -Value Get-MyHistory
I’m really liking the ability to copy commands and create my own tools. I have a few more that will be sharing with you. And if you’ve built something cool, I hope you’ll let everyone know in the article comments.