Friday Fun: Testing Google Chrome Bookmarks with PowerShell

I was cleaning up and organizing bookmarks in Google Chrome today and decided to find out where they were stored on my computer. I found the Bookmarks file in a local app data folder. Opening it up in Notepad I was pleasantly surprised to discover it is in JSON. Excellent! This gives me an opportunity to try out some of the new web cmdlets in PowerShell v3 and build a little tool to help find broken links.

The first step is to convert the file from JSON into PowerShell objects.

$File = "$env:localappdata\Google\Chrome\User Data\Default\Bookmarks"
$data = Get-content $file | out-string | ConvertFrom-Json

To convert from JSON, the input needs to be one long string. Get-Content writes an array of strings so by piping to Out-String first, ConvertFrom-JSON is happy. Here’s what I end up with.


checksum                                roots                                                      version
--------                                -----                                                      -------
03a5a00f42bb4860f6f8dd4d543e34af        @{bookmark_bar=; other=; synced=}                                1

The roots property is where things are stored.

$data.roots | format-list

bookmark_bar : @{children=System.Object[]; date_added=12989558548133917; date_modified=12998163702118866; id=1;
               name=Bookmarks bar; type=folder}
other        : @{children=System.Object[]; date_added=12989558548133917; date_modified=12998151911083334; id=2;
               name=Other bookmarks; type=folder}
synced       : @{children=System.Object[]; date_added=12989558548133917; date_modified=0; id=3; name=Mobile bookmarks;

As far as I know these are hard-coded properties. Each property can have a nested property called children which will be a collection of bookmarks and subfolders.


children      : {@{date_added=12993566428951635; id=67; • View unanswered posts; type=url;
                url=}, @{date_added=12989558569500214;
                id=5; Control Panel; type=url;
                @{date_added=12989558569506214; id=7; name=Vet Followers; type=url;
                url=}, @{date_added=12989558569508214; id=8; name=Google+;
date_added    : 12989558548133917
date_modified : 12998163702118866
id            : 1
name          : Bookmarks bar
type          : folder

Here’s what an child item looks like:

date_added : 12998151910630334
id : 76
name : Facebook
type : url
url :

Awesome. All I need to do is get the url.

$data.roots.bookmark_bar.children | select Name,url

name                                                        url
----                                                        --- • View unanswered posts             Control Panel                       
Vet Followers                                     
Power Tweet                                                 javascript:(function(){url="
Blog Dashboard                                    

To validate if the URL is good, I can use Invoke-Webrequest.

invoke-webrequest -Uri  -UseBasicParsing | Select StatusCode

All I want is the status code so I’m using basic parsing to speed things up. Now that I have the basics, I can turn this into a script.

#requires -version 3.0

#comment based help is here


Param (
[ValidateScript({Test-Path $_})]
[string]$File = "$env:localappdata\Google\Chrome\User Data\Default\Bookmarks",

Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"

#A nested function to enumerate bookmark folders
Function Get-BookmarkFolder {

Process {
 foreach ($child in $node.children) {
   #get parent folder name
   $parent = $node.Name
   if ($child.type -eq 'Folder') {
     Write-Verbose "Processing $($child.Name)"
     Get-BookmarkFolder $child
   else {
        $hash= [ordered]@{
          Folder = $parent
          Name = $
          URL = $child.url
          Added = [datetime]::FromFileTime(([double]$child.Date_Added)*10)
          Valid = $Null
          Status = $Null
        If ($Validate) {
          Write-Verbose "Validating $($child.url)"
          if ($child.url -match "^http") {
            #only test if url starts with http or https
            Try {
              $r = Invoke-WebRequest -Uri $child.url -DisableKeepAlive -UseBasicParsing
              if ($r.statuscode -eq 200) {
                $hash.Valid = $True
              } #if statuscode
              else {
                $hash.valid = $False
              $hash.status = $r.statuscode
              Remove-Variable -Name r -Force
            Catch {
              Write-Warning "Could not validate $($child.url)"
              $hash.valid = $False
              $hash.status = $Null
            } #if url
    } #if validate
        #write custom object
        New-Object -TypeName PSobject -Property $hash
  } #else url
 } #foreach
 } #process
} #end function

#convert Google Chrome Bookmark filefrom JSON
$data = Get-Content $file | Out-String | ConvertFrom-Json

#these should be the top level "folders"
$data.roots.bookmark_bar | Get-BookmarkFolder
$data.roots.other | Get-BookmarkFolder
$data.roots.synced | Get-BookmarkFolder

Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"

This script converts the bookmarks file from JSON and creates custom objects for each bookmark using the nested Get-BookMarkFolder function. This processes each child if it is a url. If it is a folder then it passes the folder name recursively to Get-BookmarkFolder. Because validation might be time consuming, I made it optional with -Validate. I also converted the date_added property into a user-friendly datetime format. The value in the file is a file time, i.e. number of ticks since 1/1/1601. Although the actual value needs to be multiplied by 10 to get the correct date time.

When I run the script, without validation I get an object like this for each bookmark.

Folder : Misc
Name : [Release][Alpha0.6] CyanogenMod 9 Touchpad – RootzWiki
Added : 8/15/2012 10:42:49 PM
Valid :
Status :

If I validate it will look like this:

Folder : Misc
Name : [Release][Alpha0.6] CyanogenMod 9 Touchpad – RootzWiki
Added : 8/15/2012 10:42:49 PM
Valid : True
status : 200

Bookmarks that fail will show as False. I haven’t gotten around to figuring out how to rewrite the bookmarks file, but I would probably end up using ConvertTo-JSON.

The web cmdlets in PowerShell v3 can be a lot of fun to work with. But one word of caution: do NOT try to turn the script in the ISE using -validate. It is possible that Invoke-WebRequest will begin consuming all memory on your computer, unless you make sure the ISE process is completely killed.

I hope you’ll download Get-ChromeBookmark and let me know what you think.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

About the Author Announcer

This is the official account for and sponsor announcements.