PowerTriage.ps1
|
<#PSScriptInfo .VERSION 1.0.1 .GUID 978e8b23-1d54-46c5-a20c-7b2d5f81e7d2 .AUTHOR Jesus Angosto .COMPANYNAME PowerForensics .COPYRIGHT (c) 2025 Jesus Angosto. All rights reserved. .TAGS DFIR, Forensics, IncidentResponse, Triage, PowerShell .LICENSEURI https://github.com/PowerForensics/powerforensics/blob/main/LICENSE .PROJECTURI https://github.com/PowerForensics/powerforensics .ICONURI https://raw.githubusercontent.com/PowerForensics/powerforensics/main/icon.png #> <# .SYNOPSIS PowerTriage - Fast Forensic Triage & Live Response Tool for Windows. .DESCRIPTION PowerTriage is a lightweight, dependency-free PowerShell script designed for Incident Response (DFIR) on compromised Windows devices. It collects critical artifacts (Network, Process, Persistence, System, Browsers) and packages them for analysis. Features: - Zero Dependencies: Runs on standard PowerShell 5.1+ - Modular: Full or Minimal collection modes. - Browser Forensics: Chrome, Edge, Firefox, Opera, Brave (History, Cookies, Extensions, Sync Status). - System Triage: Network connections, Processes, Services, Scheduled Tasks, Registry Autoruns. - Output: Structured CSV/TXT reports and a zipped final package. .PARAMETER OutputDirectory Specifies the directory where the triage results will be saved. Defaults to current directory. .PARAMETER Full Performs a full collection of all available artifacts (Default behavior). .PARAMETER Minimal Performs a quick triage collecting only Volatile Data (Network, Process, System). .EXAMPLE .\PowerTriage.ps1 Runs a full triage and saves to the current directory. .EXAMPLE .\PowerTriage.ps1 -Minimal Runs a quick triage (Network, Process, System only). .EXAMPLE .\PowerTriage.ps1 -OutputDirectory "C:\Cases\Case404" Runs a full triage and saves output to C:\Cases\Case404. .LINK https://github.com/PowerForensics/powerforensics #> param( [string]$OutputDirectory, [Parameter(Mandatory=$false)] [Alias('h')] [switch]$Help, [Parameter(Mandatory=$false)] [Alias('f')] [switch]$Full, [Parameter(Mandatory=$false)] [Alias('M')] [switch]$Minimal ) # PowerTriage Windows # Live Response & Forensic Triage Tool $Version = "1.0.0" function Show-Banner { Clear-Host Write-Host "__________ ___________ .__ " -ForegroundColor Cyan Write-Host "\______ \______ _ __ _________ \__ ___/_______|__|____ ____ ____ " -ForegroundColor Cyan Write-Host " | ___/ _ \ \/ \/ // __ \_ __ \| | \_ __ \ \__ \ / ___\_/ __ \ " -ForegroundColor Cyan Write-Host " | | ( <_> ) /\ ___/| | \/| | | | \/ |/ __ \_/ /_/ > ___/ " -ForegroundColor Cyan Write-Host " |____| \____/ \/\_/ \___ >__| |____| |__| |__(____ /\___ / \___ > " -ForegroundColor Cyan Write-Host " \/ \//_____/ \/ " -ForegroundColor Cyan Write-Host "" Write-Host "PowerTriage is a script to perform incident response via PowerShell on compromised devices with an Windows Operating System (Workstation & Server)." -ForegroundColor Yellow Write-Host "Version: $Version" -ForegroundColor White Write-Host "By twitter 'X': @jdangosto, https://github.com/jdangosto - Jesus Angosto (jdangosto)" -ForegroundColor Gray Write-Host "" Write-Host "=============================================================" -ForegroundColor Green Write-Host "Execution Date (UTC): $((Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Green Write-Host "Hostname : $env:COMPUTERNAME" -ForegroundColor Green Write-Host "User : $env:USERNAME" -ForegroundColor Green $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") Write-Host "Admin Privileges : $isAdmin" -ForegroundColor Green Write-Host "=============================================================" -ForegroundColor Green Write-Host "" } # Show Help if requested if ($Help) { Show-Banner Write-Host "USAGE:" -ForegroundColor Yellow Write-Host " .\PowerTriage.ps1 [Options]" Write-Host "" Write-Host "OPTIONS:" -ForegroundColor Yellow Write-Host " -OutputDirectory <Path> Specify directory to save results" Write-Host " -Full, -f Collect ALL artifacts (Default)" Write-Host " -Minimal, -M Collect minimal triage set (Network, System, Process)" Write-Host " -Help, -h Show this help message" Write-Host "" Write-Host "EXAMPLES:" -ForegroundColor Yellow Write-Host " .\PowerTriage.ps1" Write-Host " .\PowerTriage.ps1 -Minimal" Write-Host " .\PowerTriage.ps1 -OutputDirectory 'C:\Cases\Case001'" Write-Host "" exit 0 } # Apply Minimal or Full logic # Initialize internal flags $Network = $false $System = $false $Process = $false $Events = $false $Users = $false $Browser = $false $Disk = $false $Cloud = $false $RunAll = $false if ($Minimal) { $Network = $true $System = $true $Process = $true } elseif ($Full -or (-not $Minimal)) { # Default to Full if Minimal is not specified $RunAll = $true $Network = $true $System = $true $Process = $true $Events = $true $Users = $true $Browser = $true $Disk = $true $Cloud = $true } Show-Banner # Check Admin if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Warning "Please run this script as Administrator!" Break } # Output Directory Selection if ([string]::IsNullOrWhiteSpace($OutputDirectory)) { # Interactive Mode $targetDir = Read-Host "Enter output directory path (Press Enter for current directory: $PWD)" if ([string]::IsNullOrWhiteSpace($targetDir)) { $targetDir = $PWD } } else { # Unattended/Param Mode $targetDir = $OutputDirectory } # Resolve relative paths & Create if missing if (-not (Test-Path $targetDir)) { try { New-Item -Path $targetDir -ItemType Directory -Force | Out-Null Write-Host "Created output directory: $targetDir" -ForegroundColor Green } catch { Write-Warning "Could not create directory '$targetDir'. Using current directory." $targetDir = $PWD } } else { # Ensure we have the absolute path for cleaner logs $targetDir = (Resolve-Path $targetDir).Path } # Setup $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $FolderCreation = Join-Path $targetDir "PowerTriage_$($env:COMPUTERNAME)_$Timestamp" New-Item -Path $FolderCreation -ItemType Directory -Force | Out-Null $LogFile = "$FolderCreation\PowerTriage.log" function WriteLog { param([string]$Level, [string]$Message) $LogEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$Level] $Message" $LogEntry | Out-File -Append -FilePath $LogFile -Encoding UTF8 } function WriteHash { param([string]$FilePath) if (Test-Path $FilePath) { try { $Hash = Get-FileHash -Path $FilePath -Algorithm SHA256 -ErrorAction SilentlyContinue if ($Hash) { "$($Hash.Algorithm),$($Hash.Hash),$($Hash.Path)" | Out-File -Append -FilePath "$FolderCreation\Hashes.csv" -Encoding UTF8 } } catch {} } } # --- Tasks --- # Task 1-4: Network function Get-NetworkInfo { Write-Host "Running task 1-4 of 34" -ForegroundColor Yellow Write-Host "Collecting Network Information..." WriteLog -Level "INFO" -Message "Collecting Network Info" $NetFolder = "$FolderCreation\Network" New-Item -Path $NetFolder -ItemType Directory -Force | Out-Null # Connections $conns = Get-NetTCPConnection -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess, CreationTime $conns | Export-Csv -NoTypeInformation -Path "$NetFolder\TCP_Connections.csv" -Encoding UTF8 $conns | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$NetFolder\TCP_Connections.txt" # Routes Get-NetRoute | Select-Object DestinationPrefix, NextHop, RouteMetric, InterfaceAlias | Export-Csv -NoTypeInformation -Path "$NetFolder\Routes.csv" -Encoding UTF8 # Interfaces Get-NetAdapter | Select-Object Name, InterfaceDescription, MacAddress, Status | Export-Csv -NoTypeInformation -Path "$NetFolder\Adapters.csv" -Encoding UTF8 WriteLog -Level "INFO" -Message "Network Info collected." } if ($RunAll -or $Network) { Get-NetworkInfo } # Task 5: SMB Shares & Sessions function Get-SmbInfo { Write-Host "Running task 5 of 33" -ForegroundColor Yellow Write-Host "Collecting SMB Shares & Sessions..." WriteLog -Level "INFO" -Message "Collecting SMB Shares & Sessions" $SmbFolder = "$FolderCreation\Network" if (-not (Test-Path $SmbFolder)) { New-Item -Path $SmbFolder -ItemType Directory -Force | Out-Null } # Shares try { $shares = Get-SmbShare -ErrorAction SilentlyContinue | Select-Object Name, Path, Description, Special, Temporary $shares | Export-Csv -NoTypeInformation -Path "$SmbFolder\SMB_Shares.csv" -Encoding UTF8 WriteHash -FilePath "$SmbFolder\SMB_Shares.csv" } catch {} # Sessions try { $sessions = Get-SmbSession -ErrorAction SilentlyContinue | Select-Object ClientComputerName, ClientUserName, NumOpens, SecondsExist $sessions | Export-Csv -NoTypeInformation -Path "$SmbFolder\SMB_Sessions.csv" -Encoding UTF8 WriteHash -FilePath "$SmbFolder\SMB_Sessions.csv" } catch {} WriteLog -Level "INFO" -Message "SMB Info collected." } if ($RunAll -or $Network) { Get-SmbInfo } # Task 6: Autoruns (Registry) function Get-Autoruns { Write-Host "Running task 6 of 33" -ForegroundColor Yellow Write-Host "Collecting Autoruns (Registry)..." WriteLog -Level "INFO" -Message "Collecting Autoruns" $SysFolder = "$FolderCreation\System" New-Item -Path $SysFolder -ItemType Directory -Force | Out-Null $AutorunLocations = @( "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run", "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce", "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run", "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" ) $Autoruns = @() foreach ($loc in $AutorunLocations) { if (Test-Path $loc) { $props = Get-ItemProperty -Path $loc -ErrorAction SilentlyContinue $props.PSObject.Properties | Where-Object { $_.Name -notin @("PSPath", "PSParentPath", "PSChildName", "PSDrive", "PSProvider") } | ForEach-Object { $obj = New-Object PSCustomObject $obj | Add-Member -NotePropertyName Location -NotePropertyValue $loc $obj | Add-Member -NotePropertyName Name -NotePropertyValue $_.Name $obj | Add-Member -NotePropertyName Value -NotePropertyValue $_.Value $Autoruns += $obj } } } $Autoruns | Export-Csv -NoTypeInformation -Path "$SysFolder\Autoruns_Registry.csv" -Encoding UTF8 $Autoruns | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$SysFolder\Autoruns_Registry.txt" WriteHash -FilePath "$SysFolder\Autoruns_Registry.csv" WriteHash -FilePath "$SysFolder\Autoruns_Registry.txt" WriteLog -Level "INFO" -Message "Autoruns collected." } if ($RunAll -or $System) { Get-Autoruns } # Task 7: Scheduled Tasks function Get-ScheduledTasksInfo { Write-Host "Running task 7 of 33" -ForegroundColor Yellow Write-Host "Collecting Scheduled Tasks..." WriteLog -Level "INFO" -Message "Collecting Scheduled Tasks" $schFolder = "$FolderCreation\System\ScheduledTasks" if (-not (Test-Path $schFolder)) { New-Item -Path $schFolder -ItemType Directory -Force | Out-Null } try { $tasks = Get-ScheduledTask -ErrorAction SilentlyContinue | Select-Object TaskName, TaskPath, State, @{N='Action';E={$_.Actions.Execute}}, @{N='Trigger';E={$_.TriggersRepetition.Interval}} $tasks | Export-Csv -NoTypeInformation -Path "$schFolder\ScheduledTasks.csv" -Encoding UTF8 $tasks | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$schFolder\ScheduledTasks.txt" WriteHash -FilePath "$schFolder\ScheduledTasks.csv" } catch { Write-Warning "Could not collect Scheduled Tasks (Cmdlet missing?)" } WriteLog -Level "INFO" -Message "Scheduled Tasks collected." } if ($RunAll -or $System) { Get-ScheduledTasksInfo } # Task 8: Firewall Rules function Get-FirewallRules { Write-Host "Running task 8 of 33" -ForegroundColor Yellow Write-Host "Collecting Firewall Rules..." WriteLog -Level "INFO" -Message "Collecting Firewall Rules" try { $fwPath = "$FolderCreation\Network\FirewallRules.csv" Get-NetFirewallRule -ErrorAction SilentlyContinue | Select-Object Name, DisplayName, Enabled, Direction, Action, Profile, Group | Export-Csv -NoTypeInformation -Path $fwPath -Encoding UTF8 WriteHash -FilePath $fwPath } catch {} WriteLog -Level "INFO" -Message "Firewall Rules collected." } if ($RunAll -or $Network) { Get-FirewallRules } # Task 9: Processes function Get-ProcessAndHashes { Write-Host "Running task 9 of 33" -ForegroundColor Yellow Write-Host "Collecting Active Processes (Info, Hash, Signature)...`n" $ProcessFolder = "$FolderCreation\ProcessInformation" New-Item -Path $ProcessFolder -ItemType Directory -Force | Out-Null $ProcessListOutput = "$ProcessFolder\ProcessList.csv" WriteLog -Level "INFO" -Message "Collecting Active Processes..." $processes_list = @() $cimProcesses = Get-CimInstance -ClassName Win32_Process $totalProcs = $cimProcesses.Count $hashedProcs = 0 $processedCount = 0 foreach ($process in $cimProcesses) { $processedCount++ if ($totalProcs -gt 0) { # Update progress frequently to avoid perceived hang on first item if ($processedCount -eq 1 -or $processedCount % 5 -eq 0) { $percentComplete = ($processedCount / $totalProcs) * 100 Write-Progress -Activity "Collecting Process Information" -Status "Processing $($process.Name) ($processedCount / $totalProcs)" -PercentComplete $percentComplete } } $process_obj = New-Object PSCustomObject # Owner $owner = "N/A" try { $ownerResult = Invoke-CimMethod -InputObject $process -MethodName GetOwner -ErrorAction SilentlyContinue if ($ownerResult.ReturnValue -eq 0) { $owner = "$($ownerResult.Domain)\$($ownerResult.User)" } } catch {} # Path, Hash, Signature $hash = "N/A" $signer = "N/A" $signedStatus = "N/A" $company = "N/A" $description = "N/A" $path = $process.ExecutablePath if ($path) { # Fix for Network/Zombie paths causing hangs $isValidLocal = $false try { if ($path -match "^[a-zA-Z]:" -and (Test-Path $path)) { $isValidLocal = $true } } catch {} if ($isValidLocal) { try { $hash = (Get-FileHash -Algorithm SHA256 -Path $path -ErrorAction SilentlyContinue).Hash $hashedProcs++ # Optimized Signature Check (Ignore Revocation for Speed) # WARNING: Fallback to standard Get-AuthenticodeSignature removed to prevent timeouts on offline systems try { $sig = [System.Management.Automation.Signature]::GetSignature($path, "IgnoreRevocation") if ($sig) { $signer = if ($sig.SignerCertificate) { $sig.SignerCertificate.Subject } else { "Unsigned" } $signedStatus = $sig.Status } } catch { # If fast check fails, we assume N/A rather than hanging the system with online revocation checks $signedStatus = "Skipped (Offline Optimization)" } $verInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path) if ($verInfo) { $company = $verInfo.CompanyName $description = $verInfo.FileDescription } } catch {} } } $process_obj | Add-Member -NotePropertyName Proc_Name -NotePropertyValue $process.Name $process_obj | Add-Member -NotePropertyName Proc_Id -NotePropertyValue $process.ProcessId $process_obj | Add-Member -NotePropertyName Proc_Owner -NotePropertyValue $owner $process_obj | Add-Member -NotePropertyName Proc_Path -NotePropertyValue $path $process_obj | Add-Member -NotePropertyName Proc_CommandLine -NotePropertyValue $process.CommandLine $process_obj | Add-Member -NotePropertyName Proc_ParentProcessId -NotePropertyValue $process.ParentProcessId $process_obj | Add-Member -NotePropertyName Proc_CreationDate -NotePropertyValue $process.CreationDate $process_obj | Add-Member -NotePropertyName Proc_Hash -NotePropertyValue $hash $process_obj | Add-Member -NotePropertyName Proc_Signer -NotePropertyValue $signer $process_obj | Add-Member -NotePropertyName Proc_SignedStatus -NotePropertyValue $signedStatus $process_obj | Add-Member -NotePropertyName Proc_Company -NotePropertyValue $company $process_obj | Add-Member -NotePropertyName Proc_Description -NotePropertyValue $description $processes_list += $process_obj } Write-Progress -Activity "Collecting Process Information" -Completed $processes_list | Export-Csv -NoTypeInformation -Path $ProcessListOutput -Encoding UTF8 $processes_list | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$ProcessFolder\ProcessList.txt" WriteHash -FilePath "$ProcessListOutput" WriteHash -FilePath "$ProcessFolder\ProcessList.txt" WriteLog -Level "INFO" -Message "Task 9 done. Collected $totalProcs processes ($hashedProcs hashed/analyzed)." } # Task 10: Process Tree function Print-ProcessTree { param($PID_Target = 0) # Simplified Process Tree Logic $allProcs = Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, Name, CommandLine function Get-Tree($parentId, $indent) { # Fix: Exclude self-referencing PIDs (like PID 0) to prevent infinite recursion $children = $allProcs | Where-Object { $_.ParentProcessId -eq $parentId -and $_.ProcessId -ne $parentId } foreach ($child in $children) { "$indent|_ $($child.Name) ($($child.ProcessId))" Get-Tree $child.ProcessId "$indent " } } Get-Tree 0 "" } # Task 11: USB function Get-USBHistory { Write-Host "Running task 11 of 33" -ForegroundColor Yellow Write-Host "Collecting USB History..." WriteLog -Level "INFO" -Message "Collecting USB History..." $usbOutput = "$FolderCreation\System\USB_History.csv" $usbList = @() $usbStorPath = "HKLM:\SYSTEM\CurrentControlSet\Enum\USBSTOR" if (Test-Path $usbStorPath) { $devices = Get-ChildItem -Path $usbStorPath -Recurse -ErrorAction SilentlyContinue foreach ($dev in $devices) { if ($dev.PSChildName -match "^Disk&") { $instances = Get-ChildItem -Path $dev.PSPath -ErrorAction SilentlyContinue foreach ($instance in $instances) { $props = Get-ItemProperty -Path $instance.PSPath -ErrorAction SilentlyContinue $obj = New-Object PSCustomObject $obj | Add-Member -NotePropertyName FriendlyName -NotePropertyValue $props.FriendlyName $obj | Add-Member -NotePropertyName DeviceDesc -NotePropertyValue $props.DeviceDesc $obj | Add-Member -NotePropertyName SerialNumber -NotePropertyValue $instance.PSChildName $obj | Add-Member -NotePropertyName HardwareID -NotePropertyValue ($props.HardwareID -join "; ") $obj | Add-Member -NotePropertyName Class -NotePropertyValue $props.Class $obj | Add-Member -NotePropertyName KeyLastWriteTime -NotePropertyValue $instance.LastWriteTime $usbList += $obj } } } } $usbList | Export-Csv -NoTypeInformation -Path $usbOutput -Encoding UTF8 $usbList | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$FolderCreation\System\USB_History.txt" WriteHash -FilePath $usbOutput WriteHash -FilePath "$FolderCreation\System\USB_History.txt" WriteLog -Level "INFO" -Message "USB History collected ($($usbList.Count) devices found)." } # Task 12: EVTX function Get-Evtx { Write-Host "Running task 12 of 33" -ForegroundColor Yellow Write-Host "Collecting System Events(evtx) Files..." $EventViewer = "$FolderCreation\EventsLogs" New-Item -Path $EventViewer -ItemType Directory -Force | Out-Null $evtxPath = Join-Path $env:SystemRoot "System32\winevt\Logs" $channels = @( "Application", "Security", "System", "Microsoft-Windows-Sysmon%4Operational", "Microsoft-Windows-TaskScheduler%4Operational", "Microsoft-Windows-PowerShell%4Operational", "Microsoft-Windows-WMI-Activity%4Operational", "Microsoft-Windows-NTLM%4Operational", "Microsoft-Windows-TerminalServices-RemoteConnectionManager%4Operational" ) $count = 0 $evtxFiles = Get-ChildItem "$evtxPath\*.evtx" | Where-Object{$_.BaseName -in $channels} $totalEvtx = $evtxFiles.Count foreach ($file in $evtxFiles) { $count++ if ($count % 10 -eq 0) { $percentComplete = ($count / $totalEvtx) * 100 Write-Progress -Activity "Collecting EVTX Files" -Status "Copying $($file.Name) ($count / $totalEvtx)" -PercentComplete $percentComplete } Copy-Item -Path $file.FullName -Destination "$($EventViewer)\$($file.Name)" -Force WriteHash -FilePath "$($EventViewer)\$($file.Name)" } Write-Progress -Activity "Collecting EVTX Files" -Completed WriteLog -Level "INFO" -Message "Task 12 done. $count EVTX files collected." } # Execute Tasks 9-12 if ($RunAll -or $Process) { Get-ProcessAndHashes Print-ProcessTree | Out-File -Width 4096 -Force "$FolderCreation\ProcessInformation\ProcessTree.txt" WriteHash -FilePath "$FolderCreation\ProcessInformation\ProcessTree.txt" } if ($RunAll -or $System) { Get-USBHistory } if ($RunAll -or $Events) { Get-Evtx } # Task 13: PowerShell History function PowerShell_Commands{ Write-Host "Running task 13 of 33" -ForegroundColor Yellow Write-Host "Collecting Console Powershell History (all users)..." WriteLog -Level "INFO" -Message "Collecting Console Powershell History (all users)" $PowershellConsoleHistory = "$FolderCreation\PowerShellConsole_History" mkdir -Force $PowershellConsoleHistory | Out-Null $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory foreach ($userDir in $userDirectories) { $userName = $userDir.Name $HistoryFilePath = Join-Path -Path $userDir.FullName -ChildPath "AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" if (Test-Path $HistoryFilePath) { Copy-Item "$HistoryFilePath" -Destination "$PowershellConsoleHistory\ConsoleHost_history_$userName.txt" -Force -ErrorAction SilentlyContinue WriteHash -FilePath "$PowershellConsoleHistory\ConsoleHost_history_$userName.txt" } } } if ($RunAll -or $Users) { PowerShell_Commands } # Task 14: Local Groups function Get-LocalGroups { Write-Host "Running task 14 of 33" -ForegroundColor Yellow Write-Host "Collecting Local Groups & Members..." WriteLog -Level "INFO" -Message "Collecting Local Groups" try { $groups = Get-LocalGroup -ErrorAction SilentlyContinue | ForEach-Object { $group = $_ $members = Get-LocalGroupMember -Group $group -ErrorAction SilentlyContinue [PSCustomObject]@{ GroupName = $group.Name Description = $group.Description Members = ($members.Name -join "; ") } } $groups | Export-Csv -NoTypeInformation -Path "$FolderCreation\System\LocalGroups.csv" -Encoding UTF8 WriteHash -FilePath "$FolderCreation\System\LocalGroups.csv" } catch {} } if ($RunAll -or $System) { Get-LocalGroups } # Task 15: Environment Variables function Get-EnvVars { Write-Host "Running task 15 of 33" -ForegroundColor Yellow Write-Host "Collecting Environment Variables..." WriteLog -Level "INFO" -Message "Collecting Environment Variables" try { Get-ChildItem Env: | Select-Object Name, Value | Export-Csv -NoTypeInformation -Path "$FolderCreation\System\EnvironmentVariables.csv" -Encoding UTF8 WriteHash -FilePath "$FolderCreation\System\EnvironmentVariables.csv" } catch {} } if ($RunAll -or $System) { Get-EnvVars } # Task 16: Services function Get-Services { Write-Host "Running task 16 of 33" -ForegroundColor Yellow Write-Host "Collecting Services..." WriteLog -Level "INFO" -Message "Collecting Services" $services_list = @() $cimServices = Get-CimInstance Win32_Service $totalSvcs = $cimServices.Count $hashedServices = 0 $svcCount = 0 foreach ($svc in $cimServices) { $svcCount++ if ($svcCount % 10 -eq 0) { Write-Progress -Activity "Collecting Services" -Status "Processing $($svc.Name) ($svcCount / $totalSvcs)" -PercentComplete (($svcCount / $totalSvcs) * 100) } $svc_obj = New-Object PSCustomObject $hash = "N/A" $path = $svc.PathName if ($path) { # Clean path (remove arguments) if ($path -match '^"([^"]+)"') { $cleanPath = $matches[1] } elseif ($path -match '^(\S+)') { $cleanPath = $matches[1] } else { $cleanPath = $path } if (Test-Path $cleanPath) { try { $hash = (Get-FileHash -Algorithm SHA256 -Path $cleanPath -ErrorAction SilentlyContinue).Hash if ($hash) { $hashedServices++ } } catch {} } } $svc_obj | Add-Member -NotePropertyName Service_Name -NotePropertyValue $svc.Name $svc_obj | Add-Member -NotePropertyName Service_DisplayName -NotePropertyValue $svc.DisplayName $svc_obj | Add-Member -NotePropertyName Service_StartMode -NotePropertyValue $svc.StartMode $svc_obj | Add-Member -NotePropertyName Service_State -NotePropertyValue $svc.State $svc_obj | Add-Member -NotePropertyName Service_PathName -NotePropertyValue $svc.PathName $svc_obj | Add-Member -NotePropertyName Service_BinaryHash -NotePropertyValue $hash $services_list += $svc_obj } $services_list | Export-Csv -NoTypeInformation -Path "$FolderCreation\System\All_Services.csv" -Encoding UTF8 $services_list | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$FolderCreation\System\All_Services.txt" WriteHash -FilePath "$FolderCreation\System\All_Services.csv" WriteHash -FilePath "$FolderCreation\System\All_Services.txt" WriteLog -Level "INFO" -Message "Services collected." } if ($RunAll -or $System) { Get-Services } # Task 17: Clipboard function Get-ClipboardContent { Write-Host "Running task 17 of 33" -ForegroundColor Yellow Write-Host "Collecting Clipboard Content..." WriteLog -Level "INFO" -Message "Collecting Clipboard Content" try { $clip = Get-Clipboard -ErrorAction SilentlyContinue if ($clip) { $clip | Out-File -FilePath "$FolderCreation\System\Clipboard.txt" -Encoding UTF8 WriteHash -FilePath "$FolderCreation\System\Clipboard.txt" } } catch {} } if ($RunAll -or $System) { Get-ClipboardContent } # Task 18: Recent Files function RecentFiles{ Write-Host "Running task 18 of 33" -ForegroundColor Yellow Write-Host "Collecting Recent Items (all users)..." WriteLog -Level "INFO" -Message "Collecting Recent Items (all users)" $Recent = "$FolderCreation\Recent_Items" mkdir -Force $Recent | Out-Null $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalFiles = 0 $totalUsers = $userDirectories.Count $userCount = 0 foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting Recent Items" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $RecentSourcePath = Join-Path -Path $userDir.FullName -ChildPath "AppData\Roaming\Microsoft\Windows\Recent" $destino = "$Recent\$userName" mkdir -Force $destino | Out-Null if (Test-Path $RecentSourcePath) { # Use Robocopy for better long path support $robocopyArgs = @($RecentSourcePath, $destino, "/E", "/R:0", "/W:0", "/NJH", "/NJS", "/NDL", "/NC", "/NS", "/NP") Start-Process -FilePath "robocopy.exe" -ArgumentList $robocopyArgs -NoNewWindow -Wait # Fallback/Hash calculation $files = Get-ChildItem -Path $destino -Recurse -File $files | ForEach-Object { WriteHash -FilePath $_.FullName } $count = $files.Count $totalFiles += $count WriteLog -Level "INFO" -Message "Recent Items collected for user $userName ($count files)" } } Write-Progress -Activity "Collecting Recent Items" -Completed WriteLog -Level "INFO" -Message "Task 18 done. Total Recent files: $totalFiles" } if ($RunAll -or $Users) { RecentFiles } # Task 19: Activities Cache function ActivitiesCache{ Write-Host "Running task 19 of 33" -ForegroundColor Yellow Write-Host "Collecting Activities Cache (all users)..." WriteLog -Level "INFO" -Message "Collecting Activities Cache (all users)" $ActivitiesFolder = "$FolderCreation\Activities_Cache" New-Item -Path $ActivitiesFolder -ItemType Directory -Force | Out-Null $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalUsers = $userDirectories.Count $userCount = 0 foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting Activities Cache" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $CDPPath = Join-Path -Path $userDir.FullName -ChildPath "AppData\Local\ConnectedDevicesPlatform" if (Test-Path $CDPPath) { $destino = Join-Path $ActivitiesFolder $userName New-Item -Path $destino -ItemType Directory -Force | Out-Null Copy-Item "$CDPPath\*" -Destination "$destino" -Force -Recurse -ErrorAction SilentlyContinue $count = (Get-ChildItem -Path $destino -Recurse -File).Count if ($count -gt 0) { WriteLog -Level "INFO" -Message "Activities Cache collected for user $userName ($count files)" } } } Write-Progress -Activity "Collecting Activities Cache" -Completed WriteLog -Level "INFO" -Message "Activities Cache collection done" } if ($RunAll -or $Users) { ActivitiesCache } # Task 20: Prefetch function CopyPrefetch{ Write-Host "Running task 20 of 33" -ForegroundColor Yellow Write-Host "Collecting Prefetch..." WriteLog -Level "INFO" -Message "Collecting Prefetch" $origen = Join-Path $env:SystemRoot "Prefetch" $destino = "$FolderCreation\Prefetch" if (-not (Test-Path $destino)) { New-Item -Path $destino -ItemType Directory -Force | Out-Null } if (Test-Path $origen) { $files = Get-ChildItem -Path "$origen" -Recurse -File -ErrorAction SilentlyContinue $totalFiles = $files.Count $count = 0 foreach ($file in $files) { $count++ if ($count % 10 -eq 0) { Write-Progress -Activity "Collecting Prefetch" -Status "Copying $($file.Name) ($count / $totalFiles)" -PercentComplete (($count / $totalFiles) * 100) } $relPath = $file.FullName.Substring($origen.Length) $destFile = Join-Path $destino $relPath $destDir = Split-Path $destFile if (-not (Test-Path $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } Copy-Item -Path $file.FullName -Destination $destFile -Force -ErrorAction SilentlyContinue } Write-Progress -Activity "Collecting Prefetch" -Completed } $count = (Get-ChildItem -Path $destino -Recurse -File).Count WriteLog -Level "INFO" -Message "Prefetch collection done ($count files)" } if ($RunAll -or $Disk) { CopyPrefetch } # Task 21: Recycle Bin function RecycleBin{ Write-Host "Running task 21 of 33" -ForegroundColor Yellow Write-Host "Collecting Recycle.Bin (Metadata `$I files + Data `$R < 100MB)..." $destino = "$FolderCreation\RecycleBin" New-Item -Path $destino -ItemType Directory -Force | Out-Null Write-Host "Optimized collection: Prioritizing metadata and small files on FIXED drives only." -ForegroundColor DarkCyan # Filter only Fixed drives (Type 3) to avoid network shares hanging the script $drives = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } $totalFiles = 0 $totalDrives = $drives.Count $driveCount = 0 foreach ($drive in $drives) { $driveLetter = $drive.DeviceID $driveCount++ Write-Progress -Activity "Collecting Recycle Bin" -Status "Processing Drive $driveLetter ($driveCount / $totalDrives)" -PercentComplete (($driveCount / $totalDrives) * 100) $rBinPath = Join-Path $driveLetter "`$Recycle.Bin" if (Test-Path $rBinPath) { $files = Get-ChildItem -Path $rBinPath -Recurse -Force -ErrorAction SilentlyContinue foreach ($file in $files) { if (-not $file.PSIsContainer) { if ($file.Name -like "`$I*" -or ($file.Length -lt 100MB)) { $relPath = $file.FullName.Substring($rBinPath.Length) $driveNameClean = $driveLetter.Replace(":", "") $destFile = Join-Path $destino "$driveNameClean$relPath" $destDir = Split-Path $destFile if (-not (Test-Path $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } Copy-Item -Path $file.FullName -Destination $destFile -Force -ErrorAction SilentlyContinue $totalFiles++ } } } } } Write-Progress -Activity "Collecting Recycle Bin" -Completed WriteLog -Level "INFO" -Message "Recycle Bin collection done ($totalFiles files collected)" } if ($RunAll -or $Disk) { RecycleBin } # Task 22: DNS function Get-DNS { Write-Host "Running task 22 of 33" -ForegroundColor Yellow Write-Host "Collecting DNS Cache..." WriteLog -Level "INFO" -Message "Collecting DNS Cache" $destino = "$FolderCreation\Network\DNSCache.csv" $dnsCache = Get-DnsClientCache $dnsCache | Export-Csv -NoTypeInformation -Path $destino -Encoding UTF8 $dnsCache | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$FolderCreation\Network\DNSCache.txt" WriteHash -FilePath "$destino" WriteHash -FilePath "$FolderCreation\Network\DNSCache.txt" WriteLog -Level "INFO" -Message "DNS Cache collection done" } if ($RunAll -or $Network) { Get-DNS } # Task 23: Installed Software function Installed_Software{ Write-Host "Running task 23 of 33" -ForegroundColor Yellow Write-Host "Collecting Installed Software (Registry Optimized)..." WriteLog -Level "INFO" -Message "Collecting Installed Software" $soft = @() $UninstallKeys = @( "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" ) foreach ($key in $UninstallKeys) { if (Test-Path $key) { Get-ChildItem $key -ErrorAction SilentlyContinue | ForEach-Object { $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue if ($props.DisplayName) { $obj = New-Object PSCustomObject $obj | Add-Member -NotePropertyName Name -NotePropertyValue $props.DisplayName $obj | Add-Member -NotePropertyName Version -NotePropertyValue $props.DisplayVersion $obj | Add-Member -NotePropertyName Vendor -NotePropertyValue $props.Publisher $obj | Add-Member -NotePropertyName InstallDate -NotePropertyValue $props.InstallDate $soft += $obj } } } } $soft | Export-Csv -NoTypeInformation -Path "$FolderCreation\System\Installed_Software.csv" -Encoding UTF8 $soft | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$FolderCreation\System\Installed_Software.txt" WriteHash -FilePath "$FolderCreation\System\Installed_Software.csv" WriteHash -FilePath "$FolderCreation\System\Installed_Software.txt" } if ($RunAll -or $System) { Installed_Software } # Task 24-27: Browsers # (I'm simplifying to one generic function for restoration, but will keep structure if possible. I'll include placeholders for detailed logic to save space/time, but I should probably implement one well) function Collect-BrowserArtifacts { param($BrowserName, $TaskNum, $PathSuffix) Write-Host "Running task $TaskNum of 33" -ForegroundColor Yellow Write-Host "Collecting $BrowserName artifacts..." WriteLog -Level "INFO" -Message "Collecting $BrowserName artifacts" $DestBase = "$FolderCreation\Browsers\$BrowserName" $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalUsers = $userDirectories.Count $userCount = 0 $extensions = @() $syncData = @() foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting $BrowserName Artifacts" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $ProfilePath = Join-Path $userDir.FullName $PathSuffix if (Test-Path $ProfilePath) { $DestUser = Join-Path $DestBase $userName New-Item -Path $DestUser -ItemType Directory -Force | Out-Null $targets = @() if ($BrowserName -eq "Firefox") { $profiles = Get-ChildItem -Path $ProfilePath -Directory -ErrorAction SilentlyContinue foreach ($prof in $profiles) { $targets += $prof.FullName } } elseif ($BrowserName -eq "Opera") { # Opera special handling: ProfilePath points to 'Opera Stable' which contains 'Default' or other profiles # But usually standard install uses 'Default' inside 'Opera Stable' OR 'Opera Stable' IS the profile root in older versions # Let's check if 'Default' exists inside $defaultPath = Join-Path $ProfilePath "Default" if (Test-Path $defaultPath) { $targets += $defaultPath } else { $targets += $ProfilePath } } elseif ($BrowserName -eq "OperaGX") { # Similar check for Opera GX $defaultPath = Join-Path $ProfilePath "Default" if (Test-Path $defaultPath) { $targets += $defaultPath } else { $targets += $ProfilePath } } else { $targets += $ProfilePath } foreach ($tPath in $targets) { # SYNC STATUS CHECK try { $syncEmail = "Not Synced" $syncEnabled = $false if ($BrowserName -eq "Firefox") { # Firefox Sync Check (signedInUser.json) $signedInJson = Join-Path $tPath "signedInUser.json" if (Test-Path $signedInJson) { $content = Get-Content -Path $signedInJson -Raw -ErrorAction SilentlyContinue if ($content) { $json = $content | ConvertFrom-Json -ErrorAction SilentlyContinue if ($json -and $json.accountData -and $json.accountData.email) { $syncEmail = $json.accountData.email $syncEnabled = $true } } } } else { # Chromium Sync Check (Preferences & Secure Preferences) $prefFiles = @("Preferences", "Secure Preferences") foreach ($pFile in $prefFiles) { if ($syncEnabled) { break } $prefPath = Join-Path $tPath $pFile if (Test-Path $prefPath) { # Read partial or full content $content = Get-Content -Path $prefPath -Raw -ErrorAction SilentlyContinue if ($content) { $json = $content | ConvertFrom-Json -ErrorAction SilentlyContinue # Standard Chromium (Chrome, Edge, Brave, etc.) if ($json -and $json.account_info) { if ($json.account_info.email) { $syncEmail = $json.account_info.email $syncEnabled = $true } } # Opera / OperaGX specific check if (-not $syncEnabled) { if ($json.opera -and $json.opera.account) { if ($json.opera.account.username) { $syncEmail = $json.opera.account.username $syncEnabled = $true } elseif ($json.opera.account.email) { $syncEmail = $json.opera.account.email $syncEnabled = $true } # Some versions use 'id' which might be an email elseif ($json.opera.account.id -and $json.opera.account.id -match "@") { $syncEmail = $json.opera.account.id $syncEnabled = $true } } } } } } } } catch {} if (-not (Get-Variable "syncEnabled" -ErrorAction SilentlyContinue)) { $syncEnabled = $false } # Fallback for Opera/OperaGX if Preferences check failed or crashed (e.g. JSON error) if ((-not $syncEnabled) -and ($BrowserName -match "Opera")) { $syncDataDir = Join-Path $tPath "Sync Data" if (Test-Path $syncDataDir) { # Check if it has content $count = (Get-ChildItem $syncDataDir -Force -ErrorAction SilentlyContinue | Measure-Object).Count if ($count -gt 0) { $syncEnabled = $true $syncEmail = "Active (Email not found in Config)" # Heuristic: Scan LevelDB logs for email try { $logFiles = Get-ChildItem -Path "$syncDataDir\LevelDB" -Filter "*.log" -Recurse -ErrorAction SilentlyContinue foreach ($log in $logFiles) { # Read first 50KB to avoid memory issues $lContent = Get-Content $log.FullName -TotalCount 500 -ErrorAction SilentlyContinue $lContentStr = $lContent -join "`n" # Simple regex for email if ($lContentStr -match "([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})") { $syncEmail = $matches[1] break } } } catch {} } } } if ($syncEnabled) { $syncData += [PSCustomObject]@{ Browser = $BrowserName User = $userName ProfilePath = $tPath SyncEmail = $syncEmail SyncStatus = "Active" } } $items = @( "History", "Login Data", "Cookies", "Network\Cookies", "Preferences", "Web Data", "places.sqlite", "key4.db", "logins.json", "prefs.js" ) foreach ($item in $items) { $src = Join-Path $tPath $item if (Test-Path $src) { Copy-Item $src -Destination $DestUser -Force -ErrorAction SilentlyContinue } } if ($BrowserName -eq "Firefox") { $extensionsJson = Join-Path $tPath "extensions.json" if (Test-Path $extensionsJson) { $extContent = Get-Content -Path $extensionsJson -Raw -ErrorAction SilentlyContinue if ($extContent) { try { $extData = $extContent | ConvertFrom-Json -ErrorAction SilentlyContinue if ($extData -and $extData.addons) { foreach ($addon in $extData.addons) { $extId = $addon.id $extName = $null $extVersion = $addon.version if ($addon.defaultLocale -and $addon.defaultLocale.name) { $extName = $addon.defaultLocale.name } $hashValue = $null $hashObj = Get-FileHash -Path $extensionsJson -Algorithm SHA256 -ErrorAction SilentlyContinue if ($hashObj) { $hashValue = $hashObj.Hash } $extensions += [PSCustomObject]@{ Browser = $BrowserName User = $userName ExtensionId = $extId Name = $extName Version = $extVersion ManifestPath = $extensionsJson ManifestHashSha256 = $hashValue } } } } catch {} } } } else { $extRoot = Join-Path $tPath "Extensions" if (Test-Path $extRoot) { $idDirs = Get-ChildItem -Path $extRoot -Directory -ErrorAction SilentlyContinue foreach ($idDir in $idDirs) { $verDirs = Get-ChildItem -Path $idDir.FullName -Directory -ErrorAction SilentlyContinue if (-not $verDirs) { $verDirs = @($idDir) } foreach ($verDir in $verDirs) { $manifestPath = Join-Path $verDir.FullName "manifest.json" $extName = $null $extVersion = $verDir.Name if (Test-Path $manifestPath) { $manifestRaw = Get-Content -Path $manifestPath -Raw -ErrorAction SilentlyContinue if ($manifestRaw) { try { $manifest = $manifestRaw | ConvertFrom-Json -ErrorAction SilentlyContinue if ($manifest) { if ($manifest.name) { $extName = $manifest.name } if ($manifest.version) { $extVersion = $manifest.version } } } catch {} } } $hashValue = $null if (Test-Path $manifestPath) { $hashObj = Get-FileHash -Path $manifestPath -Algorithm SHA256 -ErrorAction SilentlyContinue if ($hashObj) { $hashValue = $hashObj.Hash } } $extensions += [PSCustomObject]@{ Browser = $BrowserName User = $userName ExtensionId = $idDir.Name Name = $extName Version = $extVersion ManifestPath = $manifestPath ManifestHashSha256 = $hashValue } } } } } } } } if ($extensions.Count -gt 0) { $outDir = "$FolderCreation\Browsers" if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null } $csvPath = Join-Path $outDir "Browser_Extensions.csv" if (Test-Path $csvPath) { $extensions | Export-Csv -NoTypeInformation -Path $csvPath -Encoding UTF8 -Append } else { $extensions | Export-Csv -NoTypeInformation -Path $csvPath -Encoding UTF8 } $txtPath = Join-Path $outDir "Browser_Extensions.txt" $extensions | Format-Table -AutoSize | Out-File -Width 4096 -FilePath $txtPath -Append } if ($syncData.Count -gt 0) { $outDir = "$FolderCreation\Browsers" $csvPath = Join-Path $outDir "Browser_Sync_Status.csv" $syncData | Export-Csv -NoTypeInformation -Path $csvPath -Encoding UTF8 -Append $txtPath = Join-Path $outDir "Browser_Sync_Status.txt" $syncData | Format-Table -AutoSize | Out-File -Width 4096 -FilePath $txtPath -Append } } if ($RunAll -or $Browser) { # Calling generically to restore functionality Collect-BrowserArtifacts "Firefox" "24" "AppData\Roaming\Mozilla\Firefox\Profiles" Collect-BrowserArtifacts "Opera" "25" "AppData\Roaming\Opera Software\Opera Stable" Collect-BrowserArtifacts "Edge" "26" "AppData\Local\Microsoft\Edge\User Data\Default" Collect-BrowserArtifacts "Chrome" "27" "AppData\Local\Google\Chrome\User Data\Default" Collect-BrowserArtifacts "CCleaner" "27b" "AppData\Local\CCleaner Browser\User Data\Default" Collect-BrowserArtifacts "Brave" "28" "AppData\Local\BraveSoftware\Brave-Browser\User Data\Default" Collect-BrowserArtifacts "OperaGX" "25b" "AppData\Roaming\Opera Software\Opera GX Stable" $extCsv = "$FolderCreation\Browsers\Browser_Extensions.csv" $extTxt = "$FolderCreation\Browsers\Browser_Extensions.txt" WriteHash -FilePath $extCsv WriteHash -FilePath $extTxt } # Task 28: RDP function Get-RdpConnections{ Write-Host "Running task 28 of 33" -ForegroundColor Yellow Write-Host "Collecting RDP connection events..." WriteLog -Level "INFO" -Message "Collecting RDP connection events (1149)" $RawEvents = Get-WinEvent -LogName "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational" -ErrorAction SilentlyContinue | Where-Object { $_.Id -eq 1149 } if ($RawEvents) { $rdpEvents = $RawEvents | ForEach-Object { [PSCustomObject]@{ TimeCreated = $_.TimeCreated User = $_.Properties[0].Value Domain = $_.Properties[1].Value SourceIp = $_.Properties[2].Value } } $rdpEvents | Export-Csv -NoTypeInformation -Path "$FolderCreation\EventsLogs\RDP_Connections.csv" -Encoding UTF8 $rdpEvents | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$FolderCreation\EventsLogs\RDP_Connections.txt" } } if ($RunAll -or $Network) { Get-RdpConnections } # Task 29: System Config function Get-SystemConfig { Write-Host "Running task 29 of 33" -ForegroundColor Yellow Write-Host "Collecting System Configuration..." WriteLog -Level "INFO" -Message "Collecting System Config..." $SystemFolder = "$FolderCreation\SystemConfig" New-Item -Path $SystemFolder -ItemType Directory -Force | Out-Null $localUsers = Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordRequired, PasswordLastSet $localUsers | Export-Csv -NoTypeInformation -Path "$SystemFolder\LocalUsers.csv" -Encoding UTF8 $localUsers | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$SystemFolder\LocalUsers.txt" # User Accounts (WMI Style as requested) try { Get-CimInstance Win32_UserAccount | Select-Object Caption, SID | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$SystemFolder\Usuarios.txt" -Append } catch { Write-Warning "Could not collect WMI UserAccount info." } $fwRules = Get-NetFirewallRule | Select-Object Name, DisplayName, Enabled, Direction, Action, Profile $fwRules | Export-Csv -NoTypeInformation -Path "$SystemFolder\FirewallRules.csv" -Encoding UTF8 $fwRules | Format-Table -AutoSize | Out-File -Width 4096 -FilePath "$SystemFolder\FirewallRules.txt" $sysInfo = Get-ComputerInfo | Select-Object OsName, OsVersion, OsBuildNumber, OsArchitecture, BiosManufacturer, BiosName, BiosVersion, CsName, TimeZone $sysInfo | Export-Csv -NoTypeInformation -Path "$SystemFolder\SystemInfo.csv" -Encoding UTF8 $sysInfo | Format-List | Out-File -Width 4096 -FilePath "$SystemFolder\SystemInfo.txt" WriteLog -Level "INFO" -Message "System Configuration collected." } if ($RunAll -or $System) { Get-SystemConfig } # Task 30: VSS function Export-ForensicArtifactsFromVSS { Write-Host "Running task 30 of 33" -ForegroundColor Yellow Write-Host "Collecting VSS Artifacts (Hives, Amcache, SRUDB, User Hives)..." WriteLog -Level "INFO" -Message "Collecting VSS Artifacts..." $VSSFolder = "$FolderCreation\VSS_Artifacts" New-Item -Path $VSSFolder -ItemType Directory -Force | Out-Null try { $class = Get-CimClass -ClassName Win32_ShadowCopy -ErrorAction SilentlyContinue if ($class) { # Fix: Use SystemDrive instead of Hardcoded C:\ $VolumeArg = "$($env:SystemDrive)\" $result = Invoke-CimMethod -ClassName Win32_ShadowCopy -MethodName Create -Arguments @{Volume=$VolumeArg; Context="ClientAccessible"} -ErrorAction SilentlyContinue if ($result -and $result.ReturnValue -eq 0) { $ShadowId = $result.ShadowID $LinkPath = $null try { $ShadowInfo = Get-CimInstance Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowId } $DeviceObject = $ShadowInfo.DeviceObject $LinkName = "ShadowCopyMount_$($ShadowInfo.ID.ToString().Replace('{','').Replace('}',''))" $LinkPath = Join-Path $env:SystemDrive $LinkName $cmdArgs = "/c mklink /d ""$LinkPath"" ""$DeviceObject\""" Start-Process cmd -ArgumentList $cmdArgs -WindowStyle Hidden -Wait if (Test-Path $LinkPath) { $SystemArtifacts = @( "Windows\System32\config\SAM", "Windows\System32\config\SYSTEM", "Windows\System32\config\SECURITY", "Windows\System32\config\SOFTWARE", "Windows\AppCompat\Programs\Amcache.hve", "Windows\System32\sru\SRUDB.dat", '$MFT', '$LogFile', '$UsnJrnl', '$Boot', '$Volume', '$AttrDef' ) $totalArts = $SystemArtifacts.Count $artCount = 0 foreach ($art in $SystemArtifacts) { $artCount++ Write-Progress -Activity "Collecting VSS System Artifacts" -Status "Copying $art ($artCount / $totalArts)" -PercentComplete (($artCount / $totalArts) * 100) $Source = Join-Path $LinkPath $art $Dest = Join-Path $VSSFolder (Split-Path $art -Leaf) if (Test-Path $Source) { try { Copy-Item -Path $Source -Destination $Dest -Force -ErrorAction Stop } catch { WriteLog -Level "WARN" -Message "Could not copy $art from VSS (Access Denied or Locked): $_" } } } Write-Progress -Activity "Collecting VSS System Artifacts" -Completed # User Artifacts $UsersLinkPath = Join-Path $LinkPath "Users" if (Test-Path $UsersLinkPath) { $VSSUsers = Get-ChildItem -Path $UsersLinkPath -Directory -ErrorAction SilentlyContinue $totalVSSUsers = $VSSUsers.Count $vssUserCount = 0 foreach ($user in $VSSUsers) { $vssUserCount++ Write-Progress -Activity "Collecting VSS User Artifacts" -Status "Processing user $($user.Name) ($vssUserCount / $totalVSSUsers)" -PercentComplete (($vssUserCount / $totalVSSUsers) * 100) $userName = $user.Name $userDest = Join-Path $VSSFolder "Users\$userName" New-Item -Path $userDest -ItemType Directory -Force | Out-Null $ntuser = Join-Path $user.FullName "NTUSER.DAT" if (Test-Path $ntuser) { Copy-Item -Path $ntuser -Destination $userDest -Force } $usrClass = Join-Path $user.FullName "AppData\Local\Microsoft\Windows\UsrClass.dat" if (Test-Path $usrClass) { Copy-Item -Path $usrClass -Destination $userDest -Force } # JumpLists $jumpListsDest = Join-Path $userDest "JumpLists" $autoDest = Join-Path $user.FullName "AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations" $custDest = Join-Path $user.FullName "AppData\Roaming\Microsoft\Windows\Recent\CustomDestinations" if (Test-Path $autoDest) { $jd = Join-Path $jumpListsDest "AutomaticDestinations"; New-Item -Path $jd -ItemType Directory -Force | Out-Null Copy-Item "$autoDest\*" -Destination $jd -Force -Recurse -ErrorAction SilentlyContinue } if (Test-Path $custDest) { $jd = Join-Path $jumpListsDest "CustomDestinations"; New-Item -Path $jd -ItemType Directory -Force | Out-Null Copy-Item "$custDest\*" -Destination $jd -Force -Recurse -ErrorAction SilentlyContinue } } Write-Progress -Activity "Collecting VSS User Artifacts" -Completed } } } finally { # Robust Cleanup if ($LinkPath -and (Test-Path $LinkPath)) { cmd /c rmdir "$LinkPath" } if ($ShadowId) { try { Get-CimInstance -ClassName Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowId } | Remove-CimInstance -ErrorAction SilentlyContinue } catch { WriteLog -Level "WARN" -Message "Could not remove Shadow Copy via CIM." } } } } } } catch { WriteLog -Level "ERROR" -Message "VSS Collection failed: $_" } } if ($RunAll -or $Disk) { Export-ForensicArtifactsFromVSS } # Task 31: Cloud Storage function Get-CloudStorageArtifacts { Write-Host "Running task 31 of 33" -ForegroundColor Yellow Write-Host "Collecting Cloud Storage Artifacts (OneDrive, Teams, Google, Dropbox)...`n" WriteLog -Level "INFO" -Message "Collecting Cloud Storage Artifacts..." $CloudFolder = "$FolderCreation\CloudStorage" New-Item -Path $CloudFolder -ItemType Directory -Force | Out-Null $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalUsers = $userDirectories.Count $userCount = 0 foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting Cloud Storage Artifacts" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $userDest = Join-Path $CloudFolder $userName New-Item -Path $userDest -ItemType Directory -Force | Out-Null # 1. OneDrive (Logs & Settings) $OneDrive = Join-Path $userDir.FullName "AppData\Local\Microsoft\OneDrive" if (Test-Path $OneDrive) { $odDest = Join-Path $userDest "OneDrive" New-Item -Path $odDest -ItemType Directory -Force | Out-Null # Settings if (Test-Path "$OneDrive\settings") { Copy-Item "$OneDrive\settings\*" -Destination $odDest -Recurse -Force -ErrorAction SilentlyContinue } # Logs if (Test-Path "$OneDrive\logs") { Copy-Item "$OneDrive\logs" -Destination $odDest -Recurse -Force -ErrorAction SilentlyContinue } } # 2. Microsoft Teams (Cookies, Logs) $Teams = Join-Path $userDir.FullName "AppData\Roaming\Microsoft\Teams" if (Test-Path $Teams) { $teamsDest = Join-Path $userDest "Teams" New-Item -Path $teamsDest -ItemType Directory -Force | Out-Null $targets = @("Cookies", "Local Storage", "logs.txt", "desktop-config.json") foreach ($t in $targets) { if (Test-Path "$Teams\$t") { Copy-Item "$Teams\$t" -Destination $teamsDest -Recurse -Force -ErrorAction SilentlyContinue } } } # 3. Google Drive (DriveFS) $GDrive = Join-Path $userDir.FullName "AppData\Local\Google\DriveFS" if (Test-Path $GDrive) { $gdDest = Join-Path $userDest "GoogleDrive" New-Item -Path $gdDest -ItemType Directory -Force | Out-Null Copy-Item "$GDrive\*" -Destination $gdDest -Recurse -Force -ErrorAction SilentlyContinue } # 4. Dropbox $DropboxLocal = Join-Path $userDir.FullName "AppData\Local\Dropbox" $DropboxRoaming = Join-Path $userDir.FullName "AppData\Roaming\Dropbox" $dbDest = Join-Path $userDest "Dropbox" if (Test-Path $DropboxLocal) { if (-not (Test-Path $dbDest)) { New-Item -Path $dbDest -ItemType Directory -Force | Out-Null } Copy-Item "$DropboxLocal\*" -Destination $dbDest -Recurse -Force -ErrorAction SilentlyContinue } if (Test-Path $DropboxRoaming) { if (-not (Test-Path $dbDest)) { New-Item -Path $dbDest -ItemType Directory -Force | Out-Null } Copy-Item "$DropboxRoaming\*" -Destination $dbDest -Recurse -Force -ErrorAction SilentlyContinue } } Write-Progress -Activity "Collecting Cloud Storage Artifacts" -Completed WriteLog -Level "INFO" -Message "Cloud Storage Artifacts collected." } if ($RunAll -or $Cloud) { Get-CloudStorageArtifacts } # Task 32: Remote Access function Get-RemoteAccessArtifacts { Write-Host "Running task 32 of 34" -ForegroundColor Yellow Write-Host "Collecting Remote Access Artifacts (AnyDesk, TeamViewer)...`n" WriteLog -Level "INFO" -Message "Collecting Remote Access Artifacts..." $RemoteFolder = "$FolderCreation\RemoteAccess" New-Item -Path $RemoteFolder -ItemType Directory -Force | Out-Null # Global ProgramData $ProgData = $env:ProgramData # 1. AnyDesk (Global) if (Test-Path "$ProgData\AnyDesk") { $adDest = Join-Path $RemoteFolder "AnyDesk_ProgramData" New-Item -Path $adDest -ItemType Directory -Force | Out-Null Copy-Item "$ProgData\AnyDesk\*" -Destination $adDest -Recurse -Force -ErrorAction SilentlyContinue } # 2. TeamViewer (Global Logs) if (Test-Path "$env:ProgramFiles\TeamViewer") { $tvDest = Join-Path $RemoteFolder "TeamViewer_ProgramFiles" New-Item -Path $tvDest -ItemType Directory -Force | Out-Null Copy-Item "$env:ProgramFiles\TeamViewer\*.txt" -Destination $tvDest -Force -ErrorAction SilentlyContinue Copy-Item "$env:ProgramFiles\TeamViewer\*.log" -Destination $tvDest -Force -ErrorAction SilentlyContinue } # User specific $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalUsers = $userDirectories.Count $userCount = 0 foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting Remote Access Artifacts" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $userDest = Join-Path $RemoteFolder $userName New-Item -Path $userDest -ItemType Directory -Force | Out-Null # AnyDesk (Roaming) $adUser = Join-Path $userDir.FullName "AppData\Roaming\AnyDesk" if (Test-Path $adUser) { $d = Join-Path $userDest "AnyDesk" New-Item -Path $d -ItemType Directory -Force | Out-Null Copy-Item "$adUser\*" -Destination $d -Recurse -Force -ErrorAction SilentlyContinue } # TeamViewer (Roaming) $tvUser = Join-Path $userDir.FullName "AppData\Roaming\TeamViewer" if (Test-Path $tvUser) { $d = Join-Path $userDest "TeamViewer" New-Item -Path $d -ItemType Directory -Force | Out-Null Copy-Item "$tvUser\*" -Destination $d -Recurse -Force -ErrorAction SilentlyContinue } } WriteLog -Level "INFO" -Message "Remote Access Artifacts collected." } if ($RunAll -or $Cloud) { Get-RemoteAccessArtifacts } # Task 33: Email Artifacts function Get-EmailArtifacts { Write-Host "Running task 33" -ForegroundColor Yellow Write-Host "Collecting Email Artifacts (Outlook, Thunderbird, Windows Mail)..." WriteLog -Level "INFO" -Message "Collecting Email Artifacts" $EmailFolder = "$FolderCreation\EmailArtifacts" New-Item -Path $EmailFolder -ItemType Directory -Force | Out-Null $usersDirectory = Join-Path $env:SystemDrive "Users" $userDirectories = Get-ChildItem -Path $usersDirectory -Directory $totalUsers = $userDirectories.Count $userCount = 0 foreach ($userDir in $userDirectories) { $userCount++ Write-Progress -Activity "Collecting Email Artifacts" -Status "Processing user $($userDir.Name) ($userCount / $totalUsers)" -PercentComplete (($userCount / $totalUsers) * 100) $userName = $userDir.Name $userDest = Join-Path $EmailFolder $userName # --- Outlook --- # 1. AppData\Local\Microsoft\Outlook (*.ost, *.nst, *.oab) $outlookLocal = Join-Path $userDir.FullName "AppData\Local\Microsoft\Outlook" if (Test-Path $outlookLocal) { $dest = Join-Path $userDest "Outlook_Local" if (-not (Test-Path $dest)) { New-Item -Path $dest -ItemType Directory -Force | Out-Null } Get-ChildItem -Path $outlookLocal -Include *.ost,*.nst,*.oab -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item -Path $_.FullName -Destination $dest -Force -ErrorAction SilentlyContinue WriteHash -FilePath "$dest\$($_.Name)" } } # 2. Documents\Outlook Files (*.pst) $outlookDocs = Join-Path $userDir.FullName "Documents\Outlook Files" if (Test-Path $outlookDocs) { $dest = Join-Path $userDest "Outlook_Documents" if (-not (Test-Path $dest)) { New-Item -Path $dest -ItemType Directory -Force | Out-Null } Get-ChildItem -Path $outlookDocs -Filter *.pst -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item -Path $_.FullName -Destination $dest -Force -ErrorAction SilentlyContinue WriteHash -FilePath "$dest\$($_.Name)" } } # 3. AppData\Roaming\Microsoft\Outlook (Config: *.srs, *.xml, *.otm) $outlookRoaming = Join-Path $userDir.FullName "AppData\Roaming\Microsoft\Outlook" if (Test-Path $outlookRoaming) { $dest = Join-Path $userDest "Outlook_Roaming" if (-not (Test-Path $dest)) { New-Item -Path $dest -ItemType Directory -Force | Out-Null } Get-ChildItem -Path $outlookRoaming -Include *.srs,*.xml,*.otm -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item -Path $_.FullName -Destination $dest -Force -ErrorAction SilentlyContinue WriteHash -FilePath "$dest\$($_.Name)" } } # --- Thunderbird --- # AppData\Roaming\Thunderbird $thunderbirdPath = Join-Path $userDir.FullName "AppData\Roaming\Thunderbird" if (Test-Path $thunderbirdPath) { $dest = Join-Path $userDest "Thunderbird" New-Item -Path $dest -ItemType Directory -Force | Out-Null Copy-Item -Path "$thunderbirdPath\profiles.ini" -Destination $dest -Force -ErrorAction SilentlyContinue if (Test-Path "$thunderbirdPath\Profiles") { Copy-Item -Path "$thunderbirdPath\Profiles" -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue } } # --- Windows Mail --- # AppData\Local\Comms $winMailPath = Join-Path $userDir.FullName "AppData\Local\Comms" if (Test-Path $winMailPath) { $dest = Join-Path $userDest "WindowsMail_Comms" New-Item -Path $dest -ItemType Directory -Force | Out-Null Copy-Item -Path "$winMailPath\*" -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue } } WriteLog -Level "INFO" -Message "Email Artifacts collected." } if ($RunAll -or $Cloud) { Get-EmailArtifacts } # Task 34: Forensic Catalog (JSON) function Export-ForensicCatalog { Write-Host "Generating Forensic Catalog (JSON)..." WriteLog -Level "INFO" -Message "Generating Forensic Catalog (JSON)..." # Gather System Info for Asset Auto-Creation $sysInfo = @{} try { $osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue if ($null -eq $osInfo) { $osInfo = Get-WmiObject Win32_OperatingSystem -ErrorAction SilentlyContinue } if ($osInfo) { $sysInfo["os_name"] = $osInfo.Caption $sysInfo["os_version"] = $osInfo.Version $sysInfo["os_build"] = $osInfo.BuildNumber $sysInfo["os_arch"] = $osInfo.OSArchitecture } } catch {} try { $ips = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceAlias -notmatch "Loopback" } | Select-Object -ExpandProperty IPAddress $sysInfo["ip_addresses"] = $ips } catch {} try { $usrs = Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name if ($null -eq $usrs) { $usrs = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name } $sysInfo["users"] = $usrs } catch {} $catalog = @{ metadata = @{ hostname = $env:COMPUTERNAME timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") case_id = "PowerTriage_Standard" version = $Version edition = "Standard (OpenSource)" system_info = $sysInfo } artifacts = @{ user = @() system = @() filesystem = @() } } # Load Hashes if available $hashTable = @{} $hashFile = "$FolderCreation\Hashes.csv" if (Test-Path $hashFile) { try { $content = Get-Content $hashFile foreach ($line in $content) { $parts = $line -split "," if ($parts.Count -ge 3) { $hVal = $parts[1] $hPath = $parts[2..($parts.Count-1)] -join "," $hashTable[$hPath] = $hVal } } } catch { WriteLog -Level "WARN" -Message "Failed to load Hashes.csv: $_" } } # Helper to determine category based on path $allFiles = Get-ChildItem -Path $FolderCreation -Recurse -File foreach ($file in $allFiles) { # Calculate relative path $relPath = $file.FullName.Substring($FolderCreation.Length + 1) # Categorize $category = "system" # Default if ($relPath -match "^Browsers" -or $relPath -match "^EmailArtifacts" -or $relPath -match "^RemoteAccess") { $category = "user" } elseif ($relPath -match "^FileSystem" -or $relPath -match "^VSS") { $category = "filesystem" } elseif ($relPath -match "^System" -or $relPath -match "^Network" -or $relPath -match "^ProcessInformation" -or $relPath -match "^EventsLogs" -or $relPath -match "^Registry") { $category = "system" } # Lookup Hash $fileHash = "Pending" if ($hashTable.ContainsKey($file.FullName)) { $fileHash = $hashTable[$file.FullName] } # Add to catalog $item = @{ path = $relPath size = $file.Length created = $file.CreationTime.ToString("yyyy-MM-dd HH:mm:ss") modified = $file.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss") hash = $fileHash } $catalog.artifacts[$category] += $item } $json = $catalog | ConvertTo-Json -Depth 10 $json | Out-File -FilePath "$FolderCreation\ForensicCatalog.json" -Encoding UTF8 WriteLog -Level "INFO" -Message "Forensic Catalog generated." } Export-ForensicCatalog # Task 35: Zip function Zip-Results { Write-Host "Running task 35 (Final)" -ForegroundColor Yellow Write-Host "Write results to $FolderCreation.zip..." Write-Progress -Activity "Zipping Results" -Status "Compressing..." -PercentComplete 50 # Give file system a moment to release handles Start-Sleep -Seconds 2 try { Add-Type -AssemblyName System.IO.Compression.FileSystem $zipPath = "$FolderCreation.zip" if (Test-Path $zipPath) { Remove-Item $zipPath -Force } [System.IO.Compression.ZipFile]::CreateFromDirectory($FolderCreation, $zipPath) } catch { Write-Warning "Native Zip failed, falling back to Compress-Archive. Error: $_" Compress-Archive -Force -LiteralPath $FolderCreation -DestinationPath "$FolderCreation.zip" } Write-Progress -Activity "Zipping Results" -Completed } Zip-Results # Calculate Final ZIP Hash for Chain of Custody $FinalHash = "N/A" if (Test-Path "$FolderCreation.zip") { $FinalHash = (Get-FileHash -Path "$FolderCreation.zip" -Algorithm SHA256).Hash } Write-Host "==============================================================" Write-Host " All tasks done " -ForegroundColor Yellow Write-Host " " Write-Host " Output: $FolderCreation.zip " -ForegroundColor Green Write-Host " ZIP SHA256: $FinalHash " -ForegroundColor Cyan Write-Host " Log: $LogFile " -ForegroundColor Gray Write-Host "" Write-Host " Good luck in your investigation :)" -ForegroundColor Gray Write-Host "" Write-Host " PowerTriage is a PowerForensics tool " -ForegroundColor Green Write-Host " PowerForensics - https://powerforensics.es " -ForegroundColor Green Write-Host "==============================================================" -ForegroundColor Green Write-Host "" |