Windows OS Hub
  • Windows Server
    • Windows Server 2022
    • Windows Server 2019
    • Windows Server 2016
    • Windows Server 2012 R2
    • Windows Server 2008 R2
    • SCCM
  • Active Directory
    • Active Directory Domain Services (AD DS)
    • Group Policies
  • Windows Clients
    • Windows 11
    • Windows 10
    • Windows 8
    • Windows 7
    • Windows XP
    • MS Office
    • Outlook
  • Virtualization
    • VMWare
    • Hyper-V
    • KVM
  • PowerShell
  • Exchange
  • Cloud
    • Azure
    • Microsoft 365
    • Office 365
  • Linux
    • CentOS
    • RHEL
    • Ubuntu
  • Home
  • About

Windows OS Hub

  • Windows Server
    • Windows Server 2022
    • Windows Server 2019
    • Windows Server 2016
    • Windows Server 2012 R2
    • Windows Server 2008 R2
    • SCCM
  • Active Directory
    • Active Directory Domain Services (AD DS)
    • Group Policies
  • Windows Clients
    • Windows 11
    • Windows 10
    • Windows 8
    • Windows 7
    • Windows XP
    • MS Office
    • Outlook
  • Virtualization
    • VMWare
    • Hyper-V
    • KVM
  • PowerShell
  • Exchange
  • Cloud
    • Azure
    • Microsoft 365
    • Office 365
  • Linux
    • CentOS
    • RHEL
    • Ubuntu

 Windows OS Hub / PowerShell / How to Delete Old User Profiles in Windows

June 8, 2023 Group PoliciesPowerShellWindows 10Windows 11Windows Server 2019Windows Server 2022

How to Delete Old User Profiles in Windows

Administrators should occasionally delete old user profiles (retired or inactive users, etc.) from C:\Users on Windows workstations and servers. The Windows user profile cleanup task is most commonly performed on Remote Desktop Services (RDS) terminal servers.

The main problem with RDS servers is the constant growth in the size of the user profile directories on the hard disk. This problem is partially solved by user profile size quotas using FSRM or NTFS quotas, using roaming profiles such as FSLogix or User Profile Disk, 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 with old (unused) user profiles.

Contents:
  • How to Delete a User Profile in Windows Manually?
  • GPO: Delete User Profiles Older Than a Specified Number of Days
  • Delete Old User Profiles with PowerShell Script

How to Delete a User Profile in Windows Manually?

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

  1. Open the Advanced System Settings (run the command SystemPropertiesAdvanced ) and go to User Profiles -> Settings;
  2. This window lists all the user profiles (local, domain, and Microsoft accounts) stored on this computer. The size of each user profile on disk is listed in the Size column;
  3. Select the user whose profile you want to delete and click the Delete button. removing user profile manually in windows

On Windows 11/10 and Windows Server 2022/2019, you can delete user profiles from disk through the Settings app. Go to Accounts -> Access work and school (or run the URI shortcut ms-settings:otherusers ). Select a user and click Remove to delete their profile data from the computer.

ms-settings - remove user profile in windows 11

When a user profile is 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 will need to manually delete the profile reference from the Windows registry:

  1. Run the Registry Editor (regedit.exe);
  2. Go to the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList;
  3. For each user logged in locally (this login method must be allowed for the user by the Allow log on locally GPO option), a separate sub-key is created with the user’s SID as the name;
  4. You can find the registry key corresponding to the user by its SID, or you can manually browse the contents of all subkeys until you find a key in which the ProfileImagePath 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 complete the correct removal of 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 the j.smith user profile reference uthe nder HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList registry.

This command works in both Windows PowerShell and new versions of PowerShell Core 6.x, 7.x.

You can remove a user profile on a remote computer using PowerShell Remoting and the 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 are going to apply an automatic profile cleanup policy to hosts in the RDS farm that are in a separate container (Organizational Unit, OU) in Active Directory.

Before applying the cleanup profiles policy to all hosts, we strongly recommend that you test it on a test host. Put one of the RDSH servers in maintenance (drain) mode and test the policy on it.
  1. Locate the OU containing 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 policy name and edit the GPO;
  3. Navigate to Computer Configuration -> Administrative Templates -> System -> User Profiles;
  4. Open the option Delete user profiles older than a specified number of days on system restart;
  5. Enable the policy and specify the number of days a user profile is considered active. When this period is over, Windows User Profile Service will automatically delete the profile at the next restart. It is recommended to specify the period of 45-90 days here;Group Policy: Delete user profiles older than a specified number days on system restart
  6. After you apply the new Group Policy settings, User Profile Service on your Windows Server will automatically delete the old user profiles. User profiles will be deleted at the next server reboot.
If you use this policy, you must ensure that there are no problems with the system time when the server is stopped/restarted (check the article “System time and date changes after reboot”). Otherwise, active user profiles may be deleted.

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

This policy didn’t work correctly 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 Trusted Installer service can change the modification date of the 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 for user profile activity against the values of the LocalProfileUnloadTimeLow and LocalProfileUnloadTimeHigh parameters under in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<USER_SID>

Get profile load time from registry parameter LocalProfileUnloadTimeHigh

You can use the following script to get the LocalProfileLoadTimeLow and LocalProfileUnloadTimeHigh registry values 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 contains 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 the profiles of disabled or inactive users.

First, let’s 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 about 32 GB.

count the total user profile size on RDS host

Script ignores Windows symbolic links (symlinks) when calculating user profile size.

Let’s see the list of users whose profiles have not been used for more than 60 days. You can use the value in the LastUseTime field 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 total size of about 18 GB).

get inactive users list by profile LastUseTime 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 the user’s SID to a name, calculates the size of each user’s profile, and displays a 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 profiles info

To remove all these user profiles, it is sufficient to pipe the list of users to the Remove-WmiObject command (it is recommended that you check the output of the script with the -WhatIf parameter 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 can change the modification date of the NTUSER.dat file in each user’s profile.

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

gwmi win32_quickfixengineering |sort installedon  |select InstalledOn -Last 1

Or using the PSWindowsUpdate module:

Get-WUHistory | Select-Object -First 10

It will most likely coincide with the date the profiles were changed. Therefore, on earlier versions of Windows, you can get a list of inactive profiles using another script that checks the 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 the profiles of some users (such as System and Network Service accounts, a local administrator account, accounts of users having active sessions, and other accounts from the 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 via a GPO at shutdown 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 the specific AD group. For example, you want to delete the profiles of users who have quit. Just add these accounts to the DisabledUsers group and run the script on the target host:

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

32 comments
7
Facebook Twitter Google + Pinterest
previous post
How to Install Free VMware Hypervisor (ESXi)
next post
How to Disable or Uninstall Internet Explorer (IE) in Windows

Related Reading

Installing Language Pack in Windows 10/11 with PowerShell

September 15, 2023

Configure Email Forwarding for Mailbox on Exchange Server/Microsoft...

September 14, 2023

How to View and Change BIOS (UEFI) Settings...

September 13, 2023

How to Create UEFI Bootable USB Drive to...

September 11, 2023

Redirect HTTP to HTTPS in IIS (Windows Server)

September 7, 2023

Categories

  • Active Directory
  • Group Policies
  • Exchange Server
  • Microsoft 365
  • Azure
  • Windows 11
  • Windows 10
  • Windows Server 2022
  • Windows Server 2019
  • Windows Server 2016
  • PowerShell
  • VMWare
  • Hyper-V
  • Linux
  • MS Office

Recent Posts

  • Installing Language Pack in Windows 10/11 with PowerShell

    September 15, 2023
  • Configure Email Forwarding for Mailbox on Exchange Server/Microsoft 365

    September 14, 2023
  • How to View and Change BIOS (UEFI) Settings with PowerShell

    September 13, 2023
  • How to Create UEFI Bootable USB Drive to Install Windows

    September 11, 2023
  • Redirect HTTP to HTTPS in IIS (Windows Server)

    September 7, 2023
  • Add an Additional Domain Controller to an Existing AD Domain

    September 6, 2023
  • How to Install an SSL Certificate on IIS (Windows Server)

    September 5, 2023
  • Managing Windows Firewall Rules with PowerShell

    August 31, 2023
  • Fixing ‘The Network Path Was Not Found’ 0x80070035 Error Code on Windows

    August 30, 2023
  • Disable Welcome Message for Microsoft 365 Groups

    August 28, 2023

Follow us

  • Facebook
  • Twitter
  • RSS
Popular Posts
  • Updating List of Trusted Root Certificates in Windows
  • Fix: Remote Desktop Licensing Mode is not Configured
  • Configure Google Chrome Settings with Group Policy
  • How to Backup and Copy Local Group Policy Settings to Another Computer
  • Allow Non-admin Users RDP Access to Windows Server
  • How to Find the Source of Account Lockouts in Active Directory
  • How to Reset the Group Policy Settings on Windows
Footer Logo

@2014 - 2023 - Windows OS Hub. All about operating systems for sysadmins


Back To Top