diagnosticsModule/Private/AdfsHealthChecks.ps1
# Windows Internal Database Service State if it is used by ADFS Function TestIsWidRunning() { $testName = "IsWidRunning" $serviceStateKey = "WIDServiceState" $serviceStartModeKey = "WIDServiceStartMode" try { $adfsConfigurationDbTestResult = New-Object TestResult -ArgumentList($testName); $adfsConfigurationDb = (Get-WmiObject -namespace root/ADFS -class SecurityTokenService).Properties["ConfigurationDatabaseConnectionString"].Value; If ($adfsConfigurationDb.Contains("microsoft##wid") -or $adfsConfigurationDb.Contains("microsoft##ssee")) { $service = get-service -name 'MSSQL$MICROSOFT##WID'; if($service -ne $null) { $widServiceState=$service.Status; If ($widServiceState -ne "Running") { $adfsConfigurationDbTestResult.Result = [ResultType]::Fail; $adfsConfigurationDbTestResult.Detail = "Current State of WID Service is: $widServiceState"; } $widService = (Get-WmiObject win32_service | Where-Object {$_.DisplayName.StartsWith($service.DisplayName)}); $adfsConfigurationDbTestResult.Output = @{$serviceStateKey = $widServiceState; $serviceStartModeKey=$widService.StartMode} return $adfsConfigurationDbTestResult; } else { throw "Get-Service with name WIDWriter does not exist."; } } } catch [Exception] { $testResult= New-Object TestResult -ArgumentList($testName); $testResult.Result = [ResultType]::NotRun; $testResult.Detail = $_.Exception.Message; $testResult.ExceptionMessage = $_.Exception.Message return $testResult; } } # Ping Federation metadata page on localhost Function TestPingFederationMetadata() { $testName = "PingFederationMetadata" $exceptionKey = "PingFedmetadataException" try { $fedmetadataUrlTestResult = New-Object TestResult -ArgumentList($testName); $fedmetadataUrlTestResult.Output = @{$exceptionKey = "NONE"} $sslBinding = GetSslBinding $fedmetadataUrl = "https://" + $sslBinding.HostNamePort + "/federationmetadata/2007-06/federationmetadata.xml"; $webClient = New-Object net.WebClient; [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12 try { $data = $webClient.DownloadData($fedmetadataUrl); } catch [Net.WebException] { $exceptionEncoded = [System.Web.HttpUtility]::HtmlEncode($_.Exception.ToString()); $fedmetadataUrlTestResult.Result = [ResultType]::Fail; $fedmetadataUrlTestResult.Detail = $exceptionEncoded; $fedmetadataUrlTestResult.Output.Set_Item($exceptionKey, $exceptionEncoded) } return $fedmetadataUrlTestResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestSslBindings() { $adfsVersion = Get-AdfsVersion $testName = "CheckAdfsSslBindings" $sslBindingsKey = "SSLBindings" $sslOutputs = @{$sslBindingsKey = $none} $sslBindingsTestResult = New-Object TestResult -ArgumentList $testName $isAdfsServiceRunning = IsAdfsServiceRunning; if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } if ($isAdfsServiceRunning -eq $false) { $sslBindingsTestResult.Result = [ResultType]::NotRun; $sslBindingsTestResult.Detail = "AD FS service is not running"; return $sslBindingsTestResult; } try { if ($adfsVersion -eq $adfs3 -or $adfsVersion -eq $adfs4) { $adfsSslBindings = Get-AdfsSslCertificate; $tlsClientPort = $adfsProperties.TlsClientPort if (($adfsSslBindings | where {$_.PortNumber -eq $tlsClientPort}).Count -eq 0) { $sslBindingsTestDetail += "SSL Binding missing for port $tlsClientPort, Certificate Authentication will fail.`n"; } $httpsPort = $adfsProperties.HttpsPort if (($adfsSslBindings | where {$_.PortNumber -eq $httpsPort}).Count -eq 0) { $sslBindingsTestDetail += "SSL Binding missing for port $httpsPort, AD FS requests will fail."; } $sslOutputs.Set_Item($sslBindingsKey, $adfsSslBindings) } else { if ($adfsVersion -eq $adfs2x) { Import-Module WebAdministration; #for ADFS 2.0, we need to find the SSL bindings. $httpsPort = GetHttpsPort $sslBinding = GetSslBinding if ($sslBinding -eq $null) { $sslBindingsTestDetail += "SSL Binding missing for port " + $httpsPort.ToString() + ", AD FS requests will fail."; } else { $sslOutputs.Set_Item($sslBindingsKey, $sslBinding) } } } $sslBindingsTestResult.Output = $sslOutputs if ($sslBindingsTestDetail) { $sslBindingsTestResult.Result = [ResultType]::Fail; $sslBindingsTestResult.Detail = $sslBindingsTestDetail; } return $sslBindingsTestResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } function Test-AdfsCertificates () { $primaryCertificateTypes = @("Service-Communications", "Token-Decrypting", "Token-Signing", "SSL") $secondaryCerticateTypes = $primaryCertificateTypes | ? {$_ -ne "Service-Communications" -and $_ -ne "SSL"} $primaryValues = @{$true=$primaryCertificateTypes; $false = $secondaryCerticateTypes} $results = @() $notRunTests = $false $notRunReason = "" if (-not (IsAdfsServiceRunning)) { $notRunTests = $true $notRunReason = "AD FS Service is not running" } try { if (Test-RunningOnAdfsSecondaryServer) { $notRunTests = $true $notRunReason = "This check does not run on AD FS Secondary Server" } } catch { $notRunTests = $true $notRunReason = "Cannot verify sync status of AD FS Server " + $_.Exception.ToString() } if ($notRunTests) { foreach ($isPrimary in $primaryValues.Keys) { foreach ($certType in $primaryValues.Item($isPrimary)) { $results += Generate-NotRunResults -certificateType $certType -notRunReason $notRunReason -isPrimary $isPrimary } } return $results } $certsToCheck = Get-AdfsCertificatesToTest foreach ($isPrimary in $primaryValues.Keys) { foreach ($certType in $primaryValues.Item($isPrimary)) { $adfsCerts = @($certsToCheck | where {$_.CertificateType -eq $certType -and $_.IsPrimary -eq $isPrimary}) foreach ($adfsCert in $adfsCerts) { try { $testsRanCount = 0 if ($null -eq $adfsCert) { $results += Generate-NotRunResults -certificateType $certType -notRunReason "Not Testing Certificate of type $certType`nIsPrimary: $isPrimary" -isPrimary $isPrimary continue } #Order Here is Relevant: If NotRunReason gets set, then other tests will inherit that reason, (and won't run) $notRunReason = "" $availableResult = Test-CertificateAvailable -certificateAvailable $adfsCert -certificateType $certType -isPrimary $isPrimary $results += $availableResult $testsRanCount++ $thumbprint = $adfsCert.Thumbprint $certToTest = $adfsCert.Certificate $storeName = $adfsCert.StoreName $storeLocation = $adfsCert.StoreLocation if ([String]::IsNullOrEmpty($notRunReason) -and (($availableResult.Result -eq [ResultType]::Fail) -or ($certToTest -eq $null))) { $notRunReason = "$certType certificate with thumbprint $thumbprint cannot be found." } $results += Test-CertificateSelfSigned -certSelfSigned $certToTest -certificateType $certType -isPrimary $isPrimary -notRunReason $notRunReason $testsRanCount++ $results += Test-CertificateHasPrivateKey -certHasPrivateKey $certToTest -certificateType $certType -isPrimary $isPrimary -notRunReason $notRunReason -storeName $storeName -storeLocation $storeLocation $testsRanCount++ $results += Test-CertificateExpired -certExpired $certToTest -certificateType $certType -isPrimary $isPrimary -notRunReason $notRunReason $testsRanCount++ $results += Test-CertificateCRL -certCrl $certToTest -certificateType $certType -isPrimary $isPrimary -notRunReason $notRunReason $testsRanCount++ if ([String]::IsNullOrEmpty($notRunReason) -and (Verify-IsCertExpired -isCertExpired $certToTest)) { $notRunReason = "Certificate is already expired." } $results += Test-CertificateAboutToExpire -certAboutToExpire $certToTest -certificateType $certType -isPrimary $isPrimary -notRunReason $notRunReason $testsRanCount++ } catch [Exception] { # catch unexpected exceptions, otherwise all test result generation will be blocked $reason = $_.Exception.Message $results += Generate-NotRunResults -certificateType $certType -notRunReason $reason -isPrimary $isPrimary -testsRanCount $testsRanCount -exceptionMessage $reason } } } } return $results } Function TestADFSDNSHostAlias { $testName = "CheckFarmDNSHostResolution" $farmNameKey = "FarmName" $resolvedHostKey = "ResolvedHost" $serviceAccountKey = "AdfsServiceAccount" $errorKey = "ErrorMessage" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } #Set this as a warning because WIA can succeed if the service account #has the SPNs for the host the DNS resolves to $testResult = New-Object TestResult -ArgumentList ($testName) $isAdfsServiceRunning = IsAdfsServiceRunning if ($isAdfsServiceRunning -eq $false) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "AD FS service is not running"; return $testResult; } $farmName = (Retrieve-AdfsProperties).HostName $serviceAccountName = (Get-WmiObject win32_service | Where-Object {$_.name -eq "adfssrv"}).StartName $resolutionResult = [System.Net.Dns]::GetHostEntry($farmName) $resolvedHostName = $resolutionResult.HostName if ($resolvedHostName -ne $farmName) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Farm Name '" + $farmName + "' is resolved as host '" + $resolvedHostName + "'. This might break windows integrated authentication scenarios.`n" $testResult.Detail += "Adfs Service Account: " + $serviceAccountName } else { $testResult.Result = [ResultType]::Pass } $testResult.Output = @{$farmNameKey = $farmName; $resolvedHostKey = $resolvedHostName; $serviceAccountKey = $serviceAccountName} return $testResult } catch [System.Net.Sockets.SocketException] { $testResult = New-Object TestResult -ArgumentList($testName); $testResult.Result = [ResultType]::Fail; $testResult.Detail = "Could not resolve the farm name {0} with exception '{1}'" -f $farmName, $_.Exception.Message; $testResult.Output = @{$farmNameKey = $farmName; $serviceAccountKey = $serviceAccountName; $errorKey = $_.Exception.ToString()} return $testResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestADFSDuplicateSPN { $testName = "CheckDuplicateSPN" $farmSPNKey = "ADFSFarmSPN" $serviceAccountKey = "ServiceAccount" $spnObjKey = "SpnObjects" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } #Verify there are no duplicate Service Principal Names (SPN) for the farm $testResult = New-Object TestResult -ArgumentList ($testName) if (IsLocalUser -eq $true) { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "Current user " + $env:USERNAME + " is not a domain account. Cannot execute this test" return $testResult } $isAdfsServiceRunning = IsAdfsServiceRunning if ($isAdfsServiceRunning -eq $false) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "AD FS service is not running"; return $testResult; } #Search both the service account and the holder of the SPN in the directory $adfsServiceAccount = (Get-WmiObject win32_service | Where-Object {$_.name -eq "adfssrv"}).StartName if ([String]::IsNullOrWhiteSpace($adfsServiceAccount)) { throw "ADFS Service account is null or empty. The WMI configuration is in an inconsistent state" } $serviceAccountParts = $adfsServiceAccount.Split('\\') if ($serviceAccountParts.Length -ne 2) { throw "Unexpected value of the service account $adfsServiceAccount. Expected in DOMAIN\\User format" } $serviceAccountDomain = $serviceAccountParts[0] $serviceSamAccountName = $serviceAccountParts[1] $farmName = (Retrieve-AdfsProperties).HostName $farmSPN = "host/" + $farmName $spnResults = GetObjectsFromAD -domain $serviceAccountDomain -filter "(servicePrincipalName=$farmSPN)" -GlobalCatalog $svcAcctSearcherResults = GetObjectsFromAD -domain $serviceAccountDomain -filter "(samAccountName=$serviceSamAccountName)" #root cause: no SPN at all if (($spnResults -eq $null) -or ($spnResults.Count -eq 0)) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "No objects in the directory with SPN $farmSPN are found." + [System.Environment]::NewLine + "AD FS Service Account: " + $adfsServiceAccount $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = "NONE"} return $testResult } #root cause: Could not find the service account. This should be very rare if (($svcAcctSearcherResults -eq $null) -or ($svcAcctSearcherResults.Count -eq 0)) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Did not find the service account $adfsServiceAccount in the directory" $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = $spnResults[0].Properties.distinguishedname} return $testResult } if ($svcAcctSearcherResults.Count -ne 1) { $testResult.Result = [ResultType]::Fail $testResult.Detail = = [String]::Format("Did not find 1 result for the service account in the directory. Found={0}", $svcAcctSearcherResults.Count) $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = $spnResults[0].Properties.distinguishedname} return $testResult } #root cause: multiple SPN if ($spnResults.Count -gt 1) { $testDetail = "Multiple objects are found in the directory with SPN:" + $farmSPN + [System.Environment]::NewLine + "Objects with SPN: " + [System.Environment]::NewLine $spnObjects = @() for ($i = 0; $i -lt $spnResults.Count; $i++) { $testDetail += $spnResults[$i].Properties.distinguishedname + [System.Environment]::NewLine $spnObjects += $spnResults[$i].Properties.distinguishedname } $testDetail += "AD FS Service Account: " + $adfsServiceAccount $testResult.Result = [ResultType]::Fail $testResult.Detail = $testDetail $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = $spnObjects} return $testResult } #root cause: SPN is in the wrong account if ($spnResults.Count -eq 1) { $spnDistinguishedName = $spnResults[0].Properties.distinguishedname $svcAccountDistinguishedName = $svcAcctSearcherResults[0].Properties.distinguishedname $spnObjectGuid = [Guid]$spnResults[0].Properties.objectguid.Item(0) $svcAccountObjectGuid = [Guid]$svcAcctSearcherResults[0].Properties.objectguid.Item(0) if ($spnObjectGuid -eq $svcAccountObjectGuid) { $testResult.Result = [ResultType]::Pass $testResult.Detail = "Found SPN in object: " + $spnDistinguishedName $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = $spnResults[0].Properties.distinguishedname} return $testResult } else { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Found SPN in object: " + $spnDistinguishedName + " but it does not correspond to service account " + $svcAccountDistinguishedName $testResult.Output = @{$farmSPNKey = $farmSPN; $serviceAccountKey = $adfsServiceAccount; $spnObjKey = $spnResults[0].Properties.distinguishedname} return $testResult } } } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestServiceAccountProperties { $testName = "TestServiceAccountProperties" $testResult = New-Object TestResult -ArgumentList ($testName) $serviceAcctKey = "AdfsServiceAccount" $userAcctCtrlKey = "AdfsServiceAccountUserAccountControl" $acctDisabledKey = "AdfsServiceAccountDisabled" $acctPwdExpKey = "AdfsServiceAccountPwdExpired" $acctLockedKey = "AdfsServiceAccountLockedOut" $testResult.Output = @{` $serviceAcctKey = $none; ` $userAcctCtrlKey = $none; ` $acctDisabledKey = $none; ` $acctPwdExpKey = $none; ` $acctLockedKey = $none } try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $Adfssrv = get-wmiobject win32_service | where {$_.Name -eq "adfssrv"} $UserName = ((($Adfssrv.StartName).Split("\"))[1]).ToUpper() $testResult.Output.Set_Item($serviceAcctKey, $Adfssrv.StartName) if (($UserName -ne "NETWORKSERVICE") -or ($UserName -ne "NETWORK SERVICE")) { $searcher = new-object DirectoryServices.DirectorySearcher([ADSI]"") $searcher.filter = "(&(objectClass=user)(sAMAccountName=$UserName))" $founduser = $searcher.findOne() if (-not $founduser) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Adfs Service Account: " + $Adfssrv.StartName + "`nNot found in Active Directory" return $testResult } if (-not $founduser.psbase.properties.useraccountcontrol) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Adfs Service Account: " + $Adfssrv.StartName + "`nHas no useraccountcontrol property" return $testResult } $testResult.Output.Set_Item($userAcctCtrlKey, $founduser.psbase.properties.useraccountcontrol[0]) $accountDisabled = $founduser.psbase.properties.useraccountcontrol[0] -band 0x02 $testResult.Output.Set_Item($acctDisabledKey, $accountDisabled) $pwExpired = $founduser.psbase.properties.useraccountcontrol[0] -band 0x800000 $testResult.Output.Set_Item($acctPwdExpKey, $pwExpired) $accountLockedOut = $founduser.psbase.properties.useraccountcontrol[0] -band 0x0010 $testResult.Output.Set_Item($acctLockedKey, $accountLockedOut) if ($accountDisabled -or $pwExpired -or $accountLockedOut) { $accountEnabled = -not $accountDisabled $testResult.Result = [ResultType]::Fail $testResult.Detail = "Adfs Service Account: " + $Adfssrv.StartName + "`nPassword Expired:$pwExpired`nAccount Enabled: $accountEnabled`nAccount Locked Out: $accountLockedOut" return $testResult } $testResult.Result = [ResultType]::Pass return $testResult } else { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "ADFS Service Account: " + $Adfssrv.StartName return $testResult } } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestAppPoolIDMatchesServiceID() { $adfsVersion = Get-AdfsVersion $testName = "TestAppPoolIDMatchesServiceID" $testResult = New-Object TestResult -ArgumentList ($testName) $pipelineModeKey = "AdfsAppPoolPipelineMode" if ($adfsVersion -ne $adfs2x) { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "Test only to be run on ADFS 2.0" return $testResult } try { Push-Location $env:windir\system32\inetsrv -ErrorAction SilentlyContinue $PipelineMode = .\appcmd list apppool adfsapppool /text:pipelinemode $testResult.Output = @{$pipelineModeKey = $PipelineMode} If ($PipelineMode.ToUpper() -eq "INTEGRATED") { $testResult.Result = [ResultType]::Pass return $testResult } $testResult.Result = [ResultType]::Fail $testResult.Detail = "Adfs Pipelinemode: " + $PipelineMode return $testResult } catch [System.Management.Automation.CommandNotFoundException] { $testResult.Result = [ResultType]::NotRun $errStr = "Could not execute appcmd.exe because it could not be found." $testResult.Detail = $errStr $testResult.ExceptionMessage = $errStr return $testResult } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } finally { Pop-Location } } Function TestComputerNameEqFarmName { $testName = "TestComputerNameEqFarmName" $testResult = New-Object TestResult -ArgumentList ($testName) $farmNameKey = "AdfsFarmName" $compNameKey = "ComputerName" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $computerName = ($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN).ToUpper() $farmName = ((Retrieve-AdfsProperties).HostName).ToUpper() $testResult.Output = @{$farmNameKey = $farmName; $compNameKey = $computerName} if ($computerName -eq $farmName) { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Computer Name: $computerName`nADFS Farm Name: $farmName" return $testResult } $testResult.Result = [ResultType]::Pass return $testResult } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestSSLUsingADFSPort() { $adfsVersion = Get-AdfsVersion $testName = "TestSSLUsingADFSPort" $testResult = New-Object TestResult -ArgumentList ($testName) $sslTpKey = "AdfsSSLCertThumbprint" $httpsPortKey = "AdfsHttpsPort" $sslBindingsKey = "AdfsSSLBindings" $testResult.Output = @{$sslTpKey = $none; $httpsPortKey = $none; $sslBindingsKey = $none} try { if ($adfsVersion -ne $adfs2x) { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "Test only to be run on ADFS 2.0 Machine" return $testResult } if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $httpsPort = (Retrieve-AdfsProperties).HttpsPort $sslBinding = GetSslBinding $AdfsCertThumbprint = $sslBinding.Thumbprint $SSLPortMatch = get-webbinding | where-object {$_.certificateHash -eq $AdfsCertThumbprint} | where-object {$_.bindingInformation.Contains($httpsPort)} $SSLPortMatchStrs = @() $SSLPortMatch | foreach { $strs += $_.ToString() } $testResult.Output.Set_Item($sslTpKey, $AdfsCertThumbprint) $testResult.Output.Set_Item($httpsPortKey, $httpsPort) if (($SSLPortMatch | measure).Count -gt 0) { $testResult.Output.Set_Item($sslBindingsKey, $SSLPortMatchStrs) $sslMatches = "SSL Port Matches:`n" foreach ($sslMatch in $sslPortMatch) { if ($sslMatch.ItemXPath.Contains("Default Web Site")) { $testResult.Result = [ResultType]::Pass return $testResult } $sslPortMatch += $sslMatch.ItemXPath.Split("'")[1] + "`n" } $testResult.Result = [ResultType]::Fail $testResult.Detail = "SSL Binding with certificate with Thumbprint: $AdfsCertThumbprint and Port: $httpsPort`n" + $sslMatches return $testResult } else { $testResult.Result = [ResultType]::Fail $testResult.Detail = "No SSL Binding for Certificate with Thumbprint: $AdfsCertThumbprint and Port: $httpsPort" return $testResult } } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestSSLCertSubjectContainsADFSFarmName() { $adfsVersion = Get-AdfsVersion $testName = "TestSSLCertSubjectContainsADFSFarmName" $testResult = New-Object TestResult -ArgumentList ($testName) $farmNameKey = "ADFSFarmName" $sslTpKey = "SslCertThumbprints" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $sslCertHashes = @() switch ($adfsVersion) { {($_ -eq $adfs3) -or ($_ -eq $adfs4)} { foreach ($sslCert in (Get-AdfsSslCertificate)) { if (-not $sslCertHashes.Contains($sslCert.CertificateHash)) { $sslCertHashes += $sslCert.CertificateHash } } } $adfs2x { get-website -name "Default Web Site" | get-webbinding | where {-not [String]::IsNullOrEmpty($_.certificateHash)} | foreach { $sslCertHashes += $_.certificateHash} } default { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "Invalid ADFS Version" return $testResult } } $farmName = (Retrieve-AdfsProperties).HostName $failureOutput = "ADFS Farm Name: $farmName`n" $testResult.Output = @{$farmNameKey = $farmName; $sslTpKey = $sslCertHashes} foreach ($thumbprint in $sslCertHashes) { $certToCheck = (dir Cert:\LocalMachine\My\$thumbprint) #check if cert SAN references ADFS Farmname # if it has an SAN that does not include the ADFS Farmname # fail the check $failureOutput += "Thumbprint: $thumbprint`n" $failureOutput += "Subject: " + $certToCheck.Subject + "`n" $sanExt = $certToCheck.Extensions | Where-Object {$_.Oid.FriendlyName -match "subject alternative name"} If (($sanExt | measure-object).Count -gt 0) { $failureOutput += "SANs:`n" $sanObjs = new-object -ComObject X509Enrollment.CX509ExtensionAlternativeNames $altNamesStr = [System.Convert]::ToBase64String($sanExt.RawData) $sanObjs.InitializeDecode(1, $altNamesStr) Foreach ($SAN in $sanObjs.AlternativeNames) { $strValue = $SAN.strValue $searchFilter = $strValue -replace "\*", "[\w-]+" $searchFilter = "^" + $searchFilter + "$" if ($farmName -match $searchFilter) { $testResult.Result = [ResultType]::Pass return $testResult } $failureOutput += " $strValue`n" } } else { if ($certToCheck.Subject) { $searchFilter = $certToCheck.Subject.Split(",=")[1] $searchFilter = $searchFilter -replace "\*", "[\w-]+" $searchFilter = "^" + $searchFilter + "$" if ($farmName -match $searchFilter) { $testResult.Result = [ResultType]::Pass return $testResult } } } } $testResult.Result = [ResultType]::Fail $testResult.Detail = $failureOutput return $testResult } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestAdfsAuditPolicyEnabled { $testName = "TestAdfsAuditPolicyEnabled" $testResult = New-Object TestResult -ArgumentList ($testName) $auditSettingKey = "MachineAuditPolicy" $stsAuditSetting = "StsAuditConfig" $testResult.Output = @{` $auditSettingKey = $none; $stsAuditSetting = $none; } try { #ADFS Audit Enabled Check $auditEventType="Microsoft.Identity.Health.Adfs.NetUtil.AuditEventType"; $auditEventType=[Microsoft.Identity.Health.Adfs.NetUtil]::CheckAudit(); $testResult.Output.Set_Item($auditSettingKey, $auditEventType); if (($auditEventType.HasFlag([Microsoft.Identity.Health.Adfs.NetUtil+AuditEventType]::POLICY_AUDIT_EVENT_SUCCESS)) -And ($auditEventType.HasFlag([Microsoft.Identity.Health.Adfs.NetUtil+AuditEventType]::POLICY_AUDIT_EVENT_FAILURE))) { #So far, passing if we have the right policy $testResult.Result = [ResultType]::Pass } else { $testResult.Result = [ResultType]::Fail $testResult.Detail = "Audits are not configured for Usage data collection : Expected 'Success and Failure', Actual='$auditEventType'" } #End ADFS Audit Enabled Check #and verify the STS audit setting $role = Get-AdfsRole if ($role -eq $adfsRoleSTS) { $adfsSyncSetting = (Get-ADFSSyncProperties).Role if (IsAdfsSyncPrimaryRole) { $audits = (Retrieve-AdfsProperties).LogLevel | where {$_ -like "*Audits"} | Sort-Object $auditsStr = "" foreach($audit in $audits) { $auditsStr = $auditsStr + $audit + ";" } $testResult.Output.Set_Item($stsAuditSetting, $auditsStr); if ($audits.Count -ne 2) { $testResult.Result = [ResultType]::Fail $testResult.Detail = $testResult.Detail + " ADFS Audits are not configured : Expected 'FailureAudits;SuccessAudits', Actual='$auditsStr'" } } else { #Did not run on a secondary. Cannot make any assertions on whether this part of the test failed or not $testResult.Output.Set_Item($stsAuditSetting, "(Cannot get this data from secondary node)"); } } else { #Not run on an STS $testResult.Result = [ResultType]::Pass $testResult.Output.Set_Item($stsAuditSetting, "N/A"); } return $testResult } catch [Exception] { $testResult.Result = [ResultType]::NotRun $testResult.Detail = $_.Exception.Message $testResult.ExceptionMessage = $_.Exception.Message return $testResult } } Function TestAdfsRequestToken($retryThreshold = 5, $sleepSeconds = 3) { $testName = "TestAdfsRequestToken" $testResult = New-Object TestResult -ArgumentList ($testName) $errorKey = "ErrorMessage" $testResult.Output = @{$errorKey = "NONE"} $targetEndpointUri = "" $targetRpId = "" try { $targetRpId = Get-ADFSIdentifier if ($targetRpId -eq $null) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "Could not find the STS identifier" return $testResult } $targetEndpointUri = Get-FirstEnabledWIAEndpointUri if ($targetEndpointUri -eq $null) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "No Windows Integrated Endpoints were enabled. No token requested" return $testResult } } catch [Exception] { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "Unable to initialize token request. Caught Exception: " + $_.Exception.Message; $testResult.Output.Set_Item($errorKey, $_.Exception.Message) return $testResult; } $exceptionDetail = "" for ($i = 1; $i -le $retryThreshold; $i++) { try { $tokenString = "" #attempt to load first the synthetic transactions library, and fallback to the simpler version ipmo .\Microsoft.Identity.Health.Adfs.SyntheticTransactions.dll -ErrorAction SilentlyContinue -ErrorVariable synthTxErrVar if ($synthTxErrVar -ne $null) { [Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} $sslBinding = GetSslBinding $tokenString = Test-AdfsServerToken -FederationServer $sslBinding.HostNamePort -AppliesTo $targetRpId $testResult.Result = [ResultType]::Pass; } else { $token = Test-AdfsRequestTokenFromSelf -AppliesToRpIdentifier $targetRpId -EndpointUri $targetEndpointUri $testResult.Result = [ResultType]::Pass; if ($token -is [System.IdentityModel.Tokens.GenericXmlSecurityToken]) { $xmlToken = $token -as [System.IdentityModel.Tokens.GenericXmlSecurityToken] $tokenString = $xmlToken.TokenXml.OuterXml } else { $tokenString = $token.ToString() } } $testResult.Detail = "Received Token Length: " + $tokenString.Length + "`nTotal Attempts: $i" return $testResult; } catch [Exception] { $exceptionDetail = "Attempt: $i`nLatest Exception caught while requesting token: " + $_.Exception.Message + "`n" Start-Sleep $sleepSeconds } } $testResult.Result = [ResultType]::Fail; $testResult.Detail = $exceptionDetail $testResult.Output.Set_Item($errorKey, $exceptionDetail) return $testResult; } Function TestTrustedDevicesCertificateStore { $testName = "TestTrustedDevicesCertificateStore"; Out-Verbose "Checking the AdfsTrustedDevices certificate store."; $testResult = New-Object TestResult -ArgumentList($testName); try { if ([bool](Get-Item Cert:\LocalMachine).StoreNames["AdfsTrustedDevices"] -ne $true) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "The AdfsTrustedDevices certificate store does not exist."; } return $testResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestAdfsPatches { Out-Verbose "Testing for required Windows Server patches for AD FS."; $testName = "TestAdfsPatches"; $patchesOutputKey = "MissingAdfsPatches"; $testResult = New-Object TestResult -ArgumentList($testName); try { $osVersion = Get-OsVersion; Out-Verbose "Detected OS version as $osVersion."; if ($osVersion -ne [OSVersion]::WS2012R2) { Out-Verbose "AD FS patches are only required for Windows Server 2012 R2"; $testResult.Result = [ResultType]::NotRun; return $testResult; } $patches = @( @{"PatchId" = "KB2919355"; "PatchLink" = "https://support.microsoft.com/en-us/help/2919355/"}, @{"PatchId" = "KB3000850"; "PatchLink" = "https://support.microsoft.com/en-us/help/3000850/"}, @{"PatchId" = "KB3013769"; "PatchLink" = "https://support.microsoft.com/en-us/help/3013769/"}, @{"PatchId" = "KB3020773"; "PatchLink" = "https://support.microsoft.com/en-us/help/3020773/"} ); $notinstalled = @(); foreach ($patch in $patches) { Out-Verbose "Checking patch $($patch.PatchId)"; $hotfix = Get-HotFix -Id $patch.PatchId -ErrorAction SilentlyContinue; if (!$hotfix) { Out-Verbose "Could not find the following patch: $($patch.PatchId)"; $notinstalled += $patch; } } if ($notinstalled.Count -ne 0) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "There were missing patches that are not installed."; $testResult.Output = @{$patchesOutputKey = $notinstalled}; } return $testResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestServicePrincipalName { $testName = "TestServicePrincipalName"; Out-Verbose "Checking service principal name." if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $testResult = New-Object TestResult -ArgumentList($testName); try { if (IsLocalUser -eq $true) { $testResult.Result = [ResultType]::NotRun $testResult.Detail = "Current user " + $env:USERNAME + " is not a domain account. Cannot execute this test" return $testResult } if (!(IsAdfsServiceRunning)) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "AD FS service is not running"; return $testResult; } $adfsServiceAccount = (Get-WmiObject win32_service | Where-Object {$_.name -eq $adfsServiceName}).StartName; if ([String]::IsNullOrWhiteSpace($adfsServiceAccount)) { throw "ADFS Service account is null or empty. The WMI configuration is in an inconsistent state"; } Out-Verbose "Checking format of ADFS service account. $adfsServiceAccount"; if (IsUserPrincipalNameFormat($adfsServiceAccount)) { Out-Verbose "Detected UPN format."; $serviceSamAccountParts = $adfsServiceAccount.Split('@'); $serviceSamAccountName = $serviceSamAccountParts[0]; $serviceAccountDomain = $serviceSamAccountParts[1]; } else { $serviceAccountParts = $adfsServiceAccount.Split('\\'); if ($serviceAccountParts.Length -ne 2) { throw "Unexpected value of the service account $adfsServiceAccount. Expected in DOMAIN\\User format"; } $serviceAccountDomain = $serviceAccountParts[0]; $serviceSamAccountName = $serviceAccountParts[1]; } Out-Verbose "ADFS service account = $serviceSamAccountName"; Out-Verbose "Retrieving LDAP path of service account."; $svcAccountSearchResults = GetObjectsFromAD -domain $serviceAccountDomain -filter "(samAccountName=$serviceSamAccountName)"; $ldapPath = $svcAccountSearchResults.Path -Replace "^LDAP://", ""; Out-Verbose "Service account LDAP path = $ldapPath"; $farmName = (Retrieve-AdfsProperties).HostName; Out-Verbose "Checking existence of HOST SPN"; $ret = Invoke-Expression "setspn -f -q HOST/$farmName"; Out-Verbose "SPN query result = $ret"; if ($ret.Contains("No such SPN found.")) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "No such SPN was found for $farmName"; return $testResult; } elseif ($ret.Contains("Existing SPN found!") -and !$ret.Contains($ldapPath)) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "An existing SPN was found for HOST/$farmName but it did not resolve to the ADFS service account."; return $testResult; } Out-Verbose "Successfully checked HOST SPN."; Out-Verbose "Checking existence of HTTP SPN"; $ret = Invoke-Expression "setspn -f -q HTTP/$farmName"; Out-Verbose "SPN query result = $ret"; if ($ret.Contains("No such SPN found.")) { # HTTP does not need to resolve. Out-Verbose "Unable to find HTTP SPN, this does not have to resolve."; } elseif ($ret.Contains("Existing SPN found!") -and !$ret.Contains($ldapPath)) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "An existing SPN was found for HTTP/$farmName but it did not resolve to the ADFS service account."; } return $testResult } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestProxyTrustPropagation { Param( [string[]] $adfsServers = $null ) $testName = "TestProxyTrustPropagation"; try { $testResult = New-Object TestResult -ArgumentList $testName; if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName; } if ($adfsServers -eq $null -or $adfsServers.Count -eq 0) { $testResult.Result = [ResultType]::Warning; $message = "No AD FS farm information was provided. Specify the list of servers in your farm using the -adfsServers flag."; Out-Warning $message; $testResult.Detail = $message; return $testResult; } Out-Verbose "Verifying that the proxy trust is propogating between the AD FS servers in the farm."; Out-Verbose "Farm information: $adfsServers"; $certificatesInPrimaryStore = @(GetCertificatesFromAdfsTrustedDevices); $ErroneousCertificates = @{}; foreach ($server in $adfsServers) { $session = New-PSSession -ComputerName $server -ErrorAction SilentlyContinue; if ($session -eq $null) { Out-Warning "There was a problem connecting to $server, skipping this server." continue; } Out-Verbose "Checking $server"; $def = "`$ctlStoreName = `"$ctlStoreName`"; `$localMachine = `"$localMachine`"; Function VerifyCertificatesArePresent { ${function:VerifyCertificatesArePresent} }; Function GetCertificatesFromAdfsTrustedDevices { ${function:GetCertificatesFromAdfsTrustedDevices} }" $missingCerts = Invoke-Command -Session $session -ScriptBlock { param( $certificatesInPrimaryStore, $functionDefinition ) Invoke-Expression $functionDefinition; return VerifyCertificatesArePresent -certificatesInPrimaryStore $certificatesInPrimaryStore } -ArgumentList @($certificatesInPrimaryStore, $def) if ($missingCerts.Count -ne 0) { $ErroneousCertificates.Add($server, $missingCerts); } if ($session) { Remove-PSSession $Session } } if ($ErroneousCertificates.Count -ne 0) { $testResult.Result = [ResultType]::Fail; $testResult.Detail = "There were missing certificates on some of the secondary servers. There may be an issue with proxy trust propogation." $testResult.Output = @{"ErroneousCertificates" = $ErroneousCertificates}; } return $testResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception; } } # Check office 365 endpoints Function TestOffice365Endpoints() { $testName = "CheckOffice365Endpoints" #Keys for strongly typed Result data $wstrustUsernameKey = "WSTrust2005UsernameMixedEnabled" $wsTrustUsernameProxyKey = "WSTrust2005UsernameMixedProxyEnabled" $wsTrustWindowsKey = "WSTrust2005WindowsTransportEnabled" $wsTrustWindowsProxyKey = "WSTrust2005WindowsTransportProxyEnabled" $passiveKey = "PassiveEnabled" $passiveProxyKey = "PassiveProxyEnabled" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $lyncEndpointsTestResult = New-Object TestResult -ArgumentList($testName); $isAdfsServiceRunning = IsAdfsServiceRunning if ($isAdfsServiceRunning -eq $false) { $lyncEndpointsTestResult.Result = [ResultType]::NotRun; $lyncEndpointsTestResult.Detail = "AD FS service is not running"; return $lyncEndpointsTestResult; } $adfsProperties = Retrieve-AdfsProperties if ( $null -eq $adfsProperties ) { $lyncEndpointsTestResult.Result = [ResultType]::Fail; $lyncEndpointsTestResult.Detail = "Unable to read adfs properties"; return $lyncEndpointsTestResult; } $wstrust2005windowstransport = Get-AdfsEndpoint -AddressPath /adfs/services/trust/2005/windowstransport; $wstrust2005usernamemixed = Get-AdfsEndpoint -AddressPath /adfs/services/trust/2005/usernamemixed; $passive = Get-AdfsEndpoint -AddressPath /adfs/ls/; if ($wstrust2005windowstransport.Enabled -eq $false -or $wstrust2005windowstransport.Proxy -eq $false) { $lyncEndpointsTestResult.Result = [ResultType]::Fail; $lyncEndpointsTestResult.Detail = "Lync related endpoint is not configured properly; extranet users can experience authentication failure.`n"; } if ($wstrust2005usernamemixed.Enabled -eq $false) { $lyncEndpointsTestResult.Result = [ResultType]::Fail; $lyncEndpointsTestResult.Detail += "Exchange Online related endpoint is not enabled. This will prevent rich clients such as Outlook to connect.`n"; } if ($passive.Enabled -eq $false) { $lyncEndpointsTestResult.Result = [ResultType]::Fail; $lyncEndpointsTestResult.Detail += "Passive endpoint is not enabled. This will prevent browser-based services such as Sharepoint Online or OWA to fail.`n"; } if ($lyncEndpointsTestResult.Result -eq [ResultType]::Fail) { $lyncEndpointsTestResult.Detail += "Endpoint Status:`n" ` + $wstrust2005usernamemixed.AddressPath + "`n Enabled: " + $wstrust2005usernamemixed.Enabled + "`n Proxy Enabled: " + $wstrust2005usernamemixed.Proxy + "`n" ` + $wstrust2005windowstransport.AddressPath + "`n Enabled: " + $wstrust2005windowstransport.Enabled + "`n Proxy Enabled: " + $wstrust2005windowstransport.Proxy + "`n" ` + $passive.AddressPath + "`n Enabled: " + $passive.Enabled + "`n Proxy Enabled: " + $passive.Proxy + "`n"; } $lyncEndpointsTestResult.Output = @{` $wstrustUsernameKey = $wstrust2005usernamemixed.Enabled; ` $wsTrustUsernameProxyKey = $wstrust2005usernamemixed.Proxy; ` $wsTrustWindowsKey = $wstrust2005windowstransport.Enabled; ` $wsTrustWindowsProxyKey = $wstrust2005windowstransport.Proxy; ` $passiveKey = $passive.Enabled; ` $passiveProxyKey = $passive.Proxy } return $lyncEndpointsTestResult; } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } Function TestADFSO365RelyingParty { $testName = "TestADFSO365RelyingParty" $aadRpId = "urn:federation:MicrosoftOnline" $rpIdKey = "MicrosoftOnlineRPID" $rpNameKey = "MicrosoftOnlineRPDisplayName" $rpEnabledKey = "MicrosoftOnlineRPEnabled" $rpSignAlgKey = "MicrosoftOnlineRPSignatureAlgorithm" try { if (Test-RunningOnAdfsSecondaryServer) { return Create-NotRunOnSecondaryTestResult $testName } $testResult = New-Object TestResult -ArgumentList ($testName) $testResult.Output = @{` $rpIdKey = $aadRpId ; ` $rpNameKey = $none ; ` $rpEnabledKey = $none ; ` $rpSignAlgKey = $none } $isAdfsServiceRunning = IsAdfsServiceRunning if ($isAdfsServiceRunning -eq $false) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = "AD FS service is not running"; return $testResult; } $aadRpName = "Microsoft Office 365 Identity Platform" $aadRp = Get-ADFSRelyingPartyTrust -Identifier $aadRpId if ($aadRp -eq $null) { $testResult.Result = [ResultType]::NotRun; $testResult.Detail = $aadRpName + "Relying Party trust is missing`n"; $testResult.Detail += "Expected Relying Party Identifier: " + $aadRpId; return $testResult; } $aadRpDetail = $false $testPassed = $true $testResult.Detail = "" if (-not $aadRp.Enabled) { $testResult.Result = [ResultType]::Fail; $testResult.Detail += $aadRpName + " Relying Party trust is disabled`n" $testResult.Detail += "Relying Party Trust Display Name: " + $aadRp.Name + "`n"; $testResult.Detail += "Relying Party Trust Identifier: " + $aadRp.Identifier + "`n"; $aadRpDetail = $true $testPassed = $false } if ($aadRp.SignatureAlgorithm -ne "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") { $testResult.Result = [ResultType]::Fail; $testResult.Detail += $aadRpName + " Relying Party token signature algorithm is not SHA-256`n"; if (-not $aadRpDetail) { $testResult.Detail += "Relying Party Trust Display Name: " + $aadRp.Name + "`n"; $testResult.Detail += "Relying Party Trust Identifier: " + $aadRp.Identifier + "`n"; } $testResult.Detail += "Relying Party Trust Signature Algorithm: " + $aadRp.SignatureAlgorithm; $testPassed = $false } if ($testPassed) { $testResult.Result = [ResultType]::Pass } $testResult.Output.Set_Item($rpNameKey, $aadRp.Name) $testResult.Output.Set_Item($rpEnabledKey, $aadRp.Enabled) $testResult.Output.Set_Item($rpSignAlgKey, $aadRp.SignatureAlgorithm) return $testResult } catch [Exception] { return Create-ErrorExceptionTestResult $testName $_.Exception } } |