functions/Set-PasswordStatePassword.ps1

function Set-PasswordStatePassword {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'API requires password be passed as plain text')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '', Justification = 'API requires password be passed as plain text')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'Needed for backward compatability')]
    [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'All')]
    param (
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, Position = 0)][Nullable[System.Int32]]$PasswordID,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 1)][ValidateLength(1, 255)][string]$Title,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 2)][ValidateLength(1, 255)][string]$Username,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "Password", Position = 0)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 9)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 9)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Heartbeat", Position = 9)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "HeartbeatSchedule", Position = 9)]
        [ValidateScript( {
                # Exclude german umlauts and other latin/non-latin diacritics or invalid characters that the api does not understand.
                $InvalidChars = 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
                $regex = [Regex]::Escape($InvalidChars)
                $regex = "[$regex]"
                $Invalid = [Regex]::Matches($_, $regex, 'IgnoreCase') | Select-Object -ExpandProperty Value | Sort-Object -Unique
                if ($null -ne $Invalid) {
                    throw "ERROR: The specified password contains the following illegal characters: '$Invalid'. Please do not use the characters '$InvalidChars' in your password since the api does not understand these characters."
                }
                return $true
            })]
        [string]$Password,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 4)][ValidateLength(0, 255)][string]$Description,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "GeneratePassword", Position = 0)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 10)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 10)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Heartbeat", Position = 10)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "HeartbeatSchedule", Position = 10)]
        [switch]$GeneratePassword,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 6)][string]$Notes,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 7)][ValidateLength(0, 255)][string]$Url,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 8)][ValidateLength(0, 50)][string]$AccountType,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 9)][AllowNull()][Nullable[System.Int32]]$AccountTypeID = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 10)][string]$GenericField1 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 11)][string]$GenericField2 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 12)][string]$GenericField3 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 13)][string]$GenericField4 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 14)][string]$GenericField5 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 15)][string]$GenericField6 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 16)][string]$GenericField7 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 17)][string]$GenericField8 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 18)][string]$GenericField9 = $null,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 19)][string]$GenericField10 = $null,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 20)][switch]$GenerateGenFieldPassword,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "Reset", Position = 0)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "Password", Position = 1)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "GeneratePassword", Position = 1)]
        [switch]$PasswordResetEnabled,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "ResetSchedule", Position = 1)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 1)]
        [switch]$EnablePasswordResetSchedule,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "ResetSchedule", Position = 2)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 2)]
        [ValidateScript( {
                if ($_ -notmatch '^(([0-1][0-9]|[2-2][0-3]):([0-5][0-9]))$') {
                    throw "Given PasswordResetSchedule '$_' is not a valid Schedule! Please specify a correct schedule in (Date)Time Format, e.g. '23:10', '00:15', '09:00' from 00:00-23:59."
                }
                else {
                    $true
                }
            })]
        [string]$PasswordResetSchedule,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 21)][Nullable[System.Int32]]$AddDaysToExpiryDate = $null,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 22)][Nullable[System.Int32]]$ScriptID = $null,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 23)][Nullable[System.Int32]]$PrivilegedAccountID = $null,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $true, ParameterSetName = "Heartbeat", Position = 0)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 3)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 3)]
        [switch]$HeartbeatEnabled,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "HeartbeatSchedule", Position = 0)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Heartbeat", Position = 1)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 5)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 5)]
        [ValidateScript( {
                if ($_ -notmatch '^(([0-1][0-9]|[2-2][0-3]):([0-5][0-9]))$') {
                    throw "Given HeartbeatSchedule '$_' is not a valid Schedule! Please specify a correct schedule in (Date)Time Format, e.g. '23:10', '00:15', '09:00' from 00:00-23:59."
                }
                else {
                    $true
                }
            })]
        [string]$HeartbeatSchedule,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Heartbeat", Position = 2)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 5)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 5)]
        [Nullable[System.Int32]]$ValidationScriptID = $null,
        [ValidateLength(1, 200)]
        [string]$HostName,
        [ValidateLength(1, 50)]
        [string]$ADDomainNetBIOS,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Heartbeat", Position = 3)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "Reset", Position = 6)]
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, ParameterSetName = "ResetSchedule", Position = 6)]
        [switch]$ValidateWithPrivAccount,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 24)]
        [ValidateScript( {
                # The dates for the ExpiryDate needs to be culture aware, so we cannot validate a specific date format.
                function isDate([string]$StrDate) {
                    [boolean]($StrDate -as [DateTime])
                }
                if ([string]::IsNullOrEmpty($_)) {
                    $true
                }
                elseif (!(isDate $_)) {
                    throw "Given ExpiryDate '$_' is not a valid Date format. Also, please specify the ExpiryDate in the date format that you have chosen in 'System Settings - miscellaneous - Default Locale' (Default: 'YYYY-MM-DD')."
                }
                else {
                    $true
                }
            })]
        [string]$ExpiryDate,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 25)][switch]$AllowExport,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 25)][ValidateLength(0, 200)][string]$WebUser_ID,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 25)][ValidateLength(0, 200)][string]$WebPassword_ID,
        [parameter(ValueFromPipelineByPropertyName, Mandatory = $false, Position = 26)][string]$Reason
    )

    begin {
        # Import PasswordState Environment for validation of PasswordsInPlainText setting
        $PWSProfile = Get-PasswordStateEnvironment
        # Add a reason to the audit log if specified
        If ($Reason) {
            $headerreason = @{"Reason" = "$reason" }
            $parms = @{ExtraParams = @{"Headers" = $headerreason } }
        }
        else { $parms = @{ } }
    }

    process {
        if ($PasswordID) {
            try {
                $result = Get-PasswordStatePassword -PasswordID $PasswordID @headerreason -ErrorAction Stop
            }
            catch {
                throw "Given PasswordID '$PasswordID' not found"
            }
            Write-PSFMessage -Level Verbose -Message "Password with PasswordID '$PasswordID' found. Updating password '$($result.title)'..."
        }
        else {
            throw "Please apply '-PasswordID'! PasswordID is needed to update passwords."
        }
        if ($PSCmdlet.ShouldProcess("PasswordID:$($result.PasswordID) Title:$($result.Title)")) {
            # Loop through each of the bound parameters and set the updated value on the object.
            foreach ($i in $PSBoundParameters.Keys) {
                # Replace Result property with that of the bound parameter
                $NotProcess = "PreventAuditing", "Reason", "verbose", "erroraction", "debug", "whatif", "confirm"
                if ($NotProcess -notcontains $i) {
                    if ($i -eq "Password" -and $PSBoundParameters.$($i) -eq "EncryptedPassword") {
                        $result.DecryptPassword()
                    }
                    else {
                        # if existing result object does contain a specified property replace it with the specified property value
                        if (Get-Member -InputObject $result -MemberType Property -Name $i) {
                            $result.$($i) = $PSBoundParameters.$($i)
                        }
                        # if existing result object does NOT contain a specified property, create a new member and add the new value
                        else {
                            # if specified property type is a switch parameter, we need to specified the value of sub property IsPresent
                            if ($PSBoundParameters.$($i).GetType().Name -eq "SwitchParameter") {
                                try {
                                    $result | Add-Member -MemberType NoteProperty -Name $i -Value $PSBoundParameters.$($i).IsPresent -ErrorAction Stop
                                }
                                catch {
                                    throw $_.Exception.Message
                                }
                            }
                            else {
                                try {
                                    $result | Add-Member -MemberType NoteProperty -Name $i -Value $PSBoundParameters.$($i) -ErrorAction Stop
                                }
                                catch {
                                    throw $_.Exception.Message
                                }
                            }
                        }
                    }
                }
            }
            # Change in v9: If password of existing object should not be changed (-Password or -GeneratePassword not given), we need to remove it from the result that should be send to the api as change request
            if ($PSBoundParameters.Keys -notcontains "Password") {
                $result = $result | Select-Object -Property * -ExcludeProperty Password
            }
            # Store in a new variable and remove all null values as password state doesn't like nulls.
            $body = $result
            # Initialize array for the select statement later
            $selections = @()
            # Get all properties from the object.
            $properties = ($body | Get-Member -Force | Where-Object { $_.MemberType -eq "Property" -or $_.MemberType -eq "NoteProperty" }).Name
            # Get only those properties which aren't empty and add them to our selection array.
            foreach ($item in $properties) {
                if ($body.$($item) -notlike $null) {
                    $selections += $item
                }
            }
            # Update body variable to contain only the properties with data.
            $body = $body | Select-Object $selections
            # Write back to password state.
            $uri = "/api/passwords"
            $body = "$($body | ConvertTo-Json)"
            try {
                $output = Set-PasswordStateResource -uri $uri -body $body @parms -ErrorAction Stop
            }
            catch {
                throw $_.Exception.Message
            }
            if ($output) {
                if ((Get-Member -InputObject $output -MemberType NoteProperty -Name Status) -and (Get-Member -InputObject $output -MemberType NoteProperty -Name CurrentPassword) -and (Get-Member -InputObject $output -MemberType NoteProperty -Name NewPassword)) {
                    try {
                        [PasswordResetResult]$output = $output
                    }
                    catch {
                        throw $_.Exception
                    }
                    if (!$PWSProfile.PasswordsInPlainText) {
                        $output.CurrentPassword = [EncryptedPassword]$output.CurrentPassword
                        $output.NewPassword = [EncryptedPassword]$output.NewPassword
                    }
                    return
                }
                try {
                    [PasswordResult]$output = $output
                }
                catch {
                    throw $_.Exception
                }
                if (!$PWSProfile.PasswordsInPlainText) {
                    $output.Password = [EncryptedPassword]$output.Password
                }
            }
        }
    }

    end {
        if ($output) {
            return $output
        }
    }
}

Set-Alias -Name Update-PasswordStatePassword -Value Set-PasswordStatePassword -Force