Identify and delete old user profiles?

Wondering if anyone has managed to do something clever with bigfix to delete old user profiles. We have a few fixlets (with powershell actions) based on user name(s), even name(s)*, exclusions of profiles we certainly wanna keep regardless of age, but “by date” would be the best and it’s not working at all because of Microsoft bogus dates…and also us (we replaced some icons under ALL user profiles for another job so seaching descendants of folders for dates is tricky). Delprof is deprecated I believe in latest flavors of W10.

We cannot find anything reliable timestamp on a file or another method to tell us a profile is no longer used under c:\users - say not for the past year, NTuser.dat is making stuff up, we tried registry stuff, file dates stuff, it’s totally hit and miss and I have test boxes that show the lies I get in return. I believe the GPO setting for this is also not reliable.

Has anyone created a simple job that can tell you reliably “yeah this user has not logged on in ages, you can clean the profile” (again reliably Identifing an old profile, I can handle the deletion part and exclusions) - thanks !

1 Like

Try this. Where 90 is the number of days someone last log in.

action uses wow64 redirection false
parameter “PowerShellExe”=“{ pathname of file ((it as string) of value “Path” of key “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell” of native registry) }”
delete __createfile
delete run.ps1
createfile until end
$daysToRetain = 90
$currentDate = Get-Date
$retentionDate = $currentDate.AddDays(-$daysToRetain)
$userProfiles = Get-CimInstance -Class Win32_UserProfile | Where-Object {{-not ($.Special) -and ($.LastUseTime -lt $retentionDate) -and ($_.LocalPath -like ‘C:\users\000*’)}
foreach ($profile in $userProfiles) {{
$profilepath = $profile.LocalPath
Remove-Item -Path $profilepath -Recurse -Force
$profile | Remove-CimInstance -Confirm:$false
}
end
move __createfile run.ps1
waithidden “{parameter “PowershellExe”}” -ExecutionPolicy Bypass -File run.ps1

This has been a backlog item for me to figure out, but curious if anyone has figured out how to identify accounts listed as “Account Unknown” in Users Profiles?

These typically are AD accounts that no longer exist in AD but still have profiles on a local computer/server. When doing manual disk cleanups, we delete these, but would like to automate that cleanup.

image

Thanks Remox… From testing on several machines with several users that I know for certain have or have not logged on lately, “LastUseTime” is not 100% reliable. At least no more reliable than every other attempt we made based on date so far. The key is likely finding “the file” that is representative but I looked and looked. Same With NTuser.dat and my hopes for c:\user\xxx\appdata\loca\microsoft\windows]explorer\explorerstartupLog_runboce.etl… Looked promising date wise but flat out does not exist on some.

It’s amazing Microsoft no longer offers a tool for that, maybe for the same reasons.

Those could be deleted accounts, or accounts from a domain that your user cannot access (in case you have a multi-domain environment).

The profile list shown here is stored in the Registry under a key named for the user SID, and the ‘Account Unknown’ indicates that a SID cannot be resolved; you can still get a clue by reading the ProfileImagePath though, which usually ends with something close to the username.

Try

q: (sid (name of it) as string| "Unknown", name of it, value "ProfileImagePath" of it as string) of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" of native registry

1 Like

It appears that LastUseTime reflects the last time the profile was used for any reason, not just for a user logging in.

In looking around for some other method, I found some references to using iconcache.db and usrclass.dat as a means of determining how long since a profile was last used.

Re: Issue with date modified for NTUSER.DAT - Page 3 - Microsoft Community Hub

1 Like

I haven’t found a great reference on this yet, but based on how other timestamps are stored, I believe the Registry values for “LocalProfileLoadTimeHigh” and “LocalProfileLoadTimeLow” behave like an NT FileTimeStamp, which can be combined to show the number of 100-nanosecond increments that have passed since midnight, January 1 of 1601 UTC.

I don’t know which OS introduced LocalProfileLoadTimeHigh and LocalProfileLoadTimeLow values, but on my Win11 I can display them and convert to time types with this query. Use with care though! Make sure they give the results you expect:

q: (sid (item 0 of it), hexadecimal integer (item 1 of it),"01 Jan 1601 00:00 +0000" as time + ((hexadecimal integer (item 1 of it)) as integer / 10000000) * second) of (name of it, (last 8 of ("00000000" & value "LocalProfileLoadTimeHigh" of it as integer as hexadecimal) & last 8 of ("00000000" & value "LocalProfileLoadTimeLow" of it as integer as hexadecimal)) | "") whose (item 1 of it != "") of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" of native registry
A: DOMAIN\ADMIN, 133505006503359249, ( Tue, 23 Jan 2024 11:24:10 -0500 )
A: DOMAIN\jason, 133585252165949982, ( Thu, 25 Apr 2024 08:26:56 -0500 )
2 Likes

I’ve used DelProf2.exe from here: Delprof2: User Profile Deletion Tool • Helge Klein

I copy it from a share to the local PC and use the switches they provide to delete profiles older than x days while excluding specific profiles.

1 Like

I appreciate that Bob but DELprof2 being too flaky is the reason I am here (FS techs have whacked many machines with this so they are asking us to figure something out with bigfix)… It’s unreliable in that it seems to use NTuser.dat (per doc) to determine the date which in test cases is not at all reliable - I can show you 5 profiles on my own PC that have NOT logged on in 3 years for certain (retired folks) and show a date within a few days from today. So in my case it’s erring on the side of safety and would not delete them, but sometimes it also whacks profiles in use (according to the field, which started this issue)

Best I have for now is a deletion on criteria I enter as parameters… so any Field tech account profile (aa-* or Ad-) doesn;t need to be there, and I have exclusions (do not touch anything starting with GPO or Admin*). It’s Powershell, fast, but making a small dent only - I need better date criteria and it seems that’s not available as I cannot find one reliable timestamped file under the profile - I’m not alone reporting NTuser.dat dates are bogus.

2 Likes

This is great, I am testing this on our RDP server and sending the output to our team to confirm if the output is correct on accounts that are no longer here. For the sake of simplicity, possible to just output the user name and the date in a mm/dd/yy format?

@GregD I remember there being an actual GPO that you could apply to machines (in my case RDP servers) that would delete profile folders after x time, I would set it to 365 days and forgot about it. This changed in 2016 and that GPO no longer worked. As you or others stated, the ntuser.dat file gets modified with current date even if the user hasn’t logged in. Found other admins having the same issue and having an issue with how to manage profiles now.

How about

q: (item 0 of it, (format "{0}/{1}/{2}" + month of it as two digits + day_of_month of it as two digits + year of it as string) of date(local time zone) of item 1 of it) of (sid (item 0 of it), "01 Jan 1601 00:00 +0000" as time + ((hexadecimal integer (item 1 of it)) as integer / 10000000) * second) of (name of it, (last 8 of ("00000000" & value "LocalProfileLoadTimeHigh" of it as integer as hexadecimal) & last 8 of ("00000000" & value "LocalProfileLoadTimeLow" of it as integer as hexadecimal)) | "") whose (item 1 of it != "") of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" of native registry
A: TESTMACHINE01\Jason, 09/19/2023
A: TESTMACHINE01\Administrator, 04/24/2024

Here’s the same listing, only for profiles older than 90 days:

q: (item 0 of it, (format "{0}/{1}/{2}" + month of it as two digits + day_of_month of it as two digits + year of it as string) of date(local time zone) of item 1 of it) of (sid (item 0 of it), "01 Jan 1601 00:00 +0000" as time + ((hexadecimal integer (item 1 of it)) as integer / 10000000) * second) whose (now - item 1 of it > 90 * day) of (name of it, (last 8 of ("00000000" & value "LocalProfileLoadTimeHigh" of it as integer as hexadecimal) & last 8 of ("00000000" & value "LocalProfileLoadTimeLow" of it as integer as hexadecimal)) | "") whose (item 1 of it != "") of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" of native registry

A: TESTMACHINE01\Jason, 09/19/2023

This can be expanded for readability to

(

  item 0 of it

  , (format "{0}/{1}/{2}" + month of it as two digits + day_of_month of it as two digits + year of it as string) of date(local time zone) of item 1 of it

) of 
(
   sid (item 0 of it)
   , "01 Jan 1601 00:00 +0000" as time + ((hexadecimal integer (item 1 of it)) as integer / 10000000) * second
) whose 
  (
    now - item 1 of it > 90 * day
  ) of 
(
  name of it
  , (last 8 of ("00000000" & value "LocalProfileLoadTimeHigh" of it as integer as hexadecimal) & last 8 of ("00000000" & value "LocalProfileLoadTimeLow" of it as integer as hexadecimal)) | ""
) whose 
   (
     item 1 of it != ""
   ) of keys of keys "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" of native registry

A: TESTMACHINE01\Jason, 09/19/2023
2 Likes

You sir are a mad genius! This will help us incredibly (once we validate it of course :slight_smile: )

Thank you very much!

1 Like

Much appreciated Jason!

[edit after better testing]
Unfortunately both scripts return inaccurate data still, I fear the same misinformation about actual logons is propagated everywhere in the system. What I see returns in QNA corresponds to modified dates of the user folder in windows…
And we know that is not accurate. I ran both scripts and both failed to report some machines that have a profile unused since 2022… I played with the 90 days → 365 etc, it more or less mimics the data you get from delprof2… It’s cool code though, wow, thanks for the effort !!! We are looking at a microsoft problem here rather than a bigfix one.

you have sql on that box? as for the other I don’t know. Are you sure you have users older than 90 days on that box? I am running against an rdp server and I am for 100% certain we do have accounts and I Am getting a good number of results.

I agree it’s a Microsoft issue; but if these registry values are getting updated, I’m not buying that it’s unintentional.

I could understand an AV or EDR or something updating file timestamps, but if these values in the Registry are changing, I have to think the profile really is getting loaded.

That doesn’t necessarily mean the user is logging on. I’m suspecting that Scheduled Tasks or something along those lines, configured to run under the user context, maybe causing the profile to load.

1 Like

How does this registry approach compare to the dates of the iconcache.db or usrclass.dat files?

1 Like