Private/Kinds/UserSetting.ps1

# The UserSetting Kind: the settings of the User of the Run that cause tickets.
#
# The proxy of the User, the default printer, the autostart entries, the monitors with
# their scaling, and Offline Files: five things that are set per User, that a Technician
# is asked about by telephone, and that are no part of a User Profile. One table,
# "Einstellungen des Benutzers", and one Finding where two monitors are scaled
# differently. It was a part of the UserProfile Kind until #67.
#
# Nothing is judged twice. The proxy, the default printer and the autostart entries have
# their Findings, made by the Network, the Printing and the Startup Check; they are read
# off what those gathered, where they ran before this one, and the rows of the table
# point at their Findings. So this Check stands after those three in a Check Definition
# document, and after the Check of the network dependencies, whose reading says which
# shares Offline Files keeps. Out of that order nothing fails: the settings are then read
# here, and the rows say that the Check which judges them did not run.
#
# State only (ADR-0006). Of a printer the name of the default one is kept; of an
# autostart entry where Windows has it registered - which hive, which folder - and not
# what it starts; of Offline Files whether it is on and the shares it keeps, as
# \\server\share and nothing below.
#
# Whether the Run is in the Session of the person working at the machine the Run decides,
# once, and hands over as $Observed['RunUser']. This Kind does not work it out again.
# Where it is not, nothing here is the User's: nothing is read, and there is no table
# and no Finding. That the Run is in the wrong Session the Run says, once, with the
# settings among what was therefore not read (New-UserContextFinding).
#
# Whether Offline Files is on is read by the UserProfile Kind as well, for the Finding
# about where Desktop and Documents lie, by the function that reads it here:
# Get-UserSettingOfflineFiles. Neither Check reads it from the other, so that neither
# depends on which of the two stands first.
#
# Get-UserSettingData reads the machine and decides nothing. ConvertTo-UserSettingFinding
# and ConvertTo-UserSettingSection are pure.

# The Kinds this one reads from when they ran before it, and whose Findings it points at.
$script:UserSettingReadsFrom = @('Network', 'NetworkDependency', 'Printing', 'Startup')

# What Windows calls 100 %: 96 dots per inch. A scaling is never shown as the number of
# dots - 144 is said as 150 %.
$script:UserSettingDpiOfFullScale = 96

function Get-UserSettingOfflineFiles {
    <#
    .SYNOPSIS
        Whether Offline Files is switched on, and which shares it keeps a copy of.
        Decides nothing.
    .DESCRIPTION
        State is On, Off, Absent - a Windows without Offline Files - or Unread. Asked of
        WMI, which answers without admin rights (seen on Windows 11, switched off).
 
        The shares are those the Check of the network dependencies found, where it ran
        before this one; where it did not, they are read the way it reads them. SharesRead
        says whether the list is one: an empty list that could not be read does not say
        that no share is kept. Not verified on a machine with Offline Files switched on,
        like the reading it rests on.
    .PARAMETER NetworkDependency
        What the Check of the network dependencies gathered, or nothing.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$NetworkDependency)

    $state = 'Unread'
    $problems = @()
    $cache = Get-CimInstance -ClassName Win32_OfflineFilesCache -ErrorAction SilentlyContinue -ErrorVariable problems |
        Select-Object -First 1
    if ($cache) { $state = $(if ($cache.Enabled -and $cache.Active) { 'On' } else { 'Off' }) }
    elseif (-not @($problems).Count -or
        @($problems | Where-Object { "$($_.Exception.NativeErrorCode)" -match 'InvalidClass|InvalidNamespace' }).Count) { $state = 'Absent' }

    $shares = @()
    $read = $false
    if ($state -eq 'On') {
        if ($null -ne $NetworkDependency) {
            $shares = @((Get-DataCollection $NetworkDependency 'Dependencies') | ForEach-Object { $_ } |
                Where-Object { "$(Get-DataProperty $_ 'Source')" -eq 'offline-files' } |
                ForEach-Object { "$(Get-DataProperty $_ 'UncPath')" })
            $read = -not @((Get-DataCollection $NetworkDependency 'Unexamined') | ForEach-Object { $_ } |
                Where-Object { "$(Get-DataProperty $_ 'What')" -like 'Win32_OfflineFiles*' }).Count
        }
        else {
            $unexamined = [System.Collections.Generic.List[psobject]]::new()
            $shares = @(Get-NetworkDependencyOfflineFilesTrace -Unexamined $unexamined | ForEach-Object { "$(Get-DataProperty $_ 'Path')" })
            $read = -not $unexamined.Count
        }
    }

    [pscustomobject]@{
        State      = $state
        # \\server\share of each, and nothing below it.
        Shares     = @($shares | ForEach-Object { Split-NetworkDependencyUncPath -UncPath $_ } |
            ForEach-Object { "$($_.Share)" } | Where-Object { $_ } | Sort-Object -Unique)
        SharesRead = [bool]$read
    }
}

function Initialize-UserSettingDisplayNativeMethod {
    <#
    .SYNOPSIS
        Defines what asks Windows for the monitors of the Session and the scaling of
        each. Once per session.
    .DESCRIPTION
        A type of its own, because a type is defined once per session and cannot gain
        members; compiled on first use and not at import.
 
        What Windows answers when asked for the scaling of a monitor depends on who asks.
        PowerShell is a program that does not scale itself per monitor, and to such a
        program Windows says 96 dots per inch for every monitor, or the scaling of the
        main one for all of them (Microsoft, GetDpiForMonitor: the value depends on the
        DPI awareness of the caller). So the thread that asks declares itself aware of
        each monitor for the length of the question (SetThreadDpiAwarenessContext, per
        monitor aware, version 2; Windows 10 1703 and later, version 1 from 1607) and
        takes that back afterwards. Where Windows will not have it - an older Windows -
        the answer says so, and the scaling is then reported as not read.
 
        Seen on Windows 11 with one monitor at 100 %, on Windows PowerShell 5.1 and
        PowerShell 7: one monitor, 96, as the registry says it (AppliedDPI). Not seen on
        a machine with two monitors of different scaling. The registry is no way round
        it: PerMonitorSettings under HKCU\Control Panel\Desktop holds a monitor only once
        its scaling was changed by hand, and then as steps away from what Windows
        recommends and not as a scaling.
 
        Needs no admin rights. Nothing here throws.
    #>

    [CmdletBinding()]
    param()

    if ('Gutcheck.DisplayNative' -as [type]) { return }
    Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
 
namespace Gutcheck
{
    public sealed class DisplayMonitor
    {
        public int Dpi;
        public int Width;
        public int Height;
        public bool Primary;
        // Where it stands on the desktop, and what Windows calls it (\\.\DISPLAY1): to
        // number the monitors the same in every Run.
        public int Left;
        public int Top;
        public string Device;
        // Whether the thread that asked was aware of each monitor: without that, Dpi is
        // not the scaling of this monitor.
        public bool PerMonitor;
    }
 
    public static class DisplayNative
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct Rect { public int Left, Top, Right, Bottom; }
 
        // MONITORINFOEX: MONITORINFO and the name of the device behind it.
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct MonitorInfo
        {
            public int Size; public Rect Monitor; public Rect Work; public uint Flags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Device;
        }
 
        private delegate bool MonitorProc(IntPtr monitor, IntPtr dc, IntPtr rect, IntPtr data);
 
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumDisplayMonitors(IntPtr dc, IntPtr clip, MonitorProc proc, IntPtr data);
 
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetMonitorInfoW(IntPtr monitor, ref MonitorInfo info);
 
        [DllImport("shcore.dll")]
        private static extern int GetDpiForMonitor(IntPtr monitor, int type, out uint x, out uint y);
 
        [DllImport("user32.dll")]
        private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);
 
        public static DisplayMonitor[] Read()
        {
            List<DisplayMonitor> found = new List<DisplayMonitor>();
            IntPtr before = IntPtr.Zero;
            bool aware = false;
 
            // -4: per monitor aware, version 2. -3: version 1, for a Windows before 1703.
            try
            {
                before = SetThreadDpiAwarenessContext(new IntPtr(-4));
                if (before == IntPtr.Zero) { before = SetThreadDpiAwarenessContext(new IntPtr(-3)); }
                aware = before != IntPtr.Zero;
            }
            catch (Exception) { aware = false; }
 
            try
            {
                MonitorProc each = delegate(IntPtr monitor, IntPtr dc, IntPtr rect, IntPtr data)
                {
                    DisplayMonitor row = new DisplayMonitor();
                    row.PerMonitor = aware;
                    try
                    {
                        MonitorInfo info = new MonitorInfo();
                        info.Size = Marshal.SizeOf(typeof(MonitorInfo));
                        if (GetMonitorInfoW(monitor, ref info))
                        {
                            row.Width = info.Monitor.Right - info.Monitor.Left;
                            row.Height = info.Monitor.Bottom - info.Monitor.Top;
                            // 1: MONITORINFOF_PRIMARY.
                            row.Primary = (info.Flags & 1) != 0;
                            row.Left = info.Monitor.Left;
                            row.Top = info.Monitor.Top;
                            row.Device = info.Device;
                        }
                        uint x, y;
                        // 0: MDT_EFFECTIVE_DPI, the scaling the User chose.
                        if (GetDpiForMonitor(monitor, 0, out x, out y) == 0) { row.Dpi = (int)x; }
                    }
                    catch (Exception) { }
                    found.Add(row);
                    return true;
                };
                EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, each, IntPtr.Zero);
                GC.KeepAlive(each);
            }
            catch (Exception) { }
            finally
            {
                if (aware) { try { SetThreadDpiAwarenessContext(before); } catch (Exception) { } }
            }
 
            return found.ToArray();
        }
    }
}
'@

}

function Get-UserSettingMonitor {
    <#
    .SYNOPSIS
        The monitors of this Session: of each its size in pixels, whether it is the main
        one, and its scaling as Windows gives it. Decides nothing.
    .DESCRIPTION
        Dpi is nothing where Windows did not say it for this monitor alone: see
        Initialize-UserSettingDisplayNativeMethod. Nothing at all where Windows could
        not be asked.
    #>

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

    try {
        Initialize-UserSettingDisplayNativeMethod
        foreach ($monitor in @([Gutcheck.DisplayNative]::Read())) {
            [pscustomobject]@{
                Dpi     = $(if ($monitor.PerMonitor -and $monitor.Dpi -gt 0) { [int]$monitor.Dpi } else { $null })
                Width   = [int]$monitor.Width
                Height  = [int]$monitor.Height
                Primary = [bool]$monitor.Primary
                # Where it stands and what Windows calls it: what the monitors are
                # numbered by, see Get-UserSettingMonitorSet.
                Left    = [int]$monitor.Left
                Top     = [int]$monitor.Top
                Device  = "$($monitor.Device)" -replace '^\\\\\.\\', ''
            }
        }
    }
    catch { }
}

function Get-UserSettingReading {
    <#
    .SYNOPSIS
        The settings of the User that cause tickets: proxy, default printer, autostart
        entries, monitors and Offline Files. Decides nothing.
    .DESCRIPTION
        The proxy, the printers and the autostart entries are taken from what the
        Network, the Printing and the Startup Check gathered, where they ran before this
        one, and read here where they did not. Each says whether it was read: a setting
        that could not be read is not one that is not set.
 
        Of a printer only the name of the default one is kept, of an autostart entry only
        where Windows has it registered: which hive, which folder. Not what it starts.
    .PARAMETER Observed
        What the Checks before this one gathered, keyed by Kind.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()][hashtable]$Observed = @{})

    $seen = @{}
    foreach ($kind in $script:UserSettingReadsFrom) {
        if ($Observed -and $Observed.ContainsKey($kind) -and $null -ne $Observed[$kind]) { $seen[$kind] = $Observed[$kind] }
    }

    # The proxy of this account (WinINET), as Internet Options writes it.
    $proxy = $null
    if ($seen.ContainsKey('Network')) { $proxy = Get-DataProperty $seen['Network'] 'UserProxy' }
    else {
        try {
            $settings = Get-ItemProperty -Path $script:NetworkUserProxyKey -ErrorAction Stop
            $proxy = [pscustomobject]@{
                ProxyEnable   = Get-DataProperty $settings 'ProxyEnable'
                ProxyServer   = Get-DataProperty $settings 'ProxyServer'
                ProxyOverride = Get-DataProperty $settings 'ProxyOverride'
                AutoConfigURL = Get-DataProperty $settings 'AutoConfigURL'
            }
        }
        catch { }
    }

    $printerRead = $false
    $printer = ''
    if ($seen.ContainsKey('Printing')) {
        $printerRead = $true
        $printer = "$(Get-DataProperty ((Get-DataCollection $seen['Printing'] 'Printers') | ForEach-Object { $_ } |
            Where-Object { [bool](Get-DataProperty $_ 'Default') } | Select-Object -First 1) 'Name')"

    }
    else {
        try {
            $printer = "$((Get-CimInstance -ClassName Win32_Printer -Filter 'Default=TRUE' -ErrorAction Stop | Select-Object -First 1).Name)"
            $printerRead = $true
        }
        catch { }
    }

    $autostartRead = $false
    $entries = @()
    if ($seen.ContainsKey('Startup') -and [bool](Get-DataProperty $seen['Startup'] 'Available')) {
        $autostartRead = $true
        $entries = @((Get-DataCollection $seen['Startup'] 'Entries') | ForEach-Object { $_ })
    }
    elseif (-not $seen.ContainsKey('Startup')) {
        try {
            $entries = @(Get-CimInstance -ClassName Win32_StartupCommand -ErrorAction Stop)
            $autostartRead = $true
        }
        catch { }
    }

    $network = $null
    if ($seen.ContainsKey('NetworkDependency')) { $network = $seen['NetworkDependency'] }

    [pscustomobject]@{
        Proxy              = $proxy
        DefaultPrinter     = $printer
        DefaultPrinterRead = $printerRead
        Autostart          = @($entries | ForEach-Object { [pscustomobject]@{ Location = "$(Get-DataProperty $_ 'Location')" } })
        AutostartRead      = $autostartRead
        Monitors           = @(Get-UserSettingMonitor)
        OfflineFiles       = Get-UserSettingOfflineFiles -NetworkDependency $network
    }
}

function Get-UserSettingData {
    <#
    .SYNOPSIS
        The settings of the account of the Run that cause tickets, which of the Checks
        they are read from were performed, and what the Run said about whose they are.
        Decides nothing.
    .DESCRIPTION
        Where the Run said it is not in the Session of the person working at the machine,
        nothing is read: the proxy, the printer and the monitors of an administrator are
        not the User's, and the Check may not show them as the User's.
 
        A reading that throws leaves Settings as nothing, and that is not the same as no
        setting being set: the Judge says that they were not read.
    .PARAMETER Observed
        What the Run knows before this Check. 'RunUser' is the account the Run runs as,
        the one working at the machine, and whether they differ: see
        ConvertTo-RunUserObserved. Copied across as it came. 'Network', 'Printing',
        'Startup' and 'NetworkDependency' are what those Checks gathered, each absent
        where its Check was not performed.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([hashtable]$Parameters = @{}, [AllowNull()][hashtable]$Observed = @{})

    $runUser = $null
    if ($Observed -and $Observed.ContainsKey('RunUser')) { $runUser = $Observed['RunUser'] }

    $settings = $null
    $performed = @()
    if (-not [bool](Get-DataProperty $runUser 'Differs')) {
        try { $settings = Get-UserSettingReading -Observed $Observed } catch { $settings = $null }
        $performed = @($script:UserSettingReadsFrom | Where-Object { $Observed -and $Observed.ContainsKey($_) -and $null -ne $Observed[$_] })
    }

    [pscustomobject]@{
        PSTypeName = 'Gutcheck.Data.UserSetting'
        # Which hive of the registry is the User's own, for counting their autostart entries.
        OwnSid     = Get-UserProfileOwnSid
        RunUser    = $runUser
        # Nothing where the Run is not in the Session of the User, and where they could
        # not be read: see Get-UserSettingReading.
        Settings   = $settings
        # Which of the Checks this one reads from were performed: only the Finding of a
        # Check that ran is pointed at.
        Performed  = $performed
    }
}

function Get-UserSettingScale {
    <#
    .SYNOPSIS
        The scaling of a monitor in percent, from the dots per inch Windows gives it in.
        Nothing where Windows gave none. Pure.
    #>

    [CmdletBinding()]
    [OutputType([int])]
    param([AllowNull()]$Dpi)

    $number = ConvertTo-Number $Dpi
    if ($null -eq $number -or $number -le 0) { return $null }
    [int][math]::Round($number * 100 / $script:UserSettingDpiOfFullScale)
}

function Get-UserSettingMonitorSet {
    <#
    .SYNOPSIS
        The monitors as they are reported: numbered, the main one first, each with its
        scaling in percent. Pure.
    .DESCRIPTION
        Differs says that at least two of them are scaled differently. Only among those
        whose scaling was read: one that was not is not thereby different.
 
        Numbered the same in every Run: the main one first, then from left to right and
        from top to bottom as they stand on the desktop, then by what Windows calls the
        device, then by size. The main one alone is no order: a sort does not keep the
        order of equals on Windows PowerShell 5.1, and with three monitors the second
        and the third changed places between two Runs.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Settings)

    $whole = { param($Row, [string]$Name) $n = ConvertTo-Number (Get-DataProperty $Row $Name); if ($null -eq $n) { [double]0 } else { $n } }
    $number = 0
    $monitors = @((Get-DataCollection $Settings 'Monitors') | ForEach-Object { $_ } |
        Sort-Object @{ Expression = { [bool](Get-DataProperty $_ 'Primary') }; Descending = $true },
            @{ Expression = { & $whole $_ 'Left' }; Descending = $false }, @{ Expression = { & $whole $_ 'Top' }; Descending = $false },
            @{ Expression = { "$(Get-DataProperty $_ 'Device')".ToUpperInvariant() }; Descending = $false },
            @{ Expression = { & $whole $_ 'Width' }; Descending = $false }, @{ Expression = { & $whole $_ 'Height' }; Descending = $false },
            @{ Expression = { & $whole $_ 'Dpi' }; Descending = $false } | ForEach-Object {
            $number++
            [pscustomobject]@{
                Number  = $number
                Primary = [bool](Get-DataProperty $_ 'Primary')
                Scale   = Get-UserSettingScale -Dpi (Get-DataProperty $_ 'Dpi')
                Width   = ConvertTo-UserProfileCount (Get-DataProperty $_ 'Width')
                Height  = ConvertTo-UserProfileCount (Get-DataProperty $_ 'Height')
            }
        })

    $scales = @($monitors | Where-Object { $null -ne $_.Scale } | ForEach-Object { $_.Scale } | Sort-Object -Unique)
    [pscustomobject]@{
        Monitors = $monitors
        Scales   = $scales
        Differs  = [bool]($scales.Count -gt 1)
    }
}

function ConvertTo-UserSettingFinding {
    <#
    .SYNOPSIS
        Monitors of different scaling, as information. Nothing else of the settings is
        judged here. Pure.
    .DESCRIPTION
        The proxy, the default printer and the autostart entries have their Findings,
        made by the Checks they belong to; the table of the settings points at them.
 
        Different scaling is no fault and is never more than information: it is what a
        notebook beside a large monitor looks like. It is said because of what it
        explains. One monitor, or all of the same scaling, is a row of the table and no
        Finding.
 
        So where everything is as usual this Check makes no Finding, and its table is all
        it says. Where the settings could not be read at all there is no table either,
        and then one Finding says that they were not read: a Check must not be silent
        about having read nothing.
 
        No limit and no parameter: nothing here is a matter of more or less.
 
        Nothing where the Run is not in the Session of the User: the settings were not
        read, and they would not be the User's.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Data, [hashtable]$Parameters = @{})

    $check = Get-Text 'Check.UserSetting.Settings'
    if ($null -eq $Data) {
        return New-UnavailableFinding -Category User -Check $check -Hint (Get-Text 'Hint.UserSetting.Repeat')
    }

    # Decided by the Run, and only read here.
    if ([bool](Get-DataProperty (Get-DataProperty $Data 'RunUser') 'Differs')) { return }

    $settings = Get-DataProperty $Data 'Settings'
    if ($null -eq $settings) {
        return New-UnavailableFinding -Category User -Check $check -Hint (Get-Text 'Hint.UserSetting.Repeat')
    }

    $set = Get-UserSettingMonitorSet -Settings $settings
    if (-not $set.Differs) { return }

    $scales = @($set.Monitors | Where-Object { $null -ne $_.Scale } | ForEach-Object { (Get-Text 'Value.UserSetting.Percent') -f $_.Scale })
    New-Finding -Category User -Check (Get-Text 'Check.UserSetting.Scaling') -Severity INFO `
        -Value ((Get-Text 'Value.UserSetting.Scaling') -f $set.Monitors.Count, ($scales -join ', ')) `
        -Meaning (Get-Text 'Meaning.UserSetting.Scaling') -Hint (Get-Text 'Hint.UserSetting.Scaling') `
        -Reference (New-Reference -Section 'Title.UserSetting')
}

function Get-UserSettingJudgedText {
    <#
    .SYNOPSIS
        What the table says beside a setting that another Check judges: which Finding,
        or that the Check did not run. Pure.
    .DESCRIPTION
        The Finding is named by the title its own Check gives it, read where that Check
        reads it, so that the two cannot come apart; and by the Category it stands under.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([bool]$Performed, [string]$Check, [string]$Category)

    if (-not $Performed) { return Get-Text 'Value.UserSetting.NotJudged' }
    (Get-Text 'Value.UserSetting.SeeFinding') -f $Check, (Get-ReportCategoryTitle -Category $Category)
}

function ConvertTo-UserSettingSection {
    <#
    .SYNOPSIS
        The settings of the User that cause tickets, as one table: proxy, default
        printer, autostart entries, monitors with their scaling, Offline Files. Pure.
    .DESCRIPTION
        Nothing where the Run is not in the Session of the User, and nothing where the
        settings were not gathered.
 
        A row is a setting, what it is, and what to read with it. The proxy, the default
        printer and the autostart entries are judged by the Checks they belong to: their
        rows name that Finding and judge nothing. A setting that could not be read is
        said to be unread; it is not shown as not set.
 
        Of the autostart entries the number is the User's own: those registered in the
        hive of the account, and those in its startup folder. The whole list is the
        Startup Check's.
    #>

    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Data)

    if ($null -eq $Data) { return }
    if ([bool](Get-DataProperty (Get-DataProperty $Data 'RunUser') 'Differs')) { return }
    $settings = Get-DataProperty $Data 'Settings'
    if ($null -eq $settings) { return }

    $performed = @((Get-DataCollection $Data 'Performed') | ForEach-Object { "$_" })
    $unread    = Get-Text 'Value.Cell.Unreadable'

    $nameColumn  = Get-Text 'Column.UserSetting.Name'
    $valueColumn = Get-Text 'Column.UserSetting.Value'
    $noteColumn  = Get-Text 'Column.Note'
    $rows = [System.Collections.Generic.List[psobject]]::new()
    $add = {
        param([string]$Name, [string]$Value, [string]$Note)
        $row = [ordered]@{}
        $row[$nameColumn]  = $Name
        $row[$valueColumn] = $Value
        $row[$noteColumn]  = $Note
        $rows.Add([pscustomobject]$row)
    }

    # --- Proxy ---
    $proxy = Get-DataProperty $settings 'Proxy'
    & $add (Get-Text 'Value.UserSetting.Proxy') $(if ($null -eq $proxy) { $unread } else { Get-UserProxyDescription $proxy }) `
        (Get-UserSettingJudgedText -Performed ('Network' -in $performed) -Check (Get-Text 'Check.Network.Proxy') -Category 'Network')

    # --- Default printer ---
    $printer = "$(Get-DataProperty $settings 'DefaultPrinter')".Trim()
    $printerValue = $(
        if (-not [bool](Get-DataProperty $settings 'DefaultPrinterRead')) { $unread }
        elseif ($printer) { $printer }
        else { Get-Text 'Value.UserSetting.NoDefaultPrinter' })
    & $add (Get-Text 'Value.UserSetting.DefaultPrinter') $printerValue `
        (Get-UserSettingJudgedText -Performed ('Printing' -in $performed) -Check (Get-Text 'Check.Printing.DefaultPrinter') -Category 'System')

    # --- Autostart entries of this account ---
    $autostartValue = $unread
    if ([bool](Get-DataProperty $settings 'AutostartRead')) {
        $sid = "$(Get-DataProperty $Data 'OwnSid')"
        $entries = @((Get-DataCollection $settings 'Autostart') | ForEach-Object { $_ })
        # As Windows names where an entry is registered: the hive of an account by its
        # number, and "Startup" for the startup folder of the account that asks.
        $mine = @($entries | Where-Object {
            $location = "$(Get-DataProperty $_ 'Location')"
            $location -eq 'Startup' -or ($sid -and $location.StartsWith(('HKU\{0}\' -f $sid), [StringComparison]::OrdinalIgnoreCase))
        })
        $autostartValue = (Get-Text 'Value.UserSetting.AutostartCount') -f $mine.Count, $entries.Count
    }
    & $add (Get-Text 'Value.UserSetting.Autostart') $autostartValue `
        (Get-UserSettingJudgedText -Performed ('Startup' -in $performed) -Check (Get-Text 'Check.Startup.StartupEntries') -Category 'Startup')

    # --- Monitors and their scaling ---
    $set = Get-UserSettingMonitorSet -Settings $settings
    $displayValue = $unread
    if ($set.Monitors.Count) {
        $displayValue = @(
            (Get-Text 'Value.UserSetting.Monitors') -f $set.Monitors.Count
            foreach ($monitor in $set.Monitors) {
                $scale = $(if ($null -eq $monitor.Scale) { Get-Text 'Value.UserSetting.ScaleUnread' }
                           else { (Get-Text 'Value.UserSetting.Percent') -f $monitor.Scale })
                if ($monitor.Primary) { (Get-Text 'Value.UserSetting.MonitorPrimary') -f $monitor.Number, $scale, $monitor.Width, $monitor.Height }
                else                  { (Get-Text 'Value.UserSetting.Monitor') -f $monitor.Number, $scale, $monitor.Width, $monitor.Height }
            }) -join ' | '
    }
    $displayNote = ''
    if ($set.Differs) { $displayNote = (Get-Text 'Value.UserSetting.ScalingDiffers') -f (Get-Text 'Check.UserSetting.Scaling') }
    & $add (Get-Text 'Value.UserSetting.Display') $displayValue $displayNote

    # --- Offline Files ---
    $offline = Get-DataProperty $settings 'OfflineFiles'
    $offlineNote = ''
    $offlineValue = $unread
    switch ("$(Get-DataProperty $offline 'State')") {
        'Off'    { $offlineValue = Get-Text 'Value.UserSetting.OfflineOff' }
        'Absent' { $offlineValue = Get-Text 'Value.UserSetting.OfflineAbsent' }
        'On' {
            $offlineValue = Get-Text 'Value.UserSetting.OfflineOn'
            $shares = @((Get-DataCollection $offline 'Shares') | ForEach-Object { "$_" })
            if ($shares.Count)                                         { $offlineNote = (Get-Text 'Value.UserSetting.OfflineShares') -f ($shares -join ', ') }
            elseif ([bool](Get-DataProperty $offline 'SharesRead'))    { $offlineNote = Get-Text 'Value.UserSetting.OfflineNoShares' }
            else                                                       { $offlineNote = Get-Text 'Value.UserSetting.OfflineSharesUnread' }
        }
    }
    & $add (Get-Text 'Value.UserSetting.OfflineFiles') $offlineValue $offlineNote

    New-Section -Title (Get-Text 'Title.UserSetting') -Row @($rows)
}