$outputPath = "$env:temp\CollectedData\Intune\Commands\General" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} msinfo32.exe /NFO "$outputPath\msinfo32.nfo" msinfo32.exe /report "$outputPath\msinfo32.txt" dsregcmd /status RunCommand "whoami" RunCommand "whoami /upn" RunCommand "whoami /all" RunCommand "whoami /logonid" RunCommand "whoami /fqdn" # dsregcmd_debug. Must be ran as system $ErrorActionPreference = "Stop" $Error.Clear() $line = "`*" * 120 function WaitOnSchTask { param([string]$taskName, [string]$OutFile = "$env:SystemRoot\temp\dsregcmd_debug.txt" ) [int]$timer = 0 try { "waiting on $taskName to complete" | Out-file $OutFile -Force -Append -Encoding ascii $status = (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue).State while ( ($status -ne "Ready") -and ($timer -le 150) ) { "status: $status. waiting on $taskName to complete" | Out-file $OutFile -Force -Append -Encoding ascii $status = (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue).State $timer += 5 Start-Sleep 5 } } catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException] { "Something went wrong. Scheduled task not found" | Out-file $OutFile -Force -Append -Encoding ascii } catch { "Something went wrong." | Out-file $OutFile -Force -Append -Encoding ascii $Error[0] | Out-file $OutFile -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $OutFile -Force -Append -Encoding ascii } } $TimeToRun = (Get-Date ).AddSeconds(3) $folderPath = "$env:temp\CollectedData\Intune\Commands\General" $filepath = Join-Path $folderPath "$($env:COMPUTERNAME)_dsregcmd_debug.txt" $timeStamp = (Get-Date).ToString("ddMMyyyyhhmmss") $taskName = "ODC DsregCmd Debug - $timeStamp" $Trigger = New-ScheduledTaskTrigger -At $TimeToRun -Once $User = "NT AUTHORITY\SYSTEM" $desc = "Scheduled task created by Intune One Data Collector." # Create folder if it does not exist for uploader if ( -not ( Test-Path $folderPath -ErrorAction SilentlyContinue) ) { $nil = mkdir $folderPath -Force } $line | Out-File $filepath -Force -Encoding ascii "Starting dsregcmd /debug using Scheduled Task as SYSTEM`r`n" | Out-File $filepath -Append -Force -Encoding ascii $line | Out-File $filepath -Force -Append -Encoding ascii try { $Action= New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c dsregcmd /debug >> $filepath" $nil = Register-ScheduledTask -TaskName $taskName -Trigger $Trigger -User $User -Action $Action -RunLevel Highest –Force $nil = Start-ScheduledTask -TaskName $taskName # wait for task to complete sleep 10 WaitOnSchTask -taskName $taskName -OutFile $filepath #cleanup $nil = Unregister-ScheduledTask -TaskName $taskName -TaskPath "\" -PassThru -Confirm:$false -ErrorAction SilentlyContinue } catch { "Something went wrong." | Out-file $filepath -Force -Append -Encoding ascii $Error[0] | Out-file $filepath -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $filepath -Force -Append -Encoding ascii } #cleanup try { foreach ($taskName in $(( Get-ScheduledTask -TaskPath "\" | Where-Object {$_.TaskName -match "ODC Windows Update Debug" }).TaskName) ){ "Unregistering $taskName" | Out-file $filepath -Force -Append -Encoding ascii $nil = Unregister-ScheduledTask -TaskName "$taskName" -TaskPath "\" -PassThru -Confirm:$false } Get-ScheduledTask -TaskPath "\" | Where-Object {$_.TaskName -match "ODC DsregCmd Debug" } | Out-file $filepath -Force -Append -Encoding ascii } catch { "***Warning: Unable to remove scheduled task $taskName. Please delete this entry from Task Scheduler" | Out-file $filepath -Force -Append -Encoding ascii $Error[0] | Out-file $filepath -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $filepath -Force -Append -Encoding ascii } Function GetAgeDescription($TimeSpan, [switch] $Localized) { $Age = $TimeSpan if ($Age.Days -gt 0) { $AgeDisplay = $Age.Days.ToString() if ($Age.Days -gt 1) { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Days } else { $AgeDisplay += " Days" } } else { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Day } else { $AgeDisplay += " Day" } } } else { if ($Age.Hours -gt 0) { if ($AgeDisplay.Length -gt 0) {$AgeDisplay += " "} $AgeDisplay = $Age.Hours.ToString() if ($Age.Hours -gt 1) { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Hours } else { $AgeDisplay += " Hours" } } else { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Hour } else { $AgeDisplay += " Hour" } } } if ($Age.Minutes -gt 0) { if ($AgeDisplay.Length -gt 0) {$AgeDisplay += " "} $AgeDisplay += $Age.Minutes.ToString() if ($Age.Minutes -gt 1) { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Minutes } else { $AgeDisplay += " Minutes" } } else { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Minute } else { $AgeDisplay += " Minute" } } } if ($Age.Seconds -gt 0) { if ($AgeDisplay.Length -gt 0) {$AgeDisplay += " "} $AgeDisplay += $Age.Seconds.ToString() if ($Age.Seconds -gt 1) { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Seconds } else { $AgeDisplay += " Seconds" } } else { if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Second } else { $AgeDisplay += " Second" } } } if (($Age.TotalSeconds -lt 1)) { if ($AgeDisplay.Length -gt 0) {$AgeDisplay += " "} $AgeDisplay += $Age.TotalSeconds.ToString() if ($Localized.IsPresent) { $AgeDisplay += " " + $UtilsCTSStrings.ID_Seconds } else { $AgeDisplay += " Seconds" } } } Return $AgeDisplay } $OS_Summary = new-object PSObject # Operating System Summary $CS_Summary = new-object PSObject # Computer System Summary $WMIOS = $null $error.Clear() $WMIOS = get-wmiobject -class "win32_operatingsystem" -ErrorAction SilentlyContinue if ($Error.Count -ne 0) { $errorMessage = $Error[0].Exception.Message $errorCode = "0x{0:X}" -f $Error[0].Exception.ErrorCode "Error" + $errorCode + ": $errorMessage connecting to $MachineName" | WriteTo-StdOut $Error.Clear() } # Get all data from WMI if ($WMIOS -ne $null) { #if WMIOS is null - means connection failed. Abort script execution. $WMICS = get-wmiobject -Class "win32_computersystem" $WMIProcessor = get-wmiobject -Class "Win32_processor" $OSProcessorArch = $WMIOS.OSArchitecture $OSProcessorArchDisplay = " " + $OSProcessorArch } #There is no easy way to detect the OS Architecture on pre-Windows Vista Platform if ($OSProcessorArch -eq $null) { if ($MachineName -eq ".") { #Local Computer $OSProcessorArch = $Env:PROCESSOR_ARCHITECTURE } else { $RemoteReg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$MachineName) $OSProcessorArch = ($RemoteReg.OpenSubKey("SYSTEM\CurrentControlSet\Control\Session Manager\Environment")).GetValue("PROCESSOR_ARCHITECTURE") } if ($OSProcessorArch -ne $null) { switch ($OSProcessorArch) { "AMD64" {$ProcessorArchDisplay = " (64-bit)"} "i386" {$ProcessorArchDisplay = " (32-bit)"} "IA64" {$ProcessorArchDisplay = " (64-bit - Itanium)"} default {$ProcessorArchDisplay = " ($ProcessorArch)"} } } else { $OSProcessorArchDisplay = "" } } # Build OS Summary # Name add-member -inputobject $OS_Summary -membertype noteproperty -name "Machine Name" -value $WMIOS.CSName add-member -inputobject $OS_Summary -membertype noteproperty -name "OS Name" -value ($WMIOS.Caption + " Service Pack " + $WMIOS.ServicePackMajorVersion + $OSProcessorArchDisplay) add-member -inputobject $OS_Summary -membertype noteproperty -name "Build" -value ($WMIOS.Version) add-member -inputobject $OS_Summary -membertype noteproperty -name "Time Zone/Offset" -value ((Get-WmiObject -Class Win32_TimeZone).Caption + "/" + $WMIOS.CurrentTimeZone) # Install Date #$date = [DateTime]::ParseExact($wmios.InstallDate.Substring(0, 8), "yyyyMdd", $null) #add-member -inputobject $OS_Summary -membertype noteproperty -name "Install Date" -value $date.ToShortDateString() add-member -inputobject $OS_Summary -membertype noteproperty -name "Last Reboot/Uptime" -value ($WMIOS.ConvertToDateTime($WMIOS.LastBootUpTime).ToString() + " (" + (GetAgeDescription(New-TimeSpan $WMIOS.ConvertToDateTime($WMIOS.LastBootUpTime))) + ")") # Build Computer System Summary # Name add-member -inputobject $CS_Summary -membertype noteproperty -name "Computer Model" -value $WMICS.model $numProcs=0 $ProcessorType = "" $ProcessorName = "" $ProcessorDisplayName= "" foreach ($WMIProc in $WMIProcessor) { $ProcessorType = $WMIProc.manufacturer switch ($WMIProc.NumberOfCores) { 1 {$numberOfCores = "single core"} 2 {$numberOfCores = "dual core"} 4 {$numberOfCores = "quad core"} $null {$numberOfCores = "single core"} default { $numberOfCores = $WMIProc.NumberOfCores.ToString() + " core" } } switch ($WMIProc.Architecture) { 0 {$CpuArchitecture = "x86"} 1 {$CpuArchitecture = "MIPS"} 2 {$CpuArchitecture = "Alpha"} 3 {$CpuArchitecture = "PowerPC"} 6 {$CpuArchitecture = "Itanium"} 9 {$CpuArchitecture = "x64"} } if ($ProcessorDisplayName.Length -eq 0) { $ProcessorDisplayName = " " + $numberOfCores + " $CpuArchitecture processor " + $WMIProc.name } else { if ($ProcessorName -ne $WMIProc.name) { $ProcessorDisplayName += "/ " + " " + $numberOfCores + " $CpuArchitecture processor " + $WMIProc.name } } $numProcs += 1 $ProcessorName = $WMIProc.name } $ProcessorDisplayName = "$numProcs" + $ProcessorDisplayName add-member -inputobject $CS_Summary -membertype noteproperty -name "Processor(s)" -value $ProcessorDisplayName if ($WMICS.Domain -ne $null) { add-member -inputobject $CS_Summary -membertype noteproperty -name "Machine Domain" -value $WMICS.Domain } if ($WMICS.DomainRole -ne $null) { switch ($WMICS.DomainRole) { 0 {$RoleDisplay = "Workstation"} 1 {$RoleDisplay = "Member Workstation"} 2 {$RoleDisplay = "Standalone Server"} 3 {$RoleDisplay = "Member Server"} 4 {$RoleDisplay = "Backup Domain Controller"} 5 {$RoleDisplay = "Primary Domain controller"} } add-member -inputobject $CS_Summary -membertype noteproperty -name "Role" -value $RoleDisplay } if ($WMIOS.ProductType -eq 1) { #Client $AntivirusProductWMI = get-wmiobject -query "select companyName, displayName, versionNumber, productUptoDate, onAccessScanningEnabled FROM AntivirusProduct" -Namespace "root\SecurityCenter" if ($AntivirusProductWMI.displayName -ne $null) { $AntivirusDisplay= $AntivirusProductWMI.companyName + " " + $AntivirusProductWMI.displayName + " version " + $AntivirusProductWMI.versionNumber if ($AntivirusProductWMI.onAccessScanningEnabled) { $AVScanEnabled = "Enabled" } else { $AVScanEnabled = "Disabled" } if ($AntivirusProductWMI.productUptoDate) { $AVUpToDate = "Yes" } else { $AVUpToDate = "No" } #$AntivirusStatus = "OnAccess Scan: $AVScanEnabled" + ". Up to date: $AVUpToDate" add-member -inputobject $OS_Summary -membertype noteproperty -name "Anti Malware" -value $AntivirusDisplay } else { $AntivirusProductWMI = get-wmiobject -Namespace root\SecurityCenter2 -Class AntiVirusProduct if ($AntivirusProductWMI -ne $null) { add-member -inputobject $OS_Summary -membertype noteproperty -name "AntiMalware" -value $AntivirusProductWMI.displayName } } } $SystemPolicies = get-itemproperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" $EnableLUA = $SystemPolicies.EnableLUA $ConsentPromptBehaviorAdmin = $SystemPolicies.ConsentPromptBehaviorAdmin if ($EnableLUA) { $UACDisplay = "Enabled" switch ($ConsentPromptBehaviorAdmin) { 0 {$UACDisplay += " / UAC Mode: ID_UACNoPrompt"} 1 {$UACDisplay += " / UAC Mode: ID_UACPromptCredentials"} 2 {$UACDisplay += " / UAC Mode: ID_UACPromptConsent"} 5 {$UACDisplay += " / UAC Mode: ID_UACPromptConsentApp"} } } else { $UACDisplay = "Disabled" } add-member -inputobject $OS_Summary -membertype noteproperty -name "UAC" -value $UACDisplay add-member -inputobject $OS_Summary -membertype noteproperty -name "Username" -value ($Env:USERDOMAIN + "\" + $Env:USERNAME) # https://en.wikipedia.org/wiki/Windows_10_version_history $versions = @{ "22631" = "Win11 23H2" "22622" = "Win11 22H2" "22621" = "Win11 22H2" "22000" = "Win11 21H2" "19045" = "22H2" "19044" = "21H2" "19043" = "21H1" "19042" = "20H2" "19041" = "20H1" "18363" = "1909" "18362" = "1903" "17763" = "1809" "17134" = "1803" "16299" = "1709" "15063" = "1703" "14393" = "1607" "10586" = "1511" "10240" = "RTM" } $marketingNames = @{ "22631" = "Win11 2023 Update" "22622" = "Win11 2022 Update" "22621" = "Win11 2022 Update" "22000" = "Win11 RTM" "19045" = "Oct 2022 Update" "19044" = "Nov 2021 Update" "19043" = "May 2021 Update" "19042" = "October 2020 Update" "19041" = "May 2020 Update" "18363" = "November 2019 Update" "18362" = "May 2019 Update" "17763" = "October 2018 Update" "17134" = "April 2018 Update" "16299" = "Fall Creators Update" "15063" = "Creators Update" "14393" = "Anniversary Update" "10586" = "November Update" "10240" = "RTM" } $codeNames = @{ "22631" = "Win11 23H2" "22622" = "Win11 22H2" "22621" = "Win11 22H2" "22000" = "Win11 21H2" "19045" = "22H2" "19044" = "21H2" "19043" = "21H1" "19042" = "20H2" "19041" = "20H1" "18363" = "19H2" "18362" = "19H1" "17763" = "Redstone 5" "17134" = "Redstone 4" "16299" = "Redstone 3" "15063" = "Redstone 2" "14393" = "Redstone 1" "10586" = "Threshold 2" "10240" = "Threshold 1" } $Win10Version = Get-WmiObject -Class Win32_OperatingSystem #System Center Advisor Information $SCAKey = "HKLM:\SOFTWARE\Microsoft\SystemCenterAdvisor" if (Test-Path($SCAKey)) { $CustomerID = (Get-ItemProperty -Path $SCAKey).CustomerID if ($CustomerID -ne $null) { "System Center Advisor detected. Customer ID: $CustomerID" | writeto-stdout $SCA_Summary = New-Object PSObject $SCA_Summary | add-member -membertype noteproperty -name "Customer ID" -value $CustomerID $SCA_Summary | ConvertTo-Xml2 | update-diagreport -id ("01_SCACustomerSummary") -name "System Center Advisor" -verbosity Informational } } # Joined/Registered status $ds = dsregcmd /status [bool]$isRegistered = [bool]($ds -match "Work account") [bool]$isJoined = [bool]($ds -match "AzureAdJoined : YES") $Policies = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Policies" if (test-path $Policies) { foreach ($Policy in (dir $Policies)) { if ([guid]::Empty -ne $Policy.PSChildName) { $UserID = $Policy.PSChildName } } } # Registered User and Tenant info # Sort so we always get the most current in case there are orphaned certs $cert = (dir Cert:\LocalMachine\My\ | where { $_.Issuer -match "CN=MS-Organization-Access" } ) if ($cert.count -gt 0) { $cert = ($cert | Sort-Object -Property NotBefore -Descending)[0] } # use to find registry path $thumbPrint = $cert.Thumbprint # Get the tenant name from the registry if (Test-Path HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo\$($thumbPrint)) { $userEmail = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo\$($thumbPrint)).UserEmail $tenant = $userEmail.Split('@')[1] } $MachineID = "Unknown" $DeviceName = "Unknown" $MachineIDContainer = dir HKLM:SOFTWARE\Microsoft\Enrollments -Recurse | Where {$_.Name -match 'MS DM Server'} if ($MachineIDContainer){ $MachineID = $MachineIDContainer| Get-ItemPropertyValue -Name EntDMID $DeviceName = $MachineIDContainer| Get-ItemPropertyValue -Name EntDeviceName } $SubscriptionID = $ds -match "SubscriptionID : (.+)" # Start reporting $highestWinBuildNumber = "22631" $OS_Summary | Format-List if ($Win10Version.BuildNumber -gt $highestWinBuildNumber) { "**** WARNING: Unknown version of Windows detected: Build $($Win10Version.version) *****"} if ( (Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU -eq 125) { "**** WARNING: Windows 10 OS is on the long-term servicing branch (LTSB) for updates. See https://aka.ms/IntuneLTSB for more information. *****" } "Windows build: $($Win10Version.version)" "Windows version: $($versions[$Win10Version.BuildNumber])" "Windows name: $($marketingNames[$Win10Version.BuildNumber])" "Windows code name: $($codeNames[$Win10Version.BuildNumber])" if ($isRegistered) { "Azure AD status: Registered" } elseif ($isJoined) { "Azure AD status: Joined" } else { "Azure AD status: Not registered/joined or unknown" } $accountID = "" $enrollmentID = "" if (Test-Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MDM) { $accountID = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MDM).AccountId } if (Test-Path HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Logger ) { $enrollmentID = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Logger).CurrentEnrollmentId } if (Test-Path HKLM:SOFTWARE\Microsoft\Enrollments\$enrollmentID) { $enrollmentKey = Get-ItemProperty "HKLM:SOFTWARE\Microsoft\Enrollments\$enrollmentID" $AADTenantID = $enrollmentKey.AADTenantID $contextID = ($enrollmentKey.CorrelationID) -replace "[\{\}]", "" } if ($UserID) { "Intune UserID: $UserID" } if ($MachineID) { "Intune Device ID: $MachineID"} if ($accountID) { "Intune Account ID: $accountID"} if ($DeviceName) { "Intune Device Name: $DeviceName"} if ($SubscriptionID) { "SubscriptionID: $SubscriptionID" } if ($tenant) { "Tenant name: $tenant"} if ($userEmail) { "User UPN: $userEmail"} if ($AADTenantID) { "AAD Tenant ID: $AADTenantID"} if ($contextID) { "Context ID: $contextID"} $CS_Summary | Format-List RunCommand systeminfo.exe "BIOS Information" RunCommand "Get-WmiObject -Class Win32_BIOS" RunCommand Get-ComputerInfo $line = ("*" * 120) + "`r`n" "$line Installed products list from Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" $line Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Sort-Object DisplayName | Format-Table -Wrap -AutoSize $line Write-Output "Verbose" $line Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | format-list netsh winhttp show proxy RunCommand "Get-Tpm" RunCommand "Get-Tpm | Select-Object -ExpandProperty SelfTest" RunCommand "tpmtool getdeviceinformation" RunCommand "manage-bde -status" RunCommand "Get-BitLockerVolume |fl * -Force" RunCommand "Manage-bde -protectors -get $env:systemdrive" RunCommand "Get-TpmEndorsementKeyInfo" RunCommand "Get-TpmSupportedFeature -Verbose" RunCommand "Get-CimInstance -ClassName Win32_Tpm -Namespace 'root\CIMV2\Security\MicrosoftTpm' " RunCommand "Get-CimInstance -ClassName Win32_baseboard -Namespace 'root\CIMV2' " RunCommand "reagentc /info" RunCommand "bcdedit /enum all" RunCommand "gwmi win32_bios | fl *" RunCommand "Get-Service tpm |fl *" RunCommand "powercfg /a" # put TPM logs output in files\bitlocker $outputPath = "$env:temp\CollectedData\Intune\Files\Bitlocker" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} $x = &tpmtool gatherlogs "$outputPath" # Registry keys $BLKeys = @( "HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\BitLocker", "HKLM:\SOFTWARE\Microsoft\PolicyManager\default\Bitlocker", "HKLM:\SOFTWARE\Policies\Microsoft\FVE" ) foreach ($BLKey in $BLKeys) { if (Test-Path $BLKey) { Get-PrintableRegKeyValues $BLKey } else { Write-Output "$BLKey not found" } } Get-ChildItem HKLM:\SOFTWARE\Microsoft\PolicyManager\Providers\*\Bitlocker -Recurse # Disk config RunCommand "Get-Disk | fl *" RunCommand "gwmi Win32_DiskPartition" RunCommand "gwmi -class win32_logicaldisk" RunCommand "gwmi -class win32_volume" RunCommand "netsh advfirewall show allprofiles" RunCommand "netsh advfirewall show allprofiles" RunCommand "netsh advfirewall show global" RunCommand "Get-NetFirewallProfile" RunCommand "Get-NetFirewallRule" RunCommand "Get-NetFirewallSetting" RunCommand "Get-MpComputerStatus" #Gather Defender files $mpc = "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" $outputPath = "$env:temp\CollectedData\Intune\Commands\AV" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} $mpcExists = Test-Path -Path $mpc $DefenderServiceIsRunning = (Get-Service sense).Status $WinDefendServiceIsRunning = (Get-Service windefend).Status if ($mpcExists -and ($DefenderServiceIsRunning -eq "Running" )) { RunCommand Get-MpComputerStatus -Verbose RunCommand Get-MpPreference -Verbose RunCommand Get-MpThreatDetection $output = &$mpc -getfiles # Get the full path to the cab $cabPath = $output -match "MpSupportFiles.cab" $cabPath = $cabPath -replace "^.* *(.:.*)", "`$1" copy $cabPath $outPutPath\WindowsDefender-SupportFiles.cab } else { $deflog = join-path $outputPath "DefenderErrors.txt" "Warning: Defender Service (sense) is not running. Startup type is $DefenderStartupType. This is expected if 3rd party AV is installed. Skipping Defender data collection." | Out-File $deflog -Force "Defender service (sense) status is $DefenderServiceIsRunning" | Out-File $deflog -Append -Force "Defender service (windefend) status is $WinDefendServiceIsRunning" | Out-File $deflog -Append -Force "Defender executable exists at $mpc is $mpcExists" | Out-File $deflog -Append -Force } RunCommand -cmdToRun "hostname" RunCommand -cmdToRun "ipconfig /all" RunCommand -cmdToRun "arp -a" RunCommand -cmdToRun "nbtstat -n" RunCommand -cmdToRun "netstat -ano" RunCommand -cmdToRun "netstat -anob" RunCommand -cmdToRun "reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v EnableTCPChimney" RunCommand -cmdToRun "reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v EnableRSS" RunCommand -cmdToRun "reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v EnableTCPA" RunCommand -cmdToRun "reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v DisableTaskOffload" if ($OSVersion.Major -ge 6) { RunCommand -cmdToRun "netsh int tcp show global" RunCommand -cmdToRun "netsh int ipv4 show offload" RunCommand -cmdToRun "netstat -nato -p tcp" -FileDescription "TCP/IP Basic Information" -CollectFile $true } else { RunCommand -cmdToRun "netstat -ano -p tcp" } RunCommand -cmdToRun "net config workstation" if ((Get-Service "lanmanserver").Status -eq 'Running') { RunCommand -cmdToRun "net config server" RunCommand -cmdToRun "net share" } RunCommand -cmdToRun "net sessions" RunCommand -cmdToRun "net use" RunCommand -cmdToRun "net user" RunCommand -cmdToRun "net accounts" RunCommand -cmdToRun "net statistics workstation" Get-Process | select Name, FileVersion, Path, ProductVersion | ft -auto -wrap Get-WmiObject Win32_PnPSignedDriver| select DeviceName, DriverVersion | ft -auto -wrap Get-WmiObject Win32_PnPSignedDriver | select DeviceName, DriverVersion | ft -auto schtasks.exe /Query $outputPath = "$env:temp\CollectedData\Intune\Commands\Windows Update" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} Get-WindowsUpdateLog -Logpath $outputPath\$($env:COMPUTERNAME)_WindowsUpdate.log gpresult.exe /V Get-WindowsOptionalFeature -Online | where {$_.State -eq "Enabled"} echo "======= netsh int show int ======" netsh int show int echo "======= netsh int ipv4 show int ======" netsh int ipv4 show int echo "======= netsh int ipv4 show addresses ======" netsh int ipv4 show addresses echo "======= netsh int ipv4 show ipaddresses ======" netsh int ipv4 show ipaddresses echo "======= netsh int ipv4 show compartments ======" netsh int ipv4 show compartments echo "======= netsh int ipv4 show dnsservers ======" netsh int ipv4 show dnsservers echo "======= netsh int ipv4 show winsservers ======" netsh int ipv4 show winsservers echo "======= netsh int ipv4 show dynamicportrange tcp ======" netsh int ipv4 show dynamicportrange tcp echo "======= netsh int ipv4 show dynamicportrange udp ======" netsh int ipv4 show dynamicportrange udp echo "======= netsh int ipv4 show global ======" netsh int ipv4 show global echo "======= netsh int ipv4 show icmpstats ======" netsh int ipv4 show icmpstats echo "======= netsh int ipv4 show ipstats ======" netsh int ipv4 show ipstats echo "======= netsh int ipv4 show joins ======" netsh int ipv4 show joins echo "======= netsh int ipv4 show offload ======" netsh int ipv4 show offload echo "======= netsh int ipv4 show route ======" netsh int ipv4 show route echo "======= netsh int ipv4 show subint ======" netsh int ipv4 show subint echo "======= netsh int ipv4 show tcpconnections ======" netsh int ipv4 show tcpconnections echo "======= netsh int ipv4 show tcpstats ======" netsh int ipv4 show tcpstats echo "======= netsh int ipv4 show udpconnections ======" netsh int ipv4 show udpconnections echo "======= netsh int ipv4 show udpstats ======" netsh int ipv4 show udpstats echo "======= netsh int ipv4 show destinationcache ======" netsh int ipv4 show destinationcache echo "======= netsh int ipv4 show ipnettomedia ======" netsh int ipv4 show ipnettomedia echo "======= netsh int ipv4 show neighbors ======" netsh int ipv4 show neighbors $uniqueURLs = (dsregcmd /status) | ForEach-Object { if ($_ -match ".*https://(\w+.+?)\/") { $matches[1]} } | Get-Unique $uniqueURLs += "r.manage.microsoft.com" $uniqueURLs += "r.manage.microsoft.us" $uniqueURLs += "enterpriseregistration.windows.net" $ErrorActionPreference = "Stop" foreach($uniqueURL in $uniqueURLs) { if ($uniqueURL -match "(.*):(\d+)") { $port = $Matches[2] $uniqueURL = $Matches[1] } else { $port = "443" } try { $error.Clear() $connectionTest = $false $connection = New-Object System.Net.Sockets.TCPClient $connection.ReceiveTimeout = [int32]500 $connection.SendTimeout = [int32]500 $x = ($connection.ConnectAsync( $uniqueURL, $port)).Wait(5000) $connectionTest = $connection.Connected } catch { Write-Output "Error connecting to $uniqueURL`: $error" } finally { "{0, -72} : {1,6}" -f "Able to connect to port $port on $uniqueURL", $connectionTest | Write-Output } } $DNSCommands = @("Get-DnsClient", "Get-DnsClientCache", "Get-DnsClientDohServerAddress", "Get-DnsClientGlobalSetting", "Get-DnsClientNrptGlobal", "Get-DnsClientNrptPolicy", "Get-DnsClientNrptRule", "Get-DnsClientServerAddress" ) foreach ($DNSCommand in $DNSCommands) { RunCommand $DNSCommand } w32tm /tz #NDES function Get-FilesOlderThan { Param ([int]$days = 2, [string]$path = $pwd ) $DaysAgo = (Get-Date).AddDays(-($days)) $files = @() $files = Get-ChildItem $path | where { $_.CreationTime -gt $DaysAgo } # get latest file if none meet our criteria if ((Test-Path $path) -and (-not $files) ) { $newestFile = Get-ChildItem $path |Sort-Object LastAccessTime -Descending | Select-Object -First 1 $files += $newestFile } $files } if (Test-Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector) { $installFolder = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector).InstallFolder $copyPath = "$env:temp\CollectedData\Intune\Files\NDES" if ($installFolder) { $ndesPaths = @( "$installFolder\NDESPolicyModule\Logs\NDESPlugin.log", "$installFolder\NDESConnectorSvc\Logs\Logs\NDESConnector*", "$installFolder\NDESConnectorSvc\Logs\Logs\CertificateRegistrationPoint*", "$installFolder\NDESConnectorUI\NDESConnectorUI.log", "$installFolder\NDESConnectorUI\Logs\*", "C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log" "$installFolder\NDESPolicyModule\Logs\NDESPlugin.log", "C:\NDESConnectorSetup\*.log", "$env:programfiles\Microsoft Configuration Manager\logs\ndes*" ) if (-not(test-path $copyPath) ) { $x = mkdir $copyPath -Force } ForEach ($ndesPath in $ndesPaths) { if (Test-Path $ndesPath) { $filesToCollect = Get-FilesOlderThan -path $ndesPath -days 3 foreach ($fileToCollect in $filesToCollect) { copy $fileToCollect $copyPath} } } copy "C:\NDESConnectorSetupMSI\*" $copyPath } } certutil.exe #ConfigMgr if (Test-Path HKLM:\SOFTWARE\Microsoft\CCM\Logging\@Global) { $ccmFolder = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\CCM\Logging\@Global).LogDirectory $copyPath = "$env:temp\CollectedData\Intune\Files\ConfigMgr" if ($ccmFolder) { if (-not(test-path $copyPath) ) { $x = mkdir $copyPath -Force } $filesToCollect = Get-ChildItem -Path $ccmFolder foreach ($fileToCollect in $filesToCollect) { copy $fileToCollect.PSPath $copyPath} } } else { "CCM folder not found. Exiting." } RunCommand -cmdToRun "certutil -v -template" if (Test-Path HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\CAInfo) { $config = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP\CAInfo).Configuration RunCommand -cmdToRun "certutil -CATemplates -config $config" } $ndesScript = @' # cut here <# .SYNOPSIS Highlights configuration problems on an NDES server, as configured for use with Intune Standalone SCEP certificates. .DESCRIPTION Validate-NDESConfig looks at the configuration of your NDES server and ensures it aligns to the "Configure and manage SCEP certificates with Intune" article. .NOTE This script is used purely to validate the configuration. All remedial tasks will need to be carried out manually. Where possible, a link and section description will be provided. .EXAMPLE .\Validate-NDESConfiguration -NDESServiceAccount Contoso\NDES_SVC.com -IssuingCAServerFQDN IssuingCA.contoso.com -SCEPUserCertTemplate SCEPGeneral .EXAMPLE .\Validate-NDESConfiguration -help .LINK https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure #> [CmdletBinding(DefaultParameterSetName="Unattended")] Param( [parameter(Mandatory=$false,ParameterSetName="Unattended")] [alias("ua","silent","s","unattended")] [switch]$unattend, [parameter(Mandatory=$true,ParameterSetName="NormalRun")] [alias("sa")] [ValidateScript({ if ($_ -match ".\\.") { $True } else { Throw "Please use the format Domain\Username for the NDES Service Account variable." } $EnteredDomain = $_.split("\") $ads = New-Object -ComObject ADSystemInfo $Domain = $ads.GetType().InvokeMember('DomainShortName','GetProperty', $Null, $ads, $Null) if ($EnteredDomain -like "$Domain") { $True } else { Throw "Incorrect Domain. Ensure domain is '$($Domain)\<USERNAME>'" } } )] [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." } } )] [string]$IssuingCAServerFQDN, [parameter(Mandatory=$true,ParameterSetName="NormalRun")] [alias("t")] [string]$SCEPUserCertTemplate, [parameter(ParameterSetName="Help")] [alias("h","?","/?")] [switch]$help, [parameter(ParameterSetName="Help")] [alias("u")] [switch]$usage ) ####################################################################### Function Log-ScriptEvent { [CmdletBinding()] Param( [parameter(Mandatory=$True)] [String]$LogFilePath, [parameter(Mandatory=$True)] [String]$Value, [parameter(Mandatory=$True)] [String]$Component, [parameter(Mandatory=$True)] [ValidateRange(1,3)] [Single]$Severity ) $DateTime = New-Object -ComObject WbemScripting.SWbemDateTime $DateTime.SetVarDate($(Get-Date)) $UtcValue = $DateTime.Value $UtcOffset = $UtcValue.Substring(21, $UtcValue.Length - 21) $LogLine = "<![LOG[$Value]LOG]!>" +` "<time=`"$(Get-Date -Format HH:mm:ss.fff)$($UtcOffset)`" " +` "date=`"$(Get-Date -Format M-d-yyyy)`" " +` "component=`"$Component`" " +` "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +` "type=`"$Severity`" " +` "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +` "file=`"`">" Add-Content -Path $LogFilePath -Value $LogLine } ########################################################################################################## function Show-Usage { Write-Output "" Write-Output "-help -h Displays the help." Write-Output "-usage -u Displays this usage information." Write-Output "-NDESExternalHostname -ed External DNS name for the NDES server (SSL certificate subject will be checked for this. It should be in the SAN of the certificate if" Write-Output " clients communicate directly with the NDES server)" Write-Output "-NDESServiceAccount -sa Username of the NDES service account. Format is Domain\sAMAccountName, such as Contoso\NDES_SVC." Write-Output "-IssuingCAServerFQDN -ca Name of the issuing CA to which you'll be connecting the NDES server. Format is FQDN, such as 'MyIssuingCAServer.contoso.com'." Write-Output "-SCEPUserCertTemplate -t Name of the SCEP Certificate template. Please note this is _not_ the display name of the template. Value should not contain spaces." Write-Output "" } ####################################################################### function Get-NDESHelp { Write-Output "" Write-Output "Verifies if the NDES server meets all the required configuration. " Write-Output "" Write-Output "The NDES server role is required as back-end infrastructure for Intune Standalone for delivering VPN and Wi-Fi certificates via the SCEP protocol to mobile devices and desktop clients." Write-Output "See https://docs.microsoft.com/en-us/intune/certificates-scep-configure." Write-Output "" } ####################################################################### if ($help){ Get-NDESHelp break } if ($usage){ Show-Usage break } ####################################################################### #Requires -version 3.0 #Requires -RunAsAdministrator ####################################################################### $parent = [System.IO.Path]::GetTempPath() [string] $name = [System.Guid]::NewGuid() New-Item -ItemType Directory -Path (Join-Path $parent $name) | Out-Null $TempDirPath = "$parent$name" $LogFilePath = "$($TempDirPath)\Validate-NDESConfig.log" ####################################################################### #region Proceed with Variables... if ($PSCmdlet.ParameterSetName -eq "Unattended") { $NDESServiceAccount = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\PFXCertificateConnector\CA*").Username $MscepRaEku = '1.3.6.1.4.1.311.20.2.1' # CEP Encryption # Get cert authority from the Certificate Request Agent cert. $IssuingCAServerFQDN = Get-Item 'Cert:\LocalMachine\My\*' | where { ($_.EnhancedKeyUsageList -match $MscepRaEku) -and ($_.Extensions.Format(1)[0].split('(')[0] -replace "template=" -match "CEPEncryption" ) } $SCEPUserCertTemplate = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Cryptography\MSCEP).EncryptionTemplate $confirmation = "y" } else { Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "NDES Service Account = "-NoNewline Write-Output "$($NDESServiceAccount)" Write-Output "" Write-Output "Issuing CA Server = " Write-Output "$($IssuingCAServerFQDN)" Write-Output "" Write-Output "SCEP Certificate Template = " Write-Output "$($SCEPUserCertTemplate)" Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Proceed with variables? [Y]es, [N]o" $confirmation = Read-Host } #endregion ####################################################################### if ($confirmation -eq 'y'){ Write-Output "" Write-Output "......................................................." Log-ScriptEvent $LogFilePath "Initializing log file $($TempDirPath)\Validate-NDESConfig.log" NDES_Validation 1 Log-ScriptEvent $LogFilePath "Proceeding with variables=YES" NDES_Validation 1 Log-ScriptEvent $LogFilePath "NDESServiceAccount=$($NDESServiceAccount)" NDES_Validation 1 Log-ScriptEvent $LogFilePath "IssuingCAServer=$($IssuingCAServerFQDN)" NDES_Validation 1 Log-ScriptEvent $LogFilePath "SCEPCertificateTemplate=$($SCEPUserCertTemplate)" NDES_Validation 1 ####################################################################### #region Install RSAT tools, Check if NDES and IIS installed if (-not (Get-WindowsFeature ADCS-Device-Enrollment).Installed){ Write-Output "Error: NDES Not installed" Write-Output "Exiting....................." Log-ScriptEvent $LogFilePath "NDES Not installed" NDES_Validation 3 break } Install-WindowsFeature RSAT-AD-PowerShell | Out-Null Import-Module ActiveDirectory | Out-Null if (-not (Get-WindowsFeature Web-WebServer).Installed){ $IISNotInstalled = $TRUE Write-Warning "IIS is not installed. Some tests will not run as we're unable to import the WebAdministration module" Write-Output "" Log-ScriptEvent $LogFilePath "IIS is not installed. Some tests will not run as we're unable to import the WebAdministration module" NDES_Validation 2 } else { Import-Module WebAdministration | Out-Null } #endregion ####################################################################### #region checking OS version Write-Output "" Write-Output "Checking Windows OS version..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking OS Version" NDES_Validation 1 $OSVersion = (Get-CimInstance -class Win32_OperatingSystem).Version $MinOSVersion = "6.3" if ([version]$OSVersion -lt [version]$MinOSVersion){ Write-Output "Error: Unsupported OS Version. NDES Requires 2012 R2 and above." Log-ScriptEvent $LogFilePath "Unsupported OS Version. NDES Requires 2012 R2 and above." NDES_Validation 3 } else { Write-Output "Success: " Write-Output "OS Version " Write-Output "$($OSVersion)" Write-Output " supported." Log-ScriptEvent $LogFilePath "Server is version $($OSVersion)" NDES_Validation 1 } #endregion ####################################################################### #region Checking NDES Service Account properties in Active Directory Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking NDES Service Account properties in Active Directory..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking NDES Service Account properties in Active Directory" NDES_Validation 1 $ADUser = $NDESServiceAccount.split("\")[1] $ADUserProps = (Get-ADUser $ADUser -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut) if ($ADUserProps.enabled -ne $TRUE -OR $ADUserProps.PasswordExpired -ne $false -OR $ADUserProps.LockedOut -eq $TRUE){ Write-Output "Error: Problem with the AD account. Please see output below to determine the issue" Write-Output "" Log-ScriptEvent $LogFilePath "Problem with the AD account. Please see output below to determine the issue" NDES_Validation 3 } else { Write-Output "Success: " Write-Output "NDES Service Account seems to be in working order:" Log-ScriptEvent $LogFilePath "NDES Service Account seems to be in working order" NDES_Validation 1 } Get-ADUser $ADUser -Properties SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut | fl SamAccountName,enabled,AccountExpirationDate,accountExpires,accountlockouttime,PasswordExpired,PasswordLastSet,PasswordNeverExpires,LockedOut #endregion ####################################################################### #region Checking NDES Service Account local permissions Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking NDES Service Account local permissions..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking NDES Service Account local permissions" NDES_Validation 1 if ((net localgroup) -match "Administrators"){ $LocalAdminsMember = ((net localgroup Administrators)) if ($LocalAdminsMember -like "*$NDESServiceAccount*"){ Write-Warning "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use IIS_IUSERS instead." Log-ScriptEvent $LogFilePath "NDES Service Account is a member of the local Administrators group. This will provide the requisite rights but is _not_ a secure configuration. Use IIS_IUSERS instead." NDES_Validation 2 } else { Write-Output "Success: " Write-Output "NDES Service account is not a member of the Local Administrators group" Log-ScriptEvent $LogFilePath "NDES Service account is not a member of the Local Administrators group" NDES_Validation 1 } Write-Output "" Write-Output "Checking NDES Service account is a member of the IIS_IUSR group..." Write-Output "" if ((net localgroup) -match "IIS_IUSRS"){ $IIS_IUSRMembers = ((net localgroup IIS_IUSRS)) if ($IIS_IUSRMembers -like "*$NDESServiceAccount*"){ Write-Output "Success: " Write-Output "NDES Service Account is a member of the local IIS_IUSR group" Log-ScriptEvent $LogFilePath "NDES Service Account is a member of the local IIS_IUSR group" NDES_Validation 1 } else { Write-Output "Error: NDES Service Account is not a member of the local IIS_IUSR group" Log-ScriptEvent $LogFilePath "NDES Service Account is not a member of the local IIS_IUSR group" NDES_Validation 3 Write-Output "" Write-Output "Checking Local Security Policy for explicit rights via gpedit..." Write-Output "" $TempFile = [System.IO.Path]::GetTempFileName() & "secedit" "/export" "/cfg" "$TempFile" | Out-Null $LocalSecPol = Get-Content $TempFile $ADUserProps = Get-ADUser $ADUser $NDESSVCAccountSID = $ADUserProps.SID.Value $LocalSecPolResults = $LocalSecPol | Select-String $NDESSVCAccountSID if ($LocalSecPolResults -match "SeInteractiveLogonRight" -AND $LocalSecPolResults -match "SeBatchLogonRight" -AND $LocalSecPolResults -match "SeServiceLogonRight"){ Write-Output "Success: " Write-Output "NDES Service Account has been assigned the Logon Locally, Logon as a Service and Logon as a batch job rights explicitly." Log-ScriptEvent $LogFilePath "NDES Service Account has been assigned the Logon Locally, Logon as a Service and Logon as a batch job rights explicitly." NDES_Validation 1 Write-Output "" Write-Output "Note:" Write-Output " The Logon Locally is not required in normal runtime." Write-Output "" Write-Output "Note:" Write-Output 'Consider using the IIS_IUSERS group instead of explicit rights as documented under "Step 1 - Create an NDES service account".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" } else { Write-Output "Error: NDES Service Account has _NOT_ been assigned the Logon Locally, Logon as a Service or Logon as a batch job rights _explicitly_." Write-Output 'Please review "Step 1 - Create an NDES service account".' Write-Output "https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "NDES Service Account has _NOT_ been assigned the Logon Locally, Logon as a Service or Logon as a batch job rights _explicitly_." NDES_Validation 3 } } } else { Write-Output "Error: No IIS_IUSRS group exists. Ensure IIS is installed." Write-Output 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' Write-Output "https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "No IIS_IUSRS group exists. Ensure IIS is installed." NDES_Validation 3 } } else { Write-Warning "No local Administrators group exists, likely due to this being a Domain Controller. It is not recommended to run NDES on a Domain Controller." Log-ScriptEvent $LogFilePath "No local Administrators group exists, likely due to this being a Domain Controller. It is not recommended to run NDES on a Domain Controller." NDES_Validation 2 } #endregion ####################################################################### #region Checking Windows Features are installed. Write-Output "" Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking Windows Features are installed..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking Windows Features are installed..." NDES_Validation 1 $WindowsFeatures = @("Web-Filtering","Web-Net-Ext45","NET-Framework-45-Core","NET-WCF-HTTP-Activation45","Web-Metabase","Web-WMI") foreach($WindowsFeature in $WindowsFeatures){ $Feature = Get-WindowsFeature $WindowsFeature $FeatureDisplayName = $Feature.displayName if($Feature.installed){ Write-Output "Success:" Write-Output "$FeatureDisplayName Feature Installed" Log-ScriptEvent $LogFilePath "$($FeatureDisplayName) Feature Installed" NDES_Validation 1 } else { Write-Output "Error: $FeatureDisplayName Feature not installed!" Write-Output 'Please review "Step 3.1b - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "$($FeatureDisplayName) Feature not installed" NDES_Validation 3 } } #endregion ################################################################# #region Checking NDES Install Paramaters $ErrorActionPreference = "SilentlyContinue" Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking NDES Install Paramaters..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking NDES Install Paramaters" NDES_Validation 1 $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"')) { Write-Output "Success: " Write-Output "Correct CSP used in install parameters" Write-Output "" Write-Output $InstallParams.Message Log-ScriptEvent $LogFilePath "Correct CSP used in install parameters:" NDES_Validation 1 Log-ScriptEvent $LogFilePath "$($InstallParams.Message)" NDES_Eventvwr 1 } else { Write-Output "Error: Incorrect CSP selected during install. NDES only supports the CryptoAPI CSP." Write-Output "" Write-Output $InstallParams.Message Log-ScriptEvent $LogFilePath "Error: Incorrect CSP selected during install. NDES only supports the CryptoAPI CSP" NDES_Validation 3 Log-ScriptEvent $LogFilePath "$($InstallParams.Message)" NDES_Eventvwr 3 } $ErrorActionPreference = "Continue" #endregion ################################################################# #region Checking IIS Application Pool health Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking IIS Application Pool health..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking IIS Application Pool health" NDES_Validation 1 if (-not ($IISNotInstalled -eq $TRUE)){ # If SCEP AppPool Exists if (Test-Path 'IIS:\AppPools\SCEP'){ $IISSCEPAppPoolAccount = Get-Item 'IIS:\AppPools\SCEP' | select -expandproperty processmodel | select -Expand username if ((Get-WebAppPoolState "SCEP").value -match "Started"){ $SCEPAppPoolRunning = $TRUE } } else { Write-Output "Error: SCEP Application Pool missing!" Write-Output 'Please review "Step 3.1 - Configure prerequisites on the NDES server"'. Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "SCEP Application Pool missing" NDES_Validation 3 } if ($IISSCEPAppPoolAccount -contains "$NDESServiceAccount"){ Write-Output "Success: " Write-Output "Application Pool is configured to use " Write-Output "$($IISSCEPAppPoolAccount)" Log-ScriptEvent $LogFilePath "Application Pool is configured to use $($IISSCEPAppPoolAccount)" NDES_Validation 1 } else { Write-Output "Error: Application Pool is not configured to use the NDES Service Account" Write-Output 'Please review "Step 4.1 - Configure NDES for use with Intune".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "Application Pool is not configured to use the NDES Service Account" NDES_Validation 3 } if ($SCEPAppPoolRunning){ Write-Output "Success: " Write-Output "SCEP Application Pool is Started " Log-ScriptEvent $LogFilePath "SCEP Application Pool is Started" NDES_Validation 1 } else { Write-Output "Error: SCEP Application Pool is stopped!" Write-Output "Please start the SCEP Application Pool via IIS Management Console. You should also review the Application Event log output for Errors" Log-ScriptEvent $LogFilePath "SCEP Application Pool is stopped" NDES_Validation 3 } } else { Write-Output "IIS is not installed." Log-ScriptEvent $LogFilePath "SCEP Application Pool is stopped" NDES_Validation 3 } #endregion ################################################################# #region Checking registry has been set to allow long URLs Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output 'Checking registry "HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters" has been set to allow long URLs...' Write-Output "" Log-ScriptEvent $LogFilePath "Checking registry (HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters) has been set to allow long URLs" NDES_Validation 1 if (-not ($IISNotInstalled -eq $TRUE)){ If ((Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxFieldLength).MaxfieldLength -notmatch "65534"){ Write-Output "Error: MaxFieldLength not set to 65534 in the registry!" Write-Output "" Write-Output 'Please review "Step 4.3 - Configure NDES for use with Intune".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "MaxFieldLength not set to 65534 in the registry" NDES_Validation 3 } else { Write-Output "Success: " Write-Output "MaxFieldLength set correctly" Log-ScriptEvent $LogFilePath "MaxFieldLength set correctly" NDES_Validation 1 } if ((Get-ItemProperty -Path HKLM:SYSTEM\CurrentControlSet\Services\HTTP\Parameters -Name MaxRequestBytes).MaxRequestBytes -notmatch "65534"){ Write-Output "MaxRequestBytes not set to 65534 in the registry!" Write-Output "" Write-Output 'Please review "Step 4.3 - Configure NDES for use with Intune".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure'" Log-ScriptEvent $LogFilePath "MaxRequestBytes not set to 65534 in the registry" NDES_Validation 3 } else { Write-Output "Success: " Write-Output "MaxRequestBytes set correctly" Log-ScriptEvent $LogFilePath "MaxRequestBytes set correctly" NDES_Validation 1 } } else { Write-Output "IIS is not installed." Log-ScriptEvent $LogFilePath "IIS is not installed." NDES_Validation 3 } #endregion ################################################################# #region Checking SPN has been set... Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking SPN has been set..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking SPN has been set" NDES_Validation 1 $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname $spn = setspn.exe -L $ADUser if ($spn -match $hostname){ Write-Output "Success: " Write-Output "Correct SPN set for the NDES service account:" Write-Output "" Write-Output $spn Log-ScriptEvent $LogFilePath "Correct SPN set for the NDES service account: $($spn)" NDES_Validation 1 } else { Write-Output "Error: Missing or Incorrect SPN set for the NDES Service Account!" Write-Output 'Please review "Step 3.1c - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "Missing or Incorrect SPN set for the NDES Service Account" NDES_Validation 3 } #endregion ################################################################# #region Checking there are no intermediate certs are in the Trusted Root store Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking there are no intermediate certs are in the Trusted Root store..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking there are no intermediate certs are in the Trusted Root store" NDES_Validation 1 $IntermediateCertCheck = Get-Childitem cert:\LocalMachine\root -Recurse | Where-Object {$_.Issuer -ne $_.Subject} if ($IntermediateCertCheck){ Write-Output "Error: Intermediate certificate found in the Trusted Root store. This can cause undesired effects and should be removed." Write-Output "Certificates:" Write-Output "" Write-Output $IntermediateCertCheck Log-ScriptEvent $LogFilePath "Intermediate certificate found in the Trusted Root store: $($IntermediateCertCheck)" NDES_Validation 3 } else { Write-Output "Success: " Write-Output "Trusted Root store does not contain any Intermediate certificates." Log-ScriptEvent $LogFilePath "Trusted Root store does not contain any Intermediate certificates." NDES_Validation 1 } #endregion ################################################################# #region Checking the EnrollmentAgentOffline and CEPEncryption are present $ErrorActionPreference = "Silentlycontinue" Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking the EnrollmentAgentOffline and CEPEncryption are present..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking the EnrollmentAgentOffline and CEPEncryption are present" NDES_Validation 1 $certs = Get-ChildItem cert:\LocalMachine\My\ # Looping through all certificates in LocalMachine Store Foreach ($item in $certs){ $Output = ($item.Extensions| where-object {$_.oid.FriendlyName -like "**"}).format(0).split(",") if ($Output -match "EnrollmentAgentOffline"){ $EnrollmentAgentOffline = $TRUE } if ($Output -match "CEPEncryption"){ $CEPEncryption = $TRUE } } # Checking if EnrollmentAgentOffline certificate is present if ($EnrollmentAgentOffline){ Write-Output "Success: " Write-Output "EnrollmentAgentOffline certificate is present" Log-ScriptEvent $LogFilePath "EnrollmentAgentOffline certificate is present" NDES_Validation 1 } else { Write-Output "Error: EnrollmentAgentOffline certificate is not present!" Write-Output "This can take place when an account without Enterprise Admin permissions installs NDES. You may need to remove the NDES role and reinstall with the correct permissions." Write-Output 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "EnrollmentAgentOffline certificate is not present" NDES_Validation 3 } # Checking if CEPEncryption is present if ($CEPEncryption){ Write-Output "Success: " Write-Output "CEPEncryption certificate is present" Log-ScriptEvent $LogFilePath "CEPEncryption certificate is present" NDES_Validation 1 } else { Write-Output "Error: CEPEncryption certificate is not present!" Write-Output "This can take place when an account without Enterprise Admin permissions installs NDES. You may need to remove the NDES role and reinstall with the correct permissions." Write-Output 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "CEPEncryption certificate is not present" NDES_Validation 3 } $ErrorActionPreference = "Continue" #endregion ################################################################# #region Checking registry has been set with the SCEP certificate template name Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output 'Checking registry "HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP" has been set with the SCEP certificate template name...' Write-Output "" Log-ScriptEvent $LogFilePath "Checking registry (HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP) has been set with the SCEP certificate template name" NDES_Validation 1 if (-not (Test-Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP)){ Write-Output "Error: Registry key does not exist. This can occur if the NDES role has been installed but not configured." Write-Output 'Please review "Step 3 - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "MSCEP Registry key does not exist." NDES_Validation 3 } else { $SignatureTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name SignatureTemplate).SignatureTemplate $EncryptionTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name EncryptionTemplate).EncryptionTemplate $GeneralPurposeTemplate = (Get-ItemProperty -Path HKLM:SOFTWARE\Microsoft\Cryptography\MSCEP\ -Name GeneralPurposeTemplate).GeneralPurposeTemplate $DefaultUsageTemplate = "IPSECIntermediateOffline" if ($SignatureTemplate -match $DefaultUsageTemplate -AND $EncryptionTemplate -match $DefaultUsageTemplate -AND $GeneralPurposeTemplate -match $DefaultUsageTemplate){ Write-Output "Error: Registry has not been configured with the SCEP Certificate template name. Default values have _not_ been changed." Write-Output 'Please review "Step 3.1 - Configure prerequisites on the NDES server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Write-Output "" Log-ScriptEvent $LogFilePath "Registry has not been configured with the SCEP Certificate template name. Default values have _not_ been changed." NDES_Validation 3 $FurtherReading = $FALSE } else { Write-Output "One or more default values have been changed." Write-Output "" Write-Output "Checking SignatureTemplate key..." Write-Output "" if ($SignatureTemplate -match $SCEPUserCertTemplate){ Write-Output "Success: " Write-Output "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _SignatureTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template." Write-Output "" Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate)' has been written to the registry under the _SignatureTemplate_ key" NDES_Validation 1 } else { Write-Warning '"SignatureTemplate key does not match the SCEP certificate template name. Unless your template is explicitly set for the "Signature" purpose, this can safely be ignored."' Write-Output "" Write-Output "Registry value: " Write-Output "$($SignatureTemplate)" Write-Output "" Write-Output "SCEP certificate template value: " Write-Output "$($SCEPUserCertTemplate)" Write-Output "" Log-ScriptEvent $LogFilePath "SignatureTemplate key does not match the SCEP certificate template name.Registry value=$($SignatureTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 } Write-Output "......................." Write-Output "" Write-Output "Checking EncryptionTemplate key..." Write-Output "" if ($EncryptionTemplate -match $SCEPUserCertTemplate){ Write-Output "Success: " Write-Output "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _EncryptionTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template." Write-Output "" Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate) has been written to the registry under the _EncryptionTemplate_ key" NDES_Validation 1 } else { Write-Warning '"EncryptionTemplate key does not match the SCEP certificate template name. Unless your template is explicitly set for the "Encryption" purpose, this can safely be ignored."' Write-Output "" Write-Output "Registry value: " Write-Output "$($EncryptionTemplate)" Write-Output "" Write-Output "SCEP certificate template value: " Write-Output "$($SCEPUserCertTemplate)" Write-Output "" Log-ScriptEvent $LogFilePath "EncryptionTemplate key does not match the SCEP certificate template name.Registry value=$($EncryptionTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 } Write-Output "......................." Write-Output "" Write-Output "Checking GeneralPurposeTemplate key..." Write-Output "" if ($GeneralPurposeTemplate -match $SCEPUserCertTemplate){ Write-Output "Success: " Write-Output "SCEP certificate template '$($SCEPUserCertTemplate)' has been written to the registry under the _GeneralPurposeTemplate_ key. Ensure this aligns with the usage specificed on the SCEP template" Log-ScriptEvent $LogFilePath "SCEP certificate template $($SCEPUserCertTemplate) has been written to the registry under the _GeneralPurposeTemplate_ key" NDES_Validation 1 } else { Write-Warning '"GeneralPurposeTemplate key does not match the SCEP certificate template name. Unless your template is set for the "Signature and Encryption" (General) purpose, this can safely be ignored."' Write-Output "" Write-Output "Registry value: " Write-Output "$($GeneralPurposeTemplate)" Write-Output "" Write-Output "SCEP certificate template value: " Write-Output "$($SCEPUserCertTemplate)" Write-Output "" Log-ScriptEvent $LogFilePath "GeneralPurposeTemplate key does not match the SCEP certificate template name.Registry value=$($GeneralPurposeTemplate)|SCEP certificate template value=$($SCEPUserCertTemplate)" NDES_Validation 2 } } if ($furtherreading-EQ $true){ Write-Output "......................." Write-Output "" Write-Output 'For further reading, please review "Step 4.2 - Configure NDES for use with Intune".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" } } $ErrorActionPreference = "Continue" #endregion ################################################################# #region Checking server certificate. Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking IIS SSL certificate is valid for use..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking IIS SSL certificate is valid for use" NDES_Validation 1 $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname $serverAuthEKU = "1.3.6.1.5.5.7.3.1" # Server Authentication $allSSLCerts = Get-ChildItem Cert:\LocalMachine\My $BoundServerCert = netsh http show sslcert foreach ($Cert in $allSSLCerts) { $ServerCertThumb = $cert.Thumbprint if ($BoundServerCert -match $ServerCertThumb){ $BoundServerCertThumb = $ServerCertThumb } } $ServerCertObject = Get-ChildItem Cert:\LocalMachine\My\$BoundServerCertThumb if ($ServerCertObject.Issuer -match $ServerCertObject.Subject){ $SelfSigned = $true } else { $SelfSigned = $false } if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU -AND (($ServerCertObject.Subject -match $hostname) -or ($ServerCertObject.DnsNameList -match $hostname)) -AND $ServerCertObject.Issuer -notmatch $ServerCertObject.Subject){ Write-Output "Success: " Write-Output "Certificate bound in IIS is valid:" Write-Output "" Write-Output "Subject: " Write-Output "$($ServerCertObject.Subject)" Write-Output "" Write-Output "Thumbprint: " Write-Output "$($ServerCertObject.Thumbprint)" Write-Output "" Write-Output "Valid Until: " Write-Output "$($ServerCertObject.NotAfter)" Write-Output "" Write-Output "If this NDES server is in your perimeter network, please ensure the external hostname is shown below:" $DNSNameList = $ServerCertObject.DNSNameList.unicode Write-Output "" Write-Output "Internal and External hostnames: " Write-Output "$($DNSNameList)" Log-ScriptEvent $LogFilePath "Certificate bound in IIS is valid. Subject:$($ServerCertObject.Subject)|Thumbprint:$($ServerCertObject.Thumbprint)|ValidUntil:$($ServerCertObject.NotAfter)|Internal&ExternalHostnames:$($DNSNameList)" NDES_Validation 1 } else { Write-Output "Error: The certificate bound in IIS is not valid for use. Reason:" Write-Output "" if ($ServerCertObject.EnhancedKeyUsageList -match $serverAuthEKU) { $EKUValid = $true } else { $EKUValid = $false Write-Output "Correct EKU: " Write-Output "$($EKUValid)" Write-Output "" } if ($ServerCertObject.Subject -match $hostname) { $SubjectValid = $true } else { $SubjectValid = $false Write-Output "Correct Subject: " Write-Output "$($SubjectValid)" Write-Output "" } if ($SelfSigned -eq $false){ Out-Null } else { Write-Output "Is Self-Signed: " Write-Output "$($SelfSigned)" Write-Output "" } Write-Output 'Please review "Step 4 - Configure NDES for use with Intune>To Install and bind certificates on the NDES Server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "The certificate bound in IIS is not valid for use. CorrectEKU=$($EKUValid)|CorrectSubject=$($SubjectValid)|IsSelfSigned=$($SelfSigned)" NDES_Validation 3 } #endregion ################################################################# #region Checking Client certificate. Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking encrypting certificate is valid for use..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking encrypting certificate is valid for use..." NDES_Validation 1 $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname $clientAuthEku = "1.3.6.1.5.5.7.3.2" # Client Authentication $NDESCertThumbprint = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\PFXCertificateConnector -Name EncryptionCertThumbprint).EncryptionCertThumbprint $ClientCertObject = Get-ChildItem Cert:\LocalMachine\My\$NDESCertThumbprint if ($ClientCertObject.Issuer -match $ClientCertObject.Subject){ $ClientCertSelfSigned = $true } else { $ClientCertSelfSigned = $false } if ($ClientCertObject.EnhancedKeyUsageList -match $clientAuthEku -AND $ClientCertObject.Issuer -notmatch $ClientCertObject.Subject){ Write-Output "Success: " Write-Output "Client certificate bound to NDES Connector is valid:" Write-Output "" Write-Output "Subject: " Write-Output "$($ClientCertObject.Subject)" Write-Output "" Write-Output "Thumbprint: " Write-Output "$($ClientCertObject.Thumbprint)" Write-Output "" Write-Output "Valid Until: " Write-Output "$($ClientCertObject.NotAfter)" Log-ScriptEvent $LogFilePath "Client certificate bound to NDES Connector is valid. Subject:$($ClientCertObject.Subject)|Thumbprint:$($ClientCertObject.Thumbprint)|ValidUntil:$($ClientCertObject.NotAfter)" NDES_Validation 1 } else { Write-Output "Error: The certificate bound to the NDES Connector is not valid for use. Reason:" Write-Output "" if ($ClientCertObject.EnhancedKeyUsageList -match $clientAuthEku) { $ClientCertEKUValid = $true } else { $ClientCertEKUValid = $false Write-Output "Correct EKU: " Write-Output "$($ClientCertEKUValid)" Write-Output "" } if ($ClientCertSelfSigned -eq $false){ Out-Null } else { Write-Output "Is Self-Signed: " Write-Output "$($ClientCertSelfSigned)" Write-Output "" } Write-Output 'Please review "Step 4 - Configure NDES for use with Intune>To Install and bind certificates on the NDES Server".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Log-ScriptEvent $LogFilePath "The certificate bound to the NDES Connector is not valid for use. CorrectEKU=$($ClientCertEKUValid))|IsSelfSigned=$($ClientCertSelfSigned)" NDES_Validation 3 } #endregion ################################################################# #region Checking behaviour of internal NDES URL Write-Output "" Write-Output "......................................................." $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).hostname Write-Output "" Write-Output "Checking behaviour of internal NDES URL: " Write-Output "https://$hostname/certsrv/mscep/mscep.dll" Write-Output "" Log-ScriptEvent $LogFilePath "Checking behaviour of internal NDES URL" NDES_Validation 1 Log-ScriptEvent $LogFilePath "Https://$hostname/certsrv/mscep/mscep.dll" NDES_Validation 1 $Statuscode = try {(Invoke-WebRequest -Uri https://$hostname/certsrv/mscep/mscep.dll).statuscode} catch {$_.Exception.Response.StatusCode.Value__} if ($statuscode -eq "200"){ Write-Output "Error: https://$hostname/certsrv/mscep/mscep.dll returns 200 OK. This usually signifies an error with the Intune Connector registering itself or not being installed." Log-ScriptEvent $LogFilePath "https://$hostname/certsrv/mscep/mscep.dll returns 200 OK. This usually signifies an error with the Intune Connector registering itself or not being installed" NDES_Validation 3 } elseif ($statuscode -eq "403"){ Write-Output "Trying to retrieve CA Capabilitiess..." Write-Output "" $Newstatuscode = try {(Invoke-WebRequest -Uri "https://$hostname/certsrv/mscep/mscep.dll?operation=GetCACaps&message=test").statuscode} catch {$_.Exception.Response.StatusCode.Value__} if ($Newstatuscode -eq "200"){ $CACaps = (Invoke-WebRequest -Uri "https://$hostname/certsrv/mscep?operation=GetCACaps&message=test").content } if ($CACaps){ Write-Output "Success: " Write-Output "CA Capabilities retrieved:" Write-Output "" Write-Output $CACaps Log-ScriptEvent $LogFilePath "CA Capabilities retrieved:$CACaps" NDES_Validation 1 } } else { Write-Output "Error: Unexpected Error code! This usually signifies an error with the Intune Connector registering itself or not being installed" Write-Output "Expected value is a 403. We received a $($Statuscode). This could be down to a missing reboot post policy module install. Verify last boot time and module install time further down the validation." Log-ScriptEvent $LogFilePath "Unexpected Error code. Expected:403|Received:$Statuscode" NDES_Validation 3 } #endregion ################################################################# #region Checking Servers last boot time Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking Servers last boot time..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking Servers last boot time" NDES_Validation 1 $LastBoot = (Get-WmiObject win32_operatingsystem | select csname, @{LABEL='LastBootUpTime' ;EXPRESSION={$_.ConverttoDateTime($_.lastbootuptime)}}).lastbootuptime Write-Output "Server last rebooted: "-NoNewline Write-Output "$($LastBoot). " Write-Output "Please ensure a reboot has taken place _after_ all registry changes and installing the NDES Connector. IISRESET is _not_ sufficient." Log-ScriptEvent $LogFilePath "LastBootTime:$LastBoot" NDES_Validation 1 #endregion ################################################################# #region Checking Intune Connector is installed Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking Intune Connector is installed..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking Intune Connector is installed" NDES_Validation 1 if ($IntuneConnector = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | ? {$_.DisplayName -eq "Certificate Connector for Microsoft Intune"}){ Write-Output "Success: " Write-Output "$($IntuneConnector.DisplayName) was installed on " Write-Output "$($IntuneConnector.InstallDate) " Write-Output "and is version " Write-Output "$($IntuneConnector.DisplayVersion)" Write-Output "" Log-ScriptEvent $LogFilePath "ConnectorVersion:$IntuneConnector" NDES_Validation 1 } else { Write-Output "Error: Intune Connector not installed" Write-Output 'Please review "Step 5 - Enable, install, and configure the Intune certificate connector".' Write-Output "URL: https://docs.microsoft.com/en-us/intune/certificates-scep-configure#configure-your-infrastructure" Write-Output "" Log-ScriptEvent $LogFilePath "ConnectorNotInstalled" NDES_Validation 3 } #endregion ################################################################# #region Checking Intune Connector registry keys (KeyRecoveryAgentCertificate, PfxSigningCertificate and SigningCertificate) Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking Intune Connector registry keys are intact" Write-Output "" Log-ScriptEvent $LogFilePath "Checking Intune Connector registry keys are intact" NDES_Validation 1 $ErrorActionPreference = "SilentlyContinue" $KeyRecoveryAgentCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\KeyRecoveryAgentCertificate" $PfxSigningCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\PfxSigningCertificate" $SigningCertificate = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\SigningCertificate" if (-not ($KeyRecoveryAgentCertificate)){ Write-Output "Error: KeyRecoveryAgentCertificate Registry key does not exist." Write-Output "" Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate Registry key does not exist." NDES_Validation 3 } else { $KeyRecoveryAgentCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name KeyRecoveryAgentCertificate).KeyRecoveryAgentCertificate if (-not ($KeyRecoveryAgentCertificatePresent)) { Write-Warning "KeyRecoveryAgentCertificate registry key exists but has no value" Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate missing Value" NDES_Validation 2 } else { Write-Output "Success: " Write-Output "KeyRecoveryAgentCertificate registry key exists" Log-ScriptEvent $LogFilePath "KeyRecoveryAgentCertificate registry key exists" NDES_Validation 1 } } if (-not ($PfxSigningCertificate)){ Write-Output "Error: PfxSigningCertificate Registry key does not exist." Write-Output "" Log-ScriptEvent $LogFilePath "PfxSigningCertificate Registry key does not exist." NDES_Validation 3 } else { $PfxSigningCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name PfxSigningCertificate).PfxSigningCertificate if (-not ($PfxSigningCertificatePresent)) { Write-Warning "PfxSigningCertificate registry key exists but has no value" Log-ScriptEvent $LogFilePath "PfxSigningCertificate missing Value" NDES_Validation 2 } else { Write-Output "Success: " Write-Output "PfxSigningCertificate registry keys exists" Log-ScriptEvent $LogFilePath "PfxSigningCertificate registry key exists" NDES_Validation 1 } } if (-not ($SigningCertificate)){ Write-Output "Error: SigningCertificate Registry key does not exist." Write-Output "" Log-ScriptEvent $LogFilePath "SigningCertificate Registry key does not exist" NDES_Validation 3 } else { $SigningCertificatePresent = (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\MicrosoftIntune\NDESConnector\ -Name SigningCertificate).SigningCertificate if (-not ($SigningCertificatePresent)) { Write-Warning "SigningCertificate registry key exists but has no value" Log-ScriptEvent $LogFilePath "SigningCertificate registry key exists but has no value" NDES_Validation 2 } else { Write-Output "Success: " Write-Output "SigningCertificate registry key exists" Log-ScriptEvent $LogFilePath "SigningCertificate registry key exists" NDES_Validation 1 } } $ErrorActionPreference = "Continue" #endregion ################################################################# #region Checking eventlog for pertinent errors $ErrorActionPreference = "SilentlyContinue" $EventLogCollDays = ((Get-Date).AddDays(-5)) #Number of days to go back in the event log Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Checking Event logs for pertinent errors..." Write-Output "" Log-ScriptEvent $LogFilePath "Checking Event logs for pertinent errors" NDES_Validation 1 if (-not (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After $EventLogCollDays -ErrorAction silentlycontinue)) { Write-Output "Success: " Write-Output "No errors found in the Microsoft Intune Connector" Write-Output "" Log-ScriptEvent $LogFilePath "No errors found in the Microsoft Intune Connector" NDES_Validation 1 } else { Write-Warning "Errors found in the Microsoft Intune Connector Event log. Please see below for the most recent 5, and investigate further in Event Viewer." Write-Output "" $EventsCol1 = (Get-EventLog -LogName "Microsoft Intune Connector" -EntryType Error -After $EventLogCollDays -Newest 5 | select TimeGenerated,Source,Message) $EventsCol1 | fl Log-ScriptEvent $LogFilePath "Errors found in the Microsoft Intune Connector Event log" NDES_Eventvwr 3 $i = 0 $count = @($EventsCol1).count foreach ($item in $EventsCol1) { Log-ScriptEvent $LogFilePath "$($EventsCol1[$i].TimeGenerated);$($EventsCol1[$i].Message);$($EventsCol1[$i].Source)" NDES_Eventvwr 3 $i++ } } if (-not (Get-EventLog -LogName "Application" -EntryType Error -Source NDESConnector,Microsoft-Windows-NetworkDeviceEnrollmentService -After $EventLogCollDays -ErrorAction silentlycontinue)) { Write-Output "Success: " Write-Output "No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" Write-Output "" Log-ScriptEvent $LogFilePath "No errors found in the Application log from source NetworkDeviceEnrollmentService or NDESConnector" NDES_Validation 1 } else { Write-Warning "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." Write-Output "" $EventsCol2 = (Get-EventLog -LogName "Application" -EntryType Error -Source NDESConnector,Microsoft-Windows-NetworkDeviceEnrollmentService -After $EventLogCollDays -Newest 5 | select TimeGenerated,Source,Message) $EventsCol2 |fl $i = 0 $count = @($EventsCol2).count foreach ($item in $EventsCol2) { Log-ScriptEvent $LogFilePath "$($EventsCol2[$i].TimeGenerated);$($EventsCol2[$i].Message);$($EventsCol2[$i].Source)" NDES_Eventvwr 3 $i++ } } $ErrorActionPreference = "Continue" #endregion ################################################################# #region Zip up logfiles Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Log Files..." Write-Output "" if ($PSCmdlet.ParameterSetName -eq "Unattended") { Write-Output "Automatically gathering files." $LogFileCollectionConfirmation = "y" } else { Write-Output "Do you want to gather troubleshooting files? This includes IIS, NDES Connector, NDES Plugin, CRP, and MSCEP log files, in addition to the SCEP template configuration. [Y]es, [N]o:" $LogFileCollectionConfirmation = Read-Host } if ($LogFileCollectionConfirmation -eq "y"){ $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*" foreach ($IISLog in $IISLogs){ Copy-Item -Path $IISLog.FullName -Destination $TempDirPath } foreach ($NDESConnectorLog in $NDESConnectorLogs){ Copy-Item -Path $NDESConnectorLog.FullName -Destination $TempDirPath } foreach ($NDESPluginLog in $NDESPluginLogs){ Copy-Item -Path $NDESPluginLog.FullName -Destination $TempDirPath } foreach ($MSCEPLog in $MSCEPLogs){ Copy-Item -Path $MSCEPLog.FullName -Destination $TempDirPath } foreach ($CRPLog in $CRPLogs){ Copy-Item -Path $CRPLogs.FullName -Destination $TempDirPath } $SCEPUserCertTemplateOutputFilePath = "$($TempDirPath)\SCEPUserCertTemplate.txt" certutil -v -template $SCEPUserCertTemplate > $SCEPUserCertTemplateOutputFilePath Log-ScriptEvent $LogFilePath "Collecting server logs" NDES_Validation 1 Add-Type -assembly "system.io.compression.filesystem" $Currentlocation = $env:temp $date = Get-Date -Format ddMMyyhhmmss [io.compression.zipfile]::CreateFromDirectory($TempDirPath, "$($Currentlocation)\$($date)-CertConnectorLogs-$($hostname).zip") Write-Output "" Write-Output "Success: " Write-Output "Log files copied to $($Currentlocation)\$($date)-CertConnectorLogs-$($hostname).zip" Write-Output "" } else { Log-ScriptEvent $LogFilePath "Do not collect logs" NDES_Validation 1 $WriteLogOutputPath = $True } #endregion ################################################################# #region Ending script Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "End of NDES configuration validation" Write-Output "" if ($WriteLogOutputPath -eq $True) { Write-Output "Log file copied to $($LogFilePath)" Write-Output "" # for ODC $copyPath = "$env:temp\CollectedData\Intune\Files\NDES" if ($PSCmdlet.ParameterSetName -eq "Unattended" ){ if ( -not (test-path $copyPath) ) { mkdir $copyPath -Force } copy $LogFilePath $copyPath } } Write-Output "Ending script..." Write-Output "" #endregion ################################################################# } else { Write-Output "" Write-Output "......................................................." Write-Output "" Write-Output "Incorrect variables. Please run the script again..." Write-Output "" Write-Output "Exiting................................................" Write-Output "" exit } '@ # cut here if (Get-Service -Name PKIConnectorSvc -ErrorAction SilentlyContinue) { $outputPath = "$env:temp\CollectedData\Intune\Commands\NDES" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} $logfile = Join-Path $outputPath Validate-NDESConfiguration.txt $ndesscript | out-file ndes.ps1 . .\ndes.ps1 | Out-File $logfile -Force del .\ndes.ps1 } else { "Not on Intune Certificate Connector server. Exiting module." } RunCommand -cmdToRun "Get-Service | fl *" RunCommand -cmdToRun "fltmc filters" RunCommand -cmdToRun "fltmc volumes" if (test-path HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device) { reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\current\device\ /s } else { "Warning - no policies found under HKLM:\Software\Microsoft\PolicyManager\current\device" } if ( (dir Cert:\LocalMachine\My).Count -gt 0) { dir Cert:\LocalMachine\My | Select-Object * -ExpandProperty Extensions -ExcludeProperty RawData, PrivateKey } else { "Warning - no certs found in Local Machine personal certificate store (cert:\localmachine\my)" } if ( (dir Cert:\CurrentUser\My).Count -gt 0) { dir Cert:\CurrentUser\My | Select-Object * -ExpandProperty Extensions -ExcludeProperty RawData, PrivateKey } else { "Warning - no certs found in Local Machine personal certificate store (cert:\localmachine\my)" } dir $env:systemroot\IMECache -ErrorAction SilentlyContinue dir "${env:ProgramFiles(x86)}\Microsoft Intune Management Extension" -Recurse -ErrorAction SilentlyContinue "Service information" $ErrorActionPreference = "Stop" $Error.Clear() try { Get-Service IntuneManagementExtension |fl * } catch { "IntuneManagementExtension (IME) service not installed. Verify that the device is Azure AD registered or AAD joined, has internnet connectivity, has WNS enabled, and has at least one PowerShell script or Win32 app assigned to it. See https://docs.microsoft.com/en-us/mem/intune/apps/intune-management-extension#issue-intune-management-extension-doesnt-download for more details." $Error } "Scheduled task information" $ErrorActionPreference = "Stop" try { Get-ScheduledTask  -TaskName "Intune Management Extension Health Evaluation" -ErrorAction SilentlyContinue | Select-Object * -ExpandProperty CimInstanceProperties Get-ScheduledTask  -TaskName "Intune Management Extension Health Evaluation" -ErrorAction SilentlyContinue | Get-ScheduledTaskInfo  | Select-Object * -ExpandProperty CimInstanceProperties } catch { "Warning: Unable to find IME scheduled tasks" $Error } <# Get-IMELogs * make temp folder * copy IME logs * decode to plain text NOTE: Do not run from the IntuneManagementExtension\Logs folder #> <# Parse policy message to format in more user-friendly output #> function Format-ImePolicyInfo { param($Message) [string]$JsonPrettyPrint = "" # trim anything before opening JSON brace $Message = $Message -replace "^.*?Get policies = \[", "[" # fixup if closing brace was stripped out if( -not ($Message -match "\]$") ){ $Message += "]"} $JsonPrettyPrint = ($Message | ConvertFrom-Json | ConvertTo-Json) $JsonPrettyPrint } function ConvertTo-IMEPrettyPrint { [CmdletBinding()] param ( [Parameter()] [string] $IMELog ) # Prettify output $sampleLines = Get-Content $IMELog -TotalCount 10 foreach ($sampleLine in $sampleLines) { if ($sampleLine -match $regex){ $spaces = ($Matches[3]).length + ($Matches[2]).length +2 } } $indent = " " * $spaces $regex = [regex]"(?m)^\<\!\[LOG\[(.+?\n*.*?)\]+LOG\]\!\>\<time=`"(.+?)`" date=`"(.+?)`".*file=`"`"\>\r?\n?$" $twoLineRegex = [regex]"(?m)^(.+?\n*.*?)\]LOG\]\!\>\<time=`"(.+?)`" date=`"(.+?)`".*file=`"`"\>\r?\n?$" $txtFile = $IMELog -replace "\.log", "-PrettyPrint.txt" $lines = Get-Content $IMELog $counter = 0 foreach($line in $lines) { $counter += 1 # show progress if ( $($counter % 1000) -eq 0) { Write-Host "." -NoNewline -ForegroundColor Green } # handle large binary blobs if ($line.length -gt 50000) { $line | Out-File -Force -Append $txtFile } elseif($line -match $regex){ $DateTime = "$($Matches[3]) $($Matches[2])" $Message = $Matches[1] # indicate visually that session has ended if($Message -match '\[datasensor\] whmRegionInCurrentKey is') { Write-Output "$("`r`n" * 8) $('*' * 50)`r`n NEW SESSION`r`n $('*' * 50)`r`n" | Out-File -Force -Append $txtFile } # Pretty print JSON elseif($Message -match "^Get policies = \[") { "$DateTime Get policies =" | Out-File -Force -Append $txtFile Format-ImePolicyInfo($Message) | Out-File -Force -Append $txtFile } else { "$DateTime $Message"| Out-File -Force -Append $txtFile } } elseif ($line -match $twoLineRegex) { "$($Matches[3]) $($Matches[2]) $($Matches[1])" | Out-File -Force -Append $txtFile } # beginning of log message ending in line break. Message continues on next line elseif ($line -match "^\<\!\[LOG\[(.*)") { "$indent $($Matches[1])" | Out-File -Force -Append $txtFile } # print the line without processing if no patterns match else { $line | Out-File -Force -Append $txtFile } } } #Region Main $workingFolder = Join-Path -Path $env:temp -ChildPath $("IMETemp" + $(Get-Random)) $null = mkdir $workingFolder -Force Copy-Item $env:ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log $workingFolder Copy-Item $env:ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log $workingFolder foreach ($file in $(Get-ChildItem $workingFolder\*.log)) { Write-Output "`r`nProcessing $file" ConvertTo-IMEPrettyPrint $file } $outputPath = "$env:temp\CollectedData\Intune\Files\Sidecar\PrettyPrint" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} Move-Item $workingFolder\*.txt $outputPath -Force Remove-Item $workingFolder -Recurse -Force #endregion # make sure msinfo32 has exited $timeoutInSec = 180 $elapsed = 0 $stillRunning = $true while ( ($elapsed -lt $timeoutInSec) -and $stillRunning ) { if (Get-Process -Name msinfo32 -ErrorAction SilentlyContinue) { sleep 2 $elapsed += 2 Write-DiagProgress "Waiting for msinfo32 to complete... $elapsed / $timeoutInSec seconds" } else { $stillRunning = $false } } MdmDiagnosticsTool.exe -out $pwd $outputPath = "$env:temp\CollectedData\Intune\Commands\General" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} move .\MDMDiagReport.* $outputPath RunCommand -cmdToRun "Get-CimInstance -ClassName AntiVirusProduct -Namespace ROOT\SecurityCenter2" RunCommand -cmdToRun "Get-CimInstance -ClassName AntiSpywareProduct -Namespace ROOT\SecurityCenter2" RunCommand -cmdToRun "Get-CimInstance -ClassName FirewallProduct -Namespace ROOT\SecurityCenter2" RunCommand -cmdToRun "Get-CimInstance -ClassName MSFT_MpComputerStatus -Namespace ROOT\Microsoft\ProtectionManagement" RunCommand -cmdToRun "Get-CimInstance -ClassName AntimalwareDetectionStatus -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName AntimalwareHealthStatus -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName AntimalwareInfectionStatus -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName Malware -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName ProtectionTechnologyStatus -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName SerializableToXml -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName Win32_ProviderEx -Namespace ROOT\Microsoft\SecurityClient" RunCommand -cmdToRun "Get-CimInstance -ClassName AntimalwareDetectionStatus -Namespace ROOT\Microsoft\SecurityClient" Get-BitsTransfer -AllUsers -Verbose |fl * if (test-path c:\mdmtrace) { $outputPath = "$env:temp\CollectedData\Intune\Commands\Debug" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} copy c:\mdmtrace\* $outputpath -force } # query for Company Portal status # https://learn.microsoft.com/en-us/windows/package-manager/winget/troubleshooting $outputPath = "$env:temp\CollectedData\Intune\Commands\General" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} RunCommand "winget search 9WZDNCRFJ3PZ --verbose --accept-source-agreements --disable-interactivity | Out-File $outputPath\Winget_search.txt -Append -Force" RunCommand "winget show 9WZDNCRFJ3PZ --verbose --accept-source-agreements --disable-interactivity| Out-File $outputPath\Winget_show_companyPortal.txt -Append -Force" RunCommand "winget list --verbose --accept-source-agreements --disable-interactivity | Out-File $outputPath\Winget_list.txt -Append -Force" copy $env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir\* $outPutPath $outputPath = "$env:temp\CollectedData\Intune\Commands\General" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} gpresult /SCOPE USER /H "$outputPath\$($env:COMPUTERNAME)_GPResults_user.html" gpresult /SCOPE COMPUTER /H "$outputPath\$($env:COMPUTERNAME)_GPResults_computer.html" $apscript = @' <#PSScriptInfo .VERSION 5.6 .GUID 06025137-9010-4807-bd22-53464539dfa3 .AUTHOR Michael Niehaus .COMPANYNAME Microsoft .COPYRIGHT .TAGS Windows AutoPilot .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES Version 5.6: Fixed parameter handling Version 5.5: Added support for a zip file Version 5.4: Added additional ESP details Version 5.3: Added hardware and OS version details Version 5.2: Added device registration events Version 5.1: Bug fixes Version 5.0: Bug fixes Version 4.9: Bug fixes Version 4.8: Added Delivery Optimization results (but not when using a CAB file), ensured events are displayed even when no ESP Version 4.7: Added ESP settings, fixed bugs Version 4.6: Fixed typo Version 4.5: Fixed but to properly reported Win32 app status when a Win32 app is installed during user ESP Version 4.4: Added more ODJ info Version 4.3: Added policy tracking Version 4.2: Bug fixes for Windows 10 2004 (event ID changes) Version 4.1: Renamed to Get-AutopilotDiagnostics Version 4.0: Added sidecar installation info Version 3.9: Bug fixes Version 3.8: Bug fixes Version 3.7: Modified Office logic to ensure it accurately reflected what ESP thinks the status is. Added ShowPolicies option. Version 3.2: Fixed sidecar detection logic Version 3.1: Fixed ODJ applied output Version 3.0: Added the ability to process logs as well Version 2.2: Added new IME MSI guid, new -AllSessions switch Version 2.0: Added -online parameter to look up app and policy details Version 1.0: Original published version #> <# .SYNOPSIS Displays Windows Autopilot diagnostics information from the current PC or a captured set of logs. .DESCRIPTION This script displays diagnostics information from the current PC or a captured set of logs. This includes details about the Autopilot profile settings; policies, apps, certificate profiles, etc. being tracked via the Enrollment Status Page; and additional information. This should work with Windows 10 1903 and later (earlier versions have not been validated). This script will not work on ARM64 systems due to registry redirection from the use of x86 PowerShell.exe. .PARAMETER Online Look up the actual policy and app names via the Intune Graph API .PARAMETER AllSessions Show all ESP progress instead of just the final details. .PARAMETER CABFile Processes the information in the specified CAB file (captured by MDMDiagnosticsTool.exe -area Autopilot -cab filename.cab) instead of from the registry. .PARAMETER ZIPFile Processes the information in the specified ZIP file (captured by MDMDiagnosticsTool.exe -area Autopilot -zip filename.zip) instead of from the registry. .PARAMETER ShowPolicies Shows the policy details as recorded in the NodeCache registry keys, in the order that the policies were received by the client. .EXAMPLE .\Get-AutopilotDiagnostics.ps1 .EXAMPLE .\Get-AutopilotDiagnostics.ps1 -Online .EXAMPLE .\Get-AutopilotESPStatus.ps1 -AllSessions .EXAMPLE .\Get-AutopilotDiagnostics.ps1 -CABFile C:\Autopilot.cab -Online -AllSessions .EXAMPLE .\Get-AutopilotDiagnostics.ps1 -ZIPFile C:\Autopilot.zip .EXAMPLE .\Get-AutopilotDiagnostics.ps1 -ShowPolicies #> [CmdletBinding()] param( [Parameter(Mandatory=$False)] [String] $CABFile = $null, [Parameter(Mandatory=$False)] [String] $ZIPFile = $null, [Parameter(Mandatory=$False)] [Switch] $Online = $false, [Parameter(Mandatory=$False)] [Switch] $AllSessions = $false, [Parameter(Mandatory=$False)] [Switch] $ShowPolicies = $false ) Begin { # Process log files if needed $script:useFile = $false if ($CABFile -or $ZIPFile) { if (-not (Test-Path "$($env:TEMP)\ESPStatus.tmp")) { New-Item -Path "$($env:TEMP)\ESPStatus.tmp" -ItemType "directory" | Out-Null } Remove-Item -Path "$($env:TEMP)\ESPStatus.tmp\*.*" -Force -Recurse $script:useFile = $true # If using a CAB file, extract the needed files from it if ($CABFile) { $fileList = @("MdmDiagReport_RegistryDump.reg","microsoft-windows-devicemanagement-enterprise-diagnostics-provider-admin.evtx", "microsoft-windows-user device registration-admin.evtx", "AutopilotDDSZTDFile.json", "*.csv") $fileList | % { $null = & expand.exe "$CABFile" -F:$_ "$($env:TEMP)\ESPStatus.tmp\" if (-not (Test-Path "$($env:TEMP)\ESPStatus.tmp\$_")) { Write-Error "Unable to extract $_ from $CABFile" } } } else { # If using a ZIP file, just extract the entire contents (not as easy to do selected files) Expand-Archive -Path $ZIPFile -DestinationPath "$($env:TEMP)\ESPStatus.tmp\" } # Get the hardware hash information $csvFile = (Get-ChildItem "$($env:TEMP)\ESPStatus.tmp\*.csv").FullName if ($csvFile) { $csv = Get-Content $csvFile | ConvertFrom-Csv $hash = $csv.'Hardware Hash' } # Edit the path in the .reg file $content = Get-Content -Path "$($env:TEMP)\ESPStatus.tmp\MdmDiagReport_RegistryDump.reg" $content = $content -replace "\[HKEY_CURRENT_USER\\", "[HKEY_CURRENT_USER\ESPStatus.tmp\USER\" $content = $content -replace "\[HKEY_LOCAL_MACHINE\\", "[HKEY_CURRENT_USER\ESPStatus.tmp\MACHINE\" $content = $content -replace '^ "','"' $content = $content -replace '^ @','@' $content = $content -replace 'DWORD:','dword:' "Windows Registry Editor Version 5.00`n" | Set-Content -Path "$($env:TEMP)\ESPStatus.tmp\MdmDiagReport_Edited.reg" $content | Add-Content -Path "$($env:TEMP)\ESPStatus.tmp\MdmDiagReport_Edited.reg" # Remove the registry info if it exists if (Test-Path "HKCU:\ESPStatus.tmp") { Remove-Item -Path "HKCU:\ESPStatus.tmp" -Recurse -Force } # Import the .reg file $null = & reg.exe IMPORT "$($env:TEMP)\ESPStatus.tmp\MdmDiagReport_Edited.reg" 2>&1 # Configure the (not live) constants $script:provisioningPath = "HKCU:\ESPStatus.tmp\MACHINE\software\microsoft\provisioning" $script:autopilotDiagPath = "HKCU:\ESPStatus.tmp\MACHINE\software\microsoft\provisioning\Diagnostics\Autopilot" $script:omadmPath = "HKCU:\ESPStatus.tmp\MACHINE\software\microsoft\provisioning\OMADM" $script:path = "HKCU:\ESPStatus.tmp\MACHINE\Software\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\ESPTrackingInfo\Diagnostics" $script:msiPath = "HKCU:\ESPStatus.tmp\MACHINE\Software\Microsoft\EnterpriseDesktopAppManagement" $script:officePath = "HKCU:\ESPStatus.tmp\MACHINE\Software\Microsoft\OfficeCSP" $script:sidecarPath = "HKCU:\ESPStatus.tmp\MACHINE\Software\Microsoft\IntuneManagementExtension\Win32Apps" $script:enrollmentsPath = "HKCU:\ESPStatus.tmp\MACHINE\software\microsoft\enrollments" } else { # Configure live constants $script:provisioningPath = "HKLM:\software\microsoft\provisioning" $script:autopilotDiagPath = "HKLM:\software\microsoft\provisioning\Diagnostics\Autopilot" $script:omadmPath = "HKLM:\software\microsoft\provisioning\OMADM" $script:path = "HKLM:\Software\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\ESPTrackingInfo\Diagnostics" $script:msiPath = "HKLM:\Software\Microsoft\EnterpriseDesktopAppManagement" $script:officePath = "HKLM:\Software\Microsoft\OfficeCSP" $script:sidecarPath = "HKLM:\Software\Microsoft\IntuneManagementExtension\Win32Apps" $script:enrollmentsPath = "HKLM:\Software\Microsoft\enrollments" $hash = (Get-WmiObject -Namespace root/cimv2/mdm/dmmap -Class MDM_DevDetail_Ext01 -Filter "InstanceID='Ext' AND ParentID='./DevDetail'").DeviceHardwareData } # Configure other constants $script:officeStatus = @{"0" = "None"; "10" = "Initialized"; "20" = "Download In Progress"; "25" = "Pending Download Retry"; "30" = "Download Failed"; "40" = "Download Completed"; "48" = "Pending User Session"; "50" = "Enforcement In Progress"; "55" = "Pending Enforcement Retry"; "60" = "Enforcement Failed"; "70" = "Success / Enforcement Completed"} $script:espStatus = @{"1" = "Not Installed"; "2" = "Downloading / Installing"; "3" = "Success / Installed"; "4" = "Error / Failed"} $script:policyStatus = @{"0" = "Not Processed"; "1" = "Processed"} # Configure any other global variables $script:observedTimeline = @() } Process { #------------------------ # Functions #------------------------ Function RecordStatus() { param ( [Parameter(Mandatory=$true)] [String] $detail, [Parameter(Mandatory=$true)] [String] $status, [Parameter(Mandatory=$true)] [datetime] $date ) # See if there is already an entry for this policy and status $found = $script:observedTimeline | ? { $_.Detail -eq $detail -and $_.Status -eq $status } if (-not $found) { $script:observedTimeline += New-Object PSObject -Property @{ "Date" = $date "Detail" = $detail "Status" = $status } } } Function AddDisplay() { param ( [Parameter(Mandatory=$true)] [ref]$items ) $items.Value | % { Add-Member -InputObject $_ -NotePropertyName display -NotePropertyValue $AllSessions } $items.Value[$items.Value.Count - 1].display = $true } Function ProcessApps() { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [Microsoft.Win32.RegistryKey] $currentKey, [Parameter(Mandatory=$true)] $currentUser, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$True)] [bool] $display ) Begin { if ($display) { Write-Output "Apps:" } } Process { if ($display) { Write-Output " $(([datetime]$currentKey.PSChildName).ToString('u'))" } $currentKey.Property | % { if ($_.StartsWith("./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/")) { $msiKey = [URI]::UnescapeDataString(($_.Split("/"))[6]) $fullPath = "$msiPath\$currentUser\MSI\$msiKey" if (Test-Path $fullPath) { $status = (Get-ItemProperty -Path $fullPath).Status $msiFile = (Get-ItemProperty -Path $fullPath).CurrentDownloadUrl } if ($status -eq "" -or $status -eq $null) { $status = 0 } if ($msiFile -match "IntuneWindowsAgent.msi") { $msiKey = "Intune Management Extensions ($($msiKey))" } elseif ($Online) { $found = $apps | ? {$_.ProductCode -contains $msiKey} $msiKey = "$($found.DisplayName) ($($msiKey))" } if ($status -eq 70) { if ($display) { Write-Output " MSI $msiKey : $status ($($officeStatus[$status.ToString()]))" } RecordStatus -detail "MSI $msiKey" -status $officeStatus[$status.ToString()] -date $currentKey.PSChildName } elseif ($status -eq 60) { if ($display) { Write-Output " MSI $msiKey : $status ($($officeStatus[$status.ToString()]))" } RecordStatus -detail "MSI $msiKey" -status $officeStatus[$status.ToString()] -date $currentKey.PSChildName } else { if ($display) { Write-Output " MSI $msiKey : $status ($($officeStatus[$status.ToString()]))" } RecordStatus -detail "MSI $msiKey" -status $officeStatus[$status.ToString()] -date $currentKey.PSChildName } } elseif ($_.StartsWith("./Vendor/MSFT/Office/Installation/")) { # Report the main status based on what ESP is tracking $status = Get-ItemPropertyValue -Path $currentKey.PSPath -Name $_ # Then try to get the detailed Office status $officeKey = [URI]::UnescapeDataString(($_.Split("/"))[5]) $fullPath = "$officepath\$officeKey" if (Test-Path $fullPath) { $oStatus = (Get-ItemProperty -Path $fullPath).FinalStatus if ($oStatus -eq $null) { $oStatus = (Get-ItemProperty -Path $fullPath).Status if ($oStatus -eq $null) { $oStatus = "None" } } } else { $oStatus = "None" } if ($officeStatus.Keys -contains $oStatus.ToString()) { $officeStatusText = $officeStatus[$oStatus.ToString()] } else { $officeStatusText = $oStatus } if ($status -eq 1) { if ($display) { Write-Output " Office $officeKey : $status ($($policyStatus[$status.ToString()]) / $officeStatusText)" } RecordStatus -detail "Office $officeKey" -status "$($policyStatus[$status.ToString()]) / $officeStatusText" -date $currentKey.PSChildName } else { if ($display) { Write-Output " Office $officeKey : $status ($($policyStatus[$status.ToString()]) / $officeStatusText)" } RecordStatus -detail "Office $officeKey" -status "$($policyStatus[$status.ToString()]) / $officeStatusText" -date $currentKey.PSChildName } } else { if ($display) { Write-Output " $_ : Unknown app" } } } } } Function ProcessModernApps() { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [Microsoft.Win32.RegistryKey] $currentKey, [Parameter(Mandatory=$true)] $currentUser, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$True)] [bool] $display ) Begin { if ($display) { Write-Output "Modern Apps:" } } Process { if ($display) { Write-Output " $(([datetime]$currentKey.PSChildName).ToString('u'))" } $currentKey.Property | % { $status = (Get-ItemPropertyValue -path $currentKey.PSPath -Name $_).ToString() if ($_.StartsWith("./User/Vendor/MSFT/EnterpriseModernAppManagement/AppManagement/")) { $appID = [URI]::UnescapeDataString(($_.Split("/"))[7]) $type = "User UWP" } elseif ($_.StartsWith("./Device/Vendor/MSFT/EnterpriseModernAppManagement/AppManagement/")) { $appID = [URI]::UnescapeDataString(($_.Split("/"))[7]) $type = "Device UWP" } else { $appID = $_ $type = "Unknown UWP" } if ($status -eq "1") { if ($display) { Write-Output " $type $appID : $status ($($policyStatus[$status]))" } RecordStatus -detail "UWP $appID" -status $policyStatus[$status] -date $currentKey.PSChildName } else { if ($display) { Write-Output " $type $appID : $status ($($policyStatus[$status]))" } } } } } Function ProcessSidecar() { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [Microsoft.Win32.RegistryKey] $currentKey, [Parameter(Mandatory=$true)] $currentUser, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$True)] [bool] $display ) Begin { if ($display) { Write-Output "Sidecar apps:" } } Process { if ($display) { Write-Output " $(([datetime]$currentKey.PSChildName).ToString('u'))" } $currentKey.Property | % { $win32Key = [URI]::UnescapeDataString(($_.Split("/"))[9]) $status = Get-ItemPropertyValue -path $currentKey.PSPath -Name $_ if ($Online) { $found = $apps | ? {$win32Key -match $_.Id } $win32Key = "$($found.DisplayName) ($($win32Key))" } $appGuid = $win32Key.Substring(9) $sidecarApp = "$sidecarPath\$currentUser\$appGuid" $exitCode = $null if (Test-Path $sidecarApp) { $exitCode = (Get-ItemProperty -Path $sidecarApp).ExitCode } if ($status -eq "3") { if ($exitCode -ne $null) { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]), rc = $exitCode)" } } else { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]))" } } RecordStatus -detail "Win32 $win32Key" -status $espStatus[$status.ToString()] -date $currentKey.PSChildName } elseif ($status -eq "4") { if ($exitCode -ne $null) { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]), rc = $exitCode)" } } else { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]))" } } RecordStatus -detail "Win32 $win32Key" -status $espStatus[$status.ToString()] -date $currentKey.PSChildName } else { if ($exitCode -ne $null) { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]), rc = $exitCode)" } } else { if ($display) { Write-Output " Win32 $win32Key : $status ($($espStatus[$status.ToString()]))" } } if ($status -ne "1") { RecordStatus -detail "Win32 $win32Key" -status $espStatus[$status.ToString()] -date $currentKey.PSChildName } } } } } Function ProcessPolicies() { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [Microsoft.Win32.RegistryKey] $currentKey, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$True)] [bool] $display ) Begin { if ($display) { Write-Output "Policies:" } } Process { if ($display) { Write-Output " $(([datetime]$currentKey.PSChildName).ToString('u'))" } $currentKey.Property | % { $status = Get-ItemPropertyValue -path $currentKey.PSPath -Name $_ if ($status -eq "1") { if ($display) { Write-Output " Policy $_ : $status ($($policyStatus[$status.ToString()]))" } RecordStatus -detail "Policy $_" -status $policyStatus[$status.ToString()] -date $currentKey.PSChildName } else { if ($display) { Write-Output " Policy $_ : $status ($($policyStatus[$status.ToString()]))" } } } } } Function ProcessCerts() { param ( [Parameter(Mandatory=$true,ValueFromPipeline=$True)] [Microsoft.Win32.RegistryKey] $currentKey, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$True)] [bool] $display ) Begin { if ($display) { Write-Output "Certificates:" } } Process { if ($display) { Write-Output " $(([datetime]$currentKey.PSChildName).ToString('u'))" } $currentKey.Property | % { $certKey = [URI]::UnescapeDataString(($_.Split("/"))[6]) $status = Get-ItemPropertyValue -path $currentKey.PSPath -Name $_ if ($Online) { $found = $policies | ? { $certKey.Replace("_","-") -match $_.Id } $certKey = "$($found.DisplayName) ($($certKey))" } if ($status -eq "1") { if ($display) { Write-Output " Cert $certKey : $status ($($policyStatus[$status.ToString()]))" } RecordStatus -detail "Cert $certKey" -status $policyStatus[$status.ToString()] -date $currentKey.PSChildName } else { if ($display) { Write-Output " Cert $certKey : $status ($($policyStatus[$status.ToString()]))" } } } } } Function ProcessNodeCache() { Process { $nodeCount = 0 while ($true) { # Get the nodes in order. This won't work after a while because the older numbers are deleted as new ones are added # but it will work out OK shortly after provisioning. The alternative would be to get all the subkeys and then sort # them numerically instead of alphabetically, but that can be saved for later... $node = Get-ItemProperty "$provisioningPath\NodeCache\CSP\Device\MS DM Server\Nodes\$nodeCount" -ErrorAction SilentlyContinue if ($node -eq $null) { break } $nodeCount += 1 $node | Select NodeUri, ExpectedValue } } } Function ProcessEvents() { Process { $productCode = 'IME-Not-Yet-Installed' if (Test-Path "$msiPath\S-0-0-00-0000000000-0000000000-000000000-000\MSI") { Get-ChildItem -path "$msiPath\S-0-0-00-0000000000-0000000000-000000000-000\MSI" | % { $file = (Get-ItemProperty -Path $_.PSPath).CurrentDownloadUrl if ($file -match "IntuneWindowsAgent.msi") { $productCode = Get-ItemPropertyValue -Path $_.PSPath -Name ProductCode } } } # Process device management events if ($script:useFile) { $events = Get-WinEvent -Path "$($env:TEMP)\ESPStatus.tmp\microsoft-windows-devicemanagement-enterprise-diagnostics-provider-admin.evtx" -Oldest | ? { ($_.Message -match $productCode -and $_.Id -in 1905,1906,1920,1922) -or $_.Id -in (72,100,107,109,110,111) } } else { $events = Get-WinEvent -LogName Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin -Oldest | ? { ($_.Message -match $productCode -and $_.Id -in 1905,1906,1920,1922) -or $_.Id -in (72,100,107,109,110,111) } } $events | % { $message = $_.Message $detail = "Sidecar" $event = $_ switch ($_.id) { {$_ -in (110, 109)} { $detail = "Offline Domain Join" switch ($event.Properties[0].Value) { 0 { $message = "Offline domain join not configured" } 1 { $message = "Waiting for ODJ blob" } 2 { $message = "Processed ODJ blob" } 3 { $message = "Timed out waiting for ODJ blob or connectivity" } } } 111 { $detail = "Offline Domain Join"; $message = "Starting wait for ODJ blob"} 107 { $detail = "Offline Domain Join"; $message = "Successfully applied ODJ blob"} 100 { $detail = "Offline Domain Join"; $message = "Could not establish connectivity"; } 72 { $detail = "MDM Enrollment" } 1905 { $message = "Download started" } 1906 { $message = "Download finished" } 1920 { $message = "Installation started" } 1922 { $message = "Installation finished" } {$_ -in (1922, 72)} { } } RecordStatus -detail $detail -date $_.TimeCreated -status $message } # Process device registration events if ($script:useFile) { $events = Get-WinEvent -Path "$($env:TEMP)\ESPStatus.tmp\microsoft-windows-user device registration-admin.evtx" -Oldest | ? { $_.Id -in (306, 101) } } else { $events = Get-WinEvent -LogName 'Microsoft-Windows-User Device Registration/Admin' -Oldest | ? { $_.Id -in (306, 101) } } $events | % { $message = $_.Message $detail = "Device Registration" $event = $_ switch ($_.id) { 101 { $detail = "Device Registration"; $message = "SCP discovery successful." } 304 { $detail = "Device Registration"; $message = "Hybrid AADJ device registration failed." } 306 { $detail = "Device Registration"; $message = "Hybrid AADJ device registration succeeded."; } } RecordStatus -detail $detail -date $_.TimeCreated -status $message } } } Function GetIntuneObjects() { param ( [Parameter(Mandatory=$true)] [String] $uri ) Process { Write-Verbose "GET $uri" try { $response = Invoke-MSGraphRequest -Url $uri -HttpMethod Get $objects = $response.value $objectsNextLink = $response."@odata.nextLink" while ($objectsNextLink -ne $null){ $response = (Invoke-MSGraphRequest -Url $devicesNextLink -HttpMethod Get) $objectsNextLink = $response."@odata.nextLink" $objects += $response.value } return $objects } catch { Write-Error $_.Exception return $null break } } } #------------------------ # Main code #------------------------ # If online, make sure we are able to authenticate if ($Online) { # Make sure we can connect $module = Import-Module Microsoft.Graph.Intune -PassThru -ErrorAction Ignore if (-not $module) { Write-Output "Installing module Microsoft.Graph.Intune" Install-Module Microsoft.Graph.Intune -Force } Import-Module Microsoft.Graph.Intune $graph = Connect-MSGraph Write-Output "Connected to tenant $($graph.TenantId)" # Get a list of apps Write-Output "Getting list of apps" $script:apps = GetIntuneObjects("https://graph.microsoft.com/beta/deviceAppManagement/mobileApps") # Get a list of policies (for certs) Write-Output "Getting list of policies" $script:policies = GetIntuneObjects("https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations") } # Display Autopilot diag details Write-Output "" Write-Output "AUTOPILOT DIAGNOSTICS" Write-Output "" $values = Get-ItemProperty "$autopilotDiagPath" if (-not $values.CloudAssignedTenantId) { Write-Output "This is not an Autopilot device.`n" exit 0 } if (-not $script:useFile) { $osVersion = (Get-WmiObject win32_operatingsystem).Version Write-Output "OS version: $osVersion" } Write-Output "Profile: $($values.DeploymentProfileName)" Write-Output "TenantDomain: $($values.CloudAssignedTenantDomain)" Write-Output "TenantID: $($values.CloudAssignedTenantId)" $correlations = Get-ItemProperty "$autopilotDiagPath\EstablishedCorrelations" Write-Output "ZTDID: $($correlations.ZTDRegistrationID)" Write-Output "EntDMID: $($correlations.EntDMID)" Write-Output "OobeConfig: $($values.CloudAssignedOobeConfig)" if (($values.CloudAssignedOobeConfig -band 1024) -gt 0) { Write-Output " Skip keyboard: Yes 1 - - - - - - - - - -" } else { Write-Output " Skip keyboard: No 0 - - - - - - - - - -" } if (($values.CloudAssignedOobeConfig -band 512) -gt 0) { Write-Output " Enable patch download: Yes - 1 - - - - - - - - -" } else { Write-Output " Enable patch download: No - 0 - - - - - - - - -" } if (($values.CloudAssignedOobeConfig -band 256) -gt 0) { Write-Output " Skip Windows upgrade UX: Yes - - 1 - - - - - - - -" } else { Write-Output " Skip Windows upgrade UX: No - - 0 - - - - - - - -" } if (($values.CloudAssignedOobeConfig -band 128) -gt 0) { Write-Output " AAD TPM Required: Yes - - - 1 - - - - - - -" } else { Write-Output " AAD TPM Required: No - - - 0 - - - - - - -" } if (($values.CloudAssignedOobeConfig -band 64) -gt 0) { Write-Output " AAD device auth: Yes - - - - 1 - - - - - -" } else { Write-Output " AAD device auth: No - - - - 0 - - - - - -" } if (($values.CloudAssignedOobeConfig -band 32) -gt 0) { Write-Output " TPM attestation: Yes - - - - - 1 - - - - -" } else { Write-Output " TPM attestation: No - - - - - 0 - - - - -" } if (($values.CloudAssignedOobeConfig -band 16) -gt 0) { Write-Output " Skip EULA: Yes - - - - - - 1 - - - -" } else { Write-Output " Skip EULA: No - - - - - - 0 - - - -" } if (($values.CloudAssignedOobeConfig -band 8) -gt 0) { Write-Output " Skip OEM registration: Yes - - - - - - - 1 - - -" } else { Write-Output " Skip OEM registration: No - - - - - - - 0 - - -" } if (($values.CloudAssignedOobeConfig -band 4) -gt 0) { Write-Output " Skip express settings: Yes - - - - - - - - 1 - -" } else { Write-Output " Skip express settings: No - - - - - - - - 0 - -" } if (($values.CloudAssignedOobeConfig -band 2) -gt 0) { Write-Output " Disallow admin: Yes - - - - - - - - - 1 -" } else { Write-Output " Disallow admin: No - - - - - - - - - 0 -" } # In theory we could read these values from the profile cache registry key, but it's so bungled # up in the registry export that it doesn't import without some serious massaging for embedded # quotes. So this is easier. if ($script:useFile) { $jsonFile = "$($env:TEMP)\ESPStatus.tmp\AutopilotDDSZTDFile.json" } else { $jsonFile = "$($env:WINDIR)\ServiceState\wmansvc\AutopilotDDSZTDFile.json" } if (Test-Path $jsonFile) { $json = Get-Content $jsonFile | ConvertFrom-Json $date = [datetime]$json.PolicyDownloadDate RecordStatus -date $date -detail "Autopilot profile" -status "Profile downloaded" if ($json.CloudAssignedDomainJoinMethod -eq 1) { Write-Output "Scenario: Hybrid Azure AD Join" if (Test-Path "$omadmPath\SyncML\ODJApplied") { Write-Output "ODJ applied: Yes" } else { Write-Output "ODJ applied: No" } if ($json.HybridJoinSkipDCConnectivityCheck -eq 1) { Write-Output "Skip connectivity check: Yes" } else { Write-Output "Skip connectivity check: No" } } else { Write-Output "Scenario: Azure AD Join" } } else { Write-Output "Scenario: Not available (JSON not found)" } # Get ESP properties Get-ChildItem $enrollmentsPath | ? { Test-Path "$($_.PSPath)\FirstSync" } | % { $properties = Get-ItemProperty "$($_.PSPath)\FirstSync" Write-Output "Enrollment status page:" Write-Output " Device ESP enabled: $($properties.SkipDeviceStatusPage -eq 0)" Write-Output " User ESP enabled: $($properties.SkipUserStatusPage -eq 0)" Write-Output " ESP timeout: $($properties.SyncFailureTimeout)" if ($properties.BlockInStatusPage -eq 0) { Write-Output " ESP blocking: No" } else { Write-Output " ESP blocking: Yes" if ($properties.BlockInStatusPage -band 1) { Write-Output " ESP allow reset: Yes" } if ($properties.BlockInStatusPage -band 2) { Write-Output " ESP allow try again: Yes" } if ($properties.BlockInStatusPage -band 4) { Write-Output " ESP continue anyway: Yes" } } } # Get Delivery Optimization statistics (when available) if (-not $script:useFile) { $stats = Get-DeliveryOptimizationPerfSnapThisMonth if ($stats.DownloadHttpBytes -ne 0) { $peerPct = [math]::Round( ($stats.DownloadLanBytes / $stats.DownloadHttpBytes) * 100 ) $ccPct = [math]::Round( ($stats.DownloadCacheHostBytes / $stats.DownloadHttpBytes) * 100 ) } else { $peerPct = 0 $ccPct = 0 } Write-Output "Delivery Optimization statistics:" Write-Output " Total bytes downloaded: $($stats.DownloadHttpBytes)" Write-Output " From peers: $($peerPct)% ($($stats.DownloadLanBytes))" Write-Output " From Connected Cache: $($ccPct)% ($($stats.DownloadCacheHostBytes))" } # If the ADK is installed, get some key hardware hash info $adkPath = Get-ItemPropertyValue "HKLM:\Software\Microsoft\Windows Kits\Installed Roots" -Name KitsRoot10 -ErrorAction SilentlyContinue $oa3Tool = "$adkPath\Assessment and Deployment Kit\Deployment Tools\$($env:PROCESSOR_ARCHITECTURE)\Licensing\OA30\oa3tool.exe" if ($hash -and (Test-Path $oa3Tool)) { $commandLineArgs = "/decodehwhash:$hash" $output = & "$oa3Tool" $commandLineArgs [xml] $hashXML = $output | Select -skip 8 -First ($output.Count - 12) Write-Output "Hardware information:" Write-Output " Operating system build: " $hashXML.SelectSingleNode("//p[@n='OsBuild']").v Write-Output " Manufacturer: " $hashXML.SelectSingleNode("//p[@n='SmbiosSystemManufacturer']").v Write-Output " Model: " $hashXML.SelectSingleNode("//p[@n='SmbiosSystemProductName']").v Write-Output " Serial number: " $hashXML.SelectSingleNode("//p[@n='SmbiosSystemSerialNumber']").v Write-Output " TPM version: " $hashXML.SelectSingleNode("//p[@n='TPMVersion']").v } # Process event log info ProcessEvents # Display the list of policies if ($ShowPolicies) { Write-Output " " Write-Output "POLICIES PROCESSED" ProcessNodeCache | Format-Table -Wrap } # Make sure the tracking path exists if (Test-Path $path) { # Process device ESP sessions Write-Output " " Write-Output "DEVICE ESP:" Write-Output " " if (Test-Path "$path\ExpectedPolicies") { [array]$items = Get-ChildItem "$path\ExpectedPolicies" AddDisplay ([ref]$items) $items | ProcessPolicies } if (Test-Path "$path\ExpectedMSIAppPackages") { [array]$items = Get-ChildItem "$path\ExpectedMSIAppPackages" AddDisplay ([ref]$items) $items | ProcessApps -currentUser "S-0-0-00-0000000000-0000000000-000000000-000" } if (Test-Path "$path\ExpectedModernAppPackages") { [array]$items = Get-ChildItem "$path\ExpectedModernAppPackages" AddDisplay ([ref]$items) $items | ProcessModernApps -currentUser "S-0-0-00-0000000000-0000000000-000000000-000" } if (Test-Path "$path\Sidecar") { [array]$items = Get-ChildItem "$path\Sidecar" | ? { $_.Property -match "./Device" } AddDisplay ([ref]$items) $items | ProcessSidecar -currentUser "00000000-0000-0000-0000-000000000000" } if (Test-Path "$path\ExpectedSCEPCerts") { [array]$items = Get-ChildItem "$path\ExpectedSCEPCerts" AddDisplay ([ref]$items) $items | ProcessCerts } # Process user ESP sessions Get-ChildItem "$path" | ? { $_.PSChildName.StartsWith("S-") } | % { $userPath = $_.PSPath $userSid = $_.PSChildName Write-Output " " Write-Output "USER ESP for $($userSid):" Write-Output " " if (Test-Path "$userPath\ExpectedPolicies") { [array]$items = Get-ChildItem "$userPath\ExpectedPolicies" AddDisplay ([ref]$items) $items | ProcessPolicies } if (Test-Path "$userPath\ExpectedMSIAppPackages") { [array]$items = Get-ChildItem "$userPath\ExpectedMSIAppPackages" AddDisplay ([ref]$items) $items | ProcessApps -currentUser $userSid } if (Test-Path "$userPath\ExpectedModernAppPackages") { [array]$items = Get-ChildItem "$userPath\ExpectedModernAppPackages" AddDisplay ([ref]$items) $items | ProcessModernApps -currentUser $userSid } if (Test-Path "$userPath\Sidecar") { [array]$items = Get-ChildItem "$path\Sidecar" | ? { $_.Property -match "./User" } AddDisplay ([ref]$items) $items | ProcessSidecar -currentUser $userSid } if (Test-Path "$userPath\ExpectedSCEPCerts") { [array]$items = Get-ChildItem "$userPath\ExpectedSCEPCerts" AddDisplay ([ref]$items) $items | ProcessCerts } } } else { Write-Output "ESP diagnostics info does not (yet) exist." } # Display timeline Write-Output "" Write-Output "OBSERVED TIMELINE:" Write-Output "" $observedTimeline | Sort-Object -Property Date | Format-Table @{ Label = "Date" Expression = { $_.Date.ToString("u") } }, @{ Label = "Status" Expression = { switch ($_.Color) { 'Red' { $color = "91"; break } 'Yellow' { $color = '93'; break } 'Green' { $color = "92"; break } default { $color = "0" } } $e = [char]27 "$em$($_.Status)$e[0m" } }, Detail Write-Output "" } End { # Remove the registry info if it exists if (Test-Path "HKCU:\ESPStatus.tmp") { Remove-Item -Path "HKCU:\ESPStatus.tmp" -Recurse -Force } } '@ $outputPath = "$env:temp\CollectedData\Intune\Commands\Autopilot" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} $logfile = Join-Path $outputPath Get-AutopilotDiagnostics.txt $important = Join-Path $outputPath "!_Important.txt" if (Test-Path HKLM:\software\microsoft\provisioning\Diagnostics\Autopilot) { # workaround for process() script block limitation $apscript | out-file apinfo.ps1 . .\apinfo.ps1 | Out-File $logfile -Force del .\apinfo.ps1 echo "Please see internal KB 4570137 for more information regarding decoding Autopilot ETL logs." | Out-File $important -Force } else { Write-Output "HKLM:\software\microsoft\provisioning\Diagnostics\Autopilot not found. Skipping AutoPilot tests" } echo "Please see internal KB 4570137 for more information regarding decoding Autopilot ETL logs." | Out-File $important -Force # https://gallery.technet.microsoft.com/scriptcenter/Show-Windows-Update-c7ee69bd $objSession = new-object -com "Microsoft.Update.Session" $objSearcher = $objSession.CreateupdateSearcher() $intCount = $objSearcher.GetTotalHistoryCount() $colHistory = $objSearcher.QueryHistory(0, $intCount) foreach ($objHistory in $colHistory) { $title = ($objHistory.Date).ToString("yyyy/MM/dd hh:mm UTC") + " " + $objHistory.Title + "`t" if ($objHistory.HResult -eq 0) { Write-Output "$title - Successfully installed" } elseif ($objHistory.HResult -eq -2145116140) { Write-Output "$title - Pending Reboot" } else { # Report errors for the past month if (($objHistory.Date).AddMonths(1) -gt (Get-Date)) { $hexErr = "0x$($objHistory.HResult.ToString("X8"))" Write-Output "$title - Failed to install. `tError: $hexErr" } } } Write-Output "=======================================================================================`r`n`r`n" Write-Output "============= Verbose Output ===============`r`n`r`n" Write-Output "=======================================================================================`r`n`r`n" # Verbose output foreach ($objHistory in $colHistory) { $o = New-Object PSObject $o | Add-Member -MemberType NoteProperty -Name Update $(Select-Object * -InputObject $objHistory -ExcludeProperty UninstallationSteps,Categories,UpdateIdentity | Out-String ) $o | Add-Member -MemberType NoteProperty -Name UpdateID -Value $($objHistory.UpdateIdentity | Out-String ) $o | Add-Member -MemberType NoteProperty -Name Categories -Value $($objHistory.Categories | Out-String ) "================================================================" "$($objHistory.Title | Out-String)" "================================================================" $o | fl * } $ErrorActionPreference = "Stop" $Error.Clear() $line = "`*" * 120 function WaitOnSchTask { param([string]$taskName, [string]$OutFile = "$env:SystemRoot\temp\winupdate_debug.txt" ) [int]$timer = 0 try { "waiting on $taskName to complete" | Out-file $OutFile -Force -Append -Encoding ascii $status = (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue).State while ( ($status -ne "Ready") -and ($timer -le 150) ) { "status: $status. waiting on $taskName to complete" | Out-file $OutFile -Force -Append -Encoding ascii $status = (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue).State $timer += 5 Start-Sleep 5 } } catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException] { "Something went wrong. Scheduled task not found" | Out-file $OutFile -Force -Append -Encoding ascii } catch { "Something went wrong." | Out-file $OutFile -Force -Append -Encoding ascii $Error[0] | Out-file $OutFile -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $OutFile -Force -Append -Encoding ascii } } function Write-PS1File { # write temp file $tempFilePath = Join-Path "$env:SystemRoot\temp" "winupdatetemp.ps1" $tempfileContents = @' param( [string]$OutFile = "$env:SystemRoot\temp\winupdate_debug.txt") [string]$computer = $env:COMPUTERNAME [string]$namespace = "ROOT\CIMV2\mdm\dmmap" [string[]]$classnames = @("MDM_DeviceUpdateCenter_Enrollment01", "MDM_Policy_Config01_Update02", "MDM_Policy_Result01_Update02", "MDM_Update", "MDM_Update_ApprovedUpdates01_01", "MDM_Update_FailedUpdates01_01" ,"MDM_Update_InstallableUpdates01_01", "MDM_Update_PendingRebootUpdates01_01", "MDM_Update_Rollback01","MDM_Win32CompatibilityAppraiser_UtcConfigurationDiagnosis02","MDM_Win32CompatibilityAppraiser_UniversalTelemetryClient01","MDM_DeviceStatus_Antivirus01", "MDM_DeviceStatus_Firewall01") $ErrorActionPreference = "Stop" $Error.Clear() $line = "=" * 120 # write header $line | Out-file $OutFile -Force -Encoding ascii "Policy results from ROOT\CIMV2\mdm\dmmap" | Out-file $OutFile -Force -Append -Encoding ascii "Start time: $([datetime]::Now) `t`t(Offset: $([System.TimeZoneInfo]::Local.BaseUtcOffset.Hours) hours)" | Out-file $OutFile -Force -Append -Encoding ascii "Start time UTC: $([datetime]::UtcNow)`r`n" | Out-file $OutFile -Force -Append -Encoding ascii $line | Out-file $OutFile -Force -Append -Encoding ascii foreach ($classname in $classnames) { try { $line | Out-file $OutFile -Force -Append -Encoding ascii "CLASS : $classname " | Out-file $OutFile -Force -Append -Encoding ascii $line | Out-file $OutFile -Force -Append -Encoding ascii Get-WmiObject -Class $classname -Namespace $namespace ` |Select-Object * -ExcludeProperty PSComputerName, Scope, Path, Options, ClassPath, Properties, SystemProperties, Qualifiers, Site, Container ` | Format-List -Property [a-z]* | Out-file $OutFile -Force -Append -Encoding ascii # convert time for telem if ($classname -eq 'MDM_Win32CompatibilityAppraiser_UniversalTelemetryClient01') { $timeStamps = ([xml](Get-WmiObject -Class $classname -Namespace $namespace).UtcConnectionReport).ConnectionReport.ConnectionSummary $startTime = [datetime]::FromFileTimeUtc($timeStamps.ConnectionSummaryStartingTimestamp) $endTime = [datetime]::FromFileTimeUtc($timeStamps.ConnectionSummaryEndingTimestamp) $lastSuccess = [datetime]::FromFileTimeUtc($timeStamps.TimestampOfLastSuccessfulUpload) "dateConnectionSummaryStartingTimestamp = $startTime" | Out-file $OutFile -Force -Append -Encoding ascii "ConnectionSummaryEndingTimestamp = $endtime" | Out-file $OutFile -Force -Append -Encoding ascii "TimestampOfLastSuccessfulUpload = $lastSuccess" | Out-file $OutFile -Force -Append -Encoding ascii "`r`n$line" | Out-file $OutFile -Force -Append -Encoding ascii } } catch [System.Management.ManagementException] { # ignore - class not found because it is not populated } catch { $Error[0] $Error[0].Exception.GetType().fullname | fl * | Out-file $OutFile -Force -Append -Encoding ascii } } '@ $tempfileContents | Out-File $tempFilePath -Force } # Launch process as system via scheduled task $TimeToRun = (Get-Date).AddSeconds(5) $timeStamp = (Get-Date).ToString("ddMMyyyyhhmmss") $taskName = "ODC Windows Update Debug - $timeStamp" $folderPath = "$env:temp\CollectedData\Intune\Commands\Windows Update" $filepath = Join-Path $folderPath "$($env:COMPUTERNAME)_Windows_Updates_Debug.txt" $tempFilePath = Join-Path "$env:SystemRoot\temp" "winupdatetemp.ps1" $Trigger = New-ScheduledTaskTrigger -At $TimeToRun -Once $User = "NT AUTHORITY\SYSTEM" $desc = "Scheduled task created by Intune One Data Collector." # Create folder if it does not exist for uploader if ( -not ( Test-Path $folderPath -ErrorAction SilentlyContinue) ) { $nil = mkdir $folderPath -Force } # start log $line | Out-File $filepath -Force -Encoding ascii "Starting update query using Scheduled Task as SYSTEM`r`n" | Out-File $filepath -Append -Force -Encoding ascii $line | Out-File $filepath -Force -Append -Encoding ascii # create PS1 for system to run Write-PS1File # Register task and execute try { $Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File `"$tempFilePath`" -Nologo -OutFile `"$filepath`"" $null = Register-ScheduledTask -TaskName $taskName -Trigger $Trigger -User $User -Action $Action -RunLevel Highest -Description $desc –Force $null = Start-ScheduledTask -TaskName $taskName } catch { "Something went wrong." | Out-file $filepath -Force -Append -Encoding ascii $Error[0] | Out-file $filepath -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $filepath -Force -Append -Encoding ascii } # wait for task to complete sleep 10 WaitOnSchTask -taskName $taskName -OutFile $filepath #cleanup try { foreach ($taskName in $(( Get-ScheduledTask -TaskPath "\" | Where-Object {$_.TaskName -match "ODC Windows Update Debug" }).TaskName) ){ "Unregistering $taskName" | Out-file $filepath -Force -Append -Encoding ascii $nil = Unregister-ScheduledTask -TaskName "$taskName" -TaskPath "\" -PassThru -Confirm:$false } Get-ScheduledTask -TaskPath "\" | Where-Object {$_.TaskName -match "ODC Windows Update Debug" } | Out-file $filepath -Force -Append -Encoding ascii } catch { "***Warning: Unable to remove scheduled task $taskName. Please delete this entry from Task Scheduler" | Out-file $filepath -Force -Append -Encoding ascii $Error[0] | Out-file $filepath -Force -Append -Encoding ascii $Error[0].Exception.GetType().fullname | Out-file $filepath -Force -Append -Encoding ascii } $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} copy $env:ProgramData\microsoft\diagnosticlogcsp\collectors\*.etl . foreach ($file in $(dir *.etl)) { netsh trace convert $file } move *.txt $outputPath -Force del .\*DiagnosticLogCSP_Collector*.etl reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Appraiser\GWX" Get-DeliveryOptimizationLog $ServiceManager = New-Object -ComObject "Microsoft.Update.ServiceManager" echo "`$ServiceManager = New-Object -ComObject Microsoft.Update.ServiceManager;`$ServiceManager.services| fl *" echo $("*" * 60) $ServiceManager.services| fl * RunCommand "Get-AppxPackage -AllUser -Name Microsoft.Companyportal" RunCommand "Get-AppxPackage -AllUsers -Name Microsoft.DesktopAppInstaller" RunCommand "(Get-AppxPackage -AllUsers -Name Microsoft.DesktopAppInstaller).PackageUserInformation | select -Property * -ExpandProperty usersecurityid| fl" RunCommand "Get-AppxPackage -AllUsers -Name Microsoft.Companyportal | Get-AppxPackageManifest | fl *" RunCommand "Get-AppxPackage -AllUsers | Select-Object Name | Sort-Object -Property Name" RunCommand "Get-AppxPackage -AllUsers | Select-Object -ExpandProperty Dependencies | Sort-Object -Property Name" $outputPath = "$env:temp\CollectedData\Intune\Commands\General" $x = if (-not (Test-Path $outputPath)) { mkdir $outputPath -Force} RunCommand "Get-AppxProvisionedPackage -Online -LogPath $outputPath\AppxProvisionedPackage.log | fl *" # Look for possible MSIlogs in windows\temp $ErrorActionPreference = "Stop" $copyPath = "$env:temp\CollectedData\Intune\Files\MSI Logs" if (-not(test-path $copyPath) ) { $x = mkdir $copyPath -Force } $possibleMSILogs = @() $possibleMSILogs = Get-ChildItem -Path "$env:SystemRoot\temp\*.log" -Recurse -ErrorAction SilentlyContinue $possibleMSILogs += Get-ChildItem -Path "$env:temp\*.log" -ErrorAction SilentlyContinue foreach ($possibleMSILog in $possibleMSILogs) { if (Get-ChildItem $possibleMSILog | Select-String -Pattern "\.msi" -ErrorAction SilentlyContinue) { try { Copy-Item "$possibleMSILog" $copyPath -Force } catch { "Error copying $possibleMSILog`:" | Write-Log -Level Error $Error | Write-Log -Level Error try { $tempname = "$(Get-Random -Minimum 10000 -Maximum 20000)" + "_" + "$possibleMSILog" Copy-Item $possibleMSILog "$copyPath\$tempname" -Force } catch { "Unable to copy renamed file $possibleMSILog. Skipping." | Write-Log -Level Error continue } } } } $uhs = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | where {$_.DisplayName -eq "Microsoft Update Health Tools"} if ($null -eq $uhs) { Write-Output "Microsoft Update Health Tools not installed." } else { $uhs # get first 3 chars of major.minor version [double]$PSMajorMinorVersion = $PSVersionTable.PSVersion.ToString().Substring(0,3) if ( $PSMajorMinorVersion -ge 5.1) { Get-Package -AllVersions -name "Microsoft Update Health Tools" Get-Package -AllVersions -name "Microsoft Update Health Tools" | fl * } else { Write-Output "PowerShell version $PSMajorMinorVersion is lower than 5.1. Skipping package check" } } # get first 3 chars of major.minor version [double]$PSMajorMinorVersion = $PSVersionTable.PSVersion.ToString().Substring(0,3) if ( $PSMajorMinorVersion -ge 5.1) { # TODO - wrap in custom object to sort RunCommand 'Get-Package -AllVersions | Where-Object {$_.Name -notmatch "Security Intelligence Update for Microsoft Defender Antivirus" } | ft -AutoSize' RunCommand 'Get-Package -AllVersions | fl *' } else { Write-Output "PowerShell version $PSMajorMinorVersion is lower than 5.1. Skipping package check" } $WU_Policy_Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate" $WU_Policy_AU_Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" $WU_PolicyManager_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\current\device\Update" $WU_Windows_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" function New-RuleCheckResult { [CmdletBinding()] param( [string] [Parameter(Mandatory=$true)] $ruleId, [string]$ruleDescription, [string] [ValidateSet("Passed","Warning", "Failed", "Information")] $result, [string]$ResultMessage ) $RuleResult = [PSCustomObject] [Ordered] @{ 'RuleId'= $ruleId 'RuleDescription'= $ruleDescription 'CheckResult'= $result 'CheckResultMessage'= $ResultMessage } return $RuleResult } 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($null -eq $val.$name) { 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 ($null -eq $val.$name) { return $null } return $val.$name } function Test-AlwaysAutoRebootEnabled { $ruleId = "AlwaysAutoRebootCheck" $ruleDescription = "Automatic reboot should not be enable as it forces a reboot irrespective of update configuration." $result = $null [string]$ResultMessage = $null $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. This can cause unexpected reboots when installing updates." } else { $result = "Passed" $ResultMessage = "Windows Update reboot registry keys are not set to automatically reboot." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-deploymentschedulerConnectivity { $ruleId = "TestdeploymentschedulerConnectivity" $ruleDescription = "Validates that the device can connect to deploymentscheduler.microsoft.com on TCP port 443." $result = $null [string]$ResultMessage = $null $url = "deploymentscheduler.microsoft.com" $ErrorActionPreference = "Stop" $port = 443 try { $error.Clear() $connectionTest = $false $connection = New-Object System.Net.Sockets.TCPClient $connection.ReceiveTimeout = [int32]500 $connection.SendTimeout = [int32]500 $x = ($connection.ConnectAsync( $url, $port)).Wait(5000) $connectionTest = $connection.Connected $result = "Passed" } catch { $ResultMessage = "Error connecting to $uniqueURL`: $error" $result = "Failed" } finally { $ResultMessage = "{0, -72} : {1,6}" -f "Able to connect to port $port on $url", $connectionTest } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-DevicelistenerprodConnectivity { $ruleId = "TestDevicelistenerprodConnectivity" $ruleDescription = "Validates that the device can connect to Devicelistenerprod.microsoft.com on TCP port 443." $result = $null [string]$ResultMessage = $null $url = "Devicelistenerprod.microsoft.com" $ErrorActionPreference = "Stop" $port = 443 try { $error.Clear() $connectionTest = $false $connection = New-Object System.Net.Sockets.TCPClient $connection.ReceiveTimeout = [int32]500 $connection.SendTimeout = [int32]500 $x = ($connection.ConnectAsync( $url, $port)).Wait(5000) $connectionTest = $connection.Connected $result = "Passed" } catch { $ResultMessage = "Error connecting to $uniqueURL`: $error" $result = "Failed" } finally { $ResultMessage = "{0, -72} : {1,6}" -f "Able to connect to port $port on $url", $connectionTest } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-DoWUPoliciesExist { $WU_Policy_Count = 0 if (Test-Path Registry::$WU_Policy_Key) { $WU_Policy_Count = $(Get-Item Registry::$WU_Policy_Key).ValueCount } $ruleId = "DoWUPoliciesExist" $ruleDescription = "Warns if Windows Update policies exist (from GPO or manual Registry edits)." $result = $null [string]$ResultMessage = $null if ( $WU_Policy_Count -ge 1) { $result = "Warning" $ResultMessage = ("One or more entries exist in $WU_Policy_Key. Verify that Windows Update GPOs are not present. `r`nIf these values were entered manually, please check that they do not conflict with Intune policy.") -replace "Registry::", "" } else { $result = "Passed" $ResultMessage = "No settings found in $WU_Policy_Key." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-IsWSUSConfigured { # TODO - Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate - check this as well? Probably rare $WU_Policy_WSUS_Present = $false $WU_PolicyManager_WSUS_Present = $false [string]$ResultMessage = $null $WSUS_Policy = $(Get-itemproperty Registry::$WU_Policy_Key -ErrorAction SilentlyContinue).WUServer $WSUS_PolicyManager = $(Get-itemproperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).WUServer if ( ($WSUS_Policy -notin "", "CorpWSUS") -and ($null -ne $WSUS_Policy) ) { $WU_Policy_WSUS_Present = $true } if ( ($WSUS_PolicyManager -notin "", "CorpWSUS") -and ($null -ne $WSUS_PolicyManager) ) { $WU_PolicyManager_WSUS_Present = $true } $ruleId = "WSUS_Enabled" $ruleDescription = "Warns if WSUS server is configured." $result = $null [string]$ResultMessage = $null if ( $WU_Policy_WSUS_Present -or $WU_PolicyManager_WSUS_Present) { $result = "Warning" $ResultMessage = ("WSUS server is configured. This scenario is possible to use in Intune, but this is uncommon. This often indicates that a device was migrated from ConfigMgr. `r`nVerify that Windows Update WSUS GPOs are not present. If WSUS is not being used, remove both WUServer and WUReportingServer values. `r`nWUServer values detected in keys:`r`n $WU_Policy_Key = $WU_Policy_WSUS_Present $WU_PolicyManager_Key = $WU_PolicyManager_WSUS_Present" ) -replace "Registry::", "" } else { $result = "Passed" $ResultMessage = ("No settings found in $WU_Policy_Key or $WU_PolicyManager_Key") -replace "Registry::", "" } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-WUAServStartupType { $ruleId = "TestWindowsUpdateAgent_ServiceStartupType" $ruleDescription = "Verify that Windows Update service (wuaserv) startup type is 'manual'." $result = $null [string]$ResultMessage = $null $WUAStartup = (Get-Service -Name wuauserv).StartType if ( $WUAStartup -eq "Manual" ) { $result = "Passed" $ResultMessage = "Windows Update service is set to default startup type of 'Manual'." } elseif ($WUAStartup -eq "Disabled") { $result = "Failed" $ResultMessage = "Windows Update service is disabled." } else { $result = "Warning" $ResultMessage = "Windows Update service startup type has been changed from the default value of 'Manual'." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-HealthServiceStartupType { $ruleId = "TestHealthAgent_ServiceStartupType" $ruleDescription = "Verify that Microsoft Update Health Service (uhssvc) startup type is 'Automatic'." $result = $null [string]$ResultMessage = $null if (Get-Service -Name uhssvc -ErrorAction SilentlyContinue) { $UHS_Startup = (Get-Service -Name uhssvc).StartType if ( $UHS_Startup -eq "Automatic" ) { $result = "Passed" $ResultMessage = "Microsoft Update Health Service (uhssvc) is set to default startup type of 'Automatic'." } elseif ($UHS_Startup -eq "Disabled") { $result = "Failed" $ResultMessage = "Microsoft Update Health Service (uhssvc) is disabled." } else { $result = "Warning" $ResultMessage = "Microsoft Update Health Service (uhssvc) startup type has been changed from the default value of 'Automatic'." } } else { $result = "Failed" $ResultMessage = "Update Service Health Service (uhssvc) is not installed. This device will not be able to use Feature Update policies or Expedited Update policies.`r`n" $ResultMessage += "See https://support.microsoft.com/en-us/topic/kb4023057-update-for-windows-update-service-components-fccad0ca-dc10-2e46-9ed1-7e392450fb3a for more information." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-IsHealthServicesRunning { $ruleId = "Test_Is_UHS_Running" $ruleDescription = "Verify that Microsoft Update Health Service (uhssvc) is running." $result = $null [string]$ResultMessage = $null if (Get-Service -Name uhssvc -ErrorAction SilentlyContinue) { $UHS_Status = (Get-Service -Name uhssvc).Status if ( $UHS_Status -eq "Running" ) { $result = "Passed" $ResultMessage = "Microsoft Update Health Service (uhssvc) is running." } else { $result = "Failed" $ResultMessage = "Microsoft Update Health Service (uhssvc) is not running. Current status is $UHS_Status. Check the service configuration to verify that its startup type is set to Automatic." } } else { $result = "Failed" $ResultMessage = "Update Service Health Service (uhssvc) is not installed. This device will not be able to use Feature Update policies or Expedited Update policies.`r`n" $ResultMessage += "See https://support.microsoft.com/en-us/topic/kb4023057-update-for-windows-update-service-components-fccad0ca-dc10-2e46-9ed1-7e392450fb3a for more information." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-IsTelemetryEnabled { $TelemetryLevel = @{ 0 = "Disabled" 1 = "Basic" 2 = "Enhanced" 3 = "Full" } $ruleId = "TelemetryEnabled" $ruleDescription = "Telemetry must be enabled for Feature Update and Expedited update policies to work." $result = "Failed" [string]$ResultMessage = $null # 3 different places to check. If Policymanager is populated, actual value is referenced in PolicyManager\providers $TelemetryKey_Policy = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\DataCollection" $TelemetryKey_Windows = "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\DataCollection" $TelemetryKey_PolicyManager = "HKEY_LOCAL_MACHINE\Software\Microsoft\PolicyManager\current\device\System" $TelemetrySetting_Policy = (Get-ItemProperty Registry::$TelemetryKey_Policy -ErrorAction SilentlyContinue).AllowTelemetry $TelemetrySetting_Windows = (Get-ItemProperty Registry::$TelemetryKey_Windows -ErrorAction SilentlyContinue).AllowTelemetry $TelemetrySetting_PolicyManager = Get-ItemProperty Registry::$TelemetryKey_PolicyManager -ErrorAction SilentlyContinue # default message if all 3 keys are empty $ResultMessage = "No telemetry policy found in $TelemetrySetting_Policy, $TelemetrySetting_Windows, $TelemetrySetting_PolicyManager. Expedited updates and Feature update policies will not apply to this device." if ( $TelemetrySetting_Policy -ge 1) { $result = "Passed" $ResultMessage = "AllowTelemetry set to $TelemetrySetting_Policy ($($TelemetryLevel[$TelemetrySetting_Policy]))in $TelemetryKey_Policy.`r`n" } else { $ResultMessage = "AllowTelemetry not set in $TelemetryKey_Policy.`r`n" } if ( $TelemetrySetting_Windows -ge 1) { $result = "Passed" $ResultMessage += "AllowTelemetry set to $TelemetrySetting_Windows ($($TelemetryLevel[$TelemetrySetting_Windows])) in $TelemetryKey_Windows.`r`n" } else { $ResultMessage += "AllowTelemetry not set in $TelemetryKey_Windows.`r`n" } # Construct path for actual value in the \PolicyManager\providers key based on WinningProvider value $ProviderSet = "" $ProviderSet = $TelemetrySetting_PolicyManager.AllowTelemetry_WinningProvider $TelemetryValue_PolicyManager = "" $TelemetryPath = "" if ($ProviderSet){ $ProviderSet_Key = "HKEY_LOCAL_MACHINE\Software\Microsoft\PolicyManager\providers\$ProviderSet\default\Device\System" $TelemetryPath = Get-ItemProperty Registry::$ProviderSet_Key -ErrorAction SilentlyContinue $TelemetryValue_PolicyManager = $TelemetryPath.AllowTelemetry if ( $TelemetryValue_PolicyManager -ge 1) { $result = "Passed" $ResultMessage += "AllowTelemetry set to $TelemetryValue_PolicyManager ($($TelemetryLevel[$TelemetryValue_PolicyManager])) in $ProviderSet_Key.`r`n" } else { $ResultMessage += "AllowTelemetry not set in $ProviderSet_Key.`r`n" } } else { $ResultMessage += "AllowTelemetry not set in $TelemetryKey_PolicyManager" } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-IsDeviceHealthMonitoringEnabled { # https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-devicehealthmonitoring # https://techcommunity.microsoft.com/t5/microsoft-endpoint-manager-ama/troubleshooting-blank-graphs/m-p/1833070 # CSP-only rule. No GPO equivalent $AllowDHM_Key_PolicyManager = "HKEY_LOCAL_MACHINE\software\microsoft\policymanager\current\device\DeviceHealthMonitoring" $AllowDHM_Value_PolicyManager = (Get-ItemProperty Registry::$AllowDHM_Key_PolicyManager -ErrorAction SilentlyContinue).AllowDeviceHealthMonitoring $ruleId = "Device_Health_Monitoring_Enabled" $ruleDescription = "Checks that device health monitoring is enabled." $result = $null [string]$ResultMessage = $null if ( $AllowDHM_Value_PolicyManager -eq 1) { $result = "Passed" $ResultMessage = "AllowDeviceHealthMonitoring is set to 1 (enabled) in $AllowDHM_Key_PolicyManager. This is required if Expedited Updates or Feature Update policies are configured." } else { $result = "Failed" $ResultMessage = "AllowDeviceHealthMonitoring is not enabled in $AllowDHM_Key_PolicyManager. This is required if Expedited Updates or Feature Update policies are configured." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-DHMScope { $DHM_Key_PolicyManager = "HKEY_LOCAL_MACHINE\software\microsoft\policymanager\current\device\DeviceHealthMonitoring" $AllowDHM_Value_PolicyManager = (Get-ItemProperty Registry::$DHM_Key_PolicyManager -ErrorAction SilentlyContinue).ConfigDeviceHealthMonitoringScope $helpURL = "https://docs.microsoft.com/en-us/mem/intune/protect/windows-10-expedite-updates#monitoring-and-reporting" $helpURL2 = "https://docs.microsoft.com/en-us/mem/intune/configuration/windows-health-monitoring" $ruleId = "Device_Health_Monitoring_Scope" $ruleDescription = "Checks that device health monitoring scope includes Windows Update." $result = $null [string]$ResultMessage = $null if ( $AllowDHM_Value_PolicyManager -match "WindowsUpdates") { $result = "Passed" $ResultMessage = "ConfigDeviceHealthMonitoringScope in $AllowDHM_Key_PolicyManager is set to $AllowDHM_Value_PolicyManager. This is required if Expedited Updates or Feature Update policies are configured. See $helpURL for more information." } else { $result = "Failed" $ResultMessage = "`"WindowsUpdates`" is not listed in the scope of $DHM_Key_PolicyManager\ConfigDeviceHealthMonitoringScope. This is required if Expedited Updates or Feature Update policies are configured. See $helpURL and $helpURL2 for more information." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-DHMUploadDestination{ # https://techcommunity.microsoft.com/t5/microsoft-endpoint-manager-ama/troubleshooting-blank-graphs/m-p/1833070 $DHM_Key_PolicyManager = "HKEY_LOCAL_MACHINE\software\microsoft\policymanager\current\device\DeviceHealthMonitoring" $AllowDHM_Value_PolicyManager = (Get-ItemProperty Registry::$DHM_Key_PolicyManager -ErrorAction SilentlyContinue).ConfigDeviceHealthMonitoringUploadDestination $ruleId = "Device_Health_Monitoring_Upload_Location" $ruleDescription = "Checks that device health monitoring upload location. This is a list of known service endpoints based on location." $result = $null [string]$ResultMessage = $null if ( $AllowDHM_Value_PolicyManager -in "DHM_NORTHAMERICA", "DHM_EUROPE", "DHM_SOUTHEASTASIA", "DHM_AUSTRALIA") { $result = "Passed" $ResultMessage = "ConfigDeviceHealthMonitoringScope in $DHM_Key_PolicyManager is set to $AllowDHM_Value_PolicyManager. This is required if Expedited Updates or Feature Update policies are configured." } else { $result = "Failed" $ResultMessage = "Unexpected or missing vlaue in $DHM_Key_PolicyManager\ConfigDeviceHealthMonitoringUploadDestination - $AllowDHM_Value_PolicyManager. This is required if Expedited Updates or Feature Update policies are configured. `r`nThis is an unexpected scenario and a Windows Update specialist should be consulted to investigate." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } Function Test-FeatureUpdateHoldExits { $ruleId = "SafeGuard_Hold_Exists" $ruleDescription = "Checks to see if feature update safeguard holds are present." $result = $null [string]$ResultMessage = $null $GWX_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Appraiser\GWX" $SdbEntries = (Get-ItemProperty Registry::$GWX_Key -ErrorAction SilentlyContinue).SdbEntries if ( ($SdbEntries -eq "") -or ($null -eq $SdbEntries) ) { $result = "Passed" $ResultMessage = "No known Feature Update holds found in $GWX_Key. This does not mean that no safeguard holds exist, but it is an indication that no known issues have been identified." } else { $result = "Failed" $ResultMessage = "SDB entry `"$SdbEntries`" found in $GWX_Key. This indicates that a known issue is preventing this device from applying a Feature Update. `r`nA Windows Update specialist should be consulted to investigate." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } $WU_Policy_Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate" $WU_Policy_AU_Key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" $WU_PolicyManager_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\current\device\Update" $WU_Windows_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" $PolicyRegKeyLocations = @( $WU_Policy_Key, $WU_PolicyManager_Key, $WU_Windows_Key) $AU_PolicyRegKeyLocations = @( $WU_Policy_AU_Key, $WU_PolicyManager_Key) # Enum of all 4 policy driven sources $PolicyDrivenSources = @( "SetPolicyDrivenUpdateSourceForDriverUpdates", "SetPolicyDrivenUpdateSourceForFeatureUpdates", "SetPolicyDrivenUpdateSourceForOtherUpdates", "SetPolicyDrivenUpdateSourceForQualityUpdates" ) # For reporting $PolicyDrivenSourceTable = @{ "SetPolicyDrivenUpdateSourceForDriverUpdates" = "<not present>" "SetPolicyDrivenUpdateSourceForFeatureUpdates" = "<not present>" "SetPolicyDrivenUpdateSourceForOtherUpdates" = "<not present>" "SetPolicyDrivenUpdateSourceForQualityUpdates" = "<not present>" } # Win10 only. Win11 uses AU\UseUpdateClassPolicySource Function Test-IsDualScanEnabled { # check to see that dualscan is not disabled # # If DisableDualScan is set to 1, the device will not check Windows Update and only work with WSUS. This is almost always in error after migrating from a WSUS environment. $ruleId = "Is_DualScan_Enabled" $ruleDescription = "If DisableDualScan is set to '1', the device will not search Windows Update." $result = "Failed" $SetDualScanIsConfigured = $false [string]$ResultMessage = $null [bool]$PolicyDrivenSourcesSet = $false [string[]]$PolicyDrivenSourceValues = @() # First check reg values foreach ($PolicyRegKeyLocation in $PolicyRegKeyLocations ) { $DisableDualScanValue = (Get-ItemProperty Registry::$PolicyRegKeyLocation -ErrorAction SilentlyContinue).DisableDualScan if ($DisableDualScanValue -eq 1) { $SetDualScanIsConfigured = $true } $Results += "$PolicyRegKeyLocation\DisableDualScan = $DisableDualScanValue" } foreach ($PolicyDrivenSource in $PolicyDrivenSources) { foreach ($PolicyRegKeyLocation in $PolicyRegKeyLocations) { $PolicyDrivenSourceValue = (Get-ItemProperty Registry::$PolicyRegKeyLocation -ErrorAction SilentlyContinue).$PolicyDrivenSource if ( $null -ne $PolicyDrivenSourceValue ){ if ($PolicyDrivenSourceValue -eq 1) { $PolicyDrivenSourcesSet = $true $PolicyDrivenSourceTable[$PolicyDrivenSource] = "WSUS" } elseif ($PolicyDrivenSourceValue -eq 0) { $PolicyDrivenSourceTable[$PolicyDrivenSource] = "WU" } $PolicyDrivenSourceValues += "$PolicyRegKeyLocation\$PolicyDrivenSource = $PolicyDrivenSourceValue" } } } # Report on status of dualscan and policy driven source values if ( $SetDualScanIsConfigured -and $PolicyDrivenSourcesSet -eq $true) { $result = "Warning" $ResultMessage = "DisableDualScan is set to 1 and at least one SetPolicyDrivenUpdateSource* policy exclusion is configured. This will allow the device to search Windows Update for the policy driven source. Verify that all desired update types are included. $Results Policy driven sources = `r`n`t`t`t`t $PolicyDrivenSourceValues" -replace "(.+? = \d?)", "`$1`r`n`t`t`t`t" } elseif ($SetDualScanIsConfigured -and $PolicyDrivenSourcesSet -ne $true) { $result = "Failed" $ResultMessage = "DisableDualScan is set to 1 and no SetPolicyDrivenUpdateSource* policy exclusion is configured. This will cause the device to not search Windows Update. WSUS will be used if available. $Results Policy driven sources = `r`n`t`t`t`t $PolicyDrivenSourceValues" -replace "(.+? = \d?)", "`$1`r`n`t`t`t`t" } else { $result = "Passed" $ResultMessage = "No DisableDualScan Registry entries found. Device will use Windows Update." } if ($SetDualScanIsConfigured -and $PolicyDrivenSourceValues.count-ne 4) { $ResultMessage += "`r`n`t`t***Warning: only $($PolicyDrivenSourceValues.Count) policy driven entries found. It is recommended to configure all 4 SetPolicyDrivenUpdateSource* sources. Please see https://learn.microsoft.com/en-us/windows/deployment/update/wufb-wsus." } "::::`r`n$PolicyDrivenSourceTable" $ResultMessage += "`r`n`r`nSetPolicyDrivenUpdateSourceFor update sources:`r`n============================" $ResultMessage += ($PolicyDrivenSourceTable | Format-Table -AutoSize -HideTableHeaders | Out-String) -replace "SetPolicyDrivenUpdateSourceFor", "" $ResultMessage += "`r`n" return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } # Check that HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate\AU\UseWUServer = 00000001 function Test-AreSetPolicyDrivenUpdateSourcePoliciesSet { # Windows 11 logic only. Win10 uses dualscan. Check to see that IsUseUpdateClassPolicySourceEnabled is present if any SetPolicyDrivenUpdateSource* policies are present. 0 = use Windows Update, 1 = use WSUS # # If any SetPolicyDrivenUpdateSource* policies are present, HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU\UseUpdateClassPolicySource must be set to 1 # https://learn.microsoft.com/en-us/windows/deployment/update/deployment-service-troubleshoot $ruleId = "Are_SetPolicyDrivenUpdateSourcePolicies_Present" $ruleDescription = "Validate related settings if SetPolicyDrivenUpdateSource* policies are present SetPolicyDrivenUpdateSource* policies are present.`r`nSetPolicyDrivenUpdateSource* policies require WUServer be populated and UseWUServer =1.`r`n If manually configured, UseUpdateClassPolicySource must also be set to 1. UseUpdateClassPolicySource is optional in comanaged environments" $result = "Failed" $UseUpdateClassPolicySourceEnabledIsConfigured = $false # global flag. $true if any SetPolicyDrivenUpdateSource* policy is set to '1' [bool]$PolicyDrivenSourcesSet = $false [string[]]$PolicyDrivenSourceValues = @() [string]$ResultMessage = $null [string]$tabsFormat = "`t`t`t`t`t`t" [string]$lineEndFormat = "`r`n`t`t" [string]$indent = "`t`t`t`t" [int]$PolicyDrivenSourceCount = 0 $ResultMessage += "`r`nSetPolicyDrivenUpdateSource* values :$lineEndFormat" foreach ($PolicyDrivenSource in $PolicyDrivenSources) { foreach ($PolicyRegKeyLocation in $PolicyRegKeyLocations) { $PolicyDrivenSourceValue = (Get-ItemProperty Registry::$PolicyRegKeyLocation -ErrorAction SilentlyContinue).$PolicyDrivenSource if ( $null -ne $PolicyDrivenSourceValue ){ $PolicyDrivenSourceCount += 1 if ($PolicyDrivenSourceValue -eq 1) { $PolicyDrivenSourcesSet = $true $PolicyDrivenSourceTable[$PolicyDrivenSource] = "WSUS" } elseif ($PolicyDrivenSourceValue -eq 0) { $PolicyDrivenSourceTable[$PolicyDrivenSource] = "WU" } $ResultMessage += "$indent $PolicyRegKeyLocation\$PolicyDrivenSource = $PolicyDrivenSourceValue $lineEndFormat" } } } $ResultMessage += "`r`n`r`nSetPolicyDrivenUpdateSourceFor update sources:`r`n`r`n============================" $ResultMessage += ($PolicyDrivenSourceTable | Format-Table -AutoSize -HideTableHeaders | Out-String) -replace "SetPolicyDrivenUpdateSourceFor", "" $ResultMessage += "`r`n" # If SetPolicyDrivenSourceLocation* policies are present, validate that WSUS server, UseWUServer, and UseUpdateClassPolicySource are configured if ($PolicyDrivenSourcesSet) { # we check these two values. WUServer must be present and UseUpdateClassPolicySource must be set to 1 to use PolicyDrivenSourceLocation* settings $UseWUServer = $false $ResultMessage += "One or more SetPolicyDrivenUpdateSource* are set to 1. Validate that the other Registry values in this rule are correct; engage a ConfigMgr specialist to troubleshoot any WSUS issues.`r`n" $ResultMessage += "UseWUServer and UseUpdateClassPolicySource values :$lineEndFormat" foreach ($AU_PolicyRegKeyLocation in $AU_PolicyRegKeyLocations ) { $UseUpdateClassPolicySourceValue = (Get-ItemProperty Registry::$AU_PolicyRegKeyLocation -ErrorAction SilentlyContinue).UseUpdateClassPolicySource if ($UseUpdateClassPolicySourceValue -eq 1 ) { $UseUpdateClassPolicySource = $true } $ResultMessage += "$indent $AU_PolicyRegKeyLocation\UseUpdateClassPolicySource = $UseUpdateClassPolicySourceValue $lineEndFormat" $UseWUServerValue = (Get-ItemProperty Registry::$AU_PolicyRegKeyLocation -ErrorAction SilentlyContinue).UseWUserver if ($UseWUServerValue -eq 1 ) { $UseWUServer = $true } $ResultMessage += "$indent $AU_PolicyRegKeyLocation\UseWUServer = $UseWUServerValue $lineEndFormat" } $ResultMessage += "`r`nWUServer values : $lineEndFormat" foreach ($PolicyRegKeyLocation in $PolicyRegKeyLocations ) { $WUServerValue = (Get-ItemProperty Registry::$PolicyRegKeyLocation -ErrorAction SilentlyContinue).WUServer if ($WUServerValue -match "http" ) { $UseWUServer = $true } $ResultMessage += "$indent $PolicyRegKeyLocation\WUServer = $WUServerValue $lineEndFormat" } $ResultMessage += "`r`n" if ($null -eq $UseUpdateClassPolicySourceEnabledValue) { if ( ($null -eq $UseWUServer) -or ($UseWUServerValue -eq 0) ) { $result = "Failed" $ResultMessage = "UseWUServer is not present or set to 0 and one or more PolicyDrivenSourceLocation values are set.SetPolicyDrivenSourceLocation* values will not be used and all updates will come from Windows Update.`r`n`r`n" + $ResultMessage } $result = "Warning" $ResultMessage = "UseUpdateClassPolicySource is not present and one or more SetPolicyDrivenSourceLocation values are present.`r`nIf the device is comanaged, UseUpdateClassPolicySource = 1 is optional, but UseWUServer = 1 must be set.`r`n" + $ResultMessage } elseif ($UseUpdateClassPolicySourceEnabledValue -eq 1) { # Validate WSUS before saying success if ($UseWUServer) { $result = "Passed" $ResultMessage = "UseUpdateClassPolicySource is set to 1 with one or more PolicyDrivenSourceLocation values set.`r`n Device will use WSUS for updates for SetPolicyDrivenUpdateSource* policies set to 1 and Windows Update for policies set to 0.`r`n" + $ResultMessage } else { $result = "Failed" $ResultMessage = "UseUpdateClassPolicySource is set to 1 with one or more PolicyDrivenSourceLocation values set, but value for WSUS server (WUServer) is not configured.`r`n This will cause the device to use only Windows Update and UseUpdateClassPolicySource and the associated SetPolicyDrivenSourceLocation* settings will not be used.`r`n" + $ResultMessage } } elseif($UseUpdateClassPolicySourceEnabledValue -eq 0) { $result = "Warning" $ResultMessage = "UseUpdateClassPolicySourceEnabled is set to 0, but one or more PolicyDrivenSourceLocation values are set.`r`n Validate that this is the desired configuration. All updates will come from WSUS.`r`n" + $ResultMessage } if (($PolicyDrivenSourcesSet) -and ($PolicyDrivenSourceCount -ne 4) ) { $ResultMessage += "$lineEndFormat***Warning: only $($PolicyDrivenSourceCount) policy driven entries found. It is recommended to configure all 4 SetPolicyDrivenUpdateSource* sources. Please see https://learn.microsoft.com/en-us/windows/deployment/update/wufb-wsus." } } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-InternetLocationsValue { # check to see that DoNotConnectToWindowsUpdateInternetLocations is not set to 1 # # If DoNotConnectToWindowsUpdateInternetLocations is set to 1, the device will not connect to any Windows Update Internet locations # # Even when Windows Update is configured to receive updates from an intranet update service, it will periodically retrieve information from the public # Windows Update service to enable future connections to Windows Update, and other services like Microsoft Update or the Windows Store. #Enabling this policy will disable that functionality, and may cause connection to public services such as the Windows Store to stop working. # Note: This policy applies only when this PC is configured to connect to an intranet update service using the "Specify intranet Microsoft update service location" policy. $ruleId = "DoNotConnectToWindowsUpdateInternetLocations" $ruleDescription = "If DoNotConnectToWindowsUpdateInternetLocations is set to '1', the device will not search Windows Update." $result = "Failed" [string]$ResultMessage = $null $DoNotConnectToWindowsUpdateInternetLocations_Policy = (Get-ItemProperty Registry::$WU_Policy_Key -ErrorAction SilentlyContinue).DoNotConnectToWindowsUpdateInternetLocations $DoNotConnectToWindowsUpdateInternetLocations_PolicyManager = (Get-ItemProperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).DoNotConnectToWindowsUpdateInternetLocations $DoNotConnectToWindowsUpdateInternetLocations_Windows = (Get-ItemProperty Registry::$WU_Windows_Key -ErrorAction SilentlyContinue).DoNotConnectToWindowsUpdateInternetLocations if ( ($DoNotConnectToWindowsUpdateInternetLocations_Policy -eq 1) -or ($DoNotConnectToWindowsUpdateInternetLocations_PolicyManager -eq 1) -or ($DoNotConnectToWindowsUpdateInternetLocations_Windows -eq 1) ) { $result = "Failed" $ResultMessage = "DoNotConnectToWindowsUpdateInternetLocations is set to 1. This will cause the device to not search Windows Update. $WU_Policy_Key = $DoNotConnectToWindowsUpdateInternetLocations_Policy $WU_PolicyManager_Key = $DoNotConnectToWindowsUpdateInternetLocations_PolicyManager $WU_Windows_Key = $DoNotConnectToWindowsUpdateInternetLocations_Windows" } else { $result = "Passed" $ResultMessage = "No DoNotConnectToWindowsUpdateInternetLocations Registry entries found. Device will use Windows Update." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Get-WinningProvider { # Tests to see if MDMWinsOverGP is set # https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-controlpolicyconflict $ruleId = "WinningProvider" $ruleDescription = "Checks to see if MDMWinsOverGP is set. The presence of this value set to 1 means that Intune will overwrite Group Policy results in the case of a conflict." $result = $null [string]$ResultMessage = $null $WinningProvider_Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\current\device\ControlPolicyConflict" if (Test-Path $WinningProvider_Key) { $WinningProvider_Value = (Get-ItemProperty Registry::$WinningProvider_Key -ErrorAction SilentlyContinue).MDMWinsOverGP } if ( ($WinningProvider_Value -in "", "0") -or ($null -eq $WinningProvider_Value) ) { $result = "Information" $ResultMessage = "MDMWinsOverGP is not present or disabled. Group Policy settings will take precedence over Intune settings in the case of a conflict." } else { $result = "Information" $ResultMessage = "MDMWinsOverGP is set to $WinningProvider_Value. In the case of a conflict, Intune will take precedence over conflicting Group Policy settings." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-SQMIsPopulated { # test to see if machineid is present in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SQMClient $ruleId = "SQM_MachineID_set" $ruleDescription = "Checks to see if the device has a MachineID value in the SQMClient registry key. The presence of this value indicates that the device has registered with telemetry." $result = $null [string]$ResultMessage = $null $FirstSyncTime = $null $SQMClientKey = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SQMClient" $MachineId = (Get-ItemProperty Registry::$SQMClientKey -ErrorAction SilentlyContinue).MachineId $FirstSync = (Get-ItemProperty Registry::$SQMClientKey -ErrorAction SilentlyContinue).WinSqmFirstSessionStartTime if ($FirstSync) { $FirstSyncTime = [datetime]::FromFileTime($FirstSync) } else {$FirstSyncTime = "UNKNOWN" } if ( ($MachineId -ne "") -and ($null -ne $MachineId) ) { $result = "Passed" $ResultMessage = "MachineID value $MachineId found in $SQMClientKey. The presence of this value indicates that the device has registered successfully with telemetry. `r`nFirst sync occurred on $FirstSyncTime" } else { $result = "Warning" $ResultMessage = "No MachineID value found in $SQMClientKey. This indicates that the client is not sending telemetry data." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-UseWUServer { # HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate\AU # UseWUServer 1 = WSUS, 0 = WU $ruleId = "Is_UseWUServer_Set" $ruleDescription = "If UseWUServer is set to '1', the device will not search Windows Update. Reporting to WUfB will be blocked." $result = "Failed" [string]$ResultMessage = $null if (Test-Path Registry::$WU_Policy_AU_Key){ $UseWUServer_Policy = (Get-ItemProperty Registry::$WU_Policy_AU_Key -ErrorAction SilentlyContinue).UseWUServer } if (Test-Path Registry::$WU_PolicyManager_Key){ $UseWUServer_PolicyManager = (Get-ItemProperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).UseWUServer } if (Test-Path Registry::$WU_Windows_Key) { $UseWUServer_Windows = (Get-ItemProperty Registry::$WU_Windows_Key -ErrorAction SilentlyContinue).UseWUServer } if ( ($UseWUServer_Policy -eq 1) -or ($UseWUServer_PolicyManager -eq 1) -or ($UseWUServer_Windows -eq 1) ) { $result = "Failed" $ResultMessage = "UseWUServer is set to 1. This will cause the device to not search Windows Update. WSUS will be used if available. Reporting will not work. $WU_Policy_Key = $UseWUServer_Policy $WU_PolicyManager_Key = $UseWUServer_PolicyManager $WU_Windows_Key = $UseWUServer_Windows" } else { $result = "Passed" $ResultMessage = "No UseWUServer Registry entries found. Device will use Windows Update." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-DisableWindowsUpdateAccess { # HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate # DisableWindowsUpdateAccess 1 = disable $ruleId = "Is_DisableWindowsUpdateAccess_Set" $ruleDescription = "If DisableWindowsUpdateAccess is set to '1', the device will not search Windows Update." $result = "Failed" [string]$ResultMessage = $null $DisableWindowsUpdateAccess_Policy = (Get-ItemProperty Registry::$WU_Policy_Key -ErrorAction SilentlyContinue).DisableWindowsUpdateAccess $DisableWindowsUpdateAccess_PolicyManager = (Get-ItemProperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).DisableWindowsUpdateAccess $DisableWindowsUpdateAccess_Windows = (Get-ItemProperty Registry::$WU_Windows_Key -ErrorAction SilentlyContinue).DisableWindowsUpdateAccess if ( ($DisableWindowsUpdateAccess_Policy -eq 1) -or ($DisableWindowsUpdateAccess_PolicyManager -eq 1) -or ($DisableWindowsUpdateAccess_Windows -eq 1) ) { $result = "Failed" $ResultMessage = "DisableWindowsUpdateAccess is set to 1. This will cause the device to not search Windows Update. $WU_Policy_Key = $DisableWindowsUpdateAccess_Policy $WU_PolicyManager_Key = $DisableWindowsUpdateAccess_PolicyManager $WU_Windows_Key = $DisableWindowsUpdateAccess_Windows" } else { $result = "Passed" $ResultMessage = "No DisableWindowsUpdateAccess Registry entries found. Device will use Windows Update." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-IsQualityUpdateInstalled { # Warn if device has not been updated. This will casue numerous policies to 404 # # Windows build: 10.0.19043 ===== should have minor build number if patched # $ruleId = "Is_QualityUpdateInstalled" $ruleDescription = "Some policies may fail to load with error 404 in the EnterpriseDeviceManagement event log if updates have not been applied." $result = "Passed" [string]$ResultMessage = $null $buildInfo = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' if ( ($null -eq $buildInfo.UBR ) -or ($buildInfo.UBR -eq "")){ $result = "Warning" $ResultMessage = "It appears that this device has not had a quality update applied yet. This may cause some policies to fail to load. It is recommended to update this device.`r`n" $ResultMessage += "Current build: " + $buildInfo.ProductName + " build" + $buildInfo.CurrentMajorVersionNumber + "." + $buildInfo.CurrentMinorVersionNumber + "." + $buildInfo.CurrentBuildNumber } else { $ResultMessage = "Device has at least one quality update applied." $ResultMessage += "Current build: " + $buildInfo.ProductName + " build" + $buildInfo.CurrentMajorVersionNumber + "." + $buildInfo.CurrentMinorVersionNumber + "." + $buildInfo.CurrentBuildNumber + "." + $buildInfo.UBR } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-UpdatePendingReboot { # Adapted from https://devblogs.microsoft.com/scripting/determine-pending-reboot-statuspowershell-style-part-2/ $ruleId = "Reboot_Pending" $ruleDescription = "If a reboot is pending, some tests may be inaccurate." $result = "Passed" [string]$ResultMessage = $null Try { $RebootPending = $false if (Test-Path "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { if (Get-Item "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -ErrorAction SilentlyContinue ) { $RebootPending = $true} } if (Test-Path "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { if (Get-Item "HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -ErrorAction SilentlyContinue ) { $RebootPending = $true} } if (Test-Path "HKLM:SYSTEM\CurrentControlSet\Control\Session Manager") { if (Get-Item ("HKLM:SYSTEM\CurrentControlSet\Control\Session Manager").PendingFileRenameOperations -ErrorAction SilentlyContinue ) { $RebootPending = $true} } if ( (Get-Service -Name CcmExec -ErrorAction SilentlyContinue).Status -eq "Running" ) { if ((Get-WmiObject -Namespace "ROOT\CCM\ClientSDK" -Class "CCM_ClientUtilities").DetermineIfRebootPending ) { $RebootPending = $true} } } Catch { $Error | Write-Log -Level Error } if ($RebootPending -eq $true) { $result = "Warning" $ResultMessage = "Device is pending a reboot. Some tests may be unreliable. It is recommended to reboot before gathering data or performing other testing." } else { $ResultMessage = "No pending reboot detected." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-NoAutoUpdate { # NoAutoUpdate $ruleId = "Is_NoAutoUpdate_Set" $ruleDescription = "If NoAutoUpdate is set to '1', the device will not search Windows Update. Reporting to WUfB will be blocked." $result = "Failed" [string]$ResultMessage = $null if (Test-Path Registry::$WU_Policy_AU_Key) { $NoAutoUpdate_Policy = (Get-ItemProperty Registry::$WU_Policy_AU_Key -ErrorAction SilentlyContinue).NoAutoUpdate } if (Test-Path Registry::$WU_PolicyManager_Key) { $NoAutoUpdate_PolicyManager = (Get-ItemProperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).NoAutoUpdate } if (Test-Path Registry::$WU_Windows_Key ) { $NoAutoUpdate_Windows = (Get-ItemProperty Registry::$WU_Windows_Key -ErrorAction SilentlyContinue).NoAutoUpdate } if ( ($NoAutoUpdate_Policy -eq 1) -or ($NoAutoUpdate_PolicyManager -eq 1) -or ($NoAutoUpdate_Windows -eq 1) ) { $result = "Failed" $ResultMessage = "NoAutoUpdate is set to 1. Updates will only be installed if a user manually initiates a check for updates. $WU_Policy_Key = $NoAutoUpdate_Policy $WU_PolicyManager_Key = $NoAutoUpdate_PolicyManager $WU_Windows_Key = $NoAutoUpdate_Windows" } else { $result = "Passed" $ResultMessage = "No NoAutoUpdate Registry entries found. Device will check for updates automatically." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-AllowWUfBCloudProcessing { # https://docs.microsoft.com/en-us/windows/deployment/update/deployment-service-overview # HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\default\System\AllowWUfBCloudProcessing # HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\DataCollection\AllowWUfBCloudProcessing $ruleId = "AllowWUfBCloudProcessing" $ruleDescription = "If AllowWUfBCloudProcessing is set to a value other than '8', the device will not search Windows Update. Reporting to WUfB will be blocked." $result = "Failed" [string]$ResultMessage = $null $AllowWUfBCloudProcessing_Policy = (Get-ItemProperty Registry::$WU_Policy_Key -ErrorAction SilentlyContinue).AllowWUfBCloudProcessing $AllowWUfBCloudProcessing_PolicyManager = (Get-ItemProperty Registry::$WU_PolicyManager_Key -ErrorAction SilentlyContinue).AllowWUfBCloudProcessing $AllowWUfBCloudProcessing_Windows = (Get-ItemProperty Registry::$WU_Windows_Key -ErrorAction SilentlyContinue).AllowWUfBCloudProcessing if ( ($AllowWUfBCloudProcessing_Policy -and ($AllowWUfBCloudProcessing_Policy -ne 8)) ` -or ($AllowWUfBCloudProcessing_PolicyManager -and ($AllowWUfBCloudProcessing_PolicyManager -ne 8)) ` -or ($AllowWUfBCloudProcessing_Windows -and ($AllowWUfBCloudProcessing_Windows -ne 8)) ` ) { $result = "Failed" $ResultMessage = "AllowWUfBCloudProcessing is set to a value other than '8' (enabled). DSS policies cannot be used.`r`n" $ResultMessage += "AllowWUfBCloudProcessing_Policy = $AllowWUfBCloudProcessing_Policy `r`n" $ResultMessage += "AllowWUfBCloudProcessing_PolicyManager = $AllowWUfBCloudProcessing_PolicyManager `r`n" $ResultMessage += "AllowWUfBCloudProcessing_Windows = $AllowWUfBCloudProcessing_Windows `r`n" } else { $result = "Passed" $ResultMessage = "No AllowWUfBCloudProcessing Registry entries found. Device will check for updates automatically.`r`n" $ResultMessage += "AllowWUfBCloudProcessing_Policy = $AllowWUfBCloudProcessing_Policy `r`n" $ResultMessage += "AllowWUfBCloudProcessing_PolicyManager = $AllowWUfBCloudProcessing_PolicyManager `r`n" $ResultMessage += "AllowWUfBCloudProcessing_Windows = $AllowWUfBCloudProcessing_Windows `r`n" } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-DefaultService { $ruleId = "Test_WU_Is_DefaultService" $ruleDescription = "If WU is not the default service, client most likely uses WSUS. Feature Updates and Expedited Updates are not supported in this scenario." $result = "Failed" [string]$ResultMessage = $null # RegistrationState 3 = default; any other value is incorrect for mu $ServiceManager = New-Object -ComObject Microsoft.Update.ServiceManager $MuService = ($ServiceManager.QueryServiceRegistration("7971f918-a847-4430-9279-4a52d1efe18d")).RegistrationState $WSUSService = ($ServiceManager.QueryServiceRegistration("3da21691-e39d-4da6-8a4b-b43877bcb1b7")).RegistrationState if ($MuService -eq 3) { $result = "Passed" $ResultMessage = "Microsoft Update service (7971f918-a847-4430-9279-4a52d1efe18d) is the default service. WU client will query Microsoft Update" } else { $ResultMessage = "Microsoft Update service (7971f918-a847-4430-9279-4a52d1efe18d) is not the default Windows Update provider. Most likely WSUS is used instead.`r`n Feature Update and Expedited Update policies are not supported in this scenario." if ($WSUSService -eq 3) { $ResultMessage += "`r`nWSUS service (3da21691-e39d-4da6-8a4b-b43877bcb1b7) is the default update provider."} } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-PossibleCorruption { # https://support.microsoft.com/en-us/topic/virus-scanning-recommendations-for-enterprise-computers-that-are-running-windows-or-windows-server-kb822158-c067a732-f24a-9079-d240-3733e39b40bc $ruleId = "Test-PossibleCorruption" $ruleDescription = "If reboots have been pending after device has been reset, client cache may need to be reset." $result = "Passed" [string]$ResultMessage = $null $objSession = New-Object -ComObject "Microsoft.Update.Session" $objSearcher = $objSession.CreateupdateSearcher() $intCount = $objSearcher.GetTotalHistoryCount() $colHistory = $objSearcher.QueryHistory(0, $intCount) # test driver #$colHistory = Import-Clixml ".\updatehistory.xml" $timeNow = Get-Date [string]$ResultMessage = $null foreach ($objHistory in $colHistory) { $daysSinceEvent = 0 # Only get MU updates if (($objHistory.ServiceID -eq '7971f918-a847-4430-9279-4a52d1efe18d') -and ($objHistory.Title -notmatch '.*Defender Antivirus.*') ` -and ($objHistory.Title -notmatch 'Malicious Software')) { $Title = ($objHistory.Date).ToString("yyyy/MM/dd hh:mm UTC") + " " + $objHistory.Title + "`r`n" # -2145116140 = reboot pending if ($objHistory.HResult -eq -2145116140) { $daysSinceEvent = [math]::Round(($timenow.Subtract($objHistory.Date)).totaldays, 0) if ($daysSinceEvent -gt 30) { $ResultMessage += $Title $ResultMessage += "Update `"$($objHistory.Title)`" has been pending reboot for $daysSinceEvent days.`r`nThis is highly suspicious unless the device really has not been rebooted. If the device " $ResultMessage += "has been power-cycled, there is a possibility that the Windows Update client cache is corrupt.`r`nConsider resetting the WU client cache - https://aka.ms/ResetWU " $ResultMessage += "(scripted version available at https://aka.ms/ResetWUScript.). If updates are installing as expected, this message may be ignored.`r`n`r`n" $result = "Warning" } } } } if ($ResultMessage -eq "") { $ResultMessage = "No updates pending reboots found."} return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } function Test-IsOnMeteredConnection { # check to see if default internet connection is metered # https://support.microsoft.com/en-us/windows/metered-internet-connections-faq-8a8cf4c0-b8b1-1de4-825d-24714e851659 $ruleId = "IsOnMeteredConnection" $ruleDescription = "If the default network connection is metered, some updates may not be downloaded." $result = "Passed" $isMetered = $false [string]$ResultMessage = $null # https://gist.github.com/nijave/d657fb4cdb518286942f6c2dd933b472 [void][Windows.Networking.Connectivity.NetworkInformation, Windows, ContentType = WindowsRuntime] $cost = [Windows.Networking.Connectivity.NetworkInformation]::GetInternetConnectionProfile().GetConnectionCost() $isMetered = $cost.ApproachingDataLimit -or $cost.OverDataLimit -or $cost.Roaming -or $cost.BackgroundDataUsageRestricted -or ($cost.NetworkCostType -ne "Unrestricted") $defaultConnectionName = [Windows.Networking.Connectivity.NetworkInformation]::GetInternetConnectionProfile().ProfileName if ( $isMetered ) { $result = "Warning" $ResultMessage = "The default network connecton ($defaultConnectionName) is metered. This will cause Windows Update to only download priority updates. Discuss this setting if updates are not being downloaded or updates are delayed." } else { $result = "Passed" $ResultMessage = "Default network connection $defaultConnectionName is not metered." } return New-RuleCheckResult $ruleId $ruleDescription $result $ResultMessage } $resultBlob = @() $resultBlob += Test-AlwaysAutoRebootEnabled $resultBlob += Test-deploymentschedulerConnectivity $resultBlob += Test-DevicelistenerprodConnectivity $resultBlob += Test-DoWUPoliciesExist $resultBlob += Test-IsWSUSConfigured $resultBlob += Test-WUAServStartupType $resultBlob += Test-HealthServiceStartupType $resultBlob += Test-IsTelemetryEnabled $resultBlob += Test-IsDeviceHealthMonitoringEnabled $resultBlob += Test-DHMScope $resultBlob += Test-DHMUploadDestination $resultBlob += Test-FeatureUpdateHoldExits $resultBlob += Get-WinningProvider $resultBlob += Test-IsHealthServicesRunning $resultBlob += Test-SQMIsPopulated if ( (Get-OSVersion -short) -eq 10) { $resultBlob += Test-IsDualScanEnabled } else { $resultBlob += Test-AreSetPolicyDrivenUpdateSourcePoliciesSet } $resultBlob += Test-UseWUServer $resultBlob += Test-DisableWindowsUpdateAccess $resultBlob += Test-NoAutoUpdate $resultBlob += Test-DefaultService $resultBlob += Test-AllowWUfBCloudProcessing $resultBlob += Test-PossibleCorruption $resultBlob += Test-IsOnMeteredConnection $resultBlob += Test-IsQualityUpdateInstalled $resultBlob += Test-UpdatePendingReboot $resultBlob += Test-InternetLocationsValue $resultBlob | Format-List %SystemRoot%\System32\Winevt\Logs\*odj* %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker*.evtx %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AssignedAccess*.evtx %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-Authentication User Interface%4Operational.evtx %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-PushNotification-Platform* %SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-UniversalTelemetryClient%4Operational.evtx %SystemRoot%\System32\winevt\Logs\Microsoft-Windows-TaskScheduler* %SystemRoot%\system32\Winevt\Logs\Microsoft-Windows-DeviceSetupManager* %SystemRoot%\system32\Winevt\Logs\Microsoft-Windows-HelloForBusiness* %SystemRoot%\system32\Winevt\Logs\Microsoft-Windows-SENSE%4Operational.evtx %SystemRoot%\system32\Winevt\Logs\Microsoft-Windows-Shell-Core%4Operational.evtx %SystemRoot%\system32\winevt\logs\*-bits-* %SystemRoot%\system32\winevt\logs\*AAD*.evtx %SystemRoot%\system32\winevt\logs\*PFX* %SystemRoot%\system32\winevt\logs\*PushNotifications* %SystemRoot%\system32\winevt\logs\*TWinUI* %SystemRoot%\system32\winevt\logs\*WMI* %SystemRoot%\system32\winevt\logs\*appx* %SystemRoot%\system32\winevt\logs\*bitlocker* %SystemRoot%\system32\winevt\logs\*defender* %SystemRoot%\system32\winevt\logs\*devicemanagement* %SystemRoot%\system32\winevt\logs\*firewall* %SystemRoot%\system32\winevt\logs\*intune* %SystemRoot%\system32\winevt\logs\*momlog* %SystemRoot%\system32\winevt\logs\*ncrypt* %SystemRoot%\system32\winevt\logs\*odj* %SystemRoot%\system32\winevt\logs\*operations* %SystemRoot%\system32\winevt\logs\*powershell* %SystemRoot%\system32\winevt\logs\*user*device* %SystemRoot%\system32\winevt\logs\*workplace* %SystemRoot%\system32\winevt\logs\Application.evtx %SystemRoot%\system32\winevt\logs\Microsoft-Windows-CodeIntegrity* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-Kernel-Boot%4Operational.evtx %SystemRoot%\system32\winevt\logs\Microsoft-Windows-LAPS* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-Microsoft-Windows-AppLocker* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-ModernDeployment-* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-NetworkProfile* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-Provisioning-* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-RemoteHelp* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-Store* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-TaskScheduler* %SystemRoot%\system32\winevt\logs\Microsoft-Windows-WindowsUpdateClient* %SystemRoot%\system32\winevt\logs\PFX* %SystemRoot%\system32\winevt\logs\Security.evtx %SystemRoot%\system32\winevt\logs\Setup.evtx %SystemRoot%\system32\winevt\logs\System.evtx %ProgramFiles%\System Center Advisor\GatewayData\Logs\*.* %ALLUSERSPROFILE%\Microsoft\Microsoft Antimalware\Support\*.cab %ProgramData%\Microsoft\Microsoft Antimalware\Support\*.cab %ProgramFiles%\Microsoft Antimalware\Support\*.* %SystemRoot%\Provisioning\AutoPilot\AutoPilotConfigurationFile.json %systemroot%\ServiceState\Autopilot\*.json %SystemRoot%\Logs\MeasuredBoot\*.log %systemroot%\Logs\CBS\*.log %localappdata%\Packages\Microsoft.CompanyPortal_8wekyb3d8bbwe\LocalState %SMS_LOG_PATH%\*.* %systemroot%\ccmsetup\Logs\* %ProgramFiles%\Microsoft Online Directory Sync\*.log %LOCALAPPDATA%\Temp\*.log %LocalAppData%\Local\Microsoft\Edge\User Data\MAMLog.txt %ProgramData%\Microsoft\IntuneManagementExtension\Logs\* %ProgramData%\microsoft\diagnosticlogcsp\collectors\* %ProgramFiles%\Microsoft Intune\ODJConnector\ODJConnectorUI\*.log %SystemRoot%\PolicyClient*.log %SystemRoot%\SoftwareDistribution\ReportingEvents*.log %systemroot%\temp\stdout.log %temp%\supportconsole*.* %SystemRoot%\Logs\HomeGroup\*.* %LOCALAPPDATA%\Microsoft\OnlineManagement\Logs\clientui.log %LOCALAPPDATA%\Microsoft\Windows\clientui.log %ProgramFiles%\Microsoft\OnlineManagement\Logs\*.log %ProgramFiles%\Microsoft\OnlineManagement\PolicyAgent\InventoryCache\3DA21691-E39D-4DA6-8A4B-B43877BCB1B7\*.xml %ProgramFiles%\Microsoft\OnlineManagement\PolicyAgent\ReportCache\3DA21691-E39D-4DA6-8A4B-B43877BCB1B7\*.xml %ProgramFiles%\microsoft policy platform\policyplatformclient*.log %SystemRoot%\scoconnector.etl %TEMP%\clientui.log %Temp%\SoftwarePublishing\*.log %public%\Documents\MDMDiagnostics\* %ProgramData%\Microsoft\Diagnosis\DownloadedSettings\* %SystemRoot%\temp\*MSI*.log %temp%\*MSI*.log %ProgramFiles%\Microsoft Configuration Manager\logs\ndes* %ProgramFiles%\Microsoft Intune\NDESConnectorSvc\NDESConnector.exe.config %ProgramFiles%\Microsoft Intune\NDESConnectorSvc\logs\logs\* %ProgramFiles%\Microsoft Intune\NDESConnectorUI\NDESConnectorUI.log %ProgramFiles%\Microsoft Intune\NDESPolicyModule\Logs\NDESPlugin.log %temp%\*CertConnectorLogs*.zip C:\NDESConnectorSetup\*.log C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log %SystemRoot%\Panther %SystemRoot%\inf\*.log %SystemRoot%\inf\setup*.log %LocalAppData%\mdm\*.log %ProgramData%\Microsoft\IntuneManagementExtension\Logs\* %systemroot%\system32\config\systemprofile\AppData\Local\mdm\*.log %AppData%\Microsoft\Teams\*.txt %LocalAppData%\Microsoft\Teams\SquirrelSetup.log %LocalAppData%\Microsoft\Teams\current\SquirrelSetup.log %LocalAppData%\SquirrelTemp\* %SystemRoot%\system32\wbem\logs\*.* %SystemRoot%\system32\wbem\tmf\*.* %ProgramFiles%\Microsoft Update Health Tools\Logs %SystemRoot%\Logs\DISM %ProgramData%\USOShared\Logs\System %ProgramData%\USOShared\Logs\User HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CloudManagedUpdate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager\current\device\Update HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WufbDS HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\CloudDomainJoin HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\EAS HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ProductOptions\ProductSuite HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows\CSDVersion HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\CertPropSvc HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\crypt32 HKEY_LOCAL_MACHINE\SYSTEM\SYSTEM\CurrentControlSet\services\CryptSvc HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\SCPolicySvc HKEY_LOCAL_MACHINE\HKLM\SYSTEM\CurrentControlSet\services\SCardSvr HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\SharedAccess\Parameters\FirewallPolicy\Mdm HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\TPM HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WindowsEmbedded\ProductVersion HKEY_CURRENT_USER\Software\Microsoft\SCEP HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall HKEY_CURRENT_USER\Software\Policies HKEY_CURRENT_USER\Volatile Environment HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BitLockerCsp HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CCM HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CloudManagedUpdate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MSCEP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DeviceManageabilityCSP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\DiagnosticLogCSP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EPMAgent HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EnterpriseDesktopAppManagement HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\HVSICSP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MDMWins HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftIntune HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Operations Manager HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\multivariant HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\OfficeCSP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\OnlineManagement HKEY_LOCAL_MACHINE\Software\Microsoft\POSReady HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyManager HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PolicyPlatform HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\provisioning HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\Diagnostics\AutoPilot HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\NodeCache\CSP HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SQMClient HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCenterAdvisor HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\System Center Online Client HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Updates HKEY_LOCAL_MACHINE\Software\Microsoft\WEPOS\Version HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsSelfHost HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate HKEY_LOCAL_MACHINE\SOFTWARE\microsoft\windows\assignedaccessconfiguration HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\windows\assignedaccesscsp HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Autopilot HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MDM HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\StorageSense HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Defender HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Enrollments HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WufbDS HKEY_LOCAL_MACHINE\Software\Policies HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Policies