Private/Resolve-InforcerSettingName.ps1

function Resolve-InforcerSettingName {
    <#
    .SYNOPSIS
        Resolves a settingDefinitionId to its friendly display name and optional choice option label.
    .DESCRIPTION
        Looks up the given settingDefinitionId in $script:InforcerSettingsCatalog and returns a
        hashtable with DisplayName, Description, and ValueLabel.

        When the catalog entry is missing or has no DisplayName, extracts a readable name from
        the settingDefinitionId structure (ADMX path segments, app names).
    .PARAMETER SettingDefinitionId
        The Intune settingDefinitionId to resolve.
    .PARAMETER ChoiceValue
        Optional itemId of the selected choice option.
    .OUTPUTS
        Hashtable with keys: DisplayName, Description, ValueLabel
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$SettingDefinitionId,

        [Parameter()]
        [string]$ChoiceValue
    )

    $result = @{
        DisplayName = $SettingDefinitionId
        Description = ''
        ValueLabel  = ''
    }

    if ([string]::IsNullOrEmpty($SettingDefinitionId)) {
        $result.DisplayName = ''
        return $result
    }

    # Catalog not loaded — use fallback
    if ($null -eq $script:InforcerSettingsCatalog) {
        $result.DisplayName = Get-InforcerFriendlySettingName -SettingDefinitionId $SettingDefinitionId
        return $result
    }

    $entry = $script:InforcerSettingsCatalog[$SettingDefinitionId]

    if ($null -eq $entry -or [string]::IsNullOrWhiteSpace($entry.DisplayName)) {
        # Not in catalog or empty DisplayName — extract readable name from ID
        $result.DisplayName = Get-InforcerFriendlySettingName -SettingDefinitionId $SettingDefinitionId
        if ($null -ne $entry) { $result.Description = $entry.Description }
        return $result
    }

    $result.DisplayName = $entry.DisplayName
    $result.Description = $entry.Description

    if (-not [string]::IsNullOrEmpty($ChoiceValue)) {
        $label = $entry.Options[$ChoiceValue]
        $result.ValueLabel = if ($label) { $label } else { $ChoiceValue }
    }

    return $result
}

function Get-InforcerFriendlySettingName {
    <#
    .SYNOPSIS
        Extracts a readable display name from a raw settingDefinitionId.
    .DESCRIPTION
        When the settings catalog doesn't have a friendly name, parses the ID structure
        to produce a human-readable label. Handles ADMX-backed IDs (containing ~policy~),
        Office app codes (excel16v2 → Excel), and general vendor_msft patterns.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$SettingDefinitionId)

    # Known app code → friendly name mapping
    $appNames = @{
        'excel16v2' = 'Excel'; 'excel16v8' = 'Excel'; 'word16v2' = 'Word'
        'ppt16v2' = 'PowerPoint'; 'access16v2' = 'Access'; 'outlk16v2' = 'Outlook'
        'visio16v2' = 'Visio'; 'proj16v2' = 'Project'; 'pub16v2' = 'Publisher'
        'onent16v2' = 'OneNote'; 'office16v2' = 'Office'; 'office16v8' = 'Office'
        'chromeintunev1' = 'Chrome'
    }

    # Strip user/device vendor prefix
    $stripped = $SettingDefinitionId -replace '^(user|device)_vendor_msft_policy_config_', ''
    if ($stripped -eq $SettingDefinitionId) {
        # Not an ADMX pattern — strip generic vendor prefix
        $stripped = $SettingDefinitionId -replace '^(user|device)_vendor_msft_', ''
    }

    # Extract app name
    $appLabel = ''
    foreach ($code in $appNames.Keys) {
        if ($stripped -match "^${code}[~_]") {
            $appLabel = $appNames[$code]
            $stripped = $stripped.Substring($code.Length).TrimStart('~', '_')
            break
        }
    }

    # Split on ~ to get ADMX template segments
    $segments = $stripped -split '[~]' | Where-Object { $_ -ne '' -and $_ -ne 'policy' }

    # Within each segment, split on _l_ (ADMX leaf separator)
    $parts = [System.Collections.Generic.List[string]]::new()
    foreach ($seg in $segments) {
        foreach ($p in ($seg -split '_l_')) {
            $s = $p.Trim('_')
            # Strip ADMX prefixes
            $s = $s -replace '^l_', ''
            # Strip noise suffixes
            $s = $s -replace '_?empty\d*$', ''
            $s = $s -replace 'dropid$', ''
            $s = $s -replace '_?enum$', ''
            # Strip known noise patterns
            $s = $s -replace '^microsoftoffice\w+$', ''
            $s = $s -replace '^\w+options$', ''
            $s = $s -replace '_v\d+$', ''
            $s = $s -replace '^securitysettings$', 'Security'
            $s = $s -replace '^trustcenter$', 'Trust Center'
            $s = $s -replace '^fileblocksettings$', 'File Block Settings'
            $s = $s -replace '^protectedview$', 'Protected View'
            $s = $s -replace '^cryptography$', 'Cryptography'
            if ([string]::IsNullOrWhiteSpace($s) -or $s.Length -lt 3) { continue }
            # Known compound words → readable form
            $s = $s -replace 'vbawarningspolicy', 'VBA Warnings Policy'
            $s = $s -replace 'setdefaultfileblockbehavior', 'Default File Block Behavior'
            $s = $s -replace 'macroruntimescanscope', 'Macro Runtime Scan Scope'
            $s = $s -replace 'blockxllfrominternet', 'Block XLL From Internet'
            $s = $s -replace 'retrievingcrlscertificaterevocationlists', 'Retrieving CRLs'
            $s = $s -replace 'signaturestatusdialog', 'Signature Status Dialog'
            $s = $s -replace 'setdocumentbehavioriffilevalidationfails', 'Document Behavior If File Validation Fails'
            $s = $s -replace 'publisherautomationsecuritylevel', 'Automation Security Level'
            $s = $s -replace 'determinewhethertoforceencrypted\w+', 'Force Encrypted Macros Scan'
            $s = $s -replace 'webcontentwarninglevel\w*', 'Web Content Warning Level'
            $s = $s -replace 'forcefileextenstionstomatch', 'Force File Extensions To Match'
            # Split on underscores, then camelCase to spaces
            $s = $s -replace '_', ' '
            $s = $s -creplace '([a-z])([A-Z])', '$1 $2'
            $s = (Get-Culture).TextInfo.ToTitleCase($s.ToLower())
            [void]$parts.Add($s)
        }
    }
    # Deduplicate consecutive identical parts
    $deduped = [System.Collections.Generic.List[string]]::new()
    $prev = ''
    foreach ($p in $parts) {
        if ($p -ne $prev) { [void]$deduped.Add($p); $prev = $p }
    }
    $parts = $deduped

    # Build: "App > last 2 meaningful parts"
    $tail = if ($parts.Count -gt 2) { @($parts[$parts.Count - 2], $parts[$parts.Count - 1]) }
            elseif ($parts.Count -gt 0) { $parts.ToArray() }
            else { @() }
    $display = @()
    if ($appLabel) { $display += $appLabel }
    $display += $tail
    if ($display.Count -gt 0) { return ($display -join ' > ') }
    return $SettingDefinitionId
}