PowerShell - Find Inactive Computers in Active Directory with ADSI
Today I wanted to retrieve inactive computer accounts in the Active Directory without using the Quest Active Directory Snapin or the Active Directory Module. Yes… It happens that you work on a computer that don’t have those tools once in a while, and I thought It would be fun to have a script without requirements…
Note: BTW, the following solution might not be the best or most efficient, so let me know if you know a faster/easier way to do this, I’m willing to learn more about querying AD.
Here are the key element of the script, I want:
- Computer Inactive for >=90 days
- Be able to specify a SearchRoot
- Filter on the Operating System if possible (I want only Windows Servers, without the Domain controllers for example)
- Return SamAccountName, Name, DN, Operating System, and Description
- Limit the number of object to return (can be useful for large environment)
[adsisearcher]
I already talked about ADSISearcher in a previous postso I won’t give too much details about it. [adsisearcher] type accelerator is used to search Active Directory Domain Services (ADDS)
After some research and tests I quickly got the following line which return the basic information of what I want:
([adsisearcher]"(&(objectcategory=computer)(lastlogontimestamp<=$((Get-Date).AddDays(-105).ToFileTime())))").findall()
Output:
Path Properties
---- ----------
LDAP://CN=XAVIERLAPTOP,CN=Computers,DC=FX,DC=LAB {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1VC01,OU=Servers,OU=TEST,DC=FX,DC... {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1VH02,OU=Servers,OU=TEST,DC=FX,DC... {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1VH01,OU=Servers,OU=TEST,DC=FX,DC... {logoncount, codepage, objectcategory, descrip...
LDAP://CN=DHCP1,CN=Computers,DC=FX,DC=LAB {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1SQL01,OU=Servers,OU=TEST,DC=FX,D... {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1CM01,CN=Computers,DC=FX,DC=LAB {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1OR01,OU=Servers,OU=TEST,DC=FX,DC... {logoncount, codepage, objectcategory, descrip...
LDAP://CN=LAB1VC02,OU=Servers,OU=TEST,DC=FX,DC... {logoncount, codepage, objectcategory, descrip...
Next the properties. If we a take look at the list of properties and methods available with this object we might be able to find what we need. We can do this using Get-Member
([adsisearcher]"(&(objectcategory=computer)(lastlogontimestamp<=$((Get-Date).AddDays(-105).ToFileTime())))") | Get-Member
Output:
TypeName: System.DirectoryServices.DirectorySearcher
Name MemberType Definition
---- ---------- ----------
Disposed Event System.EventHandler Disposed(System.Object, System.EventArgs)
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
FindAll Method System.DirectoryServices.SearchResultCollection FindAll()
FindOne Method System.DirectoryServices.SearchResult FindOne()
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
ToString Method string ToString()
Asynchronous Property bool Asynchronous {get;set;}
AttributeScopeQuery Property string AttributeScopeQuery {get;set;}
CacheResults Property bool CacheResults {get;set;}
ClientTimeout Property timespan ClientTimeout {get;set;}
Container Property System.ComponentModel.IContainer Container {get;}
DerefAlias Property System.DirectoryServices.DereferenceAlias DerefAlias {get;set;}
DirectorySynchronization Property System.DirectoryServices.DirectorySynchronization DirectorySynchronization {get...
ExtendedDN Property System.DirectoryServices.ExtendedDN ExtendedDN {get;set;}
Filter Property string Filter {get;set;}
PageSize Property int PageSize {get;set;}
PropertiesToLoad Property System.Collections.Specialized.StringCollection PropertiesToLoad {get;}
PropertyNamesOnly Property bool PropertyNamesOnly {get;set;}
ReferralChasing Property System.DirectoryServices.ReferralChasingOption ReferralChasing {get;set;}
SearchRoot Property adsi SearchRoot {get;set;}
SearchScope Property System.DirectoryServices.SearchScope SearchScope {get;set;}
SecurityMasks Property System.DirectoryServices.SecurityMasks SecurityMasks {get;set;}
ServerPageTimeLimit Property timespan ServerPageTimeLimit {get;set;}
ServerTimeLimit Property timespan ServerTimeLimit {get;set;}
Site Property System.ComponentModel.ISite Site {get;set;}
SizeLimit Property int SizeLimit {get;set;}
Sort Property System.DirectoryServices.SortOption Sort {get;set;}
Tombstone Property bool Tombstone {get;set;}
VirtualListView Property System.DirectoryServices.DirectoryVirtualListView VirtualListView {get;set;}
Looks like the following properties will do just what we need:
- SearchRoot (ADSI Object, Distinguished Name of the organization unit) this will be used to specify the root of the search
- SizeLimit (Integer), to limit the number of object in the output (Can be useful in large environment),
- PropertiesToLoad (String), to select the properties I want in the output,
- Filter (String/LDAP Query), to limit the query to computer with a specific Operating System.
$searcher = [adsisearcher]"(&(objectcategory=computer)(lastlogontimestamp<=$((Get-Date).AddDays(-90).ToFileTime())))"
$searcher.searchRoot = [adsi]"LDAP://OU=Servers,OU=TEST,dc=fx,dc=lab"
$searcher.SizeLimit = "5"
$searcher.Filter = "(&(objectCategory=computer)(operatingSystem=*Windows*server*))"
$searcher.PropertiesToLoad.AddRange(('name','samaccountname','cn','operatingsystem','description'))
$searcher.FindAll()
Output:
Name Value
---- -----
samaccountname {LAB1HYPE02$}
name {LAB1HYPE02}
operatingsystem {Windows Server 2012 R2 Standard}
cn {LAB1HYPE02}
adspath {LDAP://CN=LAB1HYPE02,OU=Servers,OU=TEST,DC=FX,DC=LAB}
samaccountname {LAB1HYPE01$}
name {LAB1HYPE01}
operatingsystem {Windows Server 2012 R2 Standard}
cn {LAB1HYPE01}
adspath {LDAP://CN=LAB1HYPE01,OU=Servers,OU=TEST,DC=FX,DC=LAB}
samaccountname {LAB1SQL01$}
description {SQL2012}
name {LAB1SQL01}
cn {LAB1SQL01}
operatingsystem {Windows Server 2012 Standard}
adspath {LDAP://CN=LAB1SQL01,OU=Servers,OU=TEST,DC=FX,DC=LAB}
samaccountname {LAB1OR01$}
description {SCORCH2012}
name {LAB1OR01}
cn {LAB1OR01}
operatingsystem {Windows Server 2012 Standard}
adspath {LDAP://CN=LAB1OR01,OU=Servers,OU=TEST,DC=FX,DC=LAB}
samaccountname {LAB1VC02$}
description {VMware vCenter}
name {LAB1VC02}
cn {LAB1VC02}
operatingsystem {Windows Server 2012 Standard}
adspath {LDAP://CN=LAB1VC02,OU=Servers,OU=TEST,DC=FX,DC=LAB}
The output is poorly formated and we have some extra curly brackets that need to be take care of… Let’s fix that by creating a new PowerShell object for each item retrieve by the query.
$searcher = [adsisearcher]"(&(objectcategory=computer)(lastlogontimestamp<=$((Get-Date).AddDays(-90).ToFileTime())))"
$searcher.searchRoot = [adsi]"LDAP://OU=Servers,OU=TEST,dc=fx,dc=lab"
$searcher.SizeLimit = "5"
$searcher.Filter = "(&(objectCategory=computer)(operatingSystem=*server*))"
$searcher.PropertiesToLoad.AddRange(('name','samaccountname','distinguishedname','operatingsystem','description'))
Foreach ($ComputerAccount in $searcher.FindAll()){
New-Object -TypeName PSObject -Property @{
Name = $ComputerAccount.properties.name -as [string]
SamAccountName = $ComputerAccount.properties.samaccountname -as [string]
DistinguishedName = $ComputerAccount.properties.distinguishedname -as [string]
OperatingSystem = $ComputerAccount.properties.operatingsystem -as [string]
Description = $ComputerAccount.properties.description -as [string]
}
}
DistinguishedName : CN=LAB1HYPE02,OU=Servers,OU=TEST,DC=FX,DC=LAB
Name : LAB1HYPE02
OperatingSystem : Windows Server 2012 R2 Standard
Description :
SamAccountName : LAB1HYPE02$
DistinguishedName : CN=LAB1HYPE01,OU=Servers,OU=TEST,DC=FX,DC=LAB
Name : LAB1HYPE01
OperatingSystem : Windows Server 2012 R2 Standard
Description :
SamAccountName : LAB1HYPE01$
DistinguishedName : CN=LAB1SQL01,OU=Servers,OU=TEST,DC=FX,DC=LAB
Name : LAB1SQL01
OperatingSystem : Windows Server 2012 Standard
Description : SQL2012
SamAccountName : LAB1SQL01$
DistinguishedName : CN=LAB1OR01,OU=Servers,OU=TEST,DC=FX,DC=LAB
Name : LAB1OR01
OperatingSystem : Windows Server 2012 Standard
Description : SCORCH2012
SamAccountName : LAB1OR01$
DistinguishedName : CN=LAB1VC02,OU=Servers,OU=TEST,DC=FX,DC=LAB
Name : LAB1VC02
OperatingSystem : Windows Server 2012 Standard
Description : VMware vCenter
SamAccountName : LAB1VC02$
That’s way better! Neat!
Leave a comment