Retrieve local and remote PowerShell logs

There are a few PowerShell EventLogs and some files containing your PowerShell history and the commands, script blocks, etc., that you have used. This can be very helpful if your computer or servers are hacked at your office. Or, if you want to check things 😉 In this blog post, I will show you how to retrieve all those events locally and remotely and save those in an Excel sheet.

What are the PowerShell logs being collected?

In Windows, there are a few default PowerShell EventLogs available:

  • Windows PowerShell
  • PowerShellCore/Operational
  • Microsoft-Windows-PowerShell/Admin
  • Microsoft-Windows-PowerShell/Operational
  • Microsoft-Windows-PowerShell-DesiredStateConfiguration-FileDownloadManager/Operational
  • Microsoft-Windows-WinRM/Operational

These EventLogs contain information about scripts and script blocks used on the system or DSC information. (The script can modify the list to include other EventLogs if needed.)

There are also logs containing all the commands typed in your PowerShell sessions, which were recorded by the PSReadline module. The location of these files is C:\Users\{UserName}\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine.

How does the script work?

You can start the script and specify these parameters:

  • -Computername You can specify one (W2K22DC, for example) or multiple separated by a comma (“W2K22DC, W2K22Member”, for example). If not specified, it will be your local system.
  • -Filename You can specify the location of the Excel sheet in which the results will be saved. C:\Temp\Events.xlsx, for example. The FileName parameter is Mandatory.
  • -PowerShellEventLogOnly When specified, the script will only export events from the EventLogs and not from the PSReadLine history.
  • -PSReadLineHistoryOnly When specified, the script will only export the command lines found in the PSReadLine history files, not the EventLogs data.

The data is being saved to an Excel file in two tabs, one for the EventLog data and one for the PSReadline history. When you run it against multiple computers, you can filter those on the ComputerName column.

The script also checks if the specified Excel file is already present and will append data to it if that’s the case. Both tabs have a time and date suffix, including the time you ran it so that it won’t insert the same date twice.

If the script can’t access something, it will show that on screen as a warning message. Green lines are good, and grey ones are information (That it couldn’t find events in a certain log, for example)

Running the script

Below is the screen status output when I ran the script on my Windows Server 2022 Domain Controller and specified (using the -ComputerName parameter) to scan that and the Windows 2022 member server. The output is saved to c:\temp\events.csv (Using the -Filename parameter).

Screen output of the script

Example Excel file

Based on the two scanned servers above, the Excel file looks like this:

The PowerShell Eventlogs

Some of the messages are very long. To view it in the cell, you can double-click it or use the Wrap Text button to let it expand for you:

An expanded cell to view its contents

You can use the Filter on the ComputerName, for example, to display only a specific machine or to filter for a specific keyword:

Filtering computers or text

Below is a screenshot of the PSReadLine history; as you can see, it shows all your history. 🙂 (This is dangerous if you have credentials, API keys in it, etc.)

PSReadLine History

The script

Below are the contents of the Get-PowerShellLogs script. Save it somewhere (c:\scripts\Get-PowerShellLogs.ps1, for example) and run it with the abovementioned parameters. For example:

C:\Scripts\Get-PowerShellLogs.ps1 -ComputerName w2k22member -Filename C:\Temp\events.xlsx -PowerShellEventlogOnly

Get-PowerShellLogs.ps1

#Requires -RunAsAdministrator

[CmdletBinding(DefaultparameterSetname = 'All')]
param(
    [parameter(Mandatory = $false)][string[]]$ComputerName = $env:COMPUTERNAME,    
    [parameter(Mandatory = $true)][string]$Filename,
    [parameter(Mandatory = $false, parameterSetname = "EventLog")][switch]$PowerShellEventlogOnly,
    [parameter(Mandatory = $false, parameterSetname = "History")][switch]$PSReadLineHistoryOnly
)

#Validate output $filename
if (-not ($Filename.EndsWith('.xlsx'))) {
    Write-Warning ("Specified {0} filename does not end with .xlsx, exiting..." -f $Filename)
    return
}

#Check access to the path, and if the file already exists, append if it does or test the creation of a new one
if (-not (Test-Path -Path $Filename)) {
    try {
        New-Item -Path $Filename -ItemType File -Force:$true -Confirm:$false -ErrorAction Stop | Out-Null
        Remove-Item -Path $Filename -Force:$true -Confirm:$false | Out-Null
        Write-Host ("Specified {0} filename is correct, and the path is accessible, continuing..." -f $Filename) -ForegroundColor Green
    }
    catch {
        Write-Warning ("Path to specified {0} filename is not accessible, correct or file is in use, exiting..." -f $Filename)
        return
    }
}
else {
    Write-Warning ("Specified file {0} already exists, appending data to it..." -f $Filename)
}

#Check if the ImportExcel module is installed. Install it if not
if (-not (Get-Module -ListAvailable -Name ImportExcel)) {
    Write-Warning ("The ImportExcel module was not found on the system, installing now...")
    try {
        Install-Module -Name ImportExcel -SkipPublisherCheck -Force:$true -Confirm:$false -Scope CurrentUser -ErrorAction Stop
        Import-Module -Name ImportExcel -Scope Local -ErrorAction Stop
        Write-Host ("Successfully installed the ImportExcel module, continuing..") -ForegroundColor Green
    }
    catch {
        Write-Warning ("Could not install the ImportExcel module, exiting...")
        return
    }
}
else {
    try {
        Import-Module -Name ImportExcel -Scope Local -ErrorAction Stop
        Write-Host ("The ImportExcel module was found on the system, continuing...") -ForegroundColor Green
    }
    catch {
        Write-Warning ("Error importing the ImportExcel module, exiting...")
        return  
    }
    
}

#List of PowerShell event logs to search in
$Eventlogs = @(
    'Windows PowerShell'
    'PowerShellCore/Operational'
    'Microsoft-Windows-PowerShell/Admin'
    'Microsoft-Windows-PowerShell/Operational'
    'Microsoft-Windows-PowerShell-DesiredStateConfiguration-FileDownloadManager/Operational'
    'Microsoft-Windows-WinRM/Operational'
)

#Set dateformat for the Excel tabs
$date = Get-Date -Format ddMMyyhhmm

#Loop through all computers specified in $ComputerName. If not specified, it will use your local computer
foreach ($computer in $ComputerName | Sort-Object) {
    
    #Check if the computer is reachable
    if (Test-Path -Path "\\$($computer)\c$" -ErrorAction SilentlyContinue) {
        Write-Host ("`nComputer {0} is accessible, continuing..." -f $computer) -ForegroundColor Green
    
        #Eventlogs
        if (-not $PSReadLineHistoryOnly) {

            #Search all EventLogs specified in the $eventlogs variable
            $TotalEventLogs = foreach ($Eventlog in $Eventlogs) {
                $events = Get-WinEvent -LogName $Eventlog -ComputerName $computer -ErrorAction SilentlyContinue
                if ($events.count -gt 0) {
                    Write-Host ("- Exporting {0} events from the {1} EventLog" -f $events.count, $Eventlog) -ForegroundColor Green
                    foreach ($event in $events) {
                        [PSCustomObject]@{
                            ComputerName = $computer
                            EventlogName = $Eventlog
                            TimeCreated  = $event.TimeCreated
                            EventID      = $event.Id
                            Message      = $event.Message
                        }
                    }
                }
                else {
                    Write-Host ("- No events found in the {0} Eventlog" -f $Eventlog) -ForegroundColor Gray
                }
            }

            #Create an Excel file and add an Eventlog tab containing the events for the computer
            if ($TotalEventLogs.count -gt 0) {
                try {
                    $TotalEventLogs | Export-Excel -Path $Filename -WorksheetName "PowerShell_EventLog_$($date)" -AutoFilter -AutoSize -Append
                    Write-Host ("Exported Eventlog data to {0}" -f $Filename) -ForegroundColor Green
                }
                catch {
                    Write-Warning ("Error exporting Eventlog data to {0} (File in use?), exiting..." -f $Filename)
                    return
                }
            }
        }


        #PSreadLineHistory
        if (-not $EventlogOnly) {

            #Search for all PSReadLine history files in all Windows User profiles on the system
            if (-not $PowerShellEventlogOnly) {
                Write-Host ("Checking for Users/Documents and Settings folder on {0}" -f $computer) -ForegroundColor Green
                try {
                    if (Test-Path "\\$($computer)\c$\Users") {
                        $UsersFolder = "\\$($computer)\c$\Users"
                    }
                    else {
                        $UsersFolder = "\\$($computer)\c$\Documents and Settings"
                    }
                }
                catch {
                    Write-Warning ("Error finding Users/Documents and Settings folder on {0}. Exiting..." -f $computer)
                    return
                }

                Write-Host ("Scanning for PSReadLine History files in {0}" -f $UsersFolder) -ForegroundColor Green
                $HistoryFiles = foreach ($UserProfileFolder in Get-ChildItem -Path $UsersFolder -Directory) {
                    $list = Get-ChildItem -Path "$($UserProfileFolder.FullName)\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\*.txt" -ErrorAction SilentlyContinue
                    if ($list.count -gt 0) {
                        Write-Host ("- {0} PSReadLine history file(s) found in {1}" -f $list.count, $UserProfileFolder.FullName) -ForegroundColor Green
                        foreach ($file in $list) {
                            [PSCustomObject]@{
                                HistoryFileName = $file.FullName
                            }
                        }   
                    }
                    else {
                        Write-Host ("- No PSReadLine history file(s) found in {0}" -f $UserProfileFolder.FullName) -ForegroundColor Gray
                    }
                }

                #Get the contents of the found PSReadLine history files on the system
                $TotalHistoryLogs = foreach ($file in $HistoryFiles) {
                    $HistoryData = Get-Content -Path $file.HistoryFileName -ErrorAction SilentlyContinue
                    if ($HistoryData.count -gt 0) {
                        Write-Host ("- Exporting {0} PSReadLine History events from the {1} file" -f $HistoryData.count, $file.HistoryFileName) -ForegroundColor Green
                        foreach ($line in $HistoryData) {
                            if ($line.Length -gt 0) {
                                [PSCustomObject]@{
                                    ComputerName = $computer
                                    FileName     = $File.HistoryFileName
                                    Command      = $line
                                }
                            }
                        }
                    }
                    else {
                        Write-Warning ("No PSReadLine history found in the {0} file" -f $Log)
                    }
                }

                #Create an Excel file and add the PSReadLineHistory tab containing PowerShell history
                if ($TotalHistoryLogs.count -gt 0) {
                    try {
                        $TotalHistoryLogs | Export-Excel -Path $Filename -WorksheetName "PSReadLine_History_$($date)" -AutoFilter -AutoSize -Append
                        Write-Host ("Exported PSReadLine history to {0}" -f $Filename) -ForegroundColor Green
                    }
                    catch {
                        Write-Warning ("Error exporting PSReadLine history data to {0} (File in use?), exiting..." -f $Filename)
                        return
                    }
                }
            }
        }
    }

    else {
        Write-Warning ("Specified computer {0} is not accessible, check permissions and network settings. Skipping..." -f $computer)
        continue
    }
}

Download the script(s) from GitHub here

15 thoughts on “Retrieve local and remote PowerShell logs

  1. Hi Harm:
    This is how I ran the script:
    PS C:\WINDOWS\system32> C:\Users\bromberg.DESKTOP-V44B91G\Downloads\Get-PowerShellLogs.ps1 -Filename C:\Temp\events.xlsx -PowershellEventlog
    and this is the error I got:
    [D] Do not run [R] Run once [S] Suspend [?] Help (default is “D”): r
    Specified C:\Temp\events.xlsx filename is correct, and the path is accessible, continuing…
    The ImportExcel module was found on the system, continuing…
    WARNING: Specified computer DESKTOP-V44B91G is not accessible, check permissions and network settings. Skipping…

    What permissions do I need to give?
    Dan

      • I’m still having security issues but I do thank you for your replies; I’ll file this one away for a rainy day (plenty of them here in Florida!).
        Dan

      • But I am curious why I’m being prompted for a file name when I already gave it in the command:

        C:\WINDOWS\system32>C:\Users\bromberg.DESKTOP-V44B91G\Downloads\Get-PowerShellLogs.ps1 -Filename C:\Temp\events.xlsx -PowershellEventlog

        Security warning
        Run only scripts that you trust. While scripts from the internet can be useful, this script can potentially harm your
        computer. If you trust this script, use the Unblock-File cmdlet to allow the script to run without this warning
        message. Do you want to run C:\Users\bromberg.DESKTOP-V44B91G\Downloads\Get-PowerShellLogs.ps1?
        [D] Do not run [R] Run once [S] Suspend [?] Help (default is “D”): r

        cmdlet Get-PowerShellLogs.ps1 at command pipeline position 1
        Supply values for the following parameters:
        Filename:

      • If I understood you correctly, you asked me to run the script as follows:
        unblock-file get-powershelllogs.ps1
        This results in no error messages but also no output. The cursor just moves to a new line next to a new command prompt.

        The script seems to be complaining about my ComputerName. Where do I find that? When I run the systeminfo command I get a system name (in my case, DESKTOP-V44B91G). Is that the same as the ComputerName your script is expecting? If not, where can I find that?

        When I run your script using all your parameters, except replacing your w2k22member with my DESKTOP-V44B91G as the computer name, I get this error message:
        WARNING: Specified computer DESKTOP-V44B91G is not accessible, check permissions and network settings. Skipping…
        Thanks,
        Dan

      • If you don’t specify a computername, it will connect to your local computer. If you do specify a computername using the -ComputerName parameter, if must be a name that your can reach/ping and if the computer you must be able to authenticate it using the user that you’re running the script with. To avoid permission issues in that case, you can do a net use z: \computername\$ and enter the correct credentials so that you’re already authenticated to it. (Do a net use z: /d afterwards)

      • I am the only user of this PC so to avoid any security issues, I chose not to include the computername parameter since I want it to connect only to this PC. Therefore, I omitted the ComputerName parameter and this is how I entered the command:

        .\Get-PowerShellLogs.ps1 -Filename C:\Temp\events.xlsx -PowerShellEventlog

        and this is what PS returned:

        Specified C:\Temp\events.xlsx filename is correct, and the path is accessible, continuing…
        The ImportExcel module was found on the system, continuing…
        WARNING: Specified computer DESKTOP-V44B91G is not accessible, check permissions and network settings. Skipping…

        So even though I never specified DESKTOP-V$$B91G as the ComputerName, the script appears to use it as the default (just like you said).

        Before I enter the ‘net user’ command as you suggested in your reply (net use z: \computername\$) , I have 3 basic questions:
        1) I don’t have a ‘z’ drive, is that OK?
        2) what did you mean by ‘enter the correct credentials’ – was that referring to the
        ‘z’ drive? If so, I have no other drives on this PC to use.
        3) in the net command, you wrote ‘$’, should that have been ‘s’ instead?

        Thanks!
        Dan

      • You are administrator of the system otherwise it would have given an error about that.. I think that this is the problem:
        “if (Test-Path -Path “\$($computer)\c$” -ErrorAction SilentlyContinue) ” It checks if there is a c$ default share before continuing… Does \localhost\c$ work if you use Start, Run and enter \localhost\c$ ? (You should see the contents of your c:\ drive) . Could you try and change “[parameter(Mandatory = $false)][string[]]$ComputerName = $env:COMPUTERNAME” to “[parameter(Mandatory = $false)][string[]]$ComputerName = ‘localhost” in the script and see if that works?

        The net use command was, if you wanted to retrieve logs from another system, was to connect the pc and be authenticated to it before running the script. Credentials are the username/password of the system with enough permissions. And that should have been c$, forgot the c 🙁

  2. some few comments

    In the presence test of the ImportExcel module, a command is missing. If module not present, installation and after the if…else it would be necessary to add Import-Module to load it

    to avoid duplicates in the excel file, you could do something like this:
    – Check if Excel file already exists. If yes, get the modification date.
    – Then when you do your research in the eventLogs, you could use something like that :
    $StartTime = (Get-Item -Path ‘C:\temp\0367-support-powershell.pdf’).LastWriteTime
    $EndTime = Get-Date
    Get-WinEvent -ComputerName $Computer -FilterHashtable @{
    LogName = $EventLog
    StartTime = $StartTime
    EndTime = $EndTime
    } -ErrorAction SilentlyContinue
    for the PSReadLineHistory, maybe a way like:
    – read the last line in the excel file (if existing of course)
    – Look for this line in the PSreadlineHistory and take line+1 until the end to add to the excel export file.

    It seems that this script is designed to run in interactive mode. Using the PS Module PSWriteColor, you could add indentation to the output in an easy way.

    Regards

    • First of all, thanks for your feedback 🙂 I started with a ComputerName_Tabname approach… And then you have the 30 character limit of an Excel tab and the 15 character computername… Perhaps I will change it to add a small date value to it to avoid having duplicates and still have the option to add to an existing Excel file. I didn’t use the PSWriteColor module before, I will look into that (For future posts perhaps).

      I will start the date field addidtion now and see what else I have time for today 🙂

Leave a Reply

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