SOLVED

Issue with date modified for NTUSER.DAT

Brass Contributor

Is Microsoft aware of an issue with various Windows 10 processes causing the date modified timestamp on the NTUSER.DAT file in unused profiles to change? We have specifically noticed that installation of cumulative updates modifies this file in all profiles, but there are other processes (I'm guessing scheduled tasks but I haven't found which ones) that do this too. 

 

This is particularly a problem in Education as we use the "Delete user profiles older than a specified number of days on a system restart" GPO setting to clean up old profiles on Lab and Classroom computers and that policy relies on the timestamp on the NTUSER.DAT file to determine the age of the profile. (The USMT tool also uses that file and timestamp when you specify to only capture profiles used within the last XX days.)

 

For reference, the date modified timestamp on the profile folder in the Users directory does accurately reflect the last time the profile was used.

51 Replies

Hi ,
I recently enable "Delete user profiles older than a specified number of days on a system restart" GPO and set the value to 90 days. Apply to Windows 10 computer.
in the PC i can see Registry value is available "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\System
CleanupProfiles REG_DWORD to 90.
I did few restart and wait for few days, Still old user data (Last modify date from 2018 to 2021 folders) available in C:\users\ folders.
Domain controller is Win 2019 data center.
Does anyone successful on with this GPO?
Can some help?
Below is the link which i refer.

 

https://social.technet.microsoft.com/wiki/contents/articles/28647.group-policy-how-to-automatically-...

In my response, the last mention of timestamps , I believe I meant to say the timestamps appear to be changing, not don't appear?
@Amila_M: Joe Friedel's script may work, and MB_99's method may be an improvement on the idea of scripting it, but we didn't have days and weeks to mess with this so we went back to our old solution, which was OS protection software that deletes all changes when the system gets rebooted. But, others in our organization have recommended Microsoft's Shared PC solution, as they claim to have had much success with it.

In the meantime and in-between time, we are still testing Shared PC at my location, so we don't have bona fide results that I can speak to. But, I recommend trying Shared PC on some systems to see what it might do for your issue (our issue, to be inclusive). So far, it's doing a good job of controlling what goes on, but we haven't had enough time to see what it does with the profiles, yet. I realize this is the biggest issue of all of them, because if the hard drive fills up, you've got a serious problem on your hands.
Many thanks Joe for this script !

It works perfectly in our domain and now the GPO (delete user profiles older than...) works for the computers Windows 10 after a restart.

In other situation, we use this script and this command :

Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-15))} | Remove-WmiObject

Why MS don’t solve this problem with a hotfix ?
Hello,
There were a number of fixes to address this issue, so that the Group Policy should be working with current updated builds. If you are still having issues with the Group Policy not working please file Feedback bugs.
Thanks
Darrell
Hello Darell,
Thanks for your reply.
Do you know the KB concerned ?
(We have W10 1903 build 18362)
Thanks Darell for this link ; )
I will try to install this KB in one computer concerned and check if the cleanup profile work after a restart.

@Joe Friedel 

I spoke with Microsoft Engineering on this. I asked if I could share this update. They said it was not confidential, so I'm sharing it here.

 

The display dates in the Adv System control panel use “ntuser.dat”. There are no current plans to change how the dates are displayed.  There are processes (like those used by Windows Update) that touch the 'ntuser.dat' file & update the date.

 

However, the Group Policy that cleans up profiles ("Delete user profiles older than a specified number of days on a system restart") uses a different calculation (instead of ntuser.dat).  It instead uses these 2 registry values:

 

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\SID\

LoadProfileUnloadTimeHigh

LoadProfileUnLoadTimeLow

 

I hope this adds value to this conversation and explains more about what's going on!

- rp

 

@Ryan Pertusio 

 

I'm trying to figure out just what that "different" calculation might be - but I can't make heads or tails out of it. Here's the values from a computer I tested this on after logging out:

LocalProfileUnloadTimeHigh: 0x1d78a6d (30902893)
LocalProfileUnloadTimeLow: 0x14faa06d1 (1336542929)

I also ran this from psexec on that computer to get when it says I last logged out:

net user <myusername> /domain | findstr /B /C:"Last logon"

which produced this: Last logon 8/5/2021 7:44:44 PM

This translates to 1628192525 in epoch time, which I'm assuming is the format used in the registry. And I couldn't figure out any calculation from the registry entries which would even come close to the final value. Maybe it's a mystery only MS knows?

 

Hello Darell,
We have tested the fix on many computers Windows 10 (build 18362).
Now the cleanup of old profiles work perfectly after a restart.
Thanks for your help ; )
It does not work for me. Windows Server 2016.

@Darrell_Gorter 

 

Hello Darrel,

 

Many thanks about the KB458038  ; )

Could you please tel me if MS have a KB for Windows 10 1703 build 15063.1596 ?

 

Regards.

Alexandre

Is there an update for this? We are running 21H1 and it is still not working.

@dylanrafferty-scott  - I wonder if "Shared PC" is using, basically, the same policies?! Have you tried "Shared PC" with your 21H1, instead? I still think there is a connection to Anti-Virus/Anti-Malware scans listing the latest access to the profile as when the nightly scan occurred...

Thanks for the script folks!  I have modified it to add an excluded user profiles list and based mine on the date of UserClass.dat instead of IconCache as I found some users did not have one and UserClass was the next closest I could find.

 

#Purpose: Used to set the ntuser.dat last modified date to that of the last modified date on the users UsrClass.dat file.
#This is needed because windows cumulative updates are altering the ntuser.dat last modified date which then defeats
#the ability for GPO to delete profiles based on date and USMT migrations based on date.

$ErrorActionPreference = "SilentlyContinue"
$Report = $Null
$Path = "C:\Users"
$ExcludedUsers ="Public","zabbix_agent","svc",”user_1”,”user_2”,"default","defaultuser0","public","administrator"
$UserFolders = $Path | GCI -Directory -Exclude $ExcludedUsers

ForEach ($UserFolder in $UserFolders)
{
$UserName = $UserFolder.Name
If (Test-Path "$Path\$UserName\NTUSer.dat")
    {
    $Dat = Get-Item "$Path\$UserName\NTUSer.dat" -force
    $DatTime = $Dat.LastWriteTime
	$Db = Get-Item "$Path\$Username\AppData\Local\Microsoft\Windows\UsrClass.dat" -force
	$UserClass = $Db.LastWriteTime
    $Dat.LastWriteTime = $UserClass
    Write-Host $UserName $DatTime
    Write-Host (Get-item $Path\$UserName\AppData\Local\Microsoft\Windows\UsrClass.dat -Force).LastWriteTime
    $Report = $Report + "$UserName`t$DatTime`r`n"
    $Dat = $Null
    $Db = $Null
    }
}

 


@SSUtech wrote:

Thanks for the script folks!  I have modified it to add an excluded user profiles list and based mine on the date of UserClass.dat instead of IconCache as I found some users did not have one and UserClass was the next closest I could find.

 

 

#Purpose: Used to set the ntuser.dat last modified date to that of the last modified date on the users UsrClass.dat file.
#This is needed because windows cumulative updates are altering the ntuser.dat last modified date which then defeats
#the ability for GPO to delete profiles based on date and USMT migrations based on date.

$ErrorActionPreference = "SilentlyContinue"
$Report = $Null
$Path = "C:\Users"
$ExcludedUsers ="Public","zabbix_agent","svc",”user_1”,”user_2”,"default","defaultuser0","public","administrator"
$UserFolders = $Path | GCI -Directory -Exclude $ExcludedUsers

ForEach ($UserFolder in $UserFolders)
{
$UserName = $UserFolder.Name
If (Test-Path "$Path\$UserName\NTUSer.dat")
    {
    $Dat = Get-Item "$Path\$UserName\NTUSer.dat" -force
    $DatTime = $Dat.LastWriteTime
	$Db = Get-Item "$Path\$Username\AppData\Local\Microsoft\Windows\UsrClass.dat" -force
	$UserClass = $Db.LastWriteTime
    $Dat.LastWriteTime = $UserClass
    Write-Host $UserName $DatTime
    Write-Host (Get-item $Path\$UserName\AppData\Local\Microsoft\Windows\UsrClass.dat -Force).LastWriteTime
    $Report = $Report + "$UserName`t$DatTime`r`n"
    $Dat = $Null
    $Db = $Null
    }
}

 


Future copy-pasters: Be sure to edit the quotation marks next to "user_1" and "user_2" -- looks like this awesome code snippet from @SSUtech accidentally brought in the stupid kind that breaks code.

 

FIXED QUOTES:

 

 

#Purpose: Used to set the ntuser.dat last modified date to that of the last modified date on the users UsrClass.dat file.
#This is needed because windows cumulative updates are altering the ntuser.dat last modified date which then defeats
#the ability for GPO to delete profiles based on date and USMT migrations based on date.

$ErrorActionPreference = "SilentlyContinue"
$Report = $Null
$Path = "C:\Users"
$ExcludedUsers ="Public","zabbix_agent","svc","user_1","user_2","default","defaultuser0","public","administrator"
$UserFolders = $Path | GCI -Directory -Exclude $ExcludedUsers

ForEach ($UserFolder in $UserFolders)
{
$UserName = $UserFolder.Name
If (Test-Path "$Path\$UserName\NTUSer.dat")
    {
    $Dat = Get-Item "$Path\$UserName\NTUSer.dat" -force
    $DatTime = $Dat.LastWriteTime
	$Db = Get-Item "$Path\$Username\AppData\Local\Microsoft\Windows\UsrClass.dat" -force
	$UserClass = $Db.LastWriteTime
    $Dat.LastWriteTime = $UserClass
    Write-Host $UserName $DatTime
    Write-Host (Get-item $Path\$UserName\AppData\Local\Microsoft\Windows\UsrClass.dat -Force).LastWriteTime
    $Report = $Report + "$UserName`t$DatTime`r`n"
    $Dat = $Null
    $Db = $Null
    }
}

 

Wrong thread
wrong thread. This si the thread for "Issue with date modified for NTUSER.DAT"

@Joe Friedel@Hilde Claeys, @Deleted, @Ryan Pertusio@Ryan Pertusio@AHDoug@SSUtech@millermike - Tagging folks who've contributed for visibility.


I don't believe this issue is as cut and dry as everyone here is presenting. As you've likely found out, sometimes these files you are checking for don't exist, and in some cases, they have unexpected timestamps. These values get updated for various reasons (I think mostly during OS updates). How do we know which ones to trust? Based on my experience, the LastWriteTime of the user profile directory itself and the IconCache.db file seem to be the most accurate. What do you guys think? Has anyone made any progress on this? I've created a script to gather data on all of the profiles on the device. I have this problem on thousands of computers. I've attached the data for just one of those computers. The data in the linked file represents data from one of my high traffic workstations where I've used the value of 30 for the $Delete_if_Older variable. You can see by the data, many of the profiles should be flagged for deletion, but one or more of the write times are newer, so I've set the Delete flag to False. Like I stated earlier, from what I can tell, the LastWriteTime of the user profile directory itself and the IconCache.db file seem to be the most accurate. So is it best to use the LastWriteTime from IconCache.db if it exists, and if it doesn't use the LastWriteTime of the user profile directory? I found Win32_UserProfile LastUseTime to be the least accurate out of the bunch so I just excluded it from being considered for the LastUpdated variable. What do you guys think?

Here's the data from my high traffic workstation. https://1drv.ms/x/s!AoQvLmouCXZPgaApOK6SGY_bQShhGw?e=rPCjFm

Green indicates it should have been flagged for deletion, red would indicate it should not be flagged for deletion, yellow indicates the value did not exist. All-in-all, out of 173 profiles, none of them qualified to be deleted based on a 30 day threshold. I have a feeling a Windows update ran recently on this device causing the timestamps to update.

 

Anxious to know everyone's thoughts on this as it seems to be an issue Microsoft has swept under the rug. What about the LocalProfileUnloadTime values - did anyone figure out how to get those values with PowerShell and/or determine a timestamp from this data? Maybe we can get a Microsoft rep to reply here with a good alternative. :unamused:

$Delete_if_older = '30'
$DeleteProfile = (Get-Date).AddDays(-$Delete_if_older)
$ErrorActionPreference = "SilentlyContinue"
$Path = "C:\Users"
$ExcludedUsers = @('Administrator','Public','Default')
$UserFolders = $Path | Get-ChildItem -Directory -Exclude $ExcludedUsers
$CimProfiles = Get-CimInstance -Class Win32_UserProfile
$NA = [datetime] "11/11/1111 11:11:11 AM"

$ProfileData = ForEach ($UserFolder in $UserFolders) {

    $UserName = $UserFolder.Name
    $UserProfileTime = $UserFolder.LastWriteTime

    $CimProfile = $CimProfiles | Where-Object { $_.localpath -eq $UserFolder.FullName }
    if ($CimProfile) { $CimProfileTime = $CimProfile.LastUseTime } else  { $CimProfileTime = $NA }

    $NTUsr = Get-Item "$UserFolder\NTUSer.dat" -Force
    if ($NTUsr) { $NTUsrTime = $NTUsr.LastWriteTime } else { $NTUsrTime = $NA }

    $UsrClass = Get-Item "$UserFolder\AppData\Local\Microsoft\Windows\UsrClass.dat" -force
    if ($UsrClass) { $UsrClassTime = $UsrClass.LastWriteTime } else { $UsrClassTime = $NA }

    $IconCache = Get-Item "$UserFolder\AppData\Local\IconCache.db" -Force
    if ($IconCache) { $IconCacheTime = $IconCache.LastWriteTime } else { $IconCacheTime = $NA}

    $LastUpdated = $UserProfileTime,$NTUsrTime,$UsrClassTime,$IconCacheTime | Sort-Object | Select-Object -Last 1
    if ($LastUpdated -lt $DeleteProfile) { $Delete = $True } else { $Delete = $False }
    # $NTUsr.LastWriteTime = $UsrClassTime # Update NTUser.dat LastWriteTime to that of the UsrClass.dat File

    [PSCustomObject]@{
        UserName = $UserName
        ProfileFullname = $UserFolder.FullName
        UserProfileTime = $UserProfileTime
        CimProfileTime = $CimProfileTime
        NTUsrTime = $NTUsrTime
        UsrClassTime = $UsrClassTime
        IconCacheTime = $IconCacheTime
        LastUpdated = $LastUpdated
        Delete = $Delete
    }
    'UserName','UserProfileTime','CimProfile','CimProfileTime','NTUsr','NTUsrTime','UsrClass','UsrClassTime','IconCache','IconCacheTime','LastUpdated','Delete' | ForEach-Object { Remove-Variable $_ }
}