Active Directory and Exchange Configuration Discovery using Powershell

Over the years I have assessed and analyzed many environments. Along the way I have run across many methods and tools for getting a configuration ‘snapshot’ while performing a discovery of a Microsoft environment. They all make calls to the Microsoft ‘management layer’ using different methods like, installing agents or direct access via WMI or code. They pull configuration data and provide some pretty cool canned reports around the data they gather.

These tools can be expensive for ‘integrators’ as most of these products use ‘site’ licensing, forcing many of us to find other ways to quickly take a snapshot of a company’s Active Directory and Exchange configuration.

 A few years ago, I came across a method for storing the output of multiple powershell commands into an array and then exporting them to the powershell version of XML. That xml data could then be parsed by a cool little reporting powershell module like Pscribo. That original work can be located here.

I have taken PShirwin’s original work and expanded upon it here. Each section can be used by itself or as a function as shown in the full script posted later in this document.

Active Directory Snapshot using Powershell

This section is nearly identical to the PShirwin snapshot. I added a RIDPool counter and updated his commands to support current powershell framework

$ADConfigs = @{}
 write-host " "
 write-host " "
 write-host "Pulling Active Directory Configs"
 $ADConfigs.RootDSE = $(Get-ADRootDSE)
 $ADConfigs.ForestInformation = $(Get-ADForest)
 $ADConfigs.DomainInformation = $(Get-ADDomain)
 $ADConfigs.DomainControllers = $(Get-ADDomainController -Filter *)
 $ADConfigs.DomainTrusts = (Get-ADTrust -Filter *)
 $ADConfigs.DefaultPassWordPoLicy = $(Get-ADDefaultDomainPasswordPolicy)
 $ADConfigs.FGPasswordPolicy = $(Get-ADFineGrainedPasswordPolicy -filter *)
 $ADConfigs.AuthenticationPolicies = $(Get-ADAuthenticationPolicy -LDAPFilter '(name=AuthenticationPolicy*)')
 $ADConfigs.AuthenticationPolicySilos = $(Get-ADAuthenticationPolicySilo -Filter 'Name -like "*AuthenticationPolicySilo*"')
 $ADConfigs.CentralAccessPolicies = $(Get-ADCentralAccessPolicy -Filter *)
 $ADConfigs.CentralAccessRules = $(Get-ADCentralAccessRule -Filter *)
 $ADConfigs.ClaimTransformPolicies = $(Get-ADClaimTransformPolicy -Filter *)
 $ADConfigs.ClaimTypes = $(Get-ADClaimType -Filter *)
 $ADConfigs.OrganizationalUnits = $(Get-ADOrganizationalUnit -Filter *)
 $ADConfigs.OptionalFeatures =  $(Get-ADOptionalFeature -Filter *)
 $ADConfigs.Sites = $(Get-ADReplicationSite -Filter *)
 $ADConfigs.Subnets = $(Get-ADReplicationSubnet -Filter *)
 $ADConfigs.SiteLinks = $(Get-ADReplicationSiteLink -Filter *)
 $ADConfigs.RIDPool = $((Dcdiag.exe /s:phx-ad04 /TEST:RidManager /v | findstr /c:"Available RID Pool for the Domain" /c:"rIDAllocationPool is" /c:"rIDPreviousAllocationPool is") -replace ".*is")
 $ADConfigs.ReplicationMetaData = $(Get-ADReplicationPartnerMetadata -Target (Get-ADDomain).DNSRoot -Scope Domain)
 $ADConfigs | Export-Clixml .\ADReportXML-$($expoDate).xml -Encoding UTF8

As you can see, the script creates the array $ADConfigs first. Each command that follows stores its output to a ‘member property’ that gets created ‘on the fly’ which is used later to call the data from the XML. Like so

$ImportAD = Import-CliXML .\adreportXML-04122020.xml
$ImportAD.DomainControllers - to see all domain controller data

Pull all Elevated built-in Group Memberships using Powershell

This next function is used to identify all members of the Built-in elevated groups in AD. These groups are overused and become a catchall since delegation is not well understood. I use the Group SID in the event any of the groups have been renamed.

$ADElevate = @{}
Write-Host " "
Write-Host " "
Write-Host "Retrieving Elevated Account data..."
$ADElevate.DomainAdmins = $(Get-ADGroup -Identity $('{0}-512' -f (Get-ADDomain).domainSID) | Get-ADGroupMember -Recursive)
$ADElevate.EnterpriseAdmins = $(Get-ADGroup -Identity $('{0}-519' -f (Get-ADDomain).domainSID) | Get-ADGroupMember -Recursive)
$ADElevate.SchemaAdmins = $(Get-ADGroup -Identity $('{0}-518' -f (Get-ADDomain).domainSID) | Get-ADGroupMember -Recursive)
$ADElevate.Administrators = $(Get-ADGroup -Identity S-1-5-32-544 | Get-ADGroupMember -Recursive)
$ADElevate.ServerOperators = $(Get-ADGroup -Identity S-1-5-32-549 | Get-ADGroupMember -Recursive)
$ADElevate.AccountOperators = $(Get-ADGroup -Identity S-1-5-32-548 | Get-ADGroupMember -Recursive)
$ADElevate.BackupOperators = $(Get-ADGroup -Identity S-1-5-32-551 | Get-ADGroupMember -Recursive)
$ADElevate.PrintOperators = $(Get-ADGroup -Identity S-1-5-32-550 | Get-ADGroupMember -Recursive)
$ADElevate.PowerUsers = $(Get-ADGroup -Identity S-1-5-32-547 | Get-ADGroupMember -Recursive)
$ADElevate.Guests = $(Get-ADGroup -Identity S-1-5-32-546 | Get-ADGroupMember -Recursive)
#	$ADElevate.OUPerms = $(Pull-OUPermissions)

$ADElevate | Export-Clixml .\ElevateReportXML-$($expoDate).xml -Encoding UTF8

The second to the last line is commented out to show how to call another function which follows

Pull all permissions on OUs using Powershell

The next function will query the OU structure and pull all permissions off of each OU. This is useful in identifying rogue permissions and implementing delegation. Once i find the author of this original work i will link it here.

$report = @()
$schemaIDGUID = @{}
$ErrorActionPreference = 'SilentlyContinue'
Write-Host " "
Write-Host " "
Write-Host "Retrieving permissions on OUs..."
Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID | ForEach-Object {$schemaIDGUID.add([System.GUID]$_.schemaIDGUID,$}
Get-ADObject -SearchBase "CN=Extended-Rights,$((Get-ADRootDSE).configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID | ForEach-Object {$schemaIDGUID.add([System.GUID]$_.rightsGUID,$}
$ErrorActionPreference = 'Continue'

# Get a list of all OUs.  Add in the root containers for good measure (users, computers, etc.).
$OUs  = @(Get-ADDomain | Select-Object -ExpandProperty DistinguishedName)
$OUs += Get-ADOrganizationalUnit -Filter * | Select-Object -ExpandProperty DistinguishedName
$OUs += Get-ADObject -SearchBase (Get-ADDomain).DistinguishedName -SearchScope OneLevel -LDAPFilter '(objectClass=container)' | Select-Object -ExpandProperty DistinguishedName

# Loop through each of the OUs and retrieve their permissions.
# Add report columns to contain the OU path and string names of the ObjectTypes.
ForEach ($OU in $OUs) {
	$report += Get-Acl -Path "AD:\$OU" |
	 Select-Object -ExpandProperty Access | 
	 Select-Object @{name='organizationalUnit';expression={$OU}}, `
				   @{name='objectTypeName';expression={if ($_.objectType.ToString() -eq '00000000-0000-0000-0000-000000000000') {'All'} Else {$schemaIDGUID.Item($_.objectType)}}}, `
				   @{name='inheritedObjectTypeName';expression={$schemaIDGUID.Item($_.inheritedObjectType)}}, `

return $report

The above code is executed in the main script by this line:

$ADElevate.OUPerms = $(Pull-OUPermissions)

The ‘Pull-OUPermissions’ verb is actually the name of a ‘function’ in the main script. By executing it within the array or scriptblock and storing the output into a property of the variable, enables us easy access to report on it when we are done.

A.I. Note: The reality here is that in using this technique, we are shortcutting the creation of datatables in a way that ‘could’ be used to build datasets to get to ‘Configuration as Code‘, depending on which system, service or application this data is output to . (More on that in a later post!)

Pull Active Directory Users and Groups using Powershell

This next section is basically a dump of all users, groups and their memberships which is then stored in XML format. There are so many uses for this type of data in an assessment that i will be creating a separate blog post for that at a later date. The final report looks at metrics like password expiration and disabled users and so on.

$ADUsGr = @{}

Write-Host " "
Write-Host " "
Write-Host "Retrieving Users and Group data..."

$ADUsGr.ADUsers = $(get-aduser -filter * -properties *)
$ADUsGr.ADGroups = $(get-adgroup -filter *)
$ADUsGr.ADGRecurse = $(ForEach ($ADG in $ADUsGr.ADGroups) { Get-ADGroupMember $ADG -Recursive | select @{name='GroupName';expression={$ADG.Name}},@{name='GroupCategory';expression={$ADG.GroupCategory}},@{name='GroupScope';expression={$ADG.GroupScope}},SamAccountName,Name,objectClass,distinguishedName})

$ADUsGr | Export-Clixml .\UnGReportXML-$($expoDate).xml -Encoding UTF8

Pull Active Directory Servers, Computers and Installed Features using Powershell

This function will retrieve all servers and workstations then get all installed windows features using WMI. Depending on how restrictive the environment is we may not be able to pull the installed features using this method. A connection must be made to the server to ‘query’ for this data. In a restrictive environment you may need to comment out the section that attempts to get the installed features

$ADPuters = @{}
Write-Host " "
Write-Host " "
Write-Host "Retrieving Server and Workstation data..."	

$ADPuters.ADServers = $(Get-ADComputer -Filter {OperatingSystem -like '*Windows Server*'} -Properties *)
$ADPuters.ADWorkstations = $(Get-ADComputer -Filter {OperatingSystem -notlike '*Windows Server*'} -Properties *)

$ADPuters.ADServerRoles = $(
	[Int]$ServerCount = $ADPuters.ADServers.Count
	[Int]$ServerProgressCount = '0'
	ForEach ($ADServer in ($ADPuters.ADServers | ? {$_.enabled -eq $True})) {

$ADPuters | Export-Clixml .\PuterReportXML-$($expoDate).xml -Encoding UTF8

As you can see, we call a separate function to get the features. Here is the bit that gets the installed features

Get Installed Windows Server Features using Powershell

Function Pull-ServerRoles {
 Param (
	[ parameter (
		ValueFromPipeline=$true ) ]
	$Data = @()
	Import-module servermanager
	Write-Host " "
	Write-Host " "
	Write-Host "Retrieving Server Roles..."	
	Write-Progress -Activity "Inventorying Server" -Id 1 -Status "$($ServerProgressCount) / $($ServerCount)" -CurrentOperation "$($ADSvr.DNSHostName)" -PercentComplete (($ServerProgressCount / $ServerCount) * 100)
	Write-Host "Testing Connection to $($ADSvr.DNSHostName)" -ForegroundColor White
		$TestConnection = Test-Connection -Count 2 -ComputerName $ADSvr.DNSHostName -ErrorAction SilentlyContinue
		if (!($TestConnection)) {
			Write-Host "Cannot contact $($ADSvr.DNSHostName)" -ForegroundColor Red
		Write-Host "Successfully connected to $($ADSvr.DNSHostName)" -ForegroundColor Green
		Write-Host "Gathering Installed Feature Data from $($ADSvr.Name) Please wait.." -ForegroundColor White
		$Features = (Get-WmiObject -ComputerName $ADSvr.DNSHostName win32_serverfeature).Name
		ForEach ($Feature in $Features)	{
			$Data += @{
				ServerName = $($ADSvr.DNSHostName)
				OperatingSystem = $($ADSvr.OperatingSystem)
				ServerFeature = $($Feature)
		return $Data

I dont think i created that one. Just edited it for my needs. I havent used write-progress or test-connection so thats the dead giveaway here.

The main script moves on to pull Exchange on-premise configurations and permissions. I learned this cool trick manipulating files and folders that allowed me to get permissions without an LDAP query on every ACL but havent found the way to do it in Exchange yet. I will be posting detailed descriptions of these scripts in future blog posts but for now here they are

Pull Exchange Server Configuration using Powershell

This function is very similar to the Active Directory function above in that it takes all of the ‘Get’ commands for exchange and stores the output to an array or scriptblock.

$EXConfigs = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather Exchange Organization configuration..."

$EXConfigs.Servers = $(Get-ExchangeServer)
$EXConfigs.AcceptDom = $(Get-AcceptedDomain)
$EXConfigs.RcvConn = $(Get-ReceiveConnector)
$EXConfigs.SendConn = $(Get-SendConnector)
$EXConfigs.Transport = $(Get-TransportConfig)
$EXConfigs.OutlookAnywhere = $(Get-OutlookAnywhere)
$EXConfigs.OABVirt = $(Get-OABVirtualDirectory)
$EXConfigs.WebSvcVirt = $(Get-WebServicesVirtualDirectory)
$EXConfigs.ASyncVirt = $(Get-ActiveSyncVirtualDirectory)
$EXConfigs.OwaVirt = $(Get-OwaVirtualDirectory)
$EXConfigs.EcpVirt = $(Get-EcpVirtualDirectory)
$EXConfigs.CAS = $(Get-ClientAccessServer)
$EXConfigs.Database = $(Get-MailboxDatabase)
$EXConfigs.DagInfo = $(Get-DatabaseAvailabilityGroup)
$EXConfigs.DagNet = $(Get-DatabaseAvailabilityGroupNetwork)

$EXConfigs | Export-Clixml .\ExConfigReportXML-$($expoDate).xml -Encoding UTF8

That should be pretty much everything to grab from the Exchange config.

Get Exchange Server Users, Devices and Permissions data using Powershell

The Exchange data function comes in two parts. Getting permissions on mailbox folders and getting it into a readable format is a task in itself due to a completely different output generated from the folders

$EXData = @{}
Write-Host " "
Write-Host " "
Write-Host "Gather On-Premise Exchange data..."

$EXData.DBs = $(Get-MailboxDatabase)

Write-Host "Retrieving Mailbox Data..."
$EXData.MBXs = $(ForEach ($DB in $EXData.DBs) {Get-Mailbox -database $DB.Identity -resultsize unlimited})
$EXData.MBXstats = $(ForEach ($Box in $EXData.MBXs) {Get-Mailboxstatistics $Box.alias})

Write-Host " "
Write-Host "Retrieving MobileDevice Data..."
$EXData.MobileDevices = $(Get-ActiveSyncDevice)
#	$EXData.Mobilestats = $(ForEach ($Devi in $EXData.MobileDevices) {Get-ActiveSyncDeviceStatistics $})

Write-Host "Retrieving Mailbox Permissions..."	
$EXData.MBXPerms = $(ForEach ($Box in $EXData.MBXs) {Get-MailboxPermission $Box.Identity | ? {$_.user.tostring() -ne "NT AUTHORITY\SELF" -and $_.user.tostring() -notlike "S-1-5-21*" -and $_.user.tostring() -notlike "*admin*" -and $_.IsInherited -eq $false}})

Write-Host "Retrieving Mailbox Folder Permissions..."
$ExData.MBXFolderPerms = $(ForEach ($MBox in $EXData.MBXs) {Pull-MBXFolderPerms($MBox.Name)})

$EXData | Export-Clixml .\ExDataReportXML-$($expoDate).xml -Encoding UTF8

The above script grabs all mailboxes and devices and records statistics for each. It then iterates through every mailbox and mailbox folder, pulling all administrative and user created permissions. To get the mailbox folders the above script calls this script:

Mailbox Folder Permissions using Powershell

Function Pull-MBXFolderPerms {
Param ([string]$MBXName)
	Start-Sleep -Milliseconds 600
		$Mailbox = '' + $MBXName
		$Folders = Get-MailboxFolderStatistics $Mailbox | % {$_.folderpath} | % {$_.replace("/","\")}
		Write-host "Processing folders on mailbox $Mailbox"
		ForEach ($Fold in $Folders) {
			$FolderKey = $Mailbox + ":" + $Fold
			$Permissions = Get-MailboxFolderPermission -identity $FolderKey -ErrorAction silentlycontinue
			$FolderPerms += $Permissions | ? {$_.User -notlike "Default" -and $_.User -notlike "Anonymous" -and $_.AccessRights -notlike "None" -and $_.AccessRights -notlike "Owner" } | Select-Object @{name='Mailbox';expression={$MBXName}},FolderName,@{name='UserWithAccess';expression={$_.User -join ','}},@{name='AccessRights';expression={$_.AccessRights -join ','}}
	Return $FolderPerms

As you can see we had to cut up the output of one command to apply it to the next. We’ve separated the folderperms work into its own function so we can use that same bit of code to do the same work in Exchange Online.

Now that we’ve seen all the different pieces we can wrap the whole thing up into a single script as shown here:

Leave a Reply

Your email address will not be published. Required fields are marked *

Datarift LLC 2020 Cloud Solutions
Phoenix, Arizona