Public/Reset-GkUserPassword.ps1

function Reset-GkUserPassword {
    <#
    .SYNOPSIS
        Reset a user's password, optionally forcing a change at next sign-in.

    .DESCRIPTION
        Calls PATCH /users/{id} with a passwordProfile. By default a strong random password is
        generated and returned once on the result object, and the user must change it at next
        sign-in — the safe default for a helpdesk reset.

        The generated password is returned as plain text on the result but is NOT shown by the
        default view, so it does not splash across the screen or into a transcript. Capture it
        deliberately with Select-Object Password, the same way Reset-GkAppCredential surfaces a
        new client secret.

        State-changing: supports -WhatIf / -Confirm and prompts by default.

        Requires User-PasswordProfile.ReadWrite.All — the narrow permission Graph documents for the
        passwordProfile property — or a broader user write scope. The signed-in admin also needs a
        role that outranks the target: User Administrator resets non-admins, and Privileged
        Authentication Administrator is required to reset an administrator.

    .PARAMETER UserId
        One or more user object IDs or userPrincipalNames. Accepts pipeline input, including by the
        UserPrincipalName / Id property so report output can be piped in.

    .PARAMETER NewPassword
        Use this password instead of generating one. It must satisfy the tenant password policy.
        Takes a SecureString, the PowerShell convention for a credential you already hold:
        ConvertTo-SecureString 'value' -AsPlainText -Force.

    .PARAMETER NoForceChange
        Do not require the user to change the password at next sign-in. Off by default because a
        helpdesk-set password that persists is a standing credential.

    .EXAMPLE
        Reset-GkUserPassword -UserId ada@contoso.com | Select-Object UserId, Password

        Reset with a generated password and capture it (it cannot be retrieved again).

    .EXAMPLE
        Reset-GkUserPassword -UserId ada@contoso.com -WhatIf

        Preview the reset without changing anything.

    .EXAMPLE
        Get-GkRiskyUser -RiskLevel high | Reset-GkUserPassword -Confirm:$false |
            Where-Object Outcome -eq 'Failed'

        Reset every high-risk user and inspect the failures.

    .OUTPUTS
        PSGraphKit.PasswordResetResult
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType('PSGraphKit.PasswordResetResult')]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('UserPrincipalName', 'Id')]
        [string[]] $UserId,

        [securestring] $NewPassword,

        [switch] $NoForceChange
    )

    begin {
        Test-GkConnection -FunctionName 'Reset-GkUserPassword' -Caller $PSCmdlet | Out-Null
        $forceChange = -not $NoForceChange
    }

    process {
        foreach ($uid in $UserId) {
            if ([string]::IsNullOrWhiteSpace($uid)) { continue }

            $action = if ($forceChange) { 'Reset password (change required at next sign-in)' } else { 'Reset password' }
            if (-not $PSCmdlet.ShouldProcess($uid, $action)) { continue }

            # Generate per user, so a bulk reset does not hand every account the same password.
            # The local is deliberately named differently from the parameter: a case-insensitive
            # match would be coerced back to [securestring] on assignment.
            if ($NewPassword) {
                $plainPassword = [System.Net.NetworkCredential]::new('', $NewPassword).Password
            }
            else {
                $plainPassword = New-GkPassword
            }

            $enc = [uri]::EscapeDataString($uid)
            $outcome = 'Reset'
            $errMsg = $null
            try {
                Invoke-GkGraphRequest -Method PATCH -Uri "/users/$enc" -CallerFunction 'Reset-GkUserPassword' -Body @{
                    passwordProfile = @{
                        password                             = $plainPassword
                        forceChangePasswordNextSignIn        = [bool]$forceChange
                    }
                } | Out-Null
            }
            catch {
                $outcome = 'Failed'
                $errMsg = $_.Exception.Message
                $plainPassword = $null
                Write-Warning "Failed to reset password for '$uid': $errMsg"
            }

            [pscustomobject]@{
                PSTypeName            = 'PSGraphKit.PasswordResetResult'
                UserId                = $uid
                Action                = 'ResetPassword'
                Outcome               = $outcome
                ForceChangeNextSignIn = [bool]$forceChange
                Password              = $plainPassword
                Error                 = $errMsg
            }
        }
    }
}