PowerShell Forensics Script: Non-Privileged Evidence Capture

windowsf cover

1. Project Overview

~This idea came from an incident I handled where a suspicious USB was found plugged into an endpoint we had physical access to. Forensics’ first move was to capture memory and disk images, but that takes time, and we needed to correlate what was happening now with our logs. To bypass LAPS and get actionable info fast, I researched what data would be useful to grab and which directories are accessible by default to non-privileged accounts. And so, this approach was born! Feel free to tweak it for your environment—it’ll need some tuning to fit your setup.~

This project involves creating a PowerShell script designed for digital forensics, capable of capturing critical system information without requiring administrative privileges. The script collects data such as command history, scheduled tasks, startup items, running processes, network connections, DNS queries, and recently modified files, saving them to a timestamped folder on the user's desktop for analysis by a Security Operations Center (SOC) team.

2. Script Breakdown and Forensic Relevance

Security Note: Always review code before executing. Verify integrity with the SHA256 hash provided for the full script below.

2.1 Setup and Logging

Relevance: Creating a timestamped output directory ensures all collected evidence is organized and traceable to the time of capture, which is critical for maintaining the chain of custody in forensic investigations. Logging all actions provides an audit trail, documenting what was captured or any errors encountered, which is essential for validating the integrity of the collected data.

# Get the current user and their desktop path
$currentUser = $env:USERNAME
$desktopPath = [System.IO.Path]::Combine([System.Environment]::GetFolderPath('Desktop'), "SystemCapture_$(Get-Date -Format 'yyyyMMdd_HHmmss')")

# Create the output directory on the desktop
try {
    New-Item -Path $desktopPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
    Write-Host "Created output directory: $desktopPath" -ForegroundColor Green
}
catch {
    Write-Host "Error creating output directory: $_" -ForegroundColor Red
    exit 1
}

# Function to log messages
function Write-Log {
    param($Message)
    $logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message"
    Add-Content -Path "$desktopPath\CaptureLog.txt" -Value $logMessage
    Write-Host $logMessage
}
dir shot

2.2 PowerShell Command History

Relevance: The PowerShell command history can reveal commands executed by the user or an attacker, such as file manipulations, network operations, or malicious scripts. This is critical for reconstructing the timeline of an incident and identifying unauthorized activities.

# Capture PowerShell Command History
try {
    $psHistoryPath = "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
    if (Test-Path $psHistoryPath) {
        Copy-Item -Path $psHistoryPath -Destination "$desktopPath\PowerShell_History.txt" -ErrorAction Stop
        Write-Log "Captured PowerShell command history"
    } else {
        Write-Log "PowerShell command history file not found"
    }
}
catch {
    Write-Log "Error capturing PowerShell command history: $_"
}

2.3 Scheduled Tasks

Relevance: Scheduled tasks can be used by attackers to execute malicious code at specific times or intervals, ensuring persistence. Capturing this data helps investigators identify unauthorized tasks that may indicate malware or backdoors.

# Capture Scheduled Tasks
try {
    schtasks /query /fo LIST /v | Out-File "$desktopPath\Scheduled_Tasks.txt"
    Write-Log "Captured scheduled tasks"
}
catch {
    Write-Log "Error capturing scheduled tasks: $_"
}

2.4 Startup Items

Relevance: Startup items, including those in the startup folder and registry autorun keys, can reveal programs set to run automatically, which is a common persistence mechanism for malware. This data helps identify suspicious applications that may require further analysis.

# Capture Startup Items
try {
    # Startup folder
    $startupFolder = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
    if (Test-Path $startupFolder) {
        dir $startupFolder | Out-File "$desktopPath\Startup_Folder.txt"
    } else {
        Write-Log "Startup folder not found"
    }

    # Registry autorun entries
    reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Run | Out-File "$desktopPath\Autorun_HKLM.txt"
    reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Run | Out-File "$desktopPath\Autorun_HKCU.txt"
    Write-Log "Captured startup items"
}
catch {
    Write-Log "Error capturing startup items: $_"
}

2.5 Running Processes

Relevance: A snapshot of running processes can uncover malicious or unrecognized applications, such as remote access tools or cryptominers. Details like process names, IDs, and file paths aid in correlating processes with potential threats.

# Capture Running Processes
try {
    Get-Process | Select-Object Name, Id, Path, Company, Description | Export-Csv -Path "$desktopPath\Processes.csv" -NoTypeInformation
    Write-Log "Captured running processes"
}
catch {
    Write-Log "Error capturing processes: $_"
}

2.6 Network Connections

Relevance: Network connections reveal active communications, including connections to command-and-control servers or data exfiltration endpoints. Capturing this data with process IDs helps trace suspicious activity to specific applications.

# Capture Network Connections (netstat)
try {
    netstat -ano | Out-File "$desktopPath\Netstat.txt"
    Write-Log "Captured network connections"
}
catch {
    Write-Log "Error capturing network connections: $_"
}

2.7 DNS Queries

Relevance: The DNS cache can reveal domains accessed by the system, which may include malicious or phishing sites. This data is crucial for identifying potential command-and-control communications or unauthorized web activity.

# Capture DNS Queries
try {
    ipconfig /displaydns | Out-File "$desktopPath\DNS_Queries.txt"
    Write-Log "Captured DNS queries"
}
catch {
    Write-Log "Error capturing DNS queries: $_"
}

2.8 Recently Modified Files

Relevance: Recently modified files in key directories like Temp and AppData can indicate malicious activity, such as temporary files created by malware or attacker scripts. Sorting by modification time helps prioritize files for further investigation.

# Capture Recently Modified Files in Key Directories
try {
    $dirsToCheck = @(
        "$env:LOCALAPPDATA\Temp",
        "$env:APPDATA"
    )

    foreach ($dir in $dirsToCheck) {
        if (Test-Path $dir) {
            $dirName = [System.IO.Path]::GetFileName($dir)
            dir $dir -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 50 | Out-File "$desktopPath\RecentFiles_$dirName.txt"
        }
    }
    Write-Log "Captured recently modified files in key directories"
}
catch {
    Write-Log "Error capturing recently modified files: $_"
}

3. Tips and Lessons Learned

Practical advice and insights gained from developing the script:

4. Conclusion

The PowerShell forensics script successfully captures critical system information without requiring administrative privileges, making it suitable for rapid evidence collection in restricted environments. The script enhances forensic investigations by providing structured, timestamped data for SOC analysis. Future enhancements may include additional data sources and automated analysis features.

5. Full Code

Security Note: Always review code before executing. Verify integrity with SHA256 Hash: 2CD211F1B3F88A49CC28FFB009955BFA4F80255A482986ABBA8922D46E4D27FD.

# Script to capture specific system information for forensic analysis
# Purpose: Collects evidence without requiring admin privileges for incident response

# Get the current user and their desktop path
# Retrieves the username of the logged-in user from the environment variable
$currentUser = $env:USERNAME
# Creates a timestamped folder name (e.g., SystemCapture_20250407_121530) on the desktop
$desktopPath = [System.IO.Path]::Combine([System.Environment]::GetFolderPath('Desktop'), "SystemCapture_$(Get-Date -Format 'yyyyMMdd_HHmmss')")

# Create the output directory on the desktop
# Uses try-catch to handle potential errors (e.g., permission issues)
try {
    # Creates the timestamped directory, -Force overwrites if it exists, -ErrorAction Stop ensures errors are caught
    New-Item -Path $desktopPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
    # Notifies user of successful directory creation in green text
    Write-Host "Created output directory: $desktopPath" -ForegroundColor Green
}
catch {
    # Displays error message in red if directory creation fails and exits script
    Write-Host "Error creating output directory: $_" -ForegroundColor Red
    exit 1
}

# Function to log messages
# Defines a reusable function to log actions to a file and console
function Write-Log {
    # Accepts a message parameter to log
    param($Message)
    # Formats the log entry with a timestamp (e.g., 2025-04-07 12:15:30 - Action completed)
    $logMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message"
    # Appends the log entry to CaptureLog.txt in the output directory
    Add-Content -Path "$desktopPath\CaptureLog.txt" -Value $logMessage
    # Displays the log message in the console for real-time feedback
    Write-Host $logMessage
}

# Start logging
# Logs the start of the evidence collection process, including the current user
Write-Log "Starting system information capture for user: $currentUser"

# 1. Capture PowerShell Command History
# Attempts to collect the user's PowerShell command history for forensic analysis
try {
    # Defines the path to the PowerShell history file (stored in PSReadLine)
    $psHistoryPath = "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
    # Checks if the history file exists
    if (Test-Path $psHistoryPath) {
        # Copies the history file to the output directory for preservation
        Copy-Item -Path $psHistoryPath -Destination "$desktopPath\PowerShell_History.txt" -ErrorAction Stop
        # Logs successful capture
        Write-Log "Captured PowerShell command history"
    } else {
        # Logs if the history file is not found (e.g., PSReadLine disabled)
        Write-Log "PowerShell command history file not found"
    }
}
catch {
    # Logs any errors encountered during history capture (e.g., access denied)
    Write-Log "Error capturing PowerShell command history: $_"
}

# 2. Capture Scheduled Tasks
# Collects information about scheduled tasks, which could indicate malicious persistence
try {
    # Runs schtasks with /query /fo LIST /v to get detailed task information and saves to a file
    schtasks /query /fo LIST /v | Out-File "$desktopPath\Scheduled_Tasks.txt"
    # Logs successful capture
    Write-Log "Captured scheduled tasks"
}
catch {
    # Logs any errors (e.g., command execution failure)
    Write-Log "Error capturing scheduled tasks: $_"
}

# 3. Capture Startup Items
# Collects programs set to run at startup, a common malware persistence mechanism
try {
    # Defines the path to the user's startup folder
    $startupFolder = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
    # Checks if the startup folder exists
    if (Test-Path $startupFolder) {
        # Lists contents of the startup folder and saves to a file
        dir $startupFolder | Out-File "$desktopPath\Startup_Folder.txt"
    } else {
        # Logs if the startup folder is not found
        Write-Log "Startup folder not found"
    }

    # Queries the HKLM registry for system-wide autorun entries and saves to a file
    reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Run | Out-File "$desktopPath\Autorun_HKLM.txt"
    # Queries the HKCU registry for user-specific autorun entries and saves to a file
    reg query HKCU\Software\Microsoft\Windows\CurrentVersion\Run | Out-File "$desktopPath\Autorun_HKCU.txt"
    # Logs successful capture
    Write-Log "Captured startup items"
}
catch {
    # Logs any errors (e.g., registry access issues)
    Write-Log "Error capturing startup items: $_"
}

# 4. Capture Running Processes
# Collects a list of running processes to identify potential malicious applications
try {
    # Uses Get-Process to retrieve process details (Name, ID, Path, Company, Description)
    # Exports to a CSV file for easy analysis, -NoTypeInformation removes extra metadata
    Get-Process | Select-Object Name, Id, Path, Company, Description | Export-Csv -Path "$desktopPath\Processes.csv" -NoTypeInformation
    # Logs successful capture
    Write-Log "Captured running processes"
}
catch {
    # Logs any errors (e.g., access restrictions)
    Write-Log "Error capturing processes: $_"
}

# 5. Capture Network Connections (netstat)
# Collects active network connections to detect unauthorized communications
try {
    # Runs netstat -ano to list connections with process IDs and saves to a file
    netstat -ano | Out-File "$desktopPath\Netstat.txt"
    # Logs successful capture
    Write-Log "Captured network connections"
}
catch {
    # Logs any errors (e.g., command execution failure)
    Write-Log "Error capturing network connections: $_"
}

# 6. Capture DNS Queries
# Collects the DNS cache to identify accessed domains, which may include malicious ones
try {
    # Runs ipconfig /displaydns to list cached DNS entries and saves to a file
    ipconfig /displaydns | Out-File "$desktopPath\DNS_Queries.txt"
    # Logs successful capture
    Write-Log "Captured DNS queries"
}
catch {
    # Logs any errors (e.g., command execution failure)
    Write-Log "Error capturing DNS queries: $_"
}

# 7. Capture Recently Modified Files in Key Directories
# Collects recently modified files in Temp and AppData to detect suspicious activity
try {
    # Defines directories to check (common locations for temporary or malicious files)
    $dirsToCheck = @(
        "$env:LOCALAPPDATA\Temp",
        "$env:APPDATA"
    )

    # Iterates through each directory
    foreach ($dir in $dirsToCheck) {
        # Checks if the directory exists
        if (Test-Path $dir) {
            # Gets the directory name for the output file
            $dirName = [System.IO.Path]::GetFileName($dir)
            # Lists files recursively, sorts by LastWriteTime, takes the top 50, and saves to a file
            dir $dir -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 50 | Out-File "$desktopPath\RecentFiles_$dirName.txt"
        }
    }
    # Logs successful capture
    Write-Log "Captured recently modified files in key directories"
}
catch {
    # Logs any errors (e.g., access issues or directory traversal errors)
    Write-Log "Error capturing recently modified files: $_"
}

# Finalize
# Logs the completion of the evidence collection process
Write-Log "System information capture completed. All files saved to: $desktopPath"
# Instructs the user to send the output folder to the SOC team for analysis
Write-Host "Capture completed. Please send the folder $desktopPath to your local SOC team for analysis." -ForegroundColor Green