In this blog, I will show you how you could make sure when you are performing an Azure Ad Join or using Autopilot there are no additional or unwanted local administrators by using PowerShell.
1. Introduction
A while ago I posted a linked message to ask for the differences between a normal Azure Ad join and the famous Autopilot function.
Of course, I know the differences… but I wanted to start a conversation. There are a lot of benefits you have when making sure you are only using Autopilot and blocking personal devices to be enrolled.
To start with one of the benefits: The possibility to make sure the user who enrolled device doesn’t become a local admin. This is so important because when being a local admin there is no security!
With autopilot, you could make sure the user is not a local admin when enrolling their device, you could do so by configuring the “User Account Type” to “Standard“
This is just a fantastic option, of course when they don’t know about the Shift + F10 option to just create an additional local admin. Luckily I also wrote a blog about how you could fix that one!
But what if I tell you that you could do the same with a regular Azure Ad Joined Device!.
2. The PowerShell Option
I am going to show you the two options for how you could remove local admin permissions by using PowerShell. The first option will output the PowerShell script to a file and will create a scheduled task to execute this PowerShell Script. The second one will use the same PowerShell script but now as an encoded command.
1.PowerShell script converted to a IntuneWin App
Let’s start with a nice written PowerShell script and convert that one to a Win32App so we can be sure we can deploy this app during the Device setup in the Autopilot White glove flow!
Step 1:
First, you need to know how to create an Intunwin installer. I am not going to show you how you can it :). Just take a look at the MS Docs
Prepare a Win32 app to be uploaded to Microsoft Intune | Microsoft Docs
Step 2:
Make sure you set up your own local admin user with Intune CSP like I am showing in this blog
Step 3:
You will need to write a PowerShell script to remove the existing admins from the administrator group but also you need to make sure those 2 weird SID ID’s are removed from the local administrator’s group as shown below
Those 2 SID IDs represent the “Global Administrator Role” and the “Device Local Administrator Role”. You can look them up by using the get-azureaddirectoryrole command
As shown above, those GUIDS aren’t the same as the SID you noticed earlier. Luckily Oliver Kieselbach created a nice PowerShell function to convert those Azure Ad Objects to some nice SID’S
Everyone who is assigned that role will become a local administrator on the device and that’s something I don’t want! So let’s fix it!
Some notes about the PowerShell Scripts:
*Make sure you remove the: “azuread/*” users.
*Beware of the @’ at the beginning and the end the
*Make sure your own created “local admin” is always added to the administrator’s group by configuring the “$localadminuser” variable!
$content = @'
$localadminuser = "admin"
$administratorsGroup = ([ADSI]"WinNT://$env:COMPUTERNAME").psbase.children.find("Administrators")
$administratorsGroupMembers = $administratorsGroup.psbase.invoke("Members")
foreach ($administratorsGroupMember in $administratorsGroupMembers) {
$administrator = $administratorsGroupMember.GetType().InvokeMember('Name','GetProperty',$null,$administratorsGroupMember,$null)
if (($administrator -ne "Administrator") -and ($administrator -ne $localadminuser)) {
$administratorsGroup.Remove("WinNT://$administrator")
}
}
try {
Add-LocalGroupMember -Group "Administrators" -Member $username -ErrorAction Stop
get-localuser | Set-localUser -PasswordNeverExpires:$True
} catch [Microsoft.PowerShell.Commands.MemberExistsException] {
Write-Warning "$member is already member"
}
'@
Looking at the script above, you will also notice I am adding the set-localuser -passwordneverexpires to it. I want to make sure the password never expires as I am managing it with LAPS instead.
Step 4: Get the @ content, create a folder, and output it to a file on the device. In this example, the file will be saved in the programdata\customscripts folder
# create custom folder and write PS script
$path = $(Join-Path $env:ProgramData CustomScripts)
if (!(Test-Path $path))
{
New-Item -Path $path -ItemType Directory -Force -Confirm:$false
}
Out-File -FilePath $(Join-Path $env:ProgramData CustomScripts\myScript.ps1) -Encoding unicode -Force -InputObject $content -Confirm:$false
Step 5: Okey? We got a PowerShell script on de the local device… so why don’t we just create a scheduled task, to schedule this script to launch every time a user logs in? Of course, the task will be run with system privileges.
# register script as scheduled task
$Time = New-ScheduledTaskTrigger -AtLogOn
$User = "SYSTEM"
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ex bypass -file `"C:\ProgramData\CustomScripts\myScript.ps1`""
Register-ScheduledTask -TaskName "RemoveAdmin" -Trigger $Time -User $User -Action $Action -Force
Start-ScheduledTask -TaskName "RemoveAdmin"
Step 6: Just put these pieces together and create an intunewin app and make sure you put this app in the list of apps that are needed before the user logs in the first time. You can do this on the ESP page in Intune, as I am showing below!
2. A PowerShell Base64 Encoded Script
This option is really easy. It will use the third part of the first option but now Base64 encoded so you can deploy it with A PowerShell script to make sure it’s scheduled to run each minute or at user logon.
If you don’t know how and why you need to convert these scripts to a nice base64 value, please read part 2 of this blog first
And the encoded PowerShell Script itself. Of course please make sure before you convert it, you changed the required settings I showed you earlier!
$triggers = @()
$triggers += New-ScheduledTaskTrigger -At (get-date) -Once -RepetitionInterval (New-TimeSpan -Minutes 1)
$triggers += New-ScheduledTaskTrigger -AtLogOn
$User = "SYSTEM"
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ex bypass -EncodedCommand JABsAG8AYwBhAGwAYQBkAG0AaQBuAHUAcwBlAHIAIAA9ACAAIgBhAGQAbQBpAG4AIgAKACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBzAEcAcgBvAHUAcAAgAD0AIAAoAFsAQQBEAFMASQBdACIAVwBpAG4ATgBUADoALwAvACQAZQBuAHYAOgBDAE8ATQBQAFUAVABFAFIATgBBAE0ARQAiACkALgBwAHMAYgBhAHMAZQAuAGMAaABpAGwAZAByAGUAbgAuAGYAaQBuAGQAKAAiAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAcwAiACkACgAgACAAIAAgACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBzAEcAcgBvAHUAcABNAGUAbQBiAGUAcgBzACAAPQAgACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBzAEcAcgBvAHUAcAAuAHAAcwBiAGEAcwBlAC4AaQBuAHYAbwBrAGUAKAAiAE0AZQBtAGIAZQByAHMAIgApAAoAIAAgACAAIABmAG8AcgBlAGEAYwBoACAAKAAkAGEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAcwBHAHIAbwB1AHAATQBlAG0AYgBlAHIAIABpAG4AIAAkAGEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAcwBHAHIAbwB1AHAATQBlAG0AYgBlAHIAcwApACAAewAKACAAIAAgACAAIAAgACAAIAAkAGEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAIAA9ACAAJABhAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAHMARwByAG8AdQBwAE0AZQBtAGIAZQByAC4ARwBlAHQAVAB5AHAAZQAoACkALgBJAG4AdgBvAGsAZQBNAGUAbQBiAGUAcgAoACcATgBhAG0AZQAnACwAJwBHAGUAdABQAHIAbwBwAGUAcgB0AHkAJwAsACQAbgB1AGwAbAAsACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBzAEcAcgBvAHUAcABNAGUAbQBiAGUAcgAsACQAbgB1AGwAbAApACAACgAgACAAIAAgACAAIAAgACAAaQBmACAAKAAoACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgAgAC0AbgBlACAAIgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByACIAKQAgAC0AYQBuAGQAIAAoACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgAgAC0AbgBlACAAJABsAG8AYwBhAGwAYQBkAG0AaQBuAHUAcwBlAHIAKQApACAAewAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAYQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBzAEcAcgBvAHUAcAAuAFIAZQBtAG8AdgBlACgAIgBXAGkAbgBOAFQAOgAvAC8AJABhAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByACIAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ACgAgACAAIAAgAH0ACgB0AHIAeQAgAHsACgAgACAAIAAgAEEAZABkAC0ATABvAGMAYQBsAEcAcgBvAHUAcABNAGUAbQBiAGUAcgAgAC0ARwByAG8AdQBwACAAIgBBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAHMAIgAgAC0ATQBlAG0AYgBlAHIAIAAkAHUAcwBlAHIAbgBhAG0AZQAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAUwB0AG8AcAAKAGcAZQB0AC0AbABvAGMAYQBsAHUAcwBlAHIAIAB8ACAAUwBlAHQALQBsAG8AYwBhAGwAVQBzAGUAcgAgAC0AUABhAHMAcwB3AG8AcgBkAE4AZQB2AGUAcgBFAHgAcABpAHIAZQBzADoAJABUAHIAdQBlAAoAfQAgAGMAYQB0AGMAaAAgAFsATQBpAGMAcgBvAHMAbwBmAHQALgBQAG8AdwBlAHIAUwBoAGUAbABsAC4AQwBvAG0AbQBhAG4AZABzAC4ATQBlAG0AYgBlAHIARQB4AGkAcwB0AHMARQB4AGMAZQBwAHQAaQBvAG4AXQAgAHsACgAgACAAIAAgAFcAcgBpAHQAZQAtAFcAYQByAG4AaQBuAGcAIAAiACQAbQBlAG0AYgBlAHIAIABpAHMAIABhAGwAcgBlAGEAZAB5ACAAbQBlAG0AYgBlAHIAIgAKAH0ACgAKAAoA"
$Null = Register-ScheduledTask -TaskName "Remove Admin" -Trigger $triggers -User $User -Action $Action -Force
3. 64 vs 32
I guess it is and it will always be a battle between choosing a script to run in 64 or 32-bit. I am explaining that battle in this blog
Please make sure when deploying this PowerShell script need check the “Run script in 64-bit PowerShell Host”
You need to configure this to yes because the localgroup*/localuser* commands as shown below are only available on a 64-bit PowerShell!
As shown below, even Microsoft is mentioning it in their docs!
You need to configure it to run in 64 bits! If you don’t configure this option, your PowerShell script will fail!
Conclusion
Of course, it’s not the nicest method… But hey.. it works? The user is not an admin, so he can’t see or mess with this scheduled task.
Neither he can not change nor remove the custom scripts files/folder… Job done… So your Applocker config also works the moment a user joins his device to azure.
If you want to read all other options you have to make sure your users are not local admin please read this blog
Hi Rudy,
As always and excellent blog. I have question though, in Part1 step 3 you mentioned,
“Add-LocalGroupMember -Group “Administrators” -Member $username}
get-localuser | Set-localUser -PasswordNeverExpires:$True
‘@
Looking at the script above, you will also notice I am adding the set-localuser -passwordneverexpires to it. I want to make sure the password never expires as I am managing it with LAPS instead”.
I tried the above script after Autopilot built and it does check the option “Password Never Expires” but its greyed out as the Option ” User must change the password at next logon” is still checked.
I tried running the script locally but the results are same. It only works if Manually change the password of the user. then this script works as intended event if i check “” User must change the password at next logon” it just unchecks it.
any thoughts?