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]$Username,
        [parameter(Mandatory = $true)]
        [object]$dcs,
        [parameter(Mandatory = $true)]
        [string]$TargetDC
    )
    #$user = Get-ADUser -identity $usersid -Server $TargetDC -Properties samaccountname, passwordlastset
    $time = 0
    $n = $log | Write-Log -Line "Processing User Lastlogon for user $($Username)." -Level Info
    #$dcs = Get-ADDomainController -Server $TargetDC -Filter *
    #test to remote offline DC's
    #$newDCs = @()
    #foreach ($dc in $dcs)
    #{
        #if (Test-Connection -ComputerName $dc.hostname -Count 1 -Quiet)
        #{
            #$newDCs += $dc
        #}
    #}
    foreach ($dc in $dcs)
    {
        #try
        #{
            $currentUser = Get-ADUser $usersid -Server $TargetDC | Get-ADObject -Server $dc.HostName -Properties lastLogon -ea SilentlyContinue
        #}
        #catch
        #{
            #Write-Color -Text "Unable to get user information for user ", "$($user.samaccountname)", " from domain controller ", "$($hostname)", "." -Color Green, Yellow, green, yellow
            #$n = $log | Write-Log -Line "Unable to get user information for user $($user.samaccountname) from domain controller $($hostname)." -Level Error
        #}
        
        
        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
    )
    
    # ============ #
    # VARIABLES => #
    # ============ #
    $ScriptVersion = "1.05"
    
    
    
    # Counters
    $intBadPasswordsFound = 0
    $intBadPasswordsInLists = 0
    $intBadPasswordsInListsDuplicates = 0
    $intUsersAndHashesFromAD = 0
    $intNullPasswordsFound = 0
    
    # Constant
    $strBlankPasswordNThash = '31d6cfe0d16ae931b73c59d7e0c089c0'
    
    #output object
    
    $BUsers = @()
    
    # ============ #
    # FUNCTIONS => #
    # ============ #
    
    
    
    Function Get-NTHashFromClearText
    {
        # Usage: Get-NTHashFromClearText -ClearTextPassword 'Pa$$W0rd' > 36185099f86b48b5af3cc46edd16efca
        Param ([string]$ClearTextPassword)
        Return ConvertTo-NTHash $(ConvertTo-SecureString $ClearTextPassword -AsPlainText -Force)
    }
    
    
    
    # ============ #
    # SCRIPT => #
    # ============ #
    
    
    # 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..."
    
    # 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
        Write-Verbose "FAIL: $ErrorMessage"
    }
    
    $intUsersAndHashesFromAD = $arrUsersAndHashes.Count
    
    # We can only continue, if we got users and hashes
    If ($intUsersAndHashesFromAD -lt 1)
    {
        
        Write-Verbose "No data from AD - will quit!"
        GetOut
    }
    
    # Let's deliver the good news
    Write-Verbose "AD returned $intUsersAndHashesFromAD usernames and NT hashes!"
    
    # Add blank password NT hash to hashtable
    Write-Verbose "Adding blank password NT hash to hashtable..."
    $htBadPasswords.Add($strBlankPasswordNThash, "")
    
    Write-Verbose "Loading bad password word lists..."
    
    # Load each word list
    Foreach ($WordlistPath in $arrBadPasswordFiles)
    {
        
        Write-Verbose "| Checking word list: $WordlistPath"
        
        If (Test-Path $WordlistPath)
        {
            
            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 '')
                {
                    
                    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++
                    Write-Verbose "| Duplicate password: '$BadPassword' = $NTHash (line#: $cnt_LineNumber)"
                }
                Else # New password to put into hash table
                {
                    
                    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
        {
            
            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)
    {
        
        Write-Verbose "No data from word lists - will quit!"
        GetOut
    }
    
    # Let's deliver the good news
    
    Write-Verbose "Word lists had a total of $intBadPasswordsInLists unique weak passwords!"
    

    Write-Verbose "Word lists had a total of $intBadPasswordsInListsDuplicates duplicate weak passwords!"
    <#$Tuser = New-Object System.Management.Automation.PSObject
    $Tuser | Add-Member -MemberType NoteProperty -Name Samaccountname -Value $null
    $Tuser | Add-Member -MemberType NoteProperty -Name NTHashHex -Value $null
    $Tuser | Add-Member -MemberType NoteProperty -Name Password -Value $null
    #>

    Foreach ($objUser in $arrUsersAndHashes)
    {
        $Tuser = Create-UserObject -Properties "SamAccountName", "NTHashHex","Password"
        $strUserSamAccountName = $objUser.SamAccountName
        $strUserNTHashHex = $objUser.NTHashHex
        
        If ($strUserNTHashHex -eq $null)
        {
            
            Write-Verbose "Null password found for user: $strUserSamAccountName"
            $intNullPasswordsFound++
            $TUser.Samaccountname = $strUserSamAccountName
            $Tuser.NTHashHex = $strUserNTHashHex
            $Tuser.Password = $null
            $BUsers += $TUser
            Continue
        }
        
        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
            
            Write-Verbose "Weak password found for user: $strUserSamAccountName = '$strUserBadPasswordClearText'"
            
            $BUsers += $TUser
        }
        Else
        {
            
            Write-Verbose "| Compliant password for user: $strUserSamAccountName"
        }
        
    }
    
    # Give status on found weak passwords
    
    Write-Verbose "Found $intBadPasswordsFound user(s) with weak password!"
    
    # Give status on found $null passwords
    
    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,
        [parameter(Mandatory = $false)]
        [bool]$ShowPW = $false,
        [parameter(Mandatory = $false)]
        [bool]$SeperateGPOReport = $false,
        [parameter(Mandatory = $false)]
        [bool]$SkipBadPassCheck = $false
        
    )
    Begin
    {
        #set Powershell to TLS1.2 - needed to install modules from powershell gallery
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        
        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 -Install $true
            
        }
        catch
        {
            Write-host "Failed to load pscommoncore module execution halted" 
            Write-host "Returned error message is $($_.ErrorDetails.Message)"
            Exit
            
        }
        #once the pscommon core module is loaded loggin can begin
        #set up logging into the output directory
        $log = Start-Log -Log "$($Directory)\adreportlog.txt" -Type TXT
        
        $n = $log | Write-Log -Line "Loading the DSInternals Module." -Level Info
        
        #load module dsinternals for badpassword check
        try
        {
            Test-Module -Module dsinternals -Load $true -Install $true
            
        }
        catch
        {
            Write-host "Failed to load dsinternals module execution halted"
            Write-host "Returned error message is $($_.ErrorDetails.Message)"
            $n = $log | Write-Log -Line "DSInternals Module failed to load. Exiting." -Level Error
            $n = $log | Write-Log -Line "DSInternals Module fail Message: $($_.ErrorDetails.Message)" -Level Error
            $n = $log | Close-Log
            Exit
            
        }
        $n = $log | Write-Log -Line "DSInternals Module Loaded." -Level Info
        
        
        # Dotsource in the functions you need.
    }
    Process
    {
        #clear the screen
        cls
        
        
        $staleUsersalldomains = @()
        #check the version of powershell if version 5 install Word module
        $n = $log | Write-Log -Line "Testing PowerShell Version." -Level Info
        $psver = Test-Powershell
            
        if ($psver.version -like '5*' -and $psver.elevated -eq $true)
        {
            $n = $log | Write-Log -Line "Powershell Test Passed. PSVersion: $($psver.version) PS Elevated: $($psver.elevated)" -Level Info
            Write-Color -Text "Loading the Word Module from PowerShell Gallery" -Color Green
            
            $WDResult = Test-Module -Module PSWriteWord -Load $true -install $true -ea Stop
                        
            if ($WDResult.status -eq "Loaded")
            {
                Write-Color -Text "Word Module Loaded" -Color Green
                $n = $log | Write-Log -Line "Word Module loaded." -Level Info
            }
            else
            {
                Write-Color -Text "Word Module failed to load" -Color Green
                Write-Color -Text "The message was $($WDResult.message)" -Color Yellow
                $n = $log | Write-Log -Line "Word Module failed to load. Exiting." -Level Error
                $n = $log | Close-Log
                exit
                
            }
            
        }
        else
        {
            Write-Color -Text "Powershell Version Incompatable or not running elevated - Stopping" -Color Red
            $n = $log | Write-Log -Line "Powershell incorrect version exiting. PSVersion: $($psver.version)" -Level Error
            $n = $log | Close-Log
            exit
            
        }
        
        #test to see if group policy powershell module is available
        Write-Color -Text "Checking Group Policy Module." -Color Green
        $n = $log | Write-Log -Line "Testing Group Policy Module." -Level Info
        $GPresults = Test-Module -Module grouppolicy -Load $true -Install $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
            $n = $log | Write-Log -Line "Group Policy Module failed to load. Exiting." -Level Error
            $n = $log | Write-Log -Line "Group Policy Module fail message: $($GPresults.message)" -Level Error
            $n = $log | Close-Log
            exit
        }
        else
        {
            Write-Color -Text "Group Policy Module Loaded proceeding...." -Color Green
            $n = $log | Write-Log -Line "Group Policy Module Loaded." -Level Info
            
        }
        
        #test to see if ad powershell module is available
        Write-Color -Text "Checking Active Directory Module." -Color Green
        $n = $log | Write-Log -Line "Testing Active Directory Module." -Level Info
        $ADResults = Test-Module -Module activedirectory -Load $true -Install $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
            $n = $log | Write-Log -Line "Active Directory Module failed to load. Exiting." -Level Error
            $n = $log | Write-Log -Line "Active Directory Module fail message: $($ADResults.message)." -Level Error
            $n = $log | Close-Log
            exit
        }
        else
        {
            Write-Color -Text "Active Directory Module Loaded proceeding...." -Color Green
            $n = $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 "")
        {
            Write-Color -Text "Target DC Parameter is Blank" -Color Yellow
            $n = $log | Write-Log -Line "Target DC Parameter is blank trying alternate path to aquire a Domain Controller." -Level Warn
            Write-Color -Text "Checking Local Computer to see if it is a Domain Controller." -Color Green
            if (!(Get-ADDomainController -Server (get-computerinfo).csname))
            {
                
                Write-Color -Text "This computer is not a domain controller exiting..." -Color Red
                $n = $log | Write-Log -Line "Current computer not a Domain Controller and no domain controller was supplied as a parameter. Exiting" -Level Error
                $n = $log | Close-Log
                exit
                
            }
            else
            {
                Write-Color -Text "Using Local Computer becasue it is a domain controller" -Color Green
                $n = $log | Write-Log -Line "No domain controller was supplied as a parameter bu the current computer is a domnain controller using local machine as TargetDC." -Level Info
                
                $targetDC = (Get-ComputerInfo).csname
                
            }
        }
        else
        {
            Write-Color -Text "The TargetDC $($targetDC) is being verified." -Color Green
            $n = $log | Write-Log -Line "The TargetDC $($targetDC) is being verified." -Level Info
            
            $online = Test-Connection -Count 1 -Quiet -ComputerName $targetDC
            if ($online)
            {
                Write-Color -Text "The Target DC $($targetDC) is responding to a ping. Continuing."
                $n = $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." -Color Yellow
                $n = $log | Write-Log -Line "Supplied Domain Controller not responding to a ping could not be available script might error out. Continuing." -Level Warn
            }
            
        }
        
        $n = $log | Write-Log -Line "Creating Schema Table." -Level Info
        #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
        }
        $n = $log | Write-Log -Line "Creating Schema Table Complete." -Level Info
        
        
        $n = $log | Write-Log -Line "Preparing Word Document." -Level Info
        
        try
        {
            #prepare the Word Report Document
            $template = "$($Directory)\ADTemplate.docx"
            $wordfile = "$($Directory)\AD Assesment Deliverable.docx"
            $wordDoc = get-WordDocument -filepath $template -ea Stop
            
        }
        catch
        {
            Write-Color -Text "Failed to read Word Document halting." -Color Red
            Write-Color -Text "The message was $($_.ErrorDetails.Message)" -Color Yellow
            $n = $log | Write-Log -Line "Document load failed. Exiting." -Level Error
            $n = $log | Write-Log -Line "Document load fail message: $($_.ErrorDetails.Message)." -Level Error
            $n = $log | Close-Log
            exit
        }
        
        #setting up Headder
        
        $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
        $n = $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
        $n = $log | Write-Log -Line "Collecting Base Forest Data." -Level Info
        
        $forest = Get-ADForest -Server $targetDC | select *
        
        $n = $log | Write-Log -Line "Collecting Base Forest Data complete." -Level Info
        
        #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, sites
        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 Heading5 -Supress $true 
        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 Heading5 -Supress $true 
        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
        $paragraph = Add-WordText -WordDocument $wordDoc -Text "CATALOG AND FSMO DATA" -HeadingType Heading3 -Supress $true
        $n = $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 its Domains."
        
        $forestmginfo = $forest | select globalcatalogs, DomainNamingmaster, Schemamaster
        add-table -wordDoc $wordDoc -object $forestmginfo
        
        $paragraph = Add-WordText -WordDocument $wordDoc -Text "GLOBAL CATALOG" -HeadingType Heading4 -Supress $true 
        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 Heading5 -Supress $true 
        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 Heading5 -Supress $true 
        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."
        
        
        
        
        #Suffixes for SPN and UPN
        $n = $log | Write-Log -Line "Collecting SPN/UPN Forest Data." -Level Info
        $paragraph = Add-WordText -WordDocument $wordDoc -Text "UPN AND SPN SUFFIXES" -HeadingType Heading3 -Supress $true 
        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
        $n = $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.objectversion -lt 69)
        {
            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."
            $n = $log | Write-Log -Line "Schema below supported levels recommend updating to supported version." -Level Warn
        }
        
        #Optional Features
        Write-Color -Text "Collectimng Forest Optional Features" -Color Green
        $n = $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
        $n = $log | Write-Log -Line "Collecting Directory Service Information." -Level Info
        $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 -trans $false
        
        
        #Trust Information
        Write-Color -Text "Collectimng Forest Trust Info" -Color Green
        $n = $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."
            $n = $log | Write-Log -Line "Forest Contains no trusts." -Level Info
        }
        else
        {
            $n = $log | Write-Log -Line "Forest contains trusts getting information." -Level Info
            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
            }
            $n = $log | Write-Log -Line "Trust information processed." -Level Info
        }
        
        #sites in AD
        Write-Color -Text "Collecting Site Information" -Color Green
        $n = $log | Write-Log -Line "Collecting Site Data." -Level Info
        $paragraph = Add-WordText -WordDocument $wordDoc -Text "ACTIVE DIRECTORY SITES" -HeadingType Heading2 -Supress $true 
        $paragraph = Add-WordText -WordDocument $wordDoc -Text "SITE INFORMATION" -HeadingType Heading3 -Supress $true 
        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)
        {
            $SiteDisplay = $site | select * -ExcludeProperty intrasitereplicationschedule
            Write-Color -Text "Processing Site $($Site.name)" -Color Green
            $n = $log | Write-Log -Line "Processing Site $($Site.name)" -Level Info
            add-paragraph -wordDoc $wordDoc -text "The site $($Site.name) contains the following details."
            add-table -wordDoc $wordDoc -object $SiteDisplay
                        
        }
        
        #site link info
        Write-Color -Text "Collecting Site Link Information" -Color Green
        $n = $log | Write-Log -Line "Collecting Site Link Data." -Level Info
        $sitelink = Get-ADReplicationSiteLink -filter * -Properties * -Server $targetDC |select CN,Cost,Created,Deleted,Description,Displayname,intersitetransportprotocol.modified,Name,replicationfrequencyinminutes,sitelist,sitesincluded
        $sitednfilter = $sitelink.sitesincluded[0].replace("$($sitelink.sitesincluded[0].split(",")[0]),", "")
        $sitednfilter = "," + $sitednfilter
        
        $SLoutWord = $sitelink | select name, @{
            n           = "sitesincluded"; e ={$_.sitesincluded.replace($sitednfilter,"") }
        }
        $SLoutWord.sitesincluded = $SLoutWord.sitesincluded.replace("CN=", "")
        
        
        $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
            $n = $log | Write-Log -Line "Processing Site Link $($link.name)" -Level Info
            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
        $n = $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)
        {
            $n = $log | Write-Log -Line "Site Link Bridge exists gathering data." -Level Info
            $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
                $n = $log | Write-Log -Line "Processing Site Link Bridge $($bridge.name)" -Level Info
                add-paragraph -wordDoc $wordDoc -text "The site link bridge $($bridge.name) contains the following details."
                add-table -wordDoc $wordDoc -object $bridge 
                
            }
        }
        else
        {
            $n = $log | Write-Log -Line "No Site Link Bridge exists." -Level Info
            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
        $n = $log | Write-Log -Line "Collecting Forest Data Complete." -Level Info
        
        Write-Color -Text "Starting collection on Domains" -Color Green
        $n = $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
        $n = $log | Write-Log -Line "Building list fo all forest domains." -Level Info
        foreach ($dom in $forest.domains)
        {
            $n = $log | Write-Log -Line "Processing Domain $($dom)" -Level Info
            $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
            $n = $log | Write-Log -Line "Processing Domain $($domain)." -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."
            
            $n = $log | Write-Log -Line "Processing Domain basic information." -Level Info
            $temp = Get-ADDomain -Identity $domain -Server $targetDC
            $rids = Get-RIDsremainingAdPsh -domainDN $temp.DistinguishedName
            $temp | Add-Member -MemberType NoteProperty -Name RIDsIssued -Value $rids.ridsissued -force
            $temp | Add-Member -MemberType NoteProperty -Name RIDsRemaining -Value $rids.ridsremaining -force
            
            #basic domain information
            $BDI = $temp | select Name, NetbiosName, Forest, DomainMode, DNSRoot, ParentDomain, ChildDomains, RIDsIssued, RIDsRemaining
            
            
            
            $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
            $n = $log | Write-Log -Line "Processing Domain container information." -Level Info
            $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
            $n = $log | Write-Log -Line "Processing Domain FSMO Information." -Level Info
            $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
            $n = $log | Write-Log -Line "Processing Domain mode." -Level Info
            $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
            $n = $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
            
            #build a list of online Domain Controllers for future user actions
            $OnlineDCs = @()
            $OfflineDCs = @()
            
            $failDCflag = $false
            
            foreach ($dc in $dcs)
            {
                Write-Color -Text "Processing Domain Controller $($dc.name)" -Color Green
                $n = $log | Write-Log -Line "Processing Domain Controller $($dc.name)." -Level Info
                $n = $log | Write-Log -Line "$($dc.name) General Info Gathering." -Level Info
                $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN CONTROLLER: $($dc.name)" -HeadingType Heading5 -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
                $n = $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
                $n = $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
                $n = $log | Write-Log -Line "$($dc.name) Server DNS Details Gathering." -Level Info
                $dns = Get-DnsServerSetting -All -ComputerName $dc.name -ErrorAction SilentlyContinue |select * -ExcludeProperty pscomputername,CimClass,CimInstanceProperties,CimSystemProperties
                $paragraph = Add-WordText -WordDocument $wordDoc -Text "SERVER DNS SERVICE INFORMATION" -HeadingType Heading5 -Supress $true
                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."
                }
                
                #test if DC is online
                $online = Test-Connection -ComputerName $dc.hostname -Count 1 -Quiet
                if ($online)
                {
                    $n = $log | Write-Log -Line "Domain Controller $($dc.name) is pingable." -Level Info
                    try
                    {
                        $testauser = Get-ADUser -Server $dc.hostname -Filter * | select -First 1 -ea Stop
                        $n = $log | Write-Log -Line "Domain Controller $($dc.name) is PowerShell Available." -Level Info
                        $OnlineDCs += $dc
                    }
                    catch
                    {
                        $failDCflag = $true
                        $OfflineDCs += $dc
                        $n = $log | Write-Log -Line "Domain Controller $($dc.name) is not PowerShell Available." -Level Error
                        $n = $log | Write-Log -Line "Domain Controller $($dc.name) error message $($_.ErrorDetails.Message)." -Level Error
                    }
                }
                else
                {
                    $failDCflag = $true
                    $OfflineDCs += $dc
                    $n = $log | Write-Log -Line "Domain Controller $($dc.name) is not pingable." -Level Error
                }
            }
            
            if ($failDCflag)
            {
                $OffHosts = @()
                Write-Color -Text "Some Domain Controllers were offline." -Color Yellow
                $n = $log | Write-Log -Line "Some Domain Controllers were offline." -Level Warn
                foreach ($dc in $OfflineDCs)
                {
                    $n = $log | Write-Log -Line "Domain Controller $($dc.hostname) is not reachable." -Level Warn
                    $Obj = Create-UserObject -Properties "Hostname", "IPv4Address", "Enabled", "Site"
                    $Obj.Hostname = $dc.hostname
                    $Obj.ipv4address = $dc.ipv4address
                    $Obj.Enabled = $dc.enabled
                    $Obj.site = $dc.site
                    $OffHosts += $Obj
                }
                $paragraph = Add-WordText -WordDocument $wordDoc -Text "UNAVAILABLE DOMAIN CONTROLLERS" -HeadingType Heading4 -Supress $true
                add-paragraph -wordDoc $wordDoc -text "Quest dicovered at the time of testing the following domain controllers were unavailable or unresponsive."
                 
                add-table -wordDoc $wordDoc -object $OffHosts -trans $true
                
            }
            
            
            Write-Color -Text "Getting AD DNS Zones" -Color Green
            $n = $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)
            {
                $n = $log | Write-Log -Line "Processing DNS Zone $($zone.zonename)." -Level Info
                $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 -ComputerName $targetDC | 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
            $n = $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
            $n = $log | Write-Log -Line "Gathering User Data for domain $($domain)." -Level Info
            
            #createmaster user list for the domain
            $masterUserList = @()
            $n = $log | Write-Log -Line "Creating Master User List." -Level Info
            $users = Get-ADUser -Filter * -Properties * -Server $targetDC
            $masterUserList = $users
            $masterUserList | Add-Member -MemberType NoteProperty -Name Correctedlastlogon -Value $null -Force
            $n = $log | Write-Log -Line "Master User List Creation Complete." -Level Info
            #correcting users lastlogon
                            
            #get User Information from the other domain controllers and correct the pieces of the user data such as last logon and last password change
            foreach ($dc in $OnlineDCs)
            {
                if ($dc.hostname -ne $targetDC)
                {
                    $n = $log | Write-Log -Line "Processing user specific attributes for additional DC $($dc.hostname)." -Level Info
                    $n = $log | Write-Log -Line "Fixing lastlogon for users for DC $($dc.hostname)." -Level Info
                    $tempuserlist = Get-ADUser -Filter * -Properties samaccountname, lastlogon, lastlogoff, logoncount -Server $dc.hostname
                    foreach ($u in $masteruserlist)
                    {
                        #fix lastlogon
                        $currentUsertime = $u.lastlogon
                        $dcusertime = ($tempuserlist | ?{$_.samaccountname -eq $u.samaccountname}).lastlogon
                        if ($currentUsertime -lt $dcusertime)
                        {
                            ($masterUserList | ?{$_.samaccountname -eq $u.samaccountname}).correctedlastlogon = [DateTime]::FromFileTime($dcusertime)
                        }
                        else
                        {
                            ($masterUserList | ?{$_.samaccountname -eq $u.samaccountname}).correctedlastlogon = [DateTime]::FromFileTime($currentusertime)
                        }
                        
                    }
                    $n = $log | Write-Log -Line "Finished fixing lasstlogon for dc $($dc.hostname)." -Level Info
                }
            }
            $n = $log | Write-Log -Line "Finished fixing all lastlog attributes." -Level Info
            
            #add user to master stale list if more than 90 days since lastlogon
            foreach ($u in $masteruserlist)
            {
                if ($u.correctedlastlogon -lt [datetime]::now.AddDays(-90))
                {
                    $staleUsersalldomains += $u
                }
                
            }
            
            #$TUsers = @()
            #foreach ($u in $users)
            #{
            # $newLLD = Get-ADUsersLastLogon -usersid $u.SID -TargetDC $targetDC -Username $u.displayname -dcs $OnlineDCs
            # $u | Add-Member -MemberType NoteProperty -Name CorrectedLastLogon -Value $newLLD -force
            # $TUsers += $u
            #}
            
            $obj = New-Object System.Management.Automation.PSObject
            
            $obj | Add-Member -MemberType NoteProperty -Name EnabledUsers -Value (($masterUserList | ?{ $_.enabled -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name DisabledUsers -Value (($masteruserlist | ?{ $_.enabled -eq $false }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name LockedUsers -Value (($masterUserList | ?{ $_.lockedout -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name ExpiredUsers -Value (($masterUserList | ?{ $_.accountexpirationdate -ne $null -and $_.accountexpirationdate -lt [datetime]::Now }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name ExpiringUsers -Value (($masterUserList | ?{ $_.accountexpirationdate -ne $null -and $_.accountexpirationdate -gt [datetime]::Now }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name PWDNeverExpiresUsers -Value (($masterUserList | ?{ $_.passwordneverexpires -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name CannotChangePWDUsers -Value (($masterUserList | ?{ $_.cannotchangepassword -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name PWDExpiredUsers -Value (($masterUserList | ?{ $_.passwordexpired -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name NoPreAuthUsers -Value (($masterUserList | ?{ $_.doesnotrequirepreauth -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name ReversableEncryptionUsers -Value (($masterUserList | ?{ $_.AllowReversiblePasswordEncryption -eq $true }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name StaleUsers -Value (($masterUserList | ?{ $_.correctedlastlogon -lt [datetime]::Now.AddDays(-90) }) | measure).count
            $obj | Add-Member -MemberType NoteProperty -Name TotalUsers -Value ($masterUserList | 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
            $n = $log | Write-Log -Line "Getting AD Domain Password Policy for domain $($domain)." -Level Info
            $ADPassPol = Get-ADDefaultDomainPasswordPolicy -Server $targetDC | select * -ExcludeProperty distinguishedname,objectclass,objectguid,propertynames,addedproperties,removedproperties,modifiedproperties,propertycount
                        
            $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
            $n = $log | Write-Log -Line "Getting AD Fine Grained Password Policy for domain $($domain)." -Level Info
            $ADFGPassPol = Get-ADFineGrainedPasswordPolicy -Filter * -Properties * -Server $targetDC | select * -ExcludeProperty Canonicalname, createdtimestamp, distinguishedname, dscorepropagationdata, instancetype, isdeleted, lastknownparent, modifytimestamp, msds-*, ntsecuritydescriptor, Object*, protectedfromaccidentaldeletion, sdrights*, usn*, when*, property*, addedprop*, removedprop*, Modifiedprop*
                    
            $paragraph = Add-WordText -WordDocument $wordDoc -Text "DOMAIN FINE GRAINED PASSWORD POLICY" -HeadingType Heading5 -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)
                {
                    $n = $log | Write-Log -Line "Processing Fine Grained Password policy $($FGP.name)." -Level Info
                    $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
            $n = $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
            $n = $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
            $n = $log | Write-Log -Line "Getting Privlidged groups for domain $($domain)." -Level Info
            $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 = $masterUserList |?{$_.distinguishedname -eq $member}
                            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 $user.correctedlastlogon
                                $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 -trans $false
                    
                }
            }
            $n = $log | Write-Log -Line "Finished processing privileged groups." -Level Info
            #computers
            Write-Color -Text "Processing Computers" -Color Green
            $n = $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
            if ((($computers | ?{ $_.operatingsystem -eq $null } | measure).count) -gt 0)
                {
                    add-paragraph -wordDoc $wordDoc -text "The Following COmputer have no registered OS in Active Directory. Below is a table of computer names for reference."
                    $Noosname = $computers | ?{ $_.operatingsystem -eq $null } | select CanonicalName, OperatingSystem,Created,Modified
                    add-table -wordDoc $wordDoc -object $Noosname -trans $false
                    
                }
                
                
                
                $n = $log | Write-Log -Line "Finished gathering computer data for domain $($domain)." -Level Info
            
            
            
            
            #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
            $n = $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)
            {
                $n = $log | Write-Log -Line "Processing GPO $($gp.displayname)." -Level Info
                $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 Reports (HTML)" -Color Green
            $n = $log | Write-Log -Line "Creating GPO Reports for domain $($domain)." -Level Info
            
            IF ($SeperateGPOReport)
            {
                foreach ($gp in $gpo)
                {
                    $n = $log | Write-Log -Line "Creating GPO Reports for $($gp.displayname)." -Level Info
                    Write-Color -Text "Processing GPO ", "$($gp.displayname)", "." -Color Green, yellow, green
                    $id = $gp.id
                    $gppath = "$($Directory)\GPOReport - $($gp.displayname).html"
                    try
                    {
                        Get-GPOReport -Server $targetDC -guid $id -ReportType html -Path $gppath -ea Stop
                    }
                    catch
                    {
                        $n = $log | Write-Log -Line "The GPO - $($gp.displayname) in the domain $($domain) was unable to have its report exported to a file becasue of an error." -Level Error
                        $n = $log | Write-Log -Line "The reported error message was: $($_.ErrorDetails.Message)" -Level Error
                    }
                    
                    
                }
            }
            else
            {
                $GPOReport = Get-GPOReport -domain $domain -server $TDC -All -ReportType html -Path "$($Directory)\GPOReport - All.html"
            }
            
            
            $n = $log | Write-Log -Line "Finished Processing domain $($domain)." -Level Info
        }
        Write-Color -Text "Finished processing domains" -Color Green
        $n = $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
            $n = $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
            $n = $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)
            {
                $n = $log | Write-Log -Line "Processing DC $($dc.name)." -Level Info
                $dcalive = $true
                if ((Test-Connection -Quiet -Count 1 -ComputerName $dc.name) -eq $false)
                {
                    $dcalive = $false
                }
                else
                {
                    if ((Get-ADUser -Filter * -Properties samaccountname | measure).count -lt 1)
                    {
                        $dcalive = $false
                        
                    }
                }
                #$dcalive = Test-Connection -Quiet -Count 1 -ComputerName $dc.name
                if ($dcalive -eq $true)
                {
                    $n = $log | Write-Log -Line "DC $($dc.name) is alive." -Level Info
                    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)
                    {
                        $n = $log | Write-Log -Line "DC $($dc.name) services failed." -Level Warn
                        $status = "Failed"
                        $NRS = ($Services | ?{ $_.Status -ne 'Running' }).name
                    }
                    else
                    {
                        $n = $log | Write-Log -Line "DC $($dc.name) serives passed." -Level Info
                        $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
                    $n = $log | Write-Log -Line "DC $($dc.name) performance test start." -Level Info
                    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
                    $n = $log | Write-Log -Line "DC $($dc.name) performance test end." -Level Info
                    
                }
                else
                {
                    $n = $log | Write-Log -Line "DC $($dc.name) is not alive." -Level Info
                    $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
            $n = $log | Write-Log -Line "Getting replication Info for domain $($domain)." -Level Info
            $Masterrepllist = @()
            foreach ($dc in $dcs)
            {
                $n = $log | Write-Log -Line "Getting DC $($dc.name) replication information." -Level Info
                $repl = Get-ADReplicationPartnerMetadata -Target * -Partition * -Filter * -EnumerationServer $DC.name | Select-Object Server, Partition, Partner, partnertype, lastreplicationattempt, lastreplicationsuccess, lastreplicationresult, ConsecutiveReplicationFailures
                $rout = @()
                foreach ($r in $repl)
                {
                    $server1 = $r.partner
                    $server1 = $server1.replace("CN=NTDS Settings,CN=", "")
                    $server1 = $server1.substring(0, $server1.indexof(','))
                    $r.partner = $server1
                    $rout += $r
                }
                
                $Masterrepllist += $rout
            }
            
            
            $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)
            {
                $n = $log | Write-Log -Line "Failed replication(s) present." -Level Warn
                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
            {
                $n = $log | Write-Log -Line "All replication(s) currently functional." -Level Info
                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 reference to the executing System, 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)
            {
                $n = $log | Write-Log -Line "Processing DC $($dc.name) for Performance Information." -Level Info
                $dcalive = $true
                if ((Test-Connection -Quiet -Count 1 -ComputerName $dc.name) -eq $false)
                {
                    $dcalive = $false
                }
                else
                {
                    if ((Get-ADUser -Filter * -Properties samaccountname | measure).count -lt 1)
                    {
                        $dcalive = $false
                        
                    }
                }
                if ($dcalive)
                {
                    $n = $log | Write-Log -Line "Saving DC $($dc.name) Performance Data." -Level Info
                    $processor = (Get-Counter -listset processor -ComputerName $dc.name).paths
                    $physicalDisk = (Get-Counter -listset physicaldisk -ComputerName $dc.name).paths
                    $memory = (Get-Counter -listset memory -ComputerName $dc.name).paths
                    $network = (Get-Counter -listset 'network interface' -ComputerName $dc.name).paths
                    $mycounters = $processor + $physicalDisk + $memory + $network
                    $perf = Get-Counter -Counter $mycounters -SampleInterval 3 -MaxSamples 4 -ComputerName $dc.name
                    
                    $DCH = Create-UserObject -Properties "PSComputername", "Online", "NumberofProcessors", "NumberofLogicalProcessors", "TotalPhysicalMemory", "CPUTime", "CPUIdle", "AvailableMemory", "DiskTime"
                    
                    
                    $dcWMI = Get-WmiObject -Class Win32_Computersystem -ComputerName $dc.name | select *
                    $DCH.Online = $true
                    $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
                }
                else
                {
                    $n = $log | Write-Log -Line "DC $($dc.name) not online zeroing values." -Level Warn
                    $DCH = Create-UserObject -Properties "PSComputername", "Online", "NumberofProcessors", "NumberofLogicalProcessors", "TotalPhysicalMemory", "CPUTime", "CPUIdle", "AvailableMemory", "DiskTime"
                    $DCH.PSComputername = $dc.name
                    $DCH.Online = $false
                    $DCHW += $DCH
                }
            }
            add-paragraph -wordDoc $wordDoc -Text "The Domain Controllers performance data is contained in the following 2 tables."
            add-paragraph -wordDoc $wordDoc -Text "This table contains hardware information, if the server is available."
            $HWI = $DCHW | select pscomputername,Online,numberofprocessors,numberoflogicalprocessors,totalphysicalmemory
            add-table -wordDoc $wordDoc -object $HWI -trans $false
            add-paragraph -wordDoc $wordDoc -Text "This table contains hardware performance information, if the server is available."
            $HPI = $DCHW | select pscomputername, Online, CPUTime, CPUIdle, AvailableMemory, DiskTime
            add-table -wordDoc $wordDoc -object $HPI -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
            $n = $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 $MasterUserlist)
            #{
            # $newLLD = Get-ADUsersLastLogon -usersid $u.SID -TargetDC $DMtargetDC
            # $u | Add-Member -MemberType NoteProperty -Name CorrectedLastLogon -Value $newLLD -force
            # $TUsers += $u
            #}
            #stale Users Report
        $n = $log | Write-Log -Line "Processing Stale User Information." -Level Info
            $staleU = $staleUsersalldomains | select Samaccountname, Userprincipalname, @{n = "Domain"; e = {($_.canonicalname -split"/")[0]}},correctedlastlogon, 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 $wordDoc -object $staleU -trans $false
        
        if (!$SkipBadPassCheck)
        {
            $n = $log | Write-Log -Line "Processing bad password information." -Level Info
            #bad passwords
            $NC = (Get-ADRootDSE -Server $DMTargetDC).defaultnamingcontext
            $n = $log | Write-Log -Line "Processing Users Start." -Level Info
            $BadUsers = Get-bADpasswords -Wordlists "$($Directory)\badpasswords.txt" -DC $DMTargetDC -strNamingContext $NC
            $n = $log | Write-Log -Line "Processing Users End." -Level Info
            if ($ShowPW -eq $true)
            {
                $BadUsers = $BadUsers | select SamaccountName, NTHashHex, Password
                
            }
            else
            {
                $BadUsers = $BadUsers | select SamaccountName, NTHashHex
            }
            
            
            $paragraph = Add-WordText -WordDocument $wordDoc -Text "Compramised/Weak Passwords" -HeadingType Heading3 -Supress $true
            add-paragraph -wordDoc $wordDoc -text "The following users have weak and/or comprimised passwords:"
            add-table -wordDoc $wordDoc -object $BadUsers -trans $false
        }
        #}
        
        
        $n = $log | Write-Log -Line "Writing Output Report." -Level Info
        try
        {
            Save-WordDocument -WordDocument $wordDoc -FilePath "$($Directory)\AD Assessment Report.docx" -ea Stop
            $n = $log | Write-Log -Line "Output report written." -Level Info
        }
        catch
        {
            $n = $log | Write-Log -Line "Output report failed to write." -Level Error
            $n = $log | Write-Log -Line "failure message: $($_.ErrorDetails.Message)" -Level Error
        }
        
        #validate output report is strill on the disk
        
        if (Test-Path -Path "$($Directory)\AD Assessment Report.docx" -PathType Leaf -IsValid)
        {
            $n = $log | Write-Log -Line "Output report written and file exists verified." -Level Info
        }
        else
        {
            $n = $log | Write-Log -Line "Output report written but file verification failed test-path show file does not exist." -Level Error
        }
        
        $n = $log | Close-Log
    }
    End
    {
        
    }
}