TM-SessionHistory.psm1

using namespace Microsoft.PowerShell.Commands
using namespace System.Collections.Generic
using namespace System.IO

$script:IsAdmin = Test-Admin

# Create the HistoryPath variable for use within the functions.
$script:HistoryPath = Join-Path -Path (Get-ShellPath).FullName -ChildPath 'history.log'

# Create a SessionGuid variable containing a guid to identify the session by. Used by the SessionHistory commands.
$script:SessionGuid = (New-Guid).Guid

# Set local PSVersion variable for use within the functions.
$script:PSVersion = Get-PSVersionString


class SessionHistory {
<#
    .SYNOPSIS
    This class represents a PowerShell session history item.
 
    .DESCRIPTION
    A SessionHistory object encapsulates the properties related to a single command executed in a PowerShell session.
    It includes distinguishing information about the current powershell session by pulling profile script variables
    ($script:PSVersion, $script:SessionGuid, $script:IsAdmin) and gathering current runtime state and history
    information.
 
    This class, combined with the Get-SessionHistory, Set-SessionHistory, and Remove-SessionHistory Commands facilitates
    easy and safe documentation of a sessions command history.
 
    Session History values are saved to the $script:HistoryPath by the Set-SessionHistory function which is called
    during prompt execution.
 
    The Get-SessionHistory command facilitates easy extraction of commands from a given session history.
    This can be used after an iterative design/testing session to pull out specific commands for documentation purposes.
 
    The Remove-SessionHistory command provides options for removing specific commands, or entire session histories from
    the $script:HistoryPath in case you accidentally paste a password into your session.
#>

    $Client
    $Guid
    $Admin
    $Provider
    $CurrentPath
    $ExecutionTime
    $ExecutionDuration
    $Command

    SessionHistory([HistoryInfo]$HistoryInfo) {
        $this.Client            = $script:PSVersion
        $this.Guid              = $script:SessionGuid
        $this.Admin             = $script:IsAdmin
        $this.Provider          = $pwd.Provider.Name
        $this.CurrentPath       = (Get-CurrentPath)
        $this.ExecutionTime     = $HistoryInfo.StartExecutionTime.ToString('yyyy-MM-ddTHH:mm:ss K')
        $this.ExecutionDuration = (Get-LastExecutionDuration $HistoryInfo)
        $this.Command           = $HistoryInfo.CommandLine
    }

    SessionHistory([PSCustomObject]$CustomObject) {
        $this.Client            = $CustomObject.Client
        $this.Guid              = $CustomObject.Guid
        $this.Admin             = $CustomObject.Admin
        $this.Provider          = $CustomObject.Provider
        $this.CurrentPath       = $CustomObject.CurrentPath
        $this.ExecutionTime     = $CustomObject.ExecutionTime
        $this.ExecutionDuration = $CustomObject.ExecutionDuration
        $this.Command           = $CustomObject.Command
    }

    [boolean] Equals($otherHistory) {
        if (
            ($null                            -ne $otherHistory)                   -and
            ($otherHistory.GetType().FullName -eq 'SessionHistory')                -and
            ($this.Client                     -eq $otherHistory.Client)            -and
            ($this.Guid                       -eq $otherHistory.Guid)              -and
            ($this.Admin                      -eq $otherHistory.Admin)             -and
            ($this.Provider                   -eq $otherHistory.Provider)          -and
            ($this.CurrentPath                -eq $otherHistory.CurrentPath)       -and
            ($this.ExecutionTime              -eq $otherHistory.ExecutionTime)     -and
            ($this.ExecutionDuration          -eq $otherHistory.ExecutionDuration) -and
            ($this.Command                    -eq $otherHistory.Command)
        ) {
            return $true
        } else {
            return $false
        }
    }

    [string] ToString() {
        # This prevents "The script failed due to call depth overflow." on Windows PowerShell.
        # This was "return ($this | ConvertTo-Json -Compress)" but that had depth issues.
        return (
            [PsCustomObject]@{
                Client            = $this.Client
                Guid              = $this.Guid
                Admin             = $this.Admin
                Provider          = $this.Provider
                CurrentPath       = $this.CurrentPath
                ExecutionTime     = $this.ExecutionTime
                ExecutionDuration = $this.ExecutionDuration
                Command           = $this.Command
            } | ConvertTo-Json -Compress
        )
    }
}


function Get-SessionHistory {
<#
    .SYNOPSIS
    Retrieves PowerShell session history.
 
    .DESCRIPTION
    Get-SessionHistory is a function that retrieves the history of commands executed in a previous PowerShell or
    Bash shell session. To get this functionality in your bash sessions you will also need to edit your .bashrc file
    as shown in GISTLINKGOESHERE.
 
    The SessionHistory items are retrieved from a script defined JSON file ($script:HistoryPath).
    The function can retrieve the history associated with a specific session by GUID or all available history entries
    from the file.
 
    .PARAMETER Guid
    The unique identifier associated with a specific PowerShell session.
    If specified, only the history from this session is retrieved.
 
    By Default this is populated with the profile script defined SessionGuid.
 
    .PARAMETER All
    If this switch is used then all available session history is returned.
 
    .OUTPUTS
    List[SessionHistory] - The function returns a list of SessionHistory objects.
 
    .EXAMPLE
    # The retrieved SessionHistory items can be parsed to easily select commands for reuse from your current session.
    Get-SessionHistory | Select-Object -ExpandProperty Command
 
    .EXAMPLE
    # Retrieve the commands from the 5 latest sessions.
    Get-SessionHistory -All | Select-Object -ExpandProperty Guid -Unique | Select-Object -Last 5 | %{
        Write-Host "`n`nGuid: $_"
        Get-SessionHistory -Guid $_ | Select-Object -ExpandProperty Command
    }
#>

    [CmdletBinding()]
    [CmdletBinding(DefaultParameterSetName = 'Guid')]
    [OutputType([List[SessionHistory]])]
    param(
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Guid',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$Guid = $script:SessionGuid,

        [Parameter(
            Mandatory,
            ParameterSetName = 'All'
        )]
        [switch]$All
    )

    $SessionHistory = [List[SessionHistory]]::New()
    $HistoryContent = Get-Content -Path $script:HistoryPath | ConvertFrom-Json

    foreach ($item in $HistoryContent) {
        if (($PSCmdlet.ParameterSetName -eq 'All') -or ($item.Guid -eq $Guid)) {
            $SessionHistory.Add([SessionHistory]::New($item))
        }
    }

    return $SessionHistory
}


function Remove-SessionHistory {
<#
    .SYNOPSIS
    Deletes specified entries from the PowerShell session history.
 
    .DESCRIPTION
    The Remove-SessionHistory function can either delete all entries associated with a specific session GUID or a single
    specified history item from the session history stored in the JSON history file ($script:HistoryPath).
 
    This function helps enhance the security of your scripting environment by providing the ability to remove commands
    from the session history that may have exposed sensitive information, such as passwords.
 
    Please note that this function does not replace any session history information stored in PowerShell's built-in
    history location, which is accessible via (Get-PSReadLineOption).HistorySavePath. When removing sensitive
    information users should also manage their PSReadLine history from that location as well.
 
    .PARAMETER Guid
    Specifies the unique session GUID to remove history entries for.
 
    .PARAMETER HistoryItem
    Specifies a particular SessionHistory item to remove from the $script:HistoryPath file.
 
    .EXAMPLE
    # Remove the previously run command from the session history.
    Get-SessionHistory | Select-Object -Last 1 | Remove-SessionHistory
#>

    [CmdletBinding()]
    [CmdletBinding(DefaultParameterSetName = 'HistoryItem')]
    [OutputType([Void])]
    param(
        [Parameter(
            Mandatory,
            ParameterSetName = 'Guid',
            ValueFromPipelineByPropertyName
        )]
        [string]$Guid,

        [Parameter(
            Mandatory,
            ParameterSetName = 'HistoryItem',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [SessionHistory]$HistoryItem
    )
    $OutputHistory = [List[SessionHistory]]::New()
    $SessionHistory = Get-SessionHistory -All

    foreach ($item in $SessionHistory) {
        if (
            (($PSCmdlet.ParameterSetName -eq 'Guid') -and ($item.Guid -ine $Guid)) -or
            (($PSCmdlet.ParameterSetName -eq 'HistoryItem') -and ($HistoryItem.Equals($HistoryValue) -eq $false))
        ) {
            $OutputHistory.Add($item)
        }
    }

    $OutputContent = foreach ($element in $OutputHistory) { $element.ToString() }
    $OutputContent | Out-File $script:HistoryPath -Force
}


function Set-SessionHistory {
<#
    .SYNOPSIS
    This function is run automatically during prompt execution. It records details about the last executed command's and
    appends them to the $script:HistoryPath (session history file).
 
    .DESCRIPTION
    Set-SessionHistory is used to capture the details of the last executed command in a PowerShell session and append
    them as a new SessionHistory entry to a dedicated JSON file, identified by the $script:HistoryPath variable.
 
    If no HistoryInfo information is provided, then the function captures the details of the most recently run command.
    If no $CommandHistory can be found then the function does nothing at all.
 
    This function is run at the end of the prompt, so if you close out your powershell session while a command is
    executing the commands history will not be saved.
 
    This function is run in addition to any functionality you have configured via PSReadLine.
 
    .PARAMETER CommandHistory
    The HistoryInfo object for an command executed in a PowerShell session.
    If not specified, the function will automatically retrieve the most recent command history (if any exists).
#>

    [CmdletBinding()]
    [OutputType([System.Void])]
    param(
        [Parameter(Mandatory = $false)]
        [HistoryInfo]$CommandHistory
    )

    $ErrorActionPreference = 'Ignore'
    if ($null -eq $CommandHistory) { $CommandHistory = Get-History -Count 1 }

    if (($null -ne $CommandHistory) -and ([string]::IsNullOrWhiteSpace($script:HistoryPath) -eq $false)) {
        [SessionHistory]::New($CommandHistory).ToString() | Out-File $script:HistoryPath -Append -Encoding UTF8
    }
}