Last active
August 29, 2015 14:07
-
-
Save KirkMunro/97892e7d95541dcef0e3 to your computer and use it in GitHub Desktop.
Hacking PowerShell command history
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# IMPORTANT NOTE: This hack has evolved, and HistoryPx is now a full PowerShell module | |
# that is hosted on GitHub and can be found here: github.com/KirkMunro/HistoryPx | |
# First set up some interesting hacks | |
New-Module -Name HistoryPx -ScriptBlock { | |
$PSModule = $ExecutionContext.SessionState.Module | |
$global:__ = $null | |
$global:MaximumDetailedHistoryCount = 50 | |
$global:PSDefaultParameterValues['Out-Default:OutVariable'] = 'global:__' | |
$commandHistory = [ordered]@{} | |
$previousOutput = $null | |
$onCommandInvokeBreakpoint = Set-PSBreakpoint -Variable __ -Mode Write -Action { | |
# When inside a variable breakpoint action, access the variable directly to get the old value, | |
# and use Get-Variable -Name $_.Variable -ValueOnly to get the new value | |
$success = $? -eq $true | |
$variableValue = (Get-Variable -Name $_.Variable -Scope Global -ValueOnly).Clone() | |
if ($variableValue -ne $null) { | |
$output = $variableValue | |
$previousHistoryInfo,$historyInfo = Get-History -Count 2 | |
if ($script:previousOutput -eq $output) { | |
$output = $null | |
} else { | |
$sortA = @($script:previousOutput | Sort-Object) | |
$sortB = @($output | Sort-Object) | |
if ($sortA.Count -eq $sortB.Count) { | |
$equivalent = $true | |
for ($index = 0; $index -lt $sortA.Count; $index++) { | |
if ($sortA[$index] -ne $sortB[$index]) { | |
$equivalent = $false | |
break | |
} | |
} | |
if ($equivalent) { | |
$output = $null | |
} else { | |
$script:previousOutput = $output | |
} | |
} else { | |
$script:previousOutput = $output | |
} | |
} | |
$script:commandHistory.Add($historyInfo.Id.ToString(),[pscustomobject]@{ | |
PSTypeName = 'DetailedHistoryInfo' | |
Id = $historyInfo.Id | |
CommandLine = $historyInfo.CommandLine | |
Duration = $(if ($historyInfo.ExecutionStatus -eq [System.Management.Automation.Runspaces.PipelineState]::Completed) {$historyInfo.EndExecutionTime - $historyInfo.StartExecutionTime}) | |
ExecutionStatus = $historyInfo.ExecutionStatus | |
Success = $success | |
Errors = @($global:Error.where{$_.InvocationInfo.HistoryId -eq $historyInfo.Id}) | |
Output = $(if ($output -ne $null) {$output | Where-Object {$_.PSTypeNames -notcontains 'DetailedHistoryInfo'}}) | |
}) | |
while ($script:commandHistory.Count -gt $global:MaximumDetailedHistoryCount) { | |
$script:commandHistory.RemoveAt(0) | |
} | |
} | |
} | |
Update-TypeData -TypeName DetailedHistoryInfo -DefaultDisplayPropertySet Id,CommandLine,Duration,Success | |
function Get-HistoryDetail { | |
[CmdletBinding()] | |
[OutputType('DetailedHistoryInfo')] | |
param() | |
foreach ($key in $commandHistory.Keys) { | |
$commandHistory[$key] | |
} | |
} | |
$PSModule.OnRemove = { | |
Remove-PSBreakpoint -Breakpoint $onCommandInvokeBreakpoint | |
$global:PSDefaultParameterValues.Remove('Out-Default:OutVariable') | |
Remove-Variable -Scope Global -Name MaximumDetailedHistoryCount | |
Remove-Variable -Scope Global -Name __ | |
} | |
} | Import-Module | |
# Now generate some command history | |
1 | |
2 | |
'Fish' | |
gsv blah | |
3 > $null | |
gps -id $pid | |
get-eventlog -foo bar | |
[void]3 | |
4 | |
$true | |
# And show the detailed history output. Intriguing, no? :) | |
Get-HistoryDetail | ft Id,CommandLine,Success,Output,Errors |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment