Add as many details as possible, by providing details you’ll make it easier for others to reply
How to Deploy LAPS
-
Download LAPS to a domain controller
-
Create security group called “LAPS Admins” and populate appropriate users or groups
-
Install LAPS x64 and add all Management Tools
-
Extend AD Schema
Import-Module AdmPwd.PS 2Update-AdmPwdADSchema
-
Provide permissions to workstation administrators
Set-AdmPwdReadPasswordPermission -OrgUnit "Workstations" -AllowedPrincipals "Workstation-Admins"
-
Set self permissions (what OU the Workstation-Admins can lookup passwords on)
Set-AdmPwdComputerSelfPermission -OrgUnit "Workstations"
-
Open Group Policy Management Wizard
-
Create new policy under Workstations OU called “Microsoft LAPS”
-
Edit new policy at Computer Configuration > Policies > Administrative Templates > LAPS
-
Enable “Enable local admin password management”
-
Enable “Password Settings”
-
Set Password Complexity
-
Set Password Length X+ characters
-
Set Password Age X days
-
-
Update policy on domain connected PC
Deploying LAPS with Automox Worklet
Evaluation Code
- Validates if LAPS is installed → Runs Remediation if not
- Was local admin password reset in last 30 days → Runs Remediation if not
# Check LAPS as installed (Need to extract from 64-bit registry hive so using 64-bit powershell as Automox runs in 32-bit)
$scriptblock = {Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | where-object {$_.DisplayName -eq 'Local Administrator Password Solution'}}
$software = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
If(-not ($software)){
# LAPS NOT INSTALLED
exit 1
}else{
# LAPS detected. Determine if local admin was set in last 30 days.
$scriptblock = {Get-LocalUser}
$users = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
$admin = $users | Where-Object Name -eq Administrator
[regex]$regex = '(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)'
$PasswordLastSet = ($admin.PasswordLastSet | Select-String -Pattern $regex).matches.value
# Validate LAPS reset password in last month
$expires = (Get-Date).AddMonths(-1)
IF($admin.PasswordLastSet -lt $expires){
# Non-Compliant
Exit 1
}elseif($admin.PasswordLastSet -ge $expires){
# Compliant
Exit 0
}
}
Remediation Code -
- Installs LAPS
- Writes output to Activity Log if Non-Compliant.
# Check LAPS as installed (Need to extract from 64-bit registry hive so using 64-bit powershell as Automox runs in 32-bit)
$scriptblock = {Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | where-object {$_.DisplayName -eq 'Local Administrator Password Solution'}}
$software = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
If(-not ($software)){
# Install LAPS
[void](Start-Process -FilePath 'msiexec.exe' -ArgumentList ('/qn', '/i', '"LAPS.x64.msi"') -Wait -Passthru)
# Check Results of install
if ($? -eq "True"){
Write-Output "LAPS client successfully installed. "
}else{
Write-Output "Failed to install LAPS client! "
}
}
Write-Host "Determine if local admin was set in last 30 days. "
# Get Local Users (using script block because Automox runs 32-bit and Get-LocalUser only works with 64-bit powershell on 64-bit system)
$scriptblock = {Get-LocalUser}
$users = & "$env:SystemRoot\sysnative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command $scriptblock
$admin = $users | Where-Object Name -eq Administrator
[regex]$regex = '(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)'
$PasswordLastSet = ($admin.PasswordLastSet | Select-String -Pattern $regex).matches.value
# Validate LAPS reset password in last month
$expires = (Get-Date).AddMonths(-1)
IF($admin.PasswordLastSet -lt $expires){
Write-Output "Administrator password last set $PasswordLastSet. Non-Complaint. "
exit 1
}elseif($admin.PasswordLastSet -ge $expires){
Write-Output "Administrator password last set $PasswordLastSet. Complaint. "
exit 0
}
Checking for Compliance
Using Active Directory
This proved to be a slow way of getting compliance. I’m certain this could be tweaked for larger environments to produce a faster answer.
$DaysInactive = 31
$searchbase = "OU=Workstations,DC=LapkoSoft,DC=local"
$InactiveDate = (Get-Date).Adddays(-($DaysInactive))
$computers = Get-ADComputer -Filter {(LastLogonDate -gt $InactiveDate -and Enabled -eq $true)} -SearchBase $searchbase -Properties LastLogonDate,whenCreated,OperatingSystem,ms-MCS-AdmPwdExpirationTime
$results = $computers | % {
# Check Compliance
IF($null -eq $_.'ms-Mcs-AdmPwdExpirationTime'){
# no time-stamp, LAPS likely has not run
$compliance = $false
}else{
# Get Timestamp for password last set
$PwExp = $([datetime]::FromFileTime([convert]::ToInt64($_."ms-MCS-AdmPwdExpirationTime",10)))
# Password last set longer than inactivity date, report non-compliance
IF($pwExp -gt $InactiveDate){
$compliance = $false
}else{
$complinace = $true
}
}
# Build object with results
[pscustomobject]@{
Compliance = $compliance
Name = $_.Name
"LAPS PW Expiration Time" = $PwExp
DistinguishedName = $_.DistinguishedName
DNSHostName = $_.DNSHostName
LastLogonDate = $_.LastLogonDate
OperatingSystem = $_.OperatingSystem
whenCreated = $_.whenCreated
}
}
# Get compliant and non-compliant
$compliant = $results | where Compliance -eq $true
$noncompliant = $results | where Compliance -eq $false
# Get percentage of compliant systems.
$compliance = "{0:p2}" -f (($compliant.count)/($compliant.count + $noncompliant.count))
Write-Output "LAPS Compliance at $compliance ($($compliant.count) Compliant out of $($compliant.count + $noncompliant.count) )"
Automox API
This proved to be a faster method but limited in source of truth of does it include every workstation or not.
$apiKey = Read-Host "api-key "
$outcsv = 'C:\temp\Microsoft-LAPS-Audit.csv'
$orgID = 'your-org-id'
$headers = @{ "Authorization" = "Bearer $apiKey" }
$policy = 000000 # policy # for worklet used to deploy LAPS
$start = Get-Date 1/1/2022 -f 'yyyy-MM-dd'
$end = Get-Date 1/31/2022 -f 'yyyy-MM-dd'
$i = 0
$data = do{
$url = "https://console.automox.com/api/events?o=$orgID&policyId=$policy&limit=500&page=$i&startDate=$start&endDate=$end"
$response = (Invoke-WebRequest -Method Get -Uri $url -Headers $headers).Content | ConvertFrom-Json
$response | % {
$nc = 0
$text = IF($_.data.text -match 'Non-Compliant'){$nc -eq 1}
$name = $_.server_name
[regex]$regex = '(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)'
$PasswordLastSet = ($_.data.text | Select-String -Pattern $regex).matches.value
[pscustomobject] @{
name = $name
LAPS = IF($nc -eq 0){"Compliant"}else{"Non-Compliant"}
PasswordLastSet = $PasswordLastSet
}
}
$i++
}
while ($response)
# Save output
$data | Export-Csv $outcsv -NoTypeInformation -Verbose
# Use Out-Gridview to display the results
$data | ogv