Get an AD Object without RSAT and fast
In this article we’ll see the function I’ve built to get an AD Object without RSAT and fast. I’ve been thinking for a while to write a new function, mainly because I wanted to pass multiple SamAccountNames without having to write a filter. However I recently had to go through a ton of users and fast, this is when I though that I could finally write my custom function, which leverages System.DirectoryServices.DirectorySearcher, so it doesn’t even require RSAT.
There are a lot of guides are out there how to use it, this article is meant to share with you the function I built around that.
The things I like the most about this:
- It’s fast. The more objects you’re querying, the faster it’ll be compared to Get-ADObject/Get-ADUsers/Get-ADGroup.
- You can use it to query any kind of Object.
- You can pass multiple SamAccountNames (Sam1, Sam2, SamN), SIDs or DistinguishedNames.
- You can also choose to pass a partial parameter with a wildcard, for example: MyUserSam*
- You can also choose to write a plain LDAP Query instead of the SAM/SID/DN.
- Filter down for an account status with -AccountStatus. By default you’ll get both Enabled and Disabled.
Other features:
- You can still specify whatever Properties you need, including * for all of them.
- You can choose to display the LDAP filter the function has used to query AD.
- You can also choose to translate the SID. Unfortunately what you get back it’s not a string but a byte array.
- You can specify an objectCategory with -ObjectCategory without having to use a custom ldap filter.
- Also, you can confine the search to a specific OU (SearchRoot), use a specific Server and Port.
Here’s the code 🙂
function Get-FADObject { <# .SYNOPSIS This function allows you to get one or multiple AD Objects without RSAT, leveraging System.DirectoryServices.DirectorySearcher. The F stands for Fast as in theory this function is faster than Get-ADObject. The function is also easier to use as it doesn't require to specify an LDAP Filter (although you can). .DESCRIPTION This function allows you to get one or multiple AD Objects without RSAT. You can provide different types of inputs based on your needs. You can choose one of the following in order to search for one or more objects: 1. SamAccountName (or SAM). 2. DistinguishedName (or DN). 3. SID (or objectSID). 4. LDAPFilter. This gives you the flexibility to use the function dynamically and to provide right away an array of objects (for SAM, DN and SID), instead of calling the function for every object. For a full list of parameters and usage, look at the PARAMETERs and the EXAMPLEs. .PARAMETER SamAccountName A string or an array of SamAccountName(s). This can be used with its alias "SAM". .PARAMETER DistinguishedName A string or an array of DistinguishedName(s). This can be used with its alias "DN". .PARAMETER SID A string or an array of SID(s). This can be used with its alias "objectSID". .PARAMETER LDAPFilter A string containing a pure LDAP query. .PARAMETER SearchRoot A string containing the search root of your search, uselful to restrict the search to a specific OU. By defaul this is the current user's Domain (DC=domain,dc=ext). .PARAMETER Server A string containing a specific Server to run the query against. .PARAMETER Port The port number to be used for this query. By default this is set to 389. .PARAMETER Properties An array of specific properties to retrieve from the object. By default: 'samaccountname', 'distinguishedname', 'objectsid', 'enabled', 'objectClass', 'objectGUID', 'UserPrincipalName' .PARAMETER AccountStatus A string used to allow you to restrict the search to disabled, enabled or enabled and disabled objects. By default it searches for both Enabled an Disabled objects. .PARAMETER ObjectCategory An array of categories to allow to search for a specific set of categories. You can even search for both computers and users at the same time. .PARAMETER ShowLDAPFilter A flag that will allow you to return also the LDAP filter used in this search. This can be used with its alias "includeLDAPFilter". .PARAMETER TranslateSID A flag that will allow you to translate the objectSID from an array of bytes to a string. This may slow down the execution of the function. .PARAMETER ReturnAsPSCustomObject A flag that will allow you to return the result(s) as a PSCustomObject. This may slow down the script, depending on how many entries it needs to convert! .EXAMPLE Get an AD Object based on its samaccountname PS C:\> Get-FADObject -samaccountname myuser .EXAMPLE Get AD Objects based on 3 samaccountnames PS C:\> Get-FADObject -samaccountname myuser1, myuser2, myuser3 .EXAMPLE Get AD Objects based on 2 SIDs PS C:\> Get-FADObject -SID S-1-5-21-123456789-12345678-01234567890-123456, S-1-5-21-123456789-12345678-01234567890-123457 .EXAMPLE Get AD Objects based on 2 samaccountnames, restrict the search to a specific OU, a specific AD Server and use port 636 PS C:\> Get-FADObject -samaccountname myuser1, myuser2 -SearchRoot "OU=MyUsers,DC=Domain,DC=ext" -Server ADServer01.domain.ext -Port 636 .EXAMPLE Get AD Objects based on 1 samaccountname, and search for enabled user/person only PS C:\> Get-FADObject -samaccountname myuser1 -ObjectCategory user,person -AccountStatus Enabled .EXAMPLE Get AD Objects based on 1 samaccountname, and retrieve specific properties only PS C:\> Get-FADObject -samaccountname myuser1, myuser2 -Properties samaccountname, objectsid .EXAMPLE Get AD Objects based on 1 samaccountname, and retrieve all properties PS C:\> Get-FADObject -samaccountname myuser1, myuser2 -Properties * .EXAMPLE Get AD Objects based on 1 samaccountname, retrieve specific properties only and translate the SID PS C:\> Get-FADObject -samaccountname myuser1, myuser2 -Properties samaccountname, objectsid -TranslateSID:$true .EXAMPLE Get AD Objects based on 1 samaccountname, retrieve specific properties and return also the LDAP Filter PS C:\> Get-FADObject -samaccountname myuser1, myuser2 -Properties samaccountname, objectsid -ShowLDAPFilter:$true .EXAMPLE Get AD Objects based on a custom LDAP Filter, retrieve just the objectSID and translate it. PS C:\> Get-FADObject -LDAPFilter "(&(|(objectCategory=user)(objectCategory=person))(|(SamAccountName=myuser1)(SamAccountName=myuser2)))" -Properties objectSID -TranslateSID:$true .NOTES Author: Simone Corbisiero #> [cmdletbinding()] Param( [Parameter(Mandatory = $true, ParameterSetName = 'Set_SamAccountName')][alias ("SAM")] [array] $SamAccountName, [Parameter(Mandatory = $true, ParameterSetName = 'Set_DistinguishedName')][alias ("DN")] [array] $DistinguishedName, [Parameter(Mandatory = $true, ParameterSetName = 'Set_SID')][alias ("objectSID")] [array] $SID, [Parameter(Mandatory = $true, ParameterSetName = 'Set_LDAPFilter')] [string] $LDAPFilter, [Parameter(Mandatory = $false)] [string]$SearchRoot = "DC=" + (($env:USERDNSDOMAIN).split(".") -join ",DC="), [Parameter(Mandatory = $false)] [string]$Server, [Parameter(Mandatory = $false)] [int]$Port = 389, [Parameter(Mandatory = $false)] [array]$Properties = @('samaccountname', 'distinguishedname', 'objectsid', 'enabled', 'objectClass', 'objectGUID', 'UserPrincipalName'), [Parameter(Mandatory = $false)] [ValidateSet('Enabled', 'Disabled', 'EnabledAndDisabled')] [string]$AccountStatus = 'EnabledOrDisabled', [Parameter(Mandatory = $false, ParameterSetName = 'Set_SamAccountName')] [parameter(ParameterSetName = 'Set_DistinguishedName')] [parameter(ParameterSetName = 'Set_SID')] [array]$ObjectCategory, [Parameter(Mandatory = $false)][alias ("includeLDAPFilter")] [switch]$ShowLDAPFilter, [Parameter(Mandatory = $false)] [switch]$TranslateSID, [Parameter(Mandatory = $false)] [switch]$ReturnAsPSCustomObject ) $Searcher = New-Object -TypeName System.DirectoryServices.DirectorySearcher #Build the LDAPFilter, unless it was provided by the user if (!$LDAPFilter) { write-verbose "The function was called without a custom LDAP Filter, building it based on the user's selection" $LDAPFilter = "(&" if ($ObjectCategory) { $LDAPFilter += "(|(objectCategory=" + ($ObjectCategory -join ")(objectCategory=") + "))" } switch ($AccountStatus) { 'Enabled' { $LDAPFilter += "(!userAccountControl:1.2.840.113556.1.4.803:=2)" } 'Disabled' { $LDAPFilter += "(userAccountControl:1.2.840.113556.1.4.803:=2)" } } if ($DistinguishedName) { #Build the Distinguishedname search with an OR, useful when the user specifies multiple DNs but will work also with just 1. $LDAPFilter += "(|(distinguishedname=" + ($DistinguishedName -join ")(distinguishedname=") + "))" } elseif ($SamAccountName) { #Build the SamAccountName search with an OR, useful when the user specifies multiple SamAccountNames but will work also with just 1. $LDAPFilter += "(|(SamAccountName=" + ($SamAccountName -join ")(SamAccountName=") + "))" } elseif ($SID) { #Build the SID search with an OR, useful when the user specifies multiple SIDs but will work also with just 1. $LDAPFilter += "(|(objectSID=" + ($SID -join ")(objectSID=") + "))" } $LDAPFilter += ")" } $Searcher.Filter = $LDAPFilter #Check if the user calling the function wants to retrieve all properties. If we don't specify $searcher.PropertiesToLoad.AddRange, then all properties will be retrieved. if ($Properties -ne "*") { [void]$searcher.PropertiesToLoad.AddRange($Properties) } if ($Server) { $DomainDN = "LDAP://$($Server):$($Port)/$SearchRoot" } else { $DomainDN = "LDAP://:$($Port)/$SearchRoot" } $searcher.PageSize = 1000 #Needed to avoid results being trunkated at 2000, all paging will happen in background $Domain = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $DomainDN $Searcher.SearchRoot = $Domain write-verbose "Retrieving objects.." #Force $Results to be an array to avoid issues in the for cycle later on, when working with just 1 result. [Array]$Results = @() $Results = ($Searcher.FindAll()).Properties #Check if the function has to translate the SID (binary) to a string (S-1..). if ($TranslateSID -and $Results.objectsid) { write-verbose "Converting each entry's SID, this may slow down the execution of this function." foreach ($res in $Results) { $res.Add("SID", (New-Object System.Security.Principal.SecurityIdentifier(($res.objectsid)[0], 0)).Value) } } #Check if $ReturnAsPSCustomObject is true if ($ReturnAsPSCustomObject) { Write-Warning "The switch ReturnAsPSCustomObject was selected; Depending on the amount of results, this may slow down the process a lot!" $Results = foreach ($item in $Results) { New-Object PSObject -property $item } } #Return the results based on whether the function has to include the LDAP filter or not. if ($ShowLDAPFilter) { write-verbose "Adding the LDAPFilter to the results." Return @{ LDAPFilter = $LDAPFilter Objects = $Results } } else { Return $Results } }