Forum Discussion
Issue with date modified for NTUSER.DAT
- Feb 23, 2018
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
}
}
MB_99 , Ryan Pertusio , Ryan Pertusio
This is an OK idea, but I would not fully rely on it. The best you could hope for is to put this at the top of your IF statement to use IF the file exists. Because inevitably this will run on a workstation that does not have this file and your script will be looking for it and it won't be found--what happens then? A bunch of stuff that you did not want to be deleted is now gone.
Again, my Org does not use the Group Policy to delete profiles. We're using a script on a "on run" basis, or sometimes via Task Scheduler to do the cleanup. This is the operation I landed on. The $UserProfileThreshold is set outside of the scriptblock as a script parameter. The basis for deletion is determined one of two ways. If the User's LocalProfileLoadTime is able to be determined via the registry, this timestamp is used first. If these registry properties don't exist, I take the newest of the IconCache.db and the LastWriteTime of the user profile directory. These are the three values I trust the most, the LocalProfileLoadTime value being the most accurate out of the bunch.
OldUserProfiles {
$DeleteProfile = (Get-Date).AddDays(-$UserProfileThreshold)
$ExcludedUsers = @('Administrator','Public','Default',"$ComputerName")
$UserFolders = "C:\Users" | Get-ChildItem -Directory -Exclude $ExcludedUsers
$CimProfiles = Get-CimInstance -Class Win32_UserProfile
$RegProfiles = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\*"
$NA = [datetime] "11/11/1111 11:11:11 AM"
$Valid = [datetime] "2/2/2000 22:22:22 PM"
$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}
$RegProfile = $RegProfiles | Where-Object { $_.ProfileImagePath -eq $UserFolder.FullName }
$ProfileLoadHighDec = $RegProfile.LocalProfileLoadTimeHigh
$ProfileLoadLowDec = $RegProfile.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)
$ProfileLoadTime = [DateTime]::FromFileTimeutc($TimestampInt)
# If ProfileLoadTime exists, use that as the newest timestamp, if not use the newest of UserProfileTime and IconCacheTime
if ($ProfileLoadTime -gt $Valid) {
$LastUpdated = $ProfileLoadTime
} else { $LastUpdated = $UserProfileTime,$IconCacheTime | Sort-Object | Select-Object -Last 1 }
if ($LastUpdated -lt $DeleteProfile) { $Delete = $True } else { $Delete = $False }
[PSCustomObject]@{
UserName = $UserName
ProfileFullname = $UserFolder.FullName
UserProfileTime = $UserProfileTime
CimProfileTime = $CimProfileTime
# NTUsrTime = $NTUsrTime
# UsrClassTime = $UsrClassTime
IconCacheTime = $IconCacheTime
ProfileLoadTime = $ProfileLoadTime
LastUpdated = $LastUpdated
Delete = $Delete
}
# Remove all iteration data as to not corrupt datastream
'UserName','UserProfileTime','CimProfile','CimProfileTime','NTUsr','NTUsrTime','UsrClass','UsrClassTime','IconCache','IconCacheTime','LastUpdated','Delete' | ForEach-Object { Remove-Variable $_ }
'RegProfile','ProfileLoadHighDec','ProfileLoadLowDec','ProfileLoadHighHex','ProfileLoadLowHex','ProfileHexJoined','TimestampInt','ProfileLoadTime'| ForEach-Object { Remove-Variable $_ }
}
ForEach ($UserProfile in $ProfileData) {
if ($UserProfile.Delete) {
if ($UserProfile.CimProfileTime) {
$CimProfile = $CimProfiles | Where-Object { $_.LocalPath -eq $UserProfile.ProfileFullName }
Remove-CimInstance $CimProfile
Write-Log "User OS profile permanently deleted: $($UserProfile.ProfileFullname)" $LogFile
} else { # If the profile found in C:\Users does not exist in Win32_UserProfiles
#Remove-Item $UserProfile.ProfileFullName -Recurse -Force -ErrorAction Stop
#Write-Log "User OS profile permanently deleted: $($UserProfile.ProfileFullname)" $LogFile
Write-Log "USER OS PROFILE DOES NOT EXIST IN THE REGISTRY: $($UserProfile.ProfileFullname)" $LogFile
}
}
}
Write-Log "END OldUserProfiles cleanup" $LogFile
}
- cameron1340Dec 19, 2022Copper Contributor
Your script shows accurately the issues we face in IT to support our business devices. I'm wondering if you would be willing to share a completed script you use to help us periodically run and free up disk space where profiles are not used after 90 days. I only ask because not all of us are fluent in PowerShell to achieve this outcome.
- MB_99Jan 20, 2023Copper Contributor
If anyone is interested I have created a script that will allow for cleanup eventually, it uses a scheduled task that writes to a file in the user profile, and then deletes based on the date contained in that file. It is the best I could come up with, if anyone has any other ideas for capturing the last logon without using the scheduled task please let me know. Thanks.
https://github.com/barrett101/Windows-User-Profile-Remover
- kzapater1981Oct 13, 2023Copper Contributor
Thank you so much for sharing this script.
I'm not the best at PowerShell and coding in general unfortunately, I was just querying the $WorkingFolder variable in the RemoveUserProfiles.ps1 script. I can't seem to find where the value is to change it, I've looked over the script and don't know if I'm just missing it.
I'm also curious as to how this could be deployed via Intune if you have any tips please? I'd assume it would be a Win32 package but I'm not sure on what the command line would be to use it.
Thank you once again, your help is really appreciated.