From 40f05ac8ce38b07f39f874f5692bf3b99a4f8257 Mon Sep 17 00:00:00 2001 From: Thiago Beier <146458544+thiagogbeier@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:59:12 -0400 Subject: [PATCH 01/11] v1.2 - 06/09/2025 Updated Test-NDESServiceAccountProperties function to support MSA (Managed Service Account detection / legacy service accounts detection), replaced $ResultsText call from a file (not it's an array) by Thiago Beier https://github.com/thiagogbeier --- Validate-NDESConfiguration.ps1 | 899 ++++++++++++++++++--------------- 1 file changed, 487 insertions(+), 412 deletions(-) diff --git a/Validate-NDESConfiguration.ps1 b/Validate-NDESConfiguration.ps1 index 4d35c54..9e0cee6 100644 --- a/Validate-NDESConfiguration.ps1 +++ b/Validate-NDESConfiguration.ps1 @@ -20,51 +20,52 @@ Where possible, a link and section description will be provided. https://learn.microsoft.com/en-us/troubleshoot/mem/intune/certificates/troubleshoot-scep-certificate-ndes-policy-module .NOTES -v1.0 - 1/29/2024 - Initial release. Copy/paste from ODC (https://aka.ms/IntuneODC) to allow for standalone use. -v1.1 - 5/4/2024 - Updated to support system account as service account +v1.0 - 01/29/2024 - Initial release. Copy/paste from ODC (https://aka.ms/IntuneODC) to allow for standalone use. +v1.1 - 05/04/2024 - Updated to support system account as service account +v1.2 - 06/09/2025 - Updated Test-NDESServiceAccountProperties function to support MSA (Managed Service Account detection / legacy service accounts detection), replaced $ResultsText call from a file (not it's an array) by Thiago Beier https://github.com/thiagogbeier #> -[CmdletBinding(DefaultParameterSetName="Unattended")] +[CmdletBinding(DefaultParameterSetName = "Unattended")] Param( -[parameter(Mandatory=$false,ParameterSetName="Unattended")] -[alias("ua","silent","s","unattended")] -[switch]$unattend, + [parameter(Mandatory = $false, ParameterSetName = "Unattended")] + [alias("ua", "silent", "s", "unattended")] + [switch]$unattend, -[parameter(Mandatory=$true,ParameterSetName="NormalRun")] -[alias("sa")] -[string]$NDESServiceAccount = "", + [parameter(Mandatory = $true, ParameterSetName = "NormalRun")] + [alias("sa")] + [string]$NDESServiceAccount = "", -[parameter(Mandatory=$true,ParameterSetName="NormalRun")] -[alias("ca")] -[ValidateScript({ - $Domain = ((Get-WmiObject Win32_ComputerSystem).domain).split(".\")[0] - if ($_ -match $Domain) { - $true - } - else { - Throw "The Network Device Enrollment Server and the Certificate Authority are not members of the same Active Directory domain. This is an unsupported configuration." + [parameter(Mandatory = $true, ParameterSetName = "NormalRun")] + [alias("ca")] + [ValidateScript({ + $Domain = ((Get-WmiObject Win32_ComputerSystem).domain).split(".\")[0] + if ($_ -match $Domain) { + $true + } + else { + Throw "The Network Device Enrollment Server and the Certificate Authority are not members of the same Active Directory domain. This is an unsupported configuration." + } } - } -)] -[string]$IssuingCAServerFQDN, + )] + [string]$IssuingCAServerFQDN, -[parameter(Mandatory=$true,ParameterSetName="NormalRun")] -[alias("t")] -[string]$SCEPUserCertTemplate, + [parameter(Mandatory = $true, ParameterSetName = "NormalRun")] + [alias("t")] + [string]$SCEPUserCertTemplate, -[parameter(ParameterSetName="Help")] -[alias("h","?","/?")] -[switch]$help, + [parameter(ParameterSetName = "Help")] + [alias("h", "?", "/?")] + [switch]$help, -[parameter(ParameterSetName="Help")] -[alias("u")] -[switch]$usage, + [parameter(ParameterSetName = "Help")] + [alias("u")] + [switch]$usage, -[switch]$toStdOut, -[switch]$SkipHTML, -[switch]$ODC + [switch]$toStdOut, + [switch]$SkipHTML, + [switch]$ODC ) @@ -82,89 +83,81 @@ Param( # Will write in default white text with a severity of 'Information' Write-Interactive "hi" #> -function Write-Interactive -{ +function Write-Interactive { [CmdletBinding()] [Alias()] [OutputType([int])] Param ( # Message help description - [Parameter(Mandatory=$true, - ValueFromPipeline, - ValueFromPipelineByPropertyName=$true, - Position=0)] + [Parameter(Mandatory = $true, + ValueFromPipeline, + ValueFromPipelineByPropertyName = $true, + Position = 0)] $ResultBlob, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] $Result ) - Begin - { + Begin { } - Process - { + Process { - switch ($($Script:ResultBlob.GetType()).FullName) { - System.Management.Automation.PSCustomObject - { + switch ($($Script:ResultBlob.GetType()).FullName) { + System.Management.Automation.PSCustomObject { - Write-Host "Rule: " -ForegroundColor Gray -NoNewline - Write-Host $ResultBlob.RuleId -ForegroundColor White - Write-Host "Description: " -ForegroundColor Gray -NoNewline - Write-Host $ResultBlob.RuleDescription -ForegroundColor White + Write-Host "Rule: " -ForegroundColor Gray -NoNewline + Write-Host $ResultBlob.RuleId -ForegroundColor White + Write-Host "Description: " -ForegroundColor Gray -NoNewline + Write-Host $ResultBlob.RuleDescription -ForegroundColor White - Write-Host "Result: " -ForegroundColor Gray -NoNewline - switch($ResultBlob.CheckResult) { - "Passed" - { - Write-Host $ResultBlob.CheckResult -ForegroundColor Green - } - "Failed" - { - Write-Host $ResultBlob.CheckResult -ForegroundColor Magenta # Red - } - "Warning" - { - Write-Host $ResultBlob.CheckResult -ForegroundColor Yellow + Write-Host "Result: " -ForegroundColor Gray -NoNewline + switch ($ResultBlob.CheckResult) { + "Passed" { + Write-Host $ResultBlob.CheckResult -ForegroundColor Green + } + "Failed" { + Write-Host $ResultBlob.CheckResult -ForegroundColor Magenta # Red + } + "Warning" { + Write-Host $ResultBlob.CheckResult -ForegroundColor Yellow + } } - } - Write-Host "Message: " -ForegroundColor Gray -NoNewline - Write-Host "$($ResultBlob.CheckResultMessage)`r`n" -ForegroundColor White + Write-Host "Message: " -ForegroundColor Gray -NoNewline + Write-Host "$($ResultBlob.CheckResultMessage)`r`n" -ForegroundColor White - } - - default { - switch($Result){ - - { ($_ -in ( "Passed", "1") )} { - $ResultBlob | Write-Host -ForegroundColor White - } - { ($_ -in ( "Warning", "2") )} { - $ResultBlob | Write-Host -ForegroundColor Yellow - } - { ($_ -in ( "Failed", "3") )} { - $ResultBlob | Write-Host -ForegroundColor Red } - - Default { - $ResultBlob | Write-Host -ForegroundColor White + + default { + switch ($Result) { + + { ($_ -in ( "Passed", "1") ) } { + $ResultBlob | Write-Host -ForegroundColor White + } + { ($_ -in ( "Warning", "2") ) } { + $ResultBlob | Write-Host -ForegroundColor Yellow + } + { ($_ -in ( "Failed", "3") ) } { + $ResultBlob | Write-Host -ForegroundColor Red + } + + Default { + $ResultBlob | Write-Host -ForegroundColor White + } + } } - } - } - } + } } - End - { + End { } } function New-LogEntry { - <# + <# .SYNOPSIS Script-wide logging function .DESCRIPTION @@ -183,77 +176,78 @@ function New-LogEntry { Set $global:LogName at the beginning of the script #> - [CmdletBinding()] - param( - [parameter(Mandatory=$true, ValueFromPipeline = $true, Position = 0)] - [string]$Message, + [CmdletBinding()] + param( + [parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] + [string]$Message, - [Parameter(Position = 1)] - # 1 = Information - # 2 = Warning - # 3 = Error - # 4 = Verbose - [ValidateSet(1,2,3,4)] - [string]$Severity = '1', + [Parameter(Position = 1)] + # 1 = Information + # 2 = Warning + # 3 = Error + # 4 = Verbose + [ValidateSet(1, 2, 3, 4)] + [string]$Severity = '1', - [Parameter()] - # create log in format - [string]$LogName = $Script:LogFilePath, - - # Write plain text to stdout instead of colorful text to host - [Parameter()] - [switch] - $WriteStdOut, - - # Skip HTML output - [Parameter()] - [Switch] - $NoHTML + [Parameter()] + # create log in format + [string]$LogName = $Script:LogFilePath, + + # Write plain text to stdout instead of colorful text to host + [Parameter()] + [switch] + $WriteStdOut, + + # Skip HTML output + [Parameter()] + [Switch] + $NoHTML - ) + ) - BEGIN { - if ( ($null -eq $LogName) -or ($LogName -eq "")) { Write-Error "Please set variable `$script`:LogFilePath." } - } - PROCESS { - # only log verbose if flag is set - if ( ($Level -eq "4") -and ( -not ($debugMode) ) ) { + BEGIN { + if ( ($null -eq $LogName) -or ($LogName -eq "")) { Write-Error "Please set variable `$script`:LogFilePath." } + } + PROCESS { + # only log verbose if flag is set + if ( ($Level -eq "4") -and ( -not ($debugMode) ) ) { # don't log events unless flag is set - } else { + } + else { [pscustomobject]@{ - Time = (Get-Date -f u) - Line = "`[$($MyInvocation.ScriptLineNumber)`]" - Level = $Level - Message = $Message + Time = (Get-Date -f u) + Line = "`[$($MyInvocation.ScriptLineNumber)`]" + Level = $Level + Message = $Message } | Export-Csv -Path $LogName -Append -Force -NoTypeInformation -Encoding Unicode -#[switch]$toStdOut, -#[switch]$SkipHTML## - if ($toStdOut -or $WriteStdOut -or ( ($Level -eq "Verbose") -and $debugMode) ) { Write-Output $Message } - else { Write-Interactive $Message -Result $Severity} - } + #[switch]$toStdOut, + #[switch]$SkipHTML## + if ($toStdOut -or $WriteStdOut -or ( ($Level -eq "Verbose") -and $debugMode) ) { Write-Output $Message } + else { Write-Interactive $Message -Result $Severity } } - END {} - } + } + END {} +} function Write-StatusMessage { - param( + param( - [parameter(Mandatory=$true, ValueFromPipeline = $true, Position = 0)] + [parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [string]$Message, [Parameter(Position = 1)] - [ValidateSet(1,2,3,4)] + [ValidateSet(1, 2, 3, 4)] [string]$Severity = '1') - [string]$FormattedMessage = "" + [string]$FormattedMessage = "" - $FormattedMessage ="`r`n$line`r`n$message`r`n`r`n$line`r`n" + $FormattedMessage = "`r`n$line`r`n$message`r`n`r`n$line`r`n" - New-LogEntry $FormattedMessage -Severity $Severity - } + New-LogEntry $FormattedMessage -Severity $Severity +} function Show-Usage { @@ -291,34 +285,34 @@ function Get-NDESHelp { } function Set-ServiceAccountisLocalSystem { -Param( - [parameter(Mandatory=$true)] - [bool]$isSvcAcctLclSystem + Param( + [parameter(Mandatory = $true)] + [bool]$isSvcAcctLclSystem ) $Script:SvcAcctIsComputer = $isSvcAcctLclSystem New-LogEntry "Service account is local system (computer) account = $isSvcAcctLclSystem" -Severity 1 - } +} <# Returns the name of the service account#> function Get-NDESServiceAcct { - [string]$NDESServiceAccount = "" + [string]$NDESServiceAccount = "" - $CARegPath = "HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\PFXCertificateConnector\CA*" + $CARegPath = "HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\PFXCertificateConnector\CA*" - if (Test-Path $CARegPath ) { - if ( (Get-ItemProperty $CARegPath).UseSystemAccount -eq 1) { - $NDESServiceAccount = (Get-WmiObject Win32_ComputerSystem).Domain + "`\" + $env:computerName - Set-ServiceAccountisLocalSystem $true - } - elseif ( (Get-ItemProperty $CARegPath).Username -ne "" ) { - $NDESServiceAccount = (Get-ItemProperty $CARegPath).Username - } + if (Test-Path $CARegPath ) { + if ( (Get-ItemProperty $CARegPath -ErrorAction SilentlyContinue).UseSystemAccount -eq 1) { + $NDESServiceAccount = (Get-WmiObject Win32_ComputerSystem -ErrorAction SilentlyContinue).Domain + "`\" + $env:computerName + Set-ServiceAccountisLocalSystem $true } - else { - New-LogEntry "No certificate found in $CARegPath. Please resolve this issue and run the script again." -Severity 3 - + elseif ( (Get-ItemProperty $CARegPath).Username -ne "" ) { + $NDESServiceAccount = (Get-ItemProperty $CARegPath -ErrorAction SilentlyContinue).Username } + } + else { + New-LogEntry "No certificate found in $CARegPath. Please resolve this issue and run the script again." -Severity 3 + + } New-LogEntry "Service Account detected = $NDESServiceAccount" -Severity 1 $NDESServiceAccount @@ -326,13 +320,13 @@ function Get-NDESServiceAcct { function Test-IsNDESInstalled { Write-StatusMessage "Checking to see that NDES is installed" -Severity 1 - if ( Get-Service PFXCertificateConnectorSvc) { - $ruleResult = New-TestResult -Result "Passed" -MoreInformation "NDES is installed." - } - else { - $ruleResult = New-TestResult -Result "Failed" -MoreInformation "NDES is not installed. Cannot find PFXCertificateConnectorSvc service." - New-LogEntry "Error: NDES Not installed.`r`nExiting....................." -Severity 3 - } + if ( Get-Service PFXCertificateConnectorSvc) { + $ruleResult = New-TestResult -Result "Passed" -MoreInformation "NDES is installed." + } + else { + $ruleResult = New-TestResult -Result "Failed" -MoreInformation "NDES is not installed. Cannot find PFXCertificateConnectorSvc service." + New-LogEntry "Error: NDES Not installed.`r`nExiting....................." -Severity 3 + } $ruleResult @@ -343,12 +337,12 @@ function Test-IsRSATADInstalled { [bool]$isRSATADInstalled = $false if ($isadmin) { - if ( (Get-WindowsOptionalFeature -Online -FeatureName RSAT-AD-Tools-Feature).State -eq "Enabled") { + if ( (Get-WindowsOptionalFeature -Online -FeatureName RSAT-AD-Tools-Feature -ErrorAction SilentlyContinue).State -eq "Enabled") { $isRSATADInstalled = $true } } else { - New-LogEntry "$skipInstall" -Severity 2 + New-LogEntry "$skipInstall" -Severity 2 } $isRSATADInstalled @@ -356,7 +350,7 @@ function Test-IsRSATADInstalled { function Install-RSATAD { - if ($isadmin){ + if ($isadmin) { New-LogEntry "The RSAT-AD-Tools Windows feature is not installed. This Windows Feature is required to continue. This is a requirement for AD tests. Install now?" -Severity 2 $response = Read-Host -Prompt "[y/n]" New-LogEntry "Response $response" -Severity 1 @@ -386,7 +380,7 @@ function Install-RSATAD { function Test-IsAADModuleInstalled { Write-StatusMessage "Testing if Entra ID module is installed." - if (Get-Module ActiveDirectory -ListAvailable ) { + if (Get-Module ActiveDirectory -ListAvailable -ErrorAction SilentlyContinue) { New-LogEntry "Success: ActiveDirectory module is installed." -Severity 1 $ruleResult = New-TestResult -result Passed -MoreInformation "Entra ID PowerShell module is installed" } @@ -399,7 +393,7 @@ function Test-IsAADModuleInstalled { $ruleResult } function Test-IsIISInstalled { - if (-not (Get-WindowsFeature Web-WebServer).Installed){ + if (-not (Get-WindowsFeature Web-WebServer -ErrorAction SilentlyContinue).Installed) { $script:IISNotInstalled = $true New-LogEntry "IIS is not installed. Some tests will not run as we're unable to import the WebAdministration module" -Severity 2 $ruleResult = New-TestResult -Result Failed @@ -414,17 +408,17 @@ function Test-IsIISInstalled { function Test-OSVersion { Write-StatusMessage "Checking Windows OS version..." -Severity 1 - $OSVersion = (Get-CimInstance -class Win32_OperatingSystem).Version + $OSVersion = (Get-CimInstance -class Win32_OperatingSystem -ErrorAction SilentlyContinue).Version $MinOSVersion = "6.3" - if ([version]$OSVersion -lt [version]$MinOSVersion){ - New-LogEntry "Error: Unsupported OS Version. NDES requires Windows Server 2012 R2 and above." -Severity 3 - $ruleResult = New-TestResult -Result Failed - } - else { - New-LogEntry "Success: OS Version $OSVersion is supported." -Severity 1 - $ruleResult = New-TestResult -Result Passed - } + if ([version]$OSVersion -lt [version]$MinOSVersion) { + New-LogEntry "Error: Unsupported OS Version. NDES requires Windows Server 2012 R2 and above." -Severity 3 + $ruleResult = New-TestResult -Result Failed + } + else { + New-LogEntry "Success: OS Version $OSVersion is supported." -Severity 1 + $ruleResult = New-TestResult -Result Passed + } $ruleResult } @@ -433,13 +427,14 @@ function Test-IEEnhancedSecurityMode { Write-StatusMessage "Checking Internet Explorer Enhanced Security Configuration settings" -Severity 1 # Check for the current state of Enhanced Security Configuration; 0 = not configured - $escState = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" + $escState = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" -ErrorAction SilentlyContinue if ($escState.IsInstalled -eq 0) { New-LogEntry "Success: Enhanced Security Configuration is not configured." -Severity 1 $ruleResult = New-TestResult -Result Passed - } else { + } + else { New-LogEntry "Error: Enhanced Security Configuration is configured." -Severity 3 $ruleResult = New-TestResult -Result Failed } @@ -449,23 +444,25 @@ function Test-IEEnhancedSecurityMode { function Test-PFXCertificateConnector { Write-StatusMessage "Checking the `"Log on As`" for PFX Certificate Connector for Intune" -Severity 1 - $service = Get-Service -Name "PFXCertificateConnectorSvc" + $service = Get-Service -Name "PFXCertificateConnectorSvc" -ErrorAction SilentlyContinue if ($service) { # Get the service's process - $serviceProcess = Get-WmiObject Win32_Service | Where-Object { $_.Name -eq $service.Name } + $serviceProcess = Get-WmiObject Win32_Service | Where-Object { $_.Name -eq $service.Name } -ErrorAction SilentlyContinue # Check if the service is running as Local System or as a specific user if ($serviceProcess.StartName -eq "LocalSystem") { $msg = "$($service.Name) is running as Local System" New-LogEntry $msg $ruleResult = New-TestResult -Result Information -MoreInformation $msg - } else { + } + else { $msg = "$($service.Name) is running as service account $($serviceProcess.StartName)" New-LogEntry $msg $ruleResult = New-TestResult -Result Information -MoreInformation $msg } - } else { + } + else { $msg = "PFXCertificateConnectorSvc service not found" New-LogEntry $msg -Severity 3 $ruleResult = New-TestResult -Result Failed -MoreInformation $msg @@ -487,12 +484,14 @@ function Get-TCAInfo { $msg = "TCAInfo:`r`n $formattedOutput" New-LogEntry $msg -Severity 1 $ResultsText = New-TestResult -Result Information -MoreInformation "Successfully wrote TCA information to log." - } else { + } + else { $msg = "Cannot fetch the published template details." New-LogEntry $msg -Severity 3 $ResultsText = New-TestResult -Result Warning -MoreInformation $msg } - } catch { + } + catch { $msg = "Template Details cannot be fetched." New-LogEntry $msg -Severity 3 $ResultsText = New-TestResult -Result Warning -MoreInformation $msg @@ -501,67 +500,70 @@ function Get-TCAInfo { } function Get-IntuneServices { -# Fetching the Services for Intune -write-StatusMessage "Checking all the intune services" - -# Define the list of services to check -$services = @( - "AzureADConnectAgentUpdater", - "PFXCertificateConnectorSvc", - "PkiCreateConnectorSvc", - "PfxCreateLegacyConnectorSvc", - "PkiRevokeConnectorSvc" - "PKIConnectorSvc", - "WAPCSvc", - "WAPCUpdaterSvc" -) - -# Iterate through each service and check its status -foreach ($service in $services) { - $serviceStatus = Get-Service -Name $service -ErrorAction SilentlyContinue - - - if ($serviceStatus) { - $msg = "$service is $($serviceStatus.Status)" - New-LogEntry $msg + # Fetching the Services for Intune + write-StatusMessage "Checking all the intune services" + + # Define the list of services to check + $services = @( + "AzureADConnectAgentUpdater", + "PFXCertificateConnectorSvc", + "PkiCreateConnectorSvc", + "PfxCreateLegacyConnectorSvc", + "PkiRevokeConnectorSvc" + "PKIConnectorSvc", + "WAPCSvc", + "WAPCUpdaterSvc" + ) + + # Iterate through each service and check its status + foreach ($service in $services) { + $serviceStatus = Get-Service -Name $service -ErrorAction SilentlyContinue + + + if ($serviceStatus) { + $msg = "$service is $($serviceStatus.Status)" + New-LogEntry $msg - } else { - $msg = "$service not found" - New-LogEntry $msg + } + else { + $msg = "$service not found" + New-LogEntry $msg + } } } -} function Get-ConnectorCertificate { -#Checking the validity of the Microsoft Intune ImportPFX Connector Certificate" -Write-StatusMessage "Checking Microsoft Itnne ImportPFX Connector Certificate" + #Checking the validity of the Microsoft Intune ImportPFX Connector Certificate" + Write-StatusMessage "Checking Microsoft Itnne ImportPFX Connector Certificate" -$issuer = "Microsoft Intune ImportPFX Connector CA" -$certs = Get-ChildItem -Path cert:\LocalMachine -Recurse -$matchingCerts = $certs | Where-Object { $_.Issuer -match $issuer } + $issuer = "Microsoft Intune ImportPFX Connector CA" + $certs = Get-ChildItem -Path cert:\LocalMachine -Recurse -ErrorAction SilentlyContinue + $matchingCerts = $certs | Where-Object { $_.Issuer -match $issuer } -if ($matchingCerts.Count -gt 0) { - if ($matchingCerts.Count -gt 1) { - Write-Output "$($matchingCerts.Count) certificates issued by '$issuer' found." + if ($matchingCerts.Count -gt 0) { + if ($matchingCerts.Count -gt 1) { + Write-Output "$($matchingCerts.Count) certificates issued by '$issuer' found." - } + } - foreach ($cert in $matchingCerts) { + foreach ($cert in $matchingCerts) { - $validFromDate = $cert.NotBefore - $validToDate = $cert.NotAfter - if ($validToDate -gt (Get-Date)) { - Write-output "Certificate is valid from: $($validFromDate) until: $($validToDate)" + $validFromDate = $cert.NotBefore + $validToDate = $cert.NotAfter + if ($validToDate -gt (Get-Date)) { + Write-output "Certificate is valid from: $($validFromDate) until: $($validToDate)" - } else { - Write-output "Certificate is expired! Expiration date: $($validToDate)" + } + else { + Write-output "Certificate is expired! Expiration date: $($validToDate)" + } } } -} else { - Write-Output "No certificate issued by '$issuer' found." + else { + Write-Output "No certificate issued by '$issuer' found." -} + } } function Initialize-LogFile { @@ -569,7 +571,7 @@ function Initialize-LogFile { } function Test-InstallRSATTools { - if ( -not ( Test-IsRSATADInstalled) ){ + if ( -not ( Test-IsRSATADInstalled) ) { Install-RSATAD } } @@ -581,13 +583,13 @@ function Test-WindowsFeaturesInstalled { Write-StatusMessage "Checking Windows Features are installed..." -Severity 1 - $WindowsFeatures = @("Web-Filtering","Web-Net-Ext45","NET-Framework-45-Core","NET-WCF-HTTP-Activation45","Web-Metabase","Web-WMI") + $WindowsFeatures = @("Web-Filtering", "Web-Net-Ext45", "NET-Framework-45-Core", "NET-WCF-HTTP-Activation45", "Web-Metabase", "Web-WMI") - foreach($WindowsFeature in $WindowsFeatures){ + foreach ($WindowsFeature in $WindowsFeatures) { $Feature = Get-WindowsFeature $WindowsFeature $FeatureDisplayName = $Feature.displayName - if($Feature.installed){ + if ($Feature.installed) { New-LogEntry "Success: $($FeatureDisplayName) feature is installed" -Severity 1 } else { @@ -599,15 +601,16 @@ function Test-WindowsFeaturesInstalled { function Test-IISApplicationPoolHealth { Write-StatusMessage "Checking IIS Application Pool health..." -Severity 1 - if (-not ($IISNotInstalled -eq $true)){ + if (-not ($IISNotInstalled -eq $true)) { # If SCEP AppPool Exists - if (Test-Path 'IIS:\AppPools\SCEP'){ + if (Test-Path 'IIS:\AppPools\SCEP') { $IISSCEPAppPoolAccount = Get-Item 'IIS:\AppPools\SCEP' | Select-Object -expandproperty processmodel | Select-Object -ExpandProperty username - if ( (Get-WebAppPoolState "SCEP").value -match "Started" ){ + if ( (Get-WebAppPoolState "SCEP").value -match "Started" ) { $SCEPAppPoolRunning = $true } - } else { + } + else { New-LogEntry @" Error: SCEP Application Pool missing. Please review this document: @@ -617,10 +620,12 @@ function Test-IISApplicationPoolHealth { if ($SvcAcctIsComputer) { New-LogEntry "Skipping application pool account check since local system is used as the service account" -Severity 2 - } else { - if ($IISSCEPAppPoolAccount -contains "$NDESServiceAccount"){ + } + else { + if ($IISSCEPAppPoolAccount -contains "$NDESServiceAccount") { New-LogEntry "Application Pool is configured to use $($IISSCEPAppPoolAccount)" -Severity 1 - } else { + } + else { New-LogEntry @" Error: Application Pool is not configured to use the NDES Service Account Please review this article: @@ -629,12 +634,14 @@ function Test-IISApplicationPoolHealth { } } - if ($SCEPAppPoolRunning){ + if ($SCEPAppPoolRunning) { New-LogEntry "Success: SCEP Application Pool is Started" -Severity 1 - } else { + } + else { New-LogEntry "Error: SCEP Application Pool is stopped.`r`n`t`tPlease start the SCEP Application Pool via IIS Management Console. You should also review the Application Event log output for errors." -Severity 3 } - } else { + } + else { New-LogEntry "Error: IIS is not installed" -Severity 3 } } @@ -646,13 +653,12 @@ function Test-NDESInstallParameters { Write-StatusMessage "Checking NDES Install Parameters..." - $InstallParams = @(Get-WinEvent -LogName "Microsoft-Windows-CertificateServices-Deployment/Operational" | Where-Object {$_.id -eq "105"} | - Where-Object {$_.message -match "Install-AdcsNetworkDeviceEnrollmentService"} | + $InstallParams = @(Get-WinEvent -LogName "Microsoft-Windows-CertificateServices-Deployment/Operational" | Where-Object { $_.id -eq "105" } | + Where-Object { $_.message -match "Install-AdcsNetworkDeviceEnrollmentService" } | Sort-Object -Property TimeCreated -Descending | Select-Object -First 1) if ($InstallParams.Message -match '-SigningProviderName "Microsoft Strong Cryptographic Provider"' -and ` - ($InstallParams.Message -match '-EncryptionProviderName "Microsoft Strong Cryptographic Provider"')) - { + ($InstallParams.Message -match '-EncryptionProviderName "Microsoft Strong Cryptographic Provider"')) { Write-StatusMessage "Success: Correct CSP used in install parameters" @@ -675,7 +681,7 @@ function Test-HTTPParamsRegKeys { New-LogEntry "Checking registry (HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters) has been set to allow long URLs" -Severity 1 if (-not ($IISNotInstalled -eq $true)) { - $MaxFieldLength = (Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxFieldLength).MaxfieldLength + $MaxFieldLength = (Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxFieldLength).MaxfieldLength $MaxRequestBytes = (Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxRequestBytes).MaxRequestBytes if ($MaxFieldLength -notmatch "65534") { @@ -683,7 +689,8 @@ function Test-HTTPParamsRegKeys { New-LogEntry 'Please review this article:' New-LogEntry "URL: https://learn.microsoft.com/en-us/mem/intune/protect/certificates-scep-configure" - } else { + } + else { New-LogEntry "Success: MaxFieldLength set correctly" -Severity 1 } @@ -692,10 +699,12 @@ function Test-HTTPParamsRegKeys { New-LogEntry 'Please review this article:' New-LogEntry "URL: https://learn.microsoft.com/en-us/mem/intune/protect/certificates-scep-configure'" - } else { + } + else { New-LogEntry "Success: MaxRequestBytes set correctly" -Severity 1 } - } else { + } + else { New-LogEntry "IIS is not installed." -Severity 3 } } @@ -728,9 +737,9 @@ function Test-IntermediateCerts { Write-StatusMessage "Checking there are no intermediate certs are in the Trusted Root store..." -Severity 1 - $IntermediateCertCheck = Get-Childitem cert:\LocalMachine\root -Recurse | Where-Object {$_.Issuer -ne $_.Subject} + $IntermediateCertCheck = Get-Childitem cert:\LocalMachine\root -Recurse | Where-Object { $_.Issuer -ne $_.Subject } - if ($IntermediateCertCheck){ + if ($IntermediateCertCheck) { New-LogEntry "Error: Intermediate certificate found in the Trusted Root store. This can cause undesired effects and should be removed." -Severity 3 New-LogEntry "Certificates:`r`n" -Severity 3 New-LogEntry $IntermediateCertCheck -Severity 3 @@ -759,17 +768,17 @@ function Test-Certificates { # Loop through all certificates in LocalMachine Store foreach ($item in $certs) { - $Output = ($item.Extensions | Where-Object {$_.oid.FriendlyName -like "**"}).format(0).split(",") + $Output = ($item.Extensions | Where-Object { $_.oid.FriendlyName -like "**" }).format(0).split(",") $expirationDate = $item.NotAfter - if (($Output -match "EnrollmentAgentOffline") -and ($expirationDate -gt $currentDate)){ + if (($Output -match "EnrollmentAgentOffline") -and ($expirationDate -gt $currentDate)) { $EnrollmentAgentOffline = $true $EnrollmentAgentOfflineNotAfter = $expirationDate - } + } - if (($Output -match "CEPEncryption") -and ($expirationDate -gt $currentDate)){ + if (($Output -match "CEPEncryption") -and ($expirationDate -gt $currentDate)) { $CEPEncryption = $true $CEPEncryptionNotAfter = $expirationDate @@ -781,7 +790,8 @@ function Test-Certificates { if ($EnrollmentAgentOffline) { New-LogEntry "Success: EnrollmentAgentOffline certificate is present and valid till: $($EnrollmentAgentOfflineNotAfter)" -Severity 1 -c } + c + } else { New-LogEntry @" @@ -794,17 +804,17 @@ c } # Check if CEPEncryption certificate is present if ($CEPEncryption) { - New-LogEntry "Success: CEPEncryption certificate is present and valid till: $($CEPEncryptionNotAfter)" -Severity 1 + New-LogEntry "Success: CEPEncryption certificate is present and valid till: $($CEPEncryptionNotAfter)" -Severity 1 } else { - New-LogEntry @" + New-LogEntry @" Error: CEPEncryption certificate is not present or expired. This can occur when an account without Enterprise Admin permissions installs NDES. You may need to remove the NDES role and reinstall with the correct permissions. Please review this article: URL: https://learn.microsoft.com/en-us/mem/intune/protect/certificates-scep-configure CEPEncryption certificate is not present "@ -Severity 3 -} + } # Set ErrorActionPreference back to Continue $ErrorActionPreference = "Continue" @@ -860,12 +870,13 @@ function Test-ServerCertificate { if ($ServerCertObject.Issuer -match $ServerCertObject.Subject) { $SelfSigned = $true - } else { + } + else { $SelfSigned = $false } if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU -and (($ServerCertObject.Subject -match $hostname) -or ` - ($ServerCertObject.DnsNameList -match $hostname)) -and ($ServerCertObject.Issuer -notmatch $ServerCertObject.Subject)) { + ($ServerCertObject.DnsNameList -match $hostname)) -and ($ServerCertObject.Issuer -notmatch $ServerCertObject.Subject)) { New-LogEntry "Success: Certificate bound in IIS is valid:" @@ -884,27 +895,31 @@ function Test-ServerCertificate { New-LogEntry "Internal and External hostnames: " New-LogEntry "$($DNSNameList)" New-LogEntry "Certificate bound in IIS is valid. Subject:$($ServerCertObject.Subject)|Thumbprint:$($ServerCertObject.Thumbprint)|ValidUntil:$($ServerCertObject.NotAfter)|Internal and ExternalHostnames:$($DNSNameList)" -Severity 1 - } else { + } + else { New-LogEntry "Error: The certificate bound in IIS is not valid for use. Reason:" if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU) { $EKUValid = $true - } else { + } + else { $EKUValid = $false } New-LogEntry "Correct EKU: $EKUValid" if ($ServerCertObject.Subject -match $hostname) { $SubjectValid = $true - } else { + } + else { $SubjectValid = $false } New-LogEntry "Correct Subject: $SubjectValid" if ($SelfSigned -eq $false) { Out-Null - } else { + } + else { New-LogEntry "Is Self-Signed: $SelfSigned" } @@ -926,7 +941,8 @@ function Test-ClientCertificate { if ($ClientCertObject.Issuer -match $ClientCertObject.Subject) { $ClientCertSelfSigned = $true - } else { + } + else { $ClientCertSelfSigned = $false } @@ -935,12 +951,14 @@ function Test-ClientCertificate { New-LogEntry "Subject: $($ClientCertObject.Subject)" New-LogEntry "Thumbprint: $($ClientCertObject.Thumbprint)" New-LogEntry "Valid Until:`r`n $($ClientCertObject.NotAfter)" - } else { + } + else { New-LogEntry "Error: The certificate bound to the NDES Connector is not valid for use. Reason:" -Severity 3 if ($ClientCertObject.EnhancedKeyUsageList -match $clientAuthEku) { $ClientCertEKUValid = $true - } else { + } + else { $ClientCertEKUValid = $false New-LogEntry "Correct EKU: $($ClientCertEKUValid)" -Severity 1 @@ -949,7 +967,8 @@ function Test-ClientCertificate { if ($ClientCertSelfSigned -eq $false) { New-LogEntry "ClientCertSelfSigned = $ClientCertSelfSigned" -Severity 3 - } else { + } + else { New-LogEntry "Is Self-Signed: $ClientCertSelfSigned" -Severity 1 } @@ -968,41 +987,42 @@ function Test-InternalNdesUrl { Write-StatusMessage "Checking behaviour of internal NDES URL at Https://$hostname/certsrv/mscep/mscep.dll" -Severity 1 # URL To check -$hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname -$url = "https://$hostname/certsrv/mscep/mscep.dll" # Replace with the desired URL + $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname + $url = "https://$hostname/certsrv/mscep/mscep.dll" # Replace with the desired URL -try { - $response = Invoke-WebRequest -Uri $url -UseBasicParsing - Write-statusmessage "$($response.StatusCode)" -} -catch [System.Net.WebException] { - $webResponse = $_.Exception.Response - if ($webResponse -ne $null) { - $statusCode = [int]$webResponse.StatusCode - if ($statusCode -eq 403) { - $msg = "URL Response is 403 - Intune Connector is installed" - New-LogEntry $msg -Severity 1 - } else { - $msg = "URL response $($Statuscode) : Unexpected Error code. This usually signifies an error with the Intune Connector registering itself or not being installed. + try { + $response = Invoke-WebRequest -Uri $url -UseBasicParsing + Write-statusmessage "$($response.StatusCode)" + } + catch [System.Net.WebException] { + $webResponse = $_.Exception.Response + if ($webResponse -ne $null) { + $statusCode = [int]$webResponse.StatusCode + if ($statusCode -eq 403) { + $msg = "URL Response is 403 - Intune Connector is installed" + New-LogEntry $msg -Severity 1 + } + else { + $msg = "URL response $($Statuscode) : Unexpected Error code. This usually signifies an error with the Intune Connector registering itself or not being installed. Expected value is a 403. We received a $($Statuscode). This could be down to a missing reboot after the policy module installation. Verify last boot time and module install time further down the validation." - New-LogEntry $msg -Severity 3 + New-LogEntry $msg -Severity 3 + } + } + else { + Write-Output "Error: Unable to reach $($url)" } } - else { - Write-Output "Error: Unable to reach $($url)" + catch { + Write-Output "An unexpected error occurred" } } -catch { - Write-Output "An unexpected error occurred" -} -} function Test-LastBootTime { Write-StatusMessage "Checking last boot time of the server" -Severity 1 - $LastBoot = (Get-WmiObject win32_operatingsystem | Select-Object csname, @{LABEL='LastBootUpTime';EXPRESSION={$_.ConverttoDateTime($_.lastbootuptime)}}).lastbootuptime + $LastBoot = (Get-WmiObject win32_operatingsystem | Select-Object csname, @{LABEL = 'LastBootUpTime'; EXPRESSION = { $_.ConverttoDateTime($_.lastbootuptime) } }).lastbootuptime New-LogEntry @" Server last rebooted: $LastBoot @@ -1014,19 +1034,20 @@ function Test-LastBootTime { function Test-IntuneConnectorInstall { Write-StatusMessage "Checking if Intune Connector is installed..." -Severity 1 - if ($IntuneConnector = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Where-Object {$_.DisplayName -eq "Certificate Connector for Microsoft Intune"}) { + if ($IntuneConnector = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Where-Object { $_.DisplayName -eq "Certificate Connector for Microsoft Intune" }) { $installDate = [datetime]::ParseExact($IntuneConnector.InstallDate, 'yyyymmdd', $null).ToString('dd-mm-yyyy') $ResultsText = New-TestResult -Result Passed -MoreInformation "Success: $($IntuneConnector.DisplayName) was installed on $installDate and is version $($IntuneConnector.DisplayVersion)" New-LogEntry "ConnectorVersion: $IntuneConnector" -Severity 1 - } else { + } + else { New-LogEntry @" Error: Intune Connector not installed Please review this article: https://learn.microsoft.com/en-us/mem/intune/protect/certificates-scep-configure "@ -Severity 3 - $ResultsText = New-TestResult "Intune Certificate Connector is not installed" -Result Failed + $ResultsText = New-TestResult "Intune Certificate Connector is not installed" -Result Failed } $ResultsText } @@ -1043,56 +1064,55 @@ function Test-IIS_Log { $logPattern = "*certsrv/mscep/mscep.dll*" # Get the latest log file - if (Test-Path $IISlogPath) - { + if (Test-Path $IISlogPath) { $logFiles = Get-ChildItem -Path $IISlogPath | Sort-Object LastWriteTime -Descending | Select-Object -First 2 - if ($null -ne $logFiles) { + if ($null -ne $logFiles) { - foreach ($logFile in $logFiles) { - $logContent = Get-Content -Path $logFile.FullName| Where-Object { $_ -like $logPattern } + foreach ($logFile in $logFiles) { + $logContent = Get-Content -Path $logFile.FullName | Where-Object { $_ -like $logPattern } - foreach ($entry in $logContent) { + foreach ($entry in $logContent) { - # Split the log entry into fields - $fields = $entry -split "\s+" + # Split the log entry into fields + $fields = $entry -split "\s+" - # Create an object for the log entry - $logObject = [PSCustomObject]@{ - # Date = get-date $fields[0] - # Time = $fields[1] - Date = get-date "$($fields[0]) $($fields[1])" - SIP = $fields[2] - Method = $fields[3] - URIStem = $fields[4] - URIQuery = $fields[5] - SPort = $fields[6] - Username = $fields[7] - CIP = $fields[8] - UserAgent = $fields[9] - Referer = $fields[10] - StatusCode = $fields[11] - SubStatusCode = $fields[12] - Win32StatusCode = $fields[13] - TimeTaken = $fields[14] + # Create an object for the log entry + $logObject = [PSCustomObject]@{ + # Date = get-date $fields[0] + # Time = $fields[1] + Date = get-date "$($fields[0]) $($fields[1])" + SIP = $fields[2] + Method = $fields[3] + URIStem = $fields[4] + URIQuery = $fields[5] + SPort = $fields[6] + Username = $fields[7] + CIP = $fields[8] + UserAgent = $fields[9] + Referer = $fields[10] + StatusCode = $fields[11] + SubStatusCode = $fields[12] + Win32StatusCode = $fields[13] + TimeTaken = $fields[14] + } + # Add the log object to the array + $logObjects += $logObject + } + # Output the log objects + $RecentrequestinIIS = $logObjects | Select-Object -First 9 } - # Add the log object to the array - $logObjects += $logObject - } - # Output the log objects - $RecentrequestinIIS = $logObjects | Select-Object -First 9 - } - if ($RecentrequestinIIS) { + if ($RecentrequestinIIS) { - New-LogEntry "Found SCEP request in IIS log: " -Severity 1 - foreach($CertRequest in $RecentrequestinIIS) - { - New-LogEntry "$($CertRequest)" -Severity 1 + New-LogEntry "Found SCEP request in IIS log: " -Severity 1 + foreach ($CertRequest in $RecentrequestinIIS) { + New-LogEntry "$($CertRequest)" -Severity 1 + } + $ResultsText = New-TestResult -Result Passed -MoreInformation $RecentrequestinIIS[0].ToString() } - $ResultsText = New-TestResult -Result Passed -MoreInformation $RecentrequestinIIS[0].ToString() } - } else { + else { $msg = "No log files found in the specified path." New-LogEntry $msg -Severity 2 $ResultsText = New-TestResult -Result Warning -MoreInformation $msg @@ -1123,16 +1143,16 @@ function Get-EventLogData { #check last 2 days event log $EventstartTime = (Get-Date).AddDays(-2) - # if (-not (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After (Get-Date).AddDays(-$EventLogCollDays) -ErrorAction SilentlyContinue)) { + # if (-not (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After (Get-Date).AddDays(-$EventLogCollDays) -ErrorAction SilentlyContinue)) { #Get-eventlog doesn't work for non-system build-in event, use get-winEventinstead - if (-not (Get-WinEvent -FilterHashtable @{LogName=$ConnectorlogAdmin;StartTime=$EventstartTime;ID='1001','1201','2001','3001','4001','4002'} -MaxEvents 5 -ErrorAction SilentlyContinue)){ + if (-not (Get-WinEvent -FilterHashtable @{LogName = $ConnectorlogAdmin; StartTime = $EventstartTime; ID = '1001', '1201', '2001', '3001', '4001', '4002' } -MaxEvents 5 -ErrorAction SilentlyContinue)) { New-LogEntry "Success: No errors found in the Microsoft Intune Connector" -Severity 1 } else { New-LogEntry "Errors found in the Microsoft Intune Connector Event log. Please see below for the most recent 5, and investigate further in Event Viewer." -Severity 2 # $EventsCol1 = Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After (Get-Date).AddDays(-$EventLogCollDays) -Newest 5 | Select-Object TimeGenerated, Source, Message - $EventsCol1 = Get-WinEvent -FilterHashtable @{LogName=$ConnectorlogAdmin;StartTime=$EventstartTime} -MaxEvents 5 -ErrorAction SilentlyContinue + $EventsCol1 = Get-WinEvent -FilterHashtable @{LogName = $ConnectorlogAdmin; StartTime = $EventstartTime } -MaxEvents 5 -ErrorAction SilentlyContinue $EventsCol1 | Format-List New-LogEntry "Errors found in the Microsoft Intune Connector Event log" -Severity 3 $i = 0 @@ -1144,7 +1164,7 @@ function Get-EventLogData { } if (-not (Get-EventLog -LogName "Application" -EntryType Error -Source NDESConnector, Microsoft-Windows-NetworkDeviceEnrollmentService -After (Get-Date).AddDays(-$EventLogCollDays) -ErrorAction SilentlyContinue)) { - New-LogEntry "Success: No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" -Severity 1 + New-LogEntry "Success: No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" -Severity 1 } else { New-LogEntry "Errors found in the Application Event log for source NetworkDeviceEnrollmentService or NDESConnector. Please see below for the most recent 5, and investigate further in Event Viewer." -Severity 2 @@ -1163,33 +1183,50 @@ function Get-EventLogData { function Test-NDESServiceAccountProperties { param ( - [string]$NDESServiceAccount + [string]$NDESServiceAccount, + [bool]$SvcAcctIsComputer = $false ) - + Write-StatusMessage "Checking NDES Service Account $NDESServiceAccount properties in Active Directory" -Severity 1 - $ADAccount = $NDESServiceAccount.split("\")[1] + $ADAccount = $NDESServiceAccount.Split("\")[-1] + $ADAccountProps = $null + if ($SvcAcctIsComputer) { - $ADAccountProps = Get-ADComputer $ADAccount -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut + # Check if the account is a computer object + $ADAccountProps = Get-ADComputer $ADAccount -Properties SamAccountName, Enabled, AccountExpirationDate, AccountExpires, AccountLockoutTime, PasswordExpired, PasswordLastSet, PasswordNeverExpires, LockedOut -ErrorAction SilentlyContinue + } + elseif ($ADAccount.EndsWith("$")) { + # Likely a Managed Service Account (MSA or gMSA) + Write-Host "Checking MSA" + $ADAccountProps = Get-ADServiceAccount -Filter { SamAccountName -eq $ADAccount } -Properties SamAccountName, Enabled, AccountExpirationDate, AccountExpires, AccountLockoutTime, PasswordExpired, PasswordLastSet, PasswordNeverExpires, LockedOut -ErrorAction SilentlyContinue } else { - $ADAccountProps = Get-ADUser $ADAccount -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut + # Regular user account + Write-Host "Checking legacy service account" #some NDES deployment might still have legacy way to setup service accounts + $ADAccountProps = Get-ADUser $ADAccount -Properties SamAccountName, Enabled, AccountExpirationDate, AccountExpires, AccountLockoutTime, PasswordExpired, PasswordLastSet, PasswordNeverExpires, LockedOut -ErrorAction SilentlyContinue } - if ($ADAccountProps.enabled -ne $true -OR $ADAccountProps.PasswordExpired -ne $false -OR $ADAccountProps.LockedOut -eq $true) { + if ($null -eq $ADAccountProps) { + Write-StatusMessage "Error: Could not find account $ADAccount in Active Directory." -Severity 3 + return + } + + if ($ADAccountProps.Enabled -ne $true -or $ADAccountProps.PasswordExpired -ne $false -or $ADAccountProps.LockedOut -eq $true) { Write-StatusMessage "Error: Problem with the AD account. Please see output below to determine the issue" -Severity 3 } else { - Write-StatusMessage "Success:`r`nNDES Service Account seems to be in working order:" -Severity 1 + Write-StatusMessage "Success:`r`nNDES Service Account seems to be in working order:" -Severity 1 } - $msg = $ADAccountProps | Format-List SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut | Out-String - + $msg = $ADAccountProps | Format-List SamAccountName, Enabled, AccountExpirationDate, AccountExpires, AccountLockoutTime, PasswordExpired, PasswordLastSet, PasswordNeverExpires, LockedOut | Out-String + New-LogEntry "$msg" -Severity 1 $ResultsText = New-TestResult -TestName "Test-NDESServiceAccountProperties" -MoreInformation $msg -Result Information - write-host $ResultsText - $ResultsText -} + Write-Host $ResultsText + return $ResultsText +} + function Test-NDESServiceAccountLocalPermissions { Write-StatusMessage "Checking NDES Service Account local permissions..." @@ -1199,10 +1236,10 @@ function Test-NDESServiceAccountLocalPermissions { $ResultsText = New-TestResult "Skipping NDES Service Account local permissions since local system is used as the service account..." -Result Information } else { - if ((net localgroup) -match "Administrators"){ + if ((net localgroup) -match "Administrators") { $LocalAdminsMember = (net localgroup Administrators) - if ($LocalAdminsMember -like "*$NDESServiceAccount*"){ + if ($LocalAdminsMember -like "*$NDESServiceAccount*") { $msg = "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use the IIS_IUSERS local group instead." $ResultsText = New-TestResult $msg -Result Warning } @@ -1235,13 +1272,15 @@ function Test-Connectivity { $result = $connection.BeginConnect($uniqueURL, $port, $null, $null) $wait = $result.AsyncWaitHandle.WaitOne(5000, $false) - if ($wait -and (-not $connection.Client.Connected) ){ + if ($wait -and (-not $connection.Client.Connected) ) { $connection.Close() $connectionTest = $false - } elseif (-not $wait) { + } + elseif (-not $wait) { $connection.Close() $connectionTest = $false - } else { + } + else { $null = $connection.EndConnect($result) $connectionTest = $connection.Connected } @@ -1250,7 +1289,8 @@ function Test-Connectivity { $msg = "Connection to $uniqueURL on port $port is successful." New-LogEntry $msg -Severity 1 $ResultsText = New-TestResult -Result Passed -MoreInformation $msg - } else { + } + else { $msg = "Connection to $uniqueURL on port $port failed." New-LogEntry $msg -Severity 3 $ResultsText = New-TestResult -Result Failed -MoreInformation $msg @@ -1273,11 +1313,11 @@ function Test-NDESServiceAccountLocalPermissions { $ResultsText = New-TestResult -Result Information -MoreInformation $msg } else { - if ( (net localgroup) -match "^`*Administrators$"){ + if ( (net localgroup) -match "^`*Administrators$") { $LocalAdminsMembers = (net localgroup Administrators) - if ($LocalAdminsMembers -like "*$NDESServiceAccount*"){ + if ($LocalAdminsMembers -like "*$NDESServiceAccount*") { $msg = "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use the IIS_IUSERS local group instead." New-LogEntry $msg -Severity 2 $ResultsText = New-TestResult -Result Warning -MoreInformation $msg @@ -1288,10 +1328,10 @@ function Test-NDESServiceAccountLocalPermissions { $ResultsText = New-TestResult -Result Passed -MoreInformation $msg } } - else { - $msg = "No local Administrators group exists, likely due to this being a Domain Controller or renaming the group. It is not recommended to run NDES on a Domain Controller." - New-LogEntry $msg-Severity 2 - $ResultsText = New-TestResult -Result Warning -MoreInformation $msg + else { + $msg = "No local Administrators group exists, likely due to this being a Domain Controller or renaming the group. It is not recommended to run NDES on a Domain Controller." + New-LogEntry $msg-Severity 2 + $ResultsText = New-TestResult -Result Warning -MoreInformation $msg } } $ResultsText @@ -1301,16 +1341,16 @@ Function Test-IIS_IUSR_Membership { Write-StatusMessage "Checking if the NDES service account is a member of the IIS_IUSR group..." -Severity 1 if ($SvcAcctIsComputer) { - $msg = "NDES service account is running as local system. Skipping test for local IIS_IUSR group membership." + $msg = "NDES service account is running as local system. Skipping test for local IIS_IUSR group membership." New-LogEntry $msg -Severity 1 $ResultsText = New-TestResult $msg -Result Information } else { - if ((net localgroup) -match "IIS_IUSRS"){ + if ((net localgroup) -match "IIS_IUSRS") { $IIS_IUSRMembers = (net localgroup IIS_IUSRS) - if ($IIS_IUSRMembers -like "*$NDESServiceAccount*"){ + if ($IIS_IUSRMembers -like "*$NDESServiceAccount*") { New-LogEntry "NDES service account is a member of the local IIS_IUSR group" -Severity 1 } else { @@ -1322,12 +1362,12 @@ Function Test-IIS_IUSR_Membership { $LocalSecPol = Get-Content $TempFile $ADAccount = $NDESServiceAccount.split("\")[1] # we should only be checking user accounts. If local system is the service account, we can skip this event - $ADAccountProps = (Get-ADUser $ADAccount -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut) + $ADAccountProps = (Get-ADUser $ADAccount -Properties SamAccountName, enabled, AccountExpirationDate, accountExpires, accountlockouttime, PasswordExpired, PasswordLastSet, PasswordNeverExpires, LockedOut) $NDESSVCAccountSID = $ADAccountProps.SID.Value $LocalSecPolResults = $LocalSecPol | Select-String $NDESSVCAccountSID - if ($LocalSecPolResults -match "SeInteractiveLogonRight" -and $LocalSecPolResults -match "SeBatchLogonRight" -and $LocalSecPolResults -match "SeServiceLogonRight"){ + if ($LocalSecPolResults -match "SeInteractiveLogonRight" -and $LocalSecPolResults -match "SeBatchLogonRight" -and $LocalSecPolResults -match "SeServiceLogonRight") { $msg = @" Success: @@ -1337,10 +1377,10 @@ Function Test-IIS_IUSR_Membership { Consider using the IIS_IUSERS group instead of explicit rights as described in this article: https://learn.microsoft.com/en-us/mem/intune/protect/certificates-scep-configure "@ - New-LogEntry $msg -Severity 1 - $ResultsText = New-TestResult -Result Passed -MoreInformation $msg + New-LogEntry $msg -Severity 1 + $ResultsText = New-TestResult -Result Passed -MoreInformation $msg - } + } else { $msg = "NDES Service Account has _NOT_ been assigned the Logon Locally, Logon as a Service or Logon as a batch job rights _explicitly." New-LogEntry $msg -Severity 3 @@ -1405,7 +1445,7 @@ function Compress-LogFiles { } if ($LogFileCollectionConfirmation -eq "y") { - $IISLogPath = (Get-WebConfigurationProperty "/system.applicationHost/sites/siteDefaults" -name logfile.directory).Value + "\W3SVC1" -replace "%SystemDrive%",$env:SystemDrive + $IISLogPath = (Get-WebConfigurationProperty "/system.applicationHost/sites/siteDefaults" -name logfile.directory).Value + "\W3SVC1" -replace "%SystemDrive%", $env:SystemDrive $IISLogs = Get-ChildItem $IISLogPath | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 3 $NDESConnectorLogs = Get-ChildItem "$env:SystemRoot\System32\Winevt\Logs\Microsoft-Intune-CertificateConnectors*" $NDESConnectorUpdateAgentLogs = Get-ChildItem "$env:SystemRoot\System32\Winevt\Logs\Microsoft-AzureADConnect-AgentUpdater*" @@ -1503,14 +1543,14 @@ function Get-CSVInfo { $csvData = Import-Csv $fileName $Results = @{} - foreach ($row in $csvData){ - $Results.add( $row.TestName, @{ "Passed" = $row.Passed; "Failed" = $row.Failed} ) - } + foreach ($row in $csvData) { + $Results.add( $row.TestName, @{ "Passed" = $row.Passed; "Failed" = $row.Failed } ) } + } else { New-LogEntry "File not found: $fileName" -Severity 3 break - } + } $Results } @@ -1571,12 +1611,12 @@ function New-HTMLReport { '@ -$preContent = @' + $preContent = @'
+
+_Server has Intune Certificate Connector with Local Service Account_
+
+
+
+_Server has not Intune Certificate Connector fully installed_
+
+
(Hint: You can copy and paste the commands from this page and paste them directly in to the PowerShell window.
diff --git a/ndes-gmsa.jpeg b/ndes-gmsa.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..7382694d941ca187ea5327a6e5291d975bef8ef2
GIT binary patch
literal 163734
zcmeFZ2V9d&w=W#Gii#BJ(xrqRkS={oPw1gZm)?656tK~25(pin1W1tHn~3x#C4h9K
z7b!}Wexv){=e+MeZ@Kro_q+F;^V<`W3^S8iYu5U&wdR?b=eZoe{0z7QQ3NXju3P~C
zuHasP%Y`fBU^zK6O{j(;6S%np1u%n>~Rsb+5G7J
z1sZLE{C0ZIAwP$;F@1DG{k@Gr%8_+j>Dy!!^iH(>D4WS6PBEk(W1n$sZ5mqIu@pDE
zyW;cw$21`dbA$^4Kc)#Ua?36OGT*^&OHWVAqNko-0ur(=0dJHp6sww@1B3X#1qJJwDeA(rRP0d;134w?8WOs?9wBb$k#44AsJtgZYH}a5zu|aZ
zw?)Vo_2TYvOaH+0Yr*4#7oJii*$7SX6!UTy|J;h1T+2eID83v+y>{`}r%viHb-O7C
zqiD($&qLq1mN)y4v+`b N$?_rS-XR
zho+jU^$!uXYUBdl_(I?DehM8C2x$Zy=1n`^%NJT%8`N3oS6b#gx+-ojoiQcjlk%$l
zlz7(Yr%$*yP7Iv(W>`d|z>>8%Eifm_$(x eBDSYh
z+wkCpzIDg?KKMd?oZV0;Wq3nPdRU;#@)PcenZvkO^%s3%gWA%WxM`h|vHtb~IDBT0
zze89;@Xk8o8#mt9vGuewd
w5_esxOCTF^9sFO`HeVU@o