Old profiles on a device can unnecessarily burden local storage. There is the "Shared Configuration" in Intune, which automatically cleans up old profiles based on the last login date or the free memory. Unfortunately, this does not always work reliably. As a result, I have had situations in environments where the memory was completely full and a login was then no longer possible.
Then the only thing that helps is to reinstall the device.
To prevent this problem, I created a Proactive Remediations Package for Intune, which monitors old profiles and can remove them if necessary.
Table of Contents
Use and customize package
You can use the pack in two different ways. Either just to get an overview of the old profiles or to clean them up.
The detection script is just enough for an overview, for profile cleanup via Intune you also distribute the remediation script.
The first line of Detection and Remediation defines which profiles are considered "too old". The value is to be specified in days:
Create Intune package (monitoring and/or cleanup)
To use the package in Intune, create a new one at:
Intune > Reports > Endpoint analytics > Proactive remediations > +Create script package
In the first step you assign a meaningful name such as "WIN oldProfile Monitor" or "WIN-oldProfile-Cleanup".
Then you only upload the detection script for pure monitoring. If you also want to do the cleanup, upload the remediation script as well.
(I recommend that you only activate the monitoring at first and only activate the cleanup when the result is as desired.)
You only have to set a scope tag if your environment allows it.
With the assignment, you assign the configuration to a desired target group with a reasonable interval.
Monitoring with and without deletion
In order to have everything displayed in the evaluation, you must first display all columns:
It is important to know that only the last run is saved. For example, if you run the package every hour, you only have one hour to track the action.
Do you only have the Detection rolled out, you will only find results in the column "Pre-remediation detection outputs" or at most "Pre-remediation detection error". Remediation will not and cannot be initiated.
But you can see very clearly on which device the profiles are too old:
Are detection and remediation in action, in the event of a detection, you will also see the output after applying the remediation:
Explanation of the PowerShell scripts
Within the detection and remediation script I don't use the "LastUseTime" from the profile query. This is because it is often corrupted by antivirus programs and shows the date of the last scan for all users. That's why I read the modification date of the individual profile folders.
The basis of both scripts is identical and uses the "Profile_age" variable to read out the profile folders that are older than x days, compares them with the current profiles and saves them in the "Profiles_2remove" variable.
This is done with the following command, excluding the Default, Windows, Public, and Admin folders:
# Get all User profile folders older than X days
$LastAccessedFolder = Get-ChildItem "C:\Users" | Where-Object {$_ -notlike "*Windows*" -and $_ -notlike "*default*" -and $_ -notlike "*Public*" -and $_ -notlike "*Admin*"} | Where LastWriteTime -lt (Get-Date).AddDays(-$Profile_age)
# Filter the list of folders to only include those that are not associated with local user accounts
$Profiles_notLocal = $LastAccessedFolder | Where-Object { $_.Name -notin $(Get-LocalUser).Name }
# Retrieve a list of user profiles and filter to only include the old ones
$Profiles_2remove = Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath -in $($Profiles_notLocal.FullName) }
Code language: PowerShell (powershell)
Detection (Check for old profiles)
The detection then only processes the variable "Profiles_2remove
" and triggers remediation with exit code "1":
if($Profiles_2remove){
Write-Warning "Old profiles ($Profile_age days+): $($Profiles_2remove.LocalPath)"
Exit 1
}else{
Write-Output -NoEnumerate $(Get-CimInstance -Class Win32_UserProfile | Select-Object LocalPath, LastUseTime)
Exit 0
}
Code language: PowerShell (powershell)
Remediation (Remove the profiles)
Remediation checks the same as detection and, if necessary, executes with the command "Remove-CimInstance
" to clean up the old profiles.
if($Profiles_2remove){
# Removing all old profiles
$Profiles_2remove | Remove-CimInstance
}else{
Write-Output "No old profiles found."
}
Code language: PowerShell (powershell)
For more tips and examples of Proactive Remediations packages, see:
Endpoint Analysis Proactive Remediation Community Repository
Thanks a lot for this post! We've been looking at different options for stale profile management after we found that the old GPO setting option no longer worked reliably. I'm getting ready to implement the assessment portion of this in our org now.
Jamie
Hi Jamie, glad I could help 🙂
Hi Florian,
After running the detection script I get a lot of devices with the error below, any idea how I can address it?
Pre-remediation detection error
Detect Windows Profiles Older than 35 Days
C:\WINDOWS\IMECache\HealthScripts\a88106a3-36d7-4569-a328-783467d204bd_1\detect.ps1 : The term 'Get-LocalUser' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,detect.ps1
I figured it out, I had to select to run x64 PowerShell, that got it working on all machines.
I'm having an issue with the detection method, the root folder of my profile (that i'm logged in under) has a lastwriteTime of 09/03/2023, not today's date (21/04/2023) !
I came around this by adding an extra check:
$Profile_age = 35 # max profile age in days
Try {
# Get all User profile folders older than X days
$LastAccessedFolder = Get-ChildItem "C:\Users" | Where-Object {$_ -notlike "*Windows*" -and $_ -notlike "*default*" -and $_ -notlike "*Public*" -and $_ -notlike "*Admin*"} | Where-Object LastWriteTime -lt (Get-Date).AddDays(-$Profile_age)
# Filter the list of folders to only include those that are not associated with local user accounts
$Profiles_notLocal = $LastAccessedFolder | Where-Object { $_.Name -notin $(Get-LocalUser).Name }
# Retrieve a list of user profiles and filter to only include the old ones
$Profiles_InQuestion = Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath -in $($Profiles_notLocal.FullName) }
# Retrieve a list of user profiles and remove those where registry have been active
$Profiles_2remove = $Profiles_InQuestion | Where-Object { $_.LastUseTime -lt (Get-Date).AddDays(-$Profile_age) }
if($Profiles_2remove){
Write-Warning "Old profiles ($(($Profiles_InQuestion.LastUseTime - (get-date)).Days) days): $($Profiles_2remove.LocalPath)"
Exit 1
}else{
Write-Output "No profiles older than $Profile_age days found. "
Exit 0
}
}
Catch {
Write-Error $_
}
One more question, is the profile removal logged any where that can be traced?
This will allow you to see it in the proactive remediation (but only the last run).
If you like, you can create a transcript around the remediation part to have a local log.
Is there a way to log the profile(s) that are deleted?
We have noticed the folder date no longer is reliable for profile cleanup.. anyone else seeing this problem?
Yes
This is a lifesaver! Thanks for posting.
-Sean
I'm getting this remediation error.
Remove-CimInstance : The process cannot access the file because it is being used by another process. At C:\Windows\IMECache\HealthScripts\27a82762-d200-453d-bf12-c14de6d930c9_3\remediate.ps1:16 char:29 + $Profiles_2remove | Remove-CimInstance + ~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (Win32_UserProfi...9-230331099...):CimInstance) [Remove-CimInstance], CimE xception + FullyQualifiedErrorId : HRESULT 0x80070020,Microsoft.Management.Infrastructure.CimCmdlets.RemoveCimInstanceCommand
I have had that with some clients. Do you get it all the time or only in some cases?
In my environment, the error happens once in a while, but is fixed on the next run.
Hi Florian - curious why this is preferred over the old GPO? Although your method will certainly provide more reporting, so perhaps for that reason it is preferred.
See here:
https://www.lieben.nu/liebensraum/2019/10/delete-user-profiles-older-than-a-specified-number-of-days-on-system-restart-through-intune/
I currently use this GPO for servers and it seems to work well, although to be fair I haven't monitored its successes (or failures) closely. I was looking to enable something like this in Intune when I found both of these pages!
Thanks.
Hi Scott, yes, one of the reasons for reporting and the second is that one had several computers (especially older ones) where the cleanup process didn't work the classic way.
I have a couple of systems that are having issues with the execution policy (not sure why its only a few). Has anyone had to set a specific execution policy?