Recently, I was chatting with my friend Gladys. As part of her work, she often creates Active Directory PowerShell-related tools for her colleagues. It is always good to be the toolmaker! Anyway, she doesn't want to rely on the Active Directory module from Remote Server Administration Tools (RSAT). There's no guarantee that the feature can be installed on a colleague's desktop, and for some of the tasks that need to be accomplished, it is overkill.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Instead, Gladys often builds PowerShell tools for managing bits and pieces of Active Directory using the [System.DirectoryServices] namespace. These .NET classes have been around a long time and are what we used before Microsoft released the Active Directory module. These classes should be found on your computer and do not rely on the Active Directory module. One of the go-to classes is the DirectorySearcher.
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
Gladys' company uses managed service accounts extensively, and she was searching for them in Active Directory. In my demonstration domain, I only have one.
$searcher.filter = "(&(objectClass=msDS-GroupManagedServiceAccount))"
$gmsa = $Searcher.FindOne()
The resulting object is a little different than what you might be familiar with. The properties are stored as a ResultPropertyCollection object.
Each property value is stored as a collection, so technically, you should use an index number to get the value, even if there is only a single item.
The property Gladys wanted to work with was msds-groupmsamembership, but when she got the value, it was a huge collection of numbers. It turns out that the result property was an array of bytes. The bytes represented a value. Gladys can see what the value should be using the Active Directory module.
This looks like a security object. Looking at the property with Get-Member confirms it.
Remember, Gladys is trying to avoid using the Active Directory module. She needs to take the array of bytes and turn it into an ActiveDirectorySecurity object.
During Gladys' research, she came across articles discussing how to convert the byte array into something more meaningful. But all of the articles were developer-oriented and relied on COM objects. I looked at these articles, and after banging my head against my desk for a bit and trying things in PowerShell, I finally made the mental connection on how to solve this.
Looking for Constructors
I knew I needed a [System.DirectoryServices.ActiveDirectorySecurity] object. So I checked to see if there was a constructor. That is, a method to create a new instance of the object. And there was.
This looks easy. I was hoping there was a way to use the bytes which I've saved to a variable.
[byte[]]$raw = $gmsa.Properties.'msds-groupmsamembership'[0]
But maybe the new ActiveDirectorySecurityObject will provide a clue, so I'll create one.
$adsec = [System.DirectoryServices.ActiveDirectorySecurity]::new()
This is why you need to know how to use Get-Member. I piped $adsec to Get-Member, looking for something that would give me a hint on how to use the array of bytes. I found a set method.
I already knew I needed to create some form of security descriptor, and this method takes an array of bytes as a parameter.
$adsec.SetSecurityDescriptorBinaryForm($raw)
I didn't get a result which isn't surprising since Get-Member indicates this method doesn't return anything. That is what 'void' is indicating. Did anything happen?
Success! I can pipe this to Get-Member to see what properties or methods I can use. Or pipe it to Select-Object.
Gladys can use this object in her PowerShell scripting.
Decoding the SID
But you know, there are a few more properties from the directory searcher result that might need to be converted. If for no other reason than to reinforce my process. The object's SID is also represented as a byte array.
[byte[]]$rawsid = $gmsa.properties.objectsid[0]
What type of object do I need to create?
I already knew the property name. You may need to select all properties to pipe to Get-Member and narrow down your list.
What kind of constructor options are available?
This looks promising!
$sid = [System.Security.Principal.SecurityIdentifier]::new($rawsid,0)
Better. But not ideal. Piping $sid to Get-Member for guidance, I find the Translate() method. I know from previous experience how to use the method. If you didn't, you might have to do some research to learn how to use it.
And this is what I expect since I already know the account name.
Object GUID
Remember, we're focusing on a process here. Let's try one more property and look at the object GUID, which is also stored as an array of bytes.
[byte[]]$rawguid = $gmsa.properties.objectguid[0]
I know this has to be a [System.Guid] object.
This looks easy.
Writing simple PowerShell functions to make these conversions wouldn't be that difficult.
Summary
I realize you may not have a need to use the DirectorySearcher, but you might find yourself in a situation with unknown data that you need to translate or convert. You might start with the process I've demonstrated here. If nothing else, I hope this has shown you the importance of knowing how to use Get-Member.
Comments and questions are welcome.
Thanks, as usual, Jeff!