Admin Report PowerShell Script

This is one of my semi-recent projects, and I really love what I ended up with. At its core, it is a script that recursively checks for all users in every administrator group in every domain in an Active Directory Forest, then generates a report in HTML and sends it out. Because where I work has multiple forests for development, staging, and production, I made it domain agnostic, as I like to call it. It will figure out the domain and all child domains and loop through them. Figuring out how to put the HTML together was fun. Sticking one language inside another, each with different escape characters, what could go wrong?
If I were to spend a bit more time, instead of simply sorting the domain names descending, I would identify the root domain by length since all child domains would be longer than the root. It just so happened that our root domain was last, so reversing the sort made it first, but that wouldn't be the case everywhere.

<#

.DESCRIPTION

Produces an e-mail summary of members of key administrator groups: Schema Admins, Enterprise Admins, Domain Admins, and Exchange Organization Admins. It takes no parameters, please update smpt server and email addresses in the script.

 

.EXAMPLE

.\AdminReport.ps1

 

.NOTES

Written by Rhys Ferris

 

Find me on:

LinkedIn: https://www.linkedin.com/in/rhys-ferris-2009ba138/

My Site : https://rhysferris.tech

 

License:

 

The MIT License (MIT)

 

Copyright (c) 2023 Rhys Ferris

 

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

 

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

 

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.

#>

 

Import-Module ActiveDirectory

$Forest = Get-ADForest

$domains = $Forest.Domains | sort -Descending

$output = "<H1>Administrator Report</H1><p>This email contains a wide chart and it may be difficult to view in the reading pane<br>The LastLogonDate field is an estimate and may be inaccurate up to 14 days. This is <a href=`"https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/8220-the-lastlogontimestamp-attribute-8221-8211-8220-what-it-was/ba-p/396204`">intended behavior</a> by Microsoft.<br>Additionally, service accounts that are never logged into manually may not update this date.</p><h2>Domain: $($Domains[0])</h2>"

$Groups = @("Schema Admins","Enterprise Admins","Exchange Organization Administrators")

$Attachments = @()

foreach ($Group in $Groups){

    $Members = @()

    $members += Get-ADGroup $Group -Server $domains[0] | Get-ADGroupMember -Recursive | Get-ADUser -Properties UserPrincipalName,LastLogonDate -ErrorAction SilentlyContinue

    $members = $Members | Sort-Object -Property Name

    $temp = $members | select Name,Enabled,LastLogonDate,DistinguishedName  | Format-Table -AutoSize | Out-String -Width 10000

    $output += "<h3>$Group</h3>"

    if ($members.count -eq 0){

        $output+="<p>None</p>"

    }else{

        $output += "<pre>$temp</pre>"

        $Members | Select Name,Enabled,LastLogonDate,DistinguishedName | Export-Csv -NoTypeInformation -Path "C:\UTILITIES\AuditAccounts\AdminGroupMembers\$($Domains[0]) - $Group Members.csv"

        $Attachments += "C:\UTILITIES\AuditAccounts\AdminGroupMembers\$($Domains[0]) - $Group Members.csv"

    }

}

foreach ($domain in $domains){

    $Members = @()

    $members += Get-ADGroup "Domain Admins" -Server $domain | Get-ADGroupMember -Recursive | Get-ADUser -Properties UserPrincipalName,LastLogondate -ErrorAction SilentlyContinue

    $members = $Members | Sort-Object -Property Name

    $temp = $Members | select Name,Enabled,LastLogonDate,DistinguishedName  | Format-Table -AutoSize | Out-String -Width 10000

    if($domain -ne $Domains[0]){

        $output += "<h2>Domain: $domain</h2>"

    }

    $output += "<H3>Domain Admins</H3>"

    if ($Members.Count -eq 0){

        $output+="<p>None</p>"

    }else{

        $output += "<pre>$temp</pre>"

        $members | Select Name,Enabled,LastLogonDate,DistinguishedName | Export-Csv -NoTypeInformation -Path "C:\UTILITIES\AuditAccounts\AdminGroupMembers\$Domain - Domain Admins Members.csv"

        $Attachments += "C:\UTILITIES\AuditAccounts\AdminGroupMembers\$Domain - Domain Admins Members.csv"

    }

}

$output += "<p>This script is hosted on A-ESS-USS-DC1</p>"

Send-MailMessage -To "you@email.example.com", “Them@email.example.com” -From "Admin Group Report <NoReply@email.example.com>" -Subject "Administrator Report ($($Domains[0]))" -Body $output -BodyAsHtml -SmtpServer mail.example.com -Attachments $Attachments

Remove-Item $Attachments