Create an Interactive Active Directory HTML Report With PowerShell
Table of Contents
I have covered the PowerShell module, “ReportHTML” in a previous article (Create an Interactive HTML Report for Office 365 with PowerShell) where I used it to generate Office 365 tenant reports. The module takes a little bit to learn the syntax and formatting but it’s great if you are not familiar with CSS/HTML as it does most of the heavy lifting for you. I like to generate reports using HTML because the data can be interacted with. You can filter your tables, search for items, change the ordering of the table, and also gather your data in bar and pie graphs.
My end goal was to create an Active Directory overview report using PowerShell. I looked into PSWinDocumentation but ultimately I wanted the report be interactive. I was looking for basic Active Directory items like Groups, Users, Group Types, Group Policy, etc, but I also wanted items like expiring accounts, users whose passwords will be expiring soon, newly modified AD Objects, and so on. Then I could get this report automatically e-mailed to me daily (or weekly) and I can see what has changed in my environment, and which users I need to make sure change their password soon.
An overview report like this is also valuable to managed service providers as they can quickly and easily understand a new clients environment, as well as show the customer their own environment.
While I walk you through the report, you can view it for yourself here
Below is a screenshot of the Groups tab in the report. Since the report is in HTML you can go to the Active Directory Groups table and search for an item and it will filter the table in real time. If you click the header, “Type” it will order the table by group type instead of name. The pie charts at the bottom can also be interacted with. When you hover over a pie chart it will display the value and count. So if you hovered over the purple portion in Group Membership, it will display “With Members: 18” so I know I have 18 groups that have members.
Report Features
Pie Charts
The Pie Charts will show you the value, and the count of what you are hovering over.
Search
In the top right corner of my table I can search my table for items. Below I just want to see all results with “Brad”
Header Ordering
By clicking on a different header I can change the sorting of the data. Here I change the data to order it by “Enabled” status, then “Protected from Deletion” and finally “Name”.
Report Overview
Dashboard
The Dashboard gives me a quick overview on the entire Active Directory environment. I can see the FSMO role holders, AD Recycle bin status, and all valid UPN suffixes. It also displays membership for Domain and Enterprise Admin groups, and any objects in the default Computers or Users OU. The next table displays every AD Object that has been modified in the last “X” days. You can change the amount of days by changing the variable at the start of the script. I can also see users that have not recently logged on as well as new user accounts that have been created. The Security Logs table display all logs regarding logons.
Groups
As shown earlier, the Groups report displays all of my Groups, membership for Domain and Enterprise admins and more. The bottom pie charts are dynamic and can be interacted with within the report itself.
Organizational Units
The OU tab will display all of my OU’s, modification date, protection from accidental deletion, and any linked Group Policy Objects to that OU. The pie charts below provide a glance at OU’s with GPO links as well as OU’s that are protected from accidental deletion.
Users
The Users report is very detailed, providing an in-depth look at your users and their account health. Right away you can view the total amount of users, users with passwords expiring soon, any expiring accounts, and users that have not logged on recently. The amount of days for each item (password expiring in less than X days) can be easily changed in the beginning of the script.
The Active Directory Users table shows you all of your users and some of the most important user attributes. The next 4 tables will then display expiring password users, expiring accounts, inactive users, and newly created user accounts.
Group Policy
For the Group Policy report, you will see all of your Group Policy objects, their status, modification date, and user and computer versions.
Computers
The Computers report gives you a similar overview as the Users report. Here you can see the amount of computer objects in your environment, as well as the break down for computers operating systems. In my example environment I have a lot of Windows 10 clients and more Server 2012 servers than 2016.
The 2 pie graphs below display the protection status from accidental deletion and enabled computers vs disabled. The last graph will give you a breakdown on the operating systems found in your environment. Here you can visually see how many Windows 10 devices compared to other operating systems are in my environment.
Script Overview
You can copy or download the script, and run it on any computer/server with RSAT or Active Directory right out of the box! But, I will explain the 1 module it uses as well as variables you can set if you want to change it to best fit your needs.
Modules
The script requires the ReportHTML module to be installed. It will attempt to install the module if it does not detect it by running install-module. You can also install it manually by running Install-Module ReportHTML in an administrative PowerShell console.
Variables
Unfortunately since I haven’t made this script into a function with parameters (yet!), some items are set using variables at the start of the script.
- CompanyLogo: Logo that will be in the top left corner of the report
- RightLogo: Logo that will be in the top right corner of the report
- ReportTitle: The title for the report
- ReportSavePath: Where the report will save to
- Days: Sets the days for “Find users that have not logged on in X amount of days”
- UserCreatedDays: Sets the days for “Get users who have been created in X amount of days or less”
- DayUntilPWExpireINT: Sets the days for “Get users whose passwords expire in less than X days”
- ADNumber: Sets the days for “Get AD Objects that have been modified in X days or less”
Active Directory
Since the script heavily relies on Active Directory, you will need to run it on a device with RSAT (as it gives you the Active Directory module) or domain controller. You just need the Active Directory module to be present on the system that its ran on.
Download / Source Code
You can find the source code either below or on GitHub! On GitHub you can put in feature requests, bugs/issues, and monitor when the code gets updated.
You can also copy the code below
<# .SYNOPSIS Generate graphed report for all Active Directory objects. .DESCRIPTION Generate graphed report for all Active Directory objects. .PARAMETER CompanyLogo Enter URL or UNC path to your desired Company Logo for generated report. -CompanyLogo "\\Server01\Admin\Files\CompanyLogo.png" .PARAMETER RightLogo Enter URL or UNC path to your desired right-side logo for generated report. -RightLogo "https://www.psmpartners.com/wp-content/uploads/2017/10/porcaro-stolarek-mete.png" .PARAMETER ReportTitle Enter desired title for generated report. -ReportTitle "Active Directory Report" .PARAMETER Days Users that have not logged in within [X] amount of days. -Days "1" .PARAMETER UserCreatedDays Users that have been created within [X] amount of days. -UserCreatedDays "7" .PARAMETER DaysUntilPWExpireINT Users password expires within [X] amount of days -DaysUntilPWExpireINT "7" .PARAMETER ADModNumber Active Directory Objects that have been modified within [X] amount of days. -ADModNumber "3" .NOTES Author: Bradley Wyatt Date: 12/4/2018 Modified: JBear 12/5/2018 Bradley Wyatt 12/5/2018 #> param ( #Company logo that will be displayed on the left, can be URL or UNC [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter URL or UNC path to Company Logo")] [String]$CompanyLogo = "", #Logo that will be on the right side, UNC or URL [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter URL or UNC path for Side Logo")] [String]$RightLogo = "https://www.psmpartners.com/wp-content/uploads/2017/10/porcaro-stolarek-mete.png", #Title of generated report [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter desired title for report")] [String]$ReportTitle = "Active Directory Report", #Location the report will be saved to [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter desired directory path to save; Default: C:\Automation\")] [String]$ReportSavePath = "C:\Automation\", #Find users that have not logged in X Amount of days, this sets the days [Parameter(ValueFromPipeline = $true, HelpMessage = "Users that have not logged in within [X] amount of days; Default: 1")] $Days = 1, #Get users who have been created in X amount of days and less [Parameter(ValueFromPipeline = $true, HelpMessage = "Users that have been created within [X] amount of days; Default: 7")] $UserCreatedDays = 7, #Get users whos passwords expire in less than X amount of days [Parameter(ValueFromPipeline = $true, HelpMessage = "Users password expires within [X] amount of days; Default: 7")] $DaysUntilPWExpireINT = 7, #Get AD Objects that have been modified in X days and newer [Parameter(ValueFromPipeline = $true, HelpMessage = "AD Objects that have been modified within [X] amount of days; Default: 3")] $ADModNumber = 3 #CSS template located C:\Program Files\WindowsPowerShell\Modules\ReportHTML\1.4.1.1\ #Default template is orange and named "Sample" ) Write-Host "Gathering Report Customization..." -ForegroundColor White Write-Host "__________________________________" -ForegroundColor White (Write-Host -NoNewline "Company Logo (left): " -ForegroundColor Yellow),(Write-Host $CompanyLogo -ForegroundColor White) (Write-Host -NoNewline "Company Logo (right): " -ForegroundColor Yellow),(Write-Host $RightLogo -ForegroundColor White) (Write-Host -NoNewline "Report Title: " -ForegroundColor Yellow),(Write-Host $ReportTitle -ForegroundColor White) (Write-Host -NoNewline "Report Save Path: " -ForegroundColor Yellow),(Write-Host $ReportSavePath -ForegroundColor White) (Write-Host -NoNewline "Amount of Days from Last User Logon Report: " -ForegroundColor Yellow),(Write-Host $Days -ForegroundColor White) (Write-Host -NoNewline "Amount of Days for New User Creation Report: " -ForegroundColor Yellow),(Write-Host $UserCreatedDays -ForegroundColor White) (Write-Host -NoNewline "Amount of Days for User Password Expiration Report: " -ForegroundColor Yellow),(Write-Host $DaysUntilPWExpireINT -ForegroundColor White) (Write-Host -NoNewline "Amount of Days for Newly Modified AD Objects Report: " -ForegroundColor Yellow), (Write-Host $ADModNumber -ForegroundColor White) Write-Host "__________________________________" -ForegroundColor White function LastLogonConvert ($ftDate) { $Date = [DateTime]::FromFileTime($ftDate) if ($Date -lt (Get-Date '1/1/1900') -or $date -eq 0 -or $date -eq $null) { "Never" } else { $Date } } #End function LastLogonConvert #Check for ReportHTML Module $Mod = Get-Module -ListAvailable -Name "ReportHTML" If ($null -eq $Mod) { Write-Host "ReportHTML Module is not present, attempting to install it" Install-Module -Name ReportHTML -Force Import-Module ReportHTML -ErrorAction SilentlyContinue } #Array of default Security Groups $DefaultSGs = @( "Access Control Assistance Operators" "Account Operators" "Administrators" "Allowed RODC Password Replication Group" "Backup Operators" "Certificate Service DCOM Access" "Cert Publishers" "Cloneable Domain Controllers" "Cryptographic Operators" "Denied RODC Password Replication Group" "Distributed COM Users" "DnsUpdateProxy" "DnsAdmins" "Domain Admins" "Domain Computers" "Domain Controllers" "Domain Guests" "Domain Users" "Enterprise Admins" "Enterprise Key Admins" "Enterprise Read-only Domain Controllers" "Event Log Readers" "Group Policy Creator Owners" "Guests" "Hyper-V Administrators" "IIS_IUSRS" "Incoming Forest Trust Builders" "Key Admins" "Network Configuration Operators" "Performance Log Users" "Performance Monitor Users" "Print Operators" "Pre-Windows 2000 Compatible Access" "Protected Users" "RAS and IAS Servers" "RDS Endpoint Servers" "RDS Management Servers" "RDS Remote Access Servers" "Read-only Domain Controllers" "Remote Desktop Users" "Remote Management Users" "Replicator" "Schema Admins" "Server Operators" "Storage Replica Administrators" "System Managed Accounts Group" "Terminal Server License Servers" "Users" "Windows Authorization Access Group" "WinRMRemoteWMIUsers" ) $Table = New-Object 'System.Collections.Generic.List[System.Object]' $OUTable = New-Object 'System.Collections.Generic.List[System.Object]' $UserTable = New-Object 'System.Collections.Generic.List[System.Object]' $GroupTypetable = New-Object 'System.Collections.Generic.List[System.Object]' $DefaultGrouptable = New-Object 'System.Collections.Generic.List[System.Object]' $EnabledDisabledUsersTable = New-Object 'System.Collections.Generic.List[System.Object]' $DomainAdminTable = New-Object 'System.Collections.Generic.List[System.Object]' $ExpiringAccountsTable = New-Object 'System.Collections.Generic.List[System.Object]' $CompanyInfoTable = New-Object 'System.Collections.Generic.List[System.Object]' $securityeventtable = New-Object 'System.Collections.Generic.List[System.Object]' $DomainTable = New-Object 'System.Collections.Generic.List[System.Object]' $OUGPOTable = New-Object 'System.Collections.Generic.List[System.Object]' $GroupMembershipTable = New-Object 'System.Collections.Generic.List[System.Object]' $PasswordExpirationTable = New-Object 'System.Collections.Generic.List[System.Object]' $PasswordExpireSoonTable = New-Object 'System.Collections.Generic.List[System.Object]' $userphaventloggedonrecentlytable = New-Object 'System.Collections.Generic.List[System.Object]' $EnterpriseAdminTable = New-Object 'System.Collections.Generic.List[System.Object]' $NewCreatedUsersTable = New-Object 'System.Collections.Generic.List[System.Object]' $GroupProtectionTable = New-Object 'System.Collections.Generic.List[System.Object]' $OUProtectionTable = New-Object 'System.Collections.Generic.List[System.Object]' $GPOTable = New-Object 'System.Collections.Generic.List[System.Object]' $ADObjectTable = New-Object 'System.Collections.Generic.List[System.Object]' $ProtectedUsersTable = New-Object 'System.Collections.Generic.List[System.Object]' $ComputersTable = New-Object 'System.Collections.Generic.List[System.Object]' $ComputerProtectedTable = New-Object 'System.Collections.Generic.List[System.Object]' $ComputersEnabledTable = New-Object 'System.Collections.Generic.List[System.Object]' $DefaultComputersinDefaultOUTable = New-Object 'System.Collections.Generic.List[System.Object]' $DefaultUsersinDefaultOUTable = New-Object 'System.Collections.Generic.List[System.Object]' $TOPUserTable = New-Object 'System.Collections.Generic.List[System.Object]' $TOPGroupsTable = New-Object 'System.Collections.Generic.List[System.Object]' $TOPComputersTable = New-Object 'System.Collections.Generic.List[System.Object]' $GraphComputerOS = New-Object 'System.Collections.Generic.List[System.Object]' #Get all users right away. Instead of doing several lookups, we will use this object to look up all the information needed. $AllUsers = Get-ADUser -Filter * -Properties * $GPOs = Get-GPO -All | Select-Object DisplayName, GPOStatus, ModificationTime, @{ Label = "ComputerVersion"; Expression = { $_.computer.dsversion } }, @{ Label = "UserVersion"; Expression = { $_.user.dsversion } } <########################### Dashboard ############################> Write-Host "Working on Dashboard Report..." -ForegroundColor Green $dte = (Get-Date).AddDays(- $ADModNumber) $ADObjs = Get-ADObject -Filter { whenchanged -gt $dte -and ObjectClass -ne "domainDNS" -and ObjectClass -ne "rIDManager" -and ObjectClass -ne "rIDSet" } -Properties * foreach ($ADObj in $ADObjs) { if ($ADObj.ObjectClass -eq "GroupPolicyContainer") { $Name = $ADObj.DisplayName } else { $Name = $ADObj.Name } $obj = [PSCustomObject]@{ 'Name' = $Name 'Object Type' = $ADObj.ObjectClass 'When Changed' = $ADObj.WhenChanged } $ADObjectTable.Add($obj) } $ADRecycleBinStatus = (Get-ADOptionalFeature -Filter 'name -like "Recycle Bin Feature"').EnabledScopes if ($ADRecycleBinStatus.Count -lt 1) { $ADRecycleBin = "Disabled" } else { $ADRecycleBin = "Enabled" } #Company Information $ADInfo = Get-ADDomain $ForestObj = Get-ADForest $DomainControllerobj = Get-ADDomain $Forest = $ADInfo.Forest $InfrastructureMaster = $DomainControllerobj.InfrastructureMaster $RIDMaster = $DomainControllerobj.RIDMaster $PDCEmulator = $DomainControllerobj.PDCEmulator $DomainNamingMaster = $ForestObj.DomainNamingMaster $SchemaMaster = $ForestObj.SchemaMaster $obj = [PSCustomObject]@{ 'Domain' = $Forest 'AD Recycle Bin' = $ADRecycleBin 'Infrastructure Master' = $InfrastructureMaster 'RID Master' = $RIDMaster 'PDC Emulator' = $PDCEmulator 'Domain Naming Master' = $DomainNamingMaster 'Schema Master' = $SchemaMaster } $CompanyInfoTable.Add($obj) #Get newly created users $When = ((Get-Date).AddDays(- $UserCreatedDays)).Date $NewUsers = $AllUsers | Where-Object { $_.whenCreated -ge $When } foreach ($Newuser in $Newusers) { $obj = [PSCustomObject]@{ 'Name' = $Newuser.Name 'Enabled' = $Newuser.Enabled 'Creation Date' = $Newuser.whenCreated } $NewCreatedUsersTable.Add($obj) } #Get Domain Admins $DomainAdminMembers = Get-ADGroupMember "Domain Admins" foreach ($DomainAdminMember in $DomainAdminMembers) { $Name = $DomainAdminMember.Name $Type = $DomainAdminMember.ObjectClass $Enabled = ($AllUsers | Where-Object { $_.Name -eq $Name }).Enabled $obj = [PSCustomObject]@{ 'Name' = $Name 'Enabled' = $Enabled 'Type' = $Type } $DomainAdminTable.Add($obj) } #Get Enterprise Admins $EnterpriseAdminsMembers = Get-ADGroupMember "Enterprise Admins" foreach ($EnterpriseAdminsMember in $EnterpriseAdminsMembers) { $Name = $EnterpriseAdminsMember.Name $Type = $EnterpriseAdminsMember.ObjectClass $Enabled = ($AllUsers | Where-Object { $_.Name -eq $Name }).Enabled $obj = [PSCustomObject]@{ 'Name' = $Name 'Enabled' = $Enabled 'Type' = $Type } $EnterpriseAdminTable.Add($obj) } $DefaultComputersOU = (Get-ADDomain).computerscontainer $DefaultComputers = Get-ADComputer -Filter * -Properties * -SearchBase "$DefaultComputersOU" foreach ($DefaultComputer in $DefaultComputers) { $obj = [PSCustomObject]@{ 'Name' = $DefaultComputer.Name 'Enabled' = $DefaultComputer.Enabled 'Operating System' = $DefaultComputer.OperatingSystem 'Modified Date' = $DefaultComputer.Modified 'Password Last Set' = $DefaultComputer.PasswordLastSet 'Protect from Deletion' = $DefaultComputer.ProtectedFromAccidentalDeletion } $DefaultComputersinDefaultOUTable.Add($obj) } $DefaultUsersOU = (Get-ADDomain).UsersContainer $DefaultUsers = $Allusers | Where-Object { $_.DistinguishedName -like "*$($DefaultUsersOU)" } | Select-Object Name, UserPrincipalName, Enabled, ProtectedFromAccidentalDeletion, EmailAddress, @{ Name = 'lastlogon'; Expression = { LastLogonConvert $_.lastlogon } }, DistinguishedName foreach ($DefaultUser in $DefaultUsers) { $obj = [PSCustomObject]@{ 'Name' = $DefaultUser.Name 'UserPrincipalName' = $DefaultUser.UserPrincipalName 'Enabled' = $DefaultUser.Enabled 'Protected from Deletion' = $DefaultUser.ProtectedFromAccidentalDeletion 'Last Logon' = $DefaultUser.LastLogon 'Email Address' = $DefaultUser.EmailAddress } $DefaultUsersinDefaultOUTable.Add($obj) } #Expiring Accounts $LooseUsers = Search-ADAccount -AccountExpiring -UsersOnly foreach ($LooseUser in $LooseUsers) { $NameLoose = $LooseUser.Name $UPNLoose = $LooseUser.UserPrincipalName $ExpirationDate = $LooseUser.AccountExpirationDate $enabled = $LooseUser.Enabled $obj = [PSCustomObject]@{ 'Name' = $NameLoose 'UserPrincipalName' = $UPNLoose 'Expiration Date' = $ExpirationDate 'Enabled' = $enabled } $ExpiringAccountsTable.Add($obj) } if (($ExpiringAccountsTable).Count -eq 0) { $ExpiringAccountsTable = [PSCustomObject]@{ Information = 'Information: No Users were found to expire soon' } } #Security Logs $SecurityLogs = Get-EventLog -Newest 7 -LogName "Security" | Where-Object { $_.Message -like "*An account*" } foreach ($SecurityLog in $SecurityLogs) { $TimeGenerated = $SecurityLog.TimeGenerated $EntryType = $SecurityLog.EntryType $Recipient = $SecurityLog.Message $obj = [PSCustomObject]@{ 'Time' = $TimeGenerated 'Type' = $EntryType 'Message' = $Recipient } $SecurityEventTable.Add($obj) } if (($securityeventtable).Count -eq 0) { $securityeventtable = [PSCustomObject]@{ Information = 'Information: No recent security logs' } } #Tenant Domain $Domains = Get-ADForest | Select-Object -ExpandProperty upnsuffixes | ForEach-Object{ $obj = [PSCustomObject]@{ 'UPN Suffixes' = $_ Valid = "True" } $DomainTable.Add($obj) } Write-Host "Done!" -ForegroundColor White <########################### Groups ############################> Write-Host "Working on Groups Report..." -ForegroundColor Green #Get groups and sort in alphabetical order $Groups = Get-ADGroup -Filter * -Properties * $SecurityCount = 0 $MailSecurityCount = 0 $CustomGroup = 0 $DefaultGroup = 0 $Groupswithmemebrship = 0 $Groupswithnomembership = 0 $GroupsProtected = 0 $GroupsNotProtected = 0 foreach ($Group in $Groups) { $DefaultADGroup = 'False' $Type = New-Object 'System.Collections.Generic.List[System.Object]' $Gemail = (Get-ADGroup $Group -Properties mail).mail if (($group.GroupCategory -eq "Security") -and ($Gemail -ne $Null)) { $MailSecurityCount++ } if (($group.GroupCategory -eq "Security") -and (($Gemail) -eq $Null)) { $SecurityCount++ } if ($Group.ProtectedFromAccidentalDeletion -eq $True) { $GroupsProtected++ } else { $GroupsNotProtected++ } if ($DefaultSGs -contains $Group.Name) { $DefaultADGroup = "True" $DefaultGroup++ } else { $CustomGroup++ } if ($group.GroupCategory -eq "Distribution") { $Type = "Distribution Group" } if (($group.GroupCategory -eq "Security") -and (($Gemail) -eq $Null)) { $Type = "Security Group" } if (($group.GroupCategory -eq "Security") -and (($Gemail) -ne $Null)) { $Type = "Mail-Enabled Security Group" } if ($Group.Name -ne "Domain Users") { $Users = (Get-ADGroupMember -Identity $Group | Sort-Object DisplayName | Select-Object -ExpandProperty Name) -join ", " if (!($Users)) { $Groupswithnomembership++ } else { $Groupswithmemebrship++ } } else { $Users = "Skipped Domain Users Membership" } $OwnerDN = Get-ADGroup -Filter { name -eq $Group.Name } -Properties managedBy | Select-Object -ExpandProperty ManagedBy $Manager = $AllUsers | Where-Object { $_.distinguishedname -eq $OwnerDN } | Select-Object -ExpandProperty Name $obj = [PSCustomObject]@{ 'Name' = $Group.name 'Type' = $Type 'Members' = $users 'Managed By' = $Manager 'E-mail Address' = $GEmail 'Protected from Deletion' = $Group.ProtectedFromAccidentalDeletion 'Default AD Group' = $DefaultADGroup } $table.Add($obj) } if (($table).Count -eq 0) { $table = [PSCustomObject]@{ Information = 'Information: No Groups were found' } } #TOP groups table $obj1 = [PSCustomObject]@{ 'Total Groups' = $Groups.Count 'Mail-Enabled Security Groups' = $MailSecurityCount 'Security Groups' = $SecurityCount 'Distribution Groups' = $DistroCount } $TOPGroupsTable.Add($obj1) $obj1 = [PSCustomObject]@{ 'Name' = 'Mail-Enabled Security Groups' 'Count' = $MailSecurityCount } $GroupTypetable.Add($obj1) $obj1 = [PSCustomObject]@{ 'Name' = 'Security Groups' 'Count' = $SecurityCount } $GroupTypetable.Add($obj1) $DistroCount = ($Groups | Where-Object { $_.GroupCategory -eq "Distribution" }).Count $obj1 = [PSCustomObject]@{ 'Name' = 'Distribution Groups' 'Count' = $DistroCount } $GroupTypetable.Add($obj1) #Default Group Pie Chart $obj1 = [PSCustomObject]@{ 'Name' = 'Default Groups' 'Count' = $DefaultGroup } $DefaultGrouptable.Add($obj1) $obj1 = [PSCustomObject]@{ 'Name' = 'Custom Groups' 'Count' = $CustomGroup } $DefaultGrouptable.Add($obj1) #Group Protection Pie Chart $obj1 = [PSCustomObject]@{ 'Name' = 'Protected' 'Count' = $GroupsProtected } $GroupProtectionTable.Add($obj1) $obj1 = [PSCustomObject]@{ 'Name' = 'Not Protected' 'Count' = $GroupsNotProtected } $GroupProtectionTable.Add($obj1) #Groups with membership vs no membership pie chart $objmem = [PSCustomObject]@{ 'Name' = 'With Members' 'Count' = $Groupswithmemebrship } $GroupMembershipTable.Add($objmem) $objmem = [PSCustomObject]@{ 'Name' = 'No Members' 'Count' = $Groupswithnomembership } $GroupMembershipTable.Add($objmem) Write-Host "Done!" -ForegroundColor White <########################### Organizational Units ############################> Write-Host "Working on Organizational Units Report..." -ForegroundColor Green #Get all OUs' $OUs = Get-ADOrganizationalUnit -Filter * -Properties * $OUwithLinked = 0 $OUwithnoLink = 0 $OUProtected = 0 $OUNotProtected = 0 foreach ($OU in $OUs) { $LinkedGPOs = New-Object 'System.Collections.Generic.List[System.Object]' if (($OU.linkedgrouppolicyobjects).length -lt 1) { $LinkedGPOs = "None" $OUwithnoLink++ } else { $OUwithLinked++ $GPOslinks = $OU.linkedgrouppolicyobjects foreach ($GPOlink in $GPOslinks) { $Split1 = $GPOlink -split "{" | Select-Object -Last 1 $Split2 = $Split1 -split "}" | Select-Object -First 1 $LinkedGPOs.Add((Get-GPO -Guid $Split2 -ErrorAction SilentlyContinue).DisplayName) } } if ($OU.ProtectedFromAccidentalDeletion -eq $True) { $OUProtected++ } else { $OUNotProtected++ } $LinkedGPOs = $LinkedGPOs -join ", " $obj = [PSCustomObject]@{ 'Name' = $OU.Name 'Linked GPOs' = $LinkedGPOs 'Modified Date' = $OU.WhenChanged 'Protected from Deletion' = $OU.ProtectedFromAccidentalDeletion } $OUTable.Add($obj) } if (($OUTable).Count -eq 0) { $OUTable = [PSCustomObject]@{ Information = 'Information: No Organizational Units were found' } } #OUs with no GPO Linked $obj1 = [PSCustomObject]@{ 'Name' = "OUs with no GPO's linked" 'Count' = $OUwithnoLink } $OUGPOTable.Add($obj1) $obj2 = [PSCustomObject]@{ 'Name' = "OUs with GPO's linked" 'Count' = $OUwithLinked } $OUGPOTable.Add($obj2) #OUs Protected Pie Chart $obj1 = [PSCustomObject]@{ 'Name' = "Protected" 'Count' = $OUProtected } $OUProtectionTable.Add($obj1) $obj2 = [PSCustomObject]@{ 'Name' = "Not Protected" 'Count' = $OUNotProtected } $OUProtectionTable.Add($obj2) Write-Host "Done!" -ForegroundColor White <########################### USERS ############################> Write-Host "Working on Users Report..." -ForegroundColor Green $UserEnabled = 0 $UserDisabled = 0 $UserPasswordExpires = 0 $UserPasswordNeverExpires = 0 $ProtectedUsers = 0 $NonProtectedUsers = 0 $UsersWIthPasswordsExpiringInUnderAWeek = 0 $UsersNotLoggedInOver30Days = 0 $AccountsExpiringSoon = 0 foreach ($User in $AllUsers) { $AttVar = $User | Select-Object Enabled, PasswordExpired, PasswordLastSet, PasswordNeverExpires, PasswordNotRequired, Name, SamAccountName, EmailAddress, AccountExpirationDate, @{ Name = 'lastlogon'; Expression = { LastLogonConvert $_.lastlogon } }, DistinguishedName $maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge if ((($AttVar.PasswordNeverExpires) -eq $False) -and (($AttVar.Enabled) -ne $false)) { #Get Password last set date $passwordSetDate = ($User | ForEach-Object { $_.PasswordLastSet }) if ($null -eq $passwordSetDate) { $daystoexpire = "User has never logged on" } else { #Check for Fine Grained Passwords $PasswordPol = (Get-ADUserResultantPasswordPolicy $user) if (($PasswordPol) -ne $null) { $maxPasswordAge = ($PasswordPol).MaxPasswordAge } $expireson = $passwordsetdate + $maxPasswordAge $today = (Get-Date) #Gets the count on how many days until the password expires and stores it in the $daystoexpire var $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days } } else { $daystoexpire = "N/A" } #Get users that haven't logged on in X amount of days, var is set at start of script if (($User.Enabled -eq $True) -and ($User.LastLogonDate -lt (Get-Date).AddDays(- $Days)) -and ($User.LastLogonDate -ne $NULL)) { $obj = [PSCustomObject]@{ 'Name' = $User.Name 'UserPrincipalName' = $User.UserPrincipalName 'Enabled' = $AttVar.Enabled 'Protected from Deletion' = $User.ProtectedFromAccidentalDeletion 'Last Logon' = $AttVar.lastlogon 'Password Never Expires' = $AttVar.PasswordNeverExpires 'Days Until Password Expires' = $daystoexpire } $userphaventloggedonrecentlytable.Add($obj) } if (($userphaventloggedonrecentlytable).Count -eq 0) { $userphaventloggedonrecentlytable = [PSCustomObject]@{ Information = "Information: No Users were found to have not logged on in $Days days" } } #Items for protected vs non protected users if ($User.ProtectedFromAccidentalDeletion -eq $False) { $NonProtectedUsers++ } else { $ProtectedUsers++ } #Items for the enabled vs disabled users pie chart if (($AttVar.PasswordNeverExpires) -ne $false) { $UserPasswordNeverExpires++ } else { $UserPasswordExpires++ } #Items for password expiration pie chart if (($AttVar.Enabled) -ne $false) { $UserEnabled++ } else { $UserDisabled++ } $Name = $User.Name $UPN = $User.UserPrincipalName $Enabled = $AttVar.Enabled $EmailAddress = $AttVar.EmailAddress $AccountExpiration = $AttVar.AccountExpirationDate $PasswordExpired = $AttVar.PasswordExpired $PasswordLastSet = $AttVar.PasswordLastSet $PasswordNeverExpires = $AttVar.PasswordNeverExpires $daysUntilPWExpire = $daystoexpire $obj = [PSCustomObject]@{ 'Name' = $Name 'UserPrincipalName' = $UPN 'Enabled' = $Enabled 'Protected from Deletion' = $User.ProtectedFromAccidentalDeletion 'Last Logon' = $LastLogon 'Email Address' = $EmailAddress 'Account Expiration' = $AccountExpiration 'Change Password Next Logon' = $PasswordExpired 'Password Last Set' = $PasswordLastSet 'Password Never Expires' = $PasswordNeverExpires 'Days Until Password Expires' = $daystoexpire } $usertable.Add($obj) if ($daystoexpire -lt $DaysUntilPWExpireINT) { $obj = [PSCustomObject]@{ 'Name' = $Name 'Days Until Password Expires' = $daystoexpire } $PasswordExpireSoonTable.Add($obj) } } if (($usertable).Count -eq 0) { $usertable = [PSCustomObject]@{ Information = 'Information: No Users were found' } } #Data for users enabled vs disabled pie graph $objULic = [PSCustomObject]@{ 'Name' = 'Enabled' 'Count' = $UserEnabled } $EnabledDisabledUsersTable.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = 'Disabled' 'Count' = $UserDisabled } $EnabledDisabledUsersTable.Add($objULic) #Data for users password expires pie graph $objULic = [PSCustomObject]@{ 'Name' = 'Password Expires' 'Count' = $UserPasswordExpires } $PasswordExpirationTable.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = 'Password Never Expires' 'Count' = $UserPasswordNeverExpires } $PasswordExpirationTable.Add($objULic) #Data for protected users pie graph $objULic = [PSCustomObject]@{ 'Name' = 'Protected' 'Count' = $ProtectedUsers } $ProtectedUsersTable.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = 'Not Protected' 'Count' = $NonProtectedUsers } $ProtectedUsersTable.Add($objULic) #TOP User table If (($ExpiringAccountsTable).Count -gt 0) { $objULic = [PSCustomObject]@{ 'Total Users' = $AllUsers.Count "Users with Passwords Expiring in less than $DaysUntilPWExpireINT days" = $PasswordExpireSoonTable.Count 'Expiring Accounts' = $ExpiringAccountsTable.Count "Users Haven't Logged on in $Days Days" = $userphaventloggedonrecentlytable.Count } $TOPUserTable.Add($objULic) } Else { $objULic = [PSCustomObject]@{ 'Total Users' = $AllUsers.Count "Users with Passwords Expiring in less than $DaysUntilPWExpireINT days" = $PasswordExpireSoonTable.Count 'Expiring Accounts' = "0" "Users Haven't Logged on in $Days Days" = $userphaventloggedonrecentlytable.Count } $TOPUserTable.Add($objULic) } Write-Host "Done!" -ForegroundColor White <########################### Group Policy ############################> Write-Host "Working on Group Policy Report..." -ForegroundColor Green $GPOTable = New-Object 'System.Collections.Generic.List[System.Object]' foreach ($GPO in $GPOs) { $obj = [PSCustomObject]@{ 'Name' = $GPO.DisplayName 'Status' = $GPO.GpoStatus 'Modified Date' = $GPO.ModificationTime 'User Version' = $GPO.UserVersion 'Computer Version' = $GPO.ComputerVersion } $GPOTable.Add($obj) } Write-Host "Done!" -ForegroundColor White <########################### Computers ############################> Write-Host "Working on Computers Report..." -ForegroundColor Green $Computers = Get-ADComputer -Filter * -Properties * $ComputersProtected = 0 $ComputersNotProtected = 0 $ComputerEnabled = 0 $ComputerDisabled = 0 $Server2016 = 0 $Server2012 = 0 $Server2012R2 = 0 $Server2008R2 = 0 $Windows10 = 0 $Windows8 = 0 $Windows7 = 0 $Server2012R2 = 0 foreach ($Computer in $Computers) { if ($Computer.ProtectedFromAccidentalDeletion -eq $True) { $ComputersProtected++ } else { $ComputersNotProtected++ } if ($Computer.Enabled -eq $True) { $ComputerEnabled++ } else { $ComputerDisabled++ } $obj = [PSCustomObject]@{ 'Name' = $Computer.Name 'Enabled' = $Computer.Enabled 'Operating System' = $Computer.OperatingSystem 'Modified Date' = $Computer.Modified 'Password Last Set' = $Computer.PasswordLastSet 'Protect from Deletion' = $Computer.ProtectedFromAccidentalDeletion } $ComputersTable.Add($obj) if ($Computer.OperatingSystem -like "*Server 2016*") { $Server2016++ } elseif ($Computer.OperatingSystem -like "*Server 2012 R2*") { $Server2012R2++ } elseif ($Computer.OperatingSystem -like "*Server 2012*") { $Server2012++ } elseif ($Computer.OperatingSystem -like "*Server 2008 R2*") { $Server2008R2++ } elseif ($Computer.OperatingSystem -like "*Windows 10*") { $Windows10++ } elseif ($Computer.OperatingSystem -like "*Windows 8*") { $Windows8++ } elseif ($Computer.OperatingSystem -like "*Windows 7*") { $Windows7++ } } If (($ComputersTable).Count -eq 0) { $ComputersTable = [PSCustomObject]@{ Information = 'Information: No Computers were found' } } #Data for TOP Computers data table $objULic = [PSCustomObject]@{ 'Total Computers' = $Computers.Count "Server 2016" = $Server2016 "Server 2012 R2" = $Server2012R2 "Server 2012" = $Server2012 "Server 2008 R2" = $Server2008R2 "Windows 10" = $Windows10 "Windows 8" = $Windows8 "Windows 7" = $Windows7 } $TOPComputersTable.Add($objULic) #Pie chart breaking down OS for computer obj $objULic = [PSCustomObject]@{ 'Name' = "Server 2016" "Count" = $Server2016 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Server 2012 R2" "Count" = $Server2012R2 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Server 2012" "Count" = $Server2012 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Server 2008 R2" "Count" = $Server2008R2 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Windows 10" "Count" = $Windows10 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Windows 8" "Count" = $Windows8 } $GraphComputerOS.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = "Windows 7" "Count" = $Windows7 } $GraphComputerOS.Add($objULic) #Data for protected Computers pie graph $objULic = [PSCustomObject]@{ 'Name' = 'Protected' 'Count' = $ComputerProtected } $ComputerProtectedTable.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = 'Not Protected' 'Count' = $ComputersNotProtected } $ComputerProtectedTable.Add($objULic) #Data for enabled/vs Computers pie graph $objULic = [PSCustomObject]@{ 'Name' = 'Enabled' 'Count' = $ComputerEnabled } $ComputersEnabledTable.Add($objULic) $objULic = [PSCustomObject]@{ 'Name' = 'Disabled' 'Count' = $ComputerDisabled } $ComputersEnabledTable.Add($objULic) Write-Host "Done!" -ForegroundColor White $tabarray = @('Dashboard', 'Groups', 'Organizational Units', 'Users', 'Group Policy', 'Computers') Write-Host "Compiling Report..." -ForegroundColor Green ##--OU Protection PIE CHART--## #Basic Properties $PO12 = Get-HTMLPieChartObject $PO12.Title = "Organizational Units Protected from Deletion" $PO12.Size.Height = 250 $PO12.Size.width = 250 $PO12.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PO12.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PO12.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PO12.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PO12.DataDefinition.DataNameColumnName = 'Name' $PO12.DataDefinition.DataValueColumnName = 'Count' ##--Computer OS Breakdown PIE CHART--## $PieObjectComputerObjOS = Get-HTMLPieChartObject $PieObjectComputerObjOS.Title = "Computer Operating Systems" #These file exist in the module directoy, There are 4 schemes by default $PieObjectComputerObjOS.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PieObjectComputerObjOS.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectComputerObjOS.ChartStyle.ColorSchemeName = 'Random' ##--Computers Protection PIE CHART--## #Basic Properties $PieObjectComputersProtected = Get-HTMLPieChartObject $PieObjectComputersProtected.Title = "Computers Protected from Deletion" $PieObjectComputersProtected.Size.Height = 250 $PieObjectComputersProtected.Size.width = 250 $PieObjectComputersProtected.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectComputersProtected.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PieObjectComputersProtected.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectComputersProtected.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectComputersProtected.DataDefinition.DataNameColumnName = 'Name' $PieObjectComputersProtected.DataDefinition.DataValueColumnName = 'Count' ##--Computers Enabled PIE CHART--## #Basic Properties $PieObjectComputersEnabled = Get-HTMLPieChartObject $PieObjectComputersEnabled.Title = "Computers Enabled vs Disabled" $PieObjectComputersEnabled.Size.Height = 250 $PieObjectComputersEnabled.Size.width = 250 $PieObjectComputersEnabled.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectComputersEnabled.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PieObjectComputersEnabled.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectComputersEnabled.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectComputersEnabled.DataDefinition.DataNameColumnName = 'Name' $PieObjectComputersEnabled.DataDefinition.DataValueColumnName = 'Count' ##--USERS Protection PIE CHART--## #Basic Properties $PieObjectProtectedUsers = Get-HTMLPieChartObject $PieObjectProtectedUsers.Title = "Users Protected from Deletion" $PieObjectProtectedUsers.Size.Height = 250 $PieObjectProtectedUsers.Size.width = 250 $PieObjectProtectedUsers.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectProtectedUsers.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PieObjectProtectedUsers.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectProtectedUsers.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectProtectedUsers.DataDefinition.DataNameColumnName = 'Name' $PieObjectProtectedUsers.DataDefinition.DataValueColumnName = 'Count' #Basic Properties $PieObjectOUGPOLinks = Get-HTMLPieChartObject $PieObjectOUGPOLinks.Title = "OU GPO Links" $PieObjectOUGPOLinks.Size.Height = 250 $PieObjectOUGPOLinks.Size.width = 250 $PieObjectOUGPOLinks.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectOUGPOLinks.ChartStyle.ColorSchemeName = "ColorScheme4" #There are 8 generated schemes, randomly generated at runtime $PieObjectOUGPOLinks.ChartStyle.ColorSchemeName = "Generated5" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectOUGPOLinks.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectOUGPOLinks.DataDefinition.DataNameColumnName = 'Name' $PieObjectOUGPOLinks.DataDefinition.DataValueColumnName = 'Count' #Basic Properties $PieObject4 = Get-HTMLPieChartObject $PieObject4.Title = "Office 365 Unassigned Licenses" $PieObject4.Size.Height = 250 $PieObject4.Size.width = 250 $PieObject4.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObject4.ChartStyle.ColorSchemeName = "ColorScheme4" #There are 8 generated schemes, randomly generated at runtime $PieObject4.ChartStyle.ColorSchemeName = "Generated4" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObject4.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObject4.DataDefinition.DataNameColumnName = 'Name' $PieObject4.DataDefinition.DataValueColumnName = 'Unassigned Licenses' #Basic Properties $PieObjectGroupType = Get-HTMLPieChartObject $PieObjectGroupType.Title = "Group Types" $PieObjectGroupType.Size.Height = 250 $PieObjectGroupType.Size.width = 250 $PieObjectGroupType.ChartStyle.ChartType = 'doughnut' #Pie Chart Groups with members vs no members $PieObjectGroupMembersType = Get-HTMLPieChartObject $PieObjectGroupMembersType.Title = "Group Membership" $PieObjectGroupMembersType.Size.Height = 250 $PieObjectGroupMembersType.Size.width = 250 $PieObjectGroupMembersType.ChartStyle.ChartType = 'doughnut' $PieObjectGroupMembersType.ChartStyle.ColorSchemeName = "ColorScheme4" $PieObjectGroupMembersType.ChartStyle.ColorSchemeName = "Generated8" $PieObjectGroupMembersType.ChartStyle.ColorSchemeName = 'Random' $PieObjectGroupMembersType.DataDefinition.DataNameColumnName = 'Name' $PieObjectGroupMembersType.DataDefinition.DataValueColumnName = 'Count' #Basic Properties $PieObjectGroupType2 = Get-HTMLPieChartObject $PieObjectGroupType2.Title = "Custom vs Default Groups" $PieObjectGroupType2.Size.Height = 250 $PieObjectGroupType2.Size.width = 250 $PieObjectGroupType2.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectGroupType.ChartStyle.ColorSchemeName = "ColorScheme4" #There are 8 generated schemes, randomly generated at runtime $PieObjectGroupType.ChartStyle.ColorSchemeName = "Generated8" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectGroupType.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectGroupType.DataDefinition.DataNameColumnName = 'Name' $PieObjectGroupType.DataDefinition.DataValueColumnName = 'Count' ##--Enabled users vs Disabled Users PIE CHART--## #Basic Properties $EnabledDisabledUsersPieObject = Get-HTMLPieChartObject $EnabledDisabledUsersPieObject.Title = "Enabled vs Disabled Users" $EnabledDisabledUsersPieObject.Size.Height = 250 $EnabledDisabledUsersPieObject.Size.width = 250 $EnabledDisabledUsersPieObject.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $EnabledDisabledUsersPieObject.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $EnabledDisabledUsersPieObject.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $EnabledDisabledUsersPieObject.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $EnabledDisabledUsersPieObject.DataDefinition.DataNameColumnName = 'Name' $EnabledDisabledUsersPieObject.DataDefinition.DataValueColumnName = 'Count' ##--PasswordNeverExpires PIE CHART--## #Basic Properties $PWExpiresUsersTable = Get-HTMLPieChartObject $PWExpiresUsersTable.Title = "Password Expiration" $PWExpiresUsersTable.Size.Height = 250 $PWExpiresUsersTable.Size.Width = 250 $PWExpiresUsersTable.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PWExpiresUsersTable.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PWExpiresUsersTable.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PWExpiresUsersTable.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PWExpiresUsersTable.DataDefinition.DataNameColumnName = 'Name' $PWExpiresUsersTable.DataDefinition.DataValueColumnName = 'Count' ##--Group Protection PIE CHART--## #Basic Properties $PieObjectGroupProtection = Get-HTMLPieChartObject $PieObjectGroupProtection.Title = "Groups Protected from Deletion" $PieObjectGroupProtection.Size.Height = 250 $PieObjectGroupProtection.Size.width = 250 $PieObjectGroupProtection.ChartStyle.ChartType = 'doughnut' #These file exist in the module directoy, There are 4 schemes by default $PieObjectGroupProtection.ChartStyle.ColorSchemeName = "ColorScheme3" #There are 8 generated schemes, randomly generated at runtime $PieObjectGroupProtection.ChartStyle.ColorSchemeName = "Generated3" #you can also ask for a random scheme. Which also happens ifyou have too many records for the scheme $PieObjectGroupProtection.ChartStyle.ColorSchemeName = 'Random' #Data defintion you can reference any column from name and value from the dataset. #Name and Count are the default to work with the Group function. $PieObjectGroupProtection.DataDefinition.DataNameColumnName = 'Name' $PieObjectGroupProtection.DataDefinition.DataValueColumnName = 'Count' #Dashboard Report $FinalReport = New-Object 'System.Collections.Generic.List[System.Object]' $FinalReport.Add($(Get-HTMLOpenPage -TitleText $ReportTitle -LeftLogoString $CompanyLogo -RightLogoString $RightLogo)) $FinalReport.Add($(Get-HTMLTabHeader -TabNames $tabarray)) $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[0] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Company Information")) $FinalReport.Add($(Get-HTMLContentTable $CompanyInfoTable)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Groups")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText 'Domain Administrators')) $FinalReport.Add($(Get-HTMLContentDataTable $DomainAdminTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText 'Enterprise Administrators')) $FinalReport.Add($(Get-HTMLContentDataTable $EnterpriseAdminTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Objects in Default OUs")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText 'Computers')) $FinalReport.Add($(Get-HTMLContentDataTable $DefaultComputersinDefaultOUTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText 'Users')) $FinalReport.Add($(Get-HTMLContentDataTable $DefaultUsersinDefaultOUTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "AD Objects Modified in Last $ADModNumber Days")) $FinalReport.Add($(Get-HTMLContentDataTable $ADObjectTable)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Expiring Items")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Users with Passwords Expiring in less than $DaysUntilPWExpireINT days")) $FinalReport.Add($(Get-HTMLContentDataTable $PasswordExpireSoonTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText 'Accounts Expiring Soon')) $FinalReport.Add($(Get-HTMLContentDataTable $ExpiringAccountsTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Accounts")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Users Haven't Logged on in $Days Days")) $FinalReport.Add($(Get-HTMLContentDataTable $userphaventloggedonrecentlytable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Accounts Created in $UserCreatedDays Days or Less")) $FinalReport.Add($(Get-HTMLContentDataTable $NewCreatedUsersTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Security Logs")) $FinalReport.Add($(Get-HTMLContentDataTable $securityeventtable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "UPN Suffixes")) $FinalReport.Add($(Get-HTMLContentTable $DomainTable)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLTabContentClose)) #Groups Report $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[1] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Groups Overivew")) $FinalReport.Add($(Get-HTMLContentTable $TOPGroupsTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Active Directory Groups")) $FinalReport.Add($(Get-HTMLContentDataTable $Table -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText 'Domain Administrators')) $FinalReport.Add($(Get-HTMLContentDataTable $DomainAdminTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText 'Enterprise Administrators')) $FinalReport.Add($(Get-HTMLContentDataTable $EnterpriseAdminTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Active Directory Groups Chart")) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 1 -ColumnCount 4)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectGroupType -DataSet $GroupTypetable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 2 -ColumnCount 4)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectGroupType2 -DataSet $DefaultGrouptable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 3 -ColumnCount 4)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectGroupMembersType -DataSet $GroupMembershipTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 4 -ColumnCount 4)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectGroupProtection -DataSet $GroupProtectionTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLTabContentClose)) #Organizational Unit Report $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[2] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Organizational Units")) $FinalReport.Add($(Get-HTMLContentDataTable $OUTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Organizational Units Charts")) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 1 -ColumnCount 2)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectOUGPOLinks -DataSet $OUGPOTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 2 -ColumnCount 2)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PO12 -DataSet $OUProtectionTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentclose)) $FinalReport.Add($(Get-HTMLTabContentClose)) #Users Report $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[3] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Users Overivew")) $FinalReport.Add($(Get-HTMLContentTable $TOPUserTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Active Directory Users")) $FinalReport.Add($(Get-HTMLContentDataTable $UserTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Expiring Items")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Users with Passwords Expiring in less than $DaysUntilPWExpireINT days")) $FinalReport.Add($(Get-HTMLContentDataTable $PasswordExpireSoonTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText 'Accounts Expiring Soon')) $FinalReport.Add($(Get-HTMLContentDataTable $ExpiringAccountsTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Accounts")) $FinalReport.Add($(Get-HTMLColumn1of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Users Haven't Logged on in $Days Days")) $FinalReport.Add($(Get-HTMLContentDataTable $userphaventloggedonrecentlytable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumn2of2)) $FinalReport.Add($(Get-HTMLContentOpen -BackgroundShade 1 -HeaderText "Accounts Created in $UserCreatedDays Days or Less")) $FinalReport.Add($(Get-HTMLContentDataTable $NewCreatedUsersTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Users Charts")) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 1 -ColumnCount 3)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $EnabledDisabledUsersPieObject -DataSet $EnabledDisabledUsersTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 2 -ColumnCount 3)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PWExpiresUsersTable -DataSet $PasswordExpirationTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 3 -ColumnCount 3)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectProtectedUsers -DataSet $ProtectedUsersTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLTabContentClose)) #GPO Report $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[4] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Group Policies")) $FinalReport.Add($(Get-HTMLContentDataTable $GPOTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLTabContentClose)) #Computers Report $FinalReport.Add($(Get-HTMLTabContentopen -TabName $tabarray[5] -TabHeading ("Report: " + (Get-Date -Format MM-dd-yyyy)))) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Computers Overivew")) $FinalReport.Add($(Get-HTMLContentTable $TOPComputersTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Computers")) $FinalReport.Add($(Get-HTMLContentDataTable $ComputersTable -HideFooter)) $FinalReport.Add($(Get-HTMLContentClose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Computers Charts")) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 1 -ColumnCount 2)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectComputersProtected -DataSet $ComputerProtectedTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLColumnOpen -ColumnNumber 2 -ColumnCount 2)) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectComputersEnabled -DataSet $ComputersEnabledTable)) $FinalReport.Add($(Get-HTMLColumnClose)) $FinalReport.Add($(Get-HTMLContentclose)) $FinalReport.Add($(Get-HTMLContentOpen -HeaderText "Computers Operating System Breakdown")) $FinalReport.Add($(Get-HTMLPieChart -ChartObject $PieObjectComputerObjOS -DataSet $GraphComputerOS)) $FinalReport.Add($(Get-HTMLContentclose)) $FinalReport.Add($(Get-HTMLTabContentClose)) $FinalReport.Add($(Get-HTMLClosePage)) $Day = (Get-Date).Day $Month = (Get-Date).Month $Year = (Get-Date).Year $ReportName = ("$Day - $Month - $Year - AD Report") Save-HTMLReport -ReportContent $FinalReport -ShowReport -ReportName $ReportName -ReportPath $ReportSavePath
My name is Bradley Wyatt; I am a 5x Microsoft Most Valuable Professional (MVP) in Microsoft Azure and Microsoft 365. I have given talks at many different conferences, user groups, and companies throughout the United States, ranging from PowerShell to DevOps Security best practices, and I am the 2022 North American Outstanding Contribution to the Microsoft Community winner.
34 thoughts on “Create an Interactive Active Directory HTML Report With PowerShell”
Outstanding job!! any chance we can get windows 2019 Versions and identify Windows 10 Pro/Enterprise and LTSC in the pie charts. it would also be helpful on the pie charts to put the number of devices rather than hover over them.
other then that looks great. Wishlist: export to word format for a document to deliver management.
Thanks I have made a feature request on Github that you can monitor https://github.com/bwya77/PSHTML-AD-Report/issues/12
Excellent work Brad. I am currently using pscookie monster script to generate report for my citrix environment , it creates a simple webpage(https://gallery.technet.microsoft.com/scriptcenter/PowerShell-HTML-Notificatio-e1c5759d).
Your report now took this to next level .. Thanks again for spending time to share your knowledge 🙂 Keep up the good work. Thanks.
Suresh Krishnan
Singapore
Great work once again! A Sysadmins dream.. 😉
Love the script,
is there a way to change the headers from blue too red?
Yes! I will make this setting into a param in the future but for now download the CSS file here
place it in C:\Program Files\WindowsPowerShell\Modules\ReportHTML\1.4.1.1.
Then modify this line
$FinalReport.Add($(Get-HTMLOpenPage -TitleText $ReportTitle -LeftLogoString $CompanyLogo -RightLogoString $RightLogo))
and make it
$FinalReport.Add($(Get-HTMLOpenPage -TitleText $ReportTitle -LeftLogoString $CompanyLogo -RightLogoString $RightLogo -CSSName Red))
Awesome job, would be interesting to have a language file so we could use on a different language OS server. (french) 🙂 I already
Dunno if it’s for the for the language barrier but I receive a “cannot resolve the manager XYZ on the group ABC” when running the “Groups” section
Thanks
Do any of your groups resolve, some groups have no group owner (like default groups)
These usually can be ignored
Awesome job! Just needed to translate all security groups into the german counterpart as they were not matching.
Also: Maybe use the LastLogonTimeStamp instead of lastlogon attribute? If you have more than one domain controller the lastlogon attribute will not be meaningful as it is not replicated between domain controllers.
LastLogonTimeStamp will be about 14 days minus random percentage of 5 days exact.
Add ability to send email with results?
# Use the following item to define if an email report should be sent once completed
$SendEmail = $true
# Please Specify the SMTP server address (and optional port) [servername(:port)]
$SMTPSRV = “smtpserver”
# Would you like to use SSL to send email?
$EmailSSL = $false
# Please specify the email address
$EmailFrom = “fromaddress”
# Please specify the email address(es) (separate multiple addresses with comma)
$EmailTo = “emailaddress”
# Please specify the email address(es) who will be CCd (separate multiple addresses with comma)
$EmailCc = “”
# Please specify an email subject
$EmailSubject = “Active Directory Report”
Is there a way to exclude the groups from the report? I have a plethora of groups and it not many have managers so it is just repeating cannot resolve the manager.
yes you can filter out groups. You will want something like get-adgroup -nl “GROUP”. personally I would have it read from a flat text file and then get groups that have a name not like any in that file
Amazing, great design work! Beautifully done! Works awesome! This is the html module i installed and works great.
https://www.powershellgallery.com/packages/ReportHTML/1.4.1.1
I tested on a DC 2016 works awesome!
I have a very large, global AD environment. This report looks awesome and I was hoping it would work out for me. Ends up throwing a lot of errors, consumes a lot of memory to run and after several hours it just crashes 🙁 I’m also getting the errors where no group manager is specified.
Running it from an AD Server 2016 with ReportHTML 1.4.1.1. installed.
the ReportHTML module takes a while to compile. I’m working on a script to only report on what you want (selective)
This selection based would be a greate help.
I have also made this amendment to the Script, I just added the following to the top of the script (right under the Write-Host -NoNewLine “Gathering Report Customisation” section.
$RunIntroPage = $True
$RunDashPage = $True
$RunUserAccPage = $True
$RunCompAccPage = $False
$RunSecGroupsPage = $False
$RunOUPage = $True
$RunGPOPage = $True
$RunDCDIAGPage = $False
Then in each section do this.
Dashboard Page:
if ($RunDashPage)
{
Dashboard commands
}
Users Page:
if ($RunUsersPage)
{
Users commands
}
As you can see, I have also added more pages.
Outstanding job…I love the idea and tool.
I can Confirm the script runs on Windows 2019 Domain. I do get the “Cannot Resolve the manager, ____ on the Group (every group i have)
but it does complete so there is that. 🙂
request:
Can we have it display a little more detail on the pie chart OS’s types IE:
Hyper-v 2016
Hyper-v 2019
Windows Versions, Edition, LTSC or not, Role IE: Domain Controler
List computers: last seen beyond 30 days
LIst Users Disabled: beyond 30 days
List Computers and Share w/ security objects
List DFS shares w/ Security objects
Wishlist:
export to a 2013,2016,2019 word and excel format’s
Execute remotely from a workstation w/ admin tools
I am on lots of different networks each week and I would be happy to be an alpha/beta tester for you if you could use one?
simple fix to resolve ADGroups have no Assigned ManagedBY users.
# find and replace any Groups that don’t have a managedBy user and assign one
# Donovan being the user AD login you want all unassigned Groups to be assigned ManagedBy
Get-ADgroup -LDAPFilter “(!(ManagedBy=*))” -SearchBase “DC=nwm,DC=local” | Set-ADGroup -ManagedBy Donovan
What Version of Powershell do you need?
v3 or higher
Hi All,
I need a script to generate a report of last logged in (history of login users in system) of users from all systems exist in Active directory.no matter they are currently login on system or not. thank you in advance.
are you looking for a report of computers and the last logged in user?
Hi Brad,
I came across your post and found this script. The HTML report is excellent and very neat.
However, I wanted to check, if we can create an HTML page and after selecting the options, instead of changing in powershell, call the powershell file to generate this type of report.
Actually, this would be helpful to the junior admins, who doesn’t have full access to the AD and can pull up the reports.
I can write the powershell CMD’s to call, but I’m a beginner in HTML.
Hi Brad Wyatt,
Thanks for your code. I am looking for a Active Directory Dashboard, from where i can manage my Active Directory activities like User Management like User Addition, Deletion & Updation.
Requesting you to help me on how to achieve from above code please.
I will be available on [email protected]
Fantastic job!
What would be great is add the ability to pull Bitlocker keys on here for my support guys to look at w/o having to log into AD to get them
Hi Brad,
What an amazing script ! Thanks for this great work !
I am trying to adapt it for Windows7 (not server) (i also upgrade my PowerShell version to version 3) but the result of the report page is empty … Do you have any idea where the problem come from ?
Thanks in advance
do you have the correct module as well as AD module. any errors?
Brad,
I have used this script often. However, out of no where it stopped generating the report because of the following error message: Save-HTMLReport : Cannot bind argument to parameter ‘ReportContent’ because it is null.
At line:1544 char:32
+ Save-HTMLReport -ReportContent $FinalReport -ShowReport -ReportName $ …
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Save-HTMLReport], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Save-HTMLReport
Please advise.
I want to get the report to my email automatically daily. Kindly let me know what should I do for the same.
Hi Brad Wyatt would you help me to run this script i am unable to run i have domain 2016.
can you get the report to work on a particular OU?
Thanks for the code, but I am receiving several of these errors:
“The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property”