Functions/GenXdev.Webbrowser/Get-BrowserBookmarks.ps1

################################################################################
<#
.SYNOPSIS
Returns all bookmarks from installed web browsers.
 
.DESCRIPTION
Returns bookmarks from Microsoft Edge, Google Chrome, or Mozilla Firefox.
Supports filtering by browser type and returns details like name, URL, folder,
and timestamps.
 
.PARAMETER Chrome
Returns bookmarks from Google Chrome.
 
.PARAMETER Edge
Returns bookmarks from Microsoft Edge.
 
.PARAMETER Firefox
Returns bookmarks from Mozilla Firefox.
 
.EXAMPLE
Get-BrowserBookmarks -Edge | Format-Table Name, URL, Folder
 
.EXAMPLE
gbm -Chrome -Firefox | Where-Object URL -like "*github*"
#>

function Get-BrowserBookmarks {

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Alias('gbm')]

    param (
        ########################################################################
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Chrome',
            Position = 0,
            HelpMessage = "Returns bookmarks from Google Chrome"
        )]
        [switch] $Chrome,

        ########################################################################
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Edge',
            Position = 0,
            HelpMessage = "Returns bookmarks from Microsoft Edge"
        )]
        [switch] $Edge,

        ########################################################################
        [Parameter(
            Mandatory = $false,
            ParameterSetName = 'Firefox',
            Position = 0,
            HelpMessage = "Returns bookmarks from Mozilla Firefox"
        )]
        [switch] $Firefox
    )

    begin {
        # ensure filesystem module is available
        if (-not (Get-Command -Name Expand-Path -ErrorAction SilentlyContinue)) {
            Import-Module GenXdev.FileSystem
        }

        Write-Verbose "Getting installed browsers..."

        # get list of installed browsers
        $installedBrowsers = Get-Webbrowser

        # if no browser specified, use default
        if (-not $Edge -and -not $Chrome -and -not $Firefox) {

            Write-Verbose "No browser specified, using default browser"
            $defaultBrowser = Get-DefaultWebbrowser

            if ($defaultBrowser.Name -like '*Edge*') {
                $Edge = $true
            }
            elseif ($defaultBrowser.Name -like '*Chrome*') {
                $Chrome = $true
            }
            elseif ($defaultBrowser.Name -like '*Firefox*') {
                $Firefox = $true
            }
            else {
                Write-Warning "Default browser is not Edge, Chrome, or Firefox."
                return
            }
        }
    }

    process {
        function Get-ChromiumBookmarks {
            param (
                [string] $bookmarksFilePath,
                [string] $rootFolderName,
                [string] $browserName
            )

            if (-not (Test-Path $bookmarksFilePath)) {
                Write-Verbose "Bookmarks file not found: $bookmarksFilePath"
                return @()
            }

            # read bookmarks json file
            $bookmarksContent = Get-Content -Path $bookmarksFilePath -Raw |
                ConvertFrom-Json

            $bookmarks = [System.Collections.Generic.List[object]]::new()

            # recursive function to traverse bookmark tree
            function ParseBookmarkFolder {
                param (
                    [pscustomobject] $folder,
                    [string] $parentFolder = ""
                )

                foreach ($item in $folder.children) {
                    if ($item.type -eq "folder") {
                        ParseBookmarkFolder -Folder $item `
                            -ParentFolder ($parentFolder + "\" + $item.name)
                    }
                    elseif ($item.type -eq "url") {
                        $bookmarks.Add([pscustomobject]@{
                            Name          = $item.name
                            URL           = $item.url
                            Folder        = $parentFolder
                            DateAdded     = [DateTime]::FromFileTimeUtc(
                                [int64]$item.date_added
                            )
                            DateModified  = if ($item.PSObject.Properties.Match(
                                'date_modified')) {
                                [DateTime]::FromFileTimeUtc(
                                    [int64]$item.date_modified
                                )
                            }
                            else {
                                $null
                            }
                            BrowserSource = $browserName
                        })
                    }
                }
            }

            # process each root folder
            ParseBookmarkFolder -Folder $bookmarksContent.roots.bookmark_bar `
                -ParentFolder "$rootFolderName\Bookmarks Bar"
            ParseBookmarkFolder -Folder $bookmarksContent.roots.other `
                -ParentFolder "$rootFolderName\Other Bookmarks"
            ParseBookmarkFolder -Folder $bookmarksContent.roots.synced `
                -ParentFolder "$rootFolderName\Synced Bookmarks"

            return $bookmarks
        }

        function Get-FirefoxBookmarks {
            param (
                [string] $placesFilePath,
                [string] $browserName
            )

            if (-not (Test-Path $placesFilePath)) {
                Write-Verbose "Firefox places.sqlite not found: $placesFilePath"
                return @()
            }

            $connectionString = "Data Source=$placesFilePath;Version=3;"
            $query = @"
                SELECT
                    b.title,
                    p.url,
                    b.dateAdded,
                    b.lastModified,
                    f.title AS Folder
                FROM moz_bookmarks b
                JOIN moz_places p ON b.fk = p.id
                LEFT JOIN moz_bookmarks f ON b.parent = f.id
                WHERE b.type = 1
"@


            $bookmarks = @()

            try {

                $connection = New-Object System.Data.SQLite.SQLiteConnection($connectionString)
                $connection.Open()
                $command = $connection.CreateCommand()
                $command.CommandText = $query
                $reader = $command.ExecuteReader()

                while ($reader.Read()) {
                    $bookmarks += [pscustomobject]@{
                        Name          = $reader["title"]
                        URL           = $reader["url"]
                        Folder        = $reader["Folder"]
                        DateAdded     = [DateTime]::FromFileTimeUtc($reader["dateAdded"])
                        DateModified  = [DateTime]::FromFileTimeUtc($reader["lastModified"])
                        BrowserSource = $browserName
                    }
                }

                $reader.Close()
                $connection.Close()
            }
            catch {
                Write-Host "Error reading Firefox bookmarks: $PSItem"
            }

            return $bookmarks
        }

        Write-Verbose "Processing browser selection..."

        if ($Edge) {
            $browser = $installedBrowsers |
                Where-Object { $PSItem.Name -like '*Edge*' }

            if (-not $browser) {
                Write-Warning "Microsoft Edge is not installed."
                return
            }

            $bookmarksFilePath = Join-Path `
                -Path $env:LOCALAPPDATA `
                -ChildPath 'Microsoft\Edge\User Data\Default\Bookmarks'

            $rootFolderName = 'Edge'
            $bookmarks = Get-ChromiumBookmarks `
                -BookmarksFilePath $bookmarksFilePath `
                -RootFolderName $rootFolderName `
                -BrowserName $browser.Name
        }
        elseif ($Chrome) {
            $browser = $installedBrowsers | Where-Object { $PSItem.Name -like '*Chrome*' }
            if (-not $browser) {
                Write-Host "Google Chrome is not installed."
                return
            }
            $bookmarksFilePath = Join-Path -Path "${env:LOCALAPPDATA}" -ChildPath 'Google\Chrome\User Data\Default\Bookmarks'
            $rootFolderName = 'Chrome'
            $bookmarks = Get-ChromiumBookmarks -bookmarksFilePath $bookmarksFilePath -rootFolderName $rootFolderName -browserName ($browser.Name)
        }
        elseif ($Firefox) {
            $browser = $installedBrowsers | Where-Object { $PSItem.Name -like '*Firefox*' }
            if (-not $browser) {
                Write-Host "Mozilla Firefox is not installed."
                return
            }
            $profileFolderPath = "$env:APPDATA\Mozilla\Firefox\Profiles"
            $profileFolder = Get-ChildItem -Path $profileFolderPath -Directory | Where-Object { $PSItem.Name -match '\.default-release$' } | Select-Object -First 1
            if ($null -eq $profileFolder) {
                Write-Host 'Firefox profile folder not found.'
                return
            }
            $placesFilePath = Join-Path -Path $profileFolder.FullName -ChildPath 'places.sqlite'
            $bookmarks = Get-FirefoxBookmarks -placesFilePath $placesFilePath -browserName ($browser.Name)
        }
        else {
            Write-Host 'Please specify either -Chrome, -Edge, or -Firefox switch.'
            return
        }

        return $bookmarks
    }

    end {
    }
}
################################################################################