Recently I have been creating my own module to try and replicate the functionality of Microsoft’s “ActiveDirectory” PowerShell module, but using only the .NET System.DirectoryServices namespace (often referred to as ADSI). I realise that there are already a couple of modules that try and provide the same functionality (AdsiPS and PSAD), but this was partly a learning exercise for myself and neither of the modules provided everything I required.
So I set about creating my own module, and one of the first problems I encountered was trying to replicate all the user attributes that are returned via Get-ADUser. Many of these attributes are not regular LDAP attributes retrieved from Active Directory, but rather are specially constructed (or calculated) based off a different attribute.
Originally I set out to find how to get the CannotChangePassword attribute, but I discovered that this is calculated from the NTSecurityDescriptor attribute, which itself requires a little work to format correctly. So let’s start with NTSecurityDescriptor and we can look at CannotChangePassword in the next post.
The module I am writing will focus on searching AD via ADSI, so I wrote a small script to search AD for a specific username and return just the DistinguishedName and NTSecurityDescriptor attributes.
$User = 'TestUser'
$Properties = @('distinguishedname','ntsecuritydescriptor')
$Searcher = [adsisearcher]"(samaccountname=$User)"
$Searcher.PropertiesToLoad.AddRange($Properties)
$Results = $Searcher.FindAll()
$Results | Foreach-Object {
$UserObject = [pscustomobject]@{
'DistinguishedName' = $($_.Properties.distinguishedname)
'NTSecurityDescriptor' = $($_.Properties.ntsecuritydescriptor)
}
$UserObject
}
Running it returns an object with the 2 properties I was looking for:
PS C:\Scripts> .\Get-TestUser.ps1
DistinguishedName NTSecurityDescriptor
----------------- --------------------
CN=Test User,CN=Users,DC=test,DC=lab {1, 0, 20, 140...}
However when I compared the value of NTSecurityDescriptor returned via ADSI and the same value returned via Get-ADUser, I can see that they are different. ADSI returns it as System.Byte[]
and Get-ADUser returns it as System.DirectoryServices.ActiveDirectorySecurity
. So I went back to searching how to convert the ADSI value to the Get-ADUser one.
I didn’t actually find my answer, but during the search I stumbled upon this post from Richard Siddaway and in it he details a number of ways of getting security settings from an AD object. One of the methods is via ADSI, and I can see that if he binds directly to an object, he can access the ObjectSecurity property, which looks like the thing I’m looking for.
Since I’m performing a search (and potentially getting more than one result), I actually get a System.DirectoryServices.SearchResult
object returned instead. But the directory object itself is included in that SearchResult object, and can be retrieved via the GetDirectoryEntry()
method on the search result.
So I modified the script to use the GetDirectoryEntry()
method, and then return the ObjectSecurity property from that, rather than returning the LDAP byte array that it returned before.
$User = 'TestUser'
$Properties = @('distinguishedname','ntsecuritydescriptor')
$Searcher = [adsisearcher]"(samaccountname=$User)"
$Searcher.PropertiesToLoad.AddRange($Properties)
$Results = $Searcher.FindAll()
$Results | Foreach-Object {
$UserObject = [pscustomobject]@{
'DistinguishedName' = $($_.Properties.distinguishedname)
'NTSecurityDescriptor' = $($_.GetDirectoryEntry().ObjectSecurity)
}
$UserObject
}
Running this updated script, it appears to return something similar to Get-ADUser.
PS C:\Scripts> .\Get-TestUser.ps1
DistinguishedName NTSecurityDescriptor
----------------- --------------------
CN=Test User,CN=Users,DC=test,DC=lab System.DirectoryServices.ActiveDirectorySecurity
PS C:\Scripts> Get-ADUser TestUser -Properties NTSecurityDescriptor | Select-Object DistinguishedName,NTSecurityDescriptor
DistinguishedName NTSecurityDescriptor
----------------- --------------------
CN=Test User,CN=Users,DC=test,DC=lab System.DirectoryServices.ActiveDirectorySecurity
Looking good. I accessed the first 3 entries under the “Access” sub-property using both methods, to confirm I was accessing the same data.
PS C:\Scripts> (.\Get-TestUser.ps1).NTSecurityDescriptor.Access | Select -First 3
ActiveDirectoryRights : ExtendedRight
InheritanceType : None
ObjectType : ab721a53-1e2f-11d0-9819-00aa0040529b
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Deny
IdentityReference : Everyone
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
ActiveDirectoryRights : ExtendedRight
InheritanceType : None
ObjectType : ab721a53-1e2f-11d0-9819-00aa0040529b
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Deny
IdentityReference : NT AUTHORITY\SELF
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
ActiveDirectoryRights : GenericRead
InheritanceType : None
ObjectType : 00000000-0000-0000-0000-000000000000
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : None
AccessControlType : Allow
IdentityReference : NT AUTHORITY\SELF
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
PS C:\Scripts> (Get-ADUser TestUser -Properties NTSecurityDescriptor | Select-Object DistinguishedName,NTSecurityDescriptor).NTSecurityDescriptor.Access | Select -First 3
ActiveDirectoryRights : ExtendedRight
InheritanceType : None
ObjectType : ab721a53-1e2f-11d0-9819-00aa0040529b
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Deny
IdentityReference : Everyone
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
ActiveDirectoryRights : ExtendedRight
InheritanceType : None
ObjectType : ab721a53-1e2f-11d0-9819-00aa0040529b
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : ObjectAceTypePresent
AccessControlType : Deny
IdentityReference : NT AUTHORITY\SELF
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
ActiveDirectoryRights : GenericRead
InheritanceType : None
ObjectType : 00000000-0000-0000-0000-000000000000
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : None
AccessControlType : Allow
IdentityReference : NT AUTHORITY\SELF
IsInherited : False
InheritanceFlags : None
PropagationFlags : None
This will be very helpful for my next task, which is calculating the CannotChangePassword attribute using NTSecurityDescriptor.