Last active
March 9, 2026 14:26
-
-
Save minanagehsalalma/351e506118b26ccc886292ab22ab63cf to your computer and use it in GitHub Desktop.
Export Chrome extensions inventory (all profiles/channels and even source code) to CSV + JSON — PowerShell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Exports a full inventory of installed Chrome extensions across all profiles to CSV and JSON, | |
| and physically copies the extension source files and user data. | |
| .DESCRIPTION | |
| Scans every Chrome profile on the current machine and collects extension metadata from | |
| manifest.json and the profile's Preferences file: name, version, enabled state, install | |
| time, permissions, manifest version (MV2/MV3), and more. | |
| Supports multiple Chrome channels: Stable, Beta, Dev, and Canary. | |
| Correctly identifies Unpacked extensions loaded from custom disk locations. | |
| Backs up the actual source code and stored User Data (Local Settings, Sync Settings, | |
| and IndexedDB) for each selected extension to the output directory. | |
| .PARAMETER OutputDir | |
| Directory where the CSV and JSON files are written. Defaults to the current directory. | |
| .PARAMETER ProfileFilter | |
| Limit the scan to a specific profile folder name, e.g. "Default" or "Profile 3". | |
| Omit to scan all profiles. | |
| .PARAMETER IncludePermissions | |
| When specified, captures the extension's declared permissions as a semicolon-delimited string. | |
| .PARAMETER Interactive | |
| Opens a terminal-based menu allowing you to selectively choose which extensions to export | |
| and let you specify the save destination. | |
| .PARAMETER Channel | |
| One or more Chrome channels to scan: Stable, Beta, Dev, Canary. Defaults to Stable only. | |
| .EXAMPLE | |
| .\Export-ChromeExtensions.ps1 -Interactive | |
| .EXAMPLE | |
| .\Export-ChromeExtensions.ps1 -OutputDir "C:\Reports" -IncludePermissions -Channel Stable, Beta | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [string] $OutputDir = (Get-Location).Path, | |
| [string] $ProfileFilter = "", | |
| [switch] $IncludePermissions, | |
| [switch] $Interactive, | |
| [ValidateSet("Stable","Beta","Dev","Canary")] | |
| [string[]] $Channel = @("Stable") | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = "Stop" | |
| #region -- Helpers ------------------------------------------------------------ | |
| $channelPaths = @{ | |
| Stable = "Google\Chrome\User Data" | |
| Beta = "Google\Chrome Beta\User Data" | |
| Dev = "Google\Chrome Dev\User Data" | |
| Canary = "Google\Chrome SxS\User Data" | |
| } | |
| $locationLabels = @{ | |
| 1 = "Internal" | |
| 2 = "External (registry)" | |
| 3 = "External (preferences)" | |
| 4 = "Unpacked / Command line" | |
| 5 = "External (crx)" | |
| 6 = "External (crx update)" | |
| 7 = "Internal (component)" | |
| 8 = "External (policy)" | |
| 9 = "Internal (theme)" | |
| 10 = "External (CWS update)" | |
| } | |
| function ConvertFrom-WindowsFileTime { | |
| param([string]$FileTimeString) | |
| if ([string]::IsNullOrWhiteSpace($FileTimeString)) { return $null } | |
| try { | |
| $ft = [Int64]::Parse($FileTimeString) | |
| if ($ft -le 0) { return $null } | |
| return [DateTime]::FromFileTimeUtc($ft) | |
| } catch { | |
| return $null | |
| } | |
| } | |
| function Resolve-ExtensionName { | |
| param( | |
| [hashtable] $Manifest, | |
| [string] $ManifestPath | |
| ) | |
| $name = $Manifest["name"] | |
| if ($name -isnot [string]) { return $null } | |
| if ($name -notmatch "^__MSG_(.+)__$") { return $name } | |
| $key = $Matches[1] | |
| $locales = Join-Path (Split-Path $ManifestPath -Parent) "_locales" | |
| if (-not (Test-Path $locales)) { return $name } | |
| $tryLocales = @("en","en_US","en_GB") + | |
| (Get-ChildItem $locales -Directory -ErrorAction SilentlyContinue | | |
| Select-Object -ExpandProperty Name) | |
| foreach ($loc in ($tryLocales | Select-Object -Unique)) { | |
| $msgPath = Join-Path $locales "$loc\messages.json" | |
| if (-not (Test-Path $msgPath)) { continue } | |
| try { | |
| $msgs = Get-Content $msgPath -Raw -ErrorAction Stop | ConvertFrom-Json | |
| $val = $msgs.$key.message | |
| if ($val) { return [string]$val } | |
| } catch { continue } | |
| } | |
| return $name | |
| } | |
| function Get-ProfileNameMap { | |
| param([string]$UserDataPath) | |
| $map = @{} | |
| $path = Join-Path $UserDataPath "Local State" | |
| if (-not (Test-Path $path)) { return $map } | |
| try { | |
| $ls = Get-Content $path -Raw -ErrorAction Stop | ConvertFrom-Json | |
| $cache = $ls.profile.info_cache | |
| if ($cache) { | |
| foreach ($p in $cache.PSObject.Properties) { | |
| if ($p.Value.name) { $map[$p.Name] = $p.Value.name } | |
| } | |
| } | |
| } catch {} | |
| return $map | |
| } | |
| function Read-LockedJsonFile { | |
| param([string]$Path) | |
| $tmp = $null | |
| try { | |
| $tmp = [System.IO.Path]::GetTempFileName() | |
| [System.IO.File]::Copy($Path, $tmp, $true) # VSS-style raw copy beats Get-Content on locked files | |
| $json = Get-Content $tmp -Raw -Encoding UTF8 -ErrorAction Stop | |
| return ($json | ConvertFrom-Json -ErrorAction Stop) | |
| } catch { | |
| # Fallback: direct read (works when Chrome is closed) | |
| try { | |
| return (Get-Content $Path -Raw -Encoding UTF8 -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) | |
| } catch { | |
| Write-Warning " Could not parse '$Path': $($_.Exception.Message)" | |
| return $null | |
| } | |
| } finally { | |
| if ($tmp -and (Test-Path $tmp)) { Remove-Item $tmp -Force -ErrorAction SilentlyContinue } | |
| } | |
| } | |
| function Resolve-EnabledState { | |
| param($SettingsEntry) | |
| # Primary: explicit state field | |
| if ($SettingsEntry.PSObject.Properties["state"] -and ($null -ne $SettingsEntry.state)) { | |
| $stateVal = $null | |
| if ([int]::TryParse([string]$SettingsEntry.state, [ref]$stateVal)) { | |
| return ($stateVal -eq 1) | |
| } | |
| } | |
| # Fallback 1: disable_reasons bitmask | |
| if ($SettingsEntry.PSObject.Properties["disable_reasons"] -and ($null -ne $SettingsEntry.disable_reasons)) { | |
| $reasons = $null | |
| if ([int]::TryParse([string]$SettingsEntry.disable_reasons, [ref]$reasons)) { | |
| return ($reasons -eq 0) | |
| } | |
| } | |
| # Fallback 2: blacklisted flag | |
| if ($SettingsEntry.PSObject.Properties["blacklisted"] -and ($SettingsEntry.blacklisted -eq $true)) { | |
| return $false | |
| } | |
| return $true | |
| } | |
| #endregion | |
| #region -- Main scan ---------------------------------------------------------- | |
| $results = [System.Collections.Generic.List[pscustomobject]]::new() | |
| foreach ($ch in $Channel) { | |
| $userDataPath = Join-Path $env:LOCALAPPDATA $channelPaths[$ch] | |
| if (-not (Test-Path $userDataPath)) { | |
| Write-Warning "[$ch] Chrome User Data not found -- skipping: $userDataPath" | |
| continue | |
| } | |
| Write-Host "[$ch] Scanning: $userDataPath" -ForegroundColor Cyan | |
| $profileNameMap = Get-ProfileNameMap -UserDataPath $userDataPath | |
| $profileDirs = Get-ChildItem $userDataPath -Directory -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Name -eq "Default" -or $_.Name -like "Profile *" } | | |
| Where-Object { -not $ProfileFilter -or $_.Name -eq $ProfileFilter } | | |
| Select-Object -ExpandProperty Name | |
| if (-not $profileDirs) { | |
| Write-Warning "[$ch] No matching profiles found." | |
| continue | |
| } | |
| foreach ($profDir in $profileDirs) { | |
| $profPath = Join-Path $userDataPath $profDir | |
| $extPath = Join-Path $profPath "Extensions" | |
| $settings = $null | |
| function Get-ExtensionSettings { | |
| param([string]$FilePath, [string]$Label) | |
| if (-not (Test-Path $FilePath)) { return $null } | |
| $parsed = Read-LockedJsonFile -Path $FilePath | |
| if (-not $parsed) { return $null } | |
| if ($parsed.PSObject.Properties["extensions"] -and | |
| $parsed.extensions.PSObject.Properties["settings"]) { | |
| Write-Verbose " [$Label] extensions.settings loaded from: $FilePath" | |
| return $parsed.extensions.settings | |
| } | |
| return $null | |
| } | |
| $securePrefsPath = Join-Path $profPath "Secure Preferences" | |
| $prefsPath = Join-Path $profPath "Preferences" | |
| $settings = Get-ExtensionSettings -FilePath $securePrefsPath -Label "$ch/$profDir (Secure Preferences)" | |
| if (-not $settings) { | |
| $settings = Get-ExtensionSettings -FilePath $prefsPath -Label "$ch/$profDir (Preferences)" | |
| } | |
| if (-not $settings) { | |
| Write-Warning " [$ch/$profDir] Could not load extensions.settings from Secure Preferences or Preferences." | |
| } | |
| $profileDisplay = if ($profileNameMap.ContainsKey($profDir)) { $profileNameMap[$profDir] } else { $profDir } | |
| # Merge Extension IDs found in Preferences and physical Extension Directory | |
| $extIds = @() | |
| if ($settings) { | |
| $extIds += $settings.PSObject.Properties.Name | |
| } | |
| if (Test-Path $extPath) { | |
| $extIds += Get-ChildItem $extPath -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name | |
| } | |
| $extIds = $extIds | Select-Object -Unique | Where-Object { $_ -ne $null -and $_ -ne "" } | |
| foreach ($extId in $extIds) { | |
| # --- Extract Data from Preferences --- | |
| $enabled = $null | |
| $installSrc = $null | |
| $locationCode = $null | |
| $locationLbl = $null | |
| $fromWebstore = $null | |
| $installTime = $null | |
| $unpackedPath = $null | |
| if ($settings -and $settings.PSObject.Properties[$extId]) { | |
| $s = $settings.PSObject.Properties[$extId].Value | |
| $enabled = Resolve-EnabledState -SettingsEntry $s | |
| if ($s.PSObject.Properties["location"] -and $null -ne $s.location) { | |
| $locationCode = [int]$s.location | |
| $locationLbl = if ($locationLabels.ContainsKey($locationCode)) { | |
| $locationLabels[$locationCode] | |
| } else { | |
| "Unknown ($locationCode)" | |
| } | |
| } | |
| if ($s.PSObject.Properties["from_webstore"] -and $null -ne $s.from_webstore) { | |
| $fromWebstore = [bool]$s.from_webstore | |
| } | |
| if ($s.PSObject.Properties["install_time"] -and $null -ne $s.install_time) { | |
| $installTime = ConvertFrom-WindowsFileTime -FileTimeString ([string]$s.install_time) | |
| } | |
| if ($s.PSObject.Properties["install_source"] -and $null -ne $s.install_source) { | |
| $installSrc = [string]$s.install_source | |
| } | |
| # Unpacked extensions have location Code 4 and an explicit absolute 'path' attribute | |
| if ($locationCode -eq 4 -and $s.PSObject.Properties["path"] -and $null -ne $s.path) { | |
| $unpackedPath = [string]$s.path | |
| if (-not [System.IO.Path]::IsPathRooted($unpackedPath)) { | |
| $unpackedPath = Join-Path $profPath $unpackedPath | |
| } | |
| } | |
| } | |
| # --- Locate Manifest.json --- | |
| $manifestFile = $null | |
| if ($unpackedPath -and (Test-Path (Join-Path $unpackedPath "manifest.json"))) { | |
| # Load manifest from the local disk path where the unpacked extension lives | |
| $manifestFile = Join-Path $unpackedPath "manifest.json" | |
| } elseif (Test-Path $extPath) { | |
| # Load manifest from standard installed extensions directory | |
| $idPath = Join-Path $extPath $extId | |
| $verDirs = Get-ChildItem $idPath -Directory -ErrorAction SilentlyContinue | | |
| Where-Object { Test-Path (Join-Path $_.FullName "manifest.json") } | |
| if ($verDirs) { | |
| $best = $verDirs | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | |
| $manifestFile = Join-Path $best.FullName "manifest.json" | |
| } | |
| } | |
| if (-not $manifestFile -or -not (Test-Path $manifestFile)) { | |
| continue | |
| } | |
| # --- Parse Manifest Data --- | |
| $manifest = $null | |
| $manifestHash = $null | |
| try { | |
| $manifest = Get-Content $manifestFile -Raw -ErrorAction Stop | ConvertFrom-Json | |
| $manifestHash = (Get-FileHash $manifestFile -Algorithm SHA256 -ErrorAction Stop).Hash | |
| } catch { continue } | |
| $manifestHt = @{} | |
| try { | |
| $manifest.PSObject.Properties | ForEach-Object { $manifestHt[$_.Name] = $_.Value } | |
| } catch {} | |
| $name = Resolve-ExtensionName -Manifest $manifestHt -ManifestPath $manifestFile | |
| $ver = $manifest.version | |
| $desc = if ($manifest.PSObject.Properties["description"]) { [string]$manifest.description } else { $null } | |
| $mv = if ($manifest.PSObject.Properties["manifest_version"]) { [int]$manifest.manifest_version } else { $null } | |
| $homepageUrl = if ($manifest.PSObject.Properties["homepage_url"]) { [string]$manifest.homepage_url } else { $null } | |
| $permissions = $null | |
| if ($IncludePermissions -and $manifest.PSObject.Properties["permissions"]) { | |
| try { $permissions = ($manifest.permissions | ForEach-Object { [string]$_ }) -join "; " } catch {} | |
| } | |
| # Calculate user-friendly Install Type | |
| $isUnpacked = ($locationCode -eq 4) | |
| $installType = "Unknown" | |
| if ($isUnpacked) { | |
| $installType = "Unpacked (Local)" | |
| } elseif ($fromWebstore) { | |
| $installType = "Official Web Store" | |
| } else { | |
| $installType = "Third-Party Sideloaded" | |
| } | |
| $row = [pscustomobject]@{ | |
| Channel = $ch | |
| Profile = $profileDisplay | |
| ProfileDir = $profDir | |
| ExtensionId = $extId | |
| Name = $name | |
| Version = $ver | |
| InstallType = $installType | |
| IsUnpacked = $isUnpacked | |
| ManifestVersion = $mv | |
| Description = $desc | |
| HomepageUrl = $homepageUrl | |
| Enabled = $enabled | |
| FromWebStore = $fromWebstore | |
| InstallSource = $installSrc | |
| LocationCode = $locationCode | |
| LocationLabel = $locationLbl | |
| InstallTimeUTC = $installTime | |
| ManifestPath = $manifestFile | |
| ManifestSHA256 = $manifestHash | |
| } | |
| if ($IncludePermissions) { | |
| $row | Add-Member -NotePropertyName Permissions -NotePropertyValue $permissions | |
| } | |
| $results.Add($row) | |
| } | |
| } | |
| } | |
| #endregion | |
| #region -- Output & File Copy ------------------------------------------------------------- | |
| if ($results.Count -eq 0) { | |
| Write-Warning "No extensions found. Check that Chrome is installed and profiles exist." | |
| return | |
| } | |
| $sorted = $results | Sort-Object Channel, Profile, Name | |
| if ($Interactive) { | |
| Write-Host "`n--- Interactive Extension Selection ---" -ForegroundColor Cyan | |
| Write-Host "Found $($sorted.Count) extensions. Please select which ones to export:" | |
| # 1. Select Extensions via Terminal Menu | |
| for ($i = 0; $i -lt $sorted.Count; $i++) { | |
| $item = $sorted[$i] | |
| Write-Host (" [{0,2}] {1} - {2}" -f ($i+1), $item.Profile, $item.Name) | |
| } | |
| Write-Host "" | |
| $choice = Read-Host "Enter numbers separated by commas, or press Enter to export ALL" | |
| if (-not [string]::IsNullOrWhiteSpace($choice) -and $choice.Trim().ToLower() -ne "all") { | |
| $indexes = $choice -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ } | |
| $selectedResults = [System.Collections.Generic.List[pscustomobject]]::new() | |
| foreach ($idx in $indexes) { | |
| if ($idx -ge 1 -and $idx -le $sorted.Count) { | |
| $selectedResults.Add($sorted[$idx - 1]) | |
| } | |
| } | |
| if ($selectedResults.Count -eq 0) { | |
| Write-Warning "No valid selections made. Canceling export." | |
| return | |
| } | |
| # Override the sorted list with the selected items | |
| $sorted = $selectedResults | |
| $results = $selectedResults | |
| } | |
| # 2. Select Output Directory via Terminal | |
| $defaultDir = if ($OutputDir) { $OutputDir } else { (Get-Location).Path } | |
| Write-Host "" | |
| $dirChoice = Read-Host "Enter output directory path (Press Enter for default: $defaultDir)" | |
| # Fix scope persistence by using a dedicated run variable | |
| $ActiveOutputDir = $defaultDir | |
| if (-not [string]::IsNullOrWhiteSpace($dirChoice)) { | |
| $ActiveOutputDir = $dirChoice | |
| } | |
| } else { | |
| $ActiveOutputDir = $OutputDir | |
| } | |
| if (-not (Test-Path -LiteralPath $ActiveOutputDir)) { | |
| New-Item -ItemType Directory -Path $ActiveOutputDir -Force | Out-Null | |
| } | |
| $ts = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $exportFolder = Join-Path $ActiveOutputDir "ChromeExtensionsExport_$ts" | |
| New-Item -ItemType Directory -Path $exportFolder -Force | Out-Null | |
| $csvPath = Join-Path $exportFolder "chrome_extensions_$ts.csv" | |
| $jsonPath = Join-Path $exportFolder "chrome_extensions_$ts.json" | |
| $sorted | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 | |
| $sorted | ConvertTo-Json -Depth 8 | Out-File -FilePath $jsonPath -Encoding UTF8 | |
| Write-Host "`n--- Exporting Extension Source Files & User Data ---" -ForegroundColor Cyan | |
| $copiedCount = 0 | |
| $lockedFiles = $false | |
| foreach ($item in $sorted) { | |
| # Sanitize names for folder creation | |
| $safeName = ($item.Name -replace '[\\/:*?"<>|]', '_').Trim() | |
| $safeProfile = ($item.Profile -replace '[\\/:*?"<>|]', '_').Trim() | |
| $destFolderName = "${safeProfile} - ${safeName} (v$($item.Version))" | |
| $extDest = Join-Path $exportFolder $destFolderName | |
| Write-Host " -> Exporting: $($item.Name) ($($item.Profile))..." | |
| New-Item -ItemType Directory -Path $extDest -Force | Out-Null | |
| # 1. Copy Source Files (Safely via Get-ChildItem to bypass bracket issues) | |
| if (-not [string]::IsNullOrWhiteSpace($item.ManifestPath) -and (Test-Path -LiteralPath $item.ManifestPath)) { | |
| $extSource = Split-Path $item.ManifestPath -Parent | |
| $srcDest = Join-Path $extDest "SourceFiles" | |
| New-Item -ItemType Directory -Path $srcDest -Force | Out-Null | |
| try { | |
| Get-ChildItem -LiteralPath $extSource | Copy-Item -Destination $srcDest -Recurse -Force -ErrorAction Stop | |
| } catch { | |
| Write-Warning " [!] Some source files were inaccessible." | |
| } | |
| } | |
| # 2. Copy User Data (Local Settings, Sync Settings, IndexedDB) | |
| $profPath = Join-Path (Join-Path $env:LOCALAPPDATA $channelPaths[$item.Channel]) $item.ProfileDir | |
| $localSettingsPath = Join-Path $profPath "Local Extension Settings\$($item.ExtensionId)" | |
| $syncSettingsPath = Join-Path $profPath "Sync Extension Settings\$($item.ExtensionId)" | |
| $indexedDBPath = Join-Path $profPath "IndexedDB\chrome-extension_$($item.ExtensionId)_0.indexeddb.leveldb" | |
| $dataDest = Join-Path $extDest "UserData" | |
| $hasData = $false | |
| if (Test-Path -LiteralPath $localSettingsPath) { | |
| $hasData = $true | |
| $lsDest = Join-Path $dataDest "Local Extension Settings" | |
| New-Item -ItemType Directory -Path $lsDest -Force | Out-Null | |
| try { Get-ChildItem -LiteralPath $localSettingsPath | Copy-Item -Destination $lsDest -Recurse -Force -ErrorAction Stop } catch { $lockedFiles = $true } | |
| } | |
| if (Test-Path -LiteralPath $syncSettingsPath) { | |
| $hasData = $true | |
| $ssDest = Join-Path $dataDest "Sync Extension Settings" | |
| New-Item -ItemType Directory -Path $ssDest -Force | Out-Null | |
| try { Get-ChildItem -LiteralPath $syncSettingsPath | Copy-Item -Destination $ssDest -Recurse -Force -ErrorAction Stop } catch { $lockedFiles = $true } | |
| } | |
| if (Test-Path -LiteralPath $indexedDBPath) { | |
| $hasData = $true | |
| $idbDest = Join-Path $dataDest "IndexedDB" | |
| New-Item -ItemType Directory -Path $idbDest -Force | Out-Null | |
| try { Get-ChildItem -LiteralPath $indexedDBPath | Copy-Item -Destination $idbDest -Recurse -Force -ErrorAction Stop } catch { $lockedFiles = $true } | |
| } | |
| if (-not $hasData) { | |
| New-Item -ItemType Directory -Path $dataDest -Force | Out-Null | |
| Set-Content -Path (Join-Path $dataDest "NoDataFound.txt") -Value "No local storage, sync settings, or indexedDB folders were found for this extension." | |
| } | |
| $copiedCount++ | |
| } | |
| Write-Host "`n------------------------------------------" -ForegroundColor DarkGray | |
| Write-Host " Chrome Extension Export -- Summary" -ForegroundColor Green | |
| Write-Host "------------------------------------------" -ForegroundColor DarkGray | |
| $results | | |
| Group-Object Channel, Profile | | |
| Select-Object @{N="Channel/Profile"; E={$_.Name}}, | |
| @{N="Total"; E={$_.Count}}, | |
| @{N="Enabled"; E={($_.Group | Where-Object Enabled -eq $true).Count}}, | |
| @{N="Disabled";E={($_.Group | Where-Object Enabled -eq $false).Count}}, | |
| @{N="Unpacked";E={($_.Group | Where-Object IsUnpacked -eq $true).Count}} | | |
| Format-Table -AutoSize | |
| Write-Host "Exported $($copiedCount) extensions physically to folder:" -ForegroundColor Green | |
| Write-Host " -> $exportFolder" -ForegroundColor White | |
| if ($lockedFiles) { | |
| Write-Host "`n[!] WARNING: Some User Data files were locked by Chrome and skipped." -ForegroundColor Yellow | |
| Write-Host " Close Google Chrome completely and run the script again for a perfect backup." -ForegroundColor Yellow | |
| } | |
| Write-Host "------------------------------------------" -ForegroundColor DarkGray | |
| if (-not $Interactive) { | |
| Write-Host "" | |
| Write-Host "💡 TIP: Try running this script with the -Interactive switch!" -ForegroundColor Cyan | |
| Write-Host " This will open a terminal-based menu allowing you to selectively choose which extensions to export" -ForegroundColor Cyan | |
| Write-Host " and let you specify the save destination." -ForegroundColor Cyan | |
| Write-Host " Command: .\Export-ChromeExtensions.ps1 -Interactive" -ForegroundColor Yellow | |
| Write-Host "" | |
| } | |
| #endregion |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
CHROME EXTENSION EXPORT UTILITY
USAGE
• Basic:
.\Export-ChromeExtensions.ps1• Custom:
.\Export-ChromeExtensions.ps1 -OutputDir "C:\Reports" -IncludePermissions• Multi-Channel:
.\Export-ChromeExtensions.ps1 -Channel Stable, Beta, Canary• Filter:
.\Export-ChromeExtensions.ps1 -ProfileFilter "Default"• Bypass:
powershell -ExecutionPolicy Bypass -File .\Export-ChromeExtensions.ps1NOTES
• Supports
Chrome 100+; bypasses file locks viaSecure Preferencestemp copies.• State resolution uses
disable_reasonsbitmask;Unknowncount is always0.TERMINAL OUTPUT
[Stable] Scanning:
C:\Users\Alice\AppData\Local\Google\Chrome\User Data...\ext.csv...\ext.jsonCSV EXCERPT
JSON SNIPPET
{ "Channel": "Stable", "Profile": "Personal", "ExtensionId": "cjpalh...", "Name": "LastPass", "Version": "4.119.0", "ManifestVersion": 3, "Enabled": true, "LocationLabel": "External (CWS update)", "ManifestSHA256": "A3F1C2D8E4B76091F5AA234C8D9E1047B2F38C56D7E9041A6B..." }