QADHealthcheck.psm1
<#
=========================================================================== Created with: SAPIEN Technologies, Inc., PowerShell Studio 2020 v5.7.181 Created on: 9/15/2020 9:02 AM Created by: Gary Cook Organization: Quest Filename: QADHealthcheck.psm1 ------------------------------------------------------------------------- Module Name: QADHealthcheck =========================================================================== #> function Create-UserObject { param ( [Parameter(Mandatory = $true, Position = 0)] [string[]]$Properties ) $obj = New-Object System.Management.Automation.PSObject foreach ($p in $Properties) { $obj | Add-Member -MemberType NoteProperty -Name $p -Value $null } return $obj } Function Get-RegistryValue { # Gets the specified registry value or $Null if it is missing [CmdletBinding()] Param ([string]$path, [string]$name, [string]$ComputerName) If ($ComputerName -eq $env:computername -or $ComputerName -eq "LocalHost") { $key = Get-Item -LiteralPath $path -EA 0 If ($key) { Return $key.GetValue($name, $Null) } Else { Return $Null } } Else { #path needed here is different for remote registry access $path1 = $path.SubString(6) $path2 = $path1.Replace('\', '\\') $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName) $RegKey = $Reg.OpenSubKey($path2) $Results = $RegKey.GetValue($name) If ($Null -ne $Results) { Return $Results } Else { Return $Null } } } Function OutputTimeServerRegistryKeys { Param ([string]$DCName) Write-Color -Text "Getting TimeServer Registry Keys for domain controller" -Color Green #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config AnnounceFlags #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config MaxNegPhaseCorrection #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config MaxPosPhaseCorrection #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters NtpServer #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters Type #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient SpecialPollInterval #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider Enabled $AnnounceFlags = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "AnnounceFlags" $DCName $MaxNegPhaseCorrection = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "MaxNegPhaseCorrection" $DCName $MaxPosPhaseCorrection = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "MaxPosPhaseCorrection" $DCName $NtpServer = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" "NtpServer" $DCName $NtpType = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" "Type" $DCName $SpecialPollInterval = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient" "SpecialPollInterval" $DCName $VMICTimeProviderEnabled = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider" "Enabled" $DCName $NTPSource = w32tm /query /computer:$DCName /source If ($VMICTimeProviderEnabled -eq 0) { $VMICEnabled = "Disabled" } Else { $VMICEnabled = "Enabled" } #create time server info array $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name DCName -Value $DCName $obj | Add-Member -MemberType NoteProperty -Name TimeSource -Value $NTPSource $obj | Add-Member -MemberType NoteProperty -Name AnnounceFlags -Value $AnnounceFlags $obj | Add-Member -MemberType NoteProperty -Name MaxNegPhaseCorrection -Value $MaxNegPhaseCorrection $obj | Add-Member -MemberType NoteProperty -Name MaxPosPhaseCorrection -Value $MaxPosPhaseCorrection $obj | Add-Member -MemberType NoteProperty -Name NtpServer -Value $NtpServer $obj | Add-Member -MemberType NoteProperty -Name NtpType -Value $NtpType $obj | Add-Member -MemberType NoteProperty -Name SpecialPollInterval -Value $SpecialPollInterval $obj | Add-Member -MemberType NoteProperty -Name VMICTimeProvider -Value $VMICEnabled #[void]$Script:TimeServerInfo.Add($obj) return $obj } Function OutputADFileLocations { Param ([string]$DCName) Write-Color -Text "Getting AD Database, Logfile and SYSVOL locations" -Color Green #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters 'DSA Database file' #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters 'Database log files path' #HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters SysVol $DSADatabaseFile = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" "DSA Database file" $DCName $DatabaseLogFilesPath = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" "Database log files path" $DCName $SysVol = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" "SysVol" $DCName #calculation is taken from http://blogs.metcorpconsulting.com/tech/?p=177 $DITRemotePath = $DSADatabaseFile.Replace(":", "$") $DITFile = "\\$DCName\$DITRemotePath" $DITsize = ([System.IO.FileInfo]$DITFile).Length $DITsize = ($DITsize/1GB) $DSADatabaseFileSize = "{0:N3}" -f $DITsize #create AD Database info array $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name DSADatabaseFile -Value $DSADatabaseFile $obj | Add-Member -MemberType NoteProperty -Name DatabaseLogFilesPath -Value $DatabaseLogFilesPath $obj | Add-Member -MemberType NoteProperty -Name SysVol -Value $SysVol $obj | Add-Member -MemberType NoteProperty -Name DSADatabaseFileSize -Value $DSADatabaseFileSize return $obj } function CountOUObjects ($OU, $Server) { [int]$UserCount = 0 [int]$ComputerCount = 0 [int]$GroupCount = 0 $Results = Get-ADUser -Filter * -SearchBase $OU.DistinguishedName -Server $Server -EA 0 If ($Null -eq $Results) { $UserCount = 0 } ElseIf ($Results -is [array]) { $UserCount = $Results.Count } Else { $UserCount = 1 } $Results = Get-ADComputer -Filter * -SearchBase $OU.DistinguishedName -Server $Server -EA 0 If ($Null -eq $Results) { $ComputerCount = 0 } ElseIf ($Results -is [array]) { $ComputerCount = $Results.Count } Else { $ComputerCount = 1 } $Results = Get-ADGroup -Filter * -SearchBase $OU.DistinguishedName -Server $Server -EA 0 If ($Null -eq $Results) { $GroupCount = 0 } ElseIf ($Results -is [array]) { $GroupCount = $Results.Count } Else { $GroupCount = 1 } #createou count object $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name UserCount -Value $UserCount $obj | Add-Member -MemberType NoteProperty -Name ComputerCount -Value $ComputerCount $obj | Add-Member -MemberType NoteProperty -Name GroupCount -Value $GroupCount return $obj } function Get-RIDsremainingAdPsh { param ($domainDN) $property = get-adobject "cn=rid manager$,cn=system,$domainDN" -property ridavailablepool -server ((Get-ADDomain $domaindn).RidMaster) $rid = $property.ridavailablepool [int32]$totalSIDS = $($rid) / ([math]::Pow(2, 32)) [int64]$temp64val = $totalSIDS * ([math]::Pow(2, 32)) [int32]$currentRIDPoolCount = $($rid) - $temp64val $ridsremaining = $totalSIDS - $currentRIDPoolCount $obj = New-Object System.Management.Automation.PSObject $obj | add-member -MemberType NoteProperty -Name RIDsIssued -Value $currentRIDPoolCount $obj | add-member -MemberType NoteProperty -Name RIDsRemaining -Value $ridsremaining return $obj #Write-Host "RIDs issued: $currentRIDPoolCount" #Write-Host "RIDs remaining: $ridsremaining" } function count-groupusers ($group, $server) { $temp = Get-ADGroup -Identity $group -Properties * -Server $server -ErrorAction SilentlyContinue $count = 0 foreach ($member in $temp.members) { try { $user = Get-ADUser -Identity $member -Properties * -Server $server -ErrorAction SilentlyContinue $count += 1 } catch { } } return $count } function get-adtest ($server) { $utest = Get-ADUser -Filter * -Properties * -Server $server | ?{ $_.samaccountname -like 'n*' } $OUtest = Get-ADOrganizationalUnit -Filter * -Properties * -Server $server | ?{ $_.name -like 'd*' } $gtest = Get-ADGroup -Filter * -Properties * -Server $server | ?{ ($_.members | measure).count -gt 5 } } function Get-ADUsersLastLogon() { param ( [parameter(Mandatory = $true)] [string]$usersid, [parameter(Mandatory = $true)] [string]$TargetDC ) $users = Get-ADUser -identity $usersid -Properties name, samaccountname, passwordlastset, enabled $time = 0 $dcs = Get-ADDomainController -Server $TargetDC -Filter * foreach ($dc in $dcs) { $hostname = $dc.HostName $currentUser = Get-ADUser $user.SamAccountName | Get-ADObject -Server $hostname -Properties lastLogon if ($currentUser.LastLogon -gt $time) { $time = $currentUser.LastLogon } } $dt = [DateTime]::FromFileTime($time) return $dt } Function Get-bADpasswords { [CmdletBinding()] param ( [Parameter(HelpMessage = 'Input paths for word lists with weak passwords. Only "BadPasswords.txt" is used if nothing specified.')] [alias("Wordlists")] [array]$arrBadPasswordFiles = @('BadPasswords.txt'), [Parameter(HelpMessage = 'Input name of the Domain Controller to query, e.g. "DC1".', Mandatory = $True)] [ValidateNotNullOrEmpty()] [alias("DC", "DomainController")] [string]$strDomainController, [Parameter(HelpMessage = 'Input the AD Naming Context, e.g. "DC=AD,DC=HEIDELBERG,DC=NU".', Mandatory = $True)] [ValidateNotNullOrEmpty()] [alias("NC", "NamingContext")] [string]$strNamingContext, [Parameter(HelpMessage = 'Create dump CSV file of users with weak passwords.')] [alias("CSV")] [switch]$bolWriteToCsvFile, [Parameter(HelpMessage = 'Create log file with script execution status.')] [alias("Log")] [Switch]$bolWriteToLogFile, [Parameter(HelpMessage = 'Make the log file verbose - more detailed logging.')] [alias("LogVerbose")] [switch]$bolWriteVerboseInfoToLogfile, [Parameter(HelpMessage = 'Write clear text value of weak passwords to log file.')] [alias("LogPasswords")] [switch]$bolWriteClearTextPasswordsToLogFile ) # ============ # # VARIABLES => # # ============ # $ScriptVersion = "1.05" # Set log/CSV file names with date/time $LogTimeStamp = Get-Date -Format ddMMyyyy-HHmmss $LogFileName = "Get-bADpasswords_$LogTimeStamp.txt" $CsvFileName = "Get-bADpasswords_$LogTimeStamp.csv" # Counters $intBadPasswordsFound = 0 $intBadPasswordsInLists = 0 $intBadPasswordsInListsDuplicates = 0 $intUsersAndHashesFromAD = 0 $intNullPasswordsFound = 0 # Constant $strBlankPasswordNThash = '31d6cfe0d16ae931b73c59d7e0c089c0' #output object $BUsers = @() # ============ # # FUNCTIONS => # # ============ # Function LogWrite { Param ( [string]$Logfile, [string]$LogEntryString, [ValidateSet("INFO", "DATA", "FAIL")] [string]$LogEntryType, [switch]$TimeStamp ) If ($TimeStamp -and $LogEntryType -and $LogEntryString) { $TimeNow = Get-Date -Format dd.MM.yyyy-HH:mm:ss Add-content $Logfile -Value "$TimeNow`t$LogEntryType`t$LogEntryString" } ElseIf ($TimeStamp -and $LogEntryString) { $TimeNow = Get-Date -Format dd.MM.yyyy-HH:mm:ss Add-content $Logfile -Value "$TimeNow`t$LogEntryString" } ElseIf ($LogEntryType -and $LogEntryString) { Add-content $Logfile -Value "$LogEntryType`t$LogEntryString" } Else { Add-content $Logfile -Value "$LogEntryString" } } Function Get-NTHashFromClearText { # Usage: Get-NTHashFromClearText -ClearTextPassword 'Pa$$W0rd' > 36185099f86b48b5af3cc46edd16efca Param ([string]$ClearTextPassword) Return ConvertTo-NTHash $(ConvertTo-SecureString $ClearTextPassword -AsPlainText -Force) } Function GetOut { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Ended" -LogEntryType INFO -TimeStamp } Write-Verbose "Ended" Break } # ============ # # SCRIPT => # # ============ # If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Started" -LogEntryType INFO -TimeStamp } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Version: $ScriptVersion" -LogEntryType INFO -TimeStamp } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Logfile: $LogFileName" -LogEntryType INFO -TimeStamp } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| CSVfile: $CsvFileName" -LogEntryType INFO -TimeStamp } # Create empty hash table for bad/weak passwords (case-insensitive on purpose) $htBadPasswords = @{ } # Populate array with usernames and NT hash values for enabled users only Write-Verbose "Calling Get-ADReplAccount with parameters..." If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Calling Get-ADReplAccount with parameters..." -LogEntryType INFO -TimeStamp } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| DC: $strDomainController" -LogEntryType INFO -TimeStamp } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| NC: $strNamingContext" -LogEntryType INFO -TimeStamp } # Set array to NULL for usage in ISE $arrUsersAndHashes = $null Try { $arrUsersAndHashes = Get-ADReplAccount -All -Server $strDomainController -NamingContext $strNamingContext | Where { $_.Enabled -eq $true -and $_.SamAccountType -eq 'User' } | Select SamAccountName, @{ Name = "NTHashHex"; Expression = { ConvertTo-Hex $_.NTHash } } } Catch { $ErrorMessage = $_.Exception.Message If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString $ErrorMessage -LogEntryType FAIL -TimeStamp } Write-Verbose "FAIL: $ErrorMessage" } $intUsersAndHashesFromAD = $arrUsersAndHashes.Count # We can only continue, if we got users and hashes If ($intUsersAndHashesFromAD -lt 1) { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "No data from AD - will quit!" -LogEntryType FAIL -TimeStamp } Write-Verbose "No data from AD - will quit!" GetOut } # Let's deliver the good news If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "AD returned $intUsersAndHashesFromAD usernames and NT hashes!" -LogEntryType DATA -TimeStamp } Write-Verbose "AD returned $intUsersAndHashesFromAD usernames and NT hashes!" # Add blank password NT hash to hashtable If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Adding blank password NT hash to hashtable..." -LogEntryType INFO -TimeStamp } Write-Verbose "Adding blank password NT hash to hashtable..." $htBadPasswords.Add($strBlankPasswordNThash, "") If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Loading bad password word lists..." -LogEntryType INFO -TimeStamp } Write-Verbose "Loading bad password word lists..." # Load each word list Foreach ($WordlistPath in $arrBadPasswordFiles) { If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Checking word list: $WordlistPath" -LogEntryType INFO -TimeStamp } Write-Verbose "| Checking word list: $WordlistPath" If (Test-Path $WordlistPath) { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Word list file found: $WordlistPath" -LogEntryType INFO -TimeStamp } Write-Verbose "Word list file found: $WordlistPath" $BadPasswordList = Get-Content -Path $WordlistPath $cnt_LineNumber = 1 Foreach ($BadPassword in $BadPasswordList) { # Detect and ignore emty strings If ($BadPassword -eq '') { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "| Empty input line ignored (line#: $cnt_LineNumber)." -LogEntryType INFO -TimeStamp } Write-Verbose "| Empty input line ignored (line#: $cnt_LineNumber)." $cnt_LineNumber++ Continue } $NTHash = $(Get-NTHashFromClearText $BadPassword) If ($htBadPasswords.ContainsKey($NTHash)) # NB! Case-insensitive on purpose { $intBadPasswordsInListsDuplicates++ If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Duplicate password: '$BadPassword' = $NTHash" -LogEntryType INFO -TimeStamp } Write-Verbose "| Duplicate password: '$BadPassword' = $NTHash (line#: $cnt_LineNumber)" } Else # New password to put into hash table { If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Adding to hashtable: '$BadPassword' = $NTHash" -LogEntryType INFO -TimeStamp } Write-Verbose "| Adding to hashtable: '$BadPassword' = $NTHash (line#: $cnt_LineNumber)" $htBadPasswords.Add($NTHash, $BadPassword) } # Counting line numbers $cnt_LineNumber++ } # Foreach BadPassword } Else # Word list not found { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Word list not found: $WordlistPath" -LogEntryType FAIL -TimeStamp } Write-Verbose "Word list not found: $WordlistPath" } } # Foreach BadPassword file $intBadPasswordsInLists = $htBadPasswords.Count - 1 # Count minus 1 because: blank password NT hash added by default, not from lists... # We can only continue, if we got weak passwords from word lists If ($intBadPasswordsInLists -lt 1) { If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "No data from word lists - will quit!" -LogEntryType FAIL -TimeStamp } Write-Verbose "No data from word lists - will quit!" GetOut } # Let's deliver the good news If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Word lists had a total of $intBadPasswordsInLists unique weak passwords!" -LogEntryType DATA -TimeStamp } Write-Verbose "Word lists had a total of $intBadPasswordsInLists unique weak passwords!" If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Word lists had a total of $intBadPasswordsInListsDuplicates duplicate weak passwords!" -LogEntryType DATA -TimeStamp } Write-Verbose "Word lists had a total of $intBadPasswordsInListsDuplicates duplicate weak passwords!" $Tuser = Create-UserObject -Properties "SamAccountName","NTHashHex","Password" Foreach ($objUser in $arrUsersAndHashes) { $strUserSamAccountName = $objUser.SamAccountName $strUserNTHashHex = $objUser.NTHashHex If ($strUserNTHashHex -eq $null) { If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "Null password found for user: $strUserSamAccountName" -LogEntryType INFO -TimeStamp } Write-Verbose "Null password found for user: $strUserSamAccountName" $intNullPasswordsFound++ $TUser.Samaccountname = $strUserSamAccountName $Tuser.NTHashHex = $strUserNTHashHex $Tuser.Password = $null Continue } If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Checking password hash of user: $strUserSamAccountName = $strUserNTHashHex" -LogEntryType INFO -TimeStamp } Write-Verbose "| Checking password hash of user: $strUserSamAccountName = $strUserNTHashHex" If ($htBadPasswords.ContainsKey($strUserNTHashHex)) # NB! Case-insensitive on purpose { $intBadPasswordsFound++ $strUserBadPasswordClearText = $htBadPasswords.Get_Item($strUserNTHashHex) $TUser.Samaccountname = $strUserSamAccountName $Tuser.NTHashHex = $strUserNTHashHex $Tuser.Password = $strUserBadPasswordClearText If ($bolWriteToLogFile) { If ($bolWriteClearTextPasswordsToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Weak password found for user: $strUserSamAccountName = '$strUserBadPasswordClearText'" -LogEntryType INFO -TimeStamp } Else { LogWrite -Logfile $LogFileName -LogEntryString "Weak password found for user: $strUserSamAccountName" -LogEntryType INFO -TimeStamp } } Write-Verbose "Weak password found for user: $strUserSamAccountName = '$strUserBadPasswordClearText'" # Handle CSV fil output If ($bolWriteToCsvFile) { LogWrite -Logfile $CsvFileName -LogEntryString "$strUserSamAccountName;$strUserNTHashHex" } } Else { If ($bolWriteToLogFile -and $bolWriteVerboseInfoToLogfile) { LogWrite -Logfile $LogFileName -LogEntryString "| Compliant password for user: $strUserSamAccountName" -LogEntryType INFO -TimeStamp } Write-Verbose "| Compliant password for user: $strUserSamAccountName" } $BUsers += $TUser } # Give status on found weak passwords If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Found $intBadPasswordsFound user(s) with weak password!" -LogEntryType DATA -TimeStamp } Write-Verbose "Found $intBadPasswordsFound user(s) with weak password!" # Give status on found $null passwords If ($bolWriteToLogFile) { LogWrite -Logfile $LogFileName -LogEntryString "Found $intNullPasswordsFound user(s) with null password!" -LogEntryType DATA -TimeStamp } Write-Verbose "Found $intNullPasswordsFound user(s) with null password!" # Let's exit gracefully #GetOut return $BUsers } function get-adreport { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [string]$Directory, [parameter(Mandatory = $false)] [string]$targetDC ) Begin { function add-paragraph ($wordDoc, $text) { Add-WordText -WordDocument $wordDoc -Text $text -Supress $true } function add-table ($wordDoc, $object, $trans = $true) { if ($trans -eq $true) { Add-WordTable -WordDocument $wordDoc -DataTable $object -Design MediumShading1Accent5 -Transpose -Supress $true -AutoFit Window } else { Add-WordTable -WordDocument $wordDoc -DataTable $object -Design MediumShading1Accent5 -Supress $true -AutoFit Window } } #load module pscommon core (this should be installed before execution) try { Test-Module -Module pscommoncore -Load $true } catch { Write-host "Failed to load pscommoncore module execution halted" Write-host "Returned error message is $($_.ErrorDetails.Message)" Exit } #set up logging into the output directory $log = Start-Log -Log "$($Directory)\adreportlog.txt" -Type TXT # Dotsource in the functions you need. } Process { #clear the screen cls #check the version of powershell if version 5 install Word module $psver = Test-Powershell if ($psver.version -like '5*' -and $psver.elevated -eq $true) { Write-Color -Text "Loading the Word Module from PowerShell Gallery" -Color Green $WDResult = Test-Module -Module PSWriteWord -Load $true -install $true if ($WDResult.status -eq "Loaded") { Write-Color -Text "Word Module Loaded" -Color Green $log | Write-Log -Line "Word Module loaded." -Level Info } else { Write-Color -Text "Word Module failed to load" -Color Green $log | Write-Log -Line "Word Module failed to load. Exiting." -Level Error $log | Close-Log exit } } else { Write-Color -Text "Powershell Version Incompatable or not running elevated - Stopping" -Color Red $log | Write-Log -Line "Powershell incorrect version exiting." -Level Error $log | Close-Log exit } #test to see if group policy powershell module is available $GPresults = Test-Module -Module grouppolicy -Load $true if ($GPresults.status -ne "Loaded") { Write-Color -Text "The Group Policy Module is not loaded" -Color Red Write-Color -Text "The message was $($GPresults.message)" -Color Yellow $log | Write-Log -Line "Group Policy Module failed to load. Exiting." -Level Error $log | Close-Log exit } else { Write-Color -Text "Group Policy Module Loaded proceeding...." -Color Green $log | Write-Log -Line "Group Policy Module failed Loaded." -Level Info } #test to see if ad powershell module is available $ADResults = Test-Module -Module activedirectory -Load $true if ($ADResults.status -ne "loaded") { Write-Color -Text "The Active Directory Module is not loaded" -Color Red Write-Color -Text "The message was $($ADresults.message)" -Color Yellow $log | Write-Log -Line "Active Directory Module failed to load. Exiting." -Level Error $log | Close-Log exit } else { Write-Color -Text "Active Directory Module Loaded proceeding...." -Color Green $log | Write-Log -Line "Active Directory Module Loaded." -Level Info } #if TargetDC is blank check to see if current computer is a DC if ($targetDC -eq "") { if (!(Get-ADDomainController -Server (get-computerinfo).csname)) { Write-Color -Text "This computer is not a domain controller exiting..." -Color Red $log | Write-Log -Line "Current computer not a Domain Controller and no domain controller was supplied as a parameter. Exiting" -Level Error $log | Close-Log exit } else { Write-Color -Text "Using Local Computer becasue it is a domain controller" -Color Green $log | Write-Log -Line "No domain controller was supplied as a parameter bu the current computer is a domnain controller using local machine as DC." -Level Info $targetDC = (Get-ComputerInfo).csname } } else { $online = Test-Connection -Count 1 -Quiet -ComputerName $targetDC if ($online) { Write-Color -Text "The Target DC $($targetDC) is responding to a ping. Continuing." $log | Write-Log -Line "Domain Controller supplied is responding to a ping. Continuing." -Level Info } else { Write-Color -Text "The Target DC $($targetDC) is not responding to a ping this script might error out." $log | Write-Log -Line "Supplied Domain Controller not responding to a ping could not be available script might error out. Continuing." -Level Warn } } #constants for reference #schema version table $schversion = @() $os = @("Windows 2000 Server", "Windows Server 2003", "Windows Server 2003 R2", "Windows Server 2008", "Windows Server 2008 R2", "Windows Server 2012", "Windows Server 2012 R2", "Windows Server 2016", "Windows Server 2019") $ver = @("13", "30", "31", "44", "47", "56", "69", "87", "88") for ($i = 0; $i -le ($os.Length); $i++) { $sch = New-Object System.Management.Automation.PSObject $sch | Add-Member -MemberType NoteProperty -Name OS -Value $null $sch | Add-Member -MemberType NoteProperty -Name Version -Value $null $sch.os = $os[$i] $sch.version = $ver[$i] $schversion += $sch } #prepare the Word Report Document $template = "$($Directory)\ADTemplate.docx" $wordfile = "$($Directory)\AD Assesment Deliverable.docx" $wordDoc = get-WordDocument -filepath $template $paragraph = Add-WordText -WordDocument $wordDoc -Text "QUEST AD ASSESSMENT OVERVIEW" -HeadingType Heading1 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "Quest was Tasked with performing and Active Directory assessment for your company. This deliverable contain both a report on the structure of your current AD, and an assesment of the current health of the critical components of AD. A security report on your current User/Group security posture is included. This final section test the effective response curve of you DC's and gathers basic performance data. This deliverable was produced with the results of our script-based investigation of your AD Environment. Quest used a custom script developed in house to capture your current configuration, health, and performance information." -Supress $true #$paragraph = Add-WordParagraph -WordDocument $wordDoc -Supress $True # Empty Line $paragraph = Add-WordText -WordDocument $wordDoc -Text "ASSESSMENT STRUCTURE" -HeadingType Heading2 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "This deliverable is broken into 4 main sections. Thes section will cover the reports and assesments goals of this deliverable. The outline of the sections is below." -Supress $true #$paragraph = Add-WordParagraph -WordDocument $wordDoc -Supress $True # Empty Line $ListOfItems = @('Overview of the current configuration of Active Directory', 'Forest', 'Sites', 'Domains', 'Domain Controllers','DNS Settings','Domain DNS Zones','Users', 'Password Policy','OUs','Groups', 'Computers', 'GPOs.', 'Active Directory Health Report','Replication Health','DC Health', 'Performace Report', 'DC Resorce Utilization', 'DC Performance','Security Reports', 'User Security Report') $OverrideLevels = @(0, 1, 1, 1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2) $paragraph = Add-WordList -WordDocument $wordDoc -ListType Numbered -ListData $ListOfItems -ListLevels $OverrideLevels -Supress $true #$paragraph = Add-WordParagraph -WordDocument $wordDoc -Supress $True # Empty Line #Begin Collection of information Write-Color -Text "Starting Collection of AD Data" -Color Green $log | Write-Log -Line "Starting collection of AD Data." -Level Info #Process forest info $paragraph = Add-WordText -WordDocument $wordDoc -Text "ACTIVE DIRECTORY CONFIGURATION" -HeadingType Heading1 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "FOREST" -HeadingType Heading2 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "BASE INFORMATION" -HeadingType Heading3 -Supress $true Write-Color -Text "Getting Forest Information" -Color Green $log | Write-Log -Line "Collecting Base Forest Data." -Level Info $forest = Get-ADForest -Server $targetDC | select * #base forest information add-paragraph -wordDoc $wordDoc -text "Quest was able to read the forest $($forest.name) and pull the configuration." add-paragraph -wordDoc $wordDoc -text "The base forest information is below:" $baseForestinfo = $forest | select Name, RootDomain, Domains, forestmode add-table -wordDoc $wordDoc -object $baseForestinfo #forest partition information add-paragraph -wordDoc $wordDoc -text "The forest partition information." $paragraph = Add-WordText -WordDocument $wordDoc -Text "PARTITIONS CONTAINER" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The Partitions Container in AD contains the forest wide configuration data in AD. This includes Forest DNS Zones, Enterprise configuration, Enterprise Schema, and the Domain partitions." $paragraph = Add-WordText -WordDocument $wordDoc -Text "APPLICATION PARTITIONS" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The application partitions contain dynamic data that is stored in AD and replicated to domain controllers. DNS is an example of this type of data. Programmers may use this system to store data in AD that can be retried via LDAP or ADSI." $forestpartinfo = $forest | select partitionscontainer,applicationpartitions add-table -wordDoc $wordDoc -object $forestpartinfo #forest masters and global catalogs $log | Write-Log -Line "Collecting Global Catalog and FSMO Forest Data." -Level Info add-paragraph -wordDoc $wordDoc -text "Forest Masters and Global Catalog Servers are systems that provide critical services to the Forest and it's Domains." $paragraph = Add-WordText -WordDocument $wordDoc -Text "GLOBAL CATALOG" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The global catalog is a feature of Active Directory domain controllers that allows for a domain controller to provide information on any object in the forest, regardless of whether the object is a member of the domain controller's domain." add-paragraph -wordDoc $wordDoc -text "Domain controllers with the global catalog feature enabled are referred to as global catalog servers and can perform several functions that are especially important in a multi-domain forest environment:" $ListOfItems = @('Authentication. During an interactive domain logon, a domain controller will process the authentication request and provide authorization information regarding all of the groups of which the user account is a member for inclusion in the generated user access token.', 'User Principal Name Resolution. Logon requests made using a user principal name (e.g., username@domain.com) require a search of the global catalog to identify the user account containing a matching userPrincipalName value. This search is used to identify the distinguished name of the associated user object so the authentication request can be forwarded to a domain controller in the user object''s domain.', 'Universal Group Membership. Logon requests made in multi-domain environments require the use of a global catalog that can check for the existence of any universal groups and determine if the user logging on is a member of any of those groups. Because the global catalog is the only source of universal groups membership information, access to a global catalog server is a requirement for authentication in a multidomain forest.', 'Object Search. The global catalog makes the directory structure within a forest transparent to users who perform a search. For example, any global catalog server in a forest is capable of identifying a user object given only the object''s samAccountName. Without a global catalog server, identifying a user object given only its samAccountName could require separate searches of every domain in the forest.') $OverrideLevels = @(0, 1, 1, 0) $paragraph = Add-WordList -WordDocument $wordDoc -ListType Bulleted -ListData $ListOfItems -ListLevels $OverrideLevels -Supress $true add-paragraph -wordDoc $wordDoc -text "Active Directory has five FSMO (generally pronounced 'FIZZ-mo') roles, two of which are enterprise-level (i.e., one per forest) and three of which are domain-level (i.e., one per domain). The enterprise-level FSMO roles are called the Schema Master and the Domain Naming Master." $paragraph = Add-WordText -WordDocument $wordDoc -Text "SCHEMA MASTER" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The Schema Master is an enterprise-level FSMO role; there is only one Schema Master in an Active Directory forest." add-paragraph -wordDoc $wordDoc -text "The Schema Master role owner is the only domain controller in an Active Directory forest that contains a writable schema partition. As a result, the domain controller that owns the Schema Master FSMO role must be available to modify its forest's schema. This includes activities such as raising the functional level of the forest and upgrading the operating system of a domain controller to a higher version than currently exists in the forest, either of which will introduce updates to Active Directory schema." add-paragraph -wordDoc $wordDoc -text "The Schema Master role has little overhead and its loss can be expected to result in little to no immediate operational impact; unless schema changes are necessary, it can remain offline indefinitely without noticeable effect. The Schema Master role should only be seized when the domain controller that owns the role cannot be brought back online. Bringing the Schema Master role owner back online after the role has been seized from it may introduce serious data inconsistency and integrity issues into the forest." $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN NAMING MASTER" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The Domain Naming Master is an enterprise-level role; there is only one Domain Naming Master in an Active Directory forest." add-paragraph -wordDoc $wordDoc -text "The Domain Naming Master role owner is the only domain controller in an Active Directory forest that is capable of adding new domains and application partitions to the forest. Its availability is also necessary to remove existing domains and application partitions from the forest." add-paragraph -wordDoc $wordDoc -text "The Domain Naming Master role has little overhead and its loss can be expected to result in little to no operational impact, as the addition and removal of domains and partitions are performed infrequently and are rarely time-critical operations. Consequently, the Domain Naming Master role should only need to be seized when the domain controller that owns the role cannot be brought back online." $forestmginfo = $forest | select globalcatalogs,DomainNamingmaster,Schemamaster add-table -wordDoc $wordDoc -object $forestmginfo #Suffixes for SPN and UPN $log | Write-Log -Line "Collecting SPN/UPN Forest Data." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "UPN AND SPN SUFFIXES" -HeadingType Heading4 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "The User Principal Name (UPN) suffix is part of the logon name in AD. When you create a new account, by default it will use the DNS name of your AD domain. For example, your local domain name is contoso.corp and you want to create a new user. The user that you want to create is John Doe. The standard in the organization for creating new users is first.lastname. The logon name will be john.doe@contoso.corp." add-paragraph -wordDoc $wordDoc -text "Adding addition UPN Suffixes in AD allows the users to use the same address for both email and login. This also allows a forest with multiple email domain to have multiple login UPN's." add-paragraph -wordDoc $wordDoc -text "The Service Principal Name (SPN) suffix is part of the name in AD that a client uses to uniquely identify a service. The Kerberos authentication service can use an SPN to authenticate a service. When a client wants to connect to a service, it locates an instance of the service, composes an SPN for that instance, connects to the service, and presents the SPN for the service to authenticate." $forestsuffixinfo = $forest | select UPNSuffixes, SPNSuffixes add-table -wordDoc $wordDoc -object $forestsuffixinfo Write-Color -Text "Collectimng Schema Information" -Color Green $log | Write-Log -Line "Collecting Schema Data." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "SCHEMA" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "The Microsoft Active Directory schema contains formal definitions of every object class that can be created in an Active Directory forest. The schema also contains formal definitions of every attribute that can exist in an Active Directory object." add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Schema for Forest $($forest.name) and pull the Information contained in the table below." #process schema info $schpath = (Get-ADRootDSE -Server $targetDC).schemanamingcontext $schema = Get-ADObject $schpath -Properties * $schout = $schema | select name, objectversion, modified $LObj = $schversion | ?{ $_.version -eq $schout.objectversion } $Label = $LObj.os $schout | Add-Member -MemberType NoteProperty -Name SchemaVersion -Value $Label -force add-table -wordDoc $wordDoc -object $schout #add warning if Schema is not at least 2012 if ($schout -lt "56") { add-paragraph -wordDoc $wordDoc -text "Your Current AD Schema (and Forest function level is below the minimum supported level of Windows Server 2012 you should update the forest functional level." } #Optional Features Write-Color -Text "Collectimng Forest Optional Features" -Color Green $log | Write-Log -Line "Collecting Forest Optional Features Data." -Level Info #process optional Features $optfull = Get-ADOptionalFeature -filter * -Properties * -Server $targetDC $optout = $optfull | select Name, Created, featureGUID, featurescope, enabledscopes, modified, protectedfromaccidentaldeletion, required* $otpfeatures = $optfull | select name, created, modified $optfword = @() foreach ($opt in $optfull) { $obj = New-Object System.Management.Automation.PSObject $obj | Add-Member -MemberType NoteProperty -Name Feature -Value $opt.name $obj | Add-Member -MemberType NoteProperty -Name Created -Value $opt.created $obj | Add-Member -MemberType NoteProperty -Name Modified -Value $opt.Modified $optfword += $obj } $paragraph = Add-WordText -WordDocument $wordDoc -Text "OPTIONAL FEATURES" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "AD Optional Features are functionality that must be enabled in AD to be used. They can provide additional services. For Example - The Active Directory Recycle Bin Feature." add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Optional Features for Forest $($forest.name) and pull the Information contained in the table below." $paragraph = Add-WordTable -WordDocument $wordDoc -DataTable $optfword -Design MediumShading1Accent5 -Supress $true -AutoFit Window foreach ($opt in $optout) { $paragraph = Add-WordParagraph -WordDocument $wordDoc -Supress $true add-paragraph -wordDoc $wordDoc -text "Details of the Optional Feature $($opt.name)." add-table -wordDoc $wordDoc -object $opt } #directory Services info (Tombstonelifetime) Write-Color -Text "Collecting Directory Services Info" -Color Green $ADForestconfigurationNamingContext = (Get-ADRootDSE -Server $targetDC).configurationNamingContext $DirectoryServicesConfigPartition = Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$ADForestconfigurationNamingContext" -Partition $ADForestconfigurationNamingContext -Properties * $DSWordout = $DirectoryServicesConfigPartition | select name, created, modified, tombstoneLifetime $paragraph = Add-WordText -WordDocument $wordDoc -Text "DIRECTORY SERVICES" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "Directory Services configuration information refers to the AD DS services objects that govern management of the directory service partition. The most important object in this container is the TombstoneLifeTime object." add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Directory Services for Forest $($forest.name) and pull the Information contained in the table below." add-table -wordDoc $wordDoc -object $DSWordout #Trust Information Write-Color -Text "Collectimng Forest Trust Info" -Color Green $log | Write-Log -Line "Collecting Trust Data." -Level Info $trusts = Get-ADTrust -filter * -Properties * -Server $targetDC | ?{ $_.intraforest -eq $false } $paragraph = Add-WordText -WordDocument $wordDoc -Text "FOREST TRUSTS" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "Forest Trusts are logic connections between two forests to provide authentication and shared services." if (($trusts | measure).count -eq 0) { add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Trusts for Forest $($forest.name) The forest contained no Trusts." } else { add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Trusts for Forest $($forest.name) and pull the Information contained in the tables below." foreach ($trust in $trusts) { $obj = $trust | select "Name", "Direction", "Source", "Target", "ForestTransitive", "SelectiveAuthentication", "SIDFilteringForestAware", "SIDFilteringQuarantined" add-paragraph -wordDoc $wordDoc -text "The trust between $($obj.source) and $($obj.target) has the following parameters:" add-table -wordDoc $wordDoc -object $obj } } #sites in AD Write-Color -Text "Collecting Site Information" -Color Green $log | Write-Log -Line "Collecting Site Data." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "ACTIVE DIRECTORY SITES" -HeadingType Heading2 -Supress $true -h $paragraph = Add-WordText -WordDocument $wordDoc -Text "SITE INFORMATION" -HeadingType Heading3 -Supress $true -h add-paragraph -wordDoc $wordDoc -text "Sites are physical groupings of well-connected IP subnets that are used to efficiently replicate information among Domain Controllers (DCs). It can be thought of as a mapping that describes the best routes for carrying out replication in AD, thus making efficient use of the network bandwidth. Sites help to achieve cost-efficiency and speed. It also lets one exercise better control over the replication traffic and the authentication process. When there is more than one DC in the associated site that is capable of handling client logon, services, and directory searches, sites can locate the closest DC to perform these actions. Sites also play a role in the deployment and targeting of Group Policies." #$forestsiteinfo = $forest | select sites #add-table -wordDoc $wordDoc -object $forestsiteinfo $foresttmp = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest", $forest.name) [array]$ADSites = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($foresttmp).sites $SoutWord = $ADSites | select name, domains add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Site Information for Forest $($forest.name) and compile the Information in the section below." add-table -wordDoc $wordDoc -object $SoutWord -trans $false ForEach ($Site in $ADSites) { Write-Color -Text "Processing Site $($Site.name)" -Color Green add-paragraph -wordDoc $wordDoc -text "The site $($Site.name) contains the following details." add-table -wordDoc $wordDoc -object $Site } #site link info Write-Color -Text "Collecting Site Link Information" -Color Green $log | Write-Log -Line "Collecting Site Link Data." -Level Info $sitelink = Get-ADReplicationSiteLink -filter * -Properties * -Server $targetDC $sitednfilter = $sitelink.sitesincluded[0].replace("$($sitelink.sitesincluded[0].split(",")[0]),", "") $sitednfilter = "," + $sitednfilter $SLoutWord = $sitelink | select name, @{ n = "sitesincluded"; e ={$_.sitesincluded.replace($sitednfilter,"") } } $paragraph = Add-WordText -WordDocument $wordDoc -Text "SITE LINKS" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "Site Links are lower bandwitdh, potentially unreliable connections between Active Directory Sites. A site link defins the parameters for communication between AD sites. This includes replication frequency, times, and cost." add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Site Link Information for Forest $($forest.name) and compile the Information in the section below." add-table -wordDoc $wordDoc -object $SLoutWord -trans $false ForEach ($link in $sitelink) { Write-Color -Text "Processing Site Link $($link.name)" -Color Green add-paragraph -wordDoc $wordDoc -text "The site link $($link.name) contains the following details." add-table -wordDoc $wordDoc -object $link } Write-Color -Text "Collecting Site Link Bridge Information" -Color Green $log | Write-Log -Line "Collecting SiteLink Bridge Data." -Level Info $sitelinkbridge = Get-ADReplicationSiteLinkBridge -filter * -Properties * -Server $targetDC $paragraph = Add-WordText -WordDocument $wordDoc -Text "SITE LINK BRIDGES" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "A site link bridge connects two or more site links and enables transitivity between site links. Each site link in a bridge must have a site in common with another site link in the bridge. The Knowledge Consistency Checker (KCC) uses the information on each site link to compute the cost of replication between sites in one site link and sites in the other site links of the bridge." if (($sitelinkbridge | measure).count -ne 0) { $SLBoutWord = $sitelinkbridge | select name, sitelinksincluded add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Site Link Bridge Information for Forest $($forest.name) and compile the Information in the section below." add-table -wordDoc $wordDoc -object $SLBoutWord -trans $false ForEach ($bridge in $sitelinkbridge) { Write-Color -Text "Processing Site Link Bridge $($bridge.name)" -Color Green add-paragraph -wordDoc $wordDoc -text "The site link bridge $($bridge.name) contains the following details." add-table -wordDoc $wordDoc -object $bridge } } else { add-paragraph -wordDoc $wordDoc -text "Quest was able to read the Site Link Bridge Information for Forest $($forest.name). There are no site link bridges in the forest." } Write-Color -Text "Forest Information Complete" -Color Green $log | Write-Log -Line "Collecting Forest Data Complete." -Level Info Write-Color -Text "Starting collection on Domains" -Color Green $log | Write-Log -Line "Collecting Domain Data." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAINS" -HeadingType Heading2 -Supress $true $adm = @() Write-Color -Text "Building list of all domains in forest." -Color Green $log | Write-Log -Line "Building list fo all forest domains." -Level Info foreach ($dom in $forest.domains) { $tempdom = Get-ADDomain -Identity $dom -Server (Get-ADDomainController -Discover -DomainName $dom) $rids = Get-RIDsremainingAdPsh -domainDN $tempdom.DistinguishedName $obj = New-Object System.Management.Automation.PSObject #$obj | Add-Member -MemberType NoteProperty -Name Name -Value $tempdom.name $obj | Add-Member -MemberType NoteProperty -Name NetbiosName -Value $tempdom.netbiosname $obj | Add-Member -MemberType NoteProperty -Name DNSRoot -Value $tempdom.DNSRoot $obj | Add-Member -MemberType NoteProperty -Name DomainMode -Value $tempdom.DomainMode $obj | Add-Member -MemberType NoteProperty -Name ForestRoot -Value $(if ($dom -eq $forest.rootdomain) { $true } else { $false }) $obj | Add-Member -MemberType NoteProperty -Name RIDsIssued -Value $rids.ridsissued $obj | Add-Member -MemberType NoteProperty -Name RIDsRemaining -Value $rids.ridsremaining $adm += $obj } add-paragraph -wordDoc $wordDoc -text "A domain is the logical container that sits directly below the forest container." add-paragraph -wordDoc $wordDoc -text "An Active Directory domain is a collection of objects within a Microsoft Active Directory network. An object can be a single user or a group or it can be a hardware component, such as a computer or printer. Each domain holds a database containing object identity information." add-paragraph -wordDoc $wordDoc -text "Quest Discovered the following list of domains in the forest $($forest.name) and will detail each in a section section." add-paragraph -wordDoc $wordDoc -text "Each domain will be queried for Domain Controllers, DNS, Users, Password Policy, OUs, Groups, Computers, and GPOs." add-table -wordDoc $wordDoc -object $adm -trans $false #Process each domain foreach ($domain in $forest.domains) { #get domain information Write-Color -Text "Processing Domain $($domain)" -Color Green Write-Color -Text "Processing Domain Information" -Color Green $log | Write-Log -Line "Processing Domain $($dom)." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN: $($domain)" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "Quest was able to pull information from the domain $($domain) and compile it into the following tables below." $temp = Get-ADDomain -Identity $domain -Server $targetDC #basic domain information $BDI = $temp | select Name,NetbiosName,Forest,DomainMode,DNSRoot,ParentDomain,ChildDomains $paragraph = Add-WordText -WordDocument $wordDoc -Text "BASIC INFORMATION" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "The Basic information for the domain:" add-table -wordDoc $wordDoc -object $BDI #Domain contrainers $DCon = $temp | select computerscontainer, userscontainer, domaincontrollerscontainer, lostandfoundcontainer, quotascontainer, systemcontainer $paragraph = Add-WordText -WordDocument $wordDoc -Text "CONTAINER INFORMATION" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "The Domain is using the following default containers:" add-table -wordDoc $wordDoc -object $DCon #Domain Masters $DM = $temp | select Infrastructuremaster, PDCEmulator, RIDMaster $paragraph = Add-WordText -WordDocument $wordDoc -Text "FSMO MASTER INFORMATION" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "The domain-level FSMO roles are called the Primary Domain Controller Emulator, the Relative Identifier Master, and the Infrastructure Master." add-paragraph -wordDoc $wordDoc -text "The RID Master role owner is responsible for allocating active and standby Relative Identifier (RID) pools to domain controllers in its domain. RID pools consist of a unique, contiguous range of RIDs. These RIDs are used during object creation to generate the new object's unique Security Identifier (SID). The RID Master is also responsible for moving objects from one domain to another within a forest." add-paragraph -wordDoc $wordDoc -text "The Infrastructure Master role owner is the domain controller in each domain that is responsible for managing phantom objects. Phantom objects are used to track and manage persistent references to deleted objects and link-valued attributes that refer to objects in another domain within the forest (e.g., a local-domain security group with a member user from another domain)." add-paragraph -wordDoc $wordDoc -text "The PDCE role owner is responsible for several crucial operations:" $ListOfItems = @('Backward Compatibility. The PDCE mimics the single-master behavior of a Windows NT primary domain controller. To address backward compatibility concerns, the PDCE registers as the target domain controller for legacy applications that perform writable operations and certain administrative tools that are unaware of the multi-master behavior of Active Directory domain controllers.', 'Time Synchronization. Each PDCE serves as the master time source within its domain. The PDCE in forest root domain serves as the preferred Network Time Protocol (NTP) server in the forest. The PDCE in every other domain within the forest synchronizes its clock to the forest root PDCE, non-PDCE domain controllers synchronize their clocks to their domain''s PDCE, and domain-joined hosts synchronize their clocks to their preferred domain controller. NOTE: The Kerberos authentication protocol includes timestamp information and is an example of the importance of time synchronization within an Active Directory forest. Kerberos authentication will fail if the difference between a requesting host''s clock and the clock of the authenticating domain controller exceeds 5 minutes (this tolerance is configurable, but Microsoft''s best practice recommendation is to maintain the default 5-minute value on the Maximum tolerance for computer clock synchronization setting). This behavior is intended to counter certain malicious activities, such as ''replay attacks''.', 'Password Update Processing. When computer and user passwords are changed or reset by a non-PDCE domain controller, the committed update is immediately replicated to the domain''s PDCE. If an account attempts to authenticate against a domain controller that has not yet received a recent password change through scheduled replication, the request is passed through to the domain PDCE. The PDCE will attempt to process the authentication request and instruct the requesting domain controller to either accept or reject the authentication request. This behavior ensures that passwords can reliably be processed even if recent changes have not fully-propagated through scheduled replication. The PDCE is also responsible for processing account lockouts, as all failed password authentications are passed through to the PDCE.', 'Group Policy Updates. All Group Policy Object (GPO) updates are committed to the domain PDCE. This prevents the potential for versioning conflicts that could occur if a GPO was modified on two domain controllers at approximately the same time.', 'Distributed File System. By default, Distributed File System (DFS) root servers will periodically request updated DFS namespace information from the PDCE. While this behavior can lead to resource bottle-necking, enabling the Dfsutil.exe Root Scalability parameter will allow DFS root servers to request updates from the closest domain controller.') $OverrideLevels = @(0, 0, 0, 0, 0) $paragraph = Add-WordList -WordDocument $wordDoc -ListType Bulleted -ListData $ListOfItems -ListLevels $OverrideLevels -Supress $true add-table -wordDoc $wordDoc -object $DM #domain mode $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN MODE" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "The Domain is operating in the following domain mode: $($temp.domainmode)" Write-Color -Text "Processing Domain Controllers" -Color Green $log | Write-Log -Line "Processing Domain Controllers." -Level Info #get target DC in Domain $TDC = Get-ADDomainController -Discover -DomainName $domain $dcs = Get-ADDomainController -Filter * -server $TDC $tempdc = $dcs | select name, domain, enabled, isglobalcatalog, isreadonly, site $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN CONTROLLER LIST" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "The following is a list of domain controllers in the domain $($domain). Each Domain Controller is broken out below into its own section." add-table -wordDoc $wordDoc -object $tempdc -trans $false foreach ($dc in $dcs) { Write-Color -Text "Processing Domain Controller $($dc.name)" -Color Green $log | Write-Log -Line "Processing Domain Controller $($dc.name)." -Level Info $log | Write-Log -Line "$($dc.name) General Info Gathering." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN CONTROLLER: $($dc.name)" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "Domain Controller $($dc.name) details." $DCDetails = $dc | select name, hostname, enabled, domain, ipv4address, ipv6address, isglobalcatalog, isreadonly, operatingsystem, operatingsystemservicepack, operatingsystemversion, operationmasterroles, site, ldapport, sslport add-table -wordDoc $wordDoc -object $DCDetails Write-Color -Text "Getting AD Database Information" -Color Green $log | Write-Log -Line "$($dc.name) AD DAtabase Info Gathering." -Level Info $addb = OutputADFileLocations $dc.HostName add-paragraph -wordDoc $wordDoc -text "Domain Controller $($dc.name) database details." add-table -wordDoc $wordDoc -object $addb Write-Color -Text "Getting Time Sync Information" -Color Green $log | Write-Log -Line "$($dc.name) Time Sync Info Gathering." -Level Info $ts = OutputTimeServerRegistryKeys $dc.HostName $TSDetails = $ts | select TimeSource, ntpserver, ntptype, specialpollinterval, maxnegphasecorrection, maxposphasecorrection, vmictimeprovider add-paragraph -wordDoc $wordDoc -text "Domain Controller $($dc.name) time sync details." add-table -wordDoc $wordDoc -object $TSDetails Write-Color -Text "Getting DNS Server Information" -Color Green $log | Write-Log -Line "$($dc.name) Server DNS Details Gathering." -Level Info $dns = Get-DnsServerSetting -All -ComputerName $dc.name -ErrorAction SilentlyContinue add-paragraph -wordDoc $wordDoc -text "Domain Controller $($dc.name) DNS Server details." if (($dns | measure).count -ne 0) { add-table -wordDoc $wordDoc -object $dns } else { add-paragraph -wordDoc $wordDoc -text "This Domain Controller is not a DNS Server." } } Write-Color -Text "Getting AD DNS Zones" -Color Green $log | Write-Log -Line "Domain Level DNS Zone Gathering." -Level Info $DNSZones = Get-DNSServerZone -ComputerName $targetDC | select * $DNSZonessum = $DNSZones | select ZoneName, ZoneType $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN DNS ZONES" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "Quest dicovered the following DNS Zones for the domain $($domain). Details are included in the tables below." add-table -wordDoc $wordDoc -object $DNSZonessum -trans $false foreach ($zone in $DNSZones) { $DNSZOut = $zone | select ZoneName, ZoneType, replicationscope, IsDSIntegrated, IsReverseLookupZone, IsAutoCreated add-paragraph -wordDoc $wordDoc -text "DNS Zone $($zone.zonename) Details:" add-table -wordDoc $wordDoc -object $DNSZOut } $DNSDSSettings = Get-DnsServerDsSetting | select PollingInterval,remotereplicationdelay,lazyupdateinterval,tombstoneinterval add-paragraph -wordDoc $wordDoc -Text "DNS DS Settings for the domain $($domain) are included in the table below." add-table -wordDoc $wordDoc -object $DNSDSSettings Write-Color -Text "Processinmg AD Object Information for Domain $($domain)" -Color Green $log | Write-Log -Line "Gathering AD Object Data for domain $($domain)." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "ACTIVE DIRECTORY OBJECTS" -HeadingType Heading3 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "This section details out the information on AD object in the domain $($domain). The objects contained in the section include Users, Password Policy, OUs, Groups, and Group Policy Objects." -Supress $true #users Write-Color -Text "Processing Users" -Color Green $log | Write-Log -Line "Gathering User Data for domain $($domain)." -Level Info $users = Get-ADUser -Filter * -Properties * -Server $targetDC $TUsers = @() foreach ($u in $users) { $newLLD = Get-ADUsersLastLogon -usersid $u.SID -TargetDC $targetDC $u | Add-Member -MemberType NoteProperty -Name CorrectedLastLogon -Value $newLLD $TUsers += $u } $obj = New-Object System.Management.Automation.PSObject $obj | Add-Member -MemberType NoteProperty -Name EnabledUsers -Value (($users | ?{ $_.enabled -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name DisabledUsers -Value (($users | ?{ $_.enabled -eq $false }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name LockedUsers -Value (($users | ?{ $_.lockedout -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name ExpiredUsers -Value (($users | ?{ $_.accountexpirationdate -ne $null -and $_.accountexpirationdate -lt [datetime]::Now }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name ExpiringUsers -Value (($users | ?{ $_.accountexpirationdate -ne $null -and $_.accountexpirationdate -gt [datetime]::Now }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name PWDNeverExpiresUsers -Value (($users | ?{ $_.passwordneverexpires -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name CannotChangePWDUsers -Value (($users | ?{ $_.cannotchangepassword -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name PWDExpiredUsers -Value (($users | ?{ $_.passwordexpired -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name NoPreAuthUsers -Value (($users | ?{ $_.doesnotrequirepreauth -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name ReversableEncryptionUsers -Value (($users | ?{ $_.AllowReversiblePasswordEncryption -eq $true }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name StaleUsers -Value (($tusers | ?{ $_.correctedlastlogon -lt [datetime]::Now.AddDays(-90) }) | measure).count $obj | Add-Member -MemberType NoteProperty -Name TotalUsers -Value ($users | measure).count $paragraph = Add-WordText -WordDocument $wordDoc -Text "USERS" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "User Statistics in the domain $($domain)." add-table -wordDoc $wordDoc -object $obj Write-Color -Text "Getting AD Domain Password Policy" -Color Green $log | Write-Log -Line "Getting AD Domain Password Policy for domain $($domain)." -Level Info $ADPassPol = Get-ADDefaultDomainPasswordPolicy -Server $targetDC $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN PASSWORD POLICY" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "Quest dicovered the following default password policy for the domain $($domain)and below is a table of the details." add-table -wordDoc $wordDoc -object $ADPassPol Write-Color -Text "Getting AD Domain Fine Grained Password Policy" -Color Green $ADFGPassPol = Get-ADFineGrainedPasswordPolicy -Filter * -Properties * -Server $targetDC $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN FINE GRAINED PASSWORD POLICY" -HeadingType Heading4 -Supress $true if (($ADFGPassPol | measure).count -ne 0) { add-paragraph -wordDoc $wordDoc -text "Quest dicovered the following Fine Grained password policy for the domain $($domain) and below is a Summary table With detail tables of each policy to follow." $tempfgp = $ADFGPassPol | select Name, Description, Precedence, Appliesto add-table -wordDoc $wordDoc -object $tempfgp -trans $false foreach ($FGP in $ADFGPassPol) { $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN FGP Policy $($FGP.name)" -HeadingType Heading5 -Supress $true add-paragraph -wordDoc $wordDoc -text "The deatils of the FGP Policy $($FGP.name) are contained in the table below." add-table -wordDoc $wordDoc -object $FGP } } else { add-paragraph -wordDoc $wordDoc -Text "The domain $($domain) contains no fine grained password policies." } $paragraph = Add-WordText -WordDocument $wordDoc -Text "ORGANIZATIONAL UNITS (OU)" -HeadingType Heading4 -Supress $true Write-Color -Text "Processing Organizational Units" -Color Green $log | Write-Log -Line "Getting OUs for domain $($domain)." -Level Info $ous = Get-ADOrganizationalUnit -Filter * -Properties * -Server $targetDC $oustotal = @() foreach ($ou in $ous) { $counts = CountOUObjects -OU $ou -Server $targetDC $ou | Add-Member -MemberType NoteProperty -Name UserCount -Value $counts.UserCount -Force $ou | Add-Member -MemberType NoteProperty -Name ComputerCount -Value $counts.ComputerCount -Force $ou | Add-Member -MemberType NoteProperty -Name GroupCount -Value $counts.GroupCount -Force $oustotal += $ou } add-paragraph -wordDoc $wordDoc -text "Summary of OUs in the domain $($domain)." $OUSum = $oustotal | select Name, Usercount, computercount, groupcount add-table -wordDoc $wordDoc -object $OUSum -trans $false foreach ($ou in $oustotal) { $ouout = $ou | select Name, Created, Protectedfromaccidentaldeletion, linkedgrouppolicyobjects, usercount, computercount, groupcount add-paragraph -wordDoc $wordDoc -text "Details for the OU $($ou.name):" add-table -wordDoc $wordDoc -object $ouout } #groups $paragraph = Add-WordText -WordDocument $wordDoc -Text "GROUPS" -HeadingType Heading4 -Supress $true $privGroups = @("Account Operators", "Backup Operators", "Print Operators", "Server Operators", "Cert Publishers", "Enterprise Admins", "Domain Admins", "Administrators", "Schema Admins") Write-Color -Text "Proccessing Groups" -Color Green $log | Write-Log -Line "Getting Groups for domain $($domain)." -Level Info $groups = Get-ADGroup -Filter * -Properties * -Server $targetDC $totalGP = ($groups | measure).count $BI = 0 $US = 0 $UD = 0 $GS = 0 $GD = 0 $LS = 0 $LD = 0 foreach ($grp in $groups) { if ($grp.isCriticalSystemObject -eq $true) { $BI += 1 } else { if ($grp.groupcategory -eq 'Security' -and $grp.groupscope -eq 'Global') { $GS += 1 } if ($grp.groupcategory -eq 'Distribution' -and $grp.groupscope -eq 'Global') { $GD += 1 } if ($grp.groupcategory -eq 'Security' -and $grp.groupscope -eq 'DomainLocal') { $LS += 1 } if ($grp.groupcategory -eq 'Distribution' -and $grp.groupscope -eq 'DomainLocal') { $LD += 1 } if ($grp.groupcategory -eq 'Security' -and $grp.groupscope -eq 'Universal') { $US += 1 } if ($grp.groupcategory -eq 'Distribution' -and $grp.groupscope -eq 'Universal') { $UD += 1 } } } add-paragraph -wordDoc $wordDoc -Text "Group Statistics in the domain $($domain)." $gout = @{ } $gout.Add("Total Groups", $totalgp) $gout.Add("Built-In", $BI) $gout.Add("Universal Security", $US) $gout.Add("Universal Distribution", $UD) $gout.Add("Global Security", $GS) $gout.Add("Global Distribution", $GD) $gout.Add("Domain Local Security", $LS) $gout.Add("Domain Local Distribution", $LD) add-table -wordDoc $wordDoc -object $gout -trans $false $privgrps = @() foreach ($grp in $groups) { #Write-Color -Text "Processing $($grp.name)" -Color Green foreach ($pgrp in $privGroups) { #Write-Color -Text "testing private group $($pgrp)" -Color Green if ($grp.name -eq $pgrp) { #Write-Color -Text "Found Match..." -Color Green $grp | Add-Member -MemberType NoteProperty -Name MemberCount -Value (count-groupusers -group $grp.distinguishedname -server $targetDC) -Force $privgrps += $grp } } } add-paragraph -wordDoc $wordDoc -Text "Privileged Group Statistics in the domain $($domain)." $pgout = $privgrps | select Name, MemberCount add-table -wordDoc $wordDoc -object $pgout -trans $false $paragraph = Add-WordText -WordDocument $wordDoc -Text "PRIVILEGED GROUPS" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "Each privileged group will be detailed in the following sections. Privileged groups contain users that have elevated permissions in the domain and therefore are suseptable to intrusion attempts." foreach ($pgroup in $privgrps) { if ($pgroup.MemberCount -ne 0) { $members = @() $paragraph = Add-WordText -WordDocument $wordDoc -Text "$($pgroup.name)" -HeadingType Heading5 -Supress $true add-paragraph -wordDoc $wordDoc -text "The following users are members of this privileged group." foreach ($member in $pgroup.members) { try { $user = Get-ADUser -Identity $member -Server $targetDC -Properties * -ErrorAction SilentlyContinue if (($user | measure).count -ne 0) { if ($user.passwordlastset -eq $null) { $LS = $user.created } else { $LS = $user.passwordlastset } $ed = [datetime]::Now $PWAge = NEW-TIMESPAN -Start $LS -End $ed $obj = New-Object System.Management.Automation.PSObject $obj | Add-Member -MemberType NoteProperty -Name LogonID -Value $user.samaccountname $obj | Add-Member -MemberType NoteProperty -Name Name -Value $user.DisplayName $obj | Add-Member -MemberType NoteProperty -Name Age -Value $PWAge.days $obj | Add-Member -MemberType NoteProperty -Name LastLoggedIn -Value (Get-ADUsersLastLogon -usersid $user.SID -TargetDC $targetDC) $obj | Add-Member -MemberType NoteProperty -Name NoExpire -Value $user.passwordneverexpires $obj | Add-Member -MemberType NoteProperty -Name Reversible -Value $user.AllowReversiblePasswordEncryption $obj | Add-Member -MemberType NoteProperty -Name NotReq -Value $user.passwordnotrequired $members += $obj } } catch { } } add-table -wordDoc $wordDoc -object $members } } #computers Write-Color -Text "Processing Computers" -Color Green $log | Write-Log -Line "Gathering Computer Data for domain $($domain)." -Level Info $computers = Get-ADComputer -Filter * -Properties * $paragraph = Add-WordText -WordDocument $wordDoc -Text "COMPUTERS" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "This section details the Computer Statistics for the domain $($domain)." $totalcomps = ($computers | measure).count $servers = $computers | ?{ $_.operatingsystem -like '*server*' } $servercount = ($servers | measure).count $WS = $computers | ?{ $_.operatingsystem -notlike '*server*' } $WScount = ($ws | measure).count $obj = [hashtable]::new() $obj['TotalComputers'] = $totalcomps $obj['TotalServers'] = $servercount $obj['TotalWorkStations'] = $WScount add-table -wordDoc $wordDoc -object $obj -trans $false add-paragraph -wordDoc $wordDoc -text "The Following section details operating system versions and counts." $ossum = $computers | Group-Object operatingsystem | select Name, Count | sort Name add-table -wordDoc $wordDoc -object $ossum -trans $false #gpo $paragraph = Add-WordText -WordDocument $wordDoc -Text "GROUP POLICY OBJECTS (GPO)" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -text "This section details out the GPOs in the domain $($domain)." add-paragraph -wordDoc $wordDoc -text "A Group Policy Object is a component of Group Policy that can be used as a resource in Microsoft systems to control user accounts and user activity. The Group Policy Object is implemented in an Active Directory system according to various Group Policy settings including local settings, site-wide settings, domain-level settings and settings applied to organizational units." Write-Color -Text "Processing Group Policy" -Color Green $log | Write-Log -Line "Getting GPOs for domain $($domain)." -Level Info $GPO = get-gpo -domain $domain -all $gpoout = $GPO | select displayname, gpostatus, creationtime add-paragraph -wordDoc $wordDoc -text "The following is a summary of the GPO objects in theis domain." add-table -wordDoc $wordDoc -object $gpoout -trans $false foreach ($gp in $GPO) { $GPODetails = $gp | select displayname,discription,owner,creationtime,modificationtime,gpostatus,wmifilter $paragraph = Add-WordText -WordDocument $wordDoc -Text "GPO - $($gp.displayname)" -HeadingType Heading5 -Supress $true add-paragraph -wordDoc $wordDoc -Text "Details of the GPO $($gp.displayname)." add-table -wordDoc $wordDoc -object $GPODetails } Write-Color -Text "Creating GPO Report (HTML)" -Color Green $log | Write-Log -Line "Creating GPO Report for domain $($domain)." -Level Info $GPOReport = Get-GPOReport -Server $targetDC -All -ReportType html -Path "$($Directory)\GPOReport.html" $log | Write-Log -Line "Finished Processing domain $($domain)." -Level Info } Write-Color -Text "Finished processing domains" -Color Green $log | Write-Log -Line "Finished Processing all domains." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN HEALTH REPORT" -HeadingType Heading2 -Supress $true Write-Color -Text "Processing Health and Performance" -Color Green foreach ($domain in $forest.domains) { Write-Color -Text "Processing Health for domain $($domain)" -Color Green $log | Write-Log -Line "Creating Domain Health Report for domain $($domain)." -Level Info $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN REPLICATION HEALTH $($domain)" -HeadingType Heading3 -Supress $true $paragraph = Add-WordText -WordDocument $wordDoc -Text "REPLICATION STATISTICS AND HEALTH" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -Text "Quest performed replication tests against the Domain $($domain). These test included validating replication health and verifing Domain Controller Services are running. A summary report of the findings follows." #process DCs in the Domain Write-Color -Text "Getting DC Info" -Color Green $log | Write-Log -Line "Processing DCs for domain $($domain)." -Level Info $DMTargetDC = Get-ADDomainController -Discover -DomainName $domain $dcs = Get-ADDomainController -Filter * -Server $DMTargetDC | select name $DCServices = @() $DCtime = @() foreach ($dc in $dcs) { $dcalive = Test-Connection -Quiet -Count 1 -ComputerName $dc.name if ($dcalive -eq $true) { Write-Color -Text "Processing DC Service $($dc.name)" -Color Green $Services = Get-Service -name ntds, adws, dns, dnscache, kdc, w32time, netlogon, dhcp -ComputerName $dc.name $Services | Add-Member -MemberType NoteProperty -Name Server -Value $dc.name -Force if (($Services | ?{ $_.Status -ne 'Running' } | measure).count -ne 0) { $status = "Failed" $NRS = ($Services | ?{ $_.Status -ne 'Running' }).name } else { $status = "Pass" $NRS = "None" } $Sout = New-Object System.Management.Automation.PSObject $Sout | Add-Member -MemberType NoteProperty -Name Server -Value $dc.name $Sout | Add-Member -MemberType NoteProperty -Name Status -Value $Status $Sout | Add-Member -MemberType NoteProperty -Name FailedServices -Value $NRS $DCServices += $Sout Write-Color -Text "Measuring DC performance $($dc.name)" -Color Green $dtime = Measure-Command { get-adtest -server $dc.name } $dtime | Add-Member -MemberType NoteProperty -Name Server -Value $dc.name $DCtime += $dtime } else { $Sout = New-Object System.Management.Automation.PSObject $Sout | Add-Member -MemberType NoteProperty -Name Server -Value $dc.name $Sout | Add-Member -MemberType NoteProperty -Name Status -Value "Unavailable" $Sout | Add-Member -MemberType NoteProperty -Name FailedServices -Value "N/A" $DCServices += $Sout $dtime = "Failed" $dtime | Add-Member -MemberType NoteProperty -Name Server -Value $dc.name $DCtime += $dtime } } Write-Color -Text "Getting replication info" -Color Green $log | Write-Log -Line "Getting replication Info for domain $($domain)." -Level Info $Masterrepllist = @() foreach ($dc in $dcs) { $repl = Get-ADReplicationPartnerMetadata -Target * -Partition * -Filter * -EnumerationServer $DC.hostname | Select-Object Server, Partition, Partner, partnertype, lastreplicationattempt, lastreplicationsuccess, lastreplicationresult, ConsecutiveReplicationFailures $server1 = $repl.partner $server1 = $server1.replace("CN=NTDS Settings,CN=", "") $server1 = $server1.substring(0, $server1.indexof(',')) $repl.partner = $server1 $Masterrepllist += $repl } #$rep1 = Get-ADReplicationPartnerMetadata -Target * -Partition * -Filter * -EnumerationServer $DMTargetDC | Select-Object Server, Partition, Partner, partnertype, lastreplicationattempt, lastreplicationsuccess, lastreplicationresult, ConsecutiveReplicationFailures #$rout = @() #foreach ($r in $rep1) #{ # $server1 = $r.partner # $server1 = $server1.replace("CN=NTDS Settings,CN=", "") # $server1 = $server1.substring(0, $server1.indexof(',')) # $r.partner = $server1 # $rout += $r #} $rout = $Masterrepllist | select Server, Partition, Partner, partnertype Write-Color -Text "Adding Replication table" -Color Green add-paragraph -wordDoc $wordDoc -text "Below is a list of current replication connections in the domain $($domain)." add-table -wordDoc $wordDoc -object $rout -trans $false if (($Masterrepllist | ?{ $_.lastreplicationattempt -ne $_.lastreplicationsuccess } | measure).count -ne 0) { Write-Color -Text "Adding Failed Rep info table" -Color Green add-paragraph -wordDoc $wordDoc -Text "Quest detected failed replication connections and will list the details of each below." $rout2 = $Masterrepllist | ?{ $_.lastreplicationattempt -ne $_.lastreplicationsuccess } foreach ($ro in $rout2) { Write-Color -Text "Adding Specific failed Replication item info" -Color Green add-paragraph -wordDoc $wordDoc -Text "Details of failed replication connection for server $($ro.server)." add-table -wordDoc $wordDoc -object $ro } } else { Write-Color -Text "adding all good rep item" -Color Green add-paragraph -wordDoc $wordDoc -Text "All replication connections are currently healthy." } Write-Color -Text "Adding Services Table" -Color Green add-paragraph -wordDoc $wordDoc -Text "A Check was performed against each Domain Controller for the following services: ntds, adws, dns, dnscache, kdc, w32time, netlogon, dhcp. The results of that check are in the table below." add-table -wordDoc $wordDoc -object $DCServices -trans $false #Performace check $paragraph = Add-WordText -WordDocument $wordDoc -Text "PERFORMANCE CHECK" -HeadingType Heading4 -Supress $true add-paragraph -wordDoc $wordDoc -Text "Quest ran a battery of AD commands against the domain $($domain). Each Test was run against a single DC and the time to execute was measured." add-paragraph -wordDoc $wordDoc -Text "Each test does not take into account any lag time of the connect from the testing computer to the destination DC." add-paragraph -wordDoc $wordDoc -Text "This should give you an idea of the reposeivness of each DC in the domain, but it is not a solid indicator of actual performce. Serperate performance data should be gathered." add-paragraph -wordDoc $wordDoc -Text "If the Test was executed on a DC against itself the times will be out of scope and inaccurate. The current test machine is $($Env:COMPUTERNAME)" $dout = $DCtime | select Server, totalmilliseconds Write-Color -Text "adding performance table data" -Color Green add-table -wordDoc $wordDoc -object $dout -trans $false $DCHW = @() foreach ($dc in $dcs) { $processor = (Get-Counter -listset processor -ComputerName $dc.hostname).paths $physicalDisk = (Get-Counter -listset physicaldisk -ComputerName $dc.hostname).paths $memory = (Get-Counter -listset memory -ComputerName $dc.hostname).paths $network = (Get-Counter -listset 'network interface' -ComputerName $dc.hostname).paths $mycounters = $processor + $physicalDisk + $memory + $network $perf = Get-Counter -Counter $mycounters -SampleInterval 3 -MaxSamples 4 -ComputerName $dc.hostname $DCH = Create-UserObject -Properties "PSComputername", "NumberofProcessors", "NumberofLogicalProcessors", "TotalPhysicalMemory", "CPUTime", "CPUIdle", "AvailableMemory", "DiskTime" $dcWMI = Get-WmiObject -Class Win32_Computersystem -ComputerName $dc.hostname | select * $DCH.PSComputername = $dcWMI.PScomputername $DCH.NumberofProcessors = $dcWMI.numberofprocessors $DCH.Numberoflogicalprocessors = $dcWMI.numberoflogicalprocessors $DCH.totalphysicalmemory = $dcWMI.totalphysicalmemory $DCH.cputime = (($perf.countersamples | ?{ $_.path -like '*% Processor Time' }) | measure -Property cookedvalue -Average).Average $DCH.cpuidle = (($perf.countersamples | ?{ $_.path -like '*% Idle Time' }) | measure -Property cookedvalue -Average).average $DCH.availablememory = (($perf.countersamples | ?{ $_.path -like '*Available Bytes' }) | measure -Property cookedvalue -Average).average $DCH.disktime = (($perf.countersamples | ?{ $_.path -like '*% Disk Time' }) | measure -Property cookedvalue -Average).average $DCHW += $DCH } add-paragraph -wordDoc $wordDoc -Text "The Domain Controllers passed the PC performace values in the table below." add-table -wordDoc $wordDoc -object $DCHW -trans $false } #Security Reports $paragraph = Add-WordText -WordDocument $wordDoc -Text "SECURITY REPORT" -HeadingType Heading2 -Supress $true Write-Color -Text "Processing Security" -Color Green foreach ($domain in $forest.domains) { Write-Color -Text "Creating Security Reports for domain $($domain)" -Color Green $log | Write-Log -Line "Getting Security Info for domain $($domain)." -Level Info $DMTargetDC = Get-ADDomainController -Discover -DomainName $domain $dcs = Get-ADDomainController -Filter * -Server $DMTargetDC | select name $users = Get-ADUser -Filter * -Properties * -Server $DMtargetDC $TUsers = @() foreach ($u in $users) { $newLLD = Get-ADUsersLastLogon -usersid $u.SID -TargetDC $DMtargetDC $u | Add-Member -MemberType NoteProperty -Name CorrectedLastLogon -Value $newLLD $TUsers += $u } #stale Users Report $staleU = $tusers | ?{ $_.correctedlastlogon -lt [datetime]::Now.AddDays(-90) } | select Samaccountname, Userprincipalname, correctlastlogon, enabled $paragraph = Add-WordText -WordDocument $wordDoc -Text "STALE USERS" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "The following Table list all users whose last logon date is grater then 90 days old." add-table -wordDoc -object $staleU -trans $false #bad passwords $NC = (Get-ADRootDSE -Server $DMTargetDC).defaultnamingcontext $BadUsers = Get-bADpasswords -Wordlists "badpasswords.txt" -DC $DMTargetDC -strNamingContext $NC $paragraph = Add-WordText -WordDocument $wordDoc -Text "Compramised/Week Passwords" -HeadingType Heading3 -Supress $true add-paragraph -wordDoc $wordDoc -text "The following users have Weak and Broken Passwords:" add-table -wordDoc $wordDoc -object $BadUsers -trans $false } Save-WordDocument -WordDocument $wordDoc -FilePath "$($Directory)\AD Assessment Report.docx" $log | Close-Log } End { } } |