From c1ee13d0b4dff0247ead9acf6a0a926662a2b065 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 3 Dec 2024 17:01:39 -0600 Subject: [PATCH 1/4] Address missing Active Directory Module logic Callout that we are missing a feature RSat-AD-PowerShell and have it installed. --- .../Analyzer/Invoke-AnalyzerExchangeInformation.ps1 | 4 +++- .../ExchangeInformation/Get-ExchangeInformation.ps1 | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 index ddf1e2890..340825a65 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 @@ -315,7 +315,9 @@ function Invoke-AnalyzerExchangeInformation { $displayMissingGroups.Add("Unable to determine Local System Membership as the results were blank.") } - if ($null -ne $exchangeInformation.ComputerMembership.ADGroupMembership) { + if ($exchangeInformation.ComputerMembership.ADGroupMembership -eq "NoAdModule") { + $displayMissingGroups.Add("Missing Active Directory Module. Run 'Install-WindowsFeature RSat-AD-PowerShell'") + } elseif ($null -ne $exchangeInformation.ComputerMembership.ADGroupMembership) { foreach ($adGroup in $adGroupList) { if (($null -eq ($exchangeInformation.ComputerMembership.ADGroupMembership.SID | Where-Object { $_.ToString() -eq $adGroup.SID }))) { $displayMissingGroups.Add("$($adGroup.WellKnownName) - AD Group Membership") diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index 88cd754c1..b120c2624 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -231,7 +231,18 @@ function Get-ExchangeInformation { # AD Module cmdlets don't appear to work in remote context with Invoke-Command, this is why it is now moved outside of the Invoke-ScriptBlockHandler. try { - $adPrincipalGroupMembership = (Get-ADPrincipalGroupMembership (Get-ADComputer ($Server.Split(".")[0]) -ErrorAction Stop).DistinguishedName -ErrorAction Stop) + Write-Verbose "Trying to get the computer DN" + $computerDN = (Get-ADComputer ($Server.Split(".")[0]) -ErrorAction Stop).DistinguishedName + Write-Verbose "Computer DN: $computerDN" + $adPrincipalGroupMembership = (Get-ADPrincipalGroupMembership $computerDN -ErrorAction Stop) + } catch [System.Management.Automation.CommandNotFoundException] { + if ($_.TargetObject -eq "Get-ADComputer") { + $adPrincipalGroupMembership = "NoAdModule" + Invoke-CatchActions + } else { + # If this occurs, do not run Invoke-CatchActions to let us know what is wrong here. + Write-Verbose "CommandNotFoundException thrown, but not for Get-ADComputer. Inner Exception: $_" + } } catch { # Current do not add Invoke-CatchActions as we want to be aware if this doesn't fix some things. Write-Verbose "Failed to get the AD Principal Group Membership. Inner Exception: $_" From fd0a20dc79cdc48075bdd4da7f769607bb3d2c66 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 4 Dec 2024 16:02:25 -0600 Subject: [PATCH 2/4] Fallback option for exception on Get-ADPrincipalGroupMembership --- .../Get-ExchangeInformation.ps1 | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index b120c2624..39c702b09 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -232,7 +232,8 @@ function Get-ExchangeInformation { # AD Module cmdlets don't appear to work in remote context with Invoke-Command, this is why it is now moved outside of the Invoke-ScriptBlockHandler. try { Write-Verbose "Trying to get the computer DN" - $computerDN = (Get-ADComputer ($Server.Split(".")[0]) -ErrorAction Stop).DistinguishedName + $adComputer = (Get-ADComputer ($Server.Split(".")[0]) -ErrorAction Stop -Properties MemberOf) + $computerDN = $adComputer.DistinguishedName Write-Verbose "Computer DN: $computerDN" $adPrincipalGroupMembership = (Get-ADPrincipalGroupMembership $computerDN -ErrorAction Stop) } catch [System.Management.Automation.CommandNotFoundException] { @@ -244,8 +245,29 @@ function Get-ExchangeInformation { Write-Verbose "CommandNotFoundException thrown, but not for Get-ADComputer. Inner Exception: $_" } } catch { - # Current do not add Invoke-CatchActions as we want to be aware if this doesn't fix some things. Write-Verbose "Failed to get the AD Principal Group Membership. Inner Exception: $_" + Invoke-CatchActions + if ($null -eq $adComputer -or + $null -eq $adComputer.MemberOf -or + $adComputer.MemberOf.Count -eq 0) { + Write-Verbose "Failed to get the ADComputer information to be able to find the MemberOf with Get-ADObject" + } else { + $adPrincipalGroupMembership = New-Object System.Collections.Generic.List[object] + foreach ($memberDN in $adComputer.MemberOf) { + try { + $adObject = Get-ADObject $memberDN -ErrorAction Stop -Properties "objectSid" + $adPrincipalGroupMembership.Add([PSCustomObject]@{ + Name = $adObject.Name + DistinguishedName = $adObject.DistinguishedName + ObjectGuid = $adObject.ObjectGuid + SID = $adObject.objectSid + }) + } catch { + # Currently do not add Invoke-CatchActions as we want to be aware if this doesn't fix some things. + Write-Verbose "Failed to run Get-ADObject against '$memberDN'. Inner Exception: $_" + } + } + } } $computerMembership = [PSCustomObject]@{ From e8792d6b0b383bf468dab184c4f664eac92db16b Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 4 Dec 2024 17:17:47 -0600 Subject: [PATCH 3/4] Update docs for Computer Membership changes --- docs/Diagnostics/HealthChecker/ExchangeComputerMembership.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Diagnostics/HealthChecker/ExchangeComputerMembership.md b/docs/Diagnostics/HealthChecker/ExchangeComputerMembership.md index 5ebaf1057..9e233c26b 100644 --- a/docs/Diagnostics/HealthChecker/ExchangeComputerMembership.md +++ b/docs/Diagnostics/HealthChecker/ExchangeComputerMembership.md @@ -8,6 +8,7 @@ This check is done by using the ADModule with using the cmdlets `Get-LocalGroupM If an issue is detected, the group will display with where the problem is located. Either `Local System Membership` if the group isn't part of the local system account or `AD Group Membership` if the computer object isn't a member of the group provided. +If the script is run from a computer that doesn't have Active Directory PowerShell module installed on it, `Get-ADComputer` will fail with the exception `CommandNotFoundException`. The output will then display that you should run `Install-WindowsFeature RSat-AD-PowerShell` on the computer to get the module installed. This is how you get this feature to then start reporting correctly. **Included in HTML Report?** From c61f00a5d5fcdc86e40abc74fbff217c29639fc6 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 5 Dec 2024 09:30:46 -0600 Subject: [PATCH 4/4] Get-ADObject to use Filter and GC port Get-ADObject by default only searches the current domain when using Identity. Need to adjust to use the Filter and the GC port to find the objects we want. --- .../Get-ExchangeInformation.ps1 | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index 39c702b09..848015de9 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -235,7 +235,19 @@ function Get-ExchangeInformation { $adComputer = (Get-ADComputer ($Server.Split(".")[0]) -ErrorAction Stop -Properties MemberOf) $computerDN = $adComputer.DistinguishedName Write-Verbose "Computer DN: $computerDN" - $adPrincipalGroupMembership = (Get-ADPrincipalGroupMembership $computerDN -ErrorAction Stop) + $params = @{ + Identity = $computerDN + ErrorAction = "Stop" + } + try { + $serverId = ([ADSI]("GC://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE")).dnsHostName.ToString() + Write-Verbose "Adding ServerId '$serverId' to the Get-AD* cmdlets" + $params["Server"] = $serverId + } catch { + Write-Verbose "Failed to find the root DSE. Inner Exception: $_" + Invoke-CatchActions + } + $adPrincipalGroupMembership = (Get-ADPrincipalGroupMembership @params) } catch [System.Management.Automation.CommandNotFoundException] { if ($_.TargetObject -eq "Get-ADComputer") { $adPrincipalGroupMembership = "NoAdModule" @@ -255,7 +267,22 @@ function Get-ExchangeInformation { $adPrincipalGroupMembership = New-Object System.Collections.Generic.List[object] foreach ($memberDN in $adComputer.MemberOf) { try { - $adObject = Get-ADObject $memberDN -ErrorAction Stop -Properties "objectSid" + $params = @{ + Filter = "distinguishedName -eq `"$memberDN`"" + Properties = "objectSid" + ErrorAction = "Stop" + } + + if (-not([string]::IsNullOrEmpty($serverId))) { + $params["Server"] = "$($serverId):3268" # Needs to be a GC port incase we are looking for a group outside of this domain. + } + $adObject = Get-ADObject @params + + if ($null -eq $adObject) { + Write-Verbose "Failed to find AD Object with filter '$($params.Filter)' on server '$($params.Server)'" + continue + } + $adPrincipalGroupMembership.Add([PSCustomObject]@{ Name = $adObject.Name DistinguishedName = $adObject.DistinguishedName