Get-History Modified

It was a couple of articles ago here at PowerShell.org, where I took an unused, leftover example I had sitting in a tab inside my PowerShell editor, and used it to write an article. This happens often, but usually only when they're good examples. In this recent case, while I believe it turned out well, I used something that wasn't overly amazing, and made it worthy. I'm doing that today. Again. I think.

What I can tell you, is that the content in this second tab was small, yet complete. Its home, in fact, had been only one tab over from the function that brought us the Build In Measure-Command article, I've vaguely mentioned.

To prep, let's discuss the Get-History cmdlet. It's been around for as long as I can remember, and its purpose, as indicated by its synopsis is this: "The Get-History cmdlet gets the session history, that is, the list of commands entered during the current session." You enter a command and the command is added to the history. This cmdlet allows you to view your previously entered command(s) later, if you choose to do that. Let's start with a few PowerShell commands.

PS > Get-Date
Wednesday, April 7, 2019 1:47:56 PM
PS > Get-Random
217097233
PS > (Get-Process | Select-Object -First 1).ProcessName
AGMService
PS > (Get-Alias -Name gci).DisplayName
gci -> Get-ChildItem

After knowing we've invoked these four commands, we can invoke the Get-History cmdlet to see them in succession. The Get-History, default output returns two properties: Id and CommandLine.

PS > Get-History

  Id CommandLine
  -- -----------
   1 Get-Date
   2 Get-Random
   3 (Get-Process | Select-Object -First 1).ProcessName
   4 (Get-Alias -Name gci).DisplayName

You can't tell using this output, but there are properties that aren't shown in the default, Get-History output. In addition to the Id and CommandLine properties, there is an ExecutionStatus property, a StartExecutionTime property, and finally, an EndExecutionTime property. Here's an example that includes them all.

PS > Get-History | Select-Object -Property *

Id                 : 1
CommandLine        : Get-Date
ExecutionStatus    : Completed
StartExecutionTime : 4/7/2019 1:50:35 PM
EndExecutionTime   : 4/7/2019 1:50:35 PM

Id                 : 2
CommandLine        : Get-Random
ExecutionStatus    : Completed
StartExecutionTime : 4/7/2019 1:50:37 PM
EndExecutionTime   : 4/7/2019 1:50:37 PM

Id                 : 3
CommandLine        : (Get-Process | Select-Object -First 1).ProcessName
ExecutionStatus    : Completed
StartExecutionTime : 4/7/2019 1:50:45 PM
EndExecutionTime   : 4/7/2019 1:50:45 PM

Id                 : 4
CommandLine        : (Get-Alias -Name gci).DisplayName
ExecutionStatus    : Completed
StartExecutionTime : 4/7/2019 1:51:00 PM
EndExecutionTime   : 4/7/2019 1:51:00 PM

Id                 : 5
CommandLine        : Get-History
ExecutionStatus    : Completed
StartExecutionTime : 4/7/2019 1:51:09 PM
EndExecutionTime   : 4/7/2019 1:51:09 PM

If you just found this out, and you thought what I did when I first found out, then you may have realized that we can determine how long a command took to complete, if we do a little subtraction. Subtract the start time from the end time and we know the amount time each command has taken. And that's the little teeny chunk of code I rescued from a soon-to-be abandoned VS Code tab. I've included this simple function below, and the modified output from above. While the TimeTaken property doesn't really help with the previous commands we ran, as they all ended so quickly, it easily may help for longer running commands and scripts.

Function Get-History {
    $History = Microsoft.PowerShell.Core\Get-History | Select-Object -Property *
    $History | Select-Object Id,CommandLine,
        @{Name='TimeTaken';Expression={($_.EndExecutionTime) - ($_.StartExecutionTime)}}
} # End Function: Get-History.
PS > Get-History

Id CommandLine                                        TimeTaken
-- -----------                                        ---------
 1 Get-Date                                           00:00:00.0154352
 2 Get-Random                                         00:00:00
 3 (Get-Process | Select-Object -First 1).ProcessName 00:00:00.0110123
 4 (Get-Alias -Name gci).DisplayName                  00:00:00
 5. Get-History                                       00:00:00.0154351
 6. Get-History | Select-Object -Property *           00:00:00.0158662

While everyone is still paying attention, let's assume we have a .ps1 file saved to our Desktop on Windows. Its name is sleep.ps1 and literally, all it does is sleep for 10 seconds. After we've run it as . .\Desktop\sleep.ps1, let's rerun our modified Get-History command. It does exactly what we would expect; it indicates that the TimeTaken property is at 10 seconds.

PS > Get-History

Id CommandLine                                        TimeTaken
-- -----------                                        ---------
 1 Get-Date                                           00:00:00.0154352
 2 Get-Random                                         00:00:00
 3 (Get-Process | Select-Object -First 1).ProcessName 00:00:00.0110123
 4 (Get-Alias -Name gci).DisplayName                  00:00:00
 5. Get-History                                       00:00:00.0154351
 6. Get-History | Select-Object -Property *           00:00:00.0158662
 7. . .\Desktop\sleep.ps1                             00:00:10.0229630

That's it. A quick and simply way to determine the time each command takes to complete. Add this function to your $PROFILE script and it'll always run, instead of the standard, built-in Get-History cmdlet.

≥ Tommy Maynard (Twitter: @thetommymaynard)

About Tommy Maynard

IT Pro. Passionate for #PowerShell, #AWS (certified x2), & all things automation. I'm not done learning. Author in #PSConfBook. Writes at https://powershell.org.

6 thoughts on “Get-History Modified

    1. Tommy Maynard Post author

      Hey Mike,

      While I'm not about to try it, yes, I believe you can get the same effect by modifying the formatting file. For Windows PowerShell 5.1 that's "C:\Windows\System32\WindowsPowerShell\v1.0\PowerShellCore.format.ps1xml." Personally, however, I've never modified one of these files, as it's never been required for anything I wanted, or needed, to complete. Writing a function, which takes precedence over a cmdlet of the same name, was always the easiest and cleanest for me!

      Thank you for the comment,
      Tommy

      1. Mike Shepard

        I understand your reluctance to modify one of these files. I don't use this method very often, either.

        A couple of helpful tips along that path:
        1. In 5.1 (5.0?) the files in $pshome are there for reference only and aren't directly used. So modifying them won't have any effect.
        2. Instead of modifying them, you can "prepend" a new one to the formatting pipeline so that it takes precedence over the existing one.

        I've copied the table entry from the existing format file into a here-string and added the extra column.

        $fmtxml = @'

        HistoryWithElapsedTime

        Microsoft.PowerShell.Commands.HistoryInfo

        4
        Right

        TimeTaken

        Id

        CommandLine

        $_.EndExecutionTime - $_.StartExecutionTime

        '@ | out-file c:\temp\historyExt.format.ps1xml

        update-formatdata -PrependPath C:\temp\historyExt.format.ps1xml

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.