question

DamonMaranya-9660 avatar image
0 Votes"
DamonMaranya-9660 asked RichMatheisen-8856 commented

Stale Resource Cleanup on Windows 10 Devices

I have been tasked with finding a way to clean up stale AD account logins and their associated user directories from our fleet of Windows 10 devices. But I am having trouble finding a way to do that.

I can find the local accounts on the device, and I can find the local user accounts get-localuser and I can find the AD accounts using get-aduser. But I neither of those pulls a list of non-local users that have logged into the test device. Just local and AD account respectively.

I can pull all accounts on a device using get-wmiobject but the date format used for the LasUseTime attribute does not appear to be compatible with the output of a get-time command and I can't find anywhere that explains what the format is and how to pull a compatible date. As a result, the code below does not trigger any accounts on the test machine. Despite the fact that some of them have not logged in since last year sometime.


$Users = Get-WmiObject -Class Win32_UserProfile

foreach ($User in $Users)
{

If ($User.LastUseTime -lt (Get-Date).AddDays(-90)) {(Remove-WmiObject -Class Win32_UserProfile -WhatIf)}

}

I can also pull the accounts using get-ciminstance, which uses a compatible date/time format. Running the code above with get and remove-wmiobject replaced with get and remove-ciminstance it completes successfully. But the LastUseTime shows the same date and time for all CIM instance which is todays date and the time that the script was last run. So the date condition in the IF statement is not triggered.

Does anyone have any idea how I might go about doing this?



windows-10-generalwindows-server-powershellwindows-active-directory
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

NewbieJones-6218 avatar image
0 Votes"
NewbieJones-6218 answered RichMatheisen-8856 commented
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

The was the task was described to me was along the lines of a script. But that may just have been his thinking at the time. I'll bring this up as an option and see what he says.

Thanks for the idea!

0 Votes 0 ·

Using a GPO would make the management of the situation a lot easier!

0 Votes 0 ·
RichMatheisen-8856 avatar image
0 Votes"
RichMatheisen-8856 answered DamonMaranya-9660 commented

Like this:

 $User.ConvertToDateTime($User.LastUseTime)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Looking into this a bit more, I think you're going to find that using the user profile is an unreliable way to get the information.

A local user has a "LastLogon" property, and a domain user will have a similar property. Using WMI/CIM you can get the profiles and from there you can get the SID. Get the local/domain user using the SID and use the dates from the user.

If you can't find the user in the AD or SAM then (if it's not a well-known SID) it's probably only to remove the profile.

0 Votes 0 ·

Thanks Rich. Adding ConvertToDateDime has allowed the script to complete using the WMI object. But it looks like the date that's pulled is the same as the date provided by Get-CIMInstance. So that's a dead end, as you suspected it would be.


I can find the SIDs of the AD accounts that have logged into the test device using the get-WMIObject cmdlt and iterating through them with a foreach set.

But when I try to pull the LastLogon date I am just getting the account's last logon date for the domain, not for the local device. Which is what I need as the goal here is to clean up old logins and user files from devices that tend to have multiple users over time. But which can't be decommissioned and reissued between users for one reason or another.

Most of these are shared computers are in facilities with rotating staff. So unused accounts (and their associated folders and files) tend pile up over time leading to performance issues. We are trying to automate the process of clearing out those old users and their files from just the local machines they have not logged into for 90 days or more as many of the users will still be active employees. They are just no longer working at the location with the shared machine they used to log into.

Any ideas on how to do that, or otherwise reduce the effect of user account bloat/overhead on the shared computer would be greatly appreciated.

0 Votes 0 ·
RichMatheisen-8856 avatar image
0 Votes"
RichMatheisen-8856 answered DamonMaranya-9660 commented

This took a bit of digging, but I think I have the way to get the last time a profile was loaded (as opposed to just being modified):

 Get-ChildItem "hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" |   #Get all local profiles
     ForEach-Object{
         $n = $_.name -replace 'hkey_local_machine','hklm:'
         if ([byte[]]$ba = (Get-ItemProperty $n).sid){   # not every profile has a SID (even though its name may be a SID!)
             $sid = (New-Object System.Security.Principal.SecurityIdentifier($ba, 0)).toString()
             if ((Get-ItemProperty $n).LocalProfileLoadTimeHigh){    # not every profile has been loaded
                 # convert the high/low values to a hex string
                 # convert the hex string to an unsigned 64-bit integer
                 # treat the value a filetime and conver that to a DateTime object
                 [uint64]$ftime = "0X{0:X8}{1:X8}" -f (Get-ItemProperty $n).LocalProfileLoadTimeHigh, (Get-ItemProperty $n).LocalProfileLoadTimelow
                 $LastLoaded = [DateTime]::FromFileTime($ftime)
                 [PSCustomObject]@{
                     SID = $sid
                     LocalProfileLoadTime = $LastLoaded
                 }
             }
         }
     }

That code produces PSCustomObjects that look like this:

 SID                                            LocalProfileLoadTime
 ---                                            --------------------
 S-1-5-21-1671195940-3624339693-1328601057-1001 5/25/2022 9:50:43 AM

The time on my profile (above) matches the time I logged on, not the current time.

You can use "Get-CimInstance -Class Win32_UserProfile" and compare their SIDs to the SIDs from the above code. If they match, compare the LocalProfileLoadTime to 90-day window and if the result is what you want, pipe the SimInstance to Remove-CimInstance.

But this still leave open the question of what are you going to do about all the data for that user? Were they using OneDrive? How will you deal with authentication to remove the synchronization before removing the local files (i.e. you don't want to remove the files from OneDrive, just the local copies). What about applications the user may have installed? Etc., etc., etc.


· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Wow Rich, This is awesome! Thanks you very much!

I'm something of a scripting novice so it's going to take me a bit to unpack everything here and test it with the code I already have.

The machines this would apply to are in a rather unique role of being shared between several staff members for the purposes of tracking patient data. Meaning that they don't have permissions to install apps and they use a shared drive for file storage. So I think we safe there.

Thanks again for all your work on this one!

0 Votes 0 ·