As I’m sure some of you know, I’m a big fan of Powershell scripting and find most tasks can be done far more quickly with a script than manually.
Whilst the Powershell scripts within Intune work nicely, they are run-once scripts (unless you want to start deleting registry keys) and sometimes you want a script which runs regularly.
This is where (Proactive) Remediations fits in nicely, think of it as Scheduled Tasks for Intune (but with some added logic), but they can be difficult to get your head around at first so I’ll go through how they work and the steps involved in this post.
Firstly, if you’ve never used them before, you’ll find them in the Devices – Scripts and remediations and then select the remediation tab:
The first time you load it, you’ll have to confirm you are licensed to use it, I’m assuming if you’re reading this that you must be.
Once inside, it’s split into 3 sections: Detection Script, Remediation Script and Assigment (which includes how often it runs)
Detection Script
This is the logic that decides if the second remediation script will run or not and it can be absolutely anything you can query with powershell.
What you are looking at here are the exit codes. If Intune is given an exit code of 0, it will NOT run the remediation script, this is a clean exit and the machine has (or doesn’t have) whatever you are looking for.
On the other hand, if it finds the exit code is 1, this will trigger the next script to run.
For example, if I’m looking for a particular registry key (in this case fastboot), the script will look like this:
$Path = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power"
$Name = "HiberbootEnabled"
$Type = "DWORD"
$Value = 0
Try {
$Registry = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop | Select-Object -ExpandProperty $Name
If ($Registry -eq $Value){
Write-Output "Compliant"
Exit 0
}
Write-Warning "Not Compliant"
Exit 1
}
Catch {
Write-Warning "Not Compliant"
Exit 1
}
At the top we just set what we are expecting to see in a good machine.
Then we check if the registry key exists at all with the Try code block. Obviously if it doesn’t exist, we catch the error and error out with a code 1:
}
Write-Warning "Not Compliant"
Exit 1
}
If the key exists, we then query the value of it to make sure it matches what we are looking for. If we find a match, exit with a code 0, that machine is fine and does not require anything else to run. If the value doesn’t match, we need to sort that!
Registry key is just one example though, we could also use:
Does a file exist?
$File = "C:\windows\system32\notepad.exe"
if (!$file) {
write-host "Not found"
exit 1
}
else {
write-host "Found"
exit 0
}
Or machines which haven’t rebooted in the last 3 days:
$uptime = get-uptime
if ($uptime.days -gt 3) {
write-host "Not rebooted in last 3 days"
exit 1
}
else {
write-host "All fine, recently rebooted"
exit 0
}
I’m sure you get the idea!
Remediation Script
This is the part which actually fixes whatever you were initially looking for. We don’t have to worry about exit codes for this one, it runs regardless.
Logging can be found in the Intunemanagementextension.log file, but for ease, I prefer to add my own logging via the start-transcript functionality. I often add the same logic into the remediation script as well just to be extra careful, all it takes is one typo in the detection and it could remediate everything!
So in the case of the fastboot, we simply set the reg key:
Start-Transcript -Path $env:TEMP\DisableFastBoot.txt
if((Test-Path -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power") -ne $true) {
write-host "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power does not exist"
New-Item "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power" -force -ea SilentlyContinue
write-host "Key Created" };
New-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' -Name 'HiberbootEnabled' -Value 0 -PropertyType DWord -Force -ea SilentlyContinue;
write-host "Value Set"
Stop-Transcript
For the missing file, we could download it from GitHub/Azure Blob etc.
Start-Transcript -Path $env:TEMP\copyfile.log
$File = "C:\windows\system32\notepad.exe"
if (!$file) {
write-host "File not found, downloading"
$fileurl = "https://my.file.to.download"
#Set the download location
$output = "c:\my.location\my.file.to.download"
#Download it
Invoke-WebRequest -Uri $appurl -OutFile $output -Method Get
write-host "file downloaded to $output"
}
Stop-Transcript
Or for the machine that hasn’t restarted, we could force a reboot
shutdown /t 300 /r /c "Your machine will restart in 5 minutes, please save any work now"
Scheduling
Once your scripts are created and tested, the next step is to assign them to a group, all users etc.
Once assigned, navigate back to the assignment screen and you can see your options for scheduling (and filtering if required)
Your options here are:
Once (specify date and time)
Hourly (specify how many hours apart between runs)
Daily (specify how many days apart and at what time)
Hopefully that has taken some of the mystery away and everyone will start to use this excellent feature! If there are any particular remediations you want me to add code for, just let me know in the comments.
Thank you!!!
I do have one instance where I want to delete a reg keys below I found the path and ps script
but not sure how to incorporate the code above
remove-Item -Path “HKLM:\SYSTEM\CurrentControlSet\Control\EAS”
remove-Item -Path “HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\DeviceLock”
Was thinking this
Try {
$Registry = Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\EAS
$Registry2 = Get-ItemProperty -Path “HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\DeviceLock”
\\This is where I want to delete but what should I give as parameter
If ($Registry -eq $True?){
Write-Output “EAS Present”
remove-Item -Path HKLM:\SYSTEM\CurrentControlSet\Control\EAS
Exit 0
}
ElseIf ($Registry2 -eq $Value){
Write-Output “Compliant”
remove-Item -Path HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\DeviceLock
Exit 0
}
Else {
Write-Warning “No EAS”
Exit 1
}
}
Catch {
Write-Warning “No Device Lock”
Exit 1
}
Hi,
This is where you need to split them, you have one script which detects and one which remediates.
Remove the remove-item parts and that gives a detection script (although you aren’t declaring a $value for reg2).
Then the remove items sit on their own in a remediation script which only runs if the detection is non-compliant
Very cool. How about checking the registry for a key and if it is now found, set it?
Example: Cloud Kerberos Ticket at logon.
Can be found at 2 different location – depending on distribution method:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters
or
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters
The value is always:
CloudKerberosTicketRetrievalEnabled
REG_DWORD 1
Something like this for the detection:
$Path = “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters”
$path2 = “HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters”
$Name = “CloudKerberosTicketRetrievalEnabled”
$Value = 1
Try {
$Registry = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop | Select-Object -ExpandProperty $Name
$Registry2 = Get-ItemProperty -Path $Path2 -Name $Name -ErrorAction Stop | Select-Object -ExpandProperty $Name
If ($Registry -eq $Value){
Write-Output “Compliant”
Exit 0
}
ElseIf ($Registry2 -eq $Value){
Write-Output “Compliant”
Exit 0
}
Else {
Write-Warning “Not Compliant”
Exit 1
}
}
Catch {
Write-Warning “Not Compliant”
Exit 1
}
Has this changed location I can’t find it. Closet I have found is Devices>Scripts and remediations>Remediations.
Has this moved and do you need to update the page? also I presume it will be the same if so
Thanks
Yes, thank you. I’ve updated it now. All of the other content is the same though
Thank you so much. Just want to inform you that i am eagerly waiting for your Intune book.
If I want to run a script once and want it to run before user logins for the first time (during enrollment), will powershell script be a best option or proactive remediation?
For a run-once script, best option is a standard PowerShell script assigned to the device so it runs during OOBE in the identifying apps phase of ESP
Hi Andrew
I’m looking for ‘where to start’ really.
All our users are remote & running Windows 10 Pro
The support guy before me used to have Barracuda remote for accessing devices but that contract expired and now I have a fresh Barracuda RMM access, however, I’m struggling to get it installed on most devices because of a ‘previous installation’ that I can’t easily get around, because somethings won’t uninstall.
For some of the devices, I’ve done the… uninstall – reboot – registry hack – uninstall again & about 5 reboots and managed to get through, but it’s not often that works.
Others, I’ve got the users to ship the PC back to me, I’ve reformatted it and then sent it out (because it installs on a ‘new’ PC)
I’ve still got another 50 or so PC’s to get done.
Is there a script of some sort I can run through intune to force the uninstall please?
The program is Barracuda RMM.
Barracuda support… let’s say they are less than helpful!
Thank you in advance.
Hi Andrew,
No, there is no other assignments.
The group was unassigned two weeks ago from the remediation script but continues to apply. For testing, I deleted the content (the PS scripts from IMECache folder) locally on several devices, after a while it reappears. For reference I used this remidiation script: https://github.com/damienvanrobaeys/Intune-Proactive-Remediation-scripts/tree/main/Reboot%20warning
Do you have any other assignments set?
Hi Andrew,
I have a slightly different question, about remediation scripts in general. Once a script is deployed on client machines, how can it be stopped? I removed the group I had assigned it to, but it still runs!
Hi,
Unassigning the group should stop it running, could it be the device hasn’t checked in yet?
Hi Andrew Taylor,
Thanks for writing this script. I would like to know about this script as I am not much experienced on script writing and I wanted to deploy the script to reboot the devices once in a week at least.
Can you tell me the correct path/script which I need to use from this blog and I can deploy it via Remediation and it will do the needful.
Hi,
Try this:
https://github.com/JayRHa/EndpointAnalyticsRemediationScripts/tree/main/Get-DeviceUptime_and_Reboot
great! Thanks! I will try that.
Thanks for the quick response, Andrew.
Your solution is better, because all can be implemented in only 1 proactive remediation script.
And also do you think that MS Graph can be used for such kind of detailed reports?
You could probably work with this part:
https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts/{ProacID}/deviceRunStates
The ID of each item is the script ID followed by the device ID
Hi Andrew,
I am wondering if a remediation script is used for uninstallation of unwanted software, how we can have a detailed report how many times the script has been removed this SW from one and the same device for a period of time?
I think that I can create a reg key which flags the existence of the SW before the removal and with second remediation script to catch this reg key and to take an info about the device in a csv file on the blob..
But is there more elegant solution for detailed report?
Hi,
The device status will show you the results and should repeat for each machine.
You can also access the output of a proactive remediation script so another option would be:
1) Set a reg key with a value of 1
2) Each time it runs, read the reg key, increment and replace the key value
3) Output the reg key value at the end.
That way it will show you how many times it has run against each machine
Hi Andrew
Proactive remediation has become one of my favourite tools in MEM. I do have one pretty niche requirement for a customer where they want to be able to apply settings based on the “Primary User” attribute in MEM.
Is there any way “Primary User” can be injected into the detection and remediation script?
Hi,
Would it be a user or device level script?
You can detect the primary user in graph or on the device itself, so it should be possible, but the method depends on what the script is doing afterwards
How do you get primary user it on the device itself?
It should be stored in the registry, depends on the use case though
$AADInfo = Get-Item “HKLM:/SYSTEM/CurrentControlSet/Control/CloudDomainJoin/JoinInfo”
$guids = $AADInfo.GetSubKeyNames()
foreach ($guid in $guids) {
$guidSubKey = $AADinfo.OpenSubKey($guid);
$UPN = $guidSubKey.GetValue(“UserEmail”);
}
$UPN
great article. something I’d like to point out it’s the Invoke-WebRequest usage
I replace by azcopy https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-v10
I’ve noticed Invoke-WebRequest breaking a lot regardless of some tweaks around http/https and TLS settings when downloading from azure blob storage
I place c:\programdata\%workingdir%\azcopy and from there I call the files I need to download and validate files hash every time to make sure the file is ok then I proceed with all other requirements.
Thanks Thiago! I’m a big fan of azcopy and use it regularly on my machines, just a shame I can’t script the installation of it without ending up using an invoke-webrequest to grab it from somewhere.
Ideally a powershell script or module for AZCopy would make life a lot easier.
Is that just a matter of specifying TLS 1.2? I’ve incorporated the following into any script that has to make web or API calls, just because of the unpredictability, and I haven’t had TLS related errors since.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12