Files
management-scripts/Hudu-DocumentADDS.ps1

529 lines
18 KiB
PowerShell

# Based on the script by lwhitelock, which is based on the original script by Kelvin Tegelaar https://github.com/KelvinTegelaar/AutomaticDocumentation
#####################################################################
#
# Active Directory Details to Hudu
#
# $HuduAPIKey must be supplied by RMM script
# $HuduBaseDomain is the base domain of your Hudu instance (without a trailing /)
# and must be supplied by RMM script
# $CompanyName must exactly match the name of the company in Hudu
# and must be supplied by RMM script
# This is the name of the Hudu Asset Layout you want to use.
$HuduAssetLayoutName = "Active Directory"
#####################################################################
# Get the Hudu API Module if not installed
if (Get-Module -ListAvailable -Name HuduAPI) {
Import-Module HuduAPI
} else {
Install-Module HuduAPI -Force
Import-Module HuduAPI
}
# Set Hudu logon information
New-HuduAPIKey $HuduAPIKey
New-HuduBaseUrl $HuduBaseDomain
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
}
}
# Path needed here is different for remote registry access
$path1 = $path.SubString( 6 )
$path2 = $path1.Replace( '\', '\\' )
$registry = $null
try
{
## Use the Remote Registry service
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(
[Microsoft.Win32.RegistryHive]::LocalMachine,
$ComputerName )
}
catch
{
#$e = $error[ 0 ]
#3.06, remove the verbose message as it confised some people
#wv "Could not open registry on computer $ComputerName ($e)"
}
$val = $null
If( $registry )
{
$key = $registry.OpenSubKey( $path2 )
If( $key )
{
$val = $key.GetValue( $name )
$key.Close()
}
$registry.Close()
}
Return $val
}
Function GetBasicDCInfo {
Param
(
[Parameter( Mandatory = $true )]
[String] $dn # distinguishedName of a DC
)
$DCName = $dn.SubString( 0, $dn.IndexOf( '.' ) )
$SrvName = $dn.SubString( $dn.IndexOf( '.' ) + 1 )
$Results = Get-ADDomainController -Identity $DCName -Server $SrvName -EA 0
If($? -and $Null -ne $Results)
{
$GC = $Results.IsGlobalCatalog.ToString()
$ReadOnly = $Results.IsReadOnly.ToString()
$IPv4Address = $Results.IPv4Address -join ", "
$IPv6Address = $Results.IPv6Address -join ", "
$ServerOS = $Results.OperatingSystem
$tmp = Get-RegistryValue "HKLM:\software\microsoft\windows nt\currentversion" "installationtype" $DCName
If( $null -eq $tmp ) { $ServerCore = 'Unknown' }
ElseIf( $tmp -eq 'Server Core') { $ServerCore = 'Yes' }
Else { $ServerCore = 'No' }
}
Else
{
$GC = 'Unable to retrieve status'
$ReadOnly = $GC
$ServerOS = $GC
$ServerCore = $GC
$IPv4Address = $GC
$IPv6Address = $GC
}
$obj = [PSCustomObject] @{
DCName = $DCName
GC = $GC
ReadOnly = $ReadOnly
ServerOS = $ServerOS
ServerCore = $ServerCore
IPv4Address = $IPv4Address
IPv6Address = $IPv6Address
}
Return $obj
}
Function GetTimeServerRegistryKeys {
Param
(
[String] $DCName
)
$AnnounceFlags = Get-RegistryValue "HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config" "AnnounceFlags" $DCName
If( $null -eq $AnnounceFlags )
{
## DCName can't be contacted or DCName is an appliance with no registry
$AnnounceFlags = 'n/a'
$MaxNegPhaseCorrection = 'n/a'
$MaxPosPhaseCorrection = 'n/a'
$NtpServer = 'n/a'
$NtpType = 'n/a'
$SpecialPollInterval = 'n/a'
$VMICTimeProviderEnabled = 'n/a'
$NTPSource = 'Cannot retrieve data from registry'
}
Else
{
$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 = Invoke-Command -ComputerName $DCName {w32tm /query /computer:$DCName /source}
}
If( $VMICTimeProviderEnabled -eq 'n/a' )
{
$VMICEnabled = 'n/a'
}
ElseIf( $VMICTimeProviderEnabled -eq 0 )
{
$VMICEnabled = 'Disabled'
}
Else
{
$VMICEnabled = 'Enabled'
}
$obj = [PSCustomObject] @{
DCName = $DCName.Substring(0, $_.IndexOf( '.'))
TimeSource = $NTPSource
AnnounceFlags = $AnnounceFlags
MaxNegPhaseCorrection = $MaxNegPhaseCorrection
MaxPosPhaseCorrection = $MaxPosPhaseCorrection
NtpServer = $NtpServer
NtpType = $NtpType
SpecialPollInterval = $SpecialPollInterval
VMICTimeProvider = $VMICEnabled
}
Return $obj
}
function Get-WinADForestInformation {
$Data = @{ }
$ForestInformation = $(Get-ADForest)
$Data.Forest = $ForestInformation
$Data.RootDSE = $(Get-ADRootDSE -Properties *)
$Data.ForestName = $ForestInformation.Name
$Data.ForestNameDN = $Data.RootDSE.defaultNamingContext
$Data.Domains = $ForestInformation.Domains
$Data.ForestInformation = @{
'Forest Name' = $ForestInformation.Name
'Root Domain' = $ForestInformation.RootDomain
'Forest Functional Level' = $ForestInformation.ForestMode
'# of Domains' = ($ForestInformation.Domains).Count
'Sites Count' = ($ForestInformation.Sites).Count
'Forest Domains' = ($ForestInformation.Domains) -join ", "
'Sites' = ($ForestInformation.Sites) -join ", "
}
$Data.UPNSuffixes = Invoke-Command -ScriptBlock {
$UPNSuffixList = [PSCustomObject] @{
"Primary UPN" = $ForestInformation.RootDomain
"UPN Suffixes" = $ForestInformation.UPNSuffixes -join ","
}
return $UPNSuffixList
}
$Data.GlobalCatalogs = $ForestInformation.GlobalCatalogs
$Data.SPNSuffixes = $ForestInformation.SPNSuffixes
$Data.Sites = Invoke-Command -ScriptBlock {
$Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites | Sort-Object
$SiteData = foreach ($Site in $Sites) {
[PSCustomObject] @{
"Site Name" = $site.Name
"Subnets" = ($site.Subnets | Sort-Object) -join ", "
"Servers" = ($Site.Servers) -join ", "
}
}
Return $SiteData
}
$Data.FSMO = Invoke-Command -ScriptBlock {
[PSCustomObject] @{
"Domain" = $ForestInformation.RootDomain
"Role" = 'Domain Naming Master'
"Holder" = $ForestInformation.DomainNamingMaster
}
[PSCustomObject] @{
"Domain" = $ForestInformation.RootDomain
"Role" = 'Schema Master'
"Holder" = $ForestInformation.SchemaMaster
}
foreach ($Domain in $ForestInformation.Domains) {
$DomainFSMO = Get-ADDomain $Domain | Select-Object PDCEmulator, RIDMaster, InfrastructureMaster
[PSCustomObject] @{
"Domain" = $Domain
"Role" = 'PDC Emulator'
"Holder" = $DomainFSMO.PDCEmulator
}
[PSCustomObject] @{
"Domain" = $Domain
"Role" = 'Infrastructure Master'
"Holder" = $DomainFSMO.InfrastructureMaster
}
[PSCustomObject] @{
"Domain" = $Domain
"Role" = 'RID Master'
"Holder" = $DomainFSMO.RIDMaster
}
}
Return $FSMO
}
$Data.OptionalFeatures = Invoke-Command -ScriptBlock {
$OptionalFeatures = $(Get-ADOptionalFeature -Filter * )
$Optional = @{
'Recycle Bin Enabled' = ''
'Privileged Access Management Feature Enabled' = ''
}
### Fix Optional Features
foreach ($Feature in $OptionalFeatures) {
if ($Feature.Name -eq 'Recycle Bin Feature') {
if ("$($Feature.EnabledScopes)" -eq '') {
$Optional.'Recycle Bin Enabled' = $False
}
else {
$Optional.'Recycle Bin Enabled' = $True
}
}
if ($Feature.Name -eq 'Privileged Access Management Feature') {
if ("$($Feature.EnabledScopes)" -eq '') {
$Optional.'Privileged Access Management Feature Enabled' = $False
}
else {
$Optional.'Privileged Access Management Feature Enabled' = $True
}
}
}
return $Optional
### Fix optional features
}
return $Data
}
$TableHeader = "<table style=`"width: 100%; border-collapse: collapse; border: 1px solid black;`">"
$Whitespace = "<br/>"
$TableStyling = "<th>", "<th align=`"left`" style=`"background-color:#00adef; border: 1px solid black;`">"
$RawAD = Get-WinADForestInformation
$ForestRawInfo = new-object PSCustomObject -property $RawAD.ForestInformation | convertto-html -Fragment | Select-Object -Skip 1
$ForestToc = "<div id=`"forest_summary`"></div>"
$ForestNice = $ForestToc + $TableHeader + ($ForestRawInfo -replace $TableStyling) + $Whitespace
$SiteRawInfo = $RawAD.Sites | Select-Object 'Site Name', Servers, Subnets | ConvertTo-Html -Fragment | Select-Object -Skip 1
$SiteHeader = "<p id=`"site_summary`"><i>AD Forest Physical Structure.</i></p>"
$SiteNice = $SiteHeader + $TableHeader + ($SiteRawInfo -replace $TableStyling) + $Whitespace
$DomainsRawInfo = $(Get-WinADForestInformation).Domains | ForEach-Object { Get-ADDomain $_ | Select-Object Name, NetBIOSName, DomainMode } | ConvertTo-Html -Fragment | Select-Object -Skip 1
$DomainsHeader = "<p id=`"domain_summary`"><i>AD Forest Logical Structure.</i></p>"
$DomainsNice = $DomainsHeader + $TableHeader + ($DomainsRawInfo -replace $TableStyling) + $Whitespace
$OptionalRawFeatures = new-object PSCustomObject -property $RawAD.OptionalFeatures | convertto-html -Fragment | Select-Object -Skip 1
$OptionalFeaturesToc = "<div id=`"optional_features`"></div>"
$OptionalNice = $OptionalFeaturesToc + $TableHeader + ($OptionalRawFeatures -replace $TableStyling) + $Whitespace
$UPNRawFeatures = $RawAD.UPNSuffixes | convertto-html -Fragment -as list| Select-Object -Skip 1
$UPNToc = "<div id=`"upn_suffixes`"></div>"
$UPNNice = $UPNToc + $TableHeader + ($UPNRawFeatures -replace $TableStyling) + $Whitespace
$DCRawFeatures = $RawAD.GlobalCatalogs| Sort-Object | ForEach-Object { GetBasicDCInfo $_ } | convertto-html -Fragment | Select-Object -Skip 1
$DCToc = "<div id=`"domain_controllers`"></div>"
$DCNice = $DCToc + $TableHeader + ($DCRawFeatures -replace $TableStyling) + $Whitespace
$DCRawNTPconfig = $RawAD.GlobalCatalogs | Sort-Object | ForEach-Object { (GetTimeServerRegistryKeys $_) } | convertto-html -Fragment | Select-Object -Skip 1
$NTPToc = "<div id=`"ntp_configuration`"></div>"
$DCNTPconfigNice = $NTPToc + $TableHeader + ($DCRawNTPconfig -replace $TableStyling) + $Whitespace
$FSMORawFeatures = $RawAD.FSMO | convertto-html -Fragment | Select-Object -Skip 1
$FSMOToc = "<div id=`"fsmo_roles`"></div>"
$FSMONice = $FSMOToc + $TableHeader + ($FSMORawFeatures -replace $TableStyling) + $Whitespace
#$ForestFunctionalLevel = $RawAD.RootDSE.forestFunctionality
#$DomainFunctionalLevel = $RawAD.RootDSE.domainFunctionality
#$domaincontrollerMaxLevel = $RawAD.RootDSE.domainControllerFunctionality
$passwordpolicyraw = Get-ADDefaultDomainPasswordPolicy | Select-Object ComplexityEnabled, PasswordHistoryCount, LockoutDuration, LockoutThreshold, MaxPasswordAge, MinPasswordAge | convertto-html -Fragment -As List | Select-Object -skip 1
$passwordpolicyheader = "<tr><th>Policy</th><th><b>Setting</b></th></tr>"
$passwordToc = "<div id=`"default_password_policies`"></div>"
$passwordpolicyNice = $passwordToc + $TableHeader + ($passwordpolicyheader -replace $TableStyling) + ($passwordpolicyraw -replace $TableStyling) + $Whitespace
$adminsraw = Get-ADGroupMember "Domain Admins" | Select-Object SamAccountName, Name | convertto-html -Fragment | Select-Object -Skip 1
$adminsToc = "<div id=`"domain_admins`"></div>"
$adminsnice = $adminsToc + $TableHeader + ($adminsraw -replace $TableStyling) + $Whitespace
$TotalUsers = (Get-AdUser -filter *).count
$EnabledUsers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $true }).count
$DisabledUSers = (Get-AdUser -filter * | Where-Object { $_.enabled -eq $false }).count
$DomainAdminUsers = (Get-ADGroupMember -Identity "Domain Admins").count
$EnterpriseAdminUsers = (Get-ADGroupMember -Identity "Enterprise Admins").count
$SchemaAdminUsers = (Get-ADGroupMember -Identity "Schema Admins").count
$AdminCountUsers = (Get-ADUser -LDAPFilter "(admincount=1)").count
$UsersCountObj = [PSCustomObject] @{
'Total' = $TotalUsers
'Enabled' = $EnabledUsers
'Disabled' = $DisabledUSers
'Domain Admins' = $DomainAdminUsers
'Enterprise Admins' = $EnterpriseAdminUsers
'Schema Admins' = $SchemaAdminUsers
'AdminCount users' = $AdminCountUsers
}
$userTotalsRaw = $UsersCountObj | convertto-html -Fragment | Select-Object -Skip 1
$userTotalsToc = "<div id=`"user_count`"></div>"
$userTotalsNice = $userTotalsToc + $TableHeader + ($userTotalsRaw -replace $TableStyling) + $Whitespace
$currentDate = Get-Date -Format "dddd dd/MM/yyyy HH:mm K"
$toc = '<h2><center>
<a href="#forest_summary">FOREST SUMMARY</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#site_summary">SITE SUMMARY</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#domain_summary">DOMAIN SUMMARY</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#domain_controllers">DOMAIN CONTROLLERS</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#ntp_configuration">NTP CONFIGURATION</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#fsmo_roles">FSMO ROLES</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#optional_features">OPTIONAL FEATURES</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#upn_suffixes">UPN SUFFIXES</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#default_password_policies">DEFAULT PASSWORD POLICIES</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#user_count">USER COUNT</a>&nbsp;&nbsp;|&nbsp;&nbsp;
<a href="#domain_admins">DOMAIN ADMINS</a>
</center></h2><br />'
# Setup the fields for the Asset
$AssetFields = @{
'last_updated' = $currentDate
'table_of_contents' = $toc
'forest_name' = $RawAD.ForestName
'forest_summary' = $ForestNice
'site_summary' = $SiteNice
'domain_summary' = $DomainsNice
'domain_controllers' = $DCNice
'ntp_configuration' = $DCNTPconfigNice
'fsmo_roles' = $FSMONice
'optional_features' = $OptionalNice
'upn_suffixes' = $UPNNice
'default_password_policies' = $passwordpolicyNice
'domain_admins' = $adminsnice
'user_count' = $userTotalsNice
}
# Checking if the FlexibleAsset exists. If not, create a new one.
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
if (!$Layout) {
$AssetLayoutFields = @(
@{
label = 'Last Updated'
field_type = 'Text'
show_in_list = 'true'
position = 1
},
@{
label = 'Table of Contents'
field_type = 'RichText'
show_in_list = 'false'
position = 2
},
@{
label = 'Forest Name'
field_type = 'Text'
show_in_list = 'true'
position = 3
},
@{
label = 'Forest Summary'
field_type = 'RichText'
show_in_list = 'false'
position = 4
},
@{
label = 'Site Summary'
field_type = 'RichText'
show_in_list = 'false'
position = 5
},
@{
label = 'Domain Summary'
field_type = 'RichText'
show_in_list = 'false'
position = 6
},
@{
label = 'Domain Controllers'
field_type = 'RichText'
show_in_list = 'false'
position = 7
},
@{
label = 'NTP Configuration'
field_type = 'RichText'
show_in_list = 'false'
position = 8
},
@{
label = 'FSMO Roles'
field_type = 'RichText'
show_in_list = 'false'
position = 9
},
@{
label = 'Optional Features'
field_type = 'RichText'
show_in_list = 'false'
position = 10
},
@{
label = 'UPN Suffixes'
field_type = 'RichText'
show_in_list = 'false'
position = 11
},
@{
label = 'Default Password Policies'
field_type = 'RichText'
show_in_list = 'false'
position = 12
},
@{
label = 'User Count'
field_type = 'RichText'
show_in_list = 'false'
position = 13
},
@{
label = 'Domain Admins'
field_type = 'RichText'
show_in_list = 'false'
position = 14
}
)
Write-Host "Creating New Asset Layout"
#$NewLayout = New-HuduAssetLayout -name $HuduAssetLayoutName -icon "fas fa-sitemap" -color "#00adef" -icon_color "#000000" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $AssetLayoutFields
New-HuduAssetLayout -name $HuduAssetLayoutName -icon "fas fa-sitemap" -color "#00adef" -icon_color "#000000" -include_passwords $false -include_photos $false -include_comments $false -include_files $false -fields $AssetLayoutFields
$Layout = Get-HuduAssetLayouts -name $HuduAssetLayoutName
}
$Company = Get-HuduCompanies -name $CompanyName
if ($company) {
#Upload data to Hudu
$Asset = Get-HuduAssets -name $RawAD.ForestName -companyid $company.id -assetlayoutid $layout.id
#If the Asset does not exist, we edit the body to be in the form of a new asset, if not, we just upload.
if (!$Asset) {
Write-Host "Creating new Asset"
$Asset = New-HuduAsset -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
}
else {
Write-Host "Updating Asset"
$Asset = Set-HuduAsset -asset_id $Asset.id -name $RawAD.ForestName -company_id $company.id -asset_layout_id $layout.id -fields $AssetFields
}
} else {
Write-Host "$CompanyName was not found in Hudu"
}