Could you explain to me why you can use LIKE against a collection, but you can't use NOTLIKE?
The short answer is that you can use comparison operators such as like against arrays/collections but they operate differently than with strings. When you use -like or -notlike operators against a collection then an array is returned rather than a boolean. As you performed the test in the where-object construct, which is expecting a boolean result, the array is cast as a boolean. The net result is that the array is not null and will always be cast to $true, and hence all the computer objects are returned.
The session below demonstrates the return types and how the casting always results in $true being returned.
PS C:\Users\Rob> $arr = ("Apple", "Orange", "Grapefruit")
PS C:\Users\Rob> ($arr -like "Apple").GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\Users\Rob> ($arr -notlike "Apple").GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\Users\Rob> ($arr -notlike "Apple") -as [Boolean]
True
PS C:\Users\Rob> ($arr -like "Apple") -as [Boolean]
True
PS C:\Users\Rob> ($arr -notlike "Pear") -as [Boolean]
True
PS C:\Users\Rob>
If you were to stick with the where-object you could change the test to use -notcontains.
$Servers = Get-ADComputer -LDAPFilter '(name=*)' -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { $_.servicePrincipalNames -notcontains 'MSClusterVirtualServer' } | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) } | Sort-Object CN
If the use of the wildcards is important to you then negating the result of the -like is the best approach.
$Servers = Get-ADComputer -LDAPFilter '(name=*)' -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { -not ($_.servicePrincipalNames -like '*MSClusterVirtualServer*') } | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) } | Sort-Object CN
And finally to add confusion to the mix, if you “filter left” in the get-ADComputer command you can use the -notlike operator and it will behave as you expect/hoped it would.
$Servers = Get-ADComputer -filter { (ServicePrincipalName -notlike "*MSClusterVirtualServer*") } -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) } | Sort-Object CN
However there is a corner case I discovered the hard way where some computers don’t have a ServicePrincipalName attribute, since it isn’t mandatory. The best way to deal with it is to negate the result of the affirmative test.
$Servers = Get-ADComputer -filter { -not (ServicePrincipalName -like "*MSClusterVirtualServer*") } -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) } | Sort-Object CN