Active Directory with PowerShell, ADSI, and LDAP

powershell hero 16 9 ratio
In a previous article, we began looking at alternative ways to manage Active Directory (AD) with PowerShell using an ADSI type of accelerator and the WinNT moniker. One advantage is that with WinNT you do not need to know any details about what you are querying. You do not need to know what OU or container and object might reside because it does not matter. Everything via WinNT is relatively flat. You will not have access to the AD properties that you probably want to use. So let’s see what we can do with the LDAP moniker, starting at the domain level.

[ADSI]$domain = "LDAP://DC=globomantics,DC=local"

As before, the LDAP moniker is case-sensitive. All you need to do is specify the distinguished name of the object you want to access. In this case, it is my domain. This object has much more detail.

LDAP domain object (Image Credit: Jeff Hicks)
LDAP Domain Object (Image Credit: Jeff Hicks)

I excluded a few properties so that I could fit a bit more into the screen shot. You will notice that WinNT values are arrays and COM objects. Therefore, expect to make a little effort in displaying results.

$domain | Select @{Name = "Name";Expression = {$_.Name.value}},
@{Name = "DN";Expression = {$_.DistinguishedName.value}},
@{Name = "Created";Expression = {$_.whencreated.value}},
@{Name = "Modified";Expression = {$_.whenchanged.value}}
Selecting Domain Properties (Image Credit: Jeff Hicks)
Selecting Domain Properties (Image Credit: Jeff Hicks)

In the previous article, we grabbed the domain’s password properties. We can do the same thing here but it is a bit more complicated.

Raw domain password properties (Image Credit: Jeff Hicks)
Raw Domain Password Properties (Image Credit: Jeff Hicks)

The values are there but they are wrapped up in COM objects. It is not as simple as selecting the value. In this case, you need to do some fancy COM invocations to extract the values for these particular properties. This is one of those cases where you would never figure it out on your own.
At some point, the PowerShell team added a method to all directory service objects called ConvertLargeIntegerToInt64.

$domain.ConvertLargeIntegerToInt64($domain.minPwdAge.value)

If for some reason you have an older version of PowerShell without this method, you can use this function instead:

Function Convert-ADSLargeInteger {
# Take a large value integer and return a 32 bit value
[cmdletbinding()]
Param(
[Parameter(Position = 0, Mandatory)]
[object]$adsLargeInteger
)
$highPart = $adsLargeInteger.GetType().InvokeMember("HighPart",'GetProperty',$null, $adsLargeInteger, $null)
$lowPart = $adsLargeInteger.GetType().InvokeMember("LowPart",'GetProperty', $null, $adsLargeInteger, $null)
$bytes = [System.BitConverter]::GetBytes($highPart)
$tmp = [System.Byte[]]@(0,0,0,0,0,0,0,0)
[System.Array]::Copy($bytes, 0, $tmp, 4, 4)
$highPart = [System.BitConverter]::ToInt64($tmp, 0)
$bytes = [System.BitConverter]::GetBytes($lowPart)
$lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
Write-Output ($lowPart + $highPart)
}

Do not worry too much about understanding how all of this works. It just does.

Converting large integers (Image Credit: Jeff Hicks)
Converting large Integers (Image Credit: Jeff Hicks)

That number should look somewhat familiar based on what I got using the WinNT moniker. In this case, this is a value in ticks, not seconds. 1 second equals 10000000 ticks.

$t = ($domain.ConvertLargeIntegerToInt64($domain.minPwdAge.value) /10000000
(new-timespan -seconds $t).ToString() #result: -1.00:00:00

With this concept, I can use PowerShell code to display more meaningful results.

$domain | Select @{Name = "Domain";Expression = {$_.Name.value}},
@{Name = "pwdHistoryLength";Expression = { $_.pwdHistoryLength}},
@{name = "minPwdAge";Expression = { new-timespan -seconds (($_.ConvertLargeIntegerToInt64($_.minPwdAge.value)) /10000000)}},
@{name = "maxPwdAge";Expression = { new-timespan -seconds (($_.ConvertLargeIntegerToInt64($_.minPwdAge.value)) /10000000)}}
Formatted password properties (Image Credit: Jeff Hicks)
Formatted Password Properties (Image Credit: Jeff Hicks)

The LDAP domain object also has different types of children.

LDAP child objects (Image Credit: Jeff Hicks)
LDAP Child Objects (Image Credit: Jeff Hicks)


What about users, groups, and computers? Those are child objects of things like containers and organizational units.

Listing domain OUs (Image Credit: Jeff Hicks)
Listing Domain OUs (Image Credit: Jeff Hicks)

Once you know the path, you can create a new instance of that object.

[ADSI]$employees = "LDAP://ou=employees,dc=globomantics,dc=local"
An OU ADSI object (Image Credit: Jeff Hicks)
An OU ADSI Object (Image Credit: Jeff Hicks)

Hopefully, you are picking up on a pattern here.

Getting OU children (Image Credit: Jeff Hicks)
Getting OU Children (Image Credit: Jeff Hicks)

You can pipe this to Get-Member to discover property names. Here is a quick, unformatted example of how you might use them:

$employees.children.where({$_.schemaclassname -eq 'user'}) |
Select DistinguishedName,sAMAccountName,Name,GivenName,Sn,Title,Description,WhenCreated,WhenChanged
OU user accounts (Image Credit: Jeff Hicks)
OU User Accounts (Image Credit: Jeff Hicks)


There is much more we can do with user accounts and ADSI, but I will leave that for next time.

Related Article: