SCRIPT LIBRARY · POWERSHELL
Rotate Local Admin Passwords Across the Fleet with Windows LAPS and ConfigMgr
Force an immediate local admin password rotation on every machine in a collection, with Windows LAPS doing the password part so no script ever sees one.
- What it does
- Finds the Windows LAPS policy that applies to the machine, confirms password backup is on, and triggers an immediate rotation with Reset-LapsPassword. It refuses to do anything if LAPS isn't set up.
- Requires
- Windows 10/11 or Windows Server 2019+ with the April 2023 cumulative update or later (Windows LAPS built in)
- A Windows LAPS policy from Intune, Group Policy, or local configuration
- Permissions
- Local administrator or SYSTEM on the target PC. Reading the new password later needs LAPS read rights in AD or Entra ID.
- Runs on
- Windows 10/11, Windows Server 2019+
- Tested
- Parse-checked and dry-run with mocked LAPS cmdlets and policy keys in PowerShell 7.4
Part 3 of the thread Local admin, done right
Years ago the standard way to "renew" the local admin password was a package that ran net user Administrator <password> on every machine. The first version of this post did exactly that, with the new password sitting in plain text in the script. So the one password that unlocked every PC was on the source share, on every distribution point, and in the client cache of every machine it ran on. It was also the same password everywhere, which is precisely what lateral movement loves.
Please don't do that anymore. Windows LAPS is built into Windows 10, Windows 11, and Server 2019 and later (from the April 2023 updates on). It gives every machine its own random admin password, stores it in Active Directory or Microsoft Entra ID, and rotates it on a schedule. If you haven't turned it on yet, that's the real fix, and this script won't help until you do.
What LAPS doesn't do by itself is rotate everything, right now. After an incident, after a contractor leaves, or after someone pasted a password somewhere they shouldn't have, you want a fresh password on every machine today, not whenever each one hits its schedule. That's what this script is for: push it as a package to a collection and each machine asks LAPS for an immediate rotation.
<#
.SYNOPSIS
Forces Windows LAPS to rotate the managed local admin password on this computer right now.
.DESCRIPTION
Finds which Windows LAPS policy applies (Intune/CSP, Group Policy, or local config), confirms
password backup is turned on, and calls Reset-LapsPassword. LAPS generates the new password, stores it
in Active Directory or Microsoft Entra ID, and sets it locally. No password ever passes through this
script. If LAPS isn't configured, the script stops and says so rather than falling back to anything.
Exit codes: 0 = rotated; 1 = rotation failed; 2 = Windows LAPS not available on this build;
4 = no active Windows LAPS policy.
.EXAMPLE
.\Invoke-LapsPasswordRotation.ps1
.EXAMPLE
.\Invoke-LapsPasswordRotation.ps1 -WhatIf -Verbose
#>
[CmdletBinding(SupportsShouldProcess)]
param()
# Windows LAPS reads policy from these keys, highest precedence first.
$policyRoots = [ordered]@{
'CSP (Intune)' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\LAPS'
'Group Policy' = 'HKLM:\SOFTWARE\Microsoft\Policies\LAPS'
'Local configuration' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\LAPS\Config'
}
$legacyRoot = 'HKLM:\SOFTWARE\Policies\Microsoft Services\AdmPwd'
$directories = @{ 0 = 'Disabled'; 1 = 'Microsoft Entra ID'; 2 = 'Active Directory' }
$result = [ordered]@{
ComputerName = $env:COMPUTERNAME; PolicySource = 'None'; BackupDirectory = ''
ManagedAccount = ''; Status = ''; Detail = ''
}
function Complete-Run { param([int]$Code) [pscustomobject]$result; exit $Code }
if (-not (Get-Command -Name Reset-LapsPassword -ErrorAction SilentlyContinue)) {
$result.Status = 'NotAvailable'
$result.Detail = 'Windows LAPS is not on this build. Patch to a supported cumulative update first.'
Complete-Run 2
}
$policy = $null
foreach ($source in $policyRoots.Keys) {
$values = Get-ItemProperty -Path $policyRoots[$source] -ErrorAction SilentlyContinue
if ($values -and $null -ne $values.BackupDirectory) {
$policy = $values; $result.PolicySource = $source
break
}
}
if (-not $policy) {
$result.Status = 'NoPolicy'
$result.Detail = if (Test-Path -Path $legacyRoot) { 'Only legacy Microsoft LAPS (AdmPwd) policy found. Migrate to Windows LAPS.' } else { 'No Windows LAPS policy applies to this device.' }
Complete-Run 4
}
$backup = [int]$policy.BackupDirectory
$result.BackupDirectory = if ($directories.ContainsKey($backup)) { $directories[$backup] } else { "Unknown ($backup)" }
$result.ManagedAccount = if ($policy.AutomaticAccountManagementEnabled -eq 1) {
$name = if ($policy.AutomaticAccountManagementNameOrPrefix) { $policy.AutomaticAccountManagementNameOrPrefix } else { 'WLapsAdmin' }
"$name (automatic account management)"
}
elseif ($policy.AdministratorAccountName) { $policy.AdministratorAccountName }
else { 'Built-in Administrator' }
Write-Verbose "Policy from $($result.PolicySource): backup to $($result.BackupDirectory), account $($result.ManagedAccount)"
if ($backup -eq 0) {
$result.Status = 'NoPolicy'; $result.Detail = 'A LAPS policy exists but password backup is disabled.'
Complete-Run 4
}
if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Rotate the LAPS password for '$($result.ManagedAccount)'")) {
$result.Status = 'WhatIf'; Complete-Run 0
}
try {
Reset-LapsPassword -ErrorAction Stop
$result.Status = 'Rotated'
Complete-Run 0
}
catch {
# Usually means the device can't reach a domain controller or Entra right now. LAPS retries on its own cycle.
$result.Status = 'Failed'; $result.Detail = $_.Exception.Message
Complete-Run 1
}
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-WhatIf | switch | — | Reports which policy applies, where the password is backed up, and which account is managed, without rotating anything. |
-Verbose | switch | — | Prints the policy details it found along the way. |
Run it
Rotate now on the machine you're on (elevated).
.\Invoke-LapsPasswordRotation.ps1Check which policy a machine is getting before you rely on it.
.\Invoke-LapsPasswordRotation.ps1 -WhatIf -VerboseAs a ConfigMgr package program.
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Invoke-LapsPasswordRotation.ps1Afterwards, confirm a machine's password changed (AD-backed).
Get-LapsADPassword -Identity PC-0142 | Select-Object ComputerName, Account, PasswordUpdateTime, ExpirationTimestampWhat you'll see
ComputerName : PC-0142
PolicySource : CSP (Intune)
BackupDirectory : Microsoft Entra ID
ManagedAccount : lapsadmin
Status : Rotated
Detail :
How it works
- Check Windows LAPS is there. If
Reset-LapsPassworddoesn't exist, the build is too old and the script exits 2. - Find the policy that applies. Windows LAPS reads policy from a few registry locations in a fixed order: Intune/CSP first, then Group Policy, then local configuration. The script checks them in the same order and uses the first one that has a
BackupDirectoryvalue. - Make sure backup is actually on.
BackupDirectoryof 0 means LAPS is effectively off. 1 is Entra ID, 2 is Active Directory. - Work out which account is managed. Automatic account management (Windows 11 24H2 and Server 2025), a named account from
AdministratorAccountName, or the built-in Administrator. - Rotate.
Reset-LapsPasswordasks LAPS to make a new password, store it, and set it. The script never sees the password, which is the point.
Packaging it
This one's a plain package with a program, deployed to a collection as required:
$SiteCode = 'ABC'
$SourceShare = '\\sccm01\Sources\Scripts'
$DPGroup = 'All DPs'
$Name = 'LAPS - Rotate Now'
$source = Join-Path $SourceShare 'Invoke-LapsPasswordRotation'
New-Item -ItemType Directory -Path $source -Force | Out-Null
Copy-Item .\Invoke-LapsPasswordRotation.ps1 -Destination $source
Import-Module (Join-Path $env:SMS_ADMIN_UI_PATH '..\ConfigurationManager.psd1')
Push-Location "$($SiteCode):\"
New-CMPackage -Name $Name -Path $source | Out-Null
New-CMProgram -PackageName $Name -StandardProgramName 'Rotate' -CommandLine 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Invoke-LapsPasswordRotation.ps1' -ProgramRunType WhetherOrNotUserIsLoggedOn -RunMode RunWithAdministrativeRights -RunType Hidden | Out-Null
Start-CMContentDistribution -PackageName $Name -DistributionPointGroupName $DPGroup
Pop-Location
For a reusable, parameterized version of that packaging step, see Automating MECM Package Creation with PowerShell.
Take it further
- Rotate after every use. Windows LAPS can rotate the password automatically after someone signs in with it (post-authentication actions). Turn that on and "renewal" mostly takes care of itself.
- Use the exit codes as a report. Exit 4 is "no LAPS policy", exit 2 is "build too old". Deployment status by exit code gives you a LAPS coverage report for free.
- Skip ConfigMgr for Entra-joined devices. Intune has a "Rotate local admin password" device action that does the same thing for a single device, and it's scriptable through Microsoft Graph.
Things that'll trip you up
- No LAPS, no rotation. On purpose. If there's no Windows LAPS policy, or backup is disabled, the script exits 4 and changes nothing. A fallback that sets a known password would just bring back the old problem. Machines that report exit code 4 are your list of LAPS gaps.
- The machine has to reach its directory. LAPS stores the new password in AD or Entra ID as part of the rotation, so a laptop that's off the network or VPN fails with exit code 1. That's fine. Leave the deployment in place and it'll run again when the machine comes back.
- Legacy LAPS doesn't have this. The old Microsoft LAPS (AdmPwd) client has no local "rotate now" command; you'd expire the password in AD instead. The script spots legacy-only machines and says so. They're a good nudge to finish migrating.
- Never keep a hard-coded admin password around as a backup. If you're tempted to keep the old net user package "just in case", delete it and its source folder. That password is still valid on any machine LAPS hasn't reached yet, which is exactly the one you'll forget about.