<# .SYNOPSIS Collects a complete diagnostics package from an Intune-managed Windows device. .DESCRIPTION Mimics the Intune "Collect diagnostics" action and extends it with: - MDM logs via mdmdiagnosticstool.exe (all registered areas) - Relevant Event Logs (MDM, Entra/AAD, Device Registration, ESP/Shell-Core) - Registry exports (Enrollments, PolicyManager, IME, Autopilot, GPO policies) - Identity status (dsregcmd, machine AND interactive-user context), certificates, network info - Intune Management Extension (IME) logs, PSADT / app install logs (Windows\Logs\Software, ProgramData\Logs\Software) - Defender support files, Windows Update logs, system reports - Co-management, Defender for Endpoint onboarding, Delivery Optimization state - Disk space, time sync, TPM status - Status of relevant services and scheduled tasks Result: a single zip file in %ProgramData%\Sherlog\Collect (a folder only SYSTEM and Administrators can read or write) or in -OutputPath, plus a _MANIFEST.json describing the run (version, profile, per-step outcome, redaction outcome). .PARAMETER OutputPath Folder where the zip file will be created. Default: %ProgramData%\Sherlog\Collect, created with an ACL that only grants SYSTEM and Administrators access (a standard user can neither read the package nor plant files in it). A custom folder is used as-is; the temporary work folder inside it is always locked down the same way. .PARAMETER Remote Slim profile for unattended/Intune use: skips the slow and large sections (msinfo32, Get-WindowsUpdateLog, Defender -GetFiles cab, full-range event log export, full mdmdiagnosticstool area zip) so the run stays well under the Intune script timeout and the Sherlog upload size limit, while keeping the IME and PSADT logs (last 14 days, size-capped), event logs (last 14 days), registry, identity and network data. .PARAMETER UploadUrl When set, the resulting zip is uploaded to this Sherlog drop-off endpoint, e.g. https://sherlog.nl/api/diagnostics . Requires -UploadToken. Must be https:// - a plain http:// URL is refused so the token is never sent in cleartext (http://localhost and http://127.0.0.1 are allowed for testing). .PARAMETER UploadToken The device UPLOAD token (shu_...) from the Sherlog /inbox page. It only authorizes uploads; the INBOX KEY (shk_...) that opens the inbox must never be deployed to devices, and the script refuses to run with it. The token is always redacted from every collected text file (including the transcript, which PowerShell stamps with the full command line it was invoked with), regardless of -Anonymize, and the finished zip is scanned for it (UTF-8, UTF-16 and base64 forms, binaries included) before upload. BitLocker recovery passwords (48 digits in 8 groups of 6) are redacted from every text file the same way, in every run. Note that the token is visible to whoever can read the Intune script or the PowerShell script-block log on the device (see Remediate-CollectToSherlog.ps1). With -UploadUrl and -UploadToken set, the script also pings /api/collect-status at the start of the run and again if it knows the run failed, so the inbox can show the device as "collecting" instead of staying empty for the minutes the collection takes. The ping carries only the phase, the profile, the collector version and the same (optionally anonymized) device label as the upload - never log content. .PARAMETER MaxUploadMB Client-side size guard matched against the server's MAX_UPLOAD_MB (default 100). A package over this size is not uploaded (it would be rejected with 413 anyway); the local zip is kept. .PARAMETER Proxy Explicit proxy URL for all outbound calls (e.g. http://proxy.contoso.com:8080). If omitted, the machine WinHTTP proxy is used (read from the registry, with `netsh winhttp show proxy` as a fallback), since a SYSTEM-context run has no per-user WinINET proxy settings and would otherwise fail on any proxy-only network. .PARAMETER Anonymize Best-effort redaction of tenant, company, user and device data from the package: tenant id/name, company name, domain(s), UPN/e-mail, device name, Entra/Intune device ids, user names and profile folder names, user SIDs, serial number, Defender org id, Wi-Fi SSIDs and IPv4/IPv6/MAC addresses are replaced with placeholders in every file that decodes as text (including the UTF-16 hex(2)/hex(7) values in .reg exports and base64 blobs that decode to text, such as the IME toast arguments in AppWorkload.log), and the zip name + upload device name become a salted, non-reversible label. Name-like values only match as whole words, so a domain CORP does not break "Corporation". Well-known system principals (SYSTEM, NT AUTHORITY, ...) are never redacted, since doing so would corrupt registry paths like HKEY_LOCAL_MACHINE\SYSTEM\... and break Sherlog's SYSTEM-context detection. This is best-effort, NOT a guarantee - review the package before sharing. Binary files (event logs .evtx, Defender .cab, the nested mdmdiag .zip, .etl) cannot be scrubbed and are therefore left out of an anonymized package (the *-ErrorsWarnings.txt/.json event summaries stay in); like any file that cannot be redacted they are listed in _MANIFEST.json. If redaction fails as a whole, nothing is uploaded. .PARAMETER WrapperVersion Set by the Intune remediation wrapper (Remediate-CollectToSherlog.ps1) so the manifest records which wrapper ran the collector. .EXAMPLE .\Collect-IntuneDiagnostics.ps1 .\Collect-IntuneDiagnostics.ps1 -OutputPath D:\Diag .EXAMPLE # Share-safe, best-effort anonymized package: .\Collect-IntuneDiagnostics.ps1 -Remote -Anonymize .EXAMPLE # Unattended drop-off (e.g. from an Intune remediation script): .\Collect-IntuneDiagnostics.ps1 -Remote ` -UploadUrl 'https://sherlog.nl/api/diagnostics' -UploadToken 'shu_...' .NOTES Run as Administrator (elevated PowerShell), or as SYSTEM via Intune. Targets Windows PowerShell 5.1. #> [CmdletBinding()] param( [string]$OutputPath, [switch]$Remote, [string]$UploadUrl, [string]$UploadToken, [int]$MaxUploadMB = 100, [string]$Proxy, [switch]$Anonymize, [string]$WrapperVersion ) $ScriptVersion = '1.4.5' # ============================================================================ # Shared helpers. The proxy and folder-ACL functions below are byte-identical # copies of the ones in Remediate-CollectToSherlog.ps1 (a test enforces this): # the wrapper is pasted into Intune on its own and cannot dot-source them. # ============================================================================ function Enable-SherlogTls12 { # Add TLS 1.2 without dropping what is already enabled (TLS 1.3 or # SystemDefault on newer .NET). SystemDefault (0) already lets the OS pick. try { $current = [Net.ServicePointManager]::SecurityProtocol if ([int]$current -ne 0 -and -not ($current -band [Net.SecurityProtocolType]::Tls12)) { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } } catch { Write-Verbose "TLS setup: $($_.Exception.Message)" } } function Protect-SherlogText { # One short, single-line, ASCII-only string that never carries the secret: # used for the Intune output line and for the status-ping reason (PS 5.1 # mangles non-ASCII in -Body; the server caps the reason again anyway). param([string]$Text, [string]$Secret, [int]$Max = 400) $t = "$Text" if ($Secret) { $t = $t -replace [regex]::Escape($Secret), '' } $t = ($t -replace '[^\x20-\x7E]+', ' ').Trim() if ($t.Length -gt $Max) { $t = $t.Substring(0, $Max) + '...' } return $t } function ConvertFrom-SherlogProxyServer { # WinHTTP proxy string -> proxy URL for https traffic, or $null. # Accepts 'host:port', 'http://host:port' and the per-protocol form # 'http=a:80;https=b:443' (https wins, then http; socks/ftp-only is # unusable for Invoke-WebRequest and yields $null). param([string]$Server) if (-not $Server) { return $null } $plain = $null $perProtocol = @{} foreach ($entry in ($Server -split '[;\s]+')) { if (-not $entry) { continue } if ($entry -match '^([A-Za-z]+)=(.+)$') { $perProtocol[$matches[1].ToLowerInvariant()] = $matches[2] } elseif (-not $plain) { $plain = $entry } } $chosen = $plain if ($perProtocol.ContainsKey('https')) { $chosen = $perProtocol['https'] } elseif ($perProtocol.ContainsKey('http')) { $chosen = $perProtocol['http'] } if (-not $chosen) { return $null } if ($chosen -match '^https?://') { return $chosen.TrimEnd('/') } if ($chosen -match '^[A-Za-z][A-Za-z0-9+.-]*://') { return $null } return 'http://' + $chosen.TrimEnd('/') } function ConvertFrom-SherlogWinHttpSetting { # Decodes the WinHttpSettings REG_BINARY (what `netsh winhttp set proxy` # writes): DWORD size, DWORD counter, DWORD flags (1 = direct, # 2 = proxy bit), DWORD len + ASCII proxy, DWORD len + ASCII bypass list. # Returns Server/Bypass ('' Server = direct), or $null when unreadable. param([byte[]]$Bytes) if (-not $Bytes -or $Bytes.Length -lt 16) { return $null } $flags = [BitConverter]::ToInt32($Bytes, 8) $server = '' $bypass = '' if ($flags -band 2) { $len = [BitConverter]::ToInt32($Bytes, 12) if ($len -lt 0 -or 16 + $len -gt $Bytes.Length) { return $null } $server = [Text.Encoding]::ASCII.GetString($Bytes, 16, $len) $offset = 16 + $len if ($offset + 4 -le $Bytes.Length) { $blen = [BitConverter]::ToInt32($Bytes, $offset) if ($blen -gt 0 -and $offset + 4 + $blen -le $Bytes.Length) { $bypass = [Text.Encoding]::ASCII.GetString($Bytes, $offset + 4, $blen) } } } return [pscustomobject]@{ Server = $server.Trim([char]0, ' '); Bypass = $bypass.Trim([char]0, ' ') } } function Get-SherlogNetshProxy { # Locale-independent parse of `netsh winhttp show proxy`: the labels are # translated ("Proxy Server(s)", "Proxyserver", ...), but the layout is # always "