Troubleshooter/Azure/WindowsTroubleshooter.ps1 (486 lines of code) (raw):

param( [switch]$returnCompactFormat, [switch]$returnAsJson ) function New-RuleCheckResult { [CmdletBinding()] param( [string][Parameter(Mandatory=$true)]$ruleId, [string]$ruleName, [string]$ruleDescription, [string][ValidateSet("Passed","PassedWithWarning", "Failed", "Information")]$result, [string]$resultMessage, [string]$ruleGroupId = $ruleId, [string]$ruleGroupName, [string]$resultMessageId = $ruleId, [array]$resultMessageArguments = @() ) if ($returnCompactFormat.IsPresent) { $compactResult = [pscustomobject] [ordered] @{ 'RuleId'= $ruleId 'RuleGroupId'= $ruleGroupId 'CheckResult'= $result 'CheckResultMessageId'= $resultMessageId 'CheckResultMessageArguments'= $resultMessageArguments } return $compactResult } $fullResult = [pscustomobject] [ordered] @{ 'RuleId'= $ruleId 'RuleGroupId'= $ruleGroupId 'RuleName'= $ruleName 'RuleGroupName' = $ruleGroupName 'RuleDescription'= $ruleDescription 'CheckResult'= $result 'CheckResultMessage'= $resultMessage 'CheckResultMessageId'= $resultMessageId 'CheckResultMessageArguments'= $resultMessageArguments } return $fullResult } function checkRegValue { [CmdletBinding()] param( [string][Parameter(Mandatory=$true)]$path, [string][Parameter(Mandatory=$true)]$name, [int][Parameter(Mandatory=$true)]$valueToCheck ) $val = Get-ItemProperty -path $path -name $name -ErrorAction SilentlyContinue if($val.$name -eq $null) { return $null } if($val.$name -eq $valueToCheck) { return $true } else { return $false } } function getRegValue { [CmdletBinding()] param( [string][Parameter(Mandatory = $true)]$path, [string][Parameter(Mandatory = $true)]$name ) $val = Get-ItemProperty -path $path -name $name -ErrorAction SilentlyContinue if ($val.$name -eq $null) { return $null } return $val.$name } function Validate-DotNetFrameworkInstalled { $netFrameworkDownloadLink = "https://dotnet.microsoft.com/en-us/download/dotnet-framework" $ruleId = "DotNetFrameworkInstalledCheck" $ruleName = "Dot Net Framework installation check" $ruleDescription = ".NET Framework version 4.0 or higher is required." $result = $null $resultMessage = $null $ruleGroupId = "prerequisites" $ruleGroupName = "Prerequisites Checks" $resultMessageArguments = @() # https://docs.microsoft.com/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed $dotNetFullRegistryPath = "HKLM:SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full" if((Get-ChildItem $dotNetFullRegistryPath -ErrorAction SilentlyContinue) -ne $null) { $versionCheck = (Get-ChildItem $dotNetFullRegistryPath) | Get-ItemPropertyValue -Name Release | ForEach-Object { $_ -ge 378389 } if($versionCheck -eq $true) { $result = "Passed" $resultMessage = ".NET Framework version 4.0+ is found" } else { $result = "Failed" $resultMessage = ".NET Framework version 4.0 or higher is required: $netFrameworkDownloadLink." $resultMessageArguments += $netFrameworkDownloadLink } } else{ $result = "Failed" $resultMessage = ".NET Framework version 4.0 or higher is required: $netFrameworkDownloadLink." $resultMessageArguments += $netFrameworkDownloadLink } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-TLSVersion { $ruleId = "TlsVersionCheck" $ruleName = "TLS version check" $ruleDescription = "Client and Server connections must support TLS 1.2." $result = $null $reason = "" $resultMessage = $null $ruleGroupId = "prerequisites" $ruleGroupName = "Prerequisites Checks" $tls12RegistryPath = "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\SCHANNEL\\Protocols\\TLS 1.2\\" $serverEnabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Server")) "Enabled" 1 $serverNotDisabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Server")) "DisabledByDefault" 0 $clientEnabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Client")) "Enabled" 1 $clientNotDisabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Client")) "DisabledByDefault" 0 $serverNotEnabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Server")) "Enabled" 0 $serverDisabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Server")) "DisabledByDefault" 1 $clientNotEnabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Client")) "Enabled" 0 $clientDisabled = checkRegValue ([System.String]::Concat($tls12RegistryPath, "Client")) "DisabledByDefault" 1 if ($validationResults[0].CheckResult -ne "Passed" -and [System.Environment]::OSVersion.Version -ge [System.Version]"6.0.6001") { $result = "Failed" $resultMessageId = "$ruleId.$result" $resultMessage = "Enable TLS 1.2 on your operating system. Follow the instructions to enable it: https://support.microsoft.com/help/4019276/update-to-add-support-for-tls-1-1-and-tls-1-2-in-windows." } elseif([System.Environment]::OSVersion.Version -ge [System.Version]"6.1.7601" -and [System.Environment]::OSVersion.Version -le [System.Version]"6.1.8400") { if($clientNotDisabled -and $serverNotDisabled -and !($serverNotEnabled -and $clientNotEnabled)) { $result = "Passed" $resultMessage = "TLS 1.2 is enabled on the Operating System" $resultMessageId = "$ruleId.$result" } else { $result = "Failed" $reason = "NotExplicitlyEnabled" $resultMessageId = "$ruleId.$result.$reason" $resultMessage = "Enable TLS 1.2 on your operating system. Follow the instructions to enable it: https://docs.microsoft.com/windows-server/security/tls/tls-registry-settings#tls-12." } } elseif([System.Environment]::OSVersion.Version -ge [System.Version]"6.2.9200") { if($clientDisabled -or $serverDisabled -or $serverNotEnabled -or $clientNotEnabled) { $result = "Failed" $reason = "ExplicitlyDisabled" $resultMessageId = "$ruleId.$result.$reason" $resultMessage = "TLS 1.2 is available on the Operating System, but currently disabled. Follow the instructions to re-enable: https://docs.microsoft.com/windows-server/security/tls/tls-registry-settings#tls-12." } else { $result = "Passed" $reason = "EnabledByDefault" $resultMessageId = "$ruleId.$result.$reason" $resultMessage = "TLS 1.2 is enabled on the Operating System." } } else { $result = "Failed" $reason = "NoDefaultSupport" $resultMessageId = "$ruleId.$result.$reason" $resultMessage = "Your OS does not support TLS 1.2." } return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId } function Validate-WindowsPatchExtension { $ruleId = "PatchExtensionCheck" $ruleName = "Windows Patch extension check" $ruleDescription = "WindowsPatchExtension should be installed on the machine for Periodic Assessment." $result = $null $resultMessage = $null $ruleGroupId = "extensions" $ruleGroupName = "Extension Checks" $resultMessageArguments = @() $extensionPath = "C:\Packages\Plugins\Microsoft.CPlat.Core.WindowsPatchExtension" if (Test-Path -Path $extensionPath -PathType Container) { $result = "Passed" $resultMessage = "Windows Patch extension is installed on the machine." } else { $result = "Failed" $resultMessage = "Windows Patch extension is not installed on the machine." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-ServiceStatus { [CmdletBinding()] param( [string][Parameter(Mandatory=$true)]$service, [string][Parameter(Mandatory=$true)]$ruleId, [string][Parameter(Mandatory=$true)]$ruleName, [string]$ruleDescription = "Agent related services must be running to ensure proper working of the agent." ) $result = $null $resultMessage = $null $ruleGroupId = "guestagentservices" $ruleGroupName = "Azure Guest Agent Services Check" $resultMessageArguments = @() $serviceObj = Get-Service -Name $service -ErrorAction SilentlyContinue if ($null -ne $serviceObj -and $serviceObj.Status -eq 'Running') { $result = "Passed" $resultMessage = "$service is running." } else { $result = "Failed" $resultMessage = "$service is not running." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-AzureGuestAgentServices { $services = @( @{ "service" = "RdAgent"; "ruleId" = "RdAgentServiceCheck"; "ruleName" = "RdAgent service must be running" }, @{ "service" = "WindowsAzureGuestAgent"; "ruleId" = "WindowsAzureGuestAgentServiceCheck"; "ruleName" = "Windows Azure guest agent service must be running" } ) # Validate Service Status for each service foreach ($service in $services) { Validate-ServiceStatus $service.service $service.ruleId $service.ruleName } } function Validate-WireServerConnectivity{ $ruleId = "WireServerConnectivityCheck" $ruleName = "Wire Server connectivity check" $ruleDescription = "Wire Server must be reachable" $result = $null $resultMessage = $null $ruleGroupId = "connectivity" $ruleGroupName = "Connectivity Checks" $resultMessageArguments = @() $result = "Passed" $testResult1 = Test-NetConnection -ComputerName 168.63.129.16 -Port 80 if ($testResult1.TcpTestSucceeded) { $ResultMessage = "Connection to port 80 succeeded." $resultMessageId = "$ruleId.$result" } else { $ResultMessage = "Connection to port 80 failed." $result = "Failed" $reason = "Port80Failed" $resultMessageId = "$ruleId.$result.$reason" } $testResult2 = Test-NetConnection -ComputerName 168.63.129.16 -Port 32526 if ($testResult2.TcpTestSucceeded) { $resultMessage += "`nConnection to port 32526 succeeded." } else { $resultMessage += "`nConnection to port 32526 failed." $result = "Failed" $reason += "Port32526Failed" $resultMessageId += ".$reason" } return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Is-EndpointReachable { [CmdletBinding()] param( [string][Parameter(Mandatory=$true)]$endpoint ) try { if((Test-NetConnection -ComputerName $endpoint -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded) { $result = "Passed" } else { $result = "Failed" } } catch { $client = New-Object Net.Sockets.TcpClient try { $client.Connect($endpoint, 443) $result = "Passed" } catch { $result = "Failed" } finally { $client.Dispose() } } return $result } function Validate-WindowsUpdateEndpointConnectivity { param( [string[]]$endpoints = @( "windowsupdate.microsoft.com", "dl.delivery.mp.microsoft.com", "download.windowsupdate.com", "download.microsoft.com", "ntservicepack.microsoft.com", "go.microsoft.com", "dl.delivery.mp.microsoft.com" ) ) $failedEndpoints = @() foreach ($endpoint in $endpoints) { $result = Is-EndpointReachable $endpoint if ($result -eq "Failed") { $failedEndpoints += $endpoint } } $ruleId = "WUEndpointConnectivityCheck" $ruleName = "Windows update endpoints connectivity check" $ruleDescription = "Proxy and firewall configuration must allow the system to communicate with Windows Update endpoints." $result = if ($failedEndpoints.Count -eq 0) { "Passed" } else { "Failed" } $resultMessage = if ($failedEndpoints.Count -eq 0) { "All endpoints are reachable." } else { "Failed to reach the following endpoints: $($failedEndpoints -join ', ')" } $ruleGroupId = "connectivity" $ruleGroupName = "Connectivity Checks" $resultMessageId = "$ruleId.$result" $resultMessageArguments = $failedEndpoints return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-AlwaysAutoRebootEnabled { $ruleId = "AlwaysAutoRebootCheck" $ruleName = "Automatic reboot check" $ruleDescription = "Automatic reboot should not be enabled as it forces a reboot irrespective of update configuration." $result = $null $resultMessage = $null $ruleGroupId = "machinesettings" $ruleGroupName = "Machine Update Settings Checks" $automaticUpdatePath = "HKLM:\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU" $rebootEnabledBySchedule = checkRegValue ($automaticUpdatePath) "AlwaysAutoRebootAtScheduledTime" 1 $rebootEnabledByDuration = getRegValue ($automaticUpdatePath) "AlwaysAutoRebootAtScheduledTimeMinutes" if ( $rebootEnabledBySchedule -or $rebootEnabledByDuration ) { $result = "PassedWithWarning" $resultMessage = "Windows Update reboot registry keys are set to automatic reboot and this can cause unexpected reboots on your machine when installing updates." } else { $result = "Passed" $resultMessage = "Windows Update reboot registry keys are not set to automatic reboot." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-AutomaticUpdateEnabled { $ruleId = "AutomaticUpdateCheck" $ruleName = "Automatic update check" $ruleDescription = "Automatic Update should not be enabled on the machine." $result = $null $resultMessage = $null $ruleGroupId = "machinesettings" $ruleGroupName = "Machine Update Settings Checks" $automaticUpdatePath = "HKLM:\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU" $autoUpdateEnabled = checkRegValue ($automaticUpdatePath) "AUOptions" 4 if ( $autoUpdateEnabled ) { $result = "PassedWithWarning" $resultMessage = "Windows Update will automatically download and install new updates as they become available." } else { $result = "Passed" $resultMessage = "Windows Update is not set to automatically install updates as they become available." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-WSUSServerConfigured { $ruleId = "WSUSServerConfiguredCheck" $ruleName = "WSUS server configured check" $ruleDescription = "Increase awareness on WSUS configured on the server." $result = $null $resultMessage = $null $ruleGroupId = "machinesettings" $ruleGroupName = "Machine Update Settings Checks" $automaticUpdatePath = "HKLM:\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate" $wsusServerConfigured = getRegValue ($automaticUpdatePath) "WUServer" if ( $null -ne $wsusServerConfigured ) { $result = "PassedWithWarning" $resultMessage = "Windows Updates are downloading from a configured WSUS Server $wsusServerConfigured." $resultMessageArguments = @() + $wsusServerConfigured } else { $result = "Passed" $resultMessage = "Windows Updates are downloading from the default Windows Update location." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-HttpsConnection { $ruleId = "HttpsConnectionCheck" $ruleName = "Https connection check" $result = $null $resultMessage = $null $ruleGroupId = "prerequisites" $ruleGroupName = "Prerequisites Checks" $endpoint = "login.microsoftonline.com" $resultMessageArguments = @() + $endpoint $result = Is-EndpointReachable $endpoint if($result -eq "Passed") { $resultMessage = "Machine is able to make https requests." } else { $resultMessage = "Machine is not able to make https requests." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-ProxySettings { $ruleId = "ProxySettingsCheck" $ruleName = "Proxy settings check" $ruleDescription = "Check if Proxy is enabled on the VM." $result = $null $resultMessage = "" $ruleGroupId = "prerequisites" $ruleGroupName = "prerequisite checks" $resultMessageId = "" $resultMessageArguments = @() $res = netsh winhttp show proxy if ($res -like '*Direct access*') { $result = "Passed" $resultMessage = "Proxy is not set." } else { $result = "PassedWithWarning" $resultMessage = "Proxy is set. Ensure all required endpoints are reachable." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-WUIsEnabled { $windowsServiceName = "wuauserv" $windowsServiceDisplayName = "Windows Update" $ruleId = "WUServiceRunningCheck" $ruleName = "Windows Update service must be running" $ruleDescription = "Windows update service must not be in the disabled state." $result = $null $resultMessage = $null $ruleGroupId = "machinesettings" $ruleGroupName = "Machine Update Settings" $resultMessageArguments = @() if(Get-Service -Name $windowsServiceName -ErrorAction SilentlyContinue | select -property name,starttype | Where-Object {$_.StartType -eq "Disabled"} | Select-Object) { $result = "Failed" $resultMessage = "$windowsServiceDisplayName service ($windowsServiceName) is disabled. Please set it to automatic or manual. You can run 'sc config wuauserv start= demand' to set it to manual." } else { $result = "Passed" $resultMessage = "$windowsServiceDisplayName service ($windowsServiceName) is running." } $resultMessageId = "$ruleId.$result" return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } function Validate-MUIsEnabled { $ruleId = "MUEnableCheck" $ruleName = "Microsoft Update enabled check" $ruleDescription = "Microsoft Update must be running to receive updates for Microsoft Products." $result = $null $reason = "" $resultMessage = $null $ruleGroupId = "machinesettings" $ruleGroupName = "Machine Update Settings" $resultMessageArguments = @() # Create a COM object to interact with the Windows Update Agent API $updateServiceManager = New-Object -ComObject "Microsoft.Update.ServiceManager" # Get the services added to the WUA $services = $updateServiceManager.Services # Check if Microsoft Update (MU) is among the services $muRegistered = $services | Where-Object { $_.ServiceID -eq "7971f918-a847-4430-9279-4a52d1efe18d" } if ($muRegistered) { # Now check registry to see if MU is enabled $registryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\7971f918-a847-4430-9279-4a52d1efe18d" $registryValueName = "RegisteredWithAU" $registeredWithAU = Get-ItemProperty -Path $registryPath -Name $registryValueName -ErrorAction SilentlyContinue if ($registeredWithAU -and $registeredWithAU.RegisteredWithAU -eq 1) { $result = "Passed" $resultMessage = "Microsoft Update is registered with Windows Update and enabled." $resultMessageId = "$ruleId.$result" } else { $result = "PassedWithWarning" $reason = "MURegisteredButDisabled" $resultMessage = "Microsoft Update is registered with Windows Update but disabled. Please enable Microsoft Update to receive updates for Microsoft Products." $resultMessageId = "$ruleId.$result.$reason" } } else { $result = "PassedWithWarning" $resultMessage = "Microsoft Update is disabled. Please enable Microsoft Update to receive updates for Microsoft Products." $resultMessageId = "$ruleId.$result" } return New-RuleCheckResult $ruleId $ruleName $ruleDescription $result $resultMessage $ruleGroupId $ruleGroupName $resultMessageId $resultMessageArguments } $validationResults = @() $validationResults += Validate-DotNetFrameworkInstalled $validationResults += Validate-TLSVersion $validationResults += Validate-HttpsConnection $validationResults += Validate-ProxySettings $validationResults += Validate-AzureGuestAgentServices $validationResults += Validate-WireServerConnectivity $validationResults += Validate-WindowsPatchExtension $validationResults += Validate-WSUSServerConfigured if($null -ne $validationResults[-1] -and $validationResults[-1].CheckResult -eq "Passed") { $validationResults += Validate-WindowsUpdateEndpointConnectivity } $validationResults += Validate-AlwaysAutoRebootEnabled $validationResults += Validate-AutomaticUpdateEnabled $validationResults += Validate-WUIsEnabled $validationResults += Validate-MUIsEnabled if($returnAsJson.IsPresent) { return ConvertTo-Json $validationResults -Compress } else { return $validationResults }