Private/Rules/Find-IslEncodingIssue.ps1
|
function Find-IslEncodingIssue { <# .SYNOPSIS Flags files whose bytes Windows PowerShell 5.1 will decode as something other than UTF-8. .DESCRIPTION Intune delivers the script bytes unchanged. Without a UTF-8 BOM, Windows PowerShell 5.1 reads the file as ANSI, so every non-ASCII character in the source is corrupted before the script runs ("Grüße — ✓" became "Grüße â€" âœ""). With a BOM the source decodes correctly, but text the script *outputs* still goes through the OEM console code page on its way to Intune and loses anything outside it. .PARAMETER Context The IntuneScriptLab.ScriptContext from Get-IslScriptContext: AST, tokens, bytes and the effective ScriptType, Context and Architecture. .EXAMPLE Find-IslEncodingIssue -Context (Get-IslScriptContext -Path .\Detect.ps1) The findings this rule produces for one script, as IntuneScriptLab.Finding objects. #> [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject]$Context ) $rule = 'IslEncodingIssue' $bytes = $Context.Bytes if (-not $bytes -or $bytes.Length -eq 0) { return } $hasUtf8Bom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF $isUtf16 = $bytes.Length -ge 2 -and (($bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) -or ($bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF)) $hasNonAscii = $false foreach ($byte in $bytes) { if ($byte -gt 0x7F) { $hasNonAscii = $true; break } } if ($isUtf16) { $findingSplat = @{ RuleName = $rule Severity = 'Warning' Context = $Context Extent = $Context.Ast.Extent Message = 'File is UTF-16. Intune expects UTF-8; save as UTF-8 with BOM' Evidence = ('Microsoft Learn: "Ensure the scripts are encoded in UTF-8"; files are delivered ' + 'byte-for-byte') Fix = @{ Encoding = 'UTF8BOM' } } New-IslFinding @findingSplat return } if ($hasNonAscii -and -not $hasUtf8Bom) { $isUtf8 = $true try { $null = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes) } catch { $isUtf8 = $false } if ($isUtf8) { $findingSplat = @{ RuleName = $rule Severity = 'Warning' Context = $Context Extent = $Context.Ast.Extent Message = ('Non-ASCII characters in a UTF-8 file without a BOM: Windows PowerShell 5.1 ' + 'decodes it as ANSI and corrupts them. Save as UTF-8 with BOM') Evidence = ('Same bytes uploaded with and without BOM: without, the literal "Grüße — ✓" ran ' + 'as "Grüße â€" âœ"" (REM-ENC-BOM vs REM-PROBE-SYS64)') Fix = @{ Encoding = 'UTF8BOM' } } } else { # Not UTF-8 at all: an ANSI file. 5.1 reads it in the ANSI code page, so the characters # survive on the device, but nothing else expects it. Repair decodes it as ANSI $findingSplat = @{ RuleName = $rule Severity = 'Information' Context = $Context Extent = $Context.Ast.Extent Message = ('Non-ASCII bytes that are not UTF-8 (an ANSI code page): Windows PowerShell 5.1 ' + 'reads the file as ANSI, but Intune expects UTF-8 and other tooling reads it as such. ' + 'Save as UTF-8 with BOM') Evidence = ('Microsoft Learn: "Ensure the scripts are encoded in UTF-8"; Windows PowerShell ' + 'reads a file without a BOM in the system ANSI code page (about_Character_Encoding)') Fix = @{ Encoding = 'UTF8BOM' } } } New-IslFinding @findingSplat } if ($hasNonAscii -and $Context.ScriptType -in 'Detection', 'Remediation', 'Win32Detection') { $literals = Get-IslStringLiteral -Ast $Context.Ast | Where-Object { $_.Value -match '[^\x00-\x7F]' } | Select-Object -First 1 if ($literals) { $findingSplat = @{ RuleName = $rule Severity = 'Information' Context = $Context Extent = $literals.Extent Message = ('Non-ASCII text in output is mangled on the way to Intune (OEM code page); keep ' + 'reported output ASCII') Evidence = ('With a BOM the script ran correctly, but Intune reported "Grüße - √" for ' + '"Grüße — ✓" (REM-ENC-BOM)') } New-IslFinding @findingSplat } } } |