This is a PowerShell script which offers an Active Directory Health Check.
These checks are based on my personal best practices. Some of the checks may not be applicable to your environment.
The following fundamental guidelines apply to the script:
1) Must work for all domains in a forest tree.
2) Must work on PowerShell v3 and above.
3) Must work without module dependencies, except for the PowerShell core modules.
4) Must work without Administrator privileges.
5) Must work with Microsoft Word 2007 and above.
The following languages are supported for both Word and the operating system the script runs on:
Catalan | Danish | Dutch |
English | Finnish | French |
German | Norwegian | Portuguese |
Spanish | Swedish |
The following checks are currently included in the script:
Users | Direct member of Domain Local group |
Users | Password never expires |
Users | Password not required |
Users | Change password at next logon |
Users | Password not changed in last 12 months |
Users | Account without expiration date |
Users | Do not require Kerberos pre-authentication |
Users | Disabled |
Groups | Privileged with more than 5 members |
Groups | Privileged with no members |
Groups | With no members |
Sites | Without a description |
Sites | No server(s) |
Sites | Without a connection |
Sites | No sitelink(s) |
Sites | Without one or more subnet(s) |
Sitelinks | With one site |
Sitelinks | With more than two sites |
Sitelinks | Without a description |
Subnets in Sites | Available but not used |
Domain Controllers | No contact in last 3 months |
Member servers | Password never expires |
Member servers | Password older than 6 months |
Member servers | Account never expires |
Member servers | Account disabled |
Organisational Unit | GPO inheritance blocked |
Get your copy of the script here!
Release notes | ![]() |
Signed (.txt) | ![]() |
Unsigned (.txt) | ![]() |
Signed (.zip) | ![]() |
Unsigned (.zip) | ![]() |
All (.zip) | ![]() |
Excellent work! Thanks Jeff!
Jeff: the script looks great but one suggestion. I think it would make sense to separate out the account tests based on whether the account is disabled or not. I am not sure that people care that the account’s password was not changed in 6 months if the account is disabled.
–Tracy
Hi Tracy,
Thanks, and suggestions are always welcome 😉
In this case it was a conscious choice, for now, to not separate out those accounts. This because some of the users of the script may want to comment out check 1 because they won’t need it. So I can’t always use the output of check 1 and filter that out from the output of check 2.
However… there is something on my to-do list for the next major release that I think will comply with your suggestion: Exclusions to a check 😉
Jeff.
Jeff, nice work – though some things I do not get. For instance:
1) For all switches you’re doing:
..
[Switch]$PDF=$false,
..
If($PDF -eq $Null)
{ $PDF = $False }
..
If(!(Test-Path Variable:PDF))
{ $PDF = $False }
What’s the point (since it will take the default value when not specified)?
2) For certain switches you’re checking if they were provided using:
$PSBoundParameters.ContainsKey(‘Sites’)
Why not set/use a default value, use Test-Path Variable:Sites or check if it’s $true using if( $Sites ..) (not If( $Sites -eq $true ..)
3) In Get-PriviledgedGroupsMemberCount (PS: it’s privileged)
$PriviledgedGroups = @(“S-1-5-32-544″;”S-1-5-32-548″;”S-1-5-32-549″;”S-1-5-32-551″;”$DomainSIDValue-519″;”$DomainSIDValue-518”)
foreach ($PriviledgedGroup in $PriviledgedGroups) {
$Source = New-Object DirectoryServices.DirectorySearcher(“LDAP://$DomainName”)
$source.SearchScope = ‘Subtree’
$source.PageSize = 100000
$Source.filter = “(objectSID=$PriviledgedGroup)”
Why not combine it in a single AD query using LDAP filters – this will make the AD people happy too 🙂
4) In Get-ADSiteLink (example of redundant code, there are more ..):
if ($Sites -ne $null) {
foreach ($Site in $Sites) {
$Object = New-Object -TypeName ‘PSObject’
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Domain’ -Value $Domain.FQDN
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Name’ -Value $($SiteLink.properties.item(“name”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Description’ -Value $($SiteLink.properties.item(“Description”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘ReplicationInterval’ -Value $($SiteLink.properties.item(“replinterval”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Site’ -Value $(($SiteLink.Properties.item(“sitelist”) -split ‘,’ -replace ‘CN=’,”)[0])
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘SiteCount’ -Value ($SiteLink.Properties.item(“sitelist”)).count
$Object
}
} else {
$Object = New-Object -TypeName ‘PSObject’
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Domain’ -Value $Domain.FQDN
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Name’ -Value $($SiteLink.properties.item(“name”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Description’ -Value $($SiteLink.properties.item(“Description”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘ReplicationInterval’ -Value $($SiteLink.properties.item(“replinterval”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Site’ -Value ”
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘SiteCount’ -Value ‘0’
$Object
}
can be rewritten as:
foreach ($Site in $Sites) {
$Object = New-Object -TypeName ‘PSObject’
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Domain’ -Value $Domain.FQDN
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Name’ -Value $($SiteLink.properties.item(“name”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Description’ -Value $($SiteLink.properties.item(“Description”))
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘ReplicationInterval’ -Value $($SiteLink.properties.item(“replinterval”))
if ($Sites -ne $null) {
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Site’ -Value $(($SiteLink.Properties.item(“sitelist”) -split ‘,’ -replace ‘CN=’,”)[0])
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘SiteCount’ -Value ($SiteLink.Properties.item(“sitelist”)).count
}
} else {
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘Site’ -Value ”
$Object | Add-Member -MemberType ‘NoteProperty’ -Name ‘SiteCount’ -Value ‘0’
}
$Object
}
Keep up the good work!
Just noticed, why loop through all the elements after splitting up the Sites from the SiteList property when you are not processing those values and only add the first one – not the $Site DN?
( $(($SiteLink.Properties.item(“sitelist”) -split ‘,’ -replace ‘CN=’,”)[0]) )
What am I missing something here?
Hi Michel,
Sweet feedback! I’ll include most of them in the next version… do you mind some credits in the comments? 😉
For the part where the Word/PDF file is generated I’m not responsible, but I’ll fwd your feedback to the guys that is are.
I’m testing for the usage of the variable this way because I want to be consistent in checking for the usage of the variable, no matter the variable type.
Not the most optimal way, but I prefer consistency.
I am / will also be checking for the usage of variables that aren’t of the type [switch].
Thanks for taking the time to write and post the feedback, much appreciated!
Jeff.
No reason, will update in the next release 🙂
Michel,
I am the guy whose code Jeff is using. I do all the hoops because some people would get a variable not defined on some of the switches. I have no idea why. In my testing, I could never reproduce the error but I heard from enough people having the issue I had to do something. Doing the two-step shuffle of:
If($PDF -eq $Null)
{
$PDF = $False
}
If(!(Test-Path Variable:PDF))
{
$PDF = $False
}
“fixed” the issue for the people having the undefined variable issue.
I am in the process of changing some of my code to include extra “()” in logic tests because some people using non English versions of Windows and Word and PoSH V4 are having an issue. For example:
$Results = Get-Something -EA 0
If($? -and $Results -ne $Null)
{
#successful retrieval with data
}
ElseIf($? -and $Results -eq $Null)
{
#successful retrieval but no data returned
}
Else
{
#oops something went wrong
}
For some people, they always get the “oops” message.
I am having to change the code to:
If($? -and ($Results -ne $Null))
{
#successful retrieval with data
}
ElseIf($? -and ($Results -eq $Null))
{
#successful retrieval but no data returned
}
Else
{
#oops something went wrong
}
When I add the extra pair of (), for those users, the code now works and they get data.
Why is the extra pair of () needed? I have no clue, they should not be needed.
I find myself in the position of having to create code that works for everyone all the time. Why some things do not work for a few percentage of users but works for everyone else is beyond my limited understanding.
Jeff can attest to my very limited PowerShell knowledge and coding skills.
http://carlwebster.com/where-to-get-copies-of-the-documentation-scripts/
Thanks
Carl Webster
Citrix Technology Professional
http://www.CarlWebster.com
The Accidental Citrix Admin
Sorry, I am a fresher in scripting, How we can execute this script? by simply executing in powershell console or need to use any switch?
Hi Jinish Kg,
Don’t worry, everyone starts at the beginning…
Download and save the script, open a command prompt, browse to the script, type: Get-Help AD-Health-Check-v1.0-signed.ps1
Actually executing the script you can do with: .\AD-Health-Check-v1.0-signed.ps1
To execute the script, you need to make sure that script execution is enabled, which you can verify with Get-ExecutionPolicy and change with Set-ExecutionPolicy.
More information about execution policies: http://jeffwouters.nl/index.php/2011/11/powershell-and-the-exectution-policies-explained
Jeff.
Excellent script, i will add this to my article about performing an AD health check http://www.networkangel.net/active-directory-health-check-tools
Thank you for the compliment 🙂
Hello Jeff,
Is it possible to make this script running for Word 2016? For now it only supports 2013 and older.
Hi Marcel,
Take a look at http://carlwebster.com/microsoft-active-directory-health-check-powershell-script-v2-0 🙂
Jeff.
Please share the url to download the script the link is not working or please send the script to my mail
I am unable to download the script, can someone please send me on email .. suman.pattanaik@gmail.com
Kind help would be highly appreciated.