Get an AD Object without RSAT and fast

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
  }
}

 

IT Droplets

IT Droplets