Since we are in the last season of Games of Thrones, it's tough not to see some relation to the series all over the place. So when I saw the tweet below, I laughed out loud and knew I would have to use it with a different answer. Today!
It's no secret that nobody likes creating documentation. I don't like it, and you don't like it, even documentation lovers don't like it. But while you can live without documentation, you really shouldn't. And I am not talking here only about documentation that is only useful in the onboarding process of new employees or documentation concerning introducing someone to some concepts to get them easily start. I'm talking about documentation for your live environment where you know what you have, how you have set it up, but is still the same after one week, one month, or one year? Usually, not so much. And one of the worst mistakes admin can do is assume that his environment doesn't change, things are as they were when they were set up.
This article is about one little command called Get-WinADForestInformation (well, there will be some bonus things at the bottom – Dashimo, Documentimo, Excelimo, and Emailimo so you may want to stay for that). What can this command do? It can scan your Active Directory, sort it and deliver following data for your Active Directory Forest
How about domain information? It's there too:
And if that wouldn't be enough, it can also deliver you Password Quality of your users. It can verify if their passwords are on a list you provide or check for other common issues.
You can, of course, get this and all other data yourself using multiple commands. In this case, it's a single command that does it all. It has its pros and cons, but if you're not in a hurry, it can solve you some time of building this yourself, go for a coffee, a run or even go to sleep and find it delivered in the morning. If you would like to find out more, keep on reading! Otherwise, I invite you to check my other articles and if you like the idea of one command getting you lots of useful information you could review PSWinDocumentation.O365HealthService. This module extracts data from Office 365 and translates it for you. I tend to have some deviation that requires me to create one command modules. Not sure why.
Following is a list of projects that take part in this article. All links lead to their project on GitHub. All those projects are open source.
So what we say to writing Active Directory documentation? Today! About one year ago I wrote a PowerShell module called PSWinDocumentation. I've managed to have three iterations of that module, and it allowed me to create Active Directory (and in some minimal state of Office 365 and Exchange) documentation exported to Word, Excel, and SQL. While the concept was cool (according to me), it had one problem. It was very static in its design. Sure you could move things around, you could move FSMO roles from 1st page to the 10th page, change some texts and so on, but the moment you would try to expand this with your data, or data from other modules you would be stuck. There was no easy way to do this. Moreover, if someone wanted to contribute even something small to AD documentation they would need to go thru a lengthy process to analyze what the module does and where the data is coming from and how. Therefore I've decided to take a totally different approach. I've decided that to split PSWinDocumentation into smaller modules that are easier to manage and easier to use separately. It will be much more flexible focusing on datasets that one can use separately as per one needs. Sure, you can still use those modules with PSWinDocumentation or with some newer modules I've written but you can also do anything you like with it for your own needs. I've actually gone ahead and picked the same route I did with The only PowerShell Command you will ever need to find out who did what in Active Directory creating a single command called Get-WinADForestInformation. It's actually a supplement to the Find-Events command I've introduced a while back, which I am about to show you below.
Contrary to Find-Events command this one works a bit differently. If you don't provide information what you want, it gets it all, except for PasswordQuality which requires additional switch if the path to password file is not provided. This assumes that you want to process Password Quality of your users but you don't want to provide a list of passwords to verify your users against (about that I'll talk a bit later).
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality $Forest
After you execute this command depending on the size of your Active Directory environment things can take 30 seconds, 1 minute, 60 minutes or even hours to generate. Since this commands scans almost everything there is to scan (with exceptions that I hope people can help to build up in future) it really takes time to build this up. It does a lot of conversions on the fly and things tend to build up, when it comes to the time it takes to store and deliver.
As you can see output of this command is custom hashtable with values. The top level contains Forest data, but any domains you have in a forest go into FoundDomains key.
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality $Forest.FoundDomains
My Active Directory Forest has two domains. AD.EVOTEC.XYZ and AD.EVOTEC.PL. If you want to get the data from those domains you need to go down the rabbit hole.
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality $Forest.FoundDomains $Forest.FoundDomains.'ad.evotec.xyz'
Now while it may seem like there's a lot of gibberish data you can actually get each data separately.
$Forest.FoundDomains.'ad.evotec.xyz'.DomainGroups | Format-Table -AutoSize
As you can see above this data set does some grouping for the count of members and also expands Manager and Manager Email. It also has data that didn't fit the screen.
$Forest.FoundDomains.'ad.evotec.xyz'.DomainGroups[0] Group Name : Administrators Group Category : Security Group Scope : DomainLocal Group SID : S-1-5-32-544 High Privileged Group : True Member Count : 3 MemberOf Count : 0 Manager : Manager Email : Group Members : {Administrator, Domain Admins, Enterprise Admins} Group Members DN : {CN=Domain Admins,CN=Users,DC=ad,DC=evotec,DC=xyz, CN=Enterprise Admins,CN=Users,DC=ad,DC=evotec,DC=xyz, CN=Administrator,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz} Domain : ad.evotec.xyz
As you can see each of those objects also provide additional data that are hidden from the sight of Format-Table. As you can see Group Members and Group Members DN are lists of other objects. It's useful if you want to use for further processing, but for displaying purposes not so much. That's why I've added Splitter parameter for Get-WinADForestInformation. Word of caution here: I've only added this in a few places. I may need to review all the code and apply splitter where possible (and where it makes sense).
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality -Splitter "`r`n" $Forest.FoundDomains.'ad.evotec.xyz'.DomainGroups | Out-HTMLView
This allows, for example, adding a new line for display purposes in a table like below. I am not yet sure if it will stay like that in future releases, that's why I count on people's feedback on GitHub.
Get-WinADForestInformation by default removes any keys that are empty or are not requested by a user. This means if some checks will return no data the final custom hashtable output will be trimmed to only data that is there or was requested. This action can be prevented.
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality -DontRemoveSupportData -DontRemoveEmpty
For that purpose, there were two switches created. DontRemoveSupportData and DontRemoveEmpty. In a scenario like above where you ask for all the data, only DontRemoveEmpty matters.
As you can see above, there are a lot of gaps that didn't return any data for my test domains. By default, empty fields are removed, but it may be useful to know that there was no data returned. What about DontRemoveSupportData? Well, it's designed for a bit different case. You see, you can ask the command to deliver only specific data. For example, you can only ask for one, two or ten different things and it should provide only that data.
$Forest = Get-WinADForestInformation -Verbose -PasswordQuality -DontRemoveSupportData -TypesRequired DomainGroups -Splitter "`r`n" VERBOSE: Getting all information - Start VERBOSE: Getting forest information - Start VERBOSE: Getting forest information - Domains VERBOSE: Getting domain information - ad.evotec.xyz DomainGroupsFullList VERBOSE: Getting domain information - ad.evotec.xyz DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 151 milliseconds VERBOSE: Getting domain information - ad.evotec.xyz DomainUsersFullList VERBOSE: Getting domain information - ad.evotec.xyz DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 174 milliseconds VERBOSE: Getting domain information - ad.evotec.xyz DomainGroups VERBOSE: Getting domain information - ad.evotec.xyz DomainGroups - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 42 milliseconds VERBOSE: Getting domain information - ad.evotec.xyz - Time to generate: 0 days, 0 hours, 0 minutes, 0 seconds, 435 milliseconds VERBOSE: Getting domain information - ad.evotec.pl DomainGroupsFullList VERBOSE: Getting domain information - ad.evotec.pl DomainGroupsFullList - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 334 milliseconds VERBOSE: Getting domain information - ad.evotec.pl DomainUsersFullList VERBOSE: Getting domain information - ad.evotec.pl DomainUsersFullList - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 32 milliseconds VERBOSE: Getting domain information - ad.evotec.pl DomainGroups VERBOSE: Getting domain information - ad.evotec.pl DomainGroups - Time: 0 days, 0 hours, 0 minutes, 0 seconds, 19 milliseconds VERBOSE: Getting domain information - ad.evotec.pl - Time to generate: 0 days, 0 hours, 0 minutes, 1 seconds, 416 milliseconds VERBOSE: Getting forest information - Domains - Time: 0 days, 0 hours, 0 minutes, 1 seconds, 858 milliseconds VERBOSE: Getting forest information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 0 seconds, 249 milliseconds VERBOSE: Getting all information - Stop - Time to generate: 0 days, 0 hours, 0 minutes, 2 seconds, 114 milliseconds $Forest $Forest.FoundDomains.'ad.evotec.xyz'
We requested only for DomainGroups but to build that data two more data types had to be requested to build it. In this case, it was DomainGroupsFullList and DomainUsersFullList. If you want to keep that support data you can use DontRemoveSupportData switch and have that data left with the original request. But if you don't need it, skipping it will make sure only what you requested stays.
I've mentioned in the beginning, and you may have seen me using PasswordQuality switch earlier in the article, but its use case is unique. Password Quality section is supposed to work based on the Password List that you provide. This password list is stored in a file (each password per line). This means you can check all your users against define list of passwords and see if they use passwords like CompanyName2017, CompanyName2018, CompanyNameWinter2019 and so on. But even without a defined list of passwords it still returns a lot of useful data. That's why I've created PasswordQuality switch. If you don't provide a file with password list to verify against it will return empty data for passwords section. But if you want to get the data anyway (minus verification for known passwords) you can use the switch. It will use a file that I've prepared with a single password P@ssw0rd!. Clear? Makes sense?
Ok, so I've shown you the basics of using the command. Whether you ask for one data set, five datasets or all of it you can eat it any way you want. What you do with that command is up to you. You can use it in PowerShell, export as CSV, Excel or HTML or simply save in XML. But like I said at the beginning of this article the goal for PSWinDocumentation is to be deprecated. But to depreciate something I've to offer something back, something that will meet the goals that I've mentioned in the beginning. So for PSWinDocumentationV2, I've prepared two PowerShell Modules. Those are Documentimo and Excelimo. If the names don't tell you much, well let's see them in action, shall we? Keep in mind that those modules are very alpha modules. I've done some basic testing to replicate what PSWinDocumentation provided but at the same time something that will allow you to build on top of what was there.
Import-Module Documentimo -Force $Table = Get-Process | Select-Object -First 5 $TableForCharts = @( [PSCustomObject] @{ Name = 'Test 1'; SomeValue = 1 } [PSCustomObject] @{ Name = 'Test 2'; SomeValue = 5 } [PSCustomObject] @{ Name = 'Test 3'; SomeValue = 6 } ) Documentimo -FilePath $PSScriptRoot\Test.docx { DocTOC -Title 'Table of content' DocPageBreak -Verbose DocNumbering -Text 'My document' -Level 0 -Type Numbered -Heading Heading1 { DocText -Text 'Test', ' Test2' -Color Yellow DocTable -DataTable $Table -Design ColorfulGrid DocPageBreak } DocNumbering -Text 'Chart' { DocChart -Title 'Processes' -DataTable $Table -Key 'ProcessName' -Value 'Handles' } DocNumbering -Text 'AnotherChart' { DocChart -Title 'Priviliged Group Members' -DataTable $TableForCharts -Key 'Name' -Value 'SomeValue' } $Table1 = Get-Process | Select-Object -First 5 DocTable -DataTable $Table -Design ColorfulGrid DocList { DocListItem -Text 'Test' -Level 0 DocListItem -Text 'Test1' -Level 2 } DocList -Type Numbered { DocListItem -Text 'Test' -Level 0 DocListItem -Text 'Test1' -Level 2 } DocTable -DataTable $Table1 -Design ColorfulGrid } -Open
Documentimo basically allows you to create Word documents without Word installed. While the example above is just there to show you the basics here's how you can use it to create Active Directory documentation.
Import-Module PSWinDocumentation.AD -Force Import-Module Documentimo.psd1 -Force if ($null -eq $ADForest) { $ADForest = Get-WinADForestInformation -Verbose -PasswordQuality -DontRemoveEmpty } $CompanyName = 'Evotec' Documentimo -FilePath "$PSScriptRoot\Starter-AD.docx" { DocTOC -Title 'Table of content' DocPageBreak DocText { "This document provides low-level documentation of Active Directory infrastructure in Evotec organization. This document contains general data that has been exported from Active Directory and provides an overview of the whole environment." } DocNumbering -Text 'General Information - Forest Summary' -Level 0 -Type Numbered -Heading Heading1 { DocText { "Active Directory at $CompanyName has a forest name $($ADForest.Forest). Following table contains forest summary with important information:" } DocTable -DataTable $ADForest.ForestInformation -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Forest Summary' DocText -LineBreak DocText -Text 'Following table contains FSMO servers:' DocTable -DataTable $ADForest.ForestFSMO -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'FSMO Roles' DocText -LineBreak DocText -Text 'Following table contains optional forest features:' DocTable -DataTable $ADForest.ForestOptionalFeatures -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Optional Features' DocText -LineBreak DocText -Text 'Following UPN suffixes were created in this forest:' DocTable -DataTable $ADForest.ForestUPNSuffixes -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'UPN Suffixes' DocText -LineBreak if ($ADForest.ForestSPNSuffixes) { DocText -Text 'Following SPN suffixes were created in this forest:' DocTable -DataTable $ADForest.ForestSPNSuffixes -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'UPN Suffixes' } else { DocText -Text 'No SPN suffixes were created in this forest.' } DocPageBreak DocNumbering -Text 'General Information - Forest Sites' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Forest Sites list can be found below:" DocTable -DataTable $ADForest.ForestSites1 -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' DocText -LineBreak DocText -Text "Forest Sites list can be found below:" DocTable -DataTable $ADForest.ForestSites2 -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' # DocText -LineBreak } DocNumbering -Text 'General Information - Subnets' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Table below contains information regarding relation between Subnets and sites" DocTable -DataTable $ADForest.ForestSubnets1 -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' DocText -LineBreak DocText -Text "Table below contains information regarding relation between Subnets and sites" DocTable -DataTable $ADForest.ForestSubnets2 -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' # DocText -LineBreak } DocNumbering -Text 'General Information - Site Links' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Forest Site Links information is available in table below" DocTable -DataTable $ADForest.ForestSiteLinks -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' # DocText -LineBreak } } foreach ($Domain in $ADForest.FoundDomains.Keys) { DocPageBreak DocNumbering -Text "General Information - Domain $Domain" -Level 0 -Type Numbered -Heading Heading1 { DocNumbering -Text 'General Information - Domain Summary' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following domain exists within forest $($ADForest.ForestName):" DocList -Type Bulleted { DocListItem -Level 1 -Text "Domain $($ADForest.FoundDomains.$Domain.DomainInformation.DistinguishedName)" DocListItem -Level 2 -Text "Name for fully qualified domain name (FQDN): $($ADForest.FoundDomains.$Domain.DomainInformation.DNSRoot)" DocListItem -Level 2 -Text "Name for NetBIOS: $($ADForest.FoundDomains.$Domain.DomainInformation.NetBIOSName)" } } DocNumbering -Text 'General Information - Domain Controllers' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text 'Following table contains domain controllers' DocTable -DataTable ($ADForest.FoundDomains.$Domain.DomainControllers) -Design ColorfulGridAccent5 -AutoFit Window #-OverwriteTitle 'Forest Summary' DocText -LineBreak DocText -Text "Following table contains FSMO servers with roles for domain $Domain" DocTable -DataTable ($ADForest.FoundDomains.$Domain.DomainFSMO) -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle "FSMO Roles for $Domain" } DocNumbering -Text 'General Information - Password Policies' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains password policies for all users within $Domain" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainDefaultPasswordPolicy -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle "Default Password Policy for $Domain" } DocNumbering -Text 'General Information - Fine-grained Password Policies' -Level 1 -Type Numbered -Heading Heading1 { if ($ADForest.FoundDomains.$Domain.DomainFineGrainedPolicies) { DocText -Text 'Following table contains Fine-grained password policies' DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainFineGrainedPolicies -Design ColorfulGridAccent5 -AutoFit Window # -OverwriteTitle "Fine-grained Password Policy for <Domain>" } else { DocText { "Following section should cover fine-grained password policies. " ` + "There were no fine-grained password polices defined in $Domain. There was no formal requirement to have them set up." } } } DocNumbering -Text 'General Information - Group Policies' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains group policies for $Domain" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainGroupPolicies -Design ColorfulGridAccent5 -AutoFit Window } DocNumbering -Text 'General Information - Group Policies Details' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains group policies for $Domain" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainGroupPoliciesDetails -Design ColorfulGridAccent5 -AutoFit Window -MaximumColumns 6 } DocNumbering -Text 'General Information - DNS A/SRV Records' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains SRV records for Kerberos and LDAP" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainDNSSRV -Design ColorfulGridAccent5 -AutoFit Window -MaximumColumns 10 DocText -LineBreak DocText -Text "Following table contains A records for Kerberos and LDAP" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainDNSA -Design ColorfulGridAccent5 -AutoFit Window -MaximumColumns 10 } DocNumbering -Text 'General Information - Trusts' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains trusts established with domains..." DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainTrusts -Design ColorfulGridAccent5 -AutoFit Window -MaximumColumns 10 DocText -LineBreak } DocNumbering -Text 'General Information - Organizational Units' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text "Following table contains all OU's created in $Domain" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainOrganizationalUnits -Design ColorfulGridAccent5 -AutoFit Window -MaximumColumns 4 DocText -LineBreak } DocNumbering -Text 'General Information - Priviliged Groups' -Level 1 -Type Numbered -Heading Heading1 { DocText -Text 'Following table contains list of priviliged groups and count of the members in it.' DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainGroupsPriviliged -Design ColorfulGridAccent5 -AutoFit Window DocChart -Title 'Priviliged Group Members' -DataTable $ADForest.FoundDomains.$Domain.DomainGroupsPriviliged -Key 'Group Name' -Value 'Member Count' } DocNumbering -Text "General Information - Domain Users in $Domain" -Level 1 -Type Numbered -Heading Heading1 { DocNumbering -Text 'General Information - Users Count' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text "Following table and chart shows number of users in its categories" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainUsersCount -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Users Count' DocChart -Title 'Servers Count' -DataTable $ADForest.FoundDomains.$Domain.DomainUsersCount } DocNumbering -Text 'General Information - Domain Administrators' -Level 2 -Type Numbered -Heading Heading2 { if ($ADForest.FoundDomains.$Domain.DomainAdministratorsRecursive) { DocText -Text 'Following users have highest priviliges and are able to control a lot of Windows resources.' DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainAdministratorsRecursive -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text 'No Domain Administrators users were defined for this domain.' } } DocNumbering -Text 'General Information - Enterprise Administrators' -Level 2 -Type Numbered -Heading Heading2 { if ($ADForest.FoundDomains.$Domain.DomainEnterpriseAdministratorsRecursive) { DocText -Text 'Following users have highest priviliges across Forest and are able to control a lot of Windows resources.' DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainEnterpriseAdministratorsRecursive -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text 'No Enterprise Administrators users were defined for this domain.' } } } DocNumbering -Text "General Information - Computer Objects in $Domain" -Level 1 -Type Numbered -Heading Heading1 { DocNumbering -Text 'General Information - Computers' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text "Following table and chart shows number of computers and their versions" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainComputersCount -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Computers Count' DocChart -Title 'Servers Count' -DataTable $ADForest.FoundDomains.$Domain.DomainComputersCount -Key 'System Name' -Value 'System Count' } DocNumbering -Text 'General Information - Servers' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text "Following table and chart shows number of servers and their versions" DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainServersCount -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Servers Count' DocChart -Title 'Servers Count' -DataTable $ADForest.FoundDomains.$Domain.DomainServersCount -Key 'System Name' -Value 'System Count' } DocNumbering -Text 'General Information - Unknown Computers' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text "Following table and chart shows number of unknown object computers in domain." DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainComputersUnknownCount -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Unknown Computers Count' DocChart -Title 'Servers Count' -DataTable $ADForest.FoundDomains.$Domain.DomainComputersUnknownCount -Key 'System Name' -Value 'System Count' } } DocNumbering -Text 'Domain Password Quality' -Level 1 -Type Numbered -Heading Heading1 { Doctext { "This section provides overview about password quality used in $Domain. One should review if all those potentially" ` + " dangerous approaches to password quality should be left as is or addressed in one way or another." } DocNumbering -Text 'Password Quality - Passwords with Reversible Encryption' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text 'Passwords of these accounts are stored using reversible encryption.' if ($ADForest.FoundDomains.$Domain.DomainPasswordClearTextPassword) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordClearTextPassword -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text 'There are no accounts that have passwords stored using reversible encryption.' } } DocNumbering -Text 'Password Quality - Passwords with LM Hash' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'LM-hashes is the oldest password storage used by Windows, dating back to OS/2 system.' ` + ' Due to the limited charset allowed, they are fairly easy to crack. Following accounts are affected:' } if ($ADForest.FoundDomains.$Domain.DomainPasswordLMHash) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordLMHash -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'LM-hashes is the oldest password storage used by Windows, dating back to OS/2 system.' ` + ' There were no accounts found that use LM Hashes.' } } } DocNumbering -Text 'Password Quality - Empty Passwords' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text 'Following accounts have no password set:' if ($ADForest.FoundDomains.$Domain.DomainPasswordEmptyPassword) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordEmptyPassword -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text "There are no accounts in $Domain that have no password set." } } DocNumbering -Text 'Password Quality - Known passwords' -Level 2 -Type Numbered -Heading Heading2 { DocText { "Passwords of these accounts have been found in given dictionary. It's highly recommended to " ` + "notify those users and ask them to change their passwords asap!" } if ($ADForest.FoundDomains.$Domain.DomainPasswordWeakPassword) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordWeakPassword -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text 'There were no passwords found that match given dictionary.' } } DocNumbering -Text 'Password Quality - Default Computer Password' -Level 2 -Type Numbered -Heading Heading2 { DocText -Text 'These computer objects have their password set to default:' if ($ADForest.FoundDomains.$Domain.DomainPasswordDefaultComputerPassword) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordDefaultComputerPassword -Design ColorfulGridAccent5 -AutoFit Window } else { DocText -Text 'There were no accounts found that match default computer password criteria.' } } DocNumbering -Text 'Password Quality - Password Not Required' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'These accounts are not required to have a password. For some accounts it may be perfectly acceptable ' ` + ' but for some it may not. Those accounts should be reviewed and accepted or changed to proper security.' } if ($ADForest.FoundDomains.$Domain.DomainPasswordPasswordNotRequired) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordPasswordNotRequired -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'There were no accounts found that does not require password.' } } } DocNumbering -Text 'Password Quality - Non expiring passwords' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'Following account have do not expire password policy set on them. Those accounts should be reviewed whether ' ` + 'allowing them to never expire is good idea and accepted risk.' } if ($ADForest.FoundDomains.$Domain.DomainPasswordPasswordNeverExpires) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordPasswordNeverExpires -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { "There are no accounts in $Domain that never expire." } } } DocNumbering -Text 'Password Quality - AES Keys Missing' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'Following accounts have their Kerberos AES keys missing' } if ($ADForest.FoundDomains.$Domain.DomainPasswordAESKeysMissing) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordAESKeysMissing -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'There are no accounts that hvae their Kerberos AES keys missing.' } } } DocNumbering -Text 'Password Quality - Kerberos Pre-Auth Not Required' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'Kerberos pre-authentication is not required for these accounts' } if ($ADForest.FoundDomains.$Domain.DomainPasswordPreAuthNotRequired) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordPreAuthNotRequired -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'There were no accounts found that do not require pre-authentication.' } } } DocNumbering -Text 'Password Quality - Only DES Encryption Allowed' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'Only DES encryption is allowed to be used with these accounts' } if ($ADForest.FoundDomains.$Domain.DomainPasswordDESEncryptionOnly) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordDESEncryptionOnly -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'There are no account that require only DES encryption.' } } } DocNumbering -Text 'Password Quality - Delegatable to Service' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'These accounts are allowed to be delegated to a service:' } if ($ADForest.FoundDomains.$Domain.DomainPasswordDelegatableAdmins) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordDelegatableAdmins -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'No accounts were found that are allowed to be delegated to a service.' } } } DocNumbering -Text 'Password Quality - Groups of Users With Same Password' -Level 2 -Type Numbered -Heading Heading2 { DocText { 'Following groups of users have same passwords:' } if ($ADForest.FoundDomains.$Domain.DomainPasswordDuplicatePasswordGroups) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordDuplicatePasswordGroups -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { "There are no 2 passwords that are the same in $Domain." } } } DocNumbering -Text 'Password Quality - Leaked Passwords' -Level 2 -Type Numbered -Heading Heading2 { DocText { "Passwords of these accounts have been found in given HASH dictionary (https://haveibeenpwned.com/). It's highly recommended to " ` + "notify those users and ask them to change their passwords asap!" } if ($ADForest.FoundDomains.$Domain.DomainPasswordHashesWeakPassword) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordHashesWeakPassword -Design ColorfulGridAccent5 -AutoFit Window } else { DocText { 'There were no passwords found that match in given dictionary.' } } } DocNumbering -Text 'Password Quality - Statistics' -Level 2 -Type Numbered -Heading Heading2 { DocText { "Following table and chart shows password statistics" } if ($ADForest.FoundDomains.$Domain.DomainPasswordStats) { DocTable -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordStats -Design ColorfulGridAccent5 -AutoFit Window -OverwriteTitle 'Password Quality - Statistics' } else { DocText { 'There were no passwords found that match in given dictionary.' } } DocChart -Title 'Password Statistics' -DataTable $ADForest.FoundDomains.$Domain.DomainPasswordStats # Hashtables don't require Key/Value pair } } } } } -Open
I hope this gives you some ideas. Like I mentioned above, it's still early alpha so if you have any problems with it, or want some changes to let me know on GitHub Documentimo project. You can use Documentimo by installing it from PowerShell Gallery.
Install-Module Documentimo -Force
Similarly to Documentimo, Excelimo offers a similar approach when building Excel Workbook. I've not spent much time designing the look and feel of Excel but below code should give you some ideas on how to use it. Again, keep in mind this is alpha material.
Import-Module PSWinDocumentation.AD -Force Import-Module Excelimo -Force if ($null -eq $ADForest) { $ADForest = Get-WinADForestInformation -Verbose -PasswordQuality } Excel -FilePath $PSScriptRoot\"Run-Demo02.xlsx" { WorkbookProperties -Title 'PSWinDocumentation - Active Directory Demo' Worksheet -DataTable $ADForest.ForestInformation -Name 'Forest Information' -TabColor Green -AutoFilter -AutoFit Worksheet -DataTable $ADForest.ForestSites -Name 'Forest Sites' -TabColor RoyalBlue -AutoFilter -AutoFit Worksheet -DataTable $ADForest.ForestOptionalFeatures -Name 'Forest Optional Features' -TabColor Red -AutoFilter -AutoFit } -Open
You can install Excelimo directly from PowerShell Gallery.
Install-Module Excelimo -Force
Dashimo PowerShell module has been on the market for a while now, but I wanted to take a moment to show you how to build Active Directory stuff with it. It's also an excellent material to show how you can use two separate modules to bind data together. Something that wasn't possible with the original PSWinDocumentation. This one binds Find-Events command that I released a while ago to provide your last seven days of changes in Active Directory and mixing it up with current AD state. And with Dashimo's ability to have conditional formatting you can easily show what matters to you with it.
Import-Module Dashimo -Force Import-Module PSWinDocumentation.AD -Force Import-Module PSWinReportingV2 if ($null -eq $DataSetForest) { $DataSetForest = Get-WinADForestInformation -Verbose -DontRemoveEmpty -PasswordQuality -Splitter "`r`n" } if ($null -eq $DataSetEvents) { $DataSetEvents = Find-Events -Report ADUserChangesDetailed, ADUserChanges, ADUserLockouts, ADUserStatus, ADGroupChanges -Servers 'AD1', 'AD2' -DatesRange Last7days -Quiet } Dashboard -Name 'Dashimo Test' -FilePath $PSScriptRoot\DashboardActiveDirectory.html -Show { Tab -Name 'Forest' { Section -Name 'Forest Information' -Invisible { Section -Name 'Forest Information' { Table -HideFooter -DataTable $DataSetForest.ForestInformation } Section -Name 'FSMO Roles' { Table -HideFooter -DataTable $DataSetForest.ForestFSMO } } Section -Name 'Forest Domain Controllers' -Collapsable { Panel { Table -HideFooter -DataTable $DataSetForest.ForestDomainControllers } } Section -Name 'Forest Optional Features / UPN Suffixes / SPN Suffixes' -Collapsable { Panel { Table -HideFooter -DataTable $DataSetForest.ForestOptionalFeatures -Verbose } Panel { Table -HideFooter -DataTable $DataSetForest.ForestUPNSuffixes -Verbose } Panel { Table -HideFooter -DataTable $DataSetForest.ForestSPNSuffixes -Verbose } } Section -Name 'Sites / Subnets / SiteLinks' -Collapsable { Panel { Table -HideFooter -DataTable $DataSetForest.ForestSites -Verbose } Panel { Table -HideFooter -DataTable $DataSetForest.ForestSubnets -Verbose } Panel { Table -HideFooter -DataTable $DataSetForest.ForestSiteLinks -Verbose } } } foreach ($Domain in $DataSetForest.FoundDomains.Keys) { Tab -Name $Domain { Section -Name 'Domain Controllers / FSMO Roles' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainControllers -Verbose } Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainFSMO -Verbose } } Section -Name 'Password Policies' -Invisible { Section -Name 'Default Password Policy' { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainDefaultPasswordPolicy -Verbose } Section -Name 'Domain Fine Grained Policies' { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainFineGrainedPolicies -Verbose } } Section -Name 'Users' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainUsers } } Section -Name 'Computers' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainComputers } } Section -Name 'Groups Priviliged' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainGroupsPriviliged } Panel { #Chart -DataTable $DataSetForest.FoundDomains.'ad.evotec.xyz'.DomainGroupsPriviliged -DataNames 'Group Name' -DataCategories $DataSetForest.FoundDomains.'ad.evotec.xyz'.DomainGroupsPriviliged.'Members Count' -DataValues 'Members Count' } } Section -Name 'Organizational Units' { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainOrganizationalUnits } Section -Name 'OU ACL Basic' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainOrganizationalUnitsBasicACL } } Section -Name 'OU ACL Extended' { Panel { Table -HideFooter -DataTable $DataSetForest.FoundDomains.$Domain.DomainOrganizationalUnitsExtended } } } } Tab -Name 'Changes in Last 7 days' { Section -Name 'Group Changes' -Collapsable { Table -HideFooter -DataTable $DataSetEvents.ADGroupChanges } Section -Name 'User Status' -Collapsable { Table -HideFooter -DataTable $DataSetEvents.ADUserStatus } Section -Name 'User Changes' -Collapsable { Table -HideFooter -DataTable $DataSetEvents.ADGroupChanges } Section -Name 'User Lockouts' -Collapsable { Table -HideFooter -DataTable $DataSetEvents.ADUserStatus } } }
If you would like to learn more about Dashimo, you can read the latest article on it. Installing it is as easy as this:
Install-Module Dashimo -Force
To finish up this, a bit lengthy article I wanted to show you how you can send the data from PSWinDocumentation.AD module (or any other module) to email. While most likely most people got this covered, my Emailimo module uses a bit different approach (similar to what I've been using in some of my latest modules branded with IMO (I made one)). If you would like to find out more about Emailimo module have a read here.
And if you notice the attachment, since we used AttachSelf switch we got HTML attached, that has few more functionalities, built-in.
Nice right? You don't have to build HTML yourself, and you don't have to play with HTML at all. You use it in a similar way you use Dashimo, Documentimo, and Excelimo. Code to generate that is below.
Import-Module Emailimo -Force if ($null -eq $DataSetForest) { $DataSetForest = Get-WinADForestInformation -Verbose -TypesRequired ForestInformation } if ($null -eq $DataSetEvents) { $DataSetEvents = Find-Events -Report ADUserChangesDetailed, ADUserChanges, ADUserLockouts, ADUserStatus, ADGroupChanges -Servers 'AD1', 'AD2' -DatesRange Last14days } $UserNotify = 'Przemyslaw Klys' Email -FilePath 'C:\Users\przemyslaw.klys\OneDrive - Evotec\Support\GitHub\Emailimo\Ignore\Temp.html' { EmailHeader { EmailFrom -Address 'reminder@domain.pl' EmailTo -Addresses "przemyslaw.klys@domain.pl" EmailServer -Server 'mail.euvic.pl' -UserName 'rpassword' -Password 'C:\Support\Important\Password-Evotec-Reminder.txt' -PasswordAsSecure -PasswordFromFile EmailOptions -Priority High -DeliveryNotifications Never EmailSubject -Subject 'This is a test email' } EmailBody -FontFamily 'Calibri' -Size 15 { EmailText -Text "Hello ", $UserNotify, "," -Color None, Blue, None -Verbose -LineBreak EmailText -Text "Here's some ", 'Forest information:' -Color None, Blue EmailTable -DataTable $DataSetForest.ForestInformation EmailText -LineBreak EmailText -Text 'And here are ', 'User changes', ' detailed:' -Color BlueViolet, Green, Red EmailTable -DataTable $DataSetEvents.ADUserChangesDetailed EmailTextBox { 'You can add more tables, and more things. You can even attach self which gives you' 'couple of nice things... such as different JS/CSS.' } EmailText -LineBreak EmailText -Text 'Keep in mind this is still ', 'work in progress!' ` -Color None, LightSkyBlue, None, LightSkyBlue -TextDecoration none, underline, none, underline -FontWeight normal, bold, normal, bold EmailText -LineBreak EmailTextBox { 'Kind regards,' 'Evotec IT' } } } -AttachSelf
While I've shown you a couple of ways how you can utilize this and the other modules I wanted to show you that there are different types of documentation. Whether you send it via email, create excel, word, HTML it all can help you know your environment. With old PSWinDocumentation project, you were unable to merge different data together the way you wanted to. I do hope, with the release of PSWinDocumentation.AD you can have some fun with it. Finally, if you like any of that work, consider leaving a star on Github, consider opening an issue with feedback and most of all if you're capable of adding something feel free to help me out by submitting PR's. Let's build great documentation data for Active Directory and other types I'm working on together!