diff --git a/Hudu-ADDS-Documentation.ps1 b/Hudu-ADDS-Documentation.ps1 new file mode 100644 index 0000000..a10eed5 --- /dev/null +++ b/Hudu-ADDS-Documentation.ps1 @@ -0,0 +1,528 @@ +# 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 = "
| ", " | "
+
+$RawAD = Get-WinADForestInformation
+
+$ForestRawInfo = new-object PSCustomObject -property $RawAD.ForestInformation | convertto-html -Fragment | Select-Object -Skip 1
+$ForestToc = ""
+$ForestNice = $ForestToc + $TableHeader + ($ForestRawInfo -replace $TableStyling) + $Whitespace
+
+$SiteRawInfo = $RawAD.Sites | Select-Object 'Site Name', Servers, Subnets | ConvertTo-Html -Fragment | Select-Object -Skip 1
+$SiteHeader = " AD Forest Physical Structure. " +$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 = "AD Forest Logical Structure. " +$DomainsNice = $DomainsHeader + $TableHeader + ($DomainsRawInfo -replace $TableStyling) + $Whitespace + +$OptionalRawFeatures = new-object PSCustomObject -property $RawAD.OptionalFeatures | convertto-html -Fragment | Select-Object -Skip 1 +$OptionalFeaturesToc = "" +$OptionalNice = $OptionalFeaturesToc + $TableHeader + ($OptionalRawFeatures -replace $TableStyling) + $Whitespace + +$UPNRawFeatures = $RawAD.UPNSuffixes | convertto-html -Fragment -as list| Select-Object -Skip 1 +$UPNToc = "" +$UPNNice = $UPNToc + $TableHeader + ($UPNRawFeatures -replace $TableStyling) + $Whitespace + +$DCRawFeatures = $RawAD.GlobalCatalogs| Sort-Object | ForEach-Object { GetBasicDCInfo $_ } | convertto-html -Fragment | Select-Object -Skip 1 +$DCToc = "" +$DCNice = $DCToc + $TableHeader + ($DCRawFeatures -replace $TableStyling) + $Whitespace + +$DCRawNTPconfig = $RawAD.GlobalCatalogs | Sort-Object | ForEach-Object { (GetTimeServerRegistryKeys $_) } | convertto-html -Fragment | Select-Object -Skip 1 +$NTPToc = "" +$DCNTPconfigNice = $NTPToc + $TableHeader + ($DCRawNTPconfig -replace $TableStyling) + $Whitespace + +$FSMORawFeatures = $RawAD.FSMO | convertto-html -Fragment | Select-Object -Skip 1 +$FSMOToc = "" +$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 = " |
|---|---|
| Policy | Setting |