Author Posts

April 30, 2015 at 8:16 am

Reading through these forums I found a few threads discussing old user profile deletion. I work in a school district and need to delete student profiles off machines pretty regularly. All of our student profiles share a common naming scheme of their ID number which is six digits. For example, a student profile name in C:\Users could be 620145. I was wondering how I can delete specifically just these profiles that are also older than 15 days or so with a scheduled PowerShell script. Can anyone suggest anything? Here is the code I have been playing with. I'm new at this, so I could be way off. I would greatly appreciate any assistance.

(pre)[cmdletbinding(SupportsShouldProcess)]

Param(
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[int]$Days=15
)

Start-Transcript -Path C:\ProfileCleanup.txt -Append

Write-Warning "Filtering for user profiles older than $Days days"
Get-CimInstance win32_userprofile -filter “NOT localpath like '%Default%'” -Verbose |
Where {($_.LastUseTime -lt $(Get-Date).Date.AddDays(-$days)) -and ({$_.Name -match '^\d+6$'}) } |
Remove-CimInstance -Verbose

Stop-Transcript(/pre)

May 1, 2015 at 12:53 am

Looking at Win32_UserProfile I got these results

£> Get-CimInstance Win32_UserProfile | select localpath

localpath
———
C:\Users\TestUser
C:\Users\Richard
C:\Windows\ServiceProfiles\NetworkService
C:\Windows\ServiceProfiles\LocalService
C:\windows\system32\config\systemprofile

Where do your profiles that would fit the filter come from?

I'd test this by commenting out the Remove-CimInstance and seeing what comes through

May 1, 2015 at 1:28 am

[url='https://helgeklein.com/free-tools/delprof2-user-profile-deletion-tool']delprof2[/url] is a 3rd party replacement for Microsoft's [url='http://www.microsoft.com/en-us/download/details.aspx?id=5405']original delprof[/url] tool that supports modern versions of Windows.

If you want to write your own PowerShell script for deleting profiles then remember that to properly clean up profiles you shouldn't be deleting just the profile folder but you should also be deleting registry entries associated with that user.

May 1, 2015 at 6:20 am

I wrote this for some Citrix folks I used to work with. It has static exclusions and dynamic exclusions using a AD group that contained admin accounts, so anyone in that group or in the static would not be deleted I used ADSI because there would be no module or snappin dependency. Works great keeping the servers clean, comments welcome. 🙂


[CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact='Low')]
param()

function Add-Log {
     
    [CmdletBinding()]	
    param(
		[string]$Message,
        [Parameter()] [ValidateSet(“Error”, “Warn”, “Info”)]
        [string]$Level = "Info",
        [string]$Path=("$env:temp\{0}.log" -f ($MyInvocation.ScriptName).Name)
	)
    $date= Get-Date
    $outContent = "[$date]`t$Level`t`t$Message"
    Write-Verbose $outContent
    Add-Content -Path $Path -Value $outContent
}



function Get-ADSearcherGroupMember {
    param (
        [string]$Name="*",
        [string]$Domain=$env:UserDomain
    )
     
    #Get a list of domains in the forest and grab the DN of the one matching the above parameter.
    $forest= [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    $domainSrch= $forest.Domains | Where {$_.Name -like "$Domain*"}
    $domainDN=$domainSrch.GetDirectoryEntry().distinguishedName 
    #Write-Output "Found the remote domain, the full LDAP distinguished name is $DomainDN"
     
    #Create an LDAP searcher object and pass in the DN of the domain we wish to query
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$domainDN")
     
    $Searcher.filter="(&(objectCategory=group)(objectClass=group)(Name=$Name))"
    Write-Verbose ("Search Filter: {0}" -f $Searcher.Filter)
    $Searcher.PageSize = 2500
    $Searcher.SearchScope = "Subtree"

    $colPropList = "Name","Member"
    foreach ($i in $colPropList){$Searcher.PropertiesToLoad.Add($i) | Out-Null}
    $results=$Searcher.Findall()

    $object = @() 
    #Loop through the results
    Foreach($result in $results){
        $group=$result.GetDirectoryEntry()
             
        $group.properties.member | foreach {
            $user = [ADSI]("LDAP://{0}" -f $_)
            $object += New-Object PSObject -Property @{            
                Group    = $group.get("samaccountname")    
                Name     = $user.get("samaccountname")       
                Class   = $user.get("objectClass")[1]          
            } #object 
        }
    }
    $object
}

function Delete-Profile {
    [CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact='Low')]
    param(
        [string[]]$Exclude
    )
    $Exclude | Sort-Object | foreach{ Add-Log -Path $logPath -Message ("Excluding:  {0}" -f $_) }
    $profiles = Get-WMIObject -Class Win32_UserProfile
    Add-Log -Path $logPath -Message ("Found {0} profiles..." -f $profiles.Count)
    foreach ( $profile in $profiles ) {
        $Loaded = $false
        $Excluded = $false
        Add-Log -Path $logPath -Message ("Processing profile {0}..." -f $profile.LocalPath)
        if ( $profile.Loaded ) {
            Add-Log -Path $logPath -Message ("{0} profile is loaded. Deletion will be skipped." -f $profile.LocalPath)
            $Loaded = $true
        }
        
        if ( $Exclude -contains $profile.LocalPath.Substring($profile.LocalPath.lastindexofany("\") + 1, $profile.LocalPath.Length - ($profile.LocalPath.lastindexofany("\") + 1)) ) {
            Add-Log -Path $logPath -Message ("{0} profile has been excluded. Deletion will be skipped." -f $profile.LocalPath)
            $Excluded = $true
        }
        
        If ($Loaded -eq $false -And $Excluded -eq $false) {
            Add-Log -Path $logPath -Message ("Attempting to delete {0} profile..." -f $profile.LocalPath)
            try {
                if ($pscmdlet.ShouldProcess($profile.LocalPath, "Delete")) {
                    $profile.delete()
                    Add-Log -Path $logPath -Message ("{0} profile has been deleted successfully." -f $profile.LocalPath)
                }
                
            }
            catch {
                Add-Log -Path $logPath -Message ("{0} profile could not be deleted. Error: {1}" -f $profile.LocalPath, $_.Exception.Message) -Level Error
            }
        }
    }
}


$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$logName = $MyInvocation.MyCommand.Name.ToLower().Replace("ps1","log")
$logPath = "{0}\{1}" -f $scriptPath, $logName

Add-Log -Path $logPath -Message ("Initiating {0}..." -f $MyInvocation.MyCommand)
Write-Verbose ("Logging Path: {0}" -f $logPath)
Add-Log -Path $logPath -Message ("Loading static exclusions...")
$exclusions = "administrator","all users","default user","default", "localservice","networkservice","public","myserviceaccount"

Add-Log -Path $logPath -Message ("Loading dynamic exclusions from group AD group")
Get-ADSearcherGroupMember -Name "Domain Admins" -Domain $env:UserDomain | Where {$_.Class -eq "person"} | Select Name | foreach{$exclusions += $_.Name}

Add-Log -Path $logPath -Message ("Beginning profile removal...")
Delete-Profile -Exclude $exclusions @PSBoundParameters
Add-Log -Path $logPath -Message ("Completed profile removal.  Script execution complete.")