How to delete old user profile in Windows? , Ranjan.info

Administrators should periodically delete old user profiles (retired or inactive users, etc.) from C:\Users on Windows workstations and servers. The Windows User Profile cleanup task is typically performed on a Remote Desktop Services (RDS) terminal server.

The main problem with RDS servers is the constant increase in the size of user profile directories on the hard disk. This problem is partially solved by user profile size quotas, using FSRM or NTFS quotas, using FSLogix or roaming profiles such as user profile disks, redirected folders, etc. However, if you have a large number of RDS users, over time the C:\Users folder will contain a large number of directories containing old (unused) user profiles.

How to manually delete a user profile in Windows?

In Windows, you can manually delete a profile from the Control Panel:

  1. open the advanced System Settings (run the command SystemPropertiesAdvanced ) and go user profiles , Adjustment;
  2. This window lists all user profiles (local, domain, and Microsoft accounts) stored on this computer. The size of each user profile on disk is listed in Shape column;
  3. Select the user whose profile you want to delete and click delete button.

On Windows 11/10 and Windows Server 2022/2019, you can delete user profiles from disk via Adjustment Application. go for Accounts , access work and school (or run URI shortcut ms-settings:otherusers , Select a user and click remove To delete their profile data from the computer.

ms-settings - remove user profiles in Windows 11

When user profiles are properly deleted in Windows, the profile directory in C:\Users and the user entry in the registry are deleted.

Many novice administrators try to manually remove the user profile directory from the C:\Users folder. In this case, you must manually delete the profile reference from the Windows Registry:

  1. Run Registry Editor (regedit.exe,
  2. go to registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList,
  3. for each user logged in locally (this login method must be allowed by the user allow log on locally GPO option), a separate subkey is created with the user’s SID as the name;
  4. You can search for the registry key corresponding to a user by its SID, or you can manually browse through the contents of all subkeys until you find a key that contains ProfileImagePath The value points to the directory with the user profile on disk (for example, C:\Users\j.smith,profileimagepath in registry
  5. Delete this registry key to correctly delete the profile.

You can also delete a specific user’s profile using PowerShell:

Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath.split(‘\’)[-1] -eq 'j.smith' } | Remove-CimInstance

This command removes both the hard drive directory and j.smith User Profile Reference under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList registry.

You can remove user profiles on a remote computer by using the PowerShell Remoting and Invoke-Command cmdlet:

$compname="mun-wks92s3"
$user = "j.smith"
Invoke-Command -ComputerName $compname -ScriptBlock {
param($user)
Get-CimInstance -Class Win32_UserProfile | Where-Object { $_.LocalPath.split(‘\’)[-1] -eq $user } | Remove-CimInstance
} -ArgumentList $user

GPO: Delete user profiles older than a specified number of days

In Windows, there is a built-in Group Policy option to automatically delete user profiles older than xx days. You can enable this option using the Local Group Policy Editor (gpedit.msc) or with the Domain GPO Management Console (gpmc.msc, In this example, we’re going to apply an automatic profile cleanup policy to hosts in an RDS farm that reside in a separate container (Organizational Unit, OU) in Active Directory.

  1. Locate the OU that contains the computers/servers to which you want to apply the User Profile Cleanup Policy. Right-click on the OU and select Create a GPO in this domain and link it here,Create new domain GPO
  2. Specify the name of the policy and edit the GPO;
  3. go to Computer Configuration -> Administrative Templates -> System -> User Profiles,
  4. open option Delete user profiles older than a specified number of days on system restart,
  5. Enable the policy and specify for how many days the user profile is considered active. When this period expires, the Windows User Profile Service will automatically delete the profile on the next restart. It is recommended to specify the duration of 45-90 day here;Group Policy: Delete user profiles older than a specified number of days on system restart
  6. After you apply the new Group Policy setting, the User Profiles Service on your Windows Server will automatically delete the old user profiles. User profiles will be deleted on the next server reboot.
If you use this policy, you must ensure that there is no problem with the system time when the server is shut down/restarted (see the article “System time and date changes after reboot”). Otherwise, active user profiles may be deleted.

Another disadvantage is that you cannot prevent some profiles from being deleted, such as local accounts, administrators, etc.

This policy did not work properly in versions prior to Windows 11/10 and Windows Server 2022/2019. Previously, user profile inactivity was determined by the date the NTUSER.dat file was modified. When installing Windows Updates, the modification date of the Trusted Installer service may have changed NTUSER.dat file in each user’s profile. As a result, the Win32_UserProfile service thinks that the profile has been used recently.

In modern versions of Windows, this Group Policy option checks against the values ​​of User Profile Activity. LocalProfileUnloadTimeLow And LocalProfileUnloadTimeHigh HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\ parameters under

Get profile load time from registry parameter LocalProfileUnloadTimeHigh

You can use the following script to achieve LocalProfileLoadTimeLow And LocalProfileUnloadTimeHigh Registry value in normal time format:

$profilelist = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
foreach ($p in $profilelist) {
    try {
        $objUser = (New-Object System.Security.Principal.SecurityIdentifier($p.PSChildName)).Translate([System.Security.Principal.NTAccount]).value
    } catch {
        $objUser = "[UNKNOWN]"
  }
    Remove-Variable -Force LTH,LTL,UTH,UTL -ErrorAction SilentlyContinue
    $LTH = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileLoadTimeHigh -ErrorAction SilentlyContinue).LocalProfileLoadTimeHigh
    $LTL = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileLoadTimeLow -ErrorAction SilentlyContinue).LocalProfileLoadTimeLow
    $UTH = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileUnloadTimeHigh -ErrorAction SilentlyContinue).LocalProfileUnloadTimeHigh
    $UTL = '{0:X8}' -f (Get-ItemProperty -Path $p.PSPath -Name LocalProfileUnloadTimeLow -ErrorAction SilentlyContinue).LocalProfileUnloadTimeLow
    $LoadTime = if ($LTH -and $LTL) {
        [datetime]::FromFileTime("0x$LTH$LTL")
    } else {
        $null
    }
    $UnloadTime = if ($UTH -and $UTL) {
        [datetime]::FromFileTime("0x$UTH$UTL")
    } else {
        $null
    }
    [pscustomobject][ordered]@{
        User = $objUser
        SID = $p.PSChildName
        Loadtime = $LoadTime
        UnloadTime = $UnloadTime
    }
} 

Get last profile load and unload date with PowerShell

This list includes the last load time for each user profile.

Delete old user profiles with PowerShell script

Instead of using the automatic profile cleanup policy described above, you can use a simple PowerShell script to find and remove disabled or inactive users’ profiles.

First, try to calculate the size of each user’s profile in C:\Users, using a simple script from the article Getting folder size with PowerShell

gci -force ‘C:\Users\’-ErrorAction SilentlyContinue | Where { !($_.Attributes -match " ReparsePoint") }| ? { $_ -is [io.directoryinfo] } | % {
$len = 0
gci -recurse -force $_.fullname -ErrorAction SilentlyContinue | % { $len += $_.length }
$_.fullname, ‘{0:N2} GB’ -f ($len / 1Gb)
$sum = $sum + $len
}
"Total size of profiles",'{0:N2} GB' -f ($sum / 1Gb)

The total size of all user profiles in C:\Users is approximately 32 GB.

Calculate total user profile size on RDS host

Let us see the list of users whose profile has not been used for more than 60 days. you can use the value in lastusetime area of ​​the profile to find them.

Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object

It turned out that I had 127 inactive user accounts on my RDS host (with profiles of a total size of about 18GB).

Get list of inactive users by LastUseTime profile on RDSH

The following PowerShell script lists the details of user profiles that have not been updated for more than 60 days. The script converts a user’s SID to a name, calculates the size of each user’s profile, and displays the resulting table:

$allprofilesinfo = @()
$OldProfiles=Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}
Foreach ($OldProfile in $OldProfiles)
   {$objSID = New-Object System.Security.Principal.SecurityIdentifier ($OldProfile.SID)
    $objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
        $userinfo = New-Object PSObject -Property @{
            userName = $objUser.Value
            ProfilePath = $OldProfile.localpath
            LastUsedDate = $OldProfile.ConvertToDateTime($OldProfile.LastUseTime)
            FolderSize =  "{0:N2} GB" -f ((gci –force $OldProfile.localpath –Recurse -ErrorAction SilentlyContinue| measure Length -s).sum / 1Gb) 
        }
    $allprofilesinfo += $userinfo
   }
$allprofilesinfo

Powershell: List local profile information

To delete all these user profiles, it is sufficient to pipe the list of users remove-WmiObject command (it is recommended that you view the output of the script -what if parameters before running it):

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

As mentioned earlier, when installing some Windows updates, the Trusted Installer service may change the modification date of the NTUSER.dat file in each user’s profile.

The screenshot above shows that all the profiles were changed almost at the same time. Check the date of the last update installed in Windows:

gwmi win32_quickfixengineering |sort installedon  |select InstalledOn -Last 1

or using the PSWindowsUpdate module:

Get-WUHistory | Select-Object -First 10

Most likely it will coincide with the date the profile was changed. Therefore, on older versions of Windows, you can get a list of inactive profiles using another script that lastwritetime Attribute of the user’s profile directory:

$USERS= (Get-ChildItem -directory -force 'C:\Users' | Where { ((Get-Date) — $_.lastwritetime).days -ge 60 } | % {'c:\users\' + $_.Name})
foreach ($User in $USERS) {
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.LocalPath -eq $User)} | Remove-WmiObject WhatIf }

To avoid deleting profiles of some users (eg system And network service accounts, a local administrator Accounts, user accounts with active sessions, and other accounts exception list), you can modify the script as follows:

#The list of accounts, which profiles must not be deleted
$ExcludedUsers ="Public","zabbix_agent","svc",”user_1”,”user_2”
$LocalProfiles=Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}
foreach ($LocalProfile in $LocalProfiles)
{
if (!($ExcludedUsers -like $LocalProfile.LocalPath.Replace("C:\Users\","")))
{
$LocalProfile | Remove-WmiObject
Write-host $LocalProfile.LocalPath, "profile deleted” -ForegroundColor Magenta
}
}

You can run this PowerShell script on shutdown via GPO or with a PowerShell script in Task Scheduler.

It is recommended that you test the script in your environment before configuring automatic profile deletion!

You can modify the script to automatically delete all user profiles added to a specific AD group. For example, you want to delete the profiles of users who leave. just add these accounts disabled user Create a group on the target host and run the script:

$users = Get-ADGroupMember -Identity DisabledUsers | Foreach {$_.Sid.Value}
$profiles = Get-WmiObject Win32_UserProfile
$profiles | Where {$users -eq $_.Sid} | Foreach {$_.Delete()}

Leave a Comment