Get-ADComputer and Negative Comparisons

So, I’m running into an interesting problem with the Get-ADComputer cmdlet. What I am trying to do is get a list of Computers objects that haven’t logged in in more than 90 days. That part works fine, however when I attempt to filter out Cluster objects I’m running into an issue.

There are 219 total objects in the OU that I’m searching. Here is my basic command:
$Servers = Get-ADComputer -LDAPFilter ‘(name=*)’ -SearchBase ‘OU=Servers,DC=my,DC=domain,DC=com’ -Properties * | Where-Object { $.servicePrincipalNames -notlikeMSClusterVirtualServer’ } | Where-Object { $.LastLogonDate -lt (Get-Date).AddDays(-90) } | Sort-Object CN

The portion in bold does not evaluate correctly and all 219 objects are returned, however, if I try using -like it correctly catches only the Cluster objects and only 17 objects are returned. Can anyone explain to me why -like works correctly and -notlike wouldn’t?

Could you please format your code as code here in the forum? Thanks.
As far as I know the attribute servicePrincipalName is a collection. So you cannot use -notlike as it is. you could try it like this:

$Servers = Get-ADComputer -LDAPFilter ‘(name=*)’ -SearchBase ‘OU=Servers,DC=my,DC=domain,DC=com’ -Properties * |
Where-Object { (($.servicePrincipalNames) -join ‘-’) -notlike ‘MSClusterVirtualServer’ } |
Where-Object { $
.LastLogonDate -lt (Get-Date).AddDays(-90) } |
Sort-Object CN

Thank you for the reply. Sorry, I didn’t see a tag in the editor to format it as code, otherwise I would have. Could you explain to me why you can use LIKE against a collection, but you can’t use NOTLIKE?

Also, is it odd that when I dot select the object it is formatted as String? It is a collection when you use Select-Object, I get that.

PS C:\Windows\system32> $Servers | Select servicePrincipalNames | gm


   TypeName: Selected.Microsoft.ActiveDirectory.Management.ADComputer

Name                  MemberType   Definition
----                  ----------   ----------
Equals                Method       bool Equals(System.Object obj)
GetHashCode           Method       int GetHashCode()
GetType               Method       type GetType()
ToString              Method       string ToString()
servicePrincipalNames NoteProperty ADPropertyValueCollection servicePrincipalNames=Microsoft.ActiveDirectory.Managem...


PS C:\Windows\system32> $Servers.servicePrincipalNames | gm


   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB), int IComparab...
Contains         Method                bool Contains(string value)
CopyTo           Method                void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int co...
Could you explain to me why you can use LIKE against a collection, but you can't use NOTLIKE?
Unfortunately not. I just had a similar problem once and solved it this way. I'm used to work solution oriented. If it does the job I don't care why it does the job. :-D But I'm pretty sure we have some folks here in the forum able to explain this behaviour in detail. Folks? Someone? .... BTW: Does it do the job? ;-)

Not sure, I had already gotten what I needed with the following. I will check your solution later though, as that’s a solution I would not have come up with myself, so thank you.

$Servers = Get-ADComputer -LDAPFilter '(name=*)' -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { $_.LastLogonDate -lt (Get-Date).AddDays(-90) }
$Clusters = Get-ADComputer -LDAPFilter '(name=*)' -SearchBase 'OU=Servers,DC=my,DC=domain,DC=com' -Properties * | Where-Object { $_.servicePrincipalNames -like 'MSClusterVirtualServer' }
$Array = @()

ForEach ($Server in $Servers) {
    If ($Clusters.Name -notcontains $Server.Name) {
        $Array += $Server
    }
}
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

You can try the following command :

get-adcomputer -filter * -prop LastLogonDate | ? {$_.LastLogonDate -gt (Get-Date).AddDays(-104)} | select name,lastlogondate

For moreinformation, please checkout the below article.

http://www.out-null.eu/2014/04/17/howto-find-all-users-in-active-directory-who-havent-logged-in-longer-than-90-days/

Here is an article which explain the property and why it lags behind the actual last logon of a device:

http://blogs.technet.com/b/askds/archive/2009/04/15/the-lastlogontimestamp-attribute-what-it-was-designed-for-and-how-it-works.aspx

You may also automated solution which provides accurate last logon, users that have never logged on, and more