Here is the code to remove user profiles remotely with the following constraints/rules:
########## List of accounts/Student profiles older than 21 days and not test accounts to be deleted #######################
foreach ($computer in (Get-Content e:\cleanupscript\scripts\ComputerList.csv)) {
if(Test-Connection -ComputerName $computer -count 1){
$userfolders = Get-ChildItem "\\$computer\C$\users\" | Select-Object -ExpandProperty Name
}
#The list of accounts, which profiles must not be deleted
$ExcludedUsers ="Public","administrator","csimg",”DefaultAppPool”, "ksnproxy", "test1","test2","test3","test4","test5","test6","test7","test8","test9","test10","test11","test12","test13","test14","test15","test16","test17","test18","test19","test20","test21","test22","test23","test24","test25","test26","test27","test28","test29","test30","test31","test32","test33","test34","test35","test36","test37","test38","test39","test40","test41","test42","test43","test44","test45","test46","test47","test48","test49","test50","test51","test52","test53","test54","test55","test56","test57","test58","test59","test60","test61","test62","test63","test64","test65","test66","test67","test68","test69","test70","test71","test72","test73","test74","test75","test76","test77","test78","test79","test80","test81","test82","test83","test84","test85"
$StudentsProfiles = Get-WmiObject -ComputerName $computer -Class Win32_UserProfile |
Select-Object PSComputerName,
Sid,
LocalPath,
@{Label='LastUseTime';Expression={$_.ConvertToDateTime($_.LastUseTime)} } |
Where-Object -FilterScript {![System.String]::IsNullOrWhiteSpace($_.LastUseTime)} |
Where-Object -FilterScript {!($_.LocalPath -eq "C:\Windows\ServiceProfiles\ksnproxy")} |
Where-Object -FilterScript {!($ExcludedUsers -like ($_.LocalPath.Replace("C:\Users\","")))} |
Sort-Object -Property {($_.LastUseTime)} |
Select-Object -First 3 |
foreach ($StudentsProfile in $StudentsProfiles)
{
#Write-Host $StudentsProfile.LocalPath, $computer, $StudentsProfile.LastUseTime, "profile existing” -ForegroundColor Red
if (!($ExcludedUsers -like ($StudentsProfile.LocalPath.Replace("C:\Users\",""))))
{
$StudentsProfile | Remove-WmiObject
Write-Host $computer, $StudentsProfile.LocalPath, $StudentsProfile.LastUseTime, "profile deleted” -ForegroundColor Magenta
}
}
Remove-WmiObject : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the
input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:7
+ $st | Remove-WmiObject -Authentication Connect
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (@{PSComputerNam...-07 6:49:46 AM}:PSObject) [Remove-WmiObject], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.RemoveWmiObject
As an aside: The CIM cmdlets (e.g., Get-CimInstance
) superseded the WMI cmdlets (e.g., Get-WmiObject
) in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell (Core) (v6+), where all future effort will go, doesn't even have them anymore. Note that WMI still underlies the CIM cmdlets, however. For more information, see this answer.
When Remove-WmiObject
receives input from the pipeline, it expects input objects of type System.Management.ManagementObject
, which bind to its -InputObject
parameter.
By contrast, your $StudentsProfiles
variable does not contain objects of this type, because you've applied Select-Object
with property names to the original Get-WmiObject
call, which creates objects of a general purpose type, [pscustomobject]
, that is unrelated to the type of the input objects, and simply contains the specified properties with values copied from the input object's properties of the same name.
Therefore, piping the objects in collection $StudentsProfiles
to Remove-WmiObject
causes the error you saw: the input objects are of the wrong type.
Solution:
Omit the Select-Object PSComputerName, ...
call and limit the pipeline to mere filtering (Where-Object
calls, the Select-Object
call with -Last
) and sorting commands, neither of which change the identity of the input objects:
Using a streamlined version of your pipeline:
$StudentsProfiles = Get-WmiObject -ComputerName $computer -Class Win32_UserProfile |
Where-Object -FilterScript {
$_.LastUseTime -and
$_.LocalPath -ne "C:\Windows\ServiceProfiles\ksnproxy" -and
(Split-Path -Leaf $_.LocalPath) -notin $ExcludedUsers
} |
Sort-Object LastUseTime |
Select-Object -First 3
Note: $_.ConvertToDateTime($_.LastUseTime)
- i.e. explicit conversion to [datetime]
- isn't actually needed:
To test if the value is non-empty (non-null), $_.LastUseTime
- which is a string representing a timestamp, can be used as-is, because interpreting $null
or ''
(empty string) as a Boolean results in $false
, whereas any nonempty string yields $true
.
Similarly, LastUseTime
can be passed to Sort-Object
to sort by this property value directly, because the string representation of the timestamp - e.g. '20211110143544.500000-300'
- is constructed in a way that lexical sorting is equivalent to chronological sorting.
As an aside: The preferred CIM cmdlets have an advantage over the WMI cmdlets in this respect: they represent timestamps directly as [datetime]
- no conversion needed.