support Posted December 14, 2023 Share Posted December 14, 2023 Purpose: Passwordstate currently has a built in feature to import KeePass data into a folder structure with shared lists, but currently it's not possible to import into Private Lists within the User Interface. This script below will allow you to import into Private Lists for a single user. Note: This script is using the Windows API, so you must be logged into a Windows machine, with a domain account that has a login into Passwordstate. All Passwords Lists will be created as Private lists, and only that user will see the imported Folder and Password Lists. Requirements: You will need to create a Password List Template with the settings below. Take note of the Password List Template ID when creating this List. How to Run the Script: In a section below is the entire contents of the script you'll need to open in Powershell ISE. You'll need to modify the top three lines of the script with the relevant information to your environment, Passwordstate URL, path to KeePass XML file, and the Password List Template ID for the template you created above: Results: After running the script, you will see a new folder in the root of Passwords Home, which will be named after the exact name of your KeePass database. All nested Lists will be Private, and only the user who ran the script will see this folder structure and nested Password Lists/Passwords. Powershell Script Contents: $global:PasswordstateURL = "https://support.clickdemo.com" $KeePassFile = "C:\Data\Import\Keepass\Database.xml" $global:PasswordstateTemplateID = "79" add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::TLS12 switch -regex ($global:PasswordstateLinkToTemplate) { "(Y(e|es)?|True)" {$global:PasswordstateLinkToTemplate = $true; break} default {$global:PasswordstateLinkToTemplate = $false; break} } switch -regex ($global:PasswordstateTakePermsFromTemplate) { "(Y(e|es)?|True)" {$global:PasswordstateTakePermsFromTemplate = $true;break} default { $global:PasswordstateTakePermsFromTemplate = $false break } } #region Passwordstate Functions Function CreatePasswordstateFolder() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Name, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Description, [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$ParentFolderID ) Begin { } Process { $Body = @{ FolderName = $Name Description = $Description NestUnderFolderID = $ParentFolderID } $jsonBody = $Body | ConvertTo-Json $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/folders" $result = Invoke-Restmethod -Method POST -Uri $PasswordstateURLFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials $output = $result.FolderID } End { Write-Output $output } } Function CreatePasswordstatePasswordlist() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Name, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Description, [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$ParentFolderID ) Begin { } Process { $Body = @{ PasswordList = $Name Description = $Description ApplyPermissionsForUserID = "$env:USERDOMAIN\$env:USERNAME" Permission = if($global:PasswordstateTakePermsFromTemplate -eq $False) {"A"} else {$null} CopySettingsFromTemplateID = $global:PasswordstateTemplateID LinkToTemplate = $global:PasswordstateLinkToTemplate PrivatePasswordList = "true" NestUnderFolderID = $ParentFolderID SiteID = "0" } $jsonBody = $Body | ConvertTo-Json $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/passwordlists" $result = Invoke-Restmethod -Method POST -Uri $PasswordstateURLFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials $output = $result.PasswordListID } End { Write-Output $output } } Function AddPasswordstatePasswordToPasswordlist() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [Int]$PasswordListID, [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Title, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Username, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Password, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Description, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Notes, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$URL, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$OTP ) Begin { If ($Notes) { $Notes = ConvertTextToHtml -text $Notes } } Process { $Body = @{ PasswordListID = $PasswordListID Title = $Title Username = $Username Password = $Password Notes = $Notes URL = $URL OTPUri = $OTP Description = $Description } $jsonBody = $Body | ConvertTo-Json $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/passwords" #We are calling the WinAPI here, as you cannot add in passwords into a Private Password List wiht a System Wide API Key. This script will not work when running inside Passwordstate, it must be run outside of Passwordstate, in Powershell, under the identity of the user who wants to do the import. Must be an AD account. $result = Invoke-Restmethod -Method Post -Uri $PasswordstateUrlFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials $output = $result.PasswordID } End { Write-Output $output } } #endregion #region GeneralFunctions Function ConvertTextToHtml() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$text ) Begin { } Process { $html = $($text -replace "\n", "<br>") } End { Write-Output $html } } Function CreateFoldersRecursive() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$PasswordstateParentFolderID, [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [Object]$KeePassGroup, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Tree ) Begin { $Tree = $Tree + "/" + $KeePassGroup.Name } Process { $Tree = $Tree + "/" + $KeePassGroup.Name #Check if folder has children; if yes, create folder, else create password list (terminal-folder) If ($KeePassGroup.Group) { #Create subfolder in Passwordstate $passwordStateNewFolderID = CreatePasswordstateFolder -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $PasswordstateParentFolderID #Check if there are passwords in this folder; if yes, create a password list beneath the folder with the same name and all its passwords If ($KeePassGroup.Entry) { $passwordStateNewPasswordlistID = CreatePasswordstatePasswordlist -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $passwordStateNewFolderID AddPasswordsToPasswordlist -PasswordListID $passwordStateNewPasswordlistID -KeePassEntries $KeePassGroup.Entry -Tree $Tree } #do so for all SubFolders ForEach ($group in $KeePassGroup.Group) { CreateFoldersRecursive -PasswordstateParentFolderID $passwordStateNewFolderID -KeePassGroup $group -Tree $Tree } } Else { $passwordStateNewPasswordlistID = CreatePasswordstatePasswordlist -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $PasswordstateParentFolderID If ($KeePassGroup.Entry) { AddPasswordsToPasswordlist -PasswordListID $passwordStateNewPasswordlistID -KeePassEntries $KeePassGroup.Entry -Tree $Tree } } } } Function AddPasswordsToPasswordlist() { Param ( [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$PasswordListID, [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [Object]$KeePassEntries, [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)] [String]$Tree ) Begin { } Process { ForEach($KeePassEntry in $KeePassEntries) { $Title = ($KeePassEntry.String | Where-Object { $_.Key -eq "Title" }).Value $Username = ($KeePassEntry.String | Where-Object { $_.Key -eq "Username" }).Value $Password = ($KeePassEntry.String | Where-Object { $_.Key -eq "Password" }).Value.'#text' $Description = ($KeePassEntry.String | Where-Object { $_.Key -eq "Notes" }).Value $URL = ($KeePassEntry.String | Where-Object { $_.Key -eq "URL" }).Value $Secret = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Secret-Base32" }).Value.'#text' if (($Title -eq "") -AND ($Username -ne "")) { $Title = $Username } if ($Title -eq "") { $Title = "no name" } if (!$Title){ $Title = "no name" } if (!(Test-Path variable:secret) -or ($Secret -eq '' -or $Secret -eq $null)) { # Set OTP to be empty as there is no data to import $OTP = '' } else { # As we have detected a "Secret" in this KeePass record, we now build the OTP string. Algorithm, Digits and Period values may not exist in KeePass XML file, so we will need to set those with default velues $Issuer = $Title #Keepass does not store Issuer for the OTP entries, so we must assume the Title of the KeePass Record is the Issuer $Algorithm = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Algorithm" }).Value if ($Algorithm -eq ''){$Algorithm = "HMAC-SHA-1"} $Digits = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Length" }).Value if ($Digits -eq ''){$Digits = "6"} $Period = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Period" }).Value if ($Period -eq ''){$Period = "30"} $OTP = "otpauth://totp/$Issuer" + "?secret=$Secret" + "&algorithm=$Algorithm" + "&digits=$Digits" + "&period=$Period" } $result = AddPasswordstatePasswordToPasswordlist -PasswordListID $PasswordListID -Title $Title -Username $Username -Password $Password -Notes $Description -URL $URL -OTP $OTP } } End { } } #endregion try { [xml]$XmlDocument = Get-Content -Path $KeePassFile $KeePassRoot = $XmlDocument.ChildNodes.Root.Group CreateFoldersRecursive -PasswordstateParentFolderID 0 -KeePassGroup $KeePassRoot #CreateFoldersRecursive -PasswordstateParentFolderID $global:PasswordstateImportFolderID -KeePassGroup $KeePassRoot Write-Output "Success" } catch { switch -wildcard ($error[0].Exception.ToString().ToLower()) { default { Write-Output "Failed to import passwords. Error = " $error[0].Exception } } } Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now