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
}