Private/ReportGlossary.ps1

# The words of the trade a Report uses, each said once in plain words. A Report names
# a DFS-Namespace or a Primary Refresh Token by the name a Technician finds it under in
# Windows and in a search, and not by a German word made up for it; what the name means
# stands here, at the end of the Report, for whoever has not met it.
#
# Only the terms a Report uses: a list of every word Gutcheck knows is a dictionary
# nobody reads.

function Get-ReportGlossary {
    <#
    .SYNOPSIS
        Every term the glossary knows: its name, what it is, and the other ways a text
        writes it. Pure.
    .DESCRIPTION
        The name itself is always found; Also names what else counts as the same term,
        as a regular expression: "W32Time" is the Windows-Zeitgeber as well.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param()

    $entry = { param($Term, $Text, $Also) [pscustomobject]@{ Term = $Term; Text = $Text; Also = $Also } }

    # The domain
    & $entry (Get-Text 'Glossary.Term.DfsNamespace')       (Get-Text 'Glossary.Text.DfsNamespace')       ''
    & $entry (Get-Text 'Glossary.Term.Sysvol')             (Get-Text 'Glossary.Text.Sysvol')             ''
    & $entry (Get-Text 'Glossary.Term.Kerberos')           (Get-Text 'Glossary.Text.Kerberos')           ''
    & $entry (Get-Text 'Glossary.Term.Ldap')               (Get-Text 'Glossary.Text.Ldap')               ''
    & $entry (Get-Text 'Glossary.Term.Ntlm')               (Get-Text 'Glossary.Text.Ntlm')               'NTLMv[12]'
    & $entry (Get-Text 'Glossary.Term.LmLevel')            (Get-Text 'Glossary.Text.LmLevel')            'LmCompatibilityLevel|NTLM-Stufe|NTLM-Anmeldestufe'
    & $entry (Get-Text 'Glossary.Term.SecureChannel')      (Get-Text 'Glossary.Text.SecureChannel')      ''
    & $entry (Get-Text 'Glossary.Term.Forest')             (Get-Text 'Glossary.Text.Forest')             'Gesamtstruktur'
    & $entry (Get-Text 'Glossary.Term.Gpo')                (Get-Text 'Glossary.Text.Gpo')                'Gruppenrichtlinienobjekte?|GPOs?'
    & $entry (Get-Text 'Glossary.Term.ClientSideExtension') (Get-Text 'Glossary.Text.ClientSideExtension') 'Client-Side Extensions'
    & $entry (Get-Text 'Glossary.Term.DriveMaps')          (Get-Text 'Glossary.Text.DriveMaps')          ''
    & $entry (Get-Text 'Glossary.Term.WmiFilter')          (Get-Text 'Glossary.Text.WmiFilter')          ''
    & $entry (Get-Text 'Glossary.Term.W32Time')            (Get-Text 'Glossary.Text.W32Time')            'W32Time|Windows-Zeitgeber|w32tm'
    & $entry (Get-Text 'Glossary.Term.Ntp')                (Get-Text 'Glossary.Text.Ntp')                '\(S\)NTP'
    & $entry (Get-Text 'Glossary.Term.Stratum')            (Get-Text 'Glossary.Text.Stratum')            ''
    & $entry (Get-Text 'Glossary.Term.LeapIndicator')      (Get-Text 'Glossary.Text.LeapIndicator')      'Sprungindikator|Leap Indicator'

    # Signing in to Microsoft 365, and Outlook
    & $entry (Get-Text 'Glossary.Term.EntraId')            (Get-Text 'Glossary.Text.EntraId')            'Azure AD|Entra'
    & $entry (Get-Text 'Glossary.Term.Prt')                (Get-Text 'Glossary.Text.Prt')                'Primary Refresh Token|AzureAdPrt|PRT'
    & $entry (Get-Text 'Glossary.Term.Wam')                (Get-Text 'Glossary.Text.Wam')                'WAM'
    & $entry (Get-Text 'Glossary.Term.AadBroker')          (Get-Text 'Glossary.Text.AadBroker')          'AAD Broker|BrokerPlugin'
    & $entry (Get-Text 'Glossary.Term.Drs')                (Get-Text 'Glossary.Text.Drs')                'Device Registration Service'
    & $entry (Get-Text 'Glossary.Term.Adfs')               (Get-Text 'Glossary.Text.Adfs')               'AD ?FS'
    & $entry (Get-Text 'Glossary.Term.Mfa')                (Get-Text 'Glossary.Text.Mfa')                ''
    & $entry (Get-Text 'Glossary.Term.Autodiscover')       (Get-Text 'Glossary.Text.Autodiscover')       ''
    & $entry (Get-Text 'Glossary.Term.Scp')                (Get-Text 'Glossary.Text.Scp')                'SCPs'
    & $entry (Get-Text 'Glossary.Term.MapiHttp')           (Get-Text 'Glossary.Text.MapiHttp')           'MAPI'
    & $entry (Get-Text 'Glossary.Term.CacheMode')          (Get-Text 'Glossary.Text.CacheMode')          'Cache-Modus'
    & $entry (Get-Text 'Glossary.Term.Ost')                (Get-Text 'Glossary.Text.Ost')                'OST'
    & $entry (Get-Text 'Glossary.Term.Pst')                (Get-Text 'Glossary.Text.Pst')                'PST'
    & $entry (Get-Text 'Glossary.Term.ClickToRun')         (Get-Text 'Glossary.Text.ClickToRun')         ''

    # Encryption and certificates
    & $entry (Get-Text 'Glossary.Term.Tls')                (Get-Text 'Glossary.Text.Tls')                ''
    & $entry (Get-Text 'Glossary.Term.TlsHandshake')       (Get-Text 'Glossary.Text.TlsHandshake')       'Handshakes?'
    & $entry (Get-Text 'Glossary.Term.Schannel')           (Get-Text 'Glossary.Text.Schannel')           'SChannel'
    & $entry (Get-Text 'Glossary.Term.CertificateChain')   (Get-Text 'Glossary.Text.CertificateChain')   ''
    & $entry (Get-Text 'Glossary.Term.Crl')                (Get-Text 'Glossary.Text.Crl')                'CRL'
    & $entry (Get-Text 'Glossary.Term.Ctl')                (Get-Text 'Glossary.Text.Ctl')                'CTL'

    # The network
    & $entry (Get-Text 'Glossary.Term.Smb')                (Get-Text 'Glossary.Text.Smb')                ''
    & $entry (Get-Text 'Glossary.Term.Multichannel')       (Get-Text 'Glossary.Text.Multichannel')       'Multichannel'
    & $entry (Get-Text 'Glossary.Term.Oplocks')            (Get-Text 'Glossary.Text.Oplocks')            'Oplocks'
    & $entry (Get-Text 'Glossary.Term.WebDav')             (Get-Text 'Glossary.Text.WebDav')             'WebClient'
    & $entry (Get-Text 'Glossary.Term.OfflineFiles')       (Get-Text 'Glossary.Text.OfflineFiles')       ''
    & $entry (Get-Text 'Glossary.Term.FolderRedirection')  (Get-Text 'Glossary.Text.FolderRedirection')  ''
    & $entry (Get-Text 'Glossary.Term.Dhcp')               (Get-Text 'Glossary.Text.Dhcp')               ''
    & $entry (Get-Text 'Glossary.Term.Apipa')              (Get-Text 'Glossary.Text.Apipa')              '169\.254'
    & $entry (Get-Text 'Glossary.Term.DnsSuffix')          (Get-Text 'Glossary.Text.DnsSuffix')          'Suffix-Suchliste'
    & $entry (Get-Text 'Glossary.Term.SrvRecord')          (Get-Text 'Glossary.Text.SrvRecord')          ''
    & $entry (Get-Text 'Glossary.Term.Ncsi')               (Get-Text 'Glossary.Text.Ncsi')               ''
    & $entry (Get-Text 'Glossary.Term.WinHttpProxy')       (Get-Text 'Glossary.Text.WinHttpProxy')       ''
    & $entry (Get-Text 'Glossary.Term.Pac')                (Get-Text 'Glossary.Text.Pac')                ''
    & $entry (Get-Text 'Glossary.Term.InterruptModeration') (Get-Text 'Glossary.Text.InterruptModeration') 'Interrupt-Drosselung|Interrupt Moderation'

    # Windows itself
    & $entry (Get-Text 'Glossary.Term.ServiceControlManager') (Get-Text 'Glossary.Text.ServiceControlManager') ''
    & $entry (Get-Text 'Glossary.Term.Spooler')            (Get-Text 'Glossary.Text.Spooler')            'Spooler'
    & $entry (Get-Text 'Glossary.Term.FastStartup')        (Get-Text 'Glossary.Text.FastStartup')        ''
    & $entry (Get-Text 'Glossary.Term.ComponentStore')     (Get-Text 'Glossary.Text.ComponentStore')     'WinSxS'
    & $entry (Get-Text 'Glossary.Term.Odbc')               (Get-Text 'Glossary.Text.Odbc')               'ODBC|DSN'

    # Crashes and the memory
    & $entry (Get-Text 'Glossary.Term.Bluescreen')         (Get-Text 'Glossary.Text.Bluescreen')         'Bluescreens|Stop-Codes?|Minidumps?'
    & $entry (Get-Text 'Glossary.Term.LiveKernel')         (Get-Text 'Glossary.Text.LiveKernel')         'Live-Kernel|LiveKernel\w*'
    & $entry (Get-Text 'Glossary.Term.Whea')               (Get-Text 'Glossary.Text.Whea')               ''
    & $entry (Get-Text 'Glossary.Term.Win32k')             (Get-Text 'Glossary.Text.Win32k')             ''
    & $entry (Get-Text 'Glossary.Term.AccessViolation')    (Get-Text 'Glossary.Text.AccessViolation')    ''
    & $entry (Get-Text 'Glossary.Term.StackOverflow')      (Get-Text 'Glossary.Text.StackOverflow')      ''
    & $entry (Get-Text 'Glossary.Term.StackBufferOverrun') (Get-Text 'Glossary.Text.StackBufferOverrun') 'Stack Buffer Overrun|Fail-Fast'
    & $entry (Get-Text 'Glossary.Term.UserGdi')            (Get-Text 'Glossary.Text.UserGdi')            'USER-Objekte\w*|GDI-Objekte\w*'
    & $entry (Get-Text 'Glossary.Term.WorkingSet')         (Get-Text 'Glossary.Text.WorkingSet')         ''
    & $entry (Get-Text 'Glossary.Term.Commit')             (Get-Text 'Glossary.Text.Commit')             'Zugesichert\w*'
    & $entry (Get-Text 'Glossary.Term.PageFile')           (Get-Text 'Glossary.Text.PageFile')           ''
    & $entry (Get-Text 'Glossary.Term.Smart')              (Get-Text 'Glossary.Text.Smart')              'SMART'
    & $entry (Get-Text 'Glossary.Term.AcpiThermalZone')    (Get-Text 'Glossary.Text.AcpiThermalZone')    'ACPI Thermal Zones'
}

function Select-ReportGlossary {
    <#
    .SYNOPSIS
        The terms a text uses, in alphabetical order. Pure.
    .DESCRIPTION
        A term counts where it stands as a word of its own: "OST" in "Outlook-OST", and
        not in "HOST". Read in one pass, however long the text: a Report with its
        evidence is megabytes.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()][AllowEmptyString()][string]$Text, [AllowNull()][AllowEmptyCollection()]$Entry)

    $entries = @($Entry | Where-Object { $_ })
    if (-not $Text -or -not $entries.Count) { return }

    $alternatives = for ($i = 0; $i -lt $entries.Count; $i++) {
        $ways = @([regex]::Escape("$($entries[$i].Term)"))
        if ("$($entries[$i].Also)") { $ways += "$($entries[$i].Also)" }
        '(?<t{0}>{1})' -f $i, ($ways -join '|')
    }
    $pattern = [regex]('(?<![\w])(?:' + ($alternatives -join '|') + ')(?![\w])')

    $found = New-Object 'System.Collections.Generic.HashSet[int]'
    foreach ($match in $pattern.Matches($Text)) {
        for ($i = 0; $i -lt $entries.Count; $i++) {
            if ($match.Groups["t$i"].Success) { $null = $found.Add($i); break }
        }
        if ($found.Count -eq $entries.Count) { break }
    }
    $picked = @($found | ForEach-Object { $entries[$_] })
    # Ordinal, so that the order is the same on every machine whatever its language.
    [array]::Sort([object[]]$picked, [Comparison[object]] { param($a, $b) [StringComparer]::OrdinalIgnoreCase.Compare("$($a.Term)", "$($b.Term)") })
    $picked
}

function ConvertTo-ReportGlossaryText {
    <#
    .SYNOPSIS
        What a reader reads of a piece of the Report, without its markup. Pure.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()][AllowEmptyString()][string]$Html)

    if (-not $Html) { return '' }
    [System.Net.WebUtility]::HtmlDecode(($Html -replace '<[^>]*>', ' '))
}

function ConvertTo-ReportGlossaryHtml {
    <#
    .SYNOPSIS
        The terms as a list, each with what it is. Nothing where there is none. Pure.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()][AllowEmptyCollection()]$Entry)

    $entries = @($Entry | Where-Object { $_ })
    if (-not $entries.Count) { return '' }
    '<dl class="glossary">{0}</dl>' -f (($entries | ForEach-Object {
        '<div><dt>{0}</dt><dd>{1}</dd></div>' -f (ConvertTo-HtmlText "$($_.Term)"), (ConvertTo-HtmlText "$($_.Text)")
    }) -join '')
}

function ConvertTo-ReportGlossaryData {
    <#
    .SYNOPSIS
        The terms a Report uses, as data for its script: each with its name, what it is,
        and what it looks like in a text as a JavaScript pattern. Nothing where there is
        none. Pure.
    .DESCRIPTION
        So that a term is explained where it stands, and not only at the end. A pattern
        of the glossary is written for .NET and for JavaScript alike; only the name itself
        is escaped here, for JavaScript, whose escapes are fewer.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([AllowNull()][AllowEmptyCollection()]$Entry)

    $entries = @($Entry | Where-Object { $_ })
    if (-not $entries.Count) { return '' }
    $data = @($entries | ForEach-Object {
        $pattern = [regex]::Replace("$($_.Term)", '[\\^$.*+?()\[\]{}|/]', '\$0')
        if ("$($_.Also)") { $pattern = '{0}|{1}' -f $pattern, "$($_.Also)" }
        [ordered]@{ t = "$($_.Term)"; x = "$($_.Text)"; p = $pattern }
    })
    # Inside a script element nothing may close it.
    $json = (ConvertTo-Json -InputObject $data -Compress -Depth 3) -replace '<', '\u003c'
    '<script type="application/json" id="terms">{0}</script>' -f $json
}