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) } |