Forum Discussion
Issue with date modified for NTUSER.DAT
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.
Here is the code from the script:
#Purpose: Used to set the ntuser.dat last modified date to that of the last modified date on the user profile folder.
#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"
$UserFolders = $Path | GCI -DirectoryForEach ($UserFolder in $UserFolders)
{
$UserName = $UserFolder.Name
If (Test-Path "$Path\$UserName\NTUSer.dat")
{
$Dat = Get-Item "$Path\$UserName\NTUSer.dat" -force
$DatTime = $Dat.LastWriteTime
If ($UserFolder.Name -ne "default"){
$Dat.LastWriteTime = $UserFolder.LastWriteTime
}
Write-Host $UserName $DatTime
Write-Host (Get-item $Path\$UserName -Force).LastWriteTime
$Report = $Report + "$UserName`t$DatTime`r`n"
$Dat = $Null
}
}
- Ryan PertusioBrass Contributor
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
- AHDougCopper Contributor
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?
- Ross LaingCopper Contributor
I am currently having the same issue and have a call open with Microsoft about it. This has been ongoing now for months. I initially suspected windows updates and Store updates are changing the .dat files, which makes some sense. Looking at event logs you can see the user registry hives being modified and usually preceded by windows update client kicking in to download something.
Microsoft have been round the houses trying to blame everything but the OS, suggesting Anti-Virus software for example, but cant be that with no third party AV on the machine.
I can confirm that this problem has persisted in 1709 as well.
Currently away to use process monitor to confirm what is changing the registry.
My current feeling is that this is expected behaviour and no one wants to admit it.
Just to add, I am only seeing this behaviour since going to 1703 in the Summer as we have switched from roaming profiles to local profiles and UEV, and we were wiping all roaming profiles remnants using delprof tool on startup prior to the summer so machines were clean of profiles on every boot. This is not really an option if you are using local profiles, hence the switch to the GPO.
- matthew hansenCopper Contributor
Thanks for the update and I look forward to seeing if you're able to get a resolution. I can also confirm this is not caused by antivirus. We have Defender completely disabled via GPO and I had McAfee VSE uninstalled at the time of my testing.
- midnight51Copper Contributor
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=rPCjFmGreen 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.
$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 $_ } }
- Ryan PertusioCopper Contributor
I threw together some code you can use & abuse. (It has not been tested in Production, so use at your own risk. Make improvements and share if you'd like!)
To convert the LocalProfileLoadTimeHigh and LocalProfileLoadTimeLow, you combine them (text strings) and then convert to DateTime. It's convoluted. I'm guessing Microsoft could have used a single QWORD but instead chose 2 DWORDs registry entries.
# Get list of profiles $profiles = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\*" # Loop through each profile Foreach ($profile in $profiles) { # Get the SID $SID = New-Object System.Security.Principal.SecurityIdentifier($profile.PSChildName) # Convert SID to Friendly name $FriendlyName = $SID.Translate([System.Security.Principal.NTAccount]) # Trim and store variables $SidTrimmed = $SID.Value $FriendlyNameTrimmed = $FriendlyName.Value # Store the Profile Load time (in Decimal) # Example: ProfileLoadHighDec = 30997847 / ProfileLoadLowDec = 2259805116 $ProfileLoadHighDec = $profile.LocalProfileLoadTimeHigh $ProfileLoadLowDec = $profile.LocalProfileLoadTimeLow # Convert Decimal to Hex string # Example: ProfileLoadHighHex = 01d8fd57 / ProfileLoadLowHex = 86b1d3bc $ProfileLoadHighHex = [System.Convert]::ToString($ProfileLoadHighDec,16) $ProfileLoadLowHex = [System.Convert]::ToString($ProfileLoadLowDec,16) # Concatenate hex strings # Example: 01d8fd5786b1d3bc $ProfileHexJoined = -join ($ProfileLoadHighHex,$ProfileLoadLowHex) # Convert to DateTime format # Example: 11/21/2022 03:15:37 $TimestampInt = [Convert]::ToInt64($ProfileHexJoined,16) $ProfileLoadDate = [DateTime]::FromFileTimeutc($TimestampInt) # Optional output for example purposes Write-Output "SID: $SidTrimmed" Write-Output "Friendly Name: $FriendlyNameTrimmed" Write-Output "Profile Load Date: $ProfileLoadDate" Write-Output "`n" }
Running this script results in the following output:
SID: S-1-5-18
Friendly Name: NT AUTHORITY\SYSTEM
Profile Load Date: 01/01/1601 00:00:00SID: S-1-5-19
Friendly Name: NT AUTHORITY\LOCAL SERVICE
Profile Load Date: 01/01/1601 00:00:00SID: S-1-5-20
Friendly Name: NT AUTHORITY\NETWORK SERVICE
Profile Load Date: 01/01/1601 00:00:00SID: S-1-5-21-1234567890-1234567890-1234567890-1234
Friendly Name: LAPTOP-12345\username123
Profile Load Date: 11/21/2022 03:15:37Good luck!
-rp
- ianjhart2718Copper Contributor
noob alert! Late to the party I know, but I got here searching for a solution and others will too.
I believe there's a logic error in this which results in an incorrect conversion for some values of the input.
For it to work the string representation of the low DWORD value has to represent all 32 bits. In other words, some values have leading zeros. If those zeros are missing this effectively bit shifts right the high DWORD value, resulting in the wrong datetime.
The only non-contemporary date you should get is midnight 1/1/1601, the date represented by int zero. I was seeing year 1627 as an example.
Here's an alternate implementation.
Caveat: I am not a PowerShell expert, had to look up how to make a function . I am, however, confident in the logic having been weaned on K&R.
# https://techcommunity.microsoft.com/t5/windows-deployment/issue-with-date-modified-for-ntuser-dat/m-p/102438/page/3 # made a function. Added UnloadDate # changed the logic of the function function Get-ProfileTimestamp { param ( $ProfileLoadHigh,$ProfileLoadLow ) $a = ([int64]$ProfileLoadHigh -shl 32) -bor ([int64]$ProfileLoadLow) return [DateTime]::FromFileTimeutc($a) } # Get list of profiles $profiles = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\*" # Loop through each profile Foreach ($profile in $profiles) { # Get the SID $SID = New-Object System.Security.Principal.SecurityIdentifier($profile.PSChildName) # Convert SID to Friendly name $FriendlyName = $SID.Translate([System.Security.Principal.NTAccount]) # Trim and store variables $SidTrimmed = $SID.Value $FriendlyNameTrimmed = $FriendlyName.Value $ProfileLoadDate = Get-ProfileTimestamp -ProfileLoadHigh $profile.LocalProfileLoadTimeHigh -ProfileLoadLow $profile.LocalProfileLoadTimeLow $ProfileUnLoadDate = Get-ProfileTimestamp -ProfileLoadHigh $profile.LocalProfileUnLoadTimeHigh -ProfileLoadLow $profile.LocalProfileUnLoadTimeLow # Optional output for example purposes Write-Output "SID: $SidTrimmed" Write-Output "Friendly Name: $FriendlyNameTrimmed" Write-Output "Profile Load Date: $ProfileLoadDate" Write-Output "Profile UnLoad Date: $ProfileUnLoadDate" Write-Output "`n" }
For extra credit, how many bits is the data shifted for each missing 0x0?
- justinsh00Copper Contributor
midnight51 : I found you solution to most perfect or best resolution to whatever solution or approach mentioned over web.
Great and appreciate
- Liz ThrelkeldMicrosoftThanks for this feedback, Joe - I can pass this along to our team. If you are able to, please submit this feedback to the Windows Feedback hub - this is the best way to get bugs like this to our engineering teams.
- matthew hansenCopper Contributor
I'm currently running into this exact issue. Was there a bug report submitted or were you able to find a fix?
- Joe_FriedelBrass Contributor
I did submit this in the Feedback Hub app 5 months ago. It has 1 upvote and no comments. I have not tested to see if this was fixed in 1709. We developed a PowerShell script that loops through the user profiles and sets the ntuser.dat date modified value to the date modified value from the user profile folder. We just run this on a set schedule for any computers where we're using the profile deletion GPO setting.
- Hilde ClaeysCopper Contributor
hi Joe
We struggle with the same problem in our school. Could you share the powershell script with us?
- Keith FalatoCopper Contributor
I wanted to post and say that we are experiencing this same issue and wanted to see if anyone had any new information on it.
We are running 1703 Enterprise and are becoming increasing frustrated with it.
- Ralph SmithCopper Contributor
Just adding we have the same problem, Windows 10 Pro 1703 and 1709.
- HenSoloCopper Contributor
We are having this issue also with server 2012R2 RDS.
At reboot time user.dat timestamp is updated and profiles are never cleaned up
- Neil GalbraithCopper Contributor
Just want to say a big thanks for the powershell script. I've been tearing my hair out with this problem for several months.
We changed over some of our school estate to SSD's last year and their getting filled up quite quickly now and I couldn't work out why the normal Group Policy for removing profiles wasn't working.
Changing to the date on the folder isn't perfect as something else sometimes modified it as well but it's a step in the right direction.
Hopefully MS can sort it out.
- Greg HoganCopper Contributor
Can you tell me how you put this in place?
Does it work side by side with the existing GPO or, is this a stand alone script?
We are having issue you are/were having.....
- Joe_FriedelBrass Contributor
Yes, the script is used in combination with the GPO setting. We still have the "Delete user profiles older than a specified number of days on system restart" configured. The PowerShell script to adjust the timestamps on the NTUser.dat files is executed on a daily schedule by ConfigMgr using a package with the script as the source file and a program running the following command line: "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -file Change_NTUSER_Date.ps1
The program is deployed to always rerun. You could also accomplish this with a scheduled task running the script on each computer as well. It seemed easiest to use ConfigMgr to handle the distribution and execution of the script which also makes it easy to change, if needed.
- Amila_MCopper Contributor
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.- Christopher CurrivanCopper ContributorAmila_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.