Carlos Perez (@DarkOperator) recently posted on DarkOperator.com how to use PowerShell to get data from Active Directory. He is working on an Active Directory audit PowerShell project and is documenting most of the work put into it. He also covers leveraging functions for portability and using Pester to write better PowerShell code (as well as debug and handle error conditions better). Carlos also walks through how to properly code a PowerShell module as well as create and use a PowerShell project in GitHub (something I need to do better! 🙂 ). Anyone who uses PowerShell to gather Active Directory data should read these posts. The amount of detail he put into these posts is impressive and they are well worth reading!
I got in my head this week that I would like to write a Windows PowerShell module for getting information from Active Directory for the purpose of gathering information to aid in detecting miss configurations and also aid in incident response. My idea is to write the module and start publishing blog posts as I go through the process of writing the code and how I go about it. This will be my first experience with Pester also so I think it would be a fun adventure.
I start by setting goals for the module, these are:
- All output from each function will be objects.
- I will assign each object a custom type so I can create custom views for the output.
- The module must not depend on the ActiveDirectory module that ships with the different RSAT tools and use .NET and COM so as to leverage the use alternate credentials.
- Module should be able to pull information as a base for Users, Groups, Computers, Sites, Domains, Forest, OUs and GPOs.
- Module will be PSv3 or above so as to use new improvements int he latest versions of Windows PowerShell.
Carlos covers several scenarios that may arise when attempting to gather Active Directory forest data using PowerShell, including connecting to the current forest as well as others.
In the last blog post we covered setting the goals for the project, general guidelines, how I set up a project in GitHub and the creation of the module manifest. In this blog post we will cover some of the API around Active Directory that we can use in Windows PowerShell to access and query it either from a host already in the domain or with alternate credentials against a specific host.
Currently when working in Windows PowerShell there are 4 main ways to interact with Active Directory:
- Active Directory module – gets installed with RSAT or when then Domain Controller role is added to a server. Varies per version of Windows.
- System.DirectoryServices Namespace – it is a .Net wrapper around the ADSI (Active Directory Service Interface) COM object. It represents a specific path or Object in AD allowing for the pulling of information and modification.
- System.DirectoryServices.ActiveDirectory namespace – It provides several .Net classes that abstract AD services. Provides access to manipulating forest, domain, site, subnet, partition, and schema are part of the object model.
- System.DirectoryServices.AccountManagement namespace provides uniform access and manipulation of user, computer, and group security principals
Each one of the namespaces have their own peculiarities and uses. The most powerful one is classes under System.DirectoryServices do to the control it provides but with it comes more complexity, this is why it is used for those cases where the other 2 do not fit a specific role or complex searches of AD are required.
Extending the information
In the previous blog post when we look at the object returned it has all of the information properly parsed and shown so I do not have to run around parsing fields and converting them but for me a critical piece of information is not shown and that is the SID of the forest domain. If you have played with analysis of some logs and with Mimikatz attacks you know the SID is of great importance. For this we will use the System.DirectoryServices namespace, specifically the DirectoryEntry class that represents a path in AD.
We will create a helper function to generate the DirectoryEntry object, by creating the function we ensure we do not duplicate a lot of code unless we have to and will also make it easier to test.
Before we start coding lets define what we want to achieve and this is dictated in part by the APIs we want to use. in this case the Class has several constructors to create an instance of it:
We want to be able to get a DirectoryEntry int he following manners:
- For a specified path using the current user credentials.
- For a specified path using alternated credentials.
- For a specified path by connecting to a server and providing credentials