PSBrowserBookmarks.psm1
#region classes class BrowserBookmark { [string]$Source [string]$URL [string]$Title [string]$Path [string]$BMKGuid [string]$URLGuid [string]$URLHash [int]$Order [Nullable[datetime]]$DateAdded [string]$Description [string]$IconFile [int]$IconIndex [Nullable[int]]$Stars [string]$Notes BrowserBookmark([string]$Source) { $this.Source = $Source $this.DateAdded = $null $this.IconIndex = -1 $this.Order = 0 } } class BookmarkOperationResult { [int]$BookmarksAdded = 0 [int]$BookmarksSkipped = 0 [int]$BookmarksRemoved = 0 [int]$ErrorsEncountered = 0 [string]$TargetBrowser [string]$TargetPath = '' [string]$BackupFile = '' BookmarkOperationResult([string]$TargetBrowser) { $this.TargetBrowser = $TargetBrowser } BookmarkOperationResult([string]$TargetBrowser,[string]$TargetPath) { $this.TargetBrowser = $TargetBrowser $this.TargetPath = $TargetPath } } # this is strictly a helper class class ChromeBookmarkItem { [string]$guid [string]$date_added [string]$date_modified [int]$id [string]$name [string]$type = 'url' [string]$url ChromeBookmarkItem([int]$id,[string]$name,[string]$url) { $this.id = $id $this.name = $name $this.url = $url $this.guid = [GUID]::NewGuid().GUID } } # this is strictly a helper class class ChromeBookmarkFolder { [Object[]]$children = @() [string]$guid [string]$date_added [string]$date_modified [int]$id [string]$name [string]$type = 'folder' ChromeBookmarkFolder([int]$id,[string]$name) { $this.id = $id $this.name = $name $this.guid = [GUID]::NewGuid().GUID } } #endregion #region base functions function Get-IniContent { Param( [Parameter()][string]$FilePath ) <# This is a helper function to read the Firefox settings.ini and IE .url files into custom object Stolen from https://devblogs.microsoft.com/scripting/use-powershell-to-work-with-any-ini-file/ #> $ini = $null if (Test-Path $FilePath) { $ini = @{} switch -regex -file $FilePath { "^\[(.+)\]$" # Section { $section = $matches[1] $ini[$section] = @{} $CommentCount = 0 } "^(;.*)$" # Comment { } "(.+?)\s*=\s*(.*)" # Key { if (!($section)) { $section = "No-Section" $ini[$section] = @{} } $name,$value = $matches[1..2] $ini[$section][$name] = $value } } } return $ini } #endregion #region generic # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function New-BrowserBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][ValidateScript({if ($_ -as [uri]) { $true }})][string]$URL, [Parameter(Mandatory=$true)][string]$Title, [Parameter(Mandatory=$false)][string]$Description, [Parameter(Mandatory=$false)][string]$Path, [Parameter(Mandatory=$false)][ValidateRange(0,5)][Nullable[int]]$Stars, [Parameter(Mandatory=$false)][string]$Notes, [Parameter(Mandatory=$false)][Nullable[datetime]]$DateAdded = (Get-Date), [Parameter(Mandatory=$false)][switch]$AttemptIconDiscovery ) $bmk_obj = New-Object BrowserBookmark ('generic') $bmk_obj.URL = $URL $bmk_obj.Title = $Title $bmk_obj.DateAdded = $DateAdded $bmk_obj.Path = $Path if ($null -ne $Stars) { $bmk_obj.Stars = $Stars } if ($Notes) { $bmk_obj.Notes = $Notes } if ($AttemptIconDiscovery) { try { $html = Invoke-WebRequest $URL } catch { Write-Warning ($moduleMessages.nbb002 -f $_.Exception.Message) $html = $null } if ($html) { $icon_rels = @('shortcut icon','apple-touch-icon','icon') $icon_tag = $html.ParsedHtml.getElementsByTagName('link') | where {$_.rel -in $icon_rels} | Select -First 1 if ($icon_tag) { Write-Verbose ($moduleMessages.nbb004 -f $icon_tag.href,$icon_tag.rel) if ($icon_tag.href -like "//*") { # root relative $bmk_obj.IconFile = "$($URL.Substring(0,$URL.IndexOf("://"))):$($icon_tag.href)" } elseif ($icon_tag.href -like "/*") { # relative if ($URL.LastIndexOf('/') -eq ($URL.IndexOf('://') + 2)) { $bmk_obj.IconFile = "$URL$($icon_tag.href)" } else { $bmk_obj.IconFile = "$($URL.Substring(0,$URL.LastIndexOf('/')))$($icon_tag.href)" } } elseif ($icon_tag.href -match "^http(s*):\/\/") { # absolute $bmk_obj.IconFile = $icon_tag.href } else { # invalid Write-Verbose $moduleMessages.nbb005 } if ($bmk_obj.IconFile) { Write-Verbose ($moduleMessages.nbb006 -f $bmk_obj.IconFile) #2do download file and calculate icon index $bmk_obj.IconIndex = 1 } } else { Write-Verbose ($moduleMessages.nbb003 -f ($icon_rels -join ',')) } } } Write-Verbose $moduleMessages.nbb001 $bmk_obj } #endregion #region firefox # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-FirefoxBookmarkLocation { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$PlacesFile ) if ($script:SQLiteLibPath) { try { Add-Type -Path $script:SQLiteLibPath -EA Stop } catch { Write-Warning ($moduleMessages.init02 -f $_.Exception.Message) return } } else { return } $res = $null $path = $null if ($DefaultProfile) { $moz_defaultprofile = $null $moz_basepath = "$($env:APPDATA)\Mozilla\Firefox" Write-Verbose ($moduleMessages.gfl001 -f $moz_basepath) if (Test-Path "$moz_basepath\installs.ini" -PathType Leaf) { $ini_file = Get-IniContent "$moz_basepath\installs.ini" Write-Verbose ($moduleMessages.gfl002 -f $ini_file.Count) if ($ini_file.Count -gt 0) { $default_profile = $ini_file[($ini_file.GetEnumerator()[0]).Name]["Default"] -replace "\/","\" $moz_defaultprofile = "$moz_basepath\$default_profile" Write-Verbose ($moduleMessages.gfl003 -f $moz_defaultprofile) if ($moz_defaultprofile) { $moz_placespath = "$moz_defaultprofile\places.sqlite" if (Test-Path $moz_placespath) { $path = $moz_placespath } else { Write-Verbose ($moduleMessages.gfl005 -f $moz_placespath) } } } } else { Write-Verbose ($moduleMessages.gfl004 -f $moz_basepath) } } else { if (Test-Path $PlacesFile) { $path = $PlacesFile } else { Write-Verbose ($moduleMessages.gfl005 -f $PlacesFile) } } if ($path) { $sql_ok = $false $dbc_sql = New-Object -TypeName System.Data.SQLite.SQLiteConnection $dbc_sql.ConnectionString = "Data Source=$path" try { $dbc_sql.Open() } catch { Write-Warning ($moduleMessages.sql001 -f $_.Exception.Message) } if ($dbc_sql.State -eq "Open") { $cmd_sql = $dbc_sql.CreateCommand() $cmd_sql.CommandText = "SELECT COUNT(*) FROM moz_bookmarks WHERE type <> 1" $nbm = $cmd_sql.ExecuteScalar() $cmd_sql.Dispose() if ($nbm -gt 0) { Write-Verbose ($ModuleMessages.sql003 -f $nbm) $sql_ok = $true } else { Write-Warning ($ModuleMessages.sql002 -f $path) } $dbc_sql.Close() $dbc_sql.Dispose() [gc]::Collect() } if ($sql_ok) { $res = $path } } return $res } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Backup-FirefoxBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$PlacesFile, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$OutPath = (Get-Location -PSProvider FileSystem) ) $res = $null if ($DefaultProfile) { $source = Get-FirefoxBookmarkLocation -DefaultProfile } else { $source = Get-FirefoxBookmarkLocation -PlacesFile $PlacesFile } if (![string]::IsNullOrWhiteSpace($source)) { Write-Verbose ($ModuleMessages.bfb001 -f $source) if (Test-Path $OutPath -PathType Container) { $backup_file = "$($OutPath)\$(Get-Date -Format "yyyyMMdd_HHmmss")_$(Split-Path $source -Leaf).zip" try { Compress-Archive -Path $source -DestinationPath $backup_file -EA Stop $res = $backup_file } catch { Write-Warning ($ModuleMessages.bfb003 -f $_.Exception.Message,$backup_file) } } else { Write-Warning ($ModuleMessages.bfb002 -f $OutPath) } } return $res } function ConvertFrom-MozillaTime { Param( [Parameter(Mandatory=$true)][int64]$MozillaTime ) <# This is a helper function to convert a long integer Mozilla time to [datetime] #> $origin = [datetime]'1970-01-01 00:00:00' $origin.AddMilliSeconds($MozillaTime / 1000) } function ConvertTo-MozillaTime { Param( [Parameter(Mandatory=$true)][datetime]$RealTime ) <# This is a helper function to convert [datetime] to a long integer Mozilla time #> $origin = [datetime]'1970-01-01 00:00:00' ((New-TimeSpan -Start $origin -End $RealTime).TotalMilliseconds -as [int64]) * 1000 } function Get-MozillaHash { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][string]$Url ) <# This is a helper function to generate URL hashes. The procedure is described in https://github.com/bencaradocdavies/sqlite-mozilla-url-hash/blob/master/hash.c we don't generate a hash as of right now but will explore it further in the future Since PowerShell isn't very good at handling unsigned integers, it will probably have to be done in c# #> return 0 } function Get-MozillaGUID { [CmdletBinding()] Param( [Parameter(Mandatory=$false)][int]$GUIDLength = 12 ) <# This is a helper function to generate GUIDs for Mozilla tabels. The uniqueness is not checked nor enforced here so needs to be checked in the calling routine. #> $ValidChars = "0123456789-abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray() $moz_guid = "" for ($i=1; $i -le $GUIDLength; $i++) { $moz_guid += $ValidChars | Get-Random } return $moz_guid } function Get-MozillaURLMeta { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][ValidateScript({if ($_ -as [uri]) { $true }})][string]$URL ) <# This is a helper function to generate URL metadata needed by Mozilla: - prefix (http://, https:// etc.) - host (FQDN, hostname or IP address of the host, basically everything between the prefix and the first slash) - revhost (the value of host in reverse character order #> $out = [PSCustomObject]@{ 'prefix' = $URL.Substring(0,$URL.IndexOf("://") + 3) 'host' = ($URL.Substring($URL.IndexOf("://") + 3) -split "/")[0] 'revhost' = '' } for ($i = $out.host.Length - 1; $i -ge 0; $i--) { $out.revhost += $out.host.Substring($i,1) } $out.revhost += "." $out } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-FirefoxBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFile, [Parameter(Mandatory=$false)][switch]$ExcludeFolders ) if ($script:SQLiteLibPath) { try { Add-Type -Path $script:SQLiteLibPath -EA Stop } catch { Write-Warning ($moduleMessages.init02 -f $_.Exception.Message) return } } else { return } if ($DefaultProfile) { $src_file = Get-FirefoxBookmarkLocation -DefaultProfile } else { $src_file = Get-FirefoxBookmarkLocation -PlacesFile $SourceFile } if ($src_file) { $src_ok = $false $dbc_src = New-Object -TypeName System.Data.SQLite.SQLiteConnection $dbc_src.ConnectionString = "Data Source=$src_file" try { $dbc_src.Open() } catch { Write-Warning ($moduleMessages.sql001 -f $_.Exception.Message) } if ($dbc_src.State -eq "Open") { $cmd_src = $dbc_src.CreateCommand() $dta_src = New-Object System.Data.SQLite.SQLiteDataAdapter $cmd_src $dts_src = New-Object System.Data.DataSet $cmd2_src = $dbc_src.CreateCommand() $cmd_src.CommandText = "SELECT COUNT(*) FROM moz_bookmarks WHERE type <> 1" if ($cmd_src.ExecuteScalar() -gt 0) { $src_ok = $true } else { Write-Warning ($ModuleMessages.gfb002 -f $src_file) } if ($src_ok) { if (!$ExcludeFolders) { $cmd_src.CommandText = "SELECT * FROM moz_bookmarks WHERE type = 2 AND parent > 1 ORDER BY id" $null = $dta_src.Fill($dts_src) $src_folders = @{} foreach ($fld in $dts_src.Tables[0]) { Write-Verbose "Adding folder: $($fld['title'])" if ($src_folders.ContainsKey($fld['parent'])) { $fld_path = "$($src_folders[$fld['parent']]['path'])\$($fld['title'])" } else { $fld_path = $fld['title'] } $src_folders.Add($fld['id'],@{'name' = $fld['title'];'parent' = $fld['parent'];'path' = $fld_path}) } $dts_src.Dispose() } $cmd_src.CommandText = "SELECT * FROM moz_bookmarks WHERE type = 1" $null = $dta_src.Fill($dts_src) $bookmarks = $dts_src.Tables[0] $dts_src.Dispose() foreach ($bmk in $bookmarks) { $bmk_ok = $true $bmk_obj = New-Object BrowserBookmark ('mozilla') $bmk_obj.Title = $bmk['title'] $bmk_obj.BMKGuid = $bmk['guid'] if ([System.DBNull]::Value -ne $bmk['position']) { $bmk_obj.Order = $bmk['position'] } $bmk_obj.DateAdded = ConvertFrom-MozillaTime $bmk['dateAdded'] if (!$ExcludeFolders -and $bmk['parent']) { $bmk_obj.Path = $src_folders[$bmk['parent']].path } if ($bmk['fk'] -ne [DBNull]::Value) { $cmd2_src.CommandText = "SELECT * FROM moz_places WHERE id = $($bmk['fk'])" $dta2_src = New-Object System.Data.SQLite.SQLiteDataAdapter $cmd2_src $dts2_src = New-Object System.Data.DataSet $null = $dta2_src.Fill($dts2_src) if ($dts2_src.Tables[0].Rows.Count -eq 0) { Write-Verbose ($ModuleMessages.gfb003 -f $bmk['fk']) $bmk_ok = $false } else { $bmk_obj.URL = $dts2_src.Tables[0].Rows[0]['url'] if ([DBNull]::Value -eq $bmk_obj.URL) { $bmk_obj.URL = $null } if ([string]::IsNullOrWhiteSpace($bmk_obj.URL)) { $bmk_ok = $false } else { if ([DBNull]::Value -ne $dts2_src.Tables[0].Rows[0]['description']) { $desc = $dts2_src.Tables[0].Rows[0]['description'] if ($desc) { $bmk_obj.Description = $desc.Trim() } } $bmk_obj.URLGuid = $dts2_src.Tables[0].Rows[0]['guid'] $bmk_obj.URLHash = $dts2_src.Tables[0].Rows[0]['url_hash'] } } $dts2_src.Dispose() } if ($bmk_ok) { $bmk_obj } } } } if ($dbc_src.State -eq "Open") { Write-Verbose $ModuleMessages.gfb005 # this is necessary to free up SQLite handles $cmd_src.Dispose() $cmd2_src.Dispose() $dbc_src.Close() $dbc_src.Dispose() [gc]::Collect() } } else { Write-Warning ($ModuleMessages.gfb004 -f $src_file) } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Add-FirefoxBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][object[]]$Bookmark, [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFile, [Parameter(Mandatory=$false)][switch]$ExcludeFolders, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) Begin { if ($script:SQLiteLibPath) { try { Add-Type -Path $script:SQLiteLibPath -EA Stop } catch { Write-Warning ($moduleMessages.init02 -f $_.Exception.Message) return } } else { return } $out = New-Object BookmarkOperationResult ('mozilla') $tgt_folders = @{} $root_folder_id = $null $new_file = $false $existing_bookmarks = @() if ($DefaultProfile) { $target_file = Get-FirefoxBookmarkLocation -DefaultProfile } else { $target_file = Get-FirefoxBookmarkLocation -PlacesFile $TargetFile if ([string]::IsNullOrWhiteSpace($target_file)) { try { Write-Verbose ($moduleMessages.afb001 -f $TargetFile) Copy-Item -Path "$PSScriptRoot\lib\places.sqlite" -Destination $TargetFile -Force -EA Stop $target_file = $TargetFile $new_file = $true } catch { Write-Warning ($moduleMessages.afb004 -f $_.Exception.Message) $out.ErrorsEncountered++ $out break } } } if ($target_file -and !$new_file) { if ($BackupTarget) { $backup_file = Backup-FirefoxBookmark -PlacesFile $target_file -OutPath $BackupPath if ($backup_file) { Write-Verbose ($ModuleMessages.afb005 -f $backup_file) $out.BackupFile = $backup_file } else { Write-Warning ($ModuleMessages.afb006) $out.ErrorsEncountered++ $out break } } $existing_bookmarks = Get-FirefoxBookmark -SourceFile $target_file if ($existing_bookmarks) { Write-Verbose ($ModuleMessages.afb002 -f $existing_bookmarks.Count) $existing_urls = $existing_bookmarks | Select-Object -ExpandProperty url } else { Write-Verbose $ModuleMessages.afb003 $existing_urls = @() } } if ($target_file) { $out.TargetPath = $target_file $dbc_tgt = New-Object -TypeName System.Data.SQLite.SQLiteConnection $dbc_tgt.ConnectionString = "Data Source=$target_file" try { $dbc_tgt.Open() $cmd_tgt = $dbc_tgt.CreateCommand() $dta_tgt = New-Object System.Data.SQLite.SQLiteDataAdapter $cmd_tgt $dts_tgt = New-Object System.Data.DataSet } catch { Write-Warning ($moduleMessages.afb007 -f $_.Exception.Message) $out.ErrorsEncountered++ $out break } $q = "SELECT name FROM sqlite_master WHERE type='table' AND name='moz_origins'" $cmd_tgt.CommandText = $q $orgname = $cmd_tgt.ExecuteScalar() if ($orgname -eq 'moz_origins') { Write-Verbose $moduleMessages.afb026 $orgexists = $true } else { Write-Verbose $moduleMessages.afb027 $orgexists = $false } Write-Verbose $moduleMessages.afb008 $cmd_tgt.CommandText = "SELECT id FROM moz_bookmarks WHERE type = 2 AND guid='toolbar_____'" $root_folder_id = $cmd_tgt.ExecuteScalar() if (!$root_folder_id) { Write-Verbose $moduleMessages.afb009 $cmd_tgt.CommandText = "SELECT id FROM moz_bookmarks WHERE type = 2 AND guid='menu________'" $root_folder_id = $cmd_tgt.ExecuteScalar() } if (!$root_folder_id) { Write-Verbose $moduleMessages.afb010 $cmd_tgt.CommandText = "SELECT id FROM moz_bookmarks WHERE type = 2 AND guid='unfiled_____'" $root_folder_id = $cmd_tgt.ExecuteScalar() } if (!$root_folder_id) { Write-Verbose $moduleMessages.afb011 $cmd_tgt.CommandText = "SELECT MIN(id) FROM moz_bookmarks WHERE type = 2 AND parent=1" $root_folder_id = $cmd_tgt.ExecuteScalar() } if (!$root_folder_id) { Write-Warning $moduleMessages.afb012 if ($dbc_tgt.State -eq 'Open') { Write-Verbose $moduleMessages.sql004 $cmd_tgt.Dispose() $dbc_tgt.Close() } $out break } Write-Verbose ($moduleMessages.afb013 -f $root_folder_id) Write-Verbose $moduleMessages.afb014 if (!$ExcludeFolders) { $cmd_tgt.CommandText = "SELECT * FROM moz_bookmarks WHERE type = 2 AND parent >= $root_folder_id ORDER BY id" $null = $dta_tgt.Fill($dts_tgt) foreach ($fld in $dts_tgt.Tables[0]) { Write-Verbose "Adding folder: $($fld['title'])" if ($tgt_folders.ContainsKey($fld['parent'])) { $fld_path = "$($tgt_folders[$fld['parent']]['path'])\$($fld['title'])" } else { $fld_path = $fld['title'] } $tgt_folders.Add($fld['id'],@{'name' = $fld['title'];'parent' = $fld['parent'];'path' = $fld_path}) } $dts_tgt.Dispose() } } } Process { foreach ($bm in $Bookmark) { if ([string]::IsNullOrWhiteSpace($bm.url)) { Write-Verbose $moduleMessages.url001 continue } if ($existing_urls -contains $bm.url) { Write-Verbose ($moduleMessages.url002 -f $bm.url) $out.BookmarksSkipped ++ continue } else { Write-Verbose ($moduleMessages.url003 -f $bm.url) $bm_meta = Get-MozillaURLMeta -URL $bm.url if ($bm.Path -and !$ExcludeFolders) { Write-Verbose ($moduleMessages.afb015 -f $bm.Path) $path_folder = $tgt_folders.GetEnumerator() | where {$_.Value['path'] -eq $bm.Path} if ($path_folder) { Write-Verbose ($moduleMessages.afb016 -f $path_folder.Name) $bm_folder_id = $path_folder.Name } else { Write-Verbose $moduleMessages.afb017 $fld_time = ConvertTo-MozillaTime -RealTime (Get-Date) $path_parts = $bm.Path -split "\\" $parent_id = $root_folder_id $full_path = "" foreach ($fld in $path_parts) { Write-Verbose ($moduleMessages.afb018 -f $fld) if ($full_path.Length -gt 0) { $full_path = "$full_path\$fld" } else { $full_path = $fld } Write-Verbose ($moduleMessages.afb019 -f $full_path) $path_folder = $tgt_folders.GetEnumerator() | where {$_.Value['path'] -eq $full_path} if ($path_folder) { Write-Verbose ($moduleMessages.afb016 -f $path_folder.Name) $parent_id = $path_folder.Name } else { $sql_title = $fld -replace "'","''" $cmd_tgt.CommandText = "SELECT MIN(id) AS FolderEx FROM moz_bookmarks WHERE type=2 AND parent=$parent_id AND title='$sql_title'" $existing_folder = $cmd_tgt.ExecuteScalar() if ([DBNull]::Value -ne $existing_folder) { Write-Verbose ($moduleMessages.afb020 -f $existing_folder) $parent_id = $existing_folder } else { do { $guid = Get-MozillaGUID $cmd_tgt.CommandText = "SELECT id FROM moz_bookmarks WHERE guid='$guid'" } until ($null -eq $cmd_tgt.ExecuteScalar()) $cmd_tgt.CommandText = "SELECT MAX(position) FROM moz_bookmarks WHERE type=2 AND parent=$parent_id" $pos = $cmd_tgt.ExecuteScalar() Write-Verbose ($moduleMessages.afb021 -f $pos) if ([DBNull]::Value -ne $pos) { $pos++ } else { $pos = 0 } $q = "INSERT INTO moz_bookmarks (id,type,parent,position,title,guid,dateAdded,lastModified) VALUES (NULL,2,$parent_id,$pos,'$sql_title','$guid',$fld_time,$fld_time)" Write-Verbose $q $cmd_tgt.CommandText = $q $cmd_tgt.ExecuteNonQuery() $cmd_tgt.CommandText = "select last_insert_rowid()" $new_parent_id = $cmd_tgt.ExecuteScalar() $tgt_folders.Add($new_parent_id,@{'name'=$fld;'path'=$full_path;'parent'=$parent_id}) $parent_id = $new_parent_id } } } $bm_folder_id = $parent_id } } else { $bm_folder_id = $root_folder_id } } # check or add origin $q = "SELECT name FROM sqlite_master WHERE type='table' AND name='moz_origins'" $cmd_tgt.CommandText = $q $orgname = $cmd_tgt.ExecuteScalar() if ($orgexists) { $q = "SELECT MIN(id) FROM moz_origins WHERE prefix='$($bm_meta.prefix)' AND host='$($bm_meta.host)'" $cmd_tgt.CommandText = $q $orgid = $cmd_tgt.ExecuteScalar() if ([DBNull]::Value -ne $orgid) { $bm_origin_id = $orgid Write-Verbose ($moduleMessages.afb022 -f $bm_origin_id) } else { $cmd_tgt.CommandText = "INSERT INTO moz_origins (id,prefix,host,frecency) VALUES (NULL,'$($bm_meta.prefix)','$($bm_meta.host)',0)" $cmd_tgt.ExecuteNonQuery() $cmd_tgt.CommandText = "select last_insert_rowid()" $bm_origin_id = $cmd_tgt.ExecuteScalar() Write-Verbose ($moduleMessages.afb023 -f $bm_origin_id) } } # add url do { $guid = Get-MozillaGUID $cmd_tgt.CommandText = "SELECT id FROM moz_places WHERE guid='$guid'" } until ($null -eq $cmd_tgt.ExecuteScalar()) $url_hash = Get-MozillaHash -Url $bm.URL if ($orgexists) { $q = "INSERT INTO moz_places (id,url,rev_host,guid,description,url_hash,origin_id,frecency) VALUES (NULL,'$($bm.URL)','$($bm_meta.revhost)','$guid','$($bm.Description -replace "'","''")',$url_hash,$bm_origin_id,0)" } else { $q = "INSERT INTO moz_places (id,url,rev_host,guid,url_hash,frecency) VALUES (NULL,'$($bm.URL)','$($bm_meta.revhost)','$guid',$url_hash,0)" } $cmd_tgt.CommandText = $q $cmd_tgt.ExecuteNonQuery() $cmd_tgt.CommandText = "select last_insert_rowid()" $bm_url_id = $cmd_tgt.ExecuteScalar() Write-Verbose ($moduleMessages.afb024 -f $bm_url_id) # add bookmark $sql_title = $bm.Title -replace "'","''" $time_da = ConvertTo-MozillaTime $bm.DateAdded $time_lm = ConvertTo-MozillaTime (Get-Date) do { $guid = Get-MozillaGUID $cmd_tgt.CommandText = "SELECT id FROM moz_bookmarks WHERE guid='$guid'" } until ($null -eq $cmd_tgt.ExecuteScalar()) if ($null -eq $bm_folder_id) { $bm_folder_id = 'NULL' } $q = "INSERT INTO moz_bookmarks (id,type,guid,parent,fk,title,dateAdded,lastModified) VALUES (NULL,1,'$guid',$bm_folder_id,$bm_url_id,'$sql_title',$time_da,$time_lm)" $cmd_tgt.CommandText = $q $cmd_tgt.ExecuteNonQuery() $cmd_tgt.CommandText = "select last_insert_rowid()" $bm_id = $cmd_tgt.ExecuteScalar() Write-Verbose ($moduleMessages.afb025 -f $bm_id) $out.BookmarksAdded++ } } End { if ($dbc_tgt.State -eq 'Open') { Write-Verbose $moduleMessages.sql004 $cmd_tgt.Dispose() $dbc_tgt.Close() $dbc_tgt.Dispose() [gc]::Collect() } $out } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Remove-FirefoxBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][ValidateScript({if ($_ -as [uri]) { $true }})][string]$URL, [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFile, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) Write-Warning "Remove-FireFoxBookmark has not been implemented yet" if ($script:SQLiteLibPath) { try { Add-Type -Path $script:SQLiteLibPath -EA Stop } catch { Write-Warning ($moduleMessages.init02 -f $_.Exception.Message) return } } else { return } $out = New-Object BookmarkOperationResult ('mozilla') return $out } #endregion #region internet explorer # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-IEBookmarkLocation { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named Folder')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFolder ) $res = $null if ($DefaultProfile) { $fav_path = Get-ItemPropertyValue -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" -Name "Favorites" -EA SilentlyContinue if ($fav_path) { Write-Verbose ($ModuleMessages.gil002 -f $fav_path) $path = $fav_path } else { Write-Warning $ModuleMessages.gil001 } } else { $SourceFolder = $SourceFolder.Trim().TrimEnd("\") if (Test-Path $SourceFolder -PathType Container) { $path = $SourceFolder } } if ($path) { $url_files = @(Get-ChildItem -Path $path -Filter "*.url" -Recurse) if ($url_files.Count -gt 0) { $res = $path } } return $res } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Backup-IEBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFolder, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$OutPath = (Get-Location -PSProvider FileSystem) ) $res = $null if ($DefaultProfile) { $source = Get-IEBookmarkLocation -DefaultProfile } else { $source = Get-IEBookmarkLocation -SourceFolder $SourceFolder } if (![string]::IsNullOrWhiteSpace($source)) { Write-Verbose ($ModuleMessages.bib001 -f $source) if (Test-Path $OutPath -PathType Container) { $backup_file = "$($OutPath)\$(Get-Date -Format "yyyyMMdd_HHmmss")_$(Split-Path $source -Leaf).zip" try { Compress-Archive -Path $source -DestinationPath $backup_file -EA Stop $res = $backup_file } catch { Write-Warning ($ModuleMessages.bib003 -f $_.Exception.Message,$backup_file) } } else { Write-Warning ($ModuleMessages.bib002 -f $OutPath) } } $res } function ConvertTo-FileName { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][string]$Title ) <# This is a helper function to build a filename (for the .url or .website file) out of the page title #> $title_sanitized = $Title -replace '(\*|\\|\/|\?|\<|\>|\:|\||\")','-' $title_sanitized.Substring(0,[math]::Min($title_sanitized.Length,250)) } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-IEBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named Folder')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFolder, [Parameter(Mandatory=$false)][switch]$ExcludeFolders ) if ($DefaultProfile) { $fav_folder = Get-IEBookmarkLocation -DefaultProfile } else { $fav_folder = Get-IEBookmarkLocation -SourceFolder $SourceFolder } if ($fav_folder) { Get-ChildItem -Path $fav_folder -Filter "*.url" -Recurse | ForEach-Object { Write-Verbose $_.FullName $bmk_ok = $true $bmk_obj = New-Object BrowserBookmark ('ie') $bmk_obj.DateAdded = $_.CreationTime $bmk_obj.Title = $_.Name.Substring(0,$_.Name.LastIndexOf(".")) $bmkini = Get-IniContent $_.FullName if ($bmkini.Count -gt 0) { if ($bmkini['InternetShortcut']['URL'] -as [uri]) { # description field if ($bmkini['{5CBF2787-48CF-4208-B90E-EE5E5D420294}']) { $desc_line = $bmkini['{5CBF2787-48CF-4208-B90E-EE5E5D420294}']["Prop21"] if ($desc_line) { if ($desc_line.Substring(0,3) -eq '31,') { $bmk_obj.Description = $desc_line.Substring(3) } else { Write-Verbose ($ModuleMessages.gib003 -f $($desc_line.Substring(0,3))) } } else { Write-Verbose ($ModuleMessages.gib006) } } # notes field if ($bmkini['{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}']) { $desc_line = $bmkini['{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}']["Prop5"] if ($desc_line) { if ($desc_line.Substring(0,3) -eq '31,') { $bmk_obj.Notes = $desc_line.Substring(3) } else { Write-Verbose ($ModuleMessages.gib004 -f $($desc_line.Substring(0,3))) } } else { Write-Verbose ($ModuleMessages.gib007) } } # stars field if ($bmkini['{64440492-4C8B-11D1-8B70-080036B11A03}']) { $desc_line = $bmkini['{64440492-4C8B-11D1-8B70-080036B11A03}']["Prop9"] if ($desc_line) { if ($desc_line.Substring(0,3) -eq '19,') { switch ($desc_line.Substring(3) -as [int]) { 1 { $bmk_obj.Stars = 1 } 25 { $bmk_obj.Stars = 2 } 50 { $bmk_obj.Stars = 3 } 75 { $bmk_obj.Stars = 4 } 99 { $bmk_obj.Stars = 5 } default { Write-Verbose ($ModuleMessages.gib009 -f ($desc_line.Substring(3) -as [int])) } } } else { Write-Verbose ($ModuleMessages.gib005 -f $($desc_line.Substring(0,3))) } } else { Write-Verbose ($ModuleMessages.gib008) } } $bmk_obj.URL = $bmkini['InternetShortcut']['URL'] $bmk_obj.IconFile = $bmkini['InternetShortcut']['iconfile'] $bmk_obj.IconIndex = $bmkini['InternetShortcut']['iconindex'] } else { $bmk_ok = $false } } else { $bmk_ok = $false } if (!$ExcludeFolders -and ((Split-Path $_.FullName -Parent) -notlike $fav_folder)) { $bmk_obj.Path = (Split-Path $_.FullName -Parent).Substring($fav_folder.Length + 1) } if ($bmk_ok) { $bmk_obj } } } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Add-IEBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][object[]]$Bookmark, [Parameter(Mandatory=$false,ParameterSetName="Default")][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName="Named Folder")][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFolder, [Parameter(Mandatory=$false)][switch]$ExcludeFolders, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) # construction: https://support.microsoft.com/en-us/help/2568750/apply-property-error-occurs-when-attempting-to-modify-either-the-ratin Begin { $out = New-Object BookmarkOperationResult ('ie') if ($DefaultProfile) { $tgt_folder = Get-DefaultIEFaves } else { $tgt_folder = $TargetFolder.Trim().TrimEnd("\") if (!(Test-Path $tgt_folder -PathType Container)) { Write-Verbose ($ModuleMessages.aib001 -f $tgt_folder) try { $null = New-Item $tgt_folder -ItemType Directory -Force -EA Stop } catch { Write-Warning ($ModuleMessages.aib003 -f $_.Exception.Message) } } } if (Test-Path $tgt_folder -PathType Container) { $out.TargetPath = $tgt_folder $existing_bookmarks = Get-IEBookmark -SourceFolder $tgt_folder if ($existing_bookmarks) { Write-Verbose ($ModuleMessages.aib004 -f $existing_bookmarks.Count) $existing_urls = $existing_bookmarks | Select-Object -ExpandProperty url if ($BackupTarget) { $backup_file = Backup-IEBookmark -SourceFolder $tgt_folder $out.BackupFile = $backup_file } } else { Write-Verbose $ModuleMessages.aib006 $existing_urls = @() } } else { Write-Warning ($ModuleMessages.aib002 -f $tgt_folder) $out.ErrorsEncountered ++ break } } Process { foreach ($bm in $Bookmark) { if ([string]::IsNullOrWhiteSpace($bm.url)) { Write-Verbose $ModuleMessages.aib012 continue } if ($existing_urls -contains $bm.url) { Write-Verbose ($ModuleMessages.aib011 -f $bm.url) $out.BookmarksSkipped ++ continue } else { $new_filename = ConvertTo-FileName $bm.title if (!$ExcludeFolders -and $bm.path) { $new_path = "$tgt_folder\$($bm.path)" } else { $new_path = $tgt_folder } if (!(Test-Path $new_path -PathType Container)) { try { $null = New-Item $new_path -ItemType Directory -Force -EA Stop } catch { Write-Warning ($ModuleMessages.aib007 -f $new_path,$_.Exception.Message) } } if (Test-Path $new_path -PathType Container) { $new_file = "$($new_path)\$($new_filename).url" $fi = 0 While (Test-Path $new_file -PathType Leaf) { Write-Warning ($ModuleMessages.aib010 -f $new_file) $fi++ $new_file = "$($new_path)\$($new_filename)($fi).url" } } else { Write-Warning ($ModuleMessages.aib008 -f $new_path) $new_file = $null } if ($new_file) { $bkdata = @('[{000214A0-0000-0000-C000-000000000046}]', 'Prop3=19,0', '[InternetShortcut]', 'IDList=', "URL=$($bm.url)", 'Roamed=-1') if (![string]::IsNullOrWhiteSpace($bm.IconFile)) { $bkdata += "IconFile=$($bm.IconFile)" $bkdata += "IconIndex=$($bm.IconIndex)" } if (![string]::IsNullOrWhiteSpace($bm.Description)) { $bkdata += "[{5CBF2787-48CF-4208-B90E-EE5E5D420294}]" $bkdata += "Prop21=31,$($bm.Description)" } if (![string]::IsNullOrWhiteSpace($bm.Notes)) { $bkdata += "[{B9B4B3FC-2B51-4A42-B5D8-324146AFCF25}]" $bkdata += "Prop5=31,$($bm.Notes)" } if ($bm.Stars -gt 0){ $bkdata += "[{64440492-4C8B-11D1-8B70-080036B11A03}]" switch ($bm.Stars) { 1 { $bkdata += "Prop9=19,1" } 2 { $bkdata += "Prop9=19,25" } 3 { $bkdata += "Prop9=19,50" } 4 { $bkdata += "Prop9=19,75" } 5 { $bkdata += "Prop9=19,99" } } } try { $bkdata | Set-Content -LiteralPath "$new_file" -Force -EA Stop $file_ok = $true $out.BookmarksAdded ++ } catch { Write-Warning ($ModuleMessages.aib013 -f $new_file, $_.Exception.Message) $file_ok = $false $out.ErrorsEncountered ++ } if ($file_ok -and $bm.DateAdded) { Write-Verbose ($ModuleMessages.aib009 -f $new_file,(Get-Date $bm.DateAdded -Format "dd.MM.yyyy")) try { (Get-Item -LiteralPath "$new_file").LastWriteTime = $bm.DateAdded } catch { Write-Warning ($ModuleMessages.aib014 -f $new_file, $_.Exception.Message) } } } } } } End { $out } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Remove-IEBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][ValidateScript({if ($_ -as [uri]) { $true }})][string]$URL, [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named Folder')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFolder, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) Write-Warning "Remove-IEBookmark has not been implemented yet" $out = New-Object BookmarkOperationResult ('ie') return $out } #endregion #region chrome # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-ChromeBookmarkLocation { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFile ) #2do: Expand placeholders $res = $null if ($DefaultProfile) { $file = "$($env:LOCALAPPDATA)\Google\Chrome\User Data\Default\Bookmarks" $sync_disabled = $false $sync_disabled_machine = $false if (Test-Path "HKLM:\Software\Policies\Google\Chrome") { if (Get-ItemProperty -Path "HKLM:\Software\Policies\Google\Chrome" -Name "SyncDisabled") { $sync_disabled_machine = $true if ((Get-ItemPropertyValue -Path "HKLM:\Software\Policies\Google\Chrome" -Name "SyncDisabled") -eq 1) { Write-Verbose $ModuleMessages.gcl001 $sync_disabled = $true } } } if (!$sync_disabled_machine) { if (Test-Path "HKCU:\Software\Policies\Google\Chrome") { if (Get-ItemProperty -Path "HKCU:\Software\Policies\Google\Chrome" -Name "SyncDisabled") { if ((Get-ItemPropertyValue -Path "HKCU:\Software\Policies\Google\Chrome" -Name "SyncDisabled") -eq 1) { Write-Verbose $ModuleMessages.gcl002 $sync_disabled = $true } } } } if (!$sync_disabled) { Write-Verbose "Sync is not disabled, looking for Roaming Profile configuration..." $rp_support = $false $rp_support_machine = $false if (Test-Path "HKLM:\Software\Policies\Google\Chrome") { if (Get-ItemProperty -Path "HKLM:\Software\Policies\Google\Chrome" -Name "RoamingProfileSupportEnabled") { $rp_support_machine = $true if ((Get-ItemPropertyValue -Path "HKLM:\Software\Policies\Google\Chrome" -Name "RoamingProfileSupportEnabled") -eq 1) { Write-Verbose $ModuleMessages.gcl003 $rp_support = $true } } } if (!$rp_support_machine) { if (Test-Path "HKCU:\Software\Policies\Google\Chrome") { if (Get-ItemProperty -Path "HKCU:\Software\Policies\Google\Chrome" -Name "RoamingProfileSupportEnabled") { if ((Get-ItemPropertyValue -Path "HKCU:\Software\Policies\Google\Chrome" -Name "RoamingProfileSupportEnabled") -eq 1) { Write-Verbose $ModuleMessages.gcl004 $rp_support = $true } } } } } if ($rp_support) { $file = "$($env:APPDATA)\Google\Chrome\User Data\Default\Bookmarks" # 2do: Finisch expansion of profile paths # https://www.chromium.org/administrators/policy-list-3/user-data-directory-variables } else { Write-Verbose $ModuleMessages.gcl005 } if (Test-Path $file -PathType Leaf) { $path = $file } else { Write-Verbose ("File not found" -f $file) } } else { $SourceFile = $SourceFile.Trim() if (Test-Path $SourceFile -PathType Leaf) { $path = $SourceFile } } if ($path) { $bm_struct = $null $chrome_bm_cnt = Get-Content $path try { $bm_struct = $chrome_bm_cnt | ConvertFrom-Json -EA Stop } catch { Write-Warning ($ModuleMessages.gcl006 -f $_.Exception.Message) } if ($bm_struct) { if ($bm_struct.roots.bookmark_bar.children.Count -gt 0) { $res = $path } } } return $res } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Backup-ChromeBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFile, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$OutPath = (Get-Location -PSProvider FileSystem) ) $res = $null if ($DefaultProfile) { $source = Get-ChromeBookmarkLocation -DefaultProfile } else { $source = Get-ChromeBookmarkLocation -SourceFile $SourceFile } if (![string]::IsNullOrWhiteSpace($source)) { Write-Verbose ($ModuleMessages.bcb001 -f $source) if (Test-Path $OutPath -PathType Container) { $backup_file = "$($OutPath)\$(Get-Date -Format "yyyyMMdd_HHmmss")_$(Split-Path $source -Leaf).zip" try { Compress-Archive -Path $source -DestinationPath $backup_file -EA Stop $res = $backup_file } catch { Write-Warning ($ModuleMessages.bcb003 -f $_.Exception.Message,$backup_file) } } else { Write-Warning ($ModuleMessages.bcb002 -f $OutPath) } } $res } function ConvertFrom-ChromeTime { Param( [Parameter(Mandatory=$true)][string]$ChromeTime ) <# This is a helper function to convert a string (Chrome time) to a [datetime] #> $chrome_normalized = $ChromeTime.PadRight(18,"0") if ($chrome_normalized -as [int64]) { [datetime]::FromFileTime($chrome_normalized) } } function ConvertTo-ChromeTime { Param( [Parameter(Mandatory=$true)][datetime]$RealTime ) <# This is a helper function to convert a [datetime] to a 17-digit FileTime (Chrome time) #> return $RealTime.ToFileTime().ToString().Substring(0,17) } function New-ChromeFolder { Param( [Parameter(Mandatory=$true)][int]$FolderID, [Parameter(Mandatory=$true)][string]$FolderName, [Parameter(Mandatory=$false)][string]$ChromeTimeAdded, [Parameter(Mandatory=$false)][string]$ChromeTimeModified ) <# This is a helper function to create a folder object for a Chrome hierarchy #> $res = New-Object ChromeBookmarkFolder($FolderID,$FolderName) if ([string]::IsNullOrWhitespace($ChromeTimeAdded)) { $res.date_added = ConvertTo-ChromeTime -RealTime (Get-Date) } else { $res.date_added = $ChromeTimeAdded } if ([string]::IsNullOrWhitespace($ChromeTimeModified)) { $res.date_modified = ConvertTo-ChromeTime -RealTime (Get-Date) } else { $res.date_modified = $ChromeTimeModified } return $res } function New-ChromeItem { Param( [Parameter(Mandatory=$true)][int]$ItemID, [Parameter(Mandatory=$true)][string]$ItemName, [Parameter(Mandatory=$true)][string]$ItemURI, [Parameter(Mandatory=$false)][string]$ChromeTimeAdded ) <# This is a helper function to create a bookmark object for a Chrome hierarchy #> $res = New-Object ChromeBookmarkItem($ItemID,$ItemName,$ItemURI) if ([string]::IsNullOrWhitespace($ChromeTimeAdded)) { $res.date_added = ConvertTo-ChromeTime -RealTime (Get-Date) } else { $res.date_added = $ChromeTimeAdded } return $res } function Get-ChromeLastID { Param( [Parameter(Mandatory=$true)][object]$ChromeStructure ) <# This is a helper function to find the highest ID in a Chrome hierarchy #> function Get-ChromeBranchLastID { Param( [Parameter(Mandatory=$true)][object]$Branch ) $i = $Branch.id -as [int] if ($Branch.type -eq "folder") { foreach ($sub in $Branch.children) { if ($sub.id -gt $i) { $i = $sub.id } if ($sub.type -eq "folder") { $j = Get-ChromeBranchLastID $sub if ($j -gt $i) { $i = $j } } } } $i } $maxid = -1 foreach ($struct in $ChromeStructure.GetEnumerator()) { $strmaxid = Get-ChromeBranchLastID $struct.Value if ($strmaxid -gt $maxid) { $maxid = $strmaxid } } $maxid } function Process-ChromeBookmarkBranch { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][object]$ChromeStructure ) <# This is a helper function to recursively process a branch of a Chrome folder structure #> $res = $null if ($ChromeStructure.type -eq 'folder') { $res = New-ChromeFolder -FolderID $ChromeStructure.id -FolderName $ChromeStructure.name foreach ($csi in $ChromeStructure.children) { if ($csi.type -eq 'url') { $res.children += New-ChromeItem -ItemID $csi.id -ItemName $csi.name -ItemURI $csi.url -ChromeTimeAdded $csi.date_added } elseif ($csi.type -eq 'folder') { $res.children += Process-ChromeBookmarkBranch -ChromeStructure $csi } } } elseif ($ChromeStructure.type -eq 'url') { Write-Warning ($ModuleMessages.pcb001 -f $ChromeStructure.name) } else { Write-Warning ($ModuleMessages.pcb002 -f $ChromeStructure.type) } $res } function Import-ChromeBookmarkFile { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][string]$FilePath ) <# This is a helper function to read the Chrome XML file into a hashtable of Chrome Structure branches #> $res = $null if (Test-Path $FilePath -PathType Leaf) { try { $bmstruct = Get-Content -Path $FilePath | ConvertFrom-Json -EA Stop } catch { Write-Warning ($ModuleMessages.icf001 -f $_.Exception.Message) } if ($bmstruct.Version -eq 1) { $res = @{} foreach ($root in $bmstruct.roots.PSObject.Properties) { $res.Add($root.Name, (Process-ChromeBookmarkBranch -ChromeStructure $root.Value)) } } else { Write-Warning $ModuleMessages.icf002 } } else { Write-Warning ($ModuleMessages.icf003 -f $FilePath) } $res } function Read-ChromeBookmarkBranch { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][object]$Branch, [Parameter(Mandatory=$false)][object]$Parent, [Parameter(Mandatory=$false)][bool]$ProcessFolders ) <# This is a helper function to recursively convert a folder branch of a Chrome data structure to BrowserBookmark objects, adding to the Path property as we go deeper #> if ($Parent) { $Parent = "$Parent\" } $BranchPath = "$Parent$($Branch.name)" Write-Verbose ($ModuleMessages.rcb001 -f $BranchPath) if ($Branch.children.Count -gt 0) { foreach ($child in $Branch.children) { if ($child.type -eq "Folder") { Write-Verbose ($ModuleMessages.rcb002 -f $child.name) Read-ChromeBookmarkBranch -Branch $child -Parent $BranchPath -ProcessFolders $ProcessFolders } else { Write-Verbose ($ModuleMessages.rcb003 -f $child.name) $bmk_ok = $true $bmk_obj = New-Object BrowserBookmark ('chrome') $bmk_obj.DateAdded = ConvertFrom-ChromeTime $child.date_added $bmk_obj.Title = $child.name $bmk_obj.URL = $child.url $bmk_obj.BMKGuid = $child.guid if ($ProcessFolders) { $bmk_obj.Path = $BranchPath } $bmk_obj } } } } function Export-ChromeBookmarkFile { Param( [Parameter(Mandatory=$true)][object]$ChromeStructure ) <# This is a helper function to convert a Chrome data structure back to correct Chrome XM #> $output = [ordered]@{'checksum' = ''; 'roots' = @{}} foreach ($root in $ChromeStructure.GetEnumerator()) { $output.roots.Add($root.Name, $root.Value) } $output.Add('version',1) $output | ConvertTo-Json -Depth 100 } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Get-ChromeBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$SourceFile, [Parameter(Mandatory=$false)][switch]$ExcludeFolders ) $bm_file = $null $bm_struct = $null if ($DefaultProfile) { $bm_file = Get-ChromeBookmarkLocation -DefaultProfile } else { $bm_file = Get-ChromeBookmarkLocation -SourceFile $SourceFile } if ($bm_file) { $bm_struct = Import-ChromeBookmarkFile -FilePath $bm_file } if ($bm_struct) { Write-Verbose ($ModuleMessages.gcb001 -f $bm_file) if ($ExcludeFolders) { $ProcessFolders = $false } else { $ProcessFolders = $true } foreach ($root in $bm_struct.GetEnumerator()) { Read-ChromeBookmarkBranch -Branch $root.Value -Parent "" -ProcessFolders $ProcessFolders } } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Add-ChromeBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)][object[]]$Bookmark, [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFile, [Parameter(Mandatory=$false)][switch]$ExcludeFolders, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) Begin { $out = New-Object BookmarkOperationResult ('chrome') $new_file = $false if ($DefaultProfile) { $tgt_file = Get-ChromeBookmarkLocation -DefaultProfile } else { $tgt_file = Get-ChromeBookmarkLocation -SourceFile $TargetFile if (!$tgt_file) { Write-Verbose ($ModuleMessages.acb001 -f $TargetFile) try { Copy-Item -Path "$PSScriptRoot\lib\chrome.json" -Destination $TargetFile -Force -EA Stop $tgt_file = $TargetFile } catch { Write-Warning ($ModuleMessages.acb002 -f $_.Exception.Message) } } } if ($tgt_file) { $bk_structure = Import-ChromeBookmarkFile $tgt_file $out.TargetPath = $tgt_file $newid = (Get-ChromeLastID -ChromeStructure $bk_structure) + 1 Write-Verbose ($ModuleMessages.acb003 -f $newid) $existing_bookmarks = Get-ChromeBookmark -SourceFile $tgt_file } else { break } if ($existing_bookmarks) { Write-Verbose ($ModuleMessages.acb004 -f $existing_bookmarks.Count) $existing_urls = $existing_bookmarks | Select-Object -ExpandProperty url } else { Write-Verbose $ModuleMessages.acb005 $existing_urls = @() } } Process { foreach ($bm in $Bookmark) { if ([string]::IsNullOrWhiteSpace($bm.url)) { Write-Verbose $ModuleMessages.acb006 continue } elseif ($existing_urls -contains $bm.url) { Write-Verbose ($ModuleMessages.acb007 -f $bm.url) $out.BookmarksSkipped++ continue } $newitem = New-ChromeItem -ItemID $newid -ItemName $bm.Title -ItemURI $bm.URL -ChromeTimeAdded (ConvertTo-ChromeTime -RealTime $bm.DateAdded) $newid++ if ([string]::IsNullOrWhiteSpace($bm.Path) -or $ExcludeFolders) { Write-Verbose $ModuleMessages.acb008 $bk_structure['bookmark_bar'].children += $newitem $out.BookmarksAdded++ } elseif (!([string]::IsNullOrWhiteSpace($bm.Path))) { $path_parts = $bm.Path -split "\\" $cur_parent = $bk_structure['bookmark_bar'] foreach ($pp in $path_parts) { $pp_fld = $cur_parent.children.Where({($_.name -eq $pp) -and ($_.type = 'folder')})[0] if ($pp_fld) { $cur_parent = $pp_fld } else { Write-Verbose ($ModuleMessages.acb009 -f $pp,$cur_parent.name) $cur_parent.children += (New-ChromeFolder -FolderID $newid -FolderName $pp) $newid++ $cur_parent = $cur_parent.children.Where({($_.name -eq $pp) -and ($_.Type = 'folder')})[0] } } if ($null -ne $cur_parent) { Write-Verbose ($ModuleMessages.acb010 -f $cur_parent.name) $cur_parent.children += $newitem $out.BookmarksAdded++ $newid++ } } } } End { Export-ChromeBookmarkFile -ChromeStructure $bk_structure | Set-Content $tgt_file -Force $out } } # .ExternalHelp PSBrowserBookmarks.psm1-help.xml function Remove-ChromeBookmark { [CmdletBinding()] Param( [Parameter(Mandatory=$true)][ValidateScript({if ($_ -as [uri]) { $true }})][string]$URL, [Parameter(Mandatory=$false,ParameterSetName='Default')][switch]$DefaultProfile, [Parameter(Mandatory=$false,ParameterSetName='Named File')][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$TargetFile, [Parameter(Mandatory=$false)][switch]$BackupTarget, [Parameter(Mandatory=$false)][ValidateScript({$null -ne ($_ -as [System.IO.FileInfo])})][string]$BackupPath = (Get-Location -PSProvider FileSystem) ) Write-Warning "Remove-ChromeBookmark has not been implemented yet" $out = New-Object BookmarkOperationResult ('chrome') return $out } #endregion #region init Import-LocalizedData -BindingVariable "ModuleMessages" if (!(Test-Path "$PSScriptRoot\lib\System.Data.SQLite.dll")) { Write-Warning $ModuleMessages.init01 } else { New-Variable -Scope Script -Name SQLiteLibPath -Value "$PSScriptRoot\lib\System.Data.SQLite.dll" } #endregion |