Over the last few articles I’ve been demonstrating ways to leverage PowerShell scripts with Group Policy. You may need to catch up to fully understand everything I’m doing in this article, which uses a PowerShell computer start up script to remove old user profiles. This is the script I will be using:
#requires -version 3.0

#Remove-UserProfile.ps1

[cmdletbinding(SupportsShouldProcess)]

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

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

Write-Warning "Filtering for user profiles older than $Days days" 
Get-CimInstance win32_userprofile -Verbose | 
Where {$_.LastUseTime -lt $(Get-Date).Date.AddDays(-$days)} |
Remove-CimInstance -Verbose

Stop-Transcript

The script searches for all profiles using the Win32_UserProfile WMI class that have not been used in X number of days, where the default is 30. I’m not using a WMI filter because I’d have to create a filter using a date in the DMTF which is more work than I care to do. There shouldn’t be that many profiles so using Where-Object is acceptable in this case and definitely easier. Any profiles that meet the requirement will be removed using Remove-CimInstance.

I also added code to create a transcript file so I’d have a way of tracking what happened at startup. Be aware that if you test the script in the PowerShell ISE you will get an error since that host does not support transcription. Also, if you use –WhatIf to test you’ll also get an error on the Stop-Transcript line since transcription will never have been started. Technically, I don’t need the Stop-Transcript command since transcription will end as soon as the PowerShell session ends, but I wanted to be thorough. If you try this script, feel free to comment out the last line.

Setup

As before, I created a GPO but this time navigated to Computer Configuration – Policies – Windows Settings – Scripts and double-clicked on Startup.

Startup scripts in Group Policy

Startup scripts in Group Policy

On the PowerShell Scripts tab I clicked on Show Files and copied the script to the GPO so it would replicate. Then I could add the script and set a parameter value.

Add PowerShell script to startup scripts

Add PowerShell script to startup scripts

The script has a default value of 30 but in the screenshot I am setting it to 45 days. Click OK a few times to save the policy. Make sure it is linked and enabled to an organizational unit and reboot a test computer running Windows 7 or later.

Testing

The best way to test this is with a virtual machine that has a few profiles. You may need to wait a few days to “age” them. Then modify the GPO to adjust the number of days to meet your test age. Take a snapshot of the virtual machine before rebooting so that you can restore and test again if necessary. Or you can revise the script to filter for a specific user profile. Depending on your GPO configuration, you might not see the transcript file if you logon immediately. But give it a few minutes. If you still don’t see anything, then check the System and Group Policy Operational event logs. You also might want to simply run the script manually to see what happens. Again, having a snapshot to roll back will be valuable. Finally, don’t forget to take replication into account if you are making changes to the script or parameter values. You may also want to run gpupdate on the desktop prior to rebooting as well.

Editing tip

Once you set up the policy using the Group Policy management console, you can skip the GUI for revising the script or parameters. While, using the GUI is probably the recommended approach, at least for testing purposes you can access the script and it’s configuration through Windows Explorer. Go to \\yourdomain\sysvol\yourdomain\policies.

Access Group Policy startup script in Windows Explorer

Access Group Policy startup script in Windows Explorer

I sorted on Date Modified to find my policy which I’ve highlighted in the screenshot above. Open up the folder and navigate to the Machine\Scripts\Startup.

Startup folder of the policy

Startup folder of the policy

You should be able to see the script. You can edit it directly or copy a new version to this folder and let it replicate. Parameter settings are stored elsewhere. Go back up to the Script folder. You may need to enable Explorer to show hidden files. But when you do, you should get something like in the screenshot below.

pssscripts.ini for PowerShell scripts

pssscripts.ini for PowerShell scripts

The pssscripts.ini file is for PowerShell scripts. The scripts.ini is for traditional scripts. You can edit the ini directly in Notepad.

Parameter settings of the PowerSgell startup script

Parameter settings of the PowerSgell startup script

Here you can see my parameter value of 45. If I want to change it, I can do so here, save the file and let it replicate to my domain controllers. This technique will also work for user scripts. But if you are more comfortable using the GUI, then by all means continue to use the Group Policy management console.

Summary

Once you know how to use PowerShell and can write a basic script, you can take advantage of Group Policy and add a whole new level of administration. I love this “set it and forget” approach, although as with any Group Policy setting, be sure to document and test it thoroughly. Now, what sort of tasks do you want to automate for users and computers using PowerShell and Group Policy?

23 Comments
  1. Paul Csiki 9 years ago

    Nice script, when you remove the WMI instance, does it remove the profile directory in the c:\users too?

  2. Author
    JEFFERY HICKS 9 years ago

    In my testing it deleted the directory as well, but please test in a non-production environment to verify.

  3. Johnny Reel 9 years ago

    Will this leave local accounts intact?

  4. Author

    This cleans up profiles regardless of whether they belong to local or domain accounts. You can always adjust your WMI query to ignore profiles belonging to local accounts. As long as the account is relatively active it shouldn’t matter. Personally, even if it is a local account, if the profile is 1 year old I’d just assume see it gone.

  5. Marc 9 years ago

    Well, I guess you would want to leave the profiles of local administrators untouched…

  6. Author

    Again, if the local admin account has not logged on in a while I have no problem wiping the profile. But you could filter them out.

    Get-CimInstance win32_userprofile -filter “NOT localpath like ‘%Administrator%'”

  7. Shane 8 years ago

    Get-CimInstance win32_userprofile -Verbose | Where {($_.LastUseTime -lt $(Get-Date).Date.
    AddDays(-$days)) -and ($_.Special -ne $true) -and ($_.LocalPath -ne “C:\Users\Administrator”)}

    I modified mine a bit to exclude special profiles, and the local administrator profile.

    Thanks for the example!

  8. Kurt Buff 8 years ago

    I believe this would be even more useful if it were a logoff script for privileged accounts – leaving behind cached passwords for admin-level accounts on workstations is bad juju. Any way to accomplish that?

    Kurt

  9. Author

    I don’t know offhand of a way to clear cached passwords. I’d look to Group Policy to disable password caching.

  10. Kurt Buff 8 years ago

    Unfortunately, the only GP that I know of (to prevent password caching) targets the computer, and not user accounts, so would affect all user accounts on the machine. I wonder if setting this up as a scheduled task (perhaps once a day), would be a decent approach.

    Kurt

  11. Anders 8 years ago

    The Group Policy “Delete User Profiles Older than a Specified Number of Days on System Restart” could address the exact need (not to destroy the PowerShell playing around – which could be usefull too):
    http://blogs.technet.com/b/askperf/archive/2009/11/03/just-me-and-my-profile-part-2.aspx

  12. Torsten 8 years ago

    Hi,
    nice script, is it possible to generate a log where I can see the names not the SID of the profiles which were removed?

    Thanks

  13. Author

    You would need to add some code to convert the SID to a friendly name. You could use WMI

    [wmi]”\\Win81-ent-01\root\cimv2:Win32_SID.SID=’$SID'”

    or CIM

    Get-WSManInstance -ResourceURI “wmicimv2/win32_sid” -SelectorSet @{SID=”$sid”} -ComputerName $computername

  14. Mark 7 years ago

    Any reason this would not work on a Windows 10 machine?

  15. Author
    Jeff Hicks 7 years ago

    I have no reason to think it wouldn’t but you’ll have to test.

  16. Butch Hoffman 7 years ago

    I like this script, however, when I tried it, it did remove the profiles, but didn’t completely remove the folders. I had to manually remove the folders. I frequently get the error message that the folder is not empty. The powershell error was: Remove-CimInstance : The directory is not empty. How can we get the script to completely remove the directory? Thanks, Butch

  17. Author
    Jeff Hicks 7 years ago

    You could modify the script to use a Try/Catch block. In the Catch block add code to remove the folder path with -force. Or modify the script to first delete all files in the path and then remove the instance. If you go that route I’d also use Try/Catch so that if there is an error removing any files, you don’t try to remove the profile.

  18. Butch Hoffman 7 years ago

    Good morning Early Bird! Would you be so kind as to provide an example? I’m sort of a newby when it comes to using Powershell. Thanks a bunch

  19. siarik 5 years ago

    Hello Jeff

    I want to help

    How to remove folder InDesign on folder roaming user with powershell script or batch file

    example username : lim.ping

    \\hpz230-003\c$\Users\lim.ping\AppData\Roaming\Adobe\InDesign

    Thxs

  20. Rui Duarte 4 years ago

    I created this small function, it doesn't delete users with specific names, like sql, .net.

    Doesn't work in windows 2003…

    Function Remove-UserProfile {
    
    [cmdletbinding()]
    Param(
    [Parameter(Position=0)]
    [ValidateNotNullorEmpty()]
    [int]$Days=180
    )
    if ( (Get-WMIObject win32_operatingsystem).name -like '*2003*'){
        return}
    Write-Warning "Filtering for user profiles older than $Days days" 
    $sids = Get-CimInstance win32_userprofile | 
    Where {$_.LastUseTime -lt $(Get-Date).Date.AddDays(-$days) -and
         ($_.Special -ne $true) -and
         ($_.LocalPath -notlike '*Administrator*') -and
         ($_.LocalPath -notlike '*svc*') -and
         ($_.LocalPath -notlike '*god*') -and
         ($_.LocalPath -notlike '*net*') -and
         ($_.LocalPath -notlike '*sql*') -and
         ($_.Loaded -eq $False)
         }
        foreach ($obj in $sids) {
            $ad = [ADSI]"LDAP://<SID=$($Obj.SID)>"
            if ($ad.distinguishedName){$ad.distinguishedName}
            else{
                Write-Warning "deleting $($obj.LocalPath)"
                Remove-CimInstance -InputObject $obj -Verbose
                }    
        }
    }
    

  21. Frank Harris 4 years ago

    Thanks for the script! Everything works except the empty folder is left behind. I am testing the script as my Tech admin and it leaves the user folders behind. Think it is a permissions issue because when I click on the user folder for the first time it states "You don't currently have permissions to access this folder. Click Continue to permanently get access to this folder" Once I click continue I can access this.  So I want to assume if this script is run from GPO it will run as an domain admin or am I completely off with this?

    Thanks!

  22. sid 3 years ago

    Hello

    is it possible to makes exception to exclude profile not to be deleted.

    thanks

     

Leave a reply to siarik Click here to cancel the reply

Please enclose code in pre tags

Your email address will not be published.

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account