PowerShell

How I didn’t know how powerful and fast hashtables are

I've been using PowerShell for a long while now using Hashtables, OrderedDictionary, and other types of data types in PowerShell, but I never paid attention to how powerful those are. And I don't mean your general knowledge about hashtables that is already covered by Kevin Marquette in his article Everything you wanted to know about Hashtables or my article PowerShell – Few tricks about HashTables and Arrays I wish I knew when I started. Let's find out, how Powerful they are, shall we?

Where-Object vs ForEach loops

You see, I have this little PowerShell module called PSWinDocumentation.AD. In one of the functions, it gets all users, all groups and all computers to finally create a version of it where each User gets Manager Name and Manager Email address, along with Group Names the user belongs. For this article, I'll simplify things and get just the users SamAccountName and Managers field. The way the manager is stored for a user is by DistinguishedName. So when you want to get a manager that is attached to the user you would do something like this:

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'

$Users = foreach ($_ in $UsersAll) {
    [PSCustomobject] @{
        SamAccountName = $_.SamAccountName
        Manager       = $_.Manager
    }
}

$Users | Format-Table -AutoSize

But it doesn't look pretty, right? You don't want that. You want to show the Manager's name and Managers email address, but to do that you need to ask Active Directory for Manager based on the field you have (DN). You can do it by asking AD, or you can use the full list you already have for your users. If you went with the first option, it would mean for each user another query to AD. In the case of reusing UsersAll variable, you're working in memory, which is much faster.

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'

$UsersWithManagers = foreach ($User in $UsersAll) {
    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = ($UsersAll | Where-Object { $User.Manager -eq $_.DistinguishedName }).DisplayName
        ManagerEmail   = ($UsersAll | Where-Object { $User.Manager -eq $_.DistinguishedName }).EmailAddress
    }
}

$UsersWithManagers | Format-Table -AutoSize

Much better right? Now in my test domain, the first query was 40 milliseconds, the second query was 185 milliseconds. Not that big deal, right? My test domain is about 50 users; that's why the results aren't worrying. But if you test this on a domain with 4412 users it's going to give you 2 seconds and 94 milliseconds. However, the version with the Manager's Name and Manage's Email address will execute in 15 minutes and 49 seconds and 406 milliseconds. Not so fun right? So how one can optimize this? Well, first of all, I was using Where-Object two times for the same object. Let's change it and compare results.

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'

$UsersWithManagers = foreach ($User in $UsersAll) {
    $Manager = ($UsersAll | Where-Object { $User.Manager -eq $_.DistinguishedName })

    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = $Manager.DisplayName
        ManagerEmail   = $Manager.EmailAddress
    }
}

$UsersWithManagers | Format-Table -AutoSize

It took 7 minutes and 54 seconds. Better? Yes! But the thing about Where-Object is, that while the syntax is excellent and very useful, it's quite slow. While it works fine on smaller arrays, it struggles with more substantial items. So how can we replace Where-Object? Well, Where-Object is essentially a foreach loop. It merely checks each entry in an array against the given condition. So let's use that!

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'
$UsersWithManagers = foreach ($User in $UsersAll) {
    $Manager = foreach ($_ in $UsersAll) {
        if ($User.Manager -eq $_.DistinguishedName) {
            $_
            break
        }
    }
    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = $Manager.DisplayName
        ManagerEmail   = $Manager.EmailAddress
    }
}
$UsersWithManagers | Format-Table -AutoSize

Few more lines more, a bit worse syntax, but a much better results! Just 59 seconds and 367 milliseconds!

Hashtables to the rescue

While 59 seconds doesn't seem much, things quickly add up as you try to play with more significant data. My domain is just 4000 users. What about 10000 or 40000. What if more fields need to be looked up. Well, this is where Hashtables come in. Let's think for a second what happens in a foreach loop that I've shown above. For every User that I'm checking Manager, I go and check 4000 or fewer users (assuming manager is found sooner rather than later). And I do this every time. So 4000 times I execute another loop with 4000 users, which if my math is correct gives 16000000 (16 million loops – the real value should be smaller due to using break if entry is found earlier). It's even worse in my first example. I was doing it twice with Where-Object, which is very slow. Let's introduce hashtable into the mix.

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'
# This time we will prepare Hashtable that will keep DistinguishedName as a key
$Optimize = @{}
foreach ($_ in $UsersAll) {
    $Optimize.($_.DistinguishedName) = $_
}
# End of preparations

$UsersWithManagers = foreach ($User in $UsersAll) {
    if ($null -ne $User.Manager) {
        $Manager = $Optimize.($User.Manager)
    } else {
        $Manager = $null
    }
    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = $Manager.DisplayName
        ManagerEmail   = $Manager.EmailAddress
    }
}
$UsersWithManagers | Format-Table -AutoSize

As you can see above things have complicated a bit in the code. I've basically created HashTable with DistinguishedName as a key and all user data as a value. Then I simply check each Manager DistinguishedName if it exists in the hashtable. The result? 7 seconds and 952 milliseconds. Amazing right? While it's an added time to set it up and you may see a few added seconds it pays off in the end. Of course, if you have 50 users domain, you may not notice anything, or you may even see the slowdown (added few seconds). But for larger domains, well this rocks! But that's not all. After posting this article on Reddit @Vortex100 suggested that there's an even faster way to use hashtables (you gotta love Reddit right?!). Using it this way

Measure-Command {$a = @{};1..10000 | % {$a.$_ = $_}}
TotalMilliseconds : 10373.6452

Is a bit slower then using it like this

Measure-Command {$b = @{};1..10000 | % {$b.add($_,$_)}}
TotalMilliseconds : 55.7214

Same thing applies to accessing it the way I did

$a.someproperty

Or, accessing it as he proposed

$a[$someproperty]

With this knowledge in mind the final code should actually look like this

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'
# This time we will prepare Hashtable that will keep DistinguishedName as a key
$Optimize = @{}
foreach ($_ in $UsersAll) {
    $Optimize.Add($_.DistinguishedName,$_)
}
# End of preparations

$UsersWithManagers = foreach ($User in $UsersAll) {
    if ($null -ne $User.Manager) {
        $Manager = $Optimize[$User.Manager]
    } else {
        $Manager = $null
    }
    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = $Manager.DisplayName
        ManagerEmail   = $Manager.EmailAddress
    }
}
$UsersWithManagers | Format-Table -AutoSize

Or as @Ta11ow (PowerShell Hero 2019 himself) suggested using the index operator instead of a method for a bit nicer syntax

$UsersAll = Get-ADUser -Properties Manager, DisplayName, EmailAddress -Filter '*'
# This time we will prepare Hashtable that will keep DistinguishedName as a key
$Optimize = @{}
foreach ($item in $UsersAll) {
    $Optimize[$item.DistinguishedName] = $item
}
# End of preparations

$UsersWithManagers = foreach ($User in $UsersAll) {
    if ($null -ne $User.Manager) {
        $Manager = $Optimize[$User.Manager]
    } else {
        $Manager = $null
    }
    [PSCustomobject] @{
        SamAccountName = $User.SamAccountName
        Manager        = $User.Manager
        ManagerDisplay = $Manager.DisplayName
        ManagerEmail   = $Manager.EmailAddress
    }
}
$UsersWithManagers | Format-Table -AutoSize
How PSWinDocumentation.AD speed was affected?

How this impacts PSWinDocumentation.AD? Well, let's see a similar, but a bit more demanding query.

VERBOSE: Getting all information - Start
VERBOSE: Getting forest information - Start
VERBOSE: Getting forest information - Domains
VERBOSE: Getting domain information - domain.test DomainGroupsFullList
VERBOSE: Getting domain information - domain.test DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 841 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsersFullList
VERBOSE: Getting domain information - domain.test DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 14 seconds, 462 milliseconds
VERBOSE: Getting domain information - domain.test DomainComputersFullList
VERBOSE: Getting domain information - domain.test DomainComputersFullList - Time: 0 days, 0 hours, 0 minutes, 5 seconds, 701 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsers
VERBOSE: Getting domain information - domain.test DomainUsers - Time: 0 days, 0 hours, 14 minutes, 44 seconds, 473 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsersCount
VERBOSE: Getting domain information - domain.test DomainUsersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 3 milliseconds
VERBOSE: Getting domain information - domain.test - Time to generate: 0 days, 0 hours, 15 minutes, 5 seconds, 509 milliseconds
VERBOSE: Getting forest information - Domains - Time: 0 days, 0 hours, 15 minutes, 5 seconds, 510 milliseconds
VERBOSE: Getting forest information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 0 seconds, 121 milliseconds
VERBOSE: Getting all information - Stop - Time to generate: 0 days, 0 hours, 15 minutes, 5 seconds, 640 milliseconds

The test, even with optimizations in place (using foreach, instead of Where-Object) takes 15 minutes and 5 seconds. Again, you should consider my domain count for combined users, groups and computers is about 8000 ad objects. But there are domains much bigger than those. Let's see how a bit of hashtable magic changed the same code?

VERBOSE: Getting all information - Start
VERBOSE: Getting forest information - Start
VERBOSE: Getting forest information - Domains
VERBOSE: Getting domain information - domain.test DomainGroupsFullList
VERBOSE: Getting domain information - domain.test DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 226 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsersFullList
VERBOSE: Getting domain information - domain.test DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 19 seconds, 663 milliseconds
VERBOSE: Getting domain information - domain.test DomainComputersFullList
VERBOSE: Getting domain information - domain.test DomainComputersFullList - Time: 0 days, 0 hours, 0 minutes, 11 seconds, 333 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsers
VERBOSE: Getting domain information - domain.test DomainUsers - Time: 0 days, 0 hours, 0 minutes, 47 seconds, 146 milliseconds
VERBOSE: Getting domain information - domain.test DomainUsersCount
VERBOSE: Getting domain information - domain.test DomainUsersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 12 milliseconds
VERBOSE: Getting domain information - domain.test - Time to generate: 0 days, 0 hours, 1 minutes, 19 seconds, 434 milliseconds
VERBOSE: Getting forest information - Domains - Time: 0 days, 0 hours, 1 minutes, 19 seconds, 464 milliseconds
VERBOSE: Getting forest information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 0 seconds, 60 milliseconds
VERBOSE: Getting all information - Stop - Time to generate: 0 days, 0 hours, 1 minutes, 19 seconds, 552 milliseconds

Just 1 minute and 19 seconds. Wow! Isn't it amazing? Associative arrays, or in case of PowerShell HashTables are an abstract data type that can hold data in (key, value) pairs. The easiest thing to understand this concept is to think of it as a phone book. Key is phone number, Value is Name of the phone owner. In this phone book, you can look up a person's name by finding their phone number. Every key can only appear once, just like every phone number can only appear once on the phone book. And, every key can only have one value, just like every phone number can only refer to only one person. In comparison array's can have multiple objects with exactly the same value. That's why in case of array's you need to use foreach to find a value, while in case of HashTable you simply tell PowerShell to get you Value based on the Key. As each key is unique PowerShell knows how to find it and can deliver it very fast. Let's see how it affects the whole PSWinDocumentation.AD module, shall we?

VERBOSE: Getting all information - Start
VERBOSE: Getting forest information - Start
VERBOSE: Getting forest information - TypesRequired is null. Getting all.
VERBOSE: Getting forest information - ForestRootDSE
VERBOSE: Getting forest information - ForestRootDSE - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 48 milliseconds
VERBOSE: Getting forest information - Forest
VERBOSE: Getting forest information - Forest - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 51 milliseconds
VERBOSE: Getting forest information - ForestSchemaPropertiesComputers
VERBOSE: Getting forest information - ForestSchemaPropertiesComputers - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 607 milliseconds
VERBOSE: Getting forest information - ForestSchemaPropertiesUsers
VERBOSE: Getting forest information - ForestSchemaPropertiesUsers - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 259 milliseconds
VERBOSE: Getting forest information - ForestUPNSuffixes
VERBOSE: Getting forest information - ForestUPNSuffixes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 17 milliseconds
VERBOSE: Getting forest information - ForestSPNSuffixes
VERBOSE: Getting forest information - ForestSPNSuffixes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 9 milliseconds
VERBOSE: Getting forest information - ForestGlobalCatalogs
VERBOSE: Getting forest information - ForestGlobalCatalogs - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting forest information - ForestFSMO
VERBOSE: Getting forest information - ForestFSMO - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 10 milliseconds
VERBOSE: Getting forest information - ForestDomainControllers
VERBOSE: Getting forest information - ForestDomainControllers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 192 milliseconds
VERBOSE: Getting forest information - ForestSites
VERBOSE: Getting forest information - ForestSites - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 39 milliseconds
VERBOSE: Getting forest information - ForestSites1
VERBOSE: Getting forest information - ForestSites1 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 22 milliseconds
VERBOSE: Getting forest information - ForestSites2
VERBOSE: Getting forest information - ForestSites2 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 13 milliseconds
VERBOSE: Getting forest information - ForestSubnets
VERBOSE: Getting forest information - ForestSubnets - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 15 milliseconds
VERBOSE: Getting forest information - ForestSubnets1
VERBOSE: Getting forest information - ForestSubnets1 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 9 milliseconds
VERBOSE: Getting forest information - ForestSubnets2
VERBOSE: Getting forest information - ForestSubnets2 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 17 milliseconds
VERBOSE: Getting forest information - ForestSiteLinks
VERBOSE: Getting forest information - ForestSiteLinks - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 17 milliseconds
VERBOSE: Getting forest information - ForestOptionalFeatures
VERBOSE: Getting forest information - ForestOptionalFeatures - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 22 milliseconds
VERBOSE: Getting forest information - Domains
VERBOSE: Getting domain information - TestDomain.pl DomainRootDSE
VERBOSE: Getting domain information - TestDomain.pl DomainRootDSE - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 16 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainInformation
VERBOSE: Getting domain information - TestDomain.pl DomainInformation - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 124 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsFullList
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 2 seconds, 420 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersFullList
VERBOSE: Getting domain information - TestDomain.pl DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 14 seconds, 906 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersFullList
VERBOSE: Getting domain information - TestDomain.pl DomainComputersFullList - Time: 0 days, 0 hours, 0 minutes, 7 seconds, 325 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAll
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAll - Time: 0 days, 0 hours, 0 minutes, 31 seconds, 637 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAllCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAllCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 78 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainServers
VERBOSE: Getting domain information - TestDomain.pl DomainServers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 29 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainServersCount
VERBOSE: Getting domain information - TestDomain.pl DomainServersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 60 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputers
VERBOSE: Getting domain information - TestDomain.pl DomainComputers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 43 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 31 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknown
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknown - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 28 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknownCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknownCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 24 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainRIDs
VERBOSE: Getting domain information - TestDomain.pl DomainRIDs - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 133 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGUIDS
VERBOSE: Getting domain information - TestDomain.pl DomainGUIDS - Time: 0 days, 0 hours, 0 minutes, 3 seconds, 510 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 31 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicySilos
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicySilos - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 30 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 24 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessRules
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessRules - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 44 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTransformPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTransformPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 43 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTypes
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTypes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 50 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSData
VERBOSE: Getting domain information - TestDomain.pl DomainDNSData - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 470 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSSrv
VERBOSE: Getting domain information - TestDomain.pl DomainDNSSrv - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSA
VERBOSE: Getting domain information - TestDomain.pl DomainDNSA - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFSMO
VERBOSE: Getting domain information - TestDomain.pl DomainFSMO - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 27 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainTrustsClean
VERBOSE: Getting domain information - TestDomain.pl DomainTrustsClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 56 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainTrusts
VERBOSE: Getting domain information - TestDomain.pl DomainTrusts - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 389 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesClean
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 365 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 744 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesDetails
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesDetails - Time: 0 days, 0 hours, 0 minutes, 19 seconds, 182 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesACL
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesACL - Time: 0 days, 0 hours, 0 minutes, 11 seconds, 451 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainBitlocker
VERBOSE: Getting domain information - TestDomain.pl DomainBitlocker - Time: 0 days, 0 hours, 0 minutes, 9 seconds, 677 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainLAPS
VERBOSE: Getting domain information - TestDomain.pl DomainLAPS - Time: 0 days, 0 hours, 0 minutes, 2 seconds, 449 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDefaultPasswordPolicy
VERBOSE: Getting domain information - TestDomain.pl DomainDefaultPasswordPolicy - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 46 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsClean
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 140 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnits
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnits - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 100 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsBasicACL
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsBasicACL - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 11 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsExtendedACL
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsExtendedACL - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 323 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsers
VERBOSE: Getting domain information - TestDomain.pl DomainUsers - Time: 0 days, 0 hours, 15 minutes, 55 seconds, 605 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersAll
VERBOSE: Getting domain information - TestDomain.pl DomainUsersAll - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 123 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersSystemAccounts
VERBOSE: Getting domain information - TestDomain.pl DomainUsersSystemAccounts - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 143 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiring
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiring - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 139 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiringInclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiringInclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 137 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredInclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredInclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 148 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredExclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredExclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 159 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersCount
VERBOSE: Getting domain information - TestDomain.pl DomainUsersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 39 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainControllers
VERBOSE: Getting domain information - TestDomain.pl DomainControllers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 71 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 32 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsers
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 36 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsersExtended
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsersExtended - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 32 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroups
VERBOSE: Getting domain information - TestDomain.pl DomainGroups - Time: 0 days, 0 hours, 9 minutes, 23 seconds, 409 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembers - Time: 0 days, 0 hours, 0 minutes, 12 seconds, 399 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembersRecursive - Time: 0 days, 0 hours, 31 minutes, 9 seconds, 707 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviliged
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviliged - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 23 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecial
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecial - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 19 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 39 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembersRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 413 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 30 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembersRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 703 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAdministrators
VERBOSE: Getting domain information - TestDomain.pl DomainAdministrators - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 26 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAdministratorsRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainAdministratorsRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 705 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministrators
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministrators - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 23 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministratorsRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministratorsRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 705 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataUsers
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataUsers - Time: 0 days, 0 hours, 0 minutes, 8 seconds, 531 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswords
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswords - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 30 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswordsHashes
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswordsHashes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 9 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordClearTextPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordClearTextPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 17 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordLMHash
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordLMHash - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 1 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 30 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordEnabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordEnabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordList
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDefaultComputerPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDefaultComputerPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 29 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNotRequired
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNotRequired - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNeverExpires
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNeverExpires - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordAESKeysMissing
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordAESKeysMissing - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPreAuthNotRequired
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPreAuthNotRequired - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDESEncryptionOnly
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDESEncryptionOnly - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDelegatableAdmins
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDelegatableAdmins - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDuplicatePasswordGroups
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDuplicatePasswordGroups - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 10 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordEnabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordEnabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordStats
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordStats - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 51 milliseconds
VERBOSE: Getting domain information - TestDomain.pl - Time to generate: 0 days, 0 hours, 58 minutes, 42 seconds, 428 milliseconds
VERBOSE: Getting forest information - Domains - Time: 0 days, 0 hours, 58 minutes, 42 seconds, 503 milliseconds
VERBOSE: Getting forest information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 4 seconds, 904 milliseconds
VERBOSE: Getting all information - Stop - Time to generate: 0 days, 0 hours, 58 minutes, 47 seconds, 435 milliseconds

And this is how it looks with the new approach

VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\PSWinDocumentation.AD\0.0.8\PSWinDocumentation.AD.psd1'.
VERBOSE: Populating RepositorySourceLocation property for module PSWinDocumentation.AD.
VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\PSWinDocumentation.AD\0.0.8\PSWinDocumentation.AD.psm1'.
VERBOSE: Importing function 'Get-WinADDomainInformation'.
VERBOSE: Importing function 'Get-WinADForestInformation'.
VERBOSE: Getting all information - Start
VERBOSE: Getting forest information - Start
VERBOSE: Getting forest information - TypesRequired is null. Getting all.
VERBOSE: Getting forest information - ForestRootDSE
VERBOSE: Getting forest information - ForestRootDSE - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 21 milliseconds
VERBOSE: Getting forest information - Forest
VERBOSE: Getting forest information - Forest - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 13 milliseconds
VERBOSE: Getting forest information - ForestSchemaPropertiesComputers
VERBOSE: Getting forest information - ForestSchemaPropertiesComputers - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 369 milliseconds
VERBOSE: Getting forest information - ForestSchemaPropertiesUsers
VERBOSE: Getting forest information - ForestSchemaPropertiesUsers - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 242 milliseconds
VERBOSE: Getting forest information - ForestUPNSuffixes
VERBOSE: Getting forest information - ForestUPNSuffixes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting forest information - ForestSPNSuffixes
VERBOSE: Getting forest information - ForestSPNSuffixes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting forest information - ForestGlobalCatalogs
VERBOSE: Getting forest information - ForestGlobalCatalogs - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting forest information - ForestFSMO
VERBOSE: Getting forest information - ForestFSMO - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting forest information - ForestDomainControllers
VERBOSE: Getting forest information - ForestDomainControllers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 106 milliseconds
VERBOSE: Getting forest information - ForestSites
VERBOSE: Getting forest information - ForestSites - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 49 milliseconds
VERBOSE: Getting forest information - ForestSites1
VERBOSE: Getting forest information - ForestSites1 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 11 milliseconds
VERBOSE: Getting forest information - ForestSites2
VERBOSE: Getting forest information - ForestSites2 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting forest information - ForestSubnets
VERBOSE: Getting forest information - ForestSubnets - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 12 milliseconds
VERBOSE: Getting forest information - ForestSubnets1
VERBOSE: Getting forest information - ForestSubnets1 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting forest information - ForestSubnets2
VERBOSE: Getting forest information - ForestSubnets2 - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 11 milliseconds
VERBOSE: Getting forest information - ForestSiteLinks
VERBOSE: Getting forest information - ForestSiteLinks - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 14 milliseconds
VERBOSE: Getting forest information - ForestOptionalFeatures
VERBOSE: Getting forest information - ForestOptionalFeatures - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 14 milliseconds
VERBOSE: Getting forest information - Domains
VERBOSE: Getting domain information - TestDomain.pl DomainRootDSE
VERBOSE: Getting domain information - TestDomain.pl DomainRootDSE - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 17 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainInformation
VERBOSE: Getting domain information - TestDomain.pl DomainInformation - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 63 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsFullList
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 964 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersFullList
VERBOSE: Getting domain information - TestDomain.pl DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 20 seconds, 622 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersFullList
VERBOSE: Getting domain information - TestDomain.pl DomainComputersFullList - Time: 0 days, 0 hours, 0 minutes, 21 seconds, 758 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAll
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAll - Time: 0 days, 0 hours, 0 minutes, 39 seconds, 450 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAllCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersAllCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 73 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainServers
VERBOSE: Getting domain information - TestDomain.pl DomainServers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 21 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainServersCount
VERBOSE: Getting domain information - TestDomain.pl DomainServersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 68 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputers
VERBOSE: Getting domain information - TestDomain.pl DomainComputers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 44 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 39 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknown
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknown - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 20 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknownCount
VERBOSE: Getting domain information - TestDomain.pl DomainComputersUnknownCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 11 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainRIDs
VERBOSE: Getting domain information - TestDomain.pl DomainRIDs - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 38 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGUIDS
VERBOSE: Getting domain information - TestDomain.pl DomainGUIDS - Time: 0 days, 0 hours, 0 minutes, 3 seconds, 579 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 27 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicySilos
VERBOSE: Getting domain information - TestDomain.pl DomainAuthenticationPolicySilos - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 29 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 22 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessRules
VERBOSE: Getting domain information - TestDomain.pl DomainCentralAccessRules - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 34 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTransformPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTransformPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 25 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTypes
VERBOSE: Getting domain information - TestDomain.pl DomainClaimTypes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 43 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSData
VERBOSE: Getting domain information - TestDomain.pl DomainDNSData - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 15 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSSrv
VERBOSE: Getting domain information - TestDomain.pl DomainDNSSrv - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDNSA
VERBOSE: Getting domain information - TestDomain.pl DomainDNSA - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFSMO
VERBOSE: Getting domain information - TestDomain.pl DomainFSMO - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainTrustsClean
VERBOSE: Getting domain information - TestDomain.pl DomainTrustsClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 36 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainTrusts
VERBOSE: Getting domain information - TestDomain.pl DomainTrusts - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 46 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesClean
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 48 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 479 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesDetails
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesDetails - Time: 0 days, 0 hours, 0 minutes, 16 seconds, 881 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesACL
VERBOSE: Getting domain information - TestDomain.pl DomainGroupPoliciesACL - Time: 0 days, 0 hours, 0 minutes, 11 seconds, 496 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainBitlocker
VERBOSE: Getting domain information - TestDomain.pl DomainBitlocker - Time: 0 days, 0 hours, 0 minutes, 7 seconds, 889 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainLAPS
VERBOSE: Getting domain information - TestDomain.pl DomainLAPS - Time: 0 days, 0 hours, 0 minutes, 2 seconds, 434 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainDefaultPasswordPolicy
VERBOSE: Getting domain information - TestDomain.pl DomainDefaultPasswordPolicy - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 35 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsClean
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsClean - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 126 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnits
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnits - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 59 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsBasicACL
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsBasicACL - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 950 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsExtendedACL
VERBOSE: Getting domain information - TestDomain.pl DomainOrganizationalUnitsExtendedACL - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 290 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsers
VERBOSE: Getting domain information - TestDomain.pl DomainUsers - Time: 0 days, 0 hours, 0 minutes, 44 seconds, 843 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersAll
VERBOSE: Getting domain information - TestDomain.pl DomainUsersAll - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 25 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersSystemAccounts
VERBOSE: Getting domain information - TestDomain.pl DomainUsersSystemAccounts - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 13 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiring
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiring - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 25 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiringInclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersNeverExpiringInclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 21 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredInclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredInclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 22 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredExclDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainUsersExpiredExclDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 41 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainUsersCount
VERBOSE: Getting domain information - TestDomain.pl DomainUsersCount - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 12 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainControllers
VERBOSE: Getting domain information - TestDomain.pl DomainControllers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 54 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPolicies
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPolicies - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 29 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsers
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 12 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsersExtended
VERBOSE: Getting domain information - TestDomain.pl DomainFineGrainedPoliciesUsersExtended - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 10 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroups
VERBOSE: Getting domain information - TestDomain.pl DomainGroups - Time: 0 days, 0 hours, 0 minutes, 23 seconds, 920 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembers - Time: 0 days, 0 hours, 0 minutes, 39 seconds, 399 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsMembersRecursive - Time: 0 days, 0 hours, 1 minutes, 54 seconds, 350 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviliged
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviliged - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 12 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecial
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecial - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 16 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 298 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsSpecialMembersRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 417 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembers
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembers - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 493 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembersRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainGroupsPriviligedMembersRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 751 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAdministrators
VERBOSE: Getting domain information - TestDomain.pl DomainAdministrators - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 482 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainAdministratorsRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainAdministratorsRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 743 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministrators
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministrators - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 471 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministratorsRecursive
VERBOSE: Getting domain information - TestDomain.pl DomainEnterpriseAdministratorsRecursive - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 753 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataUsers
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataUsers - Time: 0 days, 0 hours, 0 minutes, 8 seconds, 110 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswords
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswords - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 9 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswordsHashes
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDataPasswordsHashes - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordClearTextPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordClearTextPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordLMHash
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordLMHash - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordEmptyPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 1 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordEnabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordEnabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordList
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordWeakPasswordList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDefaultComputerPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDefaultComputerPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNotRequired
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNotRequired - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 9 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNeverExpires
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPasswordNeverExpires - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordAESKeysMissing
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordAESKeysMissing - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPreAuthNotRequired
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordPreAuthNotRequired - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDESEncryptionOnly
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDESEncryptionOnly - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDelegatableAdmins
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDelegatableAdmins - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 8 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDuplicatePasswordGroups
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordDuplicatePasswordGroups - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 7 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPassword
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPassword - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 6 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordEnabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordEnabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordDisabled
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordHashesWeakPasswordDisabled - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 5 milliseconds
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordStats
VERBOSE: Getting domain information - TestDomain.pl DomainPasswordStats - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 23 milliseconds
VERBOSE: Getting domain information - TestDomain.pl - Time to generate: 0 days, 0 hours, 6 minutes, 4 seconds, 935 milliseconds
VERBOSE: Getting forest information - Domains - Time: 0 days, 0 hours, 6 minutes, 4 seconds, 973 milliseconds
VERBOSE: Getting forest information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 3 seconds, 43 milliseconds
VERBOSE: Getting all information - Stop - Time to generate: 0 days, 0 hours, 6 minutes, 8 seconds, 62 milliseconds

58 minutes vs 6 minutes and 8 seconds. And in case of my other domain 1 hour and 42 minutes down to 18 minutes and 12 seconds. So instead of going to lunch, I can go for longer coffee and have that done. I call that a win. Now keep in mind that there are still things I need to optimize within PSWinDocumentation.AD but armed with hashtables it may have a nice performance boost I was looking for. Keep in mind that Version 0.0.8 is not yet released to production. There may still be bugs that I've introduced while testing things.

Let's summarize what we've learned today, shall we?

Let's end this article with what I've started. Two links – Kevin Marquette, and his Everything you wanted to know about Hashtables and my very own PowerShell – Few tricks about HashTables and Arrays I wish I knew when I startedEspecially in my article I mention a couple of other bad practices you should avoid if you care for speed. What have we learned today? Stop using Where-Object, prefer foreach loop where speed matters. ForEach-Object is as bad in speed as Where-Object (while I didn't provide a speed test, trust me – it's slow). Use a ForEach loop instead. If you see a way to use Hashtables, use them! They are much faster than using Arrays (where it makes sense). And to make it evident, I don't want you to suddenly stop using Where-Object or ForEach-Object when your code executes in 10 seconds or less. I'm talking about optimization when it's taking 15 minutes, 1 hour or 5 hours to run and you want that to finish much sooner. Both ForEach-Object and Where-Object have their use cases, and I use them frequently. Now let me go and start fixing my long running scripts, they deserve some speed boost! In case you would like to try it out yourself, here's a test case:

Measure-Command {$a = @{}; 1..10000 | ForEach-Object {$a.$_ = $_}}

Measure-Command {$b = @{}; 1..10000 | ForEach-Object {$b.add($_, $_)}}

Measure-Command {$c = @{}; 1..10000 | ForEach-Object {$c[$_] = $_}}

Measure-Command {
    $b = @{}
    for ($i = 1; $i -le 10000; $i++) {

        $b.add($i, $i)
    }
}

And the results are

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 12
Milliseconds      : 764
Ticks             : 127641020
TotalDays         : 0,000147732662037037
TotalHours        : 0,00354558388888889
TotalMinutes      : 0,212735033333333
TotalSeconds      : 12,764102
TotalMilliseconds : 12764,102

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 139
Ticks             : 1398151
TotalDays         : 1,61823032407407E-06
TotalHours        : 3,88375277777778E-05
TotalMinutes      : 0,00233025166666667
TotalSeconds      : 0,1398151
TotalMilliseconds : 139,8151

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 138
Ticks             : 1382375
TotalDays         : 1,59997106481481E-06
TotalHours        : 3,83993055555556E-05
TotalMinutes      : 0,00230395833333333
TotalSeconds      : 0,1382375
TotalMilliseconds : 138,2375

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 56
Ticks             : 563933
TotalDays         : 6,52700231481481E-07
TotalHours        : 1,56648055555556E-05
TotalMinutes      : 0,000939888333333333
TotalSeconds      : 0,0563933
TotalMilliseconds : 56,3933

As you see, using add method is the fastest, and with for/foreach even faster than using ForEach-Object (which has % as an alias). If you're into speed you may want to reconsider your usage scenarios.

This post was last modified on %s = human-readable time difference 09:26

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

1 month ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

2 months ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 months ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

11 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 year ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 year ago