Public/reset-password.ps1
|
# reset-password.ps1 # Resets an M365 user's password. Generates a secure random password by default, # or lets the engineer set one manually. Force-change on next login defaults to off. # Requires: Graph (User.ReadWrite.All) if (-not (Get-MgContext)) { Connect-MgGraph -Scopes "User.ReadWrite.All" -ContextScope Process } $upn = Read-Host "User UPN" $user = Get-MgUser -Filter "userPrincipalName eq '$upn'" -Property "Id,DisplayName,UserPrincipalName" -ErrorAction SilentlyContinue if (-not $user) { Write-Host "User not found: $upn" -ForegroundColor Red return } $setOwn = (Read-Host "Set your own password? (y/n)") -eq "y" $forceChange = (Read-Host "Require change on next login? (y/n)") -eq "y" $isGenerated = -not $setOwn $newPassword = $null if ($setOwn) { # Engineer-supplied password: SecureString prompt, converted to plain # only at the point of use and cleared immediately after. Same pattern # as new-user.ps1. Never echoed — the engineer typed it, they know it. $securePw = Read-Host "New password" -AsSecureString if ($securePw.Length -eq 0) { Write-Host "No password entered. Aborted." -ForegroundColor Red return } $newPassword = [System.Net.NetworkCredential]::new('', $securePw).Password } else { # Cryptographic random password — same generator as offboard-user.ps1. # [System.Web.Security.Membership] is unavailable in PS 7; this portable # implementation guarantees M365 complexity rules (one of each char class). # Unlike the engineer-supplied path, this one IS displayed once below — # the engineer doesn't know the value and has to relay it to the user. $upper = [char[]]'ABCDEFGHJKLMNPQRSTUVWXYZ' $lower = [char[]]'abcdefghjkmnpqrstuvwxyz' $digit = [char[]]'23456789' $sym = [char[]]'!@#$%^&*-_=+' $all = @($upper + $lower + $digit + $sym) $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $buf = [byte[]]::new(4) $pick = { param($pool) $rng.GetBytes($buf) $pool[[BitConverter]::ToUInt32($buf, 0) % [uint32]$pool.Length] } $chars = [System.Collections.Generic.List[char]]::new() $chars.Add((& $pick $upper)) $chars.Add((& $pick $lower)) $chars.Add((& $pick $digit)) $chars.Add((& $pick $sym)) while ($chars.Count -lt 20) { $chars.Add((& $pick $all)) } for ($i = $chars.Count - 1; $i -gt 0; $i--) { $rng.GetBytes($buf) $j = [int]([BitConverter]::ToUInt32($buf, 0) % [uint32]($i + 1)) $tmp = $chars[$i]; $chars[$i] = $chars[$j]; $chars[$j] = $tmp } $newPassword = -join $chars } try { Update-MgUser -UserId $user.Id -PasswordProfile @{ Password = $newPassword ForceChangePasswordNextSignIn = $forceChange } -ErrorAction Stop Write-Host "" Write-Host " [OK] Password reset for $($user.DisplayName) ($upn)" -ForegroundColor Green if ($isGenerated) { Write-Host "" Write-Host " New password: $newPassword" -ForegroundColor Green Write-Host " Relay this to the user now — it will not be shown again." -ForegroundColor Yellow } Write-Host "" if ($forceChange) { Write-Host " User will be required to change password on next sign-in." -ForegroundColor Yellow } Write-Host "" } catch { Write-Host " [FAILED] $_" -ForegroundColor Red } finally { $newPassword = $null } |