Public/Get-BrowserHistory.ps1

<#
.SYNOPSIS
    Extracts Chrome and Edge browsing history via SQLite.

.DESCRIPTION
    A forensic tool to parse Chrome and Edge History files, both locally and remotely.
    This version uses an embedded Base64 sqlite3.exe to avoid external dependencies, enabling highly portable operation.
    
    Features:
    - Target local or remote machines.
    - Support for custom credentials.
    - Automatically discovers user profiles.
    - Time-filtered extraction (-Hours).

.PARAMETER ComputerName
    The target computer to pull history from. Defaults to localhost.

.PARAMETER Credential
    Optional credentials for remote connections.

.PARAMETER TargetUsers
    Specific user profiles to target (e.g. "tpetkoff"). If omitted, it will prompt for selection or use -AllUsers.

.PARAMETER AllUsers
    Switch to bypass the GUI prompt and extract from all discovered user profiles.

.PARAMETER Hours
    The number of hours of history to extract. Defaults to 24.

.EXAMPLE
    Get-BrowserHistory -Hours 48 -AllUsers
    Extracts the last 48 hours of history from all users on the local machine.

.EXAMPLE
    Get-BrowserHistory -ComputerName "Server01" -Credential (Get-Credential)
    Connects to Server01, prompts for a user profile, and extracts 24 hours of history.
#>

function Get-BrowserHistory {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline=$true)]
        [string]$ComputerName = "localhost",
        
        [PSCredential]$Credential,
        
        [string[]]$TargetUsers,
        
        [switch]$AllUsers,
        
        [int]$Hours = 24
    )

    begin {
        $isWindowsOS = $true
        if ($PSVersionTable.PSEdition -eq 'Core' -and -not $IsWindows) {
            $isWindowsOS = $false
        }
        if (-not $isWindowsOS) {
            Write-Error "Get-BrowserHistory requires Windows."
            return
        }

        # --- PORTABILITY LOGIC: Base64 Embedded sqlite3.exe ---
        $B64Path = if ($global:OAD_SqliteB64Path) { $global:OAD_SqliteB64Path } else { Join-Path -Path $PSScriptRoot -ChildPath "..\Private\sqlite3.exe.b64" }
        $Base64Sqlite = (Get-Content -Path $B64Path -Raw).Trim()
        $SqliteTool = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath "sqlite3_omniadmin.exe"

        try {
            if (-not (Test-Path $SqliteTool)) {
                if ($Base64Sqlite -ne "PASTE_BASE64_HERE") {
                    $bytes = [Convert]::FromBase64String($Base64Sqlite)
                    [IO.File]::WriteAllBytes($SqliteTool, $bytes)
                } else {
                    # Fallback mechanism if placeholder is unreplaced
                    Write-Warning "Placeholder Base64 used. Relying on system sqlite3.exe if available..."
                    $SqliteTool = "sqlite3.exe"
                }
            }
        } catch {
            Write-Error "Failed to unpack sqlite3.exe: $($_.Exception.Message)"
            return
        }

        # --- SETUP ---
        $LocalTempBase = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath "History_Analysis"
        New-Item -Path $LocalTempBase -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
        
        # Calculate Unix cutoff time based on hours
        $CutoffTime = ([DateTime]::UtcNow.AddHours(-$Hours).ToFileTime()) / 10
    }

    process {
        $UseTempDrive = $false
        $BaseRoot = "\\$ComputerName\C$"

        try {
            # Connection Logic
            if ($Credential) {
                Write-Verbose "Using provided credentials..."
                $TempDriveName = "ForensicTemp_$(Get-Random)"
                New-PSDrive -Name $TempDriveName -PSProvider FileSystem -Root "\\$ComputerName\C$" -Credential $Credential -ErrorAction Stop | Out-Null
                $BaseRoot = "$($TempDriveName):"
                $UseTempDrive = $true
            }

            # User Discovery
            if ($null -eq $TargetUsers -or $TargetUsers.Count -eq 0) {
                $UsersPath = "$BaseRoot\Users"
                if (Test-Path $UsersPath) {
                    $FoundUsers = Get-ChildItem -Path $UsersPath -Directory -ErrorAction Stop | 
                                  Where-Object { $_.Name -notin @("Public", "Default", "All Users", "Default User", "Administrator") } |
                                  Select-Object -ExpandProperty Name
                    
                    if ($AllUsers) { $TargetUsers = $FoundUsers } 
                    else { 
                        $headerWidth = 42
                        Write-Host ""
                        Write-Host ("═" * $headerWidth) -ForegroundColor Cyan
                        Write-Host " 🔍 BROWSER HISTORY: $ComputerName"  -ForegroundColor Cyan
                        Write-Host " Hours: $Hours │ Users: $($FoundUsers.Count) found" -ForegroundColor DarkGray
                        Write-Host ("═" * $headerWidth) -ForegroundColor Cyan
                        Write-Host ""
                        Write-Host " Available Users:" -ForegroundColor Cyan
                        for ($i = 0; $i -lt $FoundUsers.Count; $i++) {
                            Write-Host " [$i] $($FoundUsers[$i])" -ForegroundColor White
                        }
                        
                        Write-Host ""
                        $selection = Read-Host "Enter user numbers to target (comma-separated, or leave blank for ALL)"
                        
                        if ([string]::IsNullOrWhiteSpace($selection)) {
                            $TargetUsers = $FoundUsers
                        } else {
                            $TargetUsers = @()
                            $indexes = $selection -split ',' | ForEach-Object { $_.Trim() }
                            foreach ($idx in $indexes) {
                                if ([int]::TryParse($idx, [ref]$null) -and $idx -ge 0 -and $idx -lt $FoundUsers.Count) {
                                    $TargetUsers += $FoundUsers[[int]$idx]
                                } else {
                                    Write-Warning "Invalid selection: $idx"
                                }
                            }
                            
                            if ($TargetUsers.Count -eq 0) {
                                Write-Warning "No valid users selected. Defaulting to All Users."
                                $TargetUsers = $FoundUsers
                            }
                        }
                    }
                }
            }

            if (-not $TargetUsers) { return }

            $allHistory = @()

            # Extraction Loop
            foreach ($User in $TargetUsers) {
                $Paths = @{
                    "Chrome" = "$BaseRoot\Users\$User\AppData\Local\Google\Chrome\User Data\Default\History"
                    "Edge"   = "$BaseRoot\Users\$User\AppData\Local\Microsoft\Edge\User Data\Default\History"
                }

                foreach ($Browser in $Paths.Keys) {
                    $SourcePath = $Paths[$Browser]
                    $DestFile   = Join-Path -Path $LocalTempBase -ChildPath "${Browser}_${User}_$(Get-Random).db"

                    if (Test-Path $SourcePath -ErrorAction SilentlyContinue) {
                        try {
                            Copy-Item -Path $SourcePath -Destination $DestFile -Force -ErrorAction Stop
                            
                            $Query = "SELECT datetime(visit_time/1000000-11644473600, 'unixepoch', 'localtime') as Time, urls.title as Title, urls.url as URL FROM visits JOIN urls ON visits.url = urls.id WHERE visits.visit_time > $CutoffTime ORDER BY visits.visit_time DESC;"
                            
                            # Execute embedded sqlite3.exe to export CSV
                            $Output = & $SqliteTool -header -csv $DestFile $Query

                            if ($Output) {
                                $Output | ConvertFrom-Csv | ForEach-Object {
                                    $allHistory += [PSCustomObject]@{
                                        Computer = $ComputerName
                                        User     = $User
                                        Browser  = $Browser
                                        Time     = [DateTime]$_.Time
                                        Title    = $_.Title
                                        URL      = $_.URL
                                    }
                                }
                            }
                        }
                        catch { Write-Error "Failed extracting $Browser history for $User : $($_.Exception.Message)" }
                        finally { Remove-Item -Path $DestFile -Force -ErrorAction SilentlyContinue }
                    }
                }
            }
            return $allHistory
        }
        finally {
            if ($UseTempDrive) { Remove-PSDrive -Name $TempDriveName -ErrorAction SilentlyContinue }
        }
    }
}