param ( [switch]$zip, [switch]$encrypt, [switch]$verbose ) # Verbose output if ($verbose) { $VerbosePreference = "continue" } # get the date/time for the back filename $dateTime = Get-Date -format ("yyyyMMdd-HHmmss") # set the binaries path, do not rely on the path variables as they are not hashed $bw = "$PSScriptRoot\lib\bw.exe" $gpg = "$PSScriptRoot\lib\gpg\bin\gpg.exe" $sdelete = "$PSScriptRoot\lib\sdelete.exe" # set bitwarden server to my self hosted instance #& $bw config server https://bitwarden.johnhgaunt.com # begin while loop to login, if login is incorrect, ask user again while ($true) { # ask for api client id/secret and password $clientID = Read-Host "Please enter your Bitwarden API client_id" $env:BW_CLIENTID = "$clientID" $clientSecret = Read-Host -assecurestring "Please enter your bitwarden API client_secret" $clientSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($clientSecret)) $env:BW_CLIENTSECRET = "$clientSecret" # test login & $bw login --apikey --raw $bwStatus = $(ConvertFrom-Json $(& $bw status)) if ($bwStatus."Status" -eq "locked") { # Authentication was successful # start new loop for password unlock while ($true) { $password = Read-Host -assecurestring "Please enter your Bitwarden password" $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)) $sessionKey = $(& $bw unlock $password --raw --nointeraction) # get the bw status to see if the login was successfull and inform user $bwStatus = $(ConvertFrom-Json $(& $bw status --session $sessionKey)) if ($bwStatus."Status" -eq "unlocked") { $username = $bwStatus."userEmail" break } else { # just writing a new line Write-Host " " Write-Warning "Unable to unlock your vault, please try agian." } } break } else { Write-Host " " Write-Warning "Unable to authenticate, please try agian." } } # Export the vault to both CSV and JSON files, this allows best compatibility to import again or switch managers. Write-Host "Exporting vault to both CSV and JSON files." Write-Verbose "Exporting vault to CSV." & $bw export $password --output "$PSScriptRoot\Bitwarden User $username Export $dateTime.csv" --format csv --session $sessionKey # just writing a new line Write-Host " " Write-Verbose "Exporting vault to JSON." & $bw export $password --output "$PSScriptRoot\Bitwarden User $username Export $dateTime.json" --format json --session $sessionKey # just writing a new line Write-Host " " # look for organizations Write-Host "Looking for Organizations..." $organizations = $(ConvertFrom-Json $(& $bw list organizations --session $sessionKey)) Write-Host "Found $(($organizations | Measure-Object).count) Organiztaions." # loop through the found organizations and again export both as CSV and JSON for best compatibility $organizations | ForEach-Object { Write-Host "Exporting organization $($_.name) vault to both CSV and JSON files." Write-Verbose "Exporting organization vault to CSV." & $bw export $password --organizationid $_.id --output "$PSScriptRoot\Bitwarden Organization $($_.name) Export $dateTime.csv" --format csv --session $sessionKey # just writing a new line Write-Host " " Write-Verbose "Exporting organization vault to JSON." & $bw export $password --organizationid $_.id --output "$PSScriptRoot\Bitwarden Organization $($_.name) Export $dateTime.json" --format json --session $sessionKey # just writing a new line Write-Host " " } # find all items with attachments Write-Host "Looking for items with attachments..." $itemsWithAttachments = $((ConvertFrom-Json $(& $bw list items --session $sessionKey)) | Where-Object attachments) Write-Host "Found $(($itemsWithAttachments | Measure-Object).count) items with attachments." # loop through all the items with attachments and download them into a folder with the name of the item Write-Host "Downloading attachments..." $itemsWithAttachments | ForEach-Object { Write-Verbose "Working on item $($_.name) ($($_.id))." $folder="$PSScriptRoot\attachments\$($_.name)" $itemID=$_.id $_.attachments | ForEach-Object { Write-Verbose "Downloading attachment ($($_.id)) with name $($_.fileName) to $folder." & $bw get attachment $_.id --itemid $itemID --output "$folder\$($_.fileName)" --session $sessionKey # just writing a new line Write-Host " " Start-Sleep -Milliseconds 500 } } # zip file name used below $zipFilename = "Bitwarden Backup $dateTime.zip" # if the user wants the export zipped or encrypted if ($zip -or $encrypt) { # zip the export Write-Host "Zipping the backup together..." Compress-Archive -Path $PSScriptRoot\*.csv, $PSScriptRoot\*.json, $PSScriptRoot\attachments -DestinationPath "$PSScriptRoot\$zipFilename" # securely delete the export items with sdelete Write-Host "Securely deleting the exports and attachments..." & $sdelete -s -p 25 $PSScriptRoot\*.csv $PSScriptRoot\*.json $PSScriptRoot\attachments # if encrypting the export if ($encrypt) { # encrypt the zip export with gpg Write-Host "Encrypting the backup zip with your bitwarden password..." & $gpg --no-options --batch --passphrase "$password" --symmetric --cipher-algo AES256 --digest-algo SHA512 --compression-algo Uncompressed --output "$PSScriptRoot\$zipFilename.gpg" "$PSScriptRoot\$zipFilename" # securely delete the zip export with sdelete Write-Host "Securely deleting the zip file..." & $sdelete -p 25 "$PSScriptRoot\$zipFilename" } } # logout of bitwaren to ensure the session is destroyed & $bw logout # remove all the variables Remove-Variable -Name * -ErrorAction SilentlyContinue <# # Restore of attachements cd C:\users\jgaunt\Git\bitwardenbackup\attachments bw status $folders = Get-childItem -Directory $items = bw list items | convertfrom-json $array = @() foreach ($folder in $folders) { foreach ($item in $items) { if ($folder.name -eq $item.name -and $item.type -gt 1) { #$array += bw get item $item.id | convertfrom-json $attachements = Get-ChildItem $folder foreach ($attachement in $attachements) { bw create attachment --file "$($attachement.FullName)" --itemid $item.id } Remove-Item -Recurse $folder } } } #>