Public/User/Set-VergeUser.ps1

function Set-VergeUser {
    <#
    .SYNOPSIS
        Modifies an existing user in VergeOS.

    .DESCRIPTION
        Set-VergeUser updates the settings of an existing user account.
        Only the specified parameters will be modified.

    .PARAMETER Name
        The username of the user to modify.

    .PARAMETER Key
        The unique key (ID) of the user to modify.

    .PARAMETER User
        A user object from Get-VergeUser to modify.

    .PARAMETER Password
        A new password for the user. Can be a SecureString or plain text string.

    .PARAMETER DisplayName
        The display name for the user.

    .PARAMETER Email
        The email address for the user.

    .PARAMETER Enabled
        Enable or disable the user account.

    .PARAMETER RequirePasswordChange
        Require the user to change their password at next login.

    .PARAMETER PhysicalAccess
        Enable or disable console/SSH access for this user.

    .PARAMETER TwoFactorEnabled
        Enable or disable two-factor authentication for this user.

    .PARAMETER TwoFactorType
        The type of 2FA to use. Valid values: Email, Authenticator.

    .PARAMETER TwoFactorSetupRequired
        Require the user to set up 2FA at next login.

    .PARAMETER SSHKeys
        SSH public keys for the user (replaces existing keys).

    .PARAMETER PassThru
        Return the modified user object.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Set-VergeUser -Name "jsmith" -Email "john.smith@company.com"

        Updates the email address for user jsmith.

    .EXAMPLE
        Set-VergeUser -Name "jsmith" -Password (ConvertTo-SecureString "NewPass123!" -AsPlainText -Force) -RequirePasswordChange

        Changes the user's password and requires them to change it at next login.

    .EXAMPLE
        Get-VergeUser -Name "jsmith" | Set-VergeUser -TwoFactorEnabled $true -TwoFactorType Authenticator -PassThru

        Enables authenticator-based 2FA for the user.

    .EXAMPLE
        Set-VergeUser -Name "jsmith" -Enabled $false

        Disables the user account.

    .OUTPUTS
        None by default. Verge.User when -PassThru is specified.

    .NOTES
        Use Get-VergeUser to retrieve users.
        Use Enable-VergeUser and Disable-VergeUser for simple enable/disable operations.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory, ParameterSetName = 'ByKey')]
        [int]$Key,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByObject')]
        [PSTypeName('Verge.User')]
        [PSCustomObject]$User,

        [Parameter()]
        [object]$Password,

        [Parameter()]
        [string]$DisplayName,

        [Parameter()]
        [ValidatePattern('^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@([a-zA-Z0-9][a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])+(\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])*$', ErrorMessage = 'Invalid email address format')]
        [string]$Email,

        [Parameter()]
        [bool]$Enabled,

        [Parameter()]
        [bool]$RequirePasswordChange,

        [Parameter()]
        [bool]$PhysicalAccess,

        [Parameter()]
        [bool]$TwoFactorEnabled,

        [Parameter()]
        [ValidateSet('Email', 'Authenticator')]
        [string]$TwoFactorType,

        [Parameter()]
        [bool]$TwoFactorSetupRequired,

        [Parameter()]
        [string[]]$SSHKeys,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Map friendly names to API values
        $twoFactorTypeMap = @{
            'Email'         = 'email'
            'Authenticator' = 'authenticator'
        }
    }

    process {
        # Resolve user key
        $userKey = $null
        $userName = $null

        switch ($PSCmdlet.ParameterSetName) {
            'ByKey' {
                $userKey = $Key
                # Get the user name for display
                $existingUser = Get-VergeUser -Key $Key -Server $Server -ErrorAction SilentlyContinue
                $userName = if ($existingUser) { $existingUser.Name } else { "Key $Key" }
            }
            'ByName' {
                $existingUser = Get-VergeUser -Name $Name -Server $Server -ErrorAction SilentlyContinue
                if (-not $existingUser) {
                    Write-Error -Message "User not found: $Name" -ErrorId 'UserNotFound' -Category ObjectNotFound
                    return
                }
                $userKey = $existingUser.Key
                $userName = $Name
            }
            'ByObject' {
                $userKey = $User.Key
                $userName = $User.Name
                if (-not $Server -and $User._Connection) {
                    $Server = $User._Connection
                }
            }
        }

        if (-not $userKey) {
            Write-Error -Message "Could not resolve user key" -ErrorId 'UserNotFound' -Category ObjectNotFound
            return
        }

        # Build request body with only changed parameters
        $body = @{}

        if ($Password) {
            # Convert SecureString password if needed
            $plainPassword = if ($Password -is [System.Security.SecureString]) {
                [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
                    [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
                )
            }
            else {
                $Password.ToString()
            }
            $body['password'] = $plainPassword
        }

        if ($PSBoundParameters.ContainsKey('DisplayName')) {
            $body['displayname'] = $DisplayName
        }

        if ($PSBoundParameters.ContainsKey('Email')) {
            $body['email'] = $Email.ToLower()
        }

        if ($PSBoundParameters.ContainsKey('Enabled')) {
            $body['enabled'] = $Enabled
        }

        if ($PSBoundParameters.ContainsKey('RequirePasswordChange')) {
            $body['change_password'] = $RequirePasswordChange
        }

        if ($PSBoundParameters.ContainsKey('PhysicalAccess')) {
            $body['physical_access'] = $PhysicalAccess
        }

        if ($PSBoundParameters.ContainsKey('TwoFactorEnabled')) {
            $body['two_factor_authentication'] = $TwoFactorEnabled
        }

        if ($PSBoundParameters.ContainsKey('TwoFactorType')) {
            $body['two_factor_type'] = $twoFactorTypeMap[$TwoFactorType]
        }

        if ($PSBoundParameters.ContainsKey('TwoFactorSetupRequired')) {
            $body['two_factor_setup_next_login'] = $TwoFactorSetupRequired
        }

        if ($PSBoundParameters.ContainsKey('SSHKeys')) {
            $body['ssh_keys'] = if ($SSHKeys -and $SSHKeys.Count -gt 0) {
                $SSHKeys -join "`n"
            } else {
                ''
            }
        }

        # Check if there's anything to update
        if ($body.Count -eq 0) {
            Write-Warning "No parameters specified to update for user '$userName'"
            return
        }

        # Build description of changes
        $changes = ($body.Keys | Where-Object { $_ -ne 'password' }) -join ', '
        if ($body.ContainsKey('password')) {
            $changes = if ($changes) { "$changes, password" } else { 'password' }
        }

        if ($PSCmdlet.ShouldProcess($userName, "Modify User ($changes)")) {
            try {
                Write-Verbose "Updating user '$userName' (Key: $userKey)"
                $response = Invoke-VergeAPI -Method PUT -Endpoint "users/$userKey" -Body $body -Connection $Server

                Write-Verbose "User '$userName' updated successfully"

                if ($PassThru) {
                    # Return the updated user
                    Start-Sleep -Milliseconds 500
                    Get-VergeUser -Key $userKey -Server $Server
                }
            }
            catch {
                throw "Failed to update user '$userName': $($_.Exception.Message)"
            }
        }
    }
}