Aug 31 2017 09:09 AM
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.
Jun 17 2021 01:58 AM - edited Jun 17 2021 02:00 AM
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.
Jun 17 2021 07:52 AM
Jun 17 2021 08:10 AM
Jun 17 2021 11:08 AM
Jun 17 2021 03:01 PM
Jun 23 2021 11:25 AM
Jun 23 2021 01:08 PM
This would have been in October 20, 2020—KB4580386 (OS Builds 18362.1171 and 18363.1171) Preview (microsoft.com)
So last October.
Jun 24 2021 09:03 AM
Jul 08 2021 03:48 PM
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
Aug 05 2021 08:16 PM
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?
Sep 01 2021 02:25 AM
Oct 25 2021 01:01 PM
Oct 29 2021 05:46 AM
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
Nov 09 2021 07:59 AM
Mar 28 2022 10:54 AM - edited Mar 28 2022 10:59 AM
Mar 28 2022 10:54 AM - edited Mar 28 2022 10:59 AM
@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...
Apr 15 2022 09:47 AM
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
}
}
Apr 18 2022 05:23 PM
@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
}
}
May 04 2022 10:23 AM
Nov 28 2022 10:46 PM
@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.
$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 $_ }
}