Private/BrowserProfile.ps1
|
# The browser profiles of the User of the Run: one list, for every Kind that reads a browser. # # The BrowserExtension Kind found the profiles of Edge, Chrome and Firefox itself, and the # Browser Kind needs the same ones. Two Kinds that each look for profiles in their own way # end up disagreeing about which there are - a profile with the extension in it that the # Browser Check never mentions - so both ask here, and nowhere else. # # Only the folders of the account the Run runs as are read: its own LocalApplicationData # and ApplicationData. Of another User of the machine nothing is listed (ADR-0006). # # The listing is a Gatherer helper: it lists what is there and decides nothing. Beside it # stand what every Kind that reads a browser reads the same way - an extension's entry and whether it is on, in # Chromium and in Firefox, and where a browser's program is - so that no two of them call # one extension on and off, or one browser installed and not. function Get-BrowserProfileRoot { <# .SYNOPSIS Where each browser keeps the profiles of the User of the Run, whether or not the folder is there. .DESCRIPTION Chrome and Edge are Chromium: one "User Data" folder holds every profile, each a folder of its own. Firefox keeps its profiles in a "Profiles" folder of its own. In this order, which is the order a Report names them in. #> [CmdletBinding()] [OutputType([psobject])] param() $local = [Environment]::GetFolderPath('LocalApplicationData') $roaming = [Environment]::GetFolderPath('ApplicationData') [pscustomobject]@{ Browser = 'Chrome'; Engine = 'Chromium'; Root = (Join-Path $local 'Google\Chrome\User Data') } [pscustomobject]@{ Browser = 'Edge'; Engine = 'Chromium'; Root = (Join-Path $local 'Microsoft\Edge\User Data') } [pscustomobject]@{ Browser = 'Firefox'; Engine = 'Firefox'; Root = (Join-Path $roaming 'Mozilla\Firefox\Profiles') } } function Get-BrowserProfile { <# .SYNOPSIS Every profile of the User of the Run: the browser it belongs to, the name the User knows it by, and the folder it lives in. .DESCRIPTION A Chromium browser keeps more than profiles in its "User Data" folder: caches, components, "System Profile" and "Guest Profile", which no User works in. A profile is "Default" or "Profile <n>". A Firefox profile is every folder of "Profiles", named "abcd1234.name"; the name is the part after the dot. .PARAMETER Browser Only the profiles of this browser: Chrome, Edge or Firefox. All three where none is named. .PARAMETER Root Where to look instead of the User's own folder. Given with a Browser. #> [CmdletBinding()] [OutputType([psobject])] param( [AllowEmptyString()][string]$Browser = '', [AllowEmptyString()][string]$Root = '' ) foreach ($place in @(Get-BrowserProfileRoot | Where-Object { -not $Browser -or $_.Browser -eq $Browser })) { $folder = $place.Root if ($Root) { $folder = $Root } if (-not (Test-Path -LiteralPath $folder -PathType Container)) { continue } $found = @(Get-ChildItem -LiteralPath $folder -Directory -ErrorAction SilentlyContinue) if ($place.Engine -eq 'Chromium') { $found = @($found | Where-Object { $_.Name -match '^(Default|Profile \d+)$' }) } foreach ($one in $found) { $name = $one.Name if ($place.Engine -eq 'Firefox') { $name = ($one.Name -split '\.', 2)[-1] } [pscustomobject]@{ Browser = $place.Browser; Engine = $place.Engine; Profile = $name; Folder = $one.FullName } } } } function Test-BrowserExtensionOn { <# .SYNOPSIS Whether a Chromium extension's entry in the preferences says it is switched on. Pure. .DESCRIPTION Older browsers write "state" (1 is on); newer ones leave it out and write the reasons it is off, as a number or a list - none means on. #> [CmdletBinding()] [OutputType([bool])] param([AllowNull()]$Entry) $reasons = @(@(Get-DataProperty $Entry 'disable_reasons') | Where-Object { $null -ne $_ -and "$_" -ne '0' }) if ($reasons.Count) { return $false } $state = Get-DataProperty $Entry 'state' if ($null -ne $state -and "$state" -eq '0') { return $false } $true } function Get-BrowserPreferenceSetting { # The extension entries of a Chromium preferences document, by id, each as a table of # its fields; an empty table where the document names none. Pure. [CmdletBinding()] [OutputType([hashtable])] param([AllowNull()]$Document) $found = @{} if ($null -eq $Document -or $Document -is [string]) { return $found } $settings = Get-DataProperty (Get-DataProperty $Document 'extensions') 'settings' if ($null -eq $settings -or $settings -is [string] -or $settings -is [ValueType]) { return $found } foreach ($one in $settings.PSObject.Properties) { $fields = @{} if ($one.Value -and $one.Value -isnot [string] -and $one.Value -isnot [ValueType]) { foreach ($field in $one.Value.PSObject.Properties) { $fields[$field.Name] = $field.Value } } $found[$one.Name] = $fields } $found } function Test-BrowserFirefoxExtensionOn { # Whether a Firefox add-on, as extensions.json lists it, is switched on: active, and # not switched off by the User. Pure. [CmdletBinding()] [OutputType([bool])] param([AllowNull()]$Addon) [bool](Get-DataProperty $Addon 'active') -and -not [bool](Get-DataProperty $Addon 'userDisabled') } # The program of each browser, as Windows registers it and as the event log names it in a # crash. $script:BrowserProgram = @{ Chrome = 'chrome.exe'; Edge = 'msedge.exe'; Firefox = 'firefox.exe' } function Get-BrowserExecutable { <# .SYNOPSIS Where a browser's program is, as Windows registers it for the machine or for this User (App Paths); '' where it registers none. .DESCRIPTION Every installer of the three registers its program there, so that "Run" finds it. The path is as registered: whether the file is still there is the caller's to ask. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)][ValidateSet('Chrome', 'Edge', 'Firefox')][string]$Browser) foreach ($hive in 'HKLM:', 'HKCU:') { $key = '{0}\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{1}' -f $hive, $script:BrowserProgram[$Browser] $path = "$((Get-ItemProperty -LiteralPath $key -ErrorAction SilentlyContinue).'(default)')".Trim('"', ' ') if ($path) { return $path } } '' } |