Parse-IntuneDiag.ps1
|
<#PSScriptInfo .VERSION 1.0 .GUID 179d80d5-1c29-4dd9-8e84-65b1871f177b .AUTHOR JeffGilbert .COMPANYNAME Community .COPYRIGHT MIT License .RELEASENOTES v1.0.0 - 4.20.26 - Initial release #> <# .SYNOPSIS Watches a directory for Microsoft Intune diagnostic ZIP files and automatically extracts, processes, and organizes their contents. .DESCRIPTION This script monitors a specified inbox directory for newly created or renamed ZIP files (typically produced by Microsoft Intune diagnostics). When a matching ZIP is detected, the script automatically: - Extracts the ZIP contents - Collects EVTX files into structured folders - Collects and expands CAB files - Extracts MDM Logs (EVTX and LOG) - Converts EVTX files into JSON for easier analysis The script is designed to handle modern browser download behavior (temporary files renamed on completion) and uses FileSystemWatcher events to reliably detect completed ZIP files. It can be run interactively or under SYSTEM context (for example, as a scheduled task or service wrapper). .PARAMETER HomeDirectory Specifies the base working directory used to store extracted data and output. Subdirectories such as `outbox` and `extracted` are created automatically. Default: C:\Parse-IntuneDiag .PARAMETER Inbox (Optional) Specifies the directory to watch for incoming ZIP files. If not provided, the script automatically detects the interactive user's Downloads folder. If the specified inbox directory does not exist, it will be created automatically. .INPUTS None. This script does not accept pipeline input. .OUTPUTS None. The script performs file system operations and writes status information to the console. All processed data is written to disk under the specified HomeDirectory. .EXAMPLE .\Parse-IntuneDiag.ps1 Runs the script using the interactive user's Downloads folder as the inbox and C:\Parse-IntuneDiag as the working directory. .EXAMPLE .\Parse-IntuneDiag.ps1 -Inbox "D:\IntuneDiagnostics" Watches D:\IntuneDiagnostics for incoming ZIP files instead of the Downloads folder. .EXAMPLE .\Parse-IntuneDiag.ps1 -HomeDirectory "D:\Parse-Results" Uses D:\Parse-Results as the base directory for extracted and processed diagnostic data. .NOTES Author: Jeff Gilbert Purpose: Automate parsing and analysis of Microsoft Intune diagnostic packages Execution Context: Interactive user or SYSTEM The script uses in-memory tracking to avoid processing the same ZIP file more than once during a single execution. Press Ctrl+C to stop the watcher and gracefully shut it down. .LINK https://github.com/jeffgilb/Parse-IntuneDiag #> param( [string]$HomeDirectory = (Join-Path $env:SystemDrive 'Parse-IntuneDiag'), [string]$inbox ) # --------------------- Helper function --------------------- function Get-InteractiveUserDownloads { try { $explorer = Get-Process explorer -IncludeUserName -ErrorAction Stop | Select-Object -First 1 $sid = (New-Object System.Security.Principal.NTAccount($explorer.UserName)). Translate([System.Security.Principal.SecurityIdentifier]).Value $regPath = "Registry::HKEY_USERS\$sid\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" $downloads = (Get-ItemProperty -Path $regPath -ErrorAction Stop). '{374DE290-123F-4565-9164-39C4925E467B}' return [Environment]::ExpandEnvironmentVariables($downloads) } catch { return $null } } # --------------------- SYSTEM-safe Initialization --------------------- do { $durationSeconds = 2 $steps = 50 # smoother animation $delay = $durationSeconds / $steps Clear-Host for ($i = 1; $i -le $steps; $i++) { $percent = ($i / $steps) * 100 Write-Progress -Activity "Initializing" -Status "Starting up…" -PercentComplete $percent Start-Sleep -Seconds $delay } # Clear the progress bar Write-Progress -Activity "Initializing" -Completed $downloadsDir = Get-InteractiveUserDownloads } until ($downloadsDir -and (Test-Path $downloadsDir)) $outboxPath = Join-Path $HomeDirectory 'outbox' $extractPath = Join-Path $HomeDirectory 'extracted' foreach ($path in @($outboxPath, $extractPath)) { if (-not (Test-Path $path)) { New-Item -Path $path -ItemType Directory -Force | Out-Null } } if ( $inbox ) { if (-not (Test-Path $inbox)){ New-Item -Path $inbox -ItemType Directory -Force | Out-Null } $inboxPath = $inbox $downloadsDir = $inboxPath.TrimEnd('\') } else {$inboxPath = $downloadsDir.TrimEnd('\') write-host $inboxPath} Clear-Host Write-Host "Watching for ZIP files in $inboxPath" -ForegroundColor Green Write-Host "(Ctrl+C to stop)" -ForegroundColor Yellow # -------------------- File Watcher -------------------- $watcher = New-Object System.IO.FileSystemWatcher $inboxPath, '*.zip' $watcher.IncludeSubdirectories = $false $watcher.EnableRaisingEvents = $true $script:ProcessedZips = New-Object System.Collections.Generic.HashSet[string] # -------------------- Event Handler -------------------- $handler = { try { if (-not $script:ProcessedZips) { $script:ProcessedZips = New-Object System.Collections.Generic.HashSet[string] } $path = $Event.SourceEventArgs.FullPath if ($path -notlike '*.zip') { return } Write-Host "`nCollected Diagnostics ZIP detected: " -ForegroundColor Cyan Write-Host " -> $path" -ForegroundColor Green $inboxPath = $Event.MessageData.Inbox $outboxPath = $Event.MessageData.Outbox $extract = $Event.MessageData.Extract $zipFiles = Get-ChildItem -Path $inboxPath -Filter 'DiagLogs-*.zip' -File foreach ($zip in $zipFiles) { if ($script:ProcessedZips.Contains($zip.FullName)) { continue } $folderName = [IO.Path]::GetFileNameWithoutExtension($zip.Name) $destination = Join-Path $extract $folderName Write-Host "[*] Processing $($zip.Name)" -ForegroundColor Yellow Start-Sleep -Seconds 2 # brief pause for file system stability Expand-Archive -Path $zip.FullName -DestinationPath $destination -Force Remove-Item $zip.FullName -Force $script:ProcessedZips.Add($zip.FullName) #----------------------------------------------------- # Extract EVTX #----------------------------------------------------- $sourceRoot = $destination $evtxMain = "$outboxPath\$folderName\EVTX\Main" Write-Host " [+] Extracting Main EVTX files..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $evtxMain -Force | Out-Null $evtxCount = 0 Get-ChildItem -Path $sourceRoot -Recurse -Filter "*.evtx" -File | ForEach-Object { $ErrorActionPreference = 'SilentlyContinue' Write-Host " -> Copied: $($_.Name)" -ForegroundColor DarkGray Copy-Item -Path $_.FullName -Destination $evtxMain -Force $evtxCount++ } Write-Host " [OK] Copied $evtxCount Main EVTX files" -ForegroundColor Green #----------------------------------------------------- # Extract CAB #----------------------------------------------------- $sourceRoot = $destination $cabFolder = "$outboxPath\$folderName\CAB" Write-Host " [+] Extracting CAB files..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $cabFolder -Force | Out-Null $cabCount = 0 Get-ChildItem -Path $sourceRoot -Recurse -Filter "mdm*.cab" -File | ForEach-Object { Write-Host " -> Copied: $($_.Name)" -ForegroundColor DarkGray Copy-Item -Path $_.FullName -Destination $cabFolder -Force $cabCount++ } Write-Host " [OK] Copied $cabCount CAB files" -ForegroundColor Green #----------------------------------------------------- # Expand CAB #----------------------------------------------------- $sourceRoot = $cabFolder $outDir = $null Write-Host " [+] Expanding CAB archives..." -ForegroundColor Yellow Get-ChildItem -Path $sourceRoot -Recurse -Filter "mdm*.cab" -File | ForEach-Object { $cabPath = $_.FullName $outDir = Join-Path $_.DirectoryName $_.BaseName New-Item -ItemType Directory -Path $outDir -Force | Out-Null Write-Host " -> Expanding $($_.Name)..." -ForegroundColor DarkGray expand.exe "$cabPath" -F:* "$outDir" | Out-Null } Write-Host " [OK] CAB expansion complete" -ForegroundColor Green #----------------------------------------------------- # MDMLOGS EVTX #----------------------------------------------------- if ($outDir) { $sourceRoot = $outDir $evtxMdmlogs = "$outboxPath\$folderName\EVTX\MdmLogs" Write-Host " [+] Extracting MdmLogs EVTX files..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $evtxMdmlogs -Force | Out-Null $mdmEvtxCount = 0 Get-ChildItem -Path $sourceRoot -Recurse -Filter "*.evtx" -File | ForEach-Object { Copy-Item -Path $_.FullName -Destination $evtxMdmlogs -Force Write-Host " -> Copied: $($_.Name)" -ForegroundColor DarkGray $mdmEvtxCount++ } Write-Host " [OK] Copied $mdmEvtxCount MdmLogs EVTX files" -ForegroundColor Green } #----------------------------------------------------- # MDMLOGS LOG #----------------------------------------------------- if ($outDir) { $sourceRoot = $outDir $mdmlogs = "$outboxPath\$folderName\LOGS" Write-Host " [+] Extracting MdmLogs LOG files..." -ForegroundColor Yellow New-Item -ItemType Directory -Path $mdmlogs -Force | Out-Null $mdmLogCount = 0 Get-ChildItem -Path $sourceRoot -Recurse -Filter "*.log" -File | ForEach-Object { Copy-Item -Path $_.FullName -Destination $mdmlogs -Force Write-Host " -> Copied: $($_.Name)" -ForegroundColor DarkGray $mdmLogCount++ } Write-Host " [OK] Copied $mdmLogCount MdmLogs LOG files" -ForegroundColor Green } #----------------------------------------------------- # CONVERT EVTXs TO JSON #----------------------------------------------------- Write-Host " [+] Converting EVTX files to JSON..." -ForegroundColor Yellow $evtxPaths = @( $evtxMain, $evtxMdmlogs ) $jsonCount = 0 foreach ($evtxPath in $evtxPaths) { if ($evtxPath -eq $evtxMain){ $jsonOutDir = Join-Path $outboxPath "$folderName\JSON\Main" } elseif ($evtxPath -eq $evtxMdmlogs){ $jsonOutDir = Join-Path $outboxPath "$folderName\JSON\MdmLogs" } New-Item -ItemType Directory -Path $jsonOutDir -Force | Out-Null Get-ChildItem -Path $evtxPath -Filter "*.evtx" -File | ForEach-Object { $jsonPath = Join-Path $jsonOutDir ($_.Name + ".json") Get-WinEvent -Path $_.FullName | Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message | ConvertTo-Json -Depth 4 | Out-File $jsonPath -Encoding UTF8 Write-Host " -> Converted: $($_.Name)" -ForegroundColor DarkGray $jsonCount++ } } Write-Host " [OK] Converted $jsonCount files to JSON" -ForegroundColor Green Write-Host "[OK] COMPLETE: $folderName ready in $outboxPath" -ForegroundColor Cyan Write-Host "" } } catch { Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red } } # -------------------- Register Events -------------------- Register-ObjectEvent -InputObject $watcher -EventName Created -MessageData @{ Inbox = $inboxPath Outbox = $outboxPath Extract = $extractPath } -Action $handler Register-ObjectEvent -InputObject $watcher -EventName Renamed -MessageData @{ Inbox = $inboxPath Outbox = $outboxPath Extract = $extractPath } -Action $handler # -------------------- Spinner loop -------------------- $spinner = @('|','/','-','\') $index = 0 Write-Host "" try { while ($true) { $char = $spinner[$index % $spinner.Count] Write-Host "`rWatching for ZIP files " -Foregroundcolor Green -NoNewline Write-Host "(Ctrl+C to stop) " -Foregroundcolor Yellow -NoNewline Write-Host $char -Foregroundcolor Green -NoNewline $index++ Start-Sleep -Milliseconds 200 } } finally { Write-Host "`n" Get-EventSubscriber | Unregister-Event $watcher.EnableRaisingEvents = $false $watcher.Dispose() for ($i = 1; $i -le 5; $i++) { $dots = '.' * $i Write-Host "`rStopping Watcher$dots " -NoNewline -Foregroundcolor Yellow Start-Sleep -Milliseconds 400 } Write-Host "`rWatcher shutdown complete.`n" -Foregroundcolor Green } |