The LAPS: Reloaded / Revolutions

The LAPS: Reloaded / Revolutions

First, A shout out to Peter Rising | LinkedIn for delivering the blog title. We had a nice movie discussion, which movie is better: Terminator 1 and 2…. We also talked about the Matrix… Guess where the blog name came from.

This blog will be about an Idea I had to use proactive remediations to create a sort of LAPS (Local administrator password solution).

I have mentioned it a lot, you will need to make sure your end-users do not have local admin permissions by default.  There are a lot of options how you could make sure they are never a local admin when their device is enrolled. Before we proceed I recommend reading this blog first:

You don’t want to have local admin’s who are also global admin. But having no local admin on the device is not great at all. So making sure you always have a device admin with local admin permission is always a smart thing to do. When you need to troubleshoot the device or when you need to install some software manually you will need to have a user with local admin permissions.

I am going to divide this blog into  3 parts.

*In the first part I will show you how you could add a local admin to the device.

*In Part 2 I will show you how to deal with the device local admin password

*Part 3 will show you some best practices and how to solve the plain text passwords popping up in the intunemanagement.log

Part 1. Adding a local administrator

In my opinion, the device administrator role is not what you want to use. Most of the time when a customer calls, he wants to be helped immediately. Waiting before the PRT (primary refresh token) is renewed could take up to 4 hours. So a better solution would be making sure there is always a dedicated local admin available.

You could do so by creating two CSP (I hoped to see it in the setting catalog… but unfortunately it’s not there…. Yet)

When configuring this CSP, on each device a local admin would be created with the same password. And there comes the trouble. You really don’t want to have the same password on each device.

In one of my older blogs I showed you, PowerShell could be used in the first stage in the Hacking/Cyber Kill Chain: Reconnaissance.

When each device has the same local admin with the same password you are vulnerable  to lateral movement. Lateral movement is the 5th stage in the Cyber Kill Chain.

With lateral movement, the attacker could impersonate a legitimate user (the local admin) and move through multiple systems in the network.

Part 2. Dealing with the local admin password

If you want to prevent lateral movement you will need to implement LAPS. LAPS will make sure the local admin password’s are being rotated each month/specified days. With LAPS each device has it’s own local admin with a unique password.

Implementing LAPS in a normal active directory is very easy, but implementing a LAPS solution in a cloud-only environment can be a pain. Tim Hermie created a great solution.

Serverless LAPS with Intune, Function App and Key Vault (

Some time ago I also created a LAPS solution.

The LAPS and the furious! – Call4Cloud Deploy Laps to intune right now!

With this solution, a third-party RMM tool was needed and when you are sticking to the Microsoft products only, this solution is not going to work for you.

After some projects dealing with proactive remediations, I was curious if I could create a local admin password solution with some proactive remediations. Changing the password when it’s 30 days old is no problem of course. But reporting back the password with proactive remediations was a little bit more difficult.

But after some late hours, I managed to get some good reporting. I have created 2 solutions.

  • *The first solution will store the password in the registry
  • *The second solution will store the password in a custom event log

But before I will show you the solutions you will need to add/remove some columns in the proactive remediation report.

Here is the example how it looks

You will notice a column with some output:

Pre-remediation detection output:

This output will show you the output of the detection script when the password does not have to be changed. Of course, when the password is changed by the remediation script the detection script output will show you the new Password.

Please make sure you configure the remediation script to run in a 64 bits context.

The LocalAccounts Module (get-localuser) is not going to work in a 32-bits powershell on a 64-bit system

Option 1: Registry

Just like I did with my first LAPS project I am using the registry to store the password. If you make sure the end-user could not open Regedit or has no permissions to read the registry key you are safe.

Of course, you could upload the password to SharePoint/teams or azure key vault but I wanted to keep it a little bit more simple. Just one detection and one remediation script, nothing more. So I created a proactive remediation that runs each hour, to be sure you will get the new up to date password when it’s changed)

Detection script:

$currentdate = Get-Date -Format g
$username = "admin"
$maxtimebetweenreset = "30"
$lastPasswordReset = Get-LocalUser -name $username | Select-Object -ExpandProperty PasswordLastSet | Out-String
$lastPasswordResetdate = Get-Date $lastPasswordReset -Format g
$timeSpan = New-TimeSpan -Start $lastPasswordResetdate -End $currentDate
if($timeSpan.Days -ge $maxtimebetweenreset)
    Write-Host "Password needs to be reset. Password is older than $maxtimebetweenreset days"
    Exit 1
    $regPath = "HKLM:\SOFTWARE\Microsoft\DCLAPS"
    $regVal = "lapsww"
    $regData = Get-ItemProperty $regPath -Name $regVal -ErrorAction Stop  
    $lapsww = $regdata.lapsww
    Write-Host "Password does not have to be changed. Current password: $lapsww" 
    Exit 0

Of course, the detection script can’t read the existing local admin password, because it wasn’t stored in the registry key yet. To make sure all the passwords were going to be changed, I changed the maxtimebetweenreset to “0”.

Remediation script

$currentdate = Get-Date -Format g
$username = "admin"

function Get-RandomCharacters($length, $characters) {
    $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
    return [String]$characters[$random]

$password = Get-RandomCharacters -length 8 -characters  'abcdefghiklmnoprstuvwxyz'
$password += Get-RandomCharacters -length 2 -characters  'ABCDEFGHKLMNOPRSTUVWXYZ'
$password += Get-RandomCharacters -length 3 -characters '1234567890'
$password += Get-RandomCharacters -length 1 -characters '!$%&/()=?@#*+'

Try {
    	net user admin $password | out-null
	$registryPath = "HKLM:\SOFTWARE\Microsoft\DCLAPS"
	new-Item -Path $registryPath -force | out-null
	$name = "lapsww"
	$value = $password
	New-ItemProperty -Path $registryPath -Name $name -Value $value -Force | Out-Null
	$path = 'HKLM:\software\microsoft\DClaps'
	$acl = (Get-Item $path).GetAccessControl('Access')
	set-acl $path -AclObject $acl
	$acl = (Get-Item $path).GetAccessControl('Access')
	$acl.Access |where {$_.IdentityReference -eq 'BUILTIN\Users'} |%{$acl.RemoveAccessRule($_)}
	set-acl $path -AclObject $acl

	$regVal = "lapsww"
	$lapsww = $regdata.lapsww
	$regData = Get-ItemProperty $registryPath -Name $regVal -ErrorAction Stop  
    $lastPasswordReset = Get-LocalUser -name $username | Select-Object -ExpandProperty PasswordLastSet | Out-String
    $lastPasswordResetdate = Get-Date $lastPasswordReset -Format g
    $timeSpan = New-TimeSpan -Start $lastPasswordResetdate -End $currentDate
    if($timeSpan.Days -eq 0)
    Write-Output "Password has been reset to $lapsww"
    Exit 0
Write-Output $password
   Exit 1
Write-Warning "Value Missing"
Exit 1

The remediation script will be executed when the detection script will exit with the statuscode: 1. It will change the “admin” user his password to a random password. After the password has been changed, the script will force the password to be stored in a registry key. To be sure no other users are able to read the registry keys I made sure I changed the ACL.

Please make sure you change the ‘BUILTIN\Users’ part to fit the device its language. So for me (dutch) it would be ingebouwd\gebruikers.

After the remediation script is executed, the detection script will read the registry key and will show it in the output!

Option 2: The Event Log

I guess some people just don’t like the admin password to be stored in a registry key (of course…). Storing passwords is always a difficult thing, especially when it’s not encrypted. Luckily a normal user can’t read the event log.

So you might have guessed it with this solution, I will create a new event log with the new password in it when the password needs to be changed. I will also create a scheduled task to make sure the sensitive information is removed from the log file and the registry. More information later on!

Detection Script and Remediation Script

And now for the results:Open the LAPS event log and take a look!

Of course, you could choose to store the password in an existing event log. As an example, if you are storing the password in the System event log you could also retrieve the Password with the Collect Diagnostics feature.

If you want to have some more info about this feature please look at this blog.

Part 3: best practices

1.Speeding things up!

Sometimes when you are experimenting with Pro-Active remediations you want to trigger them directly and you don’t want to wait a long time before it finally begins.

To make sure it will be executed after 5 minutes, just delete the subkeys in the execution and reports registry key as shown below and restart the Intune management extension service

2.Implementing RBAC

When you need to make sure not everyone needs to have access to these passwords you could begin to implement RBAC.

In this blog I will show you how to do this:

3.The Agent Executor Log. (a hard one)

(Updated 10-06-2021) 

Of course, this agent executor log will log. I guess that’s why it’s a log. But if you are dealing with sensitive information, you must beware of the fact the plain text passwords would also be logged in this log. Together with Jos Lieben, we created a nice solution to this problem.

Maybe adding this part after the password has been changed in the remediation script and of course the detection script?

$intuneLog1 = Join-Path $Env:ProgramData -childpath "Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log"
$intuneLog2 = Join-Path $Env:ProgramData -childpath "Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log"
Set-Content -Force -Confirm:$False -Path $intuneLog1 -Value (Get-Content -Path $intuneLog1 | Select-String -Pattern "Password" -NotMatch)
Set-Content -Force -Confirm:$False -Path $intuneLog2 -Value (Get-Content -Path $intuneLog2 | Select-String -Pattern "Password" -NotMatch)

After some tests, it looks like the command is not executed during the detection or remediation so I decided to add some parts to the detection rule. It will create a new task schedule to simply remove the lines with the password in it each minute!

As you look closely, you will notice the -encodedcommand. I just converted the script I had to a base64 key. Beware you will need to specify the UTF-16LE character set!

Base64 Encode and Decode – Online

It’s obvious you need the have the agentexecutor.log and the intunemanagementextension.log when you need to start troubleshooting so removing only the sensitive information from both log files will fix it?

4.The Registry Reports

Just like with the IntuneManagementextension log, the results are also reported in the registry. When you have read the first part into speeding things up, you know where the results are going to be stored!

If you didn’t restrict access to the registry, we also need to make sure the sensitive password is removed!

foreach($Tenant in (Get-ChildItem "HKLM:\Software\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Reports")){
foreach($script in (Get-ChildItem $Tenant.PSPath)){
$json = ((Get-ItemProperty -Path (Join-Path $script.PSPath -ChildPath "Result") -Name "Result").Result | convertfrom-json)
if($json.PreRemediationDetectScriptOutput.StartsWith("Password does")){
$json.PreRemediationDetectScriptOutput = "REDACTED"
Set-ItemProperty -Path (Join-Path $script.PSPath -ChildPath "Result") -Name "Result" -Value ($json | ConvertTo-Json -Depth 10 -Compress) -Force -Confirm:$False

Just like with the Intune Management Extension log you could add this part to the Task Schedule which was also removing the password in the log file. Just add the 2 parts together and convert it to an Base64


This is the first version of this idea, I still need to deploy/test it with some more test tenants. I hope it showed you how you could use Proactive remediations for some reporting and LAPS. For now, you have got 2 options to choose from… hopefully I can add a completely new third option tomorrow.

Maybe in the near future, Microsoft will come up with their own solution but for now, go and deploy a LAPS solution to prevent lateral movement attacks!

(hopefully you would test out my idea and provide me some feedback on how to improve it)

2 thoughts on “The LAPS: Reloaded / Revolutions

  1. Great Idea!
    I suggest to store the passwords in a KeyVault instead of the returncodes.
    There’s already a (quite old, where the printscreens are outdated) tutorial in the web for that:
    That script could get updated to use it as detection / remediationscript.

    1. HI,

      If you have read the blog you will notice I also mentioned the blog from Tim Hermie 🙂 and to quote:

      “Of course, you could upload the password to SharePoint/teams or azure key vault but I wanted to keep it a little bit more simple. Just one detection and one remediation script, nothing more.”

Leave a Reply

Your email address will not be published. Required fields are marked *

1  +  4  =