Link to home
Create AccountLog in
Avatar of doctorbill
doctorbillFlag for United Kingdom of Great Britain and Northern Ireland

asked on

User Profiles

Does anyone have a powershell script that checks the age of a user profile on a terminal server and deletes it if it has been unused for a specific time or is older than a certain date?

Avatar of DEMAN-BARCELO (MVP) Thierry
DEMAN-BARCELO (MVP) Thierry
Flag of France image

Hi,

some old solutions were using a Microsoft tool "DelProf.exe". But I don't have seen recent updates of this tool.

Here is another scripted solution based on GPO that could do the work :

How to delete old profiles with GPO and Powershell​​​
Avatar of doctorbill

ASKER

This script gives an error:
gci -force 'C:\Users'-ErrorAction SilentlyContinue | ? { $_ -is [io.directoryinfo] } | % {$len = 0gci -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)

Error:
ForEach-Object : Cannot convert 'System.Object[]' to the type 'System.Management.Automation.ScriptBlock
parameter 'RemainingScripts'. Specified method is not supported.
At line:1 char:88
+ ... ryinfo] } | % {$len = 0gci -recurse -force $_.fullname -ErrorAction S ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [ForEach-Object], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.ForEachObjectCommand
This also does not return anything:
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object

If I put in 0 rather than -60 it returns all profiles but any other value returns 0
Hello,
I found another site where the lines are well reported :
How to Delete Old User Profiles Using GPO and PowerShell? | Windows OS Hub (woshub.com) 

So, the first script is working well, but not the second one for me. Probably because of the date format. 
Awesome:
The first script now works
What about the 2nd and data format - any ideas?
Yes, we should have to work the format of the value of "LastUseTime"  : 20210704161119.224000+000

Here is the little script I have done to have that working :
function ConvertDate($LastDate){
  $d=[string]$LastDate
  if($d.length -ge 25){
    $t=$d.split(".")
    $d=$t[0] 
    $findDate=[System.DateTime]::ParseExact($d,'yyyyMMddHHmmss',[System.Globalization.DateTimeFormatInfo]::CurrentInfo)
    }
  Else {$FindDate=""}
  Return $FindDate 
}

#$a=convertDate("20210704182913.353000+000")
#echo $a
Get-WMIObject -class Win32_UserProfile| Where {(!$_.Special) -and (ConvertDate($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object

Open in new window

You can also remove the part "|measure" to have the list of profiles detected. The LocalPath field should indicate the name of found profiles.


I tried to adapt the script so that it in UK date (04/07/2021 4th July 2021):
----------
function ConvertDate($LastDate){
  $d=[string]$LastDate
  if($d.length -ge 25){
    $t=$d.split(".")
    $d=$t[0]
    $findDate=[System.DateTime]::ParseExact($d,'ddMMyyyyHHmmss',[System.Globalization.DateTimeFormatInfo]::CurrentInfo)
    }
  Else {$FindDate=""}
  Return $FindDate
}

#$a=convertDate("20210704182913.353000+000")
#echo $a
Get-WMIObject -class Win32_UserProfile| Where {(!$_.Special) -and (ConvertDate($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object

Got this error:
Exception calling "ParseExact" with "3" argument(s): "The DateTime represented by the string is not supported in
calendar System.Globalization.GregorianCalendar."
At line:6 char:5
+     $findDate=[System.DateTime]::ParseExact($d,'ddMMyyyyHHmmss',[Syst ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

Correction:
---------
function ConvertDate($LastDate){
  $d=[string]$LastDate
  if($d.length -ge 25){
    $t=$d.split(".")
    $d=$t[0]
    $findDate=[System.DateTime]::ParseExact($d,'ddMMyyyyHHmmss',[System.Globalization.DateTimeFormatInfo]::CurrentInfo)
    }
  Else {$FindDate=""}
  Return $FindDate
}

#$a=convertDate("04072021182913.353000+000")
#echo $a
Get-WMIObject -class Win32_UserProfile| Where {(!$_.Special) -and (ConvertDate($_.LastUseTime) -lt (Get-Date).AddDays(-60))}| Measure-Object

Same error
Have you verified the actual format of LastUseTime?

    • You can try and test the function directly with a specific value.
Is the content of $a in comment corresponding at your data? 
The server time is set to ddMMyyyy
ie 04/07/2021
Should I be using the / characters?
No, that is not linked to your server time:

Can you indicate what are the numbers displayed by this command ? (here is a sample on my system)

PS U:\test> Get-WMIObject -class Win32_UserProfile | ft lastusetime

lastusetime
-----------
20210704201058.640000+000

Open in new window

lastusetime
-----------
20210704082448.091000+000
20210704082447.966000+000
20210704201545.436000+000
20210704201545.444000+000
20210704082447.716000+000
20210704082447.591000+000
20210704082447.419000+000
20210704082447.138000+000
20210704082446.872000+000
20210704082442.997000+000
20210704082446.684000+000
20210704082446.544000+000
20210704082446.388000+000
20210704082446.247000+000
20210704082446.091000+000
20210704082445.919000+000
20210704082445.684000+000
20210704082445.528000+000
20210704082445.450000+000
20210704082445.403000+000
20210704082445.356000+000
20210704082445.278000+000
20210704082445.184000+000
20210704082445.122000+000
20210704082445.044000+000
20210704082444.966000+000
20210704082444.903000+000
20210704201546.366000+000
20210704082444.778000+000
20210704201546.408000+000
20210704201546.415000+000
20210704201546.422000+000
Is this suggesting ALL profiles have been used today?

So you should change nothing in the script that I proposed. The format is the same:
Year (4 digits), Month, day, hour, minutes, seconds.

All dates are for the 4th of July.
Perhaps you should use another approach :

PS U:\test> get-childitem c:\users |ft name,lastwritetime,lastaccesstime

Name          LastWriteTime       LastAccessTime
----          -------------       --------------
administrator 04/06/2020 10:19:46 04/07/2021 12:18:46
annick        11/05/2021 16:40:17 04/07/2021 12:18:46
Public        25/11/2020 15:41:03 04/07/2021 22:09:05
UserNa.DOMAIN 01/07/2021 19:35:12 04/07/2021 21:50:29

Open in new window

Using that I get the following:
-------------------------------------------------------
PS C:\Users\administrator.VIVANT> get-childitem c:\users |ft n

Name                 LastWriteTime       LastAccessTime
----                 -------------       --------------
.NET v4.5            06/06/2018 16:37:33 06/06/2018 16:37:33
.NET v4.5 Classic    06/06/2018 16:37:29 06/06/2018 16:37:29
aaron                22/06/2020 15:44:49 22/06/2020 15:44:49
adam                 18/03/2021 13:27:44 18/03/2021 13:27:44
Administrator        29/03/2019 16:58:07 29/03/2019 16:58:07
administrator.VIVANT 02/07/2021 09:17:39 02/07/2021 09:17:39
carmine.pertosa      09/09/2020 11:10:30 09/09/2020 11:10:30
carolann.tullett     22/04/2021 15:42:40 22/04/2021 15:42:40
carolina.murcia      13/02/2019 11:15:56 13/02/2019 11:15:56
Cody.Dixon           20/03/2020 13:00:45 20/03/2020 13:00:45
Daniel.Crockett      02/07/2021 14:13:53 02/07/2021 14:13:53
darren.chowrimootoo  02/07/2021 08:46:41 02/07/2021 08:46:41
Hatice.Yusuf         18/02/2019 15:56:01 18/02/2019 15:56:01
inventas.admin       31/03/2020 13:39:18 31/03/2020 13:39:18
ishaq.javed          28/04/2021 12:00:36 28/04/2021 12:00:36
John.Zierold         10/02/2020 14:04:09 10/02/2020 14:04:09
jordan.kyriakou      24/11/2020 14:04:31 24/11/2020 14:04:31
krishna.mehta        02/07/2021 09:10:57 02/07/2021 09:10:57
Matt.Smith           20/03/2020 12:27:45 20/03/2020 12:27:45
michael.c            06/06/2019 20:23:40 06/06/2019 20:23:40
MSSQL$MICROSOFT##WID 01/01/2020 16:55:35 01/01/2020 16:55:35
neil.moonesawmy      02/07/2021 10:31:38 02/07/2021 10:31:38
nicole.czaja         10/02/2020 13:58:23 10/02/2020 13:58:23
peter.pieri          13/03/2020 12:31:55 13/03/2020 12:31:55
Public               12/09/2016 12:34:54 12/09/2016 12:34:54
Saidullah.Rahmani    08/01/2019 11:05:51 08/01/2019 11:05:51
sean.kapoor          13/09/2019 14:03:33 13/09/2019 14:03:33
shoheb.rahman        02/07/2021 09:09:26 02/07/2021 09:09:26
Voyager              28/06/2019 10:00:29 28/06/2019 10:00:29
Yasmin.Cadinouche    14/04/2020 10:23:34 14/04/2020 10:23:34


So you have interesting dates with old access on some profiles you can use this command:
get-childitem c:\users |where { $_.lastaccesstime -lt (Get-Date).AddDays(-60)}

Open in new window


Now, you just have to remove selected folder, after selecting only "regular" users and not "system" users.
Almost there
Can you give me the script as an example of a folder removal please
Here is a folder removal with all the content (you need full permissions on the folders):

remove-item c:\users\user1 -force -Recurse

Open in new window

Can I use the powershell to do this - ie select all folders older than a certain date and delete them, but using the powershell as above with the changed script:

Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))} | Remove-WmiObject –WhatIf
You could use the folders found (with ChildItem), to search with Get-wmiobject the Win32_Userprofile where the localpath correspond.

get-WMIObject -class Win32_UserProfile | Where { $_.localpath -eq "c:\users\folderFound"}

Open in new window


A complete script could be something like :

$f=get-childitem c:\users |where { $_.lastwritetime -lt (Get-Date).AddDays(-60)} | select fullname
foreach ($i in $f){ $g=$i.fullname; get-WMIObject -class Win32_UserProfile | Where { $_.localpath -like "$g"} }

Open in new window


Sorry - still not clear to me
How do I use this:

$f=get-childitem c:\users |where { $_.lastwritetime -lt (Get-Date).AddDays(-60)} | select fullname foreach ($i in $f){ $g=$i.fullname; get-WMIObject -class Win32_UserProfile | Where { $_.localpath -like "$g"} }
 
In this script:
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))} | Remove-WmiObject –WhatIf

And to keep user profiles such as system, network service, local administrator etc

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

Would it be:
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-60))} | Remove-WmiObject –WhatIf | Remove-WmiObject –WhatIf
ASKER CERTIFIED SOLUTION
Avatar of DEMAN-BARCELO (MVP) Thierry
DEMAN-BARCELO (MVP) Thierry
Flag of France image

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Sorry
Still unclear - I am not so versed in Powershell as you are
What is the script to remove all profiles older than 60 Days and exclude profiles such as system, network service, local administrator etc
Can you give the whole working script together so I understand please
I think we are almost there with this
What does the -WhatIf do?
Or does this script do that:

$f=get-childitem c:\users |where { $_.lastwritetime -lt (Get-Date).AddDays(-60)} | select fullname
foreach ($i in $f){ $g=$i.fullname; get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and (!$_.Loaded) -and ($_.localpath -like "$g")} } | Remove-WmiObject –WhatIf

Again - what is the function of the WhatIf please
I am not sure about the following:
{(!$_.Special) -and (!$_.Loaded)
I know the ! means do not remove but what are .Special and .Loaded referring to
SOLUTION
Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
Thanks so much for your help
Great to work with a PowerShell script expert