├── Autopilot ├── Add-GroupTagToAllAPDevices.ps1 └── README.MD ├── Azure └── AzureNetworkRequirements │ └── Test-AzureNetworkRequirements ├── GlobalSecureAccess └── Script-ShowSignOutAndDisablePrivateAccessButton.ps1 ├── Intune ├── AutopilotDualDeviceFinder.ps1 ├── CVE-2023-24932 │ └── Apply-CVE-2023-24932.ps1 ├── Connect-SettingsCatalog-To-Group.ps1 ├── IntuneNetworkRequirements │ ├── Get-IntuneNetworkRequirements.ps1 │ ├── INRCustomList.csv │ ├── README.MD │ └── changelog.md ├── Platform Scripts │ └── Reset-WindowsUpdateSettings.ps1 ├── README.MD ├── Remediations │ ├── Detect-NewOutlookDEVICE.ps1 │ ├── Detect-NewOutlookUSER.ps1 │ ├── Detect-PrintToPDFandXPS.ps1 │ ├── Remediate-NewOutlookDEVICE.ps1 │ ├── Remediate-NewOutlookUSER.ps1 │ └── Remediate-PrintToPDFandXPS.ps1 └── ScriptWrapper.ps1 ├── LICENSE ├── Other ├── List-EAMApplications.ps1 ├── Rename-ComputerRandomizer.ps1 └── Win32App-Wait.ps1 └── Windows Migration └── Windows11BasicInformationCollection.ps1 /Autopilot/Add-GroupTagToAllAPDevices.ps1: -------------------------------------------------------------------------------- 1 | if ($null -eq $($MgContext)) { 2 | Connect-MgGraph -TenantId '' -Scopes DeviceManagementServiceConfig.ReadWrite.All,DeviceManagementConfiguration.ReadWrite.All 3 | } 4 | $GroupTag = 'Windows | AP default' 5 | $graphApiVersion = 'beta' 6 | $Resource = "deviceManagement/windowsAutopilotDeviceIdentities" 7 | $APDevices = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/$graphApiVersion/$Resource/" -Method GET 8 | $body = "{ 9 | `"groupTag`":`"$GroupTag`" 10 | }" 11 | ForEach($Device in $APDevices.value){ 12 | Invoke-MgGraphRequest -ContentType "application/json" -Uri "https://graph.microsoft.com/$graphApiVersion/$Resource/$($Device.id)/UpdateDeviceProperties" -Method POST -Body $body -OutputType PSObject 13 | } 14 | -------------------------------------------------------------------------------- /Autopilot/README.MD: -------------------------------------------------------------------------------- 1 | # Warning notice 2 | 3 | Tools around Autopilot - use at your own risk! 4 | -------------------------------------------------------------------------------- /Azure/AzureNetworkRequirements/Test-AzureNetworkRequirements: -------------------------------------------------------------------------------- 1 | <# 2 | DO NOT USE THIS SCRIPT! 3 | Verify Azure adresses by using a region and servicetag 4 | 5 | https://learn.microsoft.com/en-us/azure/virtual-network/service-tags-overview#available-service-tags 6 | #> 7 | 8 | function Get-ScriptPath { 9 | <# 10 | .SYNOPSIS 11 | Get the current script path. 12 | #> 13 | if ($PSScriptRoot) { 14 | # Console or VS Code debug/run button/F5 temp console 15 | $ScriptRoot = $PSScriptRoot 16 | } else { 17 | if ($psISE) { 18 | Split-Path -Path $psISE.CurrentFile.FullPath 19 | } else { 20 | if ($profile -match 'VScode') { 21 | # VS Code "Run Code Selection" button/F8 in integrated console 22 | $ScriptRoot = Split-Path $psEditor.GetEditorContext().CurrentFile.Path 23 | } else { 24 | Write-Output 'unknown directory to set path variable. exiting script.' 25 | exit 26 | } 27 | } 28 | } 29 | $Script:PathToScript = $ScriptRoot 30 | } 31 | function Write-Log { 32 | <# 33 | .DESCRIPTION 34 | This is a modified version of the script by Ryan Ephgrave. 35 | .LINK 36 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 37 | #> 38 | Param ( 39 | [Parameter(Mandatory = $false)] 40 | $Message, 41 | $Component, 42 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 43 | [ValidateSet('1', '2', '3')][int]$Type 44 | ) 45 | if (-not($NoLog)) { 46 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 47 | $Date = Get-Date -Format 'MM-dd-yyyy' 48 | if (-not($Component)) { $Component = 'Runner' } 49 | if (-not($ToConsole)) { 50 | $LogMessage = "" 51 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 52 | } elseif ($ToConsole) { 53 | switch ($type) { 54 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 55 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 56 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 57 | default { Write-Host "T:$Type C:$Component M:$Message" } 58 | } 59 | } 60 | } 61 | } 62 | function Get-AzureServiceURLs{ 63 | <# 64 | .NOTES 65 | TODO! 66 | #> 67 | param( 68 | $Servicetag 69 | ) 70 | $rawhtml = Invoke-RestMethod -Uri 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519' -UseBasicParsing 71 | $downloadAzureJSONURL = [Regex]::Match($rawhtml, 'https://download.microsoft.com[^"]*').Value 72 | $AzureJSON = ConvertFrom-Json (Invoke-RestMethod -Uri $downloadAzureJSONURL ) 73 | } 74 | 75 | #Provided by ChatGPT(!) 76 | function Convert-CIDRToIPList { 77 | param ( 78 | [string]$CIDR 79 | ) 80 | 81 | # Split the CIDR block into IP address and prefix length 82 | $parts = $CIDR.Split('/') 83 | $ipAddress = $parts[0] 84 | $prefixLength = [int]$parts[1] 85 | 86 | # Convert the IP address to a byte array 87 | $ipBytes = [System.Net.IPAddress]::Parse($ipAddress).GetAddressBytes() 88 | 89 | # Calculate the number of addresses in the block 90 | $numAddresses = [math]::Pow(2, 32 - $prefixLength) 91 | 92 | # Convert the byte array to an integer 93 | [Array]::Reverse($ipBytes) 94 | $ipInt = [BitConverter]::ToUInt32($ipBytes, 0) 95 | 96 | # Generate the list of IP addresses 97 | $ipList = @() 98 | for ($i = 0; $i -lt $numAddresses; $i++) { 99 | $currentIpInt = $ipInt + $i 100 | $currentIpBytes = [BitConverter]::GetBytes($currentIpInt) 101 | [Array]::Reverse($currentIpBytes) 102 | $currentIp = [System.Net.IPAddress]::new($currentIpBytes) 103 | $ipList += $currentIp.IPAddressToString 104 | } 105 | 106 | return $ipList 107 | } -------------------------------------------------------------------------------- /GlobalSecureAccess/Script-ShowSignOutAndDisablePrivateAccessButton.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $TargetPath ="HKCU:\Software\Microsoft\Global Secure Access Client" 3 | ) 4 | function CreateIfNotExists 5 | { 6 | param($Path) 7 | if (-NOT (Test-Path $Path)) 8 | { 9 | New-Item -Path $Path -Force | Out-Null 10 | } 11 | } 12 | CreateIfNotExists $TargetPath 13 | Set-ItemProperty -Path $TargetPath -Name "HideSignOutButton" -Type DWord -Value "0x0" 14 | Set-ItemProperty -Path $TargetPath -Name "HideDisablePrivateAccessButton" -Type DWord -Value "0x0" -------------------------------------------------------------------------------- /Intune/AutopilotDualDeviceFinder.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script will find devices in your tenant that exist twice. 4 | .DESCRIPTION 5 | If you want to analyse how many devices are hybrid _and_ entra joined and both are linked to an autopilot object in your tenant 6 | this script is for you. It will go by displayName. The runtime for a tenant with 20.000 devices is roughly 45 seconds. 7 | #> 8 | $CertificateThumbprint = "" 9 | $ClientID = "" 10 | $TenantId = "" 11 | $NumberOfDays = "-60" #Number of days that a machine has had to been active 12 | if (-not(Get-MgContext)) { 13 | Connect-MgGraph -CertificateThumbprint $CertificateThumbprint -ClientId $ClientID -TenantId $TenantId 14 | } 15 | 16 | function Get-nextLinkData { 17 | param( 18 | $OriginalObject 19 | ) 20 | $nextLink = $OriginalObject.'@odata.nextLink' 21 | $Results = $OriginalObject 22 | while ($nextLink) { 23 | $Request = Invoke-MgGraphRequest -Uri $nextLink 24 | $Results.value += $Request.value 25 | $nextLink = '' 26 | $nextLink = $Request.'@odata.nextLink' 27 | } 28 | return $Results 29 | } 30 | $DateMinus60Days = Get-date (Get-Date).AddDays($NumberOfDays) -Format 'yyyy-MM-ddThh:mm:ssZ' 31 | $AllDevices = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/devices/?`$filter=(operatingSystem eq 'Windows') and (approximateLastSignInDateTime ge $DateMinus60Days)" 32 | if ($AllDevices.'@odata.nextLink') { 33 | $AllDevices = Get-nextLinkData -OriginalObject $AllDevices 34 | } 35 | $DupeNames = ($AllDevices.value | Group-Object -Property displayName | Where-Object { $_.count -gt 1 }).Name 36 | $ToBeDeleted = [System.Collections.ArrayList]::new() 37 | foreach ($DupeName in $DupeNames) { 38 | $PhysicalIDs = ($AllDevices.Value | Where-Object { $_.displayname -eq $DupeName }).physicalIds 39 | if ($PhysicalIDs) { 40 | $AutopilotIDs = $PhysicalIDs | Select-String -Pattern '[ZTDID]:' -SimpleMatch 41 | if ($AutopilotIDs) { 42 | $AutopilotIDs = ($AutopilotIDs).line.Substring(8.36) 43 | } 44 | } 45 | if ($AutopilotIDs.Count -eq 2) { 46 | $EntraDevice = $AllDevices.value | Where-Object { $_.displayname -eq $DupeName -and $_.trustType -eq 'AzureAd' } 47 | $HybridDevice = $AllDevices.value | Where-Object { $_.displayname -eq $DupeName -and $_.trustType -eq 'ServerAd' } 48 | if ($HybridDevice.approximateLastSignInDateTime -ge $EntraDevice.approximateLastSignInDateTime) { 49 | $ToBeDeleteObject = [PSCustomObject]@{ 50 | DeviceName = $EntraDevice.displayName 51 | EntraDeviceID = $EntraDevice.deviceId 52 | EntraDeviceLastSignIn = $EntraDevice.approximateLastSignInDateTime 53 | HybridDeviceID = $HybridDevice.deviceId 54 | HybridDeviceLastSignIn = $HybridDevice.approximateLastSignInDateTime 55 | } 56 | $ToBeDeleted.add($ToBeDeleteObject) | Out-Null 57 | } 58 | } 59 | } 60 | $ToBeDeleted -------------------------------------------------------------------------------- /Intune/CVE-2023-24932/Apply-CVE-2023-24932.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | <# 3 | .SYNOPSIS 4 | THIS SCRIPT ISN'T DONE! DO NOT USE! 5 | Script applies the steps outline in https://support.microsoft.com/en-us/topic/how-to-manage-the-windows-boot-manager-revocations-for-secure-boot-changes-associated-with-cve-2023-24932-41a975df-beb2-40c1-99a3-b3ff139f832d#bkmk_install 6 | .EXAMPLE 7 | Apply-CVE-2023-24932.ps1 8 | .NOTES 9 | Version: 1.0 10 | Author: Martin Himken 11 | Original script name: Apply-CVE-2023-24932.ps1 12 | Initial 19.02.2025 13 | Last Update: 02.03.2025 14 | #> 15 | param( 16 | [System.IO.DirectoryInfo]$WorkingDirectory = "C:\BLR\", 17 | [System.IO.DirectoryInfo]$LogDirectory = "C:\BLR\", 18 | $LockscreenImagePath = "lockscreen.jpg", 19 | $RegistryPath = "HKLM:\SOFTWARE\Apply-CVE-2023-24932" 20 | ) 21 | function Initialize-Script { 22 | <# 23 | .SYNOPSIS 24 | Will initialize most of the required variables throughout this script. 25 | #> 26 | $Script:DateTime = Get-Date -Format yyyyMMdd_HHmmss 27 | if (-not($Script:CurrentLocation)) { 28 | $Script:CurrentLocation = Get-Location 29 | } 30 | if (-not(Test-Path $WorkingDirectory )) { New-Item $WorkingDirectory -ItemType Directory -Force | Out-Null } 31 | if ((Get-Location).path -ne $WorkingDirectory) { 32 | Set-Location $WorkingDirectory 33 | } 34 | Get-ScriptPath 35 | if (-not($Script:LogFile)) { 36 | $LogPrefix = '23-24932' #CVE2023-24932 Fix 37 | $Script:LogFile = Join-Path -Path $LogDirectory -ChildPath ('{0}_{1}.log' -f $LogPrefix, $Script:DateTime) 38 | if (-not(Test-Path $LogDirectory)) { New-Item $LogDirectory -ItemType Directory -Force | Out-Null } 39 | } 40 | if ($PSVersionTable.psversion.major -lt 7) { 41 | Write-Log -Message 'Please follow the manual - PowerShell 7 is currently required to run this script.' -Component 'InitializeScript' -Type 3 42 | Exit 1 43 | } 44 | $FullLockscreenImagePath = Join-Path -Path $WorkingDirectory -ChildPath $LockscreenImagePath 45 | if (-not(Test-Path $RegistryPath)) { 46 | New-Item -Path $RegistryPath -ItemType File -Force 47 | $Script:WasRunBefore = Get-ItemProperty -Path $RegistryPath -Name 'InitialRunDate' -ErrorAction SilentlyContinue 48 | if (-not($Script:WasRunBefore)) { 49 | New-ItemProperty -Path $RegistryPath -Name 'InitialRunDate' -Value $Script:DateTime 50 | } else { 51 | New-ItemProperty -Path $RegistryPath -Name 'LastRunDate' -Value $Script:DateTime 52 | } 53 | } 54 | #ToDO! 55 | #$WindowsMajorCorrected = [System.Environment]::OSVersion.Version | ForEach-Object {} 56 | } 57 | function Write-Log { 58 | <# 59 | .DESCRIPTION 60 | This is a modified version of the script by Ryan Ephgrave. 61 | .LINK 62 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 63 | #> 64 | Param ( 65 | [Parameter(Mandatory = $false)] 66 | $Message, 67 | $Component, 68 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 69 | [ValidateSet('1', '2', '3')][int]$Type 70 | ) 71 | if (-not($NoLog)) { 72 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 73 | $Date = Get-Date -Format 'MM-dd-yyyy' 74 | if (-not($Component)) { $Component = 'Runner' } 75 | if (-not($ToConsole)) { 76 | $LogMessage = "" 77 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 78 | } elseif ($ToConsole) { 79 | switch ($type) { 80 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 81 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 82 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 83 | default { Write-Host "T:$Type C:$Component M:$Message" } 84 | } 85 | } 86 | } 87 | } 88 | 89 | function Get-FixStatus { 90 | $Script:CurrentDBStatusRegistry = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing" -Name 'WindowsUEFICA2023Capable').WindowsUEFICA2023Capable 91 | $Script:SecureBootDBStatus = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI db).bytes) -match 'Windows UEFI CA 2023' 92 | $Script:SecureBootDBXStatus = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI dbx).bytes) -match 'Microsoft Windows Production PCA 2011' 93 | $Script:CurrentAvailableUpdatesValue = (Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot -Name 'AvailableUpdates').AvailableUpdates 94 | } 95 | function Get-FixPhase { 96 | Get-FixStatus 97 | switch ($Script:CurrentDBStatusRegistry) { 98 | "0" { $WindowsUEFICA2023Capable = "NotInDB" } 99 | "1" { $WindowsUEFICA2023Capable = "Prepared" } 100 | "2" { $WindowsUEFICA2023Capable = "PreparedAndActive" } 101 | default { $WindowsUEFICA2023Capable = "NotInDB" } 102 | } 103 | switch ($Script:CurrentAvailableUpdatesValue) { 104 | "0" { $Script:CurrentPhase = "Eval" } 105 | "320" { $Script:CurrentPhase = 1 } 106 | "256" { $Script:CurrentPhase = 2 } 107 | "640" { $Script:CurrentPhase = 3 } 108 | default { $Script:CurrentPhase = $false } 109 | } 110 | if (-not($Script:CurrentPhase)) { 111 | Write-Log -Message 'AvailableUpdates key is missing the required July 2024 patch probably was not applied' -Component 'GetFixPhase' -Type 3 112 | Exit 1 113 | } 114 | if ($Script:CurrentPhase -eq "Eval" -and -not($Script:SecureBootDBStatus) -and -not($Script:SecureBootDBXStatus)) { 115 | Write-Log 'Machine is not patched yet, starting phase 1' -Component 'GetFixPhase' 116 | Start-PhaseOne 117 | } 118 | if ($Script:CurrentPhase -eq "2" -and $Script:SecureBootDBStatus -and $WindowsUEFICA2023Capable -eq 'Prepared') { 119 | Write-Log 'Phase 1 completed, next reboot will make it phase two - rebooting' -Component 'GetFixPhase' 120 | Start-PhaseTwo 121 | } 122 | if ($Script:CurrentPhase -eq "Eval" -and $WindowsUEFICA2023Capable -eq "Prepared") { 123 | #Might not be required! 124 | } 125 | if ($Script:CurrentPhase -eq "Eval" -and $WindowsUEFICA2023Capable -eq "PreparedAndActive" -and -not($Script:SecureBootDBXStatus)) { 126 | Start-PhaseThree 127 | } 128 | if ($Script:CurrentPhase -eq "Eval" -and $WindowsUEFICA2023Capable -eq "PreparedAndActive" -and $Script:SecureBootDBXStatus){ 129 | Start-PhaseFinish 130 | } 131 | } 132 | 133 | function Start-PhaseOne { 134 | New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot -Name 'AvailableUpdates' -PropertyType DWORD -Value 320 -Force | Out-Null 135 | Restart-ComputerControl -EventIDToWaitfor 1800 136 | } 137 | function Start-PhaseTwo { 138 | Restart-ComputerControl -EventIDToWaitfor 1799 139 | } 140 | function Start-PhaseThree { 141 | New-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot -Name 'AvailableUpdates' -PropertyType DWORD -Value 640 -Force | Out-Null 142 | Restart-ComputerControl -EventIDToWaitfor 1042 143 | } 144 | 145 | function Restart-ComputerControl { 146 | param( 147 | [int]$EventIDToWaitfor 148 | ) 149 | if(-not($EventIDToWaitfor)){ 150 | Write-Log -Message "No event ID provided - Computer will restart"-Component 'Restart' 151 | #RESTART THE COMPUTER - LOCK SCREEN 152 | } 153 | else{ 154 | #Find EventID given and THEN reboot 155 | while(-not($Script:EventFound)){ 156 | $Script:EventFound = Get-WinEvent -LogName System -FilterXPath "*[System[Provider[@Name='Microsoft-Windows-TPM-WMI']][EventID=$EventIDToWaitfor]]" 157 | Start-Sleep -Seconds 5 158 | } 159 | } 160 | Exit 1610 161 | #Scheduled task that checks for EventIDToWaitFor and then auto-reboots 162 | #Needs a way to block the users from sign-in - unlock if it takes longer than 5 minutes 163 | #Add custom Lockscreen in the meantime 164 | } 165 | 166 | Start-CVEErrorHandling{ 167 | $Errors = Get-WinEvent -LogName System -FilterXPath "*[System[Provider[@Name='Microsoft-Windows-TPM-WMI'] and (EventID=1795 or EventID=1796 or EventID=1797 or EventID=1798)]]" -ErrorAction SilentlyContinue 168 | if(-not($Errors)){ 169 | Write-Log -Message 'No errors found after reboot - continue' -Component 'CVEErrorHandling' 170 | return true 171 | } 172 | else{ 173 | Write-Log -Message "Error $(($Errors | Select-Object -First 1).id) found after reboot! Please troubleshoot this manually!" -Component 'CVEErrorHandling' -Type 3 174 | return $false 175 | } 176 | } 177 | 178 | #Start coding! 179 | #ADJUST TO HOWEVER MANY PHASES WE NEED! 180 | Get-FixPhase 181 | if ($FIXMEIFDONE) { 182 | Write-Log -Message 'The bootloader is now signed with the current Windows UEFI CA 2023' -Component 'BlackLotusFixMain' 183 | Set-Location $Script:CurrentLocation 184 | #Exit 0 185 | } 186 | Write-Log -Message 'If you reached this point, it means something went wrong.' -Component 'BlackLotusFixMain' 187 | #Exit 1 -------------------------------------------------------------------------------- /Intune/Connect-SettingsCatalog-To-Group.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Will assign a given set of configuration profiles to a given group. 4 | .DESCRIPTION 5 | Needs works by looking for a prefix in the name of configuration profiles and will assign that to a given group. 6 | .NOTES 7 | Version: 0.1 8 | Versionname: 9 | Intial creation date: 04.03.2024 10 | Last change date: 04.03.2024 11 | #> 12 | [CmdletBinding()] 13 | param( 14 | [string]$ConfigProfilePrefix = 'CIS', 15 | [string]$GroupName, 16 | [System.IO.DirectoryInfo]$LogDirectory = "C:\Logs\", 17 | [string[]]$RequiredGraphScopes, 18 | [string]$CertificateThumbprint, 19 | [string]$ClientID, 20 | [string]$TenantID, 21 | [Parameter(Mandatory = $false)] 22 | [ValidateSet('/beta', '/v1.0')] 23 | [String]$TenantAPIToUse = '/beta', 24 | [switch]$ToConsole 25 | ) 26 | if (-not(Test-Path $LogDirectory)) { New-Item $LogDirectory -ItemType Directory -Force | Out-Null } 27 | $LogPrefix = 'SCToGroup' 28 | $LogFile = Join-Path -Path $LogDirectory -ChildPath ('{0}_{1}.log' -f $LogPrefix, $DateTime) 29 | function Write-Log { 30 | <# 31 | .DESCRIPTION 32 | This is a modified version of Ryan Ephgrave's script 33 | .LINK 34 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 35 | #> 36 | Param ( 37 | [Parameter(Mandatory = $false)] 38 | $Message, 39 | $Component, 40 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 41 | [ValidateSet('1', '2', '3')][int]$Type 42 | ) 43 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 44 | $Date = Get-Date -Format 'MM-dd-yyyy' 45 | if (-not($Component)) { $Component = 'Runner' } 46 | if (-not($Type)) { $Type = 1 } 47 | if (-not($ToConsole)) { 48 | $LogMessage = "" 49 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 50 | } elseif ($ToConsole) { 51 | switch ($type) { 52 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 53 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 54 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 55 | default { Write-Host "T:$Type C:$Component M:$Message" } 56 | } 57 | } 58 | } 59 | function Connect-ToGraph { 60 | param( 61 | [string]$AuthMethod 62 | ) 63 | switch -Exact ($AuthMethod) { 64 | 'SignInAuth' { 65 | $Splat = @{} 66 | if (-not([string]::IsNullOrEmpty($TenantID))) { 67 | $Splat['TenantId'] = $TenantID 68 | } 69 | $Splat['Scopes'] = $RequiredGraphScopes 70 | } 71 | 'SignInAuthCustom' { 72 | $Splat = @{ 73 | 'TenantId' = $TenantID 74 | 'ClientId' = $ClientID 75 | } 76 | } 77 | 'CertificateAuth' { 78 | $Splat = @{ 79 | 'TenantId' = $TenantID 80 | 'ClientId' = $ClientID 81 | 'CertificateThumbPrint' = $CertificateThumbprint 82 | } 83 | } 84 | 'AccessTokenAuth' { 85 | $Splat = @{ 86 | 'AccessToken' = $AccessToken 87 | } 88 | } 89 | } 90 | Connect-MgGraph @Splat 91 | $Splat = $Null 92 | $MgContext = Get-MgContext 93 | if ($null -eq $($MgContext)) { 94 | Write-Log -Message 'The connection could not be established, please verify you can connect by using Connect-MgGraph' -Component 'GFDConnectToGraph' -Type 3 95 | Return $false 96 | } 97 | if ($Null -ne ($RequiredGraphScopes | Where-Object { $_ -notin $MgContext.Scopes })) { 98 | Write-Log -Message 'The required Microsoft Graph scopes are not present in the authentication context. Please use Disconnect-MgGraph and try again' -Component 'GFDConnectToGraph' -Type 3 99 | Return $false 100 | } 101 | 102 | Return $true 103 | } 104 | function Get-nextLinkData { 105 | param( 106 | $OriginalObject 107 | ) 108 | $nextLink = $OriginalObject.'@odata.nextLink' 109 | $Results = $OriginalObject 110 | while ($nextLink) { 111 | $Request = Invoke-MgGraphRequest -Uri $nextLink 112 | $Results.value += $Request.value 113 | $nextLink = '' 114 | $nextLink = $Request.'@odata.nextLink' 115 | } 116 | return $Results 117 | } 118 | function Get-ConfigurationProfiles { 119 | 120 | } 121 | #Start Coding! 122 | $MgContext = Get-MgContext 123 | if ($null -eq $($MgContext)) { 124 | if (Connect-ToGraph -AuthMethod CertificateAuth) { 125 | Write-Log -Message 'Connection failed - please consult the logs' -Component 'CisToGroupCore' -Type 2 126 | } 127 | } else { 128 | if ($Null -ne ($RequiredGraphScopes | Where-Object { $_ -notin $MgContext.Scopes })) { 129 | Write-Log -Message 'The required Microsoft Graph scopes are not present in the authentication context. Please use Disconnect-MgGraph and try again' -Component 'CisToGroupCore' -Type 3 130 | exit 6 131 | } 132 | } 133 | $AllSettingsCatalogProfiles = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com$TenantAPIToUse/deviceManagement/configurationPolicies/?`$select=id&`$filter=startsWith(name, '$ConfigProfilePrefix')" 134 | $AllSettingsCatalogProfiles = Get-nextLinkData -OriginalObject $AllSettingsCatalogProfiles 135 | $GroupID = (Get-MgGroup -Search "displayname:$GroupName" -ConsistencyLevel eventual).id 136 | foreach ($Configuration in $AllSettingsCatalogProfiles.value) { 137 | $RequestURI = "https://graph.microsoft.com$TenantAPIToUse/deviceManagement/configurationPolicies('$($Configuration.id)')/assign" 138 | $JSONPayload = ConvertTo-Json -Depth 4 @{ 139 | "assignments" = @( 140 | @{ 141 | target = @{ 142 | "@odata.type" = "#microsoft.graph.groupAssignmentTarget" 143 | groupId = $GroupID 144 | } 145 | } 146 | ) 147 | } 148 | Invoke-MgRestMethod -Method POST -Uri $RequestURI -Body $JSONPayload 149 | } 150 | Disconnect-MgGraph -------------------------------------------------------------------------------- /Intune/IntuneNetworkRequirements/Get-IntuneNetworkRequirements.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.0 -RunAsAdministrator 2 | <# 3 | .SYNOPSIS 4 | This script will test network connections to various Intune services using PowerShell 7 5 | .DESCRIPTION 6 | Welcome to the first release of INR - Intune Network Requirements. This script will allow you to test several different service areas 7 | related to Intune. The main way this script is intended to run is not once, but at least **twice**. 8 | **Requirements 9 | * PowerShell 7 10 | * RTFM https://manima.de/2024/08/intune-network-requirements-everything-i-learned/ 11 | * Admin rights (if you want to test your currently set NTP server) 12 | 13 | Instructions: 14 | 1. Run the script on an unmanaged network, but ideally close to you or on the same provider. 15 | 2. Run the script using the same parameters on your managed network where you're experiencing issues 16 | 3. Run the script again (if necessary) to compare the two results and get a difference between the results. 17 | 18 | This is the only way to reliably verify results. It is not possible to deterministically test the endpoints because there is no 19 | documentation of which endpoint has which responses. 20 | .PARAMETER TestAllServiceAreas 21 | Specifies whether to test all target services. 22 | .PARAMETER UseMSJSON 23 | Specifies whether to use MSJSON for network requests. 24 | .PARAMETER UseMS365JSON 25 | Specifies whether to use MS365JSON for network requests. 26 | .PARAMETER CustomURLFile 27 | Recommended. Put this file next to the script. Specifies the path to the CSV file containing the URLs, ports, and protocols to test. The default value is "INRCustomList.csv". 28 | .PARAMETER AllowBestEffort 29 | Recommended. Specifies whether to allow best effort testing (will try to resolve wildcard URLs) for URLs that don't have an exact match. 30 | .PARAMETER CheckCertRevocation 31 | Recommended. Will verify if certificates that are presented by URLs are verified. 32 | .PARAMETER GCC 33 | Will test the GCC specific URLs - this related to RemoteHelp and Device Health currently. 34 | .PARAMETER Intune 35 | Specifies whether to test all of the Intune service area (this merges a lot of different areas). 36 | .PARAMETER Autopilot 37 | Specifies whether to test all of the Autopilot service area (this merges a lot of different areas). 38 | .PARAMETER WindowsActivation 39 | Specifies whether to test the Windows Activation service area. 40 | .PARAMETER EntraID 41 | Specifies whether to test the EntraID service area. 42 | .PARAMETER WindowsUpdate 43 | Specifies whether to test the Windows Update service area. 44 | .PARAMETER DeliveryOptimization 45 | Specifies whether to test the Delivery Optimization service area. 46 | .PARAMETER NTP 47 | Specifies whether to test the NTP service area. 48 | .PARAMETER DNS 49 | Warning: This is not put into the result CSV. It will be available in the log. Specifies whether to test the DNS service area. 50 | .PARAMETER DiagnosticsData 51 | Specifies whether to test the Diagnostics Data service area. 52 | .PARAMETER DiagnosticsDataUpload 53 | Specifies whether to test the Diagnostics Data Upload service area. 54 | .PARAMETER NCSI 55 | Specifies whether to test the NCSI service area. 56 | .PARAMETER WindowsNotificationService 57 | Specifies whether to test the WindowsNotificationService service area. 58 | .PARAMETER WindowsStore 59 | Specifies whether to test the Windows Store service area. 60 | .PARAMETER M365 61 | Warning: This is a _lot_ of URLs and will run for a couple minutes. Specifies whether to test the M365 service area. 62 | .PARAMETER CRLs 63 | Specifies whether to test the CRLs service area. These are the well known CRLs by Microsoft, plus my own if you import the CSV. 64 | .PARAMETER SelfDeploying 65 | Specifies whether to test the self-deploying service area. 66 | .PARAMETER RemoteHelp 67 | Specifies whether to test the Remote Help service area. 68 | .PARAMETER TPMAttestation 69 | Specifies whether to test the TPM attestation service area. 70 | .PARAMETER DeviceHealth 71 | Specifies whether to test the device health service area. 72 | .PARAMETER Apple 73 | Specifies whether to test the Apple (iOS/iPadOS) service area. 74 | .PARAMETER Android 75 | Specifies whether to test the Android (Google) service area. 76 | .PARAMETER EndpointAnalytics 77 | Specifies whether to test the Endpoint Analytics service area. 78 | .PARAMETER AppInstaller 79 | Specifies whether to test the app installer (winget) service area. 80 | .PARAMETER UniversalPrint 81 | Specifies whether to test the universal print service area. 82 | .PARAMETER AppAndScript 83 | Specifies whether to test the deployment domains for Win32, Windows script, macOS app and macOS script deployment. 84 | .PARAMETER AuthenticatedProxyOnly 85 | Will test if there is an authenticated proxy in use - does not test other service areas. 86 | .PARAMETER TestSSLInspectionOnly 87 | Will test if there is any sort of SSL inspection - does not test other service areas. 88 | .PARAMETER Legacy 89 | This is not implemented yet. Specifies whether to test legacy service. 90 | .PARAMETER TenantName 91 | This must be specified if you want some of the M365 URLS to be populated automatically. This is the first part of your first .onmicrosoft.com 92 | .PARAMETER MaxDelayInMS 93 | Default: 300ms. This is my recommended value because some addresses tend to respond slowly. 94 | .PARAMETER BurstMode 95 | Will use the MaxDelayInMs, divide it into 50ms chunks and then do a quick test. Use this to find out response times. 96 | .PARAMETER BrienMode 97 | This mode allows you to run the script multiple times in succession and automatically merge the results. This can be used to 98 | change network settings in between running the script with the same parameters. The last two results will be compared. Recommended 99 | value: 2 100 | .PARAMETER MergeResults 101 | Will trigger the result merge path. If two CSV files are in the working directory, it will merge those. Otherwise use -MergeCSVs. 102 | .PARAMETER MergeShowAllResults 103 | Will merge _all_ results not just differences. 104 | .PARAMETER MergeCSVs 105 | This will accept two CSV filesnames as strings. The files must be placed into the working directory. 106 | .PARAMETER NoLog 107 | Specifies whether to disable logging. This is a switch parameter. 108 | .PARAMETER TestMethods 109 | This is not implemented yet. This allows you to chose the test methods that the script will go through. 110 | .PARAMETER OutputCSV 111 | Output the results to a CSV file. This is not enabled by default. This is a recommended default switch. 112 | .PARAMETER ShowResults 113 | Shows the results in an Out-Gridview. 114 | .PARAMETER ToConsole 115 | Specifies whether to output log messages to the console. Enabling this won't create a log. 116 | .PARAMETER WorkingDirectory 117 | Specifies the working directory where the script will be executed. The default value is "C:\INR\". 118 | .PARAMETER LogDirectory 119 | Specifies the directory where log files will be stored. The default value is "C:\INR\". 120 | .EXAMPLE 121 | This example will use the MS-JSON for MEM, ingest the custom CSV if it exists in the same folder, allow wildcard 122 | handling in URLS, check the CRLs of each certificate provided for the ASA TPM Attestation. The script will run twice, 123 | asking you to change the network environment in between (e.g. from home to VPN), and then display all the results 124 | of each pass and the merged results of the last two results. 125 | .\Get-IntuneNetworkRequirements.ps1 -UseMSJSON -AllowBestEffort -CheckCertRevocation -ShowResults -TPMAttestation -BrienMode 2 126 | .EXAMPLE 127 | This example will use the MS-JSON for MEM, my custom CSV, allow for wildcard handling in URLS, check 128 | the CRLs of each certificate provided for the ASA TPMAttestation and then display the results in a grid, 129 | while displaying potential issues in the console for the service area TPMAttestation. 130 | .\Get-IntuneNetworkRequirements.ps1 -UseMSJSON -CustomURLFile '.\INRCustomList.csv' -AllowBestEffort -CheckCertRevocation -TPMAttestation -ShowResults -ToConsole 131 | .EXAMPLE 132 | This will ingest 2 files from the working directory and compare them. The comparison is written to another CSV file while also showing the results in a grid view. 133 | .\Get-IntuneNetworkRequirements.ps1 -MergeResults -MergeCSVs ResultList_29072024_110030_SADAME-PC.csv,ResultList_30072024_084101_3T0M4W3.csv -ShowResults 134 | .NOTES 135 | Version: 1.2.2 136 | Versionname: Brien 137 | Intial creation date: 19.02.2024 138 | Last change date: 28.12.2024 139 | Latest changes: https://github.com/MHimken/toolbox/tree/main/Autopilot/MEMNetworkRequirements/changelog.md 140 | Shoutouts: 141 | * WinAdmins Community - especially Chris for helping me figure out some of the features. 142 | * badssl.com and httpstat.us are awesome! 143 | #> 144 | [CmdletBinding(DefaultParameterSetName = 'TestMSJSON')] 145 | param( 146 | [Parameter(ParameterSetName = 'AllAreas', Position = 0)] 147 | [switch]$TestAllServiceAreas, 148 | [Parameter(ParameterSetName = 'AllAreas')] 149 | [Parameter(ParameterSetName = 'TestMSJSON', Position = 0)] 150 | [switch]$UseMSJSON, 151 | [Parameter(ParameterSetName = 'AllAreas')] 152 | [Parameter(ParameterSetName = 'TestMS365JSON', Position = 0)] 153 | [switch]$UseMS365JSON, 154 | [Parameter(ParameterSetName = 'AllAreas')] 155 | [Parameter(ParameterSetName = 'TestMSJSON')] 156 | [Parameter(ParameterSetName = 'TestMS365JSON')] 157 | [Parameter(ParameterSetName = 'TestCustom', Position = 0)] 158 | [string]$CustomURLFile, 159 | [Parameter(ParameterSetName = 'AllAreas')] 160 | [Parameter(ParameterSetName = 'TestMSJSON', Position = 1)] 161 | [Parameter(ParameterSetName = 'TestMS365JSON')] 162 | [Parameter(ParameterSetName = 'TestCustom')] 163 | [switch]$AllowBestEffort, 164 | [Parameter(ParameterSetName = 'AllAreas')] 165 | [Parameter(ParameterSetName = 'TestMSJSON', Position = 2)] 166 | [Parameter(ParameterSetName = 'TestMS365JSON')] 167 | [Parameter(ParameterSetName = 'TestCustom')] 168 | [switch]$CheckCertRevocation, 169 | [Parameter(ParameterSetName = 'AllAreas')] 170 | [Parameter(ParameterSetName = 'TestMSJSON')] 171 | [Parameter(ParameterSetName = 'TestCustom')] 172 | [switch]$GCC, 173 | #Service Areas 174 | [Parameter(ParameterSetName = 'TestMSJSON')] 175 | [Parameter(ParameterSetName = 'TestCustom')] 176 | [switch]$Intune, 177 | [Parameter(ParameterSetName = 'TestMSJSON')] 178 | [Parameter(ParameterSetName = 'TestCustom')] 179 | [switch]$Autopilot, 180 | [Parameter(ParameterSetName = 'TestMSJSON')] 181 | [Parameter(ParameterSetName = 'TestCustom')] 182 | [switch]$WindowsActivation, 183 | [Parameter(ParameterSetName = 'TestMSJSON')] 184 | [Parameter(ParameterSetName = 'TestCustom')] 185 | [switch]$EntraID, 186 | [Parameter(ParameterSetName = 'TestMSJSON')] 187 | [Parameter(ParameterSetName = 'TestCustom')] 188 | [switch]$WindowsUpdate, 189 | [Parameter(ParameterSetName = 'TestMSJSON')] 190 | [Parameter(ParameterSetName = 'TestCustom')] 191 | [switch]$DeliveryOptimization, 192 | [Parameter(ParameterSetName = 'TestMSJSON')] 193 | [Parameter(ParameterSetName = 'TestCustom')] 194 | [switch]$NTP, 195 | [Parameter(ParameterSetName = 'TestMSJSON')] 196 | [Parameter(ParameterSetName = 'TestCustom')] 197 | [switch]$DNS, 198 | [Parameter(ParameterSetName = 'TestMSJSON')] 199 | [Parameter(ParameterSetName = 'TestCustom')] 200 | [switch]$DiagnosticsData, 201 | [Parameter(ParameterSetName = 'TestMSJSON')] 202 | [Parameter(ParameterSetName = 'TestCustom')] 203 | [switch]$DiagnosticsDataUpload, 204 | [Parameter(ParameterSetName = 'TestMSJSON')] 205 | [Parameter(ParameterSetName = 'TestCustom')] 206 | [switch]$NCSI, 207 | [Parameter(ParameterSetName = 'TestMSJSON')] 208 | [Parameter(ParameterSetName = 'TestCustom')] 209 | [switch]$WindowsNotificationService, 210 | [Parameter(ParameterSetName = 'TestMSJSON')] 211 | [Parameter(ParameterSetName = 'TestCustom')] 212 | [switch]$WindowsStore, 213 | [Parameter(ParameterSetName = 'TestMSJSON')] 214 | [Parameter(ParameterSetName = 'TestMS365JSON')] 215 | [Parameter(ParameterSetName = 'TestCustom')] 216 | [switch]$M365, 217 | [Parameter(ParameterSetName = 'TestMSJSON')] 218 | [Parameter(ParameterSetName = 'TestCustom')] 219 | [switch]$CRLs, 220 | [Parameter(ParameterSetName = 'TestMSJSON')] 221 | [Parameter(ParameterSetName = 'TestCustom')] 222 | [switch]$SelfDeploying, 223 | [Parameter(ParameterSetName = 'TestMSJSON')] 224 | [Parameter(ParameterSetName = 'TestCustom')] 225 | [switch]$RemoteHelp, 226 | [Parameter(ParameterSetName = 'TestMSJSON')] 227 | [Parameter(ParameterSetName = 'TestCustom')] 228 | [switch]$TPMAttestation, 229 | [Parameter(ParameterSetName = 'TestMSJSON')] 230 | [Parameter(ParameterSetName = 'TestCustom')] 231 | [switch]$DeviceHealth, 232 | [Parameter(ParameterSetName = 'TestMSJSON')] 233 | [Parameter(ParameterSetName = 'TestCustom')] 234 | [switch]$Apple, 235 | [Parameter(ParameterSetName = 'TestMSJSON')] 236 | [Parameter(ParameterSetName = 'TestCustom')] 237 | [switch]$Android, 238 | [Parameter(ParameterSetName = 'TestMSJSON')] 239 | [Parameter(ParameterSetName = 'TestCustom')] 240 | [switch]$EndpointAnalytics, 241 | [Parameter(ParameterSetName = 'TestMSJSON')] 242 | [Parameter(ParameterSetName = 'TestCustom')] 243 | [switch]$AppInstaller, 244 | [Parameter(ParameterSetName = 'TestMSJSON')] 245 | [Parameter(ParameterSetName = 'TestCustom')] 246 | [switch]$UniversalPrint, 247 | [Parameter(ParameterSetName = 'TestMSJSON')] 248 | [Parameter(ParameterSetName = 'TestCustom')] 249 | [switch]$AppAndScript, 250 | 251 | #Not Service area specific 252 | [Parameter(ParameterSetName = 'TestMSJSON')] 253 | [Parameter(ParameterSetName = 'TestCustom')] 254 | [switch]$AuthenticatedProxyOnly, 255 | [Parameter(ParameterSetName = 'TestMSJSON')] 256 | [Parameter(ParameterSetName = 'TestCustom')] 257 | [switch]$TestSSLInspectionOnly, 258 | [Parameter(ParameterSetName = 'TestMSJSON')] 259 | [Parameter(ParameterSetName = 'TestCustom')] 260 | [switch]$Legacy, 261 | 262 | #Special Methods 263 | [Parameter(ParameterSetName = 'AllAreas', Mandatory)] 264 | [Parameter(ParameterSetName = 'TestMS365JSON', Mandatory)] 265 | [string]$TenantName, 266 | [Parameter(ParameterSetName = 'AllAreas')] 267 | [Parameter(ParameterSetName = 'TestMSJSON')] 268 | [Parameter(ParameterSetName = 'TestMS365JSON')] 269 | [Parameter(ParameterSetName = 'TestCustom')] 270 | [int]$MaxDelayInMS = 300, #300 is recommended due to some Microsoft services being heavy load (like MS Update) 271 | [Parameter(ParameterSetName = 'TestMSJSON')] 272 | [Parameter(ParameterSetName = 'TestMS365JSON')] 273 | [Parameter(ParameterSetName = 'TestCustom')] 274 | [switch]$BurstMode, #Divide the delay by 50 and try different speeds. Give warning when more than 10 URLs are tested 275 | [Parameter(ParameterSetName = 'TestMSJSON')] 276 | [Parameter(ParameterSetName = 'TestMS365JSON')] 277 | [Parameter(ParameterSetName = 'TestCustom')] 278 | [int]$BrienMode, 279 | 280 | #Merge options 281 | [Parameter(ParameterSetName = 'Merge', Position = 0)] 282 | [Parameter(ParameterSetName = 'TestMSJSON')] 283 | [Parameter(ParameterSetName = 'TestMS365JSON')] 284 | [Parameter(ParameterSetName = 'TestCustom')] 285 | [switch]$MergeResults, 286 | [Parameter(ParameterSetName = 'Merge')] 287 | [Parameter(ParameterSetName = 'TestMSJSON')] 288 | [Parameter(ParameterSetName = 'TestMS365JSON')] 289 | [Parameter(ParameterSetName = 'TestCustom')] 290 | [switch]$MergeShowAllResults, 291 | [Parameter(ParameterSetName = 'Merge')] 292 | [string[]]$MergeCSVs, 293 | 294 | #Output options 295 | [Parameter(ParameterSetName = 'AllAreas')] 296 | [Parameter(ParameterSetName = 'TestMSJSON')] 297 | [Parameter(ParameterSetName = 'TestMS365JSON')] 298 | [Parameter(ParameterSetName = 'TestCustom')] 299 | [Parameter(ParameterSetName = 'Merge')] 300 | [switch]$OutputCSV, 301 | [Parameter(ParameterSetName = 'AllAreas')] 302 | [Parameter(ParameterSetName = 'TestMSJSON')] 303 | [Parameter(ParameterSetName = 'TestMS365JSON')] 304 | [Parameter(ParameterSetName = 'TestCustom')] 305 | [Parameter(ParameterSetName = 'Merge')] 306 | [switch]$ShowResults, 307 | #Common parameters 308 | [switch]$NoLog, 309 | [switch]$ToConsole, 310 | [System.IO.DirectoryInfo]$WorkingDirectory = "C:\INR\", 311 | [System.IO.DirectoryInfo]$LogDirectory = "C:\INR\" 312 | ) 313 | 314 | #Preparation 315 | function Get-ScriptPath { 316 | <# 317 | .SYNOPSIS 318 | Get the current script path. 319 | #> 320 | if ($PSScriptRoot) { 321 | # Console or VS Code debug/run button/F5 temp console 322 | $ScriptRoot = $PSScriptRoot 323 | } else { 324 | if ($psISE) { 325 | Split-Path -Path $psISE.CurrentFile.FullPath 326 | } else { 327 | if ($profile -match 'VScode') { 328 | # VS Code "Run Code Selection" button/F8 in integrated console 329 | $ScriptRoot = Split-Path $psEditor.GetEditorContext().CurrentFile.Path 330 | } else { 331 | Write-Output 'unknown directory to set path variable. exiting script.' 332 | exit 333 | } 334 | } 335 | } 336 | $Script:PathToScript = $ScriptRoot 337 | } 338 | function Initialize-Script { 339 | <# 340 | .SYNOPSIS 341 | Will initialize most of the required variables throughout this script. 342 | #> 343 | $Script:DateTime = Get-Date -Format yyyyMMdd_HHmmss 344 | if (-not($Script:CurrentLocation)) { 345 | $Script:CurrentLocation = Get-Location 346 | } 347 | if (-not(Test-Path $WorkingDirectory )) { New-Item $WorkingDirectory -ItemType Directory -Force | Out-Null } 348 | if ((Get-Location).path -ne $WorkingDirectory) { 349 | Set-Location $WorkingDirectory 350 | } 351 | Get-ScriptPath 352 | if (-not($Script:LogFile)) { 353 | $LogPrefix = 'INR' 354 | $Script:LogFile = Join-Path -Path $LogDirectory -ChildPath ('{0}_{1}.log' -f $LogPrefix, $Script:DateTime) 355 | if (-not(Test-Path $LogDirectory)) { New-Item $LogDirectory -ItemType Directory -Force | Out-Null } 356 | } 357 | if ($PSVersionTable.psversion.major -lt 7) { 358 | Write-Log -Message 'Please follow the manual - PowerShell 7 is currently required to run this script.' -Component 'InitializeScript' -Type 3 359 | Exit 1 360 | } 361 | $Script:GUID = (New-Guid).Guid 362 | $Script:M365ServiceURLs = [System.Collections.ArrayList]::new() 363 | $Script:WildCardURLs = [System.Collections.ArrayList]::new() 364 | $Script:CRLURLsToCheck = [System.Collections.ArrayList]::new() 365 | $Script:URLsToVerify = [System.Collections.ArrayList]::new() 366 | $Script:DNSCache = [System.Collections.ArrayList]::new() 367 | $Script:TCPCache = [System.Collections.ArrayList]::new() 368 | if ($Script:FinalResultList) { 369 | Get-Variable FinalResultList | Clear-Variable 370 | } 371 | $Script:FinalResultList = [System.Collections.ArrayList]::new() 372 | $Script:ExternalIP = (ConvertFrom-Json (Invoke-WebRequest "https://geo-prod.do.dsp.mp.microsoft.com/geo")).ExternalIpAddress 373 | Write-Log -Message "External IP: $($Script:ExternalIP)" -Component 'InitializeScript' 374 | Import-CustomURLFile 375 | if ($UseMSJSON) { 376 | Get-M365Service -MEM 377 | } 378 | if ($UseMS365JSON) { 379 | Get-M365Service -M365 380 | } 381 | if (-not($Script:M365ServiceURLs) -and -not($Script:ManualURLs)) { 382 | Write-Log 'No domains have been imported, please specify -UseMSJSON, -UseMS365JSON or -CustomURLFile' -Component 'InitializeScript' -Type 3 383 | exit 5 384 | } 385 | } 386 | function Write-Log { 387 | <# 388 | .DESCRIPTION 389 | This is a modified version of the script by Ryan Ephgrave. 390 | .LINK 391 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 392 | #> 393 | Param ( 394 | [Parameter(Mandatory = $false)] 395 | $Message, 396 | $Component, 397 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 398 | [ValidateSet('1', '2', '3')][int]$Type 399 | ) 400 | if (-not($NoLog)) { 401 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 402 | $Date = Get-Date -Format 'MM-dd-yyyy' 403 | if (-not($Component)) { $Component = 'Runner' } 404 | if (-not($ToConsole)) { 405 | $LogMessage = "" 406 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 407 | } elseif ($ToConsole) { 408 | switch ($type) { 409 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 410 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 411 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 412 | default { Write-Host "T:$Type C:$Component M:$Message" } 413 | } 414 | } 415 | } 416 | } 417 | function Write-SettingsToLog { 418 | if (-not($MergeResults)) { 419 | Write-Log "Settings used to run the script: 420 | General settings 421 | TestAllServiceAreas: $TestAllServiceAreas 422 | UseMSJSON: $UseMSJSON 423 | UseMS365JSON: $UseMS365JSON 424 | CustomURLFile: $CustomURLFile 425 | AllowBestEffort: $AllowBestEffort 426 | CheckCertRevocation: $CheckCertRevocation 427 | GCC: $GCC 428 | 429 | ASAs 430 | Intune: $Intune 431 | Autopilot: $Autopilot 432 | WindowsActivation: $WindowsActivation 433 | EntraID: $EntraID 434 | WindowsUpdate: $WindowsUpdate 435 | DeliveryOptimization: $DeliveryOptimization 436 | NTP: $NTP 437 | DNS: $DNS 438 | DiagnosticsData: $DiagnosticsData 439 | DiagnosticsDataUpload: $DiagnosticsDataUpload 440 | NCSI: $NCSI 441 | WindowsNotificationService: $WindowsNotificationService 442 | WindowsStore: $WindowsStore 443 | M365: $M365 444 | CRLs: $CRLs 445 | SelfDeploying: $SelfDeploying 446 | RemoteHelp: $RemoteHelp 447 | TPMAttestation: $TPMAttestation 448 | DeviceHealth: $DeviceHealth 449 | Apple: $Apple 450 | Android: $Android 451 | EndpointAnalytics: $EndpointAnalytics 452 | AppInstaller: $AppInstaller 453 | 454 | Other tests 455 | AuthenticatedProxyOnly: $AuthenticatedProxyOnly 456 | TestSSLInspectionOnly: $TestSSLInspectionOnly 457 | Legacy: $Legacy 458 | 459 | Additional Settings 460 | TenantName: $TenantName 461 | MaxDelayInMS: $MaxDelayInMS 462 | BurstMode: $BurstMode" -Component 'InitialzeScript' 463 | } else { 464 | Write-Log "Settings used to run the script: 465 | Merge options 466 | MergeResults: $MergeResults 467 | MergeShowAllResults: $MergeShowAllResults 468 | MergeCSVs: $MergeCSVs" -Component 'InitialzeScript' 469 | } 470 | Write-Log "Output options 471 | OutputCSV: $OutputCSV 472 | ShowResults: $ShowResults 473 | 474 | Common parameters 475 | NoLog: $NoLog 476 | ToConsole: $ToConsole 477 | WorkingDirectory: $WorkingDirectory 478 | LogDirectory: $LogDirectory" -Component 'InitialzeScript' 479 | } 480 | function Import-CustomURLFile { 481 | <# 482 | .SYNOPSIS 483 | Imports URLs from a custom CSV file. Automatically uses 'INRCustomList.csv' if no filename is specified. 484 | #> 485 | if (-not($CustomURLFile)) { 486 | Write-Log 'No CSV provided - trying autodetect for filename ' -Component 'ImportCustomURLFile' 487 | $DefaultCSVName = "INRCustomList.csv" 488 | $JoinedDefaultCSVPath = Join-Path $Script:PathToScript -ChildPath $DefaultCSVName 489 | if (Test-Path $JoinedDefaultCSVPath) { 490 | Write-Log "CSV found in $($Script:PathToScript)" -Component 'ImportCustomURLFile' 491 | $CustomURLFile = $DefaultCSVName 492 | } else { 493 | Write-Log 'Autodetection did not find a custom CSV file' -Component 'ImportCustomURLFile' 494 | return 495 | } 496 | } 497 | Write-Log 'Adding custom URLs to the pool' -Component 'ImportCustomURLFile' 498 | $Header = 'URL', 'Port', 'Protocol', 'ID' 499 | $Script:ManualURLs = [System.Collections.ArrayList]::new() 500 | $TempObjects = Import-Csv -Path (Join-Path -Path $Script:PathToScript -ChildPath $CustomURLFile) -Delimiter ',' -Header $Header 501 | foreach ($Object in $TempObjects) { 502 | $URLObject = [PSCustomObject]@{ 503 | id = $Object.ID 504 | #serviceArea = $Object.serviceArea 505 | #serviceAreaDisplayName = $Object.serviceAreaDisplayName 506 | url = $Object.url.replace('*.', '') 507 | Port = $Object.port 508 | Protocol = $Object.protocol 509 | #expressRoute = $Object.expressRoute 510 | #category = $Object.category 511 | required = 'true' 512 | #notes = $Object.notes 513 | } 514 | $Script:ManualURLs.add($URLObject) | Out-Null 515 | } 516 | } 517 | function Get-URLsFromID { 518 | <# 519 | .SYNOPSIS 520 | Will put the URLs for different service areas into one big arraylist. 521 | #> 522 | param( 523 | [int[]]$IDs, 524 | [int[]]$FilterPort 525 | ) 526 | if ($Script:URLsToVerify) { 527 | Get-Variable URLsToVerify | Clear-Variable 528 | $Script:URLsToVerify = [System.Collections.ArrayList]::new() 529 | } 530 | foreach ($ID in $IDs) { 531 | if ($Script:ManualURLs) { 532 | $Script:ManualURLs | Where-Object { $_.id -eq $ID -and $_.port -notin $FilterPort } | ForEach-Object { $Script:URLsToVerify.Add($_) | Out-Null } 533 | } 534 | if ($Script:M365ServiceURLs) { 535 | $Script:M365ServiceURLs | Where-Object { $_.id -eq $ID -and $_.port -notin $FilterPort } | ForEach-Object { $Script:URLsToVerify.Add($_) | Out-Null } 536 | } 537 | if (-not($Script:URLsToVerify)) { 538 | return $false 539 | } 540 | $DuplicateURLsToVerify = [System.Collections.ArrayList]::new() 541 | foreach ($IDsFound in $Script:URLsToVerify) { 542 | $RemoveMe = $Script:URLsToVerify | Where-Object { $_.id -eq $IDsFound.id -and $_.url -eq $IDsFound.url -and $_.port -eq $IDsFound.port -and $_.protocol -eq $IDsFound.protocol } 543 | if ($RemoveMe.count -gt 1) { 544 | $counter = 0 545 | foreach ($RemoveObject in $RemoveMe) { 546 | if ($counter -gt 0) { 547 | $DuplicateURLsToVerify.add($RemoveObject) | Out-Null 548 | } 549 | $counter++ 550 | } 551 | } 552 | } 553 | $DuplicateURLsToVerify | ForEach-Object { $Script:URLsToVerify.Remove($_) } 554 | } 555 | return $true 556 | } 557 | 558 | #Import M365 Service-URLs 559 | function Find-WildcardURL { 560 | <# 561 | .SYNOPSIS 562 | Will resolve wildcards to actual URLs. If AllowBestEffort is set might also remove the wildcards from URLs if they can't be matched otherwise 563 | #> 564 | Write-Log -Message 'Now searching for nearest match for Wildcards' -Component 'FindWildcardURL' 565 | foreach ($Object in $Script:WildCardURLs) { 566 | Write-Log -Message "Searching for $($Object.url)" -Component 'FindWildcardURL' 567 | if ($($Script:M365ServiceURLs | Where-Object { $_.url -like "*$($Object.url.replace('*.',''))*" })) { 568 | continue 569 | } 570 | $ReplaceElement = $Object.url.split('.')[0] 571 | if ($ReplaceElement -ne '*') { 572 | $WildcardReplacement = $ReplaceElement.replace('*', 'INR') 573 | $NewURL = $Object.url.replace($ReplaceElement, $WildcardReplacement) 574 | } else { 575 | $NewURL = $Object.url.replace('*.', '') 576 | } 577 | 578 | Write-Log -Message 'We did not find a matching URL using best effort (no wildcard)' -Component 'FindWildcardURL' 579 | $URLObject = [PSCustomObject]@{ 580 | id = $Object.ID 581 | #serviceArea = $Object.serviceArea 582 | #serviceAreaDisplayName = $Object.serviceAreaDisplayName 583 | url = $NewURL 584 | Port = $Object.port 585 | Protocol = $Object.protocol 586 | #expressRoute = $Object.expressRoute 587 | #category = $Object.category 588 | required = $Object.required 589 | #notes = $Object.notes 590 | } 591 | 592 | $Script:M365ServiceURLs.Add($URLObject) | Out-Null 593 | } 594 | } 595 | function Get-M365Service { 596 | <# 597 | .SYNOPSIS 598 | Will grab M365 and MEM JSONs from Microsoft using a random GUID 599 | #> 600 | param( 601 | [switch]$M365, 602 | [switch]$MEM 603 | ) 604 | $EndpointURL = "https://endpoints.office.com" 605 | #if (Test-HTTP -URL $EndpointURL) { 606 | if ($M365) { 607 | Write-Log 'Adding Microsoft URLs to the pool from service area M365' -Component 'GetM365URLs' 608 | if ($Tenantname) { $URLs = Invoke-RestMethod -Uri ("$EndpointURL/endpoints/WorldWide?clientrequestid=$Script:GUID&TenantName=$Tenantname") } 609 | else { $URLs = Invoke-RestMethod -Uri ("$EndpointURL/endpoints/WorldWide?clientrequestid=$Script:GUID") } 610 | } 611 | if ($MEM) { 612 | Write-Log 'Adding Microsoft URLs to the pool from service area MEM' -Component 'GetM365URLs' 613 | $URLs = Invoke-RestMethod -Uri ("$EndpointURL/endpoints/WorldWide?ServiceAreas=MEM`&`clientrequestid=$Script:GUID") 614 | } 615 | foreach ($Object in $URLs) { 616 | $Ports = [array](($(if ($Object.tcpPorts) { $Object.tcpPorts }elseif ($Object.udpPorts) { $Object.udpPorts }else { '443' })).split(",").trim()) 617 | $Protocol = $(if ($Object.tcpPorts) { 'TCP' } elseif ($Object.udpPorts) { 'UDP' } else { 'TCP' }) 618 | foreach ($URL in $Object.urls) { 619 | foreach ($Port in $Ports) { 620 | $URLObject = [PSCustomObject]@{ 621 | id = $Object.id 622 | #serviceArea = $Object.serviceArea 623 | #serviceAreaDisplayName = $Object.serviceAreaDisplayName 624 | url = $URL 625 | Port = $Port 626 | Protocol = $Protocol 627 | #expressRoute = $Object.expressroute 628 | #category = $Object.category 629 | required = $Object.required 630 | #notes = $Object.notes 631 | } 632 | if ($URL -match '\*') { 633 | Write-Log -Message "The URI $URL contains a wildcard - trying to find nearest match later" -Component 'GetM365Service' 634 | if ($URL -in $Script:WildCardURLs.url) { 635 | continue 636 | } 637 | $Script:WildCardURLs.add($URLObject) | Out-Null 638 | continue 639 | } 640 | $Script:M365ServiceURLs.Add($URLObject) | Out-Null 641 | } 642 | } 643 | } 644 | if ($AllowBestEffort) { 645 | Write-Log -Message 'Best effort URLs are enabled - this will turn wildcards into regular URLs using a best effort method' -Component 'IntuneNetworkCheckMain' 646 | Find-WildcardURL 647 | } 648 | } 649 | 650 | #Test Functions 651 | function Test-SSLInspectionByKnownCRLs { 652 | <# 653 | .SYNOPSIS 654 | Verify CRL against known good - this is an indicator for SSLInspection 655 | #> 656 | param( 657 | [string]$CRLURL, 658 | [string]$VerifyAgainstKnownGood 659 | ) 660 | $KnownCRL = $false 661 | if (-not($Script:CRLURLsToCheck)) { 662 | if ($Script:ManualURLs) { 663 | $Script:ManualURLs | Where-Object { $_.id -in "125", "84", "9993" } | ForEach-Object { $Script:CRLURLsToCheck.add($_) | Out-Null } 664 | } 665 | if ($Script:M365ServiceURLs) { 666 | $Script:M365ServiceURLs | Where-Object { $_.id -in "125", "84", "9993" } | ForEach-Object { $Script:CRLURLsToCheck.add($_) | Out-Null } 667 | } 668 | } 669 | foreach ($URL in $Script:CRLURLsToCheck.url) { 670 | if ($CRLURL -like $("*" + $URL + "*") -or $VerifyAgainstKnownGood -eq $URL) { 671 | $KnownCRL = $true 672 | } 673 | } 674 | return $KnownCRL 675 | } 676 | function Test-SSL { 677 | <# 678 | .SYNOPSIS 679 | Opens a TCP connection to a URL and attempts to secure it using the strongest encryption method available. 680 | .NOTES 681 | Initial idea: https://learn.microsoft.com/en-us/troubleshoot/azure/azure-monitor/log-analytics/windows-agents/ssl-connectivity-mma-windows-powershell 682 | #> 683 | param( 684 | $SSLTarget, 685 | $SSLPort = 443 686 | ) 687 | $TCPSocket = New-Object Net.Sockets.TcpClient($SSLTarget, $SSLPort) 688 | $SSLStream = New-Object -TypeName Net.Security.SslStream($TCPSocket.GetStream(), $false) 689 | try { 690 | $SSLStream.AuthenticateAsClient( 691 | $SSLTarget, #targetHost 692 | $null, #clientCertificates (Collection) 693 | $true #checkCertificateRevocation 694 | ) 695 | } catch [System.Security.Authentication.AuthenticationException] { 696 | #THIS ONLY WORKS IN PS7!! 697 | if ($PSVersionTable.psversion.major -ge 7) { 698 | switch -Wildcard ($Error[0].Exception.InnerException.Message) { 699 | "Cannot determine the frame size or a corrupted frame was received." { $AuthException = "FrameSizeOrCorrupted" } 700 | "*TLS alert: 'HandshakeFailure'." { $AuthException = "HandshakeFailure" } 701 | "*validation procedure: RemoteCertificateNameMismatch" { $AuthException = "RemoteCertificateNameMismatch" } 702 | default { $AuthException = $Error[0].Exception.InnerException.Message.Split(':')[1].Trim() } 703 | } 704 | } else { 705 | Write-Log -Message 'Cannot determine exact certificate failure, defaults to "failed"' -Component 'TestSSL' -Type 2 706 | $AuthException = "Failed" 707 | } 708 | } catch [System.Management.Automation.MethodInvocationException] { 709 | Write-Log -Message 'The TCP socket closed unexpectedly. This could be random, repeat this test.' -Type 2 -Component 'TestSSL' 710 | } 711 | if ($SSLStream.IsAuthenticated) { 712 | $SSLTest = $true 713 | $CertInfo = New-Object -TypeName Security.Cryptography.X509Certificates.X509Certificate2($SSLStream.RemoteCertificate) 714 | if ($CertInfo.Thumbprint -and $CheckCertRevocation) { 715 | Write-Log -Message "Grabbing CRL for $SSLTarget and verify against known-good" -Component 'TestSSL' 716 | $CRLURIarray = $CertInfo.Extensions | Where-Object -FilterScript { $_.Oid.Value -eq '2.5.29.31' } | ForEach-Object -Process { $_.Oid.FriendlyName; $_.Format($true) } 717 | $SSLInspectionResult = $false 718 | $KnownCRL = $false 719 | if (-not($CRLURIarray)) { 720 | Write-Log "No CRL detected - SSL inspection is likely. Testing if tested URL $SSLTarget is a known address CRL itself" -Component 'TestSSL' -Type 2 721 | $VerifyAgainstKnownGoodResult = Test-SSLInspectionByKnownCRLs -VerifyAgainstKnownGood $SSLTarget 722 | if ($VerifyAgainstKnownGoodResult) { 723 | Write-Log "$SSLTarget is a known good CRL address" -Component 'TestSSL' -Type 2 724 | $KnownCRL = $true 725 | } 726 | if (-not($VerifyAgainstKnownGoodResult)) { 727 | Write-Log "SSL Inspection very likely. $SSLTarget is not a known CRL address" -Component 'TestSSL' -Type 2 728 | $SSLInspectionResult = $true 729 | } 730 | } elseif ($CRLURIarray[1].split('[').count -ge 2) { 731 | if ($CRLURIarray[1].split('[').count -eq 2) { 732 | $CRLURI = $CRLURIarray[1].Split('http://')[1].split('/')[0] 733 | $KnownCRL = Test-SSLInspectionByKnownCRLs -CRLURL $CRLURI 734 | } elseif ($CRLURIarray[1].split('[').count -gt 2) { 735 | $TestMultipleCRLs = $CRLURIarray[1].split('=').split('[').trim() | Where-Object { $_.startswith("http://") } | ForEach-Object { Test-SSLInspectionByKnownCRLs -CRLURL $_.Split('http://')[1].split('/')[0] } | Where-Object { $_ -contains $true } 736 | if ($TestMultipleCRLs) { $KnownCRL = $true } 737 | } 738 | if (-not($KnownCRL)) { 739 | Write-Log "Unknown CRL. $SSLTarget's certificate didn't provide any known CRL address" -Component 'TestSSL' -Type 2 740 | $SSLInspectionResult = $true 741 | } 742 | } 743 | } 744 | } 745 | $TCPSocket.Close() 746 | $TCPSocket.Dispose() 747 | if ($null -eq $SSLTest) { 748 | $SSLTest = $false 749 | } 750 | $Result = [PSCustomObject]@{ 751 | SSLTest = $SSLTest 752 | SSLProtocol = $SSLStream.SslProtocol 753 | Issuer = $CertInfo.Issuer 754 | AuthException = $AuthException 755 | KnownCRL = $KnownCRL 756 | SSLInterception = $SSLInspectionResult 757 | } 758 | return $Result 759 | } 760 | function Test-HTTP { 761 | <# 762 | .SYNOPSIS 763 | Checks for HTTP(s) return codes. 403 and 401 can be indicators of (authenticated) proxy interruption. 764 | #> 765 | param( 766 | [string]$HTTPURL, 767 | [int]$HTTPPort 768 | ) 769 | switch ($HTTPPort) { 770 | "80" { $URIStart = "http://" } 771 | "443" { $URIStart = "https://" } 772 | default { $URIStart = "https://" } 773 | } 774 | try { 775 | #MARKED FOR CLEANUP. Reason: PS5 only code. 776 | #We could use -SkipHttpErrorCheck in PS7... 777 | if ($PSVersionTable.psversion.major -ge 7) { 778 | $HTTPiwr = Invoke-WebRequest -Uri $($URIStart + $HTTPURL) -ConnectionTimeoutSeconds $(($MaxDelayInMS / 100)) -Method Get -SkipHttpErrorCheck 779 | } else { 780 | $HTTPiwr = Invoke-WebRequest -Uri $($URIStart + $HTTPURL) -ConnectionTimeoutSeconds $(($MaxDelayInMS / 100)) -Method Get 781 | } 782 | return $HTTPiwr.StatusCode 783 | } catch { 784 | #This is only reached if PS5 is used 785 | $Statuscode = $error[0].Exception.Response.StatusCode.value__ 786 | if ($Statuscode) { 787 | return $Statuscode 788 | } 789 | return $false 790 | } 791 | } 792 | function Test-DNS { 793 | <# 794 | .SYNOPSIS 795 | Verifies that DNS is working for a given URL and that the IP does not resolve a sinkhole (0.0.0.0 or 127.0.0.1 or ::). 796 | #> 797 | param( 798 | [string]$DNSTarget 799 | ) 800 | $DNSresult = $true 801 | if ($DNSTarget -in $Script:DNSCache.CachedURL) { 802 | $CachedResult = $Script:DNSCache | Where-Object { $_.CachedURL -eq $DNSTarget } | Select-Object -Property result 803 | return $CachedResult.result 804 | } 805 | $ResolvedDNSRecords = Resolve-DnsName -Name $DNSTarget -ErrorAction SilentlyContinue 806 | if ($ResolvedDNSRecords.count) { 807 | foreach ($DNSARecord in $ResolvedDNSRecords.IP4Address) { 808 | if ($DNSARecord.IP4Address) { 809 | if ($DNSARecord -eq '0.0.0.0' -or $DNSARecord -eq '127.0.0.1') { 810 | Write-Log -Message "DNS sinkhole detected: Address $DNSTarget resolved to an invalid address" -Component 'TestDNS' -Type 2 811 | $DNSresult = $false 812 | break 813 | } 814 | } 815 | } 816 | foreach ($DNSAAAARecord in $ResolvedDNSRecords.IP6Address) { 817 | if ($DNSAAAARecord -eq '::') { 818 | Write-Log -Message "DNS sinkhole detected: Address $DNSTarget resolved to an invalid address" -Component 'TestDNS' -Type 2 819 | $DNSresult = $false 820 | break 821 | } 822 | } 823 | } else { 824 | Write-Log -Message "No DNS record found for $DNSTarget" -Component 'TestDNS' -Type 3 825 | $DNSresult = $false 826 | } 827 | $DNSObject = [PSCustomObject]@{ 828 | CachedURL = $DNSTarget 829 | Result = $DNSresult 830 | } 831 | $Script:DNSCache.add($DNSObject) | Out-Null 832 | return $DNSresult 833 | } 834 | function Test-TCPPort { 835 | <# 836 | .SYNOPSIS 837 | Opens a TCP connection to a given URL. 838 | #> 839 | param( 840 | [string]$TCPTarget, 841 | [int]$TCPPort, 842 | [int]$MaxWaitTime 843 | ) 844 | if (-not($MaxWaitTime)) { 845 | $MaxWaitTime = $MaxDelayInMS 846 | } 847 | if ($TCPTarget -in $Script:DNSCache.CachedURL) { 848 | $DNSCachedResult = $Script:DNSCache | Where-Object { $_.CachedURL -eq $TCPTarget } | Select-Object -Property result -First 1 849 | if (-not($DNSCachedResult.result)) { 850 | return $false 851 | } 852 | } 853 | if (-not($BurstMode)) { 854 | if ($TCPTarget -in $Script:TCPCache.CachedURL -and $TCPPort -in ($Script:TCPCache.Port | Where-Object { $_.CachedURL -eq $TCPTarget })) { 855 | $TCPCachedResult = $Script:TCPCache | Where-Object { $_.CachedURL -eq $TCPTarget } | Select-Object -Property result -First 1 856 | if (-not($TCPCachedResult.result)) { 857 | return $false 858 | } 859 | } 860 | } 861 | $TCPClient = New-Object -TypeName System.Net.Sockets.TCPClient 862 | $RunningClient = $TCPClient.ConnectAsync($TCPTarget, $TCPPort) 863 | Start-Sleep -Milliseconds $MaxWaitTime 864 | $success = $false 865 | if ($RunningClient.IsCompleted) { 866 | if ($RunningClient.Status -ne 'RanToCompletion') { 867 | Write-Log "TCP Port test failed with $($RunningClient.Status)" -Component 'TestTCPPort' -Type 2 868 | if ($RunningClient.Exception.InnerException) { 869 | Write-Log "$($RunningClient.Exception.InnerException.Message)" -Component 'TestTCPPort' 870 | } 871 | } else { 872 | $success = $true 873 | } 874 | } else { 875 | Write-Log 'TCP Port did not respond in a timely fashion' -Component 'TestTCPPort' -Type 2 876 | } 877 | $TCPClient.Close() 878 | $TCPClient.Dispose() 879 | if (-not($BurstMode)) { 880 | $TCPObject = [PSCustomObject]@{ 881 | CachedURL = $TCPTarget 882 | Port = $TCPPort 883 | Result = $result 884 | } 885 | $Script:TCPCache.add($TCPObject) | Out-Null 886 | } 887 | return $success 888 | } 889 | function Test-NTPviaUDP { 890 | <# 891 | .SYNOPSIS 892 | Will test an NTP server using UDP. This is the only UDP test available currently. If more endpoints are found (like DO) those might get added too 893 | .DESCRIPTION 894 | The only service that will ever answer to a UDP request, because it has to, is a NTP server. There are other ways to test that though see the Test-NTP function 895 | HUGE thanks to https://github.com/proxb/PowerShell_Scripts/blob/master/Test-Port.ps1 and 896 | Jannik Reinhard for the idea to use NTP to test UDP https://github.com/JayRHa/Intune-Scripts/blob/main/Check-AutopilotPrerequisites/Check-AutopilotPrerequisites.ps1#L145 897 | #> 898 | param( 899 | [string]$Target, 900 | [int]$Port 901 | ) 902 | Write-Log "Test $Target via direct UDP request" -Component 'TestNTPviaUDP' 903 | $NTPData = New-Object byte[] 48 904 | $NTPData[0] = 27 905 | $udpobject = New-Object Net.Sockets.Udpclient([System.Net.Sockets.AddressFamily]::InterNetwork) 906 | $udpobject.Client.Blocking = $False 907 | $udpobject.AllowNatTraversal($true) 908 | $Error.Clear() 909 | $udpobject.Connect($Target, $Port) | Out-Null 910 | 911 | if ($udpobject.client.Connected) { 912 | Write-Log -Message 'Sending UDP test message' -Component 'TestUDPPort' 913 | [void]$udpobject.Send($NTPData, $NTPData.Length) 914 | $remoteendpoint = New-Object system.net.ipendpoint([system.net.ipaddress]::Any, 0) 915 | Start-Sleep -Milliseconds 100 #We have to wait slightly to send the data back 916 | $TestData = $udpobject.Receive([ref]$remoteendpoint) 917 | } else { 918 | Write-Log -Message 'No UDP "connection" established' -Component 'TestUDPPort' 919 | Write-Log -Message "$($Error[0].Exception.InnerException.Message)" -Component 'TestUDPPort' -Type 2 920 | return $false 921 | } 922 | $udpobject.Close() 923 | $udpobject.Dispose() 924 | if ($TestData) { 925 | $Seconds = [BitConverter]::ToUInt32( $TestData[43..40], 0 ) 926 | ( [datetime]'1/1/1900' ).AddSeconds( $Seconds ).ToLocalTime() 927 | } else { 928 | Write-Log -Message 'Did not receive a response' -Component 'TestUDPPort' -Type 2 929 | return $false 930 | } 931 | return $true 932 | } 933 | function Test-TCPBurstMode { 934 | <# 935 | .SYNOPSIS 936 | Will test a given URL in 50ms chunks (50,100,150...) until $MaxDelayInMS is reached. 937 | #> 938 | param( 939 | $WorkObject 940 | ) 941 | $MinimumWaitTime = 50 942 | $AmountofTimes = [math]::floor([decimal]($MaxDelayInMS / $MinimumWaitTime)) 943 | for ($i = 1; $i -lt $AmountofTimes + 1; $i++) { 944 | $MaxWaitTime = $($MinimumWaitTime * $i) 945 | $TCPResult = Test-TCPPort -Target $WorkObject.url -Port $WorkObject.port -MaxWaitTime $MaxWaitTime 946 | $WorkObject | Add-Member -MemberType NoteProperty -Name "TCP$MaxWaitTime" -Value $TCPResult 947 | } 948 | } 949 | function Test-Network { 950 | <# 951 | .SYNOPSIS 952 | This is the core function of this script. It combines every possible test (DNS, CRL, TCP, TLS...) into one function. 953 | .NOTES 954 | ToDo: Make each check based upon a switch that is default = on 955 | #> 956 | 957 | param( 958 | [PSCustomObject]$TestObject 959 | ) 960 | Write-Log "Testing $($TestObject.url) on port $($TestObject.port)" -Component 'TestNetwork' 961 | if ($TestObject -in $Script:FinalResultList) { 962 | Write-Log 'This URL/Port was already tested' -Component 'TestNetwork' 963 | return $true 964 | } 965 | $TestObject | Add-Member -Name 'DNSResult' -MemberType NoteProperty -Value "" 966 | $TestObject | Add-Member -Name 'TCPResult' -MemberType NoteProperty -Value "" 967 | $TestObject | Add-Member -Name 'HTTPStatusCode' -MemberType NoteProperty -Value "" 968 | $TestObject | Add-Member -Name 'SSLTest' -MemberType NoteProperty -Value "" 969 | $TestObject | Add-Member -Name 'SSLProtocol' -MemberType NoteProperty -Value "" 970 | $TestObject | Add-Member -Name 'Issuer' -MemberType NoteProperty -Value "" 971 | $TestObject | Add-Member -Name 'AuthException' -MemberType NoteProperty -Value "" 972 | $TestObject | Add-Member -Name 'KnownCRL' -MemberType NoteProperty -Value "" 973 | $TestObject | Add-Member -Name 'SSLInterception' -MemberType NoteProperty -Value "" 974 | $DNS = Test-DNS -DNSTarget $TestObject.url 975 | $TestObject.DNSResult = $DNS 976 | if ($DNS) { 977 | if ($BurstMode) { 978 | Test-TCPBurstMode -TCPTarget $TestObject.url 979 | } else { 980 | if ($TestObject.protocol -ne 'TCP') { 981 | Write-Log 'This script can not test UDP ports - only NTP (see log for those results)' -Component 'TestNetwork' -Type 2 982 | } else { 983 | $TCP = Test-TCPPort -TCPTarget $TestObject.url -TCPPort $TestObject.port 984 | $TestObject.TCPResult = $TCP 985 | } 986 | if ($TCP) { 987 | #Test HTTP(s) Connections 988 | $HTTPStatuscode = Test-HTTP -HTTPURL $TestObject.url -HTTPPort $TestObject.port 989 | if ($null -ne $HTTPStatuscode) { 990 | $TestObject.HTTPStatusCode = $HTTPStatuscode 991 | } else { 992 | $TestObject.HTTPStatusCode = $false 993 | } 994 | #Test TLS and verify everything around the cert including traffic interception 995 | $SSLTest = Test-SSL -SSLTarget $TestObject.url -SSLPort $TestObject.port 996 | $TestObject.SSlTest = $SSLTest.SSLTest 997 | 998 | if ($SSLTest.SSLTest) { 999 | $TestObject.SSLProtocol = $SSLTest.SSLProtocol 1000 | $TestObject.Issuer = $SSLTest.Issuer 1001 | if ($SSLTest.AuthException) { 1002 | $TestObject.AuthException = $SSLTest.AuthException 1003 | } 1004 | if ($null -ne $SSLTest.KnownCRL) { 1005 | $TestObject.KnownCRL = $SSLTest.KnownCRL 1006 | } 1007 | if ($null -ne $SSLTest.SSLInterception) { 1008 | $TestObject.SSLInterception = $SSLTest.SSLInterception 1009 | } 1010 | } 1011 | } 1012 | } 1013 | } 1014 | $Script:FinalResultList.add($TestObject) | Out-Null 1015 | } 1016 | 1017 | #Service Areas 1018 | function Test-DNSServers { 1019 | <# 1020 | .SYNOPSIS 1021 | This will test a mixture of public DNS servers. 1022 | .NOTES 1023 | ServiceIDs 999 1024 | #> 1025 | $ServiceIDs = 999 1026 | $ServiceArea = "DNSServer" 1027 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1028 | Write-Log 'Testing DNSservers will ignore the local "HOSTS" file.' -Component "Test$ServiceArea" 1029 | $DNSServer = Get-URLsFromID -IDs $ServiceIDs 1030 | if (-not($DNSServer)) { 1031 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1032 | Write-Log -Message 'Please use the CSV provided with this script and specify -CustomURLFile' -Component "Test$ServiceArea" 1033 | return $false 1034 | } 1035 | foreach ($DNSTarget in $Script:URLsToVerify) { 1036 | $UDPDNSTest = Resolve-DnsName "microsoft.com" -DnsOnly -Type A -Server $DNSTarget.url -NoHostsFile -QuickTimeout -ErrorAction SilentlyContinue 1037 | if (-not($UDPDNSTest)) { 1038 | Write-Log "UDP DNS test failed for DNS server $($DNSTarget.url) - trying TCP fallback" -Component $ServiceArea -Type 2 1039 | $TCPDNSTest = Resolve-DnsName "microsoft.com" -DnsOnly -Type A -Server $DNSTarget.url -NoHostsFile -TcpOnly -QuickTimeout -ErrorAction SilentlyContinue 1040 | if (-not($TCPDNSTest)) { 1041 | Write-Log "TCP DNS test failed for DNS server $($DNSTarget.url) - trying TCP fallback" -Component $ServiceArea -Type 2 1042 | } 1043 | Write-Log "TCP DNS test successful for DNS server $($DNSTarget.url)" -Component $ServiceArea 1044 | } 1045 | Write-Log "UDP DNS test successful for DNS server $($DNSTarget.url)" -Component $ServiceArea 1046 | } 1047 | return $true 1048 | } 1049 | function Test-RemoteHelp { 1050 | <# 1051 | .SYNOPSIS 1052 | This will test all URLs required for RemoteHelp. 1053 | .NOTES 1054 | ServiceIDs 181,187,189 1055 | ServiceIDs GCC 188 1056 | Remote Help - Default + Required https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=europe#remote-help 1057 | #> 1058 | $ServiceIDs = 181, 187, 189 1059 | if ($GCC) { 1060 | $ServiceIDs = 181, 187, 188, 189 1061 | } 1062 | $ServiceArea = "RemoteHelp" 1063 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1064 | $RH = Get-URLsFromID -IDs $ServiceIDs 1065 | if (-not($RH)) { 1066 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1067 | return $false 1068 | } 1069 | foreach ($RHTarget in $Script:URLsToVerify) { 1070 | Test-Network $RHTarget 1071 | } 1072 | return $true 1073 | } 1074 | function Test-TPMAttestation { 1075 | <# 1076 | .SYNOPSIS 1077 | This will test all URLs required for TPM attestation. 1078 | .NOTES 1079 | ServiceIDs 173,9998 1080 | https://learn.microsoft.com/en-us/autopilot/requirements?tabs=networking#autopilot-self-deploying-mode-and-autopilot-pre-provisioning 1081 | #> 1082 | 1083 | $ServiceIDs = 173, 9998 1084 | $ServiceArea = "TPMAtt" 1085 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1086 | $TPMAtt = Get-URLsFromID -IDs $ServiceIDs 1087 | if (-not($TPMAtt)) { 1088 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1089 | return $false 1090 | } 1091 | foreach ($TPMTarget in $Script:URLsToVerify) { 1092 | Test-Network $TPMTarget 1093 | } 1094 | return $true 1095 | } 1096 | function Test-WNS { 1097 | <# 1098 | .SYNOPSIS 1099 | This will test all URLs required for the windows push notification service (WNS). 1100 | .NOTES 1101 | ServiceIDs 169,171 1102 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=europe#windows-push-notification-serviceswns-dependencies 1103 | https://www.microsoft.com/en-us/download/details.aspx?id=44238 1104 | ToDo UPDATE to include login.live.com - see XML file. 1105 | #> 1106 | $ServiceIDs = 169, 171 1107 | $ServiceArea = "WNS" 1108 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1109 | $WNS = Get-URLsFromID -IDs $ServiceIDs 1110 | if (-not($WNS)) { 1111 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1112 | return $false 1113 | } 1114 | foreach ($WNSTarget in $Script:URLsToVerify) { 1115 | Test-Network $WNSTarget 1116 | } 1117 | return $true 1118 | } 1119 | function Test-DeviceHealth { 1120 | <# 1121 | .SYNOPSIS 1122 | This will test all URLs required for Microsoft Azure Attestation (formerly Device Health). 1123 | .NOTES 1124 | ServiceIDs 186 1125 | GCC 9995 1126 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=north-america#migrating-device-health-attestation-compliance-policies-to-microsoft-azure-attestation 1127 | https://learn.microsoft.com/en-us/windows/client-management/mdm/healthattestation-csp 1128 | #> 1129 | $ServiceIDs = 186 1130 | if ($GCC) { 1131 | $ServiceIDs = 186, 9995 1132 | } 1133 | $ServiceArea = "DeviceHealth" 1134 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1135 | $DH = Get-URLsFromID -IDs $ServiceIDs 1136 | if (-not($DH)) { 1137 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1138 | return $false 1139 | } 1140 | foreach ($DHTarget in $Script:URLsToVerify) { 1141 | Test-Network $DHTarget 1142 | } 1143 | return $true 1144 | } 1145 | function Test-DeliveryOptimization { 1146 | <# 1147 | .SYNOPSIS 1148 | This will test all URLs required for delivery optimization. 1149 | .NOTES 1150 | ServiceIDs 172,164,9994 1151 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=north-america#delivery-optimization-dependencies 1152 | https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization-faq#what-hostnames-should-i-allow-through-my-firewall-to-support-delivery-optimization 1153 | #> 1154 | $ServiceIDs = 172, 164, 9994 1155 | $ServiceArea = "DO" 1156 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1157 | Write-Log 'Filtered Port TCP 7680: Documentation will specify port 7680 TCP. This is used to listen to other clients requests (from the host)' -Component 'TestDO' 1158 | Write-Log 'Filtered Port UDP 3544: Documentation will specify port 3544 UDP in/outbound as required - this is required for P2P connections' -Component 'TestDO' 1159 | Write-Log 'Verify TCP Port 7680 is being listened on' -Component 'TestDO' 1160 | $Listening = Get-NetTCPConnection -LocalPort 7680 -ErrorAction SilentlyContinue 1161 | if (-not($Listening)) { 1162 | Write-Log 'TCP Port 7680 not being listed as listening checking service' -Component 'TestDO' 1163 | if (-not(Get-Service -Name DoSvc)) { 1164 | Write-Log 'DoSvc is not running! Delivery optimization not possible' -Component 'TestDO' -Type 3 1165 | return $false 1166 | } 1167 | } 1168 | $DeliveryOptimization = Get-URLsFromID -IDs $ServiceIDs -FilterPort 7680, 3544 1169 | if (-not($DeliveryOptimization)) { 1170 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1171 | return $false 1172 | } 1173 | foreach ($DOTarget in $Script:URLsToVerify) { 1174 | Test-Network $DOTarget 1175 | } 1176 | return $true 1177 | } 1178 | function Test-Apple { 1179 | <# 1180 | .SYNOPSIS 1181 | This will test all URLs required for managing Apple devices. 1182 | .NOTES 1183 | ServiceIDs 178 1184 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#apple-dependencies 1185 | #> 1186 | Write-Log -Message 'Port 5223 is only used as a fallback for push notifications and only valid for push.apple.com addresses' -Component 'TestApple' 1187 | Write-Log -Message 'Warning: Other URLs might be required, please also consult https://support.apple.com/de-de/101555' -Component 'TestApple' -Type 2 1188 | $ServiceIDs = 178 1189 | $ServiceArea = "Apple" 1190 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1191 | $AAPL = Get-URLsFromID -IDs $ServiceIDs 1192 | if (-not($AAPL)) { 1193 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1194 | return $false 1195 | } 1196 | foreach ($AAPLTarget in $Script:URLsToVerify) { 1197 | Test-Network $AAPLTarget 1198 | } 1199 | return $true 1200 | } 1201 | function Test-Android { 1202 | <# 1203 | .SYNOPSIS 1204 | This will test all URLs required for managing Android devices 1205 | .NOTES 1206 | ServiceIDs 179,9992 1207 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=europe#android-aosp-dependencies 1208 | #> 1209 | Write-Log -Message 'Warning: Other URLs might be required, please also consult https://static.googleusercontent.com/media/www.android.com/en//static/2016/pdfs/enterprise/Android-Enterprise-Migration-Bluebook_2019.pdf' -Component 'TestAndroid' -Type 2 1210 | $ServiceIDs = 179, 9992 1211 | $ServiceArea = "Android" 1212 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1213 | 1214 | Write-Log 'Testing Android connectivity check' -Component "Test$ServiceArea" 1215 | $AndroidConnectivity = Invoke-WebRequest -Uri https://www.google.com/generate_204 1216 | if ($AndroidConnectivity.StatusCode -ne 204) { 1217 | Write-Log 'Android connectivity check failed - not testing any other addresses' -Component "Test$ServiceArea" 1218 | return $false 1219 | } 1220 | $Googl = Get-URLsFromID -IDs $ServiceIDs 1221 | if (-not($Googl)) { 1222 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1223 | return $false 1224 | } 1225 | foreach ($GoogleTarget in $Script:URLsToVerify) { 1226 | Test-Network $GoogleTarget 1227 | } 1228 | return $true 1229 | } 1230 | function Test-CRL { 1231 | <# 1232 | .SYNOPSIS 1233 | This will test all well-known CRLs by checking the availability of their respective URLs - _not_ the actual CRL. 1234 | .NOTES 1235 | ServiceIDs 84,125,9993 1236 | Source: Martin Himken - this isn't well documented. From the MSJSON we can assume these are correct 1237 | #> 1238 | $ServiceIDs = 84, 125, 9993 1239 | $ServiceArea = "CRL" 1240 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1241 | Write-Log "CRLs should only ever be available through Port 80, however the MS-JSOn specifies 443 as well. Expect errors going forward" -Component "Test$ServiceArea" 1242 | $CertRevocation = Get-URLsFromID -IDs $ServiceIDs 1243 | if (-not($CertRevocation)) { 1244 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1245 | return $false 1246 | } 1247 | foreach ($CRLTarget in $Script:URLsToVerify) { 1248 | Test-Network $CRLTarget 1249 | } 1250 | return $true 1251 | } 1252 | function Test-WindowsActivation { 1253 | <# 1254 | .SYNOPSIS 1255 | This will test all URLs required for windows activation. 1256 | .NOTES 1257 | ServiceIDs 9991 1258 | https://support.microsoft.com/en-us/topic/windows-activation-or-validation-fails-with-error-code-0x8004fe33-a9afe65e-230b-c1ed-3414-39acd7fddf52 1259 | #> 1260 | $ServiceIDs = 9991 1261 | $ServiceArea = "WinAct" 1262 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1263 | Write-Log 'These following URLs are best effort - there is very little documentation about this' -Component "Test$ServiceArea" 1264 | $WinAct = Get-URLsFromID -IDs $ServiceIDs 1265 | if (-not($WinAct)) { 1266 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1267 | return $false 1268 | } 1269 | foreach ($WinActTarget in $Script:URLsToVerify) { 1270 | Test-Network $WinActTarget 1271 | } 1272 | return $true 1273 | } 1274 | function Test-EntraID { 1275 | <# 1276 | .SYNOPSIS 1277 | This will test all URLs required for Entra ID. 1278 | .NOTES 1279 | ServiceIDs 9990,125,84,9993 1280 | https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/tshoot-connect-connectivity#connectivity-issues-in-the-installation-wizard 1281 | "Of these URLs, the URLs listed in the following table are the absolute bare minimum to be able to connect to Microsoft Entra ID at all" 1282 | As this list contains a lot of CRLs IDs 125,84 and 9993 (Test-CRL) also apply here - this is more than the bare minimum but CRLs should always be reachable. 1283 | #> 1284 | $ServiceIDs = 9990 1285 | $ServiceArea = "EID" 1286 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1287 | Write-Log 'The following URLs are the bare minimum for EntraID to work - depending on the situation there might be more' -Component "Test$ServiceArea" 1288 | $EID = Get-URLsFromID -IDs $ServiceIDs 1289 | if (-not($EID)) { 1290 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1291 | return $false 1292 | } 1293 | foreach ($EIDTarget in $Script:URLsToVerify) { 1294 | Test-Network $EIDTarget 1295 | } 1296 | if (-not($TestAllServiceAreas)) { 1297 | $TestCRLs = Test-CRL 1298 | if (-not($TestCRLs)) { 1299 | Write-Log "Testing CRLs for $ServiceArea failed" -Component "Test$ServiceArea" -Type 2 1300 | } 1301 | } else { 1302 | Write-Log 'TestAllServiceAreas detected - not re-running sub-tests for this service area' -Component "Test$ServiceArea" 1303 | } 1304 | return $true 1305 | } 1306 | function Test-WindowsUpdate { 1307 | <# 1308 | .SYNOPSIS 1309 | This will test all URLs required for Windows Update, this does not include delivery optimization. 1310 | .NOTES 1311 | ServiceIDs 164,9984 1312 | https://learn.microsoft.com/en-us/troubleshoot/windows-client/installing-updates-features-roles/windows-update-issues-troubleshooting#device-cant-access-update-files 1313 | #> 1314 | $ServiceIDs = 164, 9984 1315 | $ServiceArea = "WU" 1316 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1317 | $WindowsUpdate = Get-URLsFromID -IDs $ServiceIDs 1318 | if (-not($WindowsUpdate)) { 1319 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1320 | return $false 1321 | } 1322 | foreach ($WUTarget in $Script:URLsToVerify) { 1323 | Test-Network $WUTarget 1324 | } 1325 | return $true 1326 | } 1327 | function Test-NTP { 1328 | <# 1329 | .SYNOPSIS 1330 | This will test NTP through multiple methods, including sending an actual NTP request to Microsoft. 1331 | .NOTES 1332 | ServiceIDs 165 1333 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#autopilot-dependencies 1334 | Would throw 0x800705B4 if the URL exists but isn't an NTP or 0x80072AF9 for not resolved 1335 | #> 1336 | 1337 | $ServiceIDs = 165 1338 | $ServiceArea = "NTPServers" 1339 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1340 | Write-Log 'Microsofts JSON claims more URLs for Port 123, where in reality its only time.windows.com' -Component "Test$ServiceArea" 1341 | Write-Log 'There are more URLs related to NCSI in the Service ID 165, which will also be tested.' -Component "Test$ServiceArea" 1342 | $NTPServerNotDefault = $true 1343 | $CurrentTimeServer = (w32tm /query /source).trim() 1344 | switch ($CurrentTimeServer) { 1345 | *80070005* { Write-Log 'You need to run this script as admin to view the currently configured NTP server' -Component "Test$ServiceArea" -Type 2 } 1346 | *80070426* { Write-Log 'The service has not been started' -Component "Test$ServiceArea" -Type 2 } 1347 | *time.windows.com* { $NTPServerNotDefault = $false; Write-Log 'time.windows.com is the default timeserver - skipping custom server test' -Component "Test$ServiceArea" } 1348 | } 1349 | if ($NTPServerNotDefault) { 1350 | if ($Autopilot) { 1351 | Write-Log 'time.windows.com is currently not the timeserver - this is a requirement for Autopilot' -Component "Test$ServiceArea" -Type 2 1352 | } 1353 | $CurrentTimeServer = $CurrentTimeServer.Split(',')[0] 1354 | $CustomTimeServerTestResult = Test-NTPviaUDP $CurrentTimeServer -Port 123 1355 | } 1356 | Write-Log 'Testing default NTP server' -Component "Test$ServiceArea" 1357 | $DefaultTimeServerTestResult = w32tm /stripchart /computer:time.windows.com /dataonly /samples:3 1358 | if ($DefaultTimeServerTestResult -like "*80072AF9*" -or $DefaultTimeServerTestResult[3 - ($DefaultTimeServerTestResult.count)] -like "*800705B4*") { 1359 | Write-Log 'Testing with w32tm failed - switching to UDP test for NTP' -Component "Test$ServiceArea" -Type 2 1360 | $DefaultTimeServerTestResult = Test-NTPviaUDP 'time.windows.com' -Port 123 1361 | } 1362 | if ($CustomTimeServerTestResult -or $DefaultTimeServerTestResult) { 1363 | Write-Log "Custom NTP Server Test: $CustomTimeServerTestResult" -Component "Test$ServiceArea" 1364 | Write-Log "Default NTP Server Test: $($null -ne $DefaultTimeServerTestResult)" -Component "Test$ServiceArea" 1365 | $NTPServers = Get-URLsFromID -IDs $ServiceIDs -FilterPort 80, 443 1366 | if (-not($NTPServers)) { 1367 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1368 | return $false 1369 | } 1370 | foreach ($NTPServersTarget in $Script:URLsToVerify) { 1371 | Test-Network $NTPServersTarget 1372 | } 1373 | return $true 1374 | } 1375 | Write-Log 'Both, custom and default, NTP servers tested did not answer as expected' -Component "Test$ServiceArea" -Type 3 1376 | return $false 1377 | } 1378 | function Test-DiagnosticsData { 1379 | <# 1380 | .SYNOPSIS 1381 | This tests all URLs required to send diagnostic data to Microsoft endpoints. 1382 | .NOTES 1383 | ServiceIDs 69,9983 1384 | https://learn.microsoft.com/en-us/windows/privacy/manage-windows-11-endpoints 1385 | #> 1386 | $ServiceIDs = 69, 9983 1387 | $ServiceArea = "Diagnostics" 1388 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1389 | $Diagnostics = Get-URLsFromID -IDs $ServiceIDs 1390 | if (-not($Diagnostics)) { 1391 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1392 | return $false 1393 | } 1394 | foreach ($DiagnosticsTarget in $Script:URLsToVerify) { 1395 | Test-Network $DiagnosticsTarget 1396 | } 1397 | return $true 1398 | } 1399 | function Test-DiagnosticsDataUpload { 1400 | <# 1401 | .SYNOPSIS 1402 | This tests all URLs required to send collected diagnostics data to Intune (yes, the .zip file). 1403 | .NOTES 1404 | ServiceIDs 182,9989 1405 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#autopilot-dependencies 1406 | https://learn.microsoft.com/en-us/mem/intune/remote-actions/collect-diagnostics#requirements-for-windows-devices 1407 | Also called "Autopilot automatic device diagnostics collection" 1408 | #> 1409 | $ServiceIDs = 182, 9989 1410 | $ServiceArea = "DiagnosticsUpload" 1411 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1412 | $DiagUpload = Get-URLsFromID -IDs $ServiceIDs 1413 | if (-not($DiagUpload)) { 1414 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1415 | return $false 1416 | } 1417 | foreach ($DiagUploadTarget in $Script:URLsToVerify) { 1418 | Test-Network $DiagUploadTarget 1419 | } 1420 | return $true 1421 | } 1422 | function Test-EndpointAnalytics { 1423 | <# 1424 | .SYNOPSIS 1425 | This will test all URLs required for endpoint analytics to receive data. 1426 | .NOTES 1427 | ServiceIDs 69,163,9988 1428 | https://learn.microsoft.com/en-us/mem/analytics/troubleshoot#bkmk_endpoints 1429 | #> 1430 | $ServiceIDs = 69, 163, 9988 1431 | $ServiceArea = "EndpAnalytics" 1432 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1433 | $EndpAnalytics = Get-URLsFromID -IDs $ServiceIDs 1434 | if (-not($EndpAnalytics)) { 1435 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1436 | return $false 1437 | } 1438 | foreach ($EndpAnalyticsTarget in $Script:URLsToVerify) { 1439 | Test-Network $EndpAnalyticsTarget 1440 | } 1441 | return $true 1442 | } 1443 | function Test-NCSI { 1444 | <# 1445 | .SYNOPSIS 1446 | This tests all the URLs required for the network connection status indicator to work. 1447 | .NOTES 1448 | ServiceIDs 165,9987 1449 | https://learn.microsoft.com/en-us/windows/privacy/manage-windows-11-endpoints 1450 | https://learn.microsoft.com/en-us/windows/privacy/manage-windows-21h2-endpoints 1451 | #> 1452 | $ServiceIDs = 165, 9987 1453 | $ServiceArea = "NetworkIndicator" 1454 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1455 | Write-Log "Service ID 165 is mixed up with NTP, hence Service ID 9987 is required to only test the correct URLs and ports" -Component "Test$ServiceArea" -Type 2 1456 | try { 1457 | $NCSIActive = (Get-ItemPropertyValue -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\NetworkConnectivityStatusIndicator -Name NoActiveProbe -ErrorAction Stop | Out-Null) -eq 0 1458 | } catch { 1459 | $NCSIActive = $true 1460 | } 1461 | if (-not($NCSIActive)) { 1462 | Write-Log 'The NCSI has been detected as disabled - continuing with network tests regardless' -Component "Test$ServiceArea" -Type 2 1463 | } 1464 | $NetworkIndicator = Get-URLsFromID -IDs $ServiceIDs -FilterPort 123 1465 | if (-not($NetworkIndicator)) { 1466 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1467 | return $false 1468 | } 1469 | foreach ($NetworkIndicatorTarget in $Script:URLsToVerify) { 1470 | Test-Network $NetworkIndicatorTarget 1471 | } 1472 | return $true 1473 | } 1474 | function Test-MicrosoftStore { 1475 | <# 1476 | .SYNOPSIS 1477 | This tests all the URLs that are required for the Microsoft Store to work. This includes Store updates. 1478 | .NOTES 1479 | ServiceIDs 9996 1480 | https://learn.microsoft.com/en-us/windows/privacy/manage-windows-11-endpoints 1481 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#microsoft-store 1482 | #> 1483 | $ServiceIDs = 9996 1484 | $ServiceArea = "MS" 1485 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1486 | $MicrosoftStore = Get-URLsFromID -IDs $ServiceIDs 1487 | if (-not($MicrosoftStore)) { 1488 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1489 | return $false 1490 | } 1491 | foreach ($MSTarget in $Script:URLsToVerify) { 1492 | Test-Network $MSTarget 1493 | } 1494 | if (-not($TestAllServiceAreas)) { 1495 | $WNSTest = Test-WNS 1496 | $DOTest = Test-DeliveryOptimization 1497 | if (-not($WNSTest -and $DOTest)) { 1498 | return $false 1499 | } 1500 | } else { 1501 | Write-Log 'TestAllServiceAreas detected - not re-running sub-tests for this service area' -Component "Test$ServiceArea" 1502 | } 1503 | return $true 1504 | } 1505 | function Test-AppInstaller { 1506 | <# 1507 | .SYNOPSIS 1508 | This tests all the URLs that are required for the AppInstaller to work. This includes winget. 1509 | .NOTES 1510 | ServiceIDs 9996 1511 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#microsoft-store 1512 | #> 1513 | $ServiceArea = "AppInstall" 1514 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1515 | Write-Log "Testing $ServiceArea is the same requirement as for the Microsoft store, this doesn't include downloads from vendor setup files (which depend on each package)" -Component "Test$ServiceArea" -Type 2 1516 | if ($TestAllServiceAreas) { 1517 | Write-Log 'TestAllServiceAreas detected - not re-running sub-tests for this service area' -Component "Test$ServiceArea" 1518 | return $true 1519 | } 1520 | $TestMSStore = Test-MicrosoftStore 1521 | if (-not($TestMSStore)) { 1522 | Write-Log 'Testing the Microsoft store failed' -Component "Test$ServiceArea" 1523 | } 1524 | return $true 1525 | } 1526 | function Test-SelfDeploying { 1527 | <# 1528 | .SYNOPSIS 1529 | This will test all the URLs that are required for the Autopilot self-deployment mode to work. 1530 | .NOTES 1531 | ServiceID 173, 9998 1532 | https://learn.microsoft.com/en-us/autopilot/requirements?tabs=networking#autopilot-self-deploying-mode-and-autopilot-pre-provisioning 1533 | #> 1534 | $ServiceIDs = 173, 9998 1535 | $ServiceArea = "SelfDepl" 1536 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1537 | $SelfDepl = Get-URLsFromID -IDs $ServiceIDs 1538 | if (-not($SelfDepl)) { 1539 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1540 | return $false 1541 | } 1542 | foreach ($SelfDeplTarget in $Script:URLsToVerify) { 1543 | Test-Network $SelfDeplTarget 1544 | } 1545 | return $true 1546 | } 1547 | function Test-Legacy { 1548 | <# 1549 | .NOTES 1550 | Test-Hybrid Join? This might prove hard, as this is hardly documented (sure, it needs Entra-ID, but AD connectivity?) 1551 | ToDo 1552 | #> 1553 | } 1554 | function Test-UniversalPrint { 1555 | <# 1556 | .SYNOPSIS 1557 | This will test all the URLs that are required for Universal Print 1558 | .NOTES 1559 | ServiceID 9982,9981,9980 1560 | https://learn.microsoft.com/en-us/universal-print/fundamentals/universal-print-faqs 1561 | As this is a script that tests client connections, the connector URLs are _not tested_. 1562 | Applicationinsight addresses are not documented. You can look at these by running "az account list-locations -o table" 1563 | #> 1564 | $ServiceIDs = 9982, 9980 1565 | if ($GCC) { 1566 | $ServiceIDs = 9981, 9980 1567 | } 1568 | $ServiceArea = "UniP" 1569 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1570 | $UniP = Get-URLsFromID -IDs $ServiceIDs 1571 | if (-not($UniP)) { 1572 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1573 | return $false 1574 | } 1575 | foreach ($UniPTarget in $Script:URLsToVerify) { 1576 | Test-Network $UniPTarget 1577 | } 1578 | return $true 1579 | } 1580 | #Special tests, not service area specific 1581 | function Test-AuthenticatedProxy { 1582 | <# 1583 | .SYNOPSIS 1584 | This will attempt to test if an authenticated proxy is being used for URLs marked as incompatible with such a system. 1585 | .NOTES 1586 | ServiceIDs 9986 1587 | These URLs don't allow authenticated proxies according to https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=europe#access-for-managed-devices 1588 | This will use something.azureedge.com as an example for *.azureedge.com - yes, that is not a documented address. 1589 | #> 1590 | $ServiceIDs = 9986 1591 | $ServiceArea = "AuthenProxy" 1592 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1593 | $AuthenProxy = Get-URLsFromID -IDs $ServiceIDs 1594 | if (-not($AuthenProxy)) { 1595 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1596 | return $false 1597 | } 1598 | foreach ($AuthenProxyTarget in $Script:URLsToVerify) { 1599 | Test-Network $AuthenProxyTarget 1600 | if ($($Script:FinalResultList | Where-Object { ($_.url -eq $AuthenProxyTarget.url -and $_.port -eq $AuthenProxyTarget.port) -and ($_.HTTPStatusCode -in 407, 403, 401 ) })) { 1601 | Write-Log -Message "The URL $($AuthenProxyTarget.url) requested authentication - this is not supported!" -Component "Test$ServiceArea" -Type 3 1602 | } 1603 | } 1604 | return $true 1605 | } 1606 | function Test-SSLInspection { 1607 | <# 1608 | .SYNOPSIS 1609 | This will attempt to test if an TLS/SSL inspection is being used for URLs marked as incompatible with such a system. 1610 | .NOTES 1611 | ServiceIDs 9985 1612 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?tabs=north-america#access-for-managed-devices 1613 | This uses checkin.dm.microsoft.com as a standin for *.dm.microsoft.com - could use discovery.dm.microsoft.com as fallback 1614 | #> 1615 | $ServiceIDs = 9985 1616 | $ServiceArea = "TLSInspec" 1617 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1618 | $TLSInspec = Get-URLsFromID -IDs $ServiceIDs 1619 | if (-not($TLSInspec)) { 1620 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1621 | return $false 1622 | } 1623 | foreach ($TLSInspecTarget in $Script:URLsToVerify) { 1624 | Test-Network $TLSInspecTarget 1625 | } 1626 | if (-not($TestAllServiceAreas)) { 1627 | $DeviceHealthTest = Test-DeviceHealth 1628 | if (-not($DeviceHealthTest)) { 1629 | return $false 1630 | } 1631 | } else { 1632 | Write-Log 'TestAllServiceAreas detected - not re-running sub-tests for this service area' -Component "Test$ServiceArea" 1633 | } 1634 | foreach ($TLSInspectObject in $Script:FinalResultList) { 1635 | if ($($Script:FinalResultList | Where-Object { $_.url -eq $TLSInspectObject.url -and $_.port -eq $TLSInspectObject.port -and $_.SSLInspection -eq 'True' })) { 1636 | Write-Log -Message "The traffic to $($TLSInspectObject.url) is probably inspected - this is not supported!" -Component "Test$ServiceArea" -Type 3 1637 | } 1638 | } 1639 | return $true 1640 | } 1641 | function Test-M365 { 1642 | <# 1643 | .SYNOPSIS 1644 | Yes, this will test _every_ M365 Common URL regardless if it's required or not. Prepare for 15 minutes of runtime minimum. 1645 | .NOTES 1646 | ServiceIDs yes 1647 | https://learn.microsoft.com/en-us/microsoft-365/enterprise/urls-and-ip-address-ranges? 1648 | #> 1649 | $ServiceIDs = 41, 43, 44, 45, 46, 47, 49, 50, 51, 53, 56, 59, 64, 66, 67, 68, 69, 70, 71, 73, 75, 78, 79, 83, 84, 86, 89, 91, 92, 93, 95, 96, 97, 105, 114, 116, 117, 118, 121, 122, 124, 125, 126, 147, 152, 153, 156, 158, 159, 160, 184 1650 | $ServiceArea = "MS365" 1651 | if (-not($UseMS365JSON)) { 1652 | Write-Log 'UseMS365JSON was not specified. This test can not be performed, because most IDs are not available' -Component "Test$ServiceArea" -Type 3 1653 | return $false 1654 | } 1655 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1656 | $M365FullTest = Get-URLsFromID -IDs $ServiceIDs 1657 | if (-not($M365FullTest)) { 1658 | Write-Log -Message 'No matching ID found for service area: Windows Update' -Component "Test$ServiceArea" -Type 3 1659 | return $false 1660 | } 1661 | foreach ($M365Target in $Script:URLsToVerify) { 1662 | Test-Network $M365Target 1663 | } 1664 | return $true 1665 | } 1666 | function Test-MDE { 1667 | <# 1668 | .SYNOPSIS 1669 | Test all URLs required to use Microsoft Defender for Endpoint. 1670 | .NOTES 1671 | ServiceIDs 1672 | https://download.microsoft.com/download/6/b/f/6bfff670-47c3-4e45-b01b-64a2610eaefa/mde-urls-commercial.xlsx 1673 | ToDo - sorry Felix, it's coming don't worry. 1674 | #> 1675 | 1676 | } 1677 | function Test-AppAndScripts { 1678 | <# 1679 | .SYNOPSIS 1680 | This will test all the URLs that are required for Win32 App deployment and PowerShell Script Deployment for Windows and Mac 1681 | .NOTES 1682 | ServiceID 170 1683 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#network-requirements-for-powershell-scripts-and-win32-apps 1684 | #> 1685 | $ServiceIDs = 170, 9979 1686 | $ServiceArea = "W32Script" 1687 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1688 | $W32Script = Get-URLsFromID -IDs $ServiceIDs 1689 | if (-not($W32Script)) { 1690 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1691 | return $false 1692 | } 1693 | foreach ($W32ScriptTarget in $Script:URLsToVerify) { 1694 | Test-Network $W32ScriptTarget 1695 | } 1696 | return $true 1697 | } 1698 | function Test-Autopilot { 1699 | <# 1700 | .SYNOPSIS 1701 | Test all URLs required to use Autopilot. This also includes a lot of other service areas. 1702 | .NOTES 1703 | ServiceIDs 164,165,169,173,182,9999 1704 | 9999 = Autopilot 1705 | https://learn.microsoft.com/en-us/autopilot/requirements?tabs=networking#windows-autopilot-deployment-service 1706 | Autopilot dependencies according to https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints?#autopilot-dependencies 1707 | #> 1708 | $ServiceIDs = '9999' 1709 | $ServiceArea = 'AP' 1710 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1711 | $AP = Get-URLsFromID -IDs $ServiceIDs 1712 | if (-not($AP)) { 1713 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1714 | return $false 1715 | } 1716 | foreach ($APTarget in $Script:URLsToVerify) { 1717 | Test-Network $APTarget 1718 | } 1719 | <# 1720 | Test-NTP #165 1721 | Test-WNS #169 1722 | Test-TPMAttestation #173 1723 | Test-DeliveryOptimization #164 1724 | Test-DiagnosticsDataUpload #182 1725 | #> 1726 | $resultlist = @{ 1727 | TestWindowsActivation = Test-WindowsActivation 1728 | EntraIDTest = Test-EntraID 1729 | DiagnosticsDataUTest = Test-DiagnosticsDataUpload 1730 | WUTest = Test-WindowsUpdate 1731 | DOTest = Test-DeliveryOptimization 1732 | NTPTest = Test-NTP 1733 | DNSTest = Test-DNSServers #Log only output! 1734 | DiagnosticsDataTest = Test-DiagnosticsData 1735 | NCSITest = Test-NCSI 1736 | WNSTest = Test-WNS 1737 | StoreTest = Test-MicrosoftStore 1738 | CRLTest = Test-CRL 1739 | LegacyTest = Test-Legacy 1740 | SelfDeployingTest = Test-SelfDeploying 1741 | TPMAttTest = Test-TPMAttestation 1742 | } 1743 | if ($resultlist.values -contains $false) { 1744 | Write-Log -Message "$resultlist" -Component "Test$ServiceArea" -Type 3 1745 | return $false 1746 | } 1747 | return $true 1748 | } 1749 | function Test-Intune { 1750 | <# 1751 | .SYNOPSIS 1752 | Test all URLs required to use Intuine. This also includes a lot of other service areas. 1753 | .NOTES 1754 | ServiceIDs 163,172,170,97,190,189 + Authentiation 56,150,59 1755 | 9997 = Defender 1756 | https://learn.microsoft.com/en-us/mem/intune/fundamentals/intune-endpoints 1757 | #> 1758 | $ServiceIDs = 56, 150, 59, 163, 172, 170, 97, 190, 189, 9998, 9985 1759 | $ServiceArea = "Int" 1760 | Write-Log "Testing Service Area $ServiceArea" -Component "Test$ServiceArea" 1761 | $Int = Get-URLsFromID -IDs $ServiceIDs 1762 | if (-not($Int)) { 1763 | Write-Log -Message "No matching ID found for service area: $ServiceArea" -Component "Test$ServiceArea" -Type 3 1764 | return $false 1765 | } 1766 | foreach ($IntTarget in $Script:URLsToVerify) { 1767 | Test-Network $IntTarget 1768 | } 1769 | $resultlist = @{ 1770 | TestAutoPilot = Test-Autopilot 1771 | EntraIDTest = Test-RemoteHelp 1772 | WNSTest = Test-WNS 1773 | DOTest = Test-DeliveryOptimization 1774 | AppleTest = Test-Apple 1775 | AndroidTest = Test-Android 1776 | StoreTest = Test-MicrosoftStore 1777 | DeviceHealth = Test-DeviceHealth 1778 | WUTest = Test-WindowsUpdate 1779 | EndpAnalytics = Test-EndpointAnalytics 1780 | #MDE = Test-MDE #Not done! 1781 | DiagnosticsDataTest = Test-DiagnosticsData 1782 | NTPTest = Test-NTP 1783 | AppAndScript = Test-AppAndScripts 1784 | } 1785 | if ($resultlist.values -contains $false) { 1786 | Write-Log -Message "$resultlist" -Component "Test$ServiceArea" -Type 3 1787 | return $false 1788 | } 1789 | return $true 1790 | } 1791 | 1792 | #Data Functions 1793 | function Build-OutputCSV { 1794 | <# 1795 | .SYNOPSIS 1796 | Creates either a URL result list or a merged CSV file. 1797 | #> 1798 | param( 1799 | [string[]]$InputCSVs 1800 | ) 1801 | if (-not($InputCSVs)) { 1802 | $OutpathFilePath = $(Join-Path $WorkingDirectory -ChildPath "ResultList$("_"+$Script:DateTime + "_"+ $Env:COMPUTERNAME).csv") 1803 | $Script:FinalResultList | Export-Csv -Path $OutpathFilePath -Encoding utf8 1804 | } elseif ($InputCSVs.Count -eq 2) { 1805 | $MergedCSVTargetFolder = $(Join-Path $WorkingDirectory -ChildPath '/MergedResults') 1806 | if (-not(Test-Path $MergedCSVTargetFolder)) { New-Item -Path $MergedCSVTargetFolder -ItemType Directory | Out-Null } 1807 | $OutpathFilePath = $(Join-Path $MergedCSVTargetFolder -ChildPath "/MergedResults$("_"+$Script:DateTime + "_"+ $Script:MergeCSVComputername1 + "_" + $Script:MergeCSVComputername2).csv") 1808 | $Script:ComparedResults | Export-Csv -Path $OutpathFilePath -Encoding utf8 -Force 1809 | } 1810 | } 1811 | function Merge-ResultFiles { 1812 | <# 1813 | .SYNOPSIS 1814 | Merges two given CSV files. 1815 | #> 1816 | param( 1817 | [PSCustomObject[]]$CSVInput 1818 | ) 1819 | if ($CSVInput) { 1820 | if ($CSVInput.count -ne 2) { 1821 | Write-Log 'Currently, this script can only handle two file comparisons. Please provide only 2 CSVs' -Component 'MergeResultFiles' -Type 3 1822 | } 1823 | } else { 1824 | Write-Log 'No input CSV given - triggering auto detection' -Component 'MergeResultFiles' 1825 | $TempCSVInput = Get-ChildItem -Filter "*.csv" | Where-Object { $_.Name -ne $CustomURLFile -and $_.Name -ne 'INRCustomList.csv' } 1826 | if (-not($TempCSVInput)) { 1827 | $TempCSVInput = Get-ChildItem -Path $WorkingDirectory -Filter "*.csv" | Where-Object { $_.Name -ne $CustomURLFile -and $_.Name -ne 'INRCustomList.csv' } 1828 | } 1829 | if ($TempCSVInput.count -ne 2) { 1830 | Write-Log "Auto detection found more or less than 2 CSV files in $WorkingDirectory" -Component 'MergeResultFiles' -Type 3 1831 | return $false 1832 | } 1833 | $CSVInput = $TempCSVInput.FullName 1834 | } 1835 | 1836 | $Script:ComparedResults = [System.Collections.ArrayList]::new() 1837 | for ($i = 0; $i -lt $CSVInput.Count; $i++) { 1838 | if (Test-Path $CSVInput[$i]) { 1839 | [System.IO.DirectoryInfo]$CSVPath = $CSVInput[$i] 1840 | } else { 1841 | [System.IO.DirectoryInfo]$CSVPath = Join-Path -Path $WorkingDirectory -ChildPath $CSVInput[$i] 1842 | } 1843 | if (-not(Test-Path $CSVPath)) { 1844 | Write-Log "File $($CSVPath) not found" -Component 'MergeResultFiles' -Type 3 1845 | return $false 1846 | } 1847 | $culture = [Globalization.CultureInfo]::CreateSpecificCulture('de-DE') 1848 | $TimeStamp = Get-Date([DateTime]::ParseExact("$($CSVPath.name.Replace('ResultList_','').substring(0,15))", 'yyyyMMdd_HHmmss', $culture)) -Format "dd.MM.yyyy HH:mm:ss" 1849 | $CSVComputername = $($CSVPath.name.Split('_')[3].split('.')[0]) 1850 | if ($i -ge 1) { 1851 | if ($CSVComputername -eq $Script:MergeCSVComputername1) { 1852 | $CSVComputername = $CSVComputername + "_older" 1853 | } 1854 | } 1855 | New-Variable "MergeCSVComputername$($i+1)" -Value $CSVComputername -Scope Script 1856 | New-Variable "MergeCSVTimestamp$($i+1)" -Value $TimeStamp -Scope Script 1857 | New-Variable "ImportedCSV$($i+1)" -Value $(Import-Csv -Path $CSVPath) 1858 | } 1859 | Write-Log 'CSV imported - checking and merging' -Component 'MergeResultFiles' 1860 | if ($ImportedCSV1.count -ne $ImportedCSV2.count) { 1861 | Write-Log 'These CSV files have different lengths - please valide that you used the same tests' -Component 'MergeResultFiles' -Type 3 1862 | Write-Log 'This happens especially when the M365 JSON or a new version of my URL list was selected as that might change at any time' -Component 'MergeResultFiles' 1863 | return $false 1864 | } 1865 | $counter = 0 1866 | if ($MergeShowAllResults) { Write-Log 'MergeShowAllResults is selected. All results will be merge into one result output instead of showing only differences' -Component 'MergeResultFiles' } 1867 | foreach ($CSVLeftObject in $ImportedCSV1) { 1868 | $Result = Compare-Object -ReferenceObject $CSVLeftObject -DifferenceObject $ImportedCSV2[$counter] -Property ID, URL, Port, DNSResult, TCPResult, HTTPStatusCode, SSLTest, SSLProtocol, Issuer, AuthException, SSLInterception 1869 | $CSVLeftObject | Add-Member -Name 'ComputerName' -MemberType NoteProperty -Value "$($Script:MergeCSVComputername1)" 1870 | $CSVLeftObject | Add-Member -Name 'TimeStamp' -MemberType NoteProperty -Value "$($Script:MergeCSVTimestamp1)" 1871 | if ($Result) { 1872 | Write-Log "Difference found at $($CSVLeftObject.url)" -Component 'MergeResultFiles' 1873 | $ImportedCSV2[$counter] | Add-Member -Name 'ComputerName' -MemberType NoteProperty -Value "$($Script:MergeCSVComputername2)" 1874 | $ImportedCSV2[$counter] | Add-Member -Name 'TimeStamp' -MemberType NoteProperty -Value "$($Script:MergeCSVTimestamp2)" 1875 | $Script:ComparedResults.add($CSVLeftObject) | Out-Null 1876 | $Script:ComparedResults.add($ImportedCSV2[$counter]) | Out-Null 1877 | } elseif ($MergeShowAllResults) { 1878 | $Script:ComparedResults.add($CSVLeftObject) | Out-Null 1879 | } 1880 | $counter++ 1881 | } 1882 | $Script:ComparedResults = $Script:ComparedResults | Sort-Object -Property url, ComputerName 1883 | return $true 1884 | } 1885 | function Start-Tests { 1886 | <# 1887 | .SYNOPSIS 1888 | Starts either all, specific, or individual tests based on user input. 1889 | #> 1890 | if ($Intune -or $TestAllServiceAreas) { 1891 | Write-Log -Message "Intune result: $(Test-Intune)" -Component 'StartTests' 1892 | } 1893 | if ($Autopilot -or $TestAllServiceAreas) { 1894 | Write-Log -Message "Autopilot result: $(Test-Autopilot)" -Component 'StartTests' 1895 | } 1896 | if ($WindowsActivation -or $TestAllServiceAreas) { 1897 | Write-Log -Message "Windows activation result: $(Test-WindowsActivation)" -Component 'StartTests' 1898 | } 1899 | if ($EntraID -or $TestAllServiceAreas) { 1900 | Write-Log -Message "Entra ID result: $(Test-EntraID)" -Component 'StartTests' 1901 | } 1902 | if ($WindowsUpdate -or $TestAllServiceAreas) { 1903 | Write-Log -Message "Windows Update result: $(Test-WindowsUpdate)" -Component 'StartTests' 1904 | } 1905 | if ($DeliveryOptimization -or $TestAllServiceAreas) { 1906 | Write-Log -Message "Delivery Optimization result: $(Test-DeliveryOptimization)" -Component 'StartTests' 1907 | } 1908 | if ($NTP -or $TestAllServiceAreas) { 1909 | Write-Log -Message "NTP result: $(Test-NTP)" -Component 'StartTests' 1910 | } 1911 | if ($DNS -or $TestAllServiceAreas) { 1912 | Write-Log -Message "DNS result: $(Test-DNSServers)" -Component 'StartTests' 1913 | } 1914 | if ($DiagnosticsData -or $TestAllServiceAreas) { 1915 | Write-Log -Message "Diagnostics Data result: $(Test-DiagnosticsData)" -Component 'StartTests' 1916 | } 1917 | if ($DiagnosticsDataUpload -or $TestAllServiceAreas) { 1918 | Write-Log -Message "Diagnostics Data Upload result: $(Test-DiagnosticsDataUpload)" -Component 'StartTests' 1919 | } 1920 | if ($NCSI -or $TestAllServiceAreas) { 1921 | Write-Log -Message "NCSI result: $(Test-NCSI)" -Component 'StartTests' 1922 | } 1923 | if ($WindowsNotificationService -or $TestAllServiceAreas) { 1924 | Write-Log -Message "WNS result: $(Test-WNS)" -Component 'StartTests' 1925 | } 1926 | if ($WindowsStore -or $TestAllServiceAreas) { 1927 | Write-Log -Message "Windows Store result: $(Test-MicrosoftStore)" -Component 'StartTests' 1928 | } 1929 | if ($M365 -or $TestAllServiceAreas) { 1930 | Write-Log -Message "M365 result: $(Test-M365)" -Component 'StartTests' 1931 | } 1932 | if ($CRLs -or $TestAllServiceAreas) { 1933 | Write-Log -Message "CRLs result: $(Test-CRL)" -Component 'StartTests' 1934 | } 1935 | if ($SelfDeploying -or $TestAllServiceAreas) { 1936 | Write-Log -Message "Self-Deploying result: $(Test-SelfDeploying)" -Component 'StartTests' 1937 | } 1938 | if ($RemoteHelp -or $TestAllServiceAreas) { 1939 | Write-Log -Message "Remote Help result: $(Test-RemoteHelp)" -Component 'StartTests' 1940 | } 1941 | if ($TPMAttestation -or $TestAllServiceAreas) { 1942 | Write-Log -Message "TPM Attestation result: $(Test-TPMAttestation)" -Component 'StartTests' 1943 | } 1944 | if ($DeviceHealth -or $TestAllServiceAreas) { 1945 | Write-Log -Message "Device Health result: $(Test-DeviceHealth)" -Component 'StartTests' 1946 | } 1947 | if ($Apple -or $TestAllServiceAreas) { 1948 | Write-Log -Message "Apple result: $(Test-Apple)" -Component 'StartTests' 1949 | } 1950 | if ($Android -or $TestAllServiceAreas) { 1951 | Write-Log -Message "Android result: $(Test-Android)" -Component 'StartTests' 1952 | } 1953 | if ($EndpointAnalytics -or $TestAllServiceAreas) { 1954 | Write-Log -Message "Endpoint Analytics result: $(Test-EndpointAnalytics)" -Component 'StartTests' 1955 | } 1956 | if ($AppInstaller -or $TestAllServiceAreas) { 1957 | Write-Log -Message "App Installer result: $(Test-AppInstaller)" -Component 'StartTests' 1958 | } 1959 | if ($AuthenticatedProxyOnly -or $TestAllServiceAreas) { 1960 | Write-Log -Message "Authenticated Proxy result: $(Test-AuthenticatedProxy)" -Component 'StartTests' 1961 | } 1962 | if ($TestSSLInspectionOnly -or $TestAllServiceAreas) { 1963 | Write-Log -Message "SSL Inspection result: $(Test-SSLInspection)" -Component 'StartTests' 1964 | } 1965 | if ($Legacy -or $TestAllServiceAreas) { 1966 | Write-Log -Message "Legacy result: $(Test-Legacy)" -Component 'StartTests' 1967 | } 1968 | if ($UniversalPrint -or $TestAllServiceAreas) { 1969 | Write-Log -Message "Universal Print result: $(Test-UniversalPrint)" -Component 'StartTests' 1970 | } 1971 | if ($AppAndScript -or $TestAllServiceAreas) { 1972 | Write-Log -Message "Win32 and Script deployment result: $(Test-AppAndScripts)" -Component 'StartTests' 1973 | } 1974 | } 1975 | function Start-Brienmode { 1976 | $Null = Read-Host -Prompt "Please press any key to continue" 1977 | Initialize-Script 1978 | Start-Tests 1979 | } 1980 | function Start-ProcessingResults { 1981 | if ($BrienMode) { 1982 | Write-Output "BrienMode is activated: This is an interactive mode that will let you test multiple times on the same box" 1983 | Write-Output "Remember this will only compare the LATEST TWO results" 1984 | for ($i = 1; $i -le $BrienMode; $i++) { 1985 | if ($i -ge 2) { 1986 | Write-Log -Message 'RERUNNING TESTS WITH NEW NETWORK PARAMETERS' -Component 'BrienMode' -Type 2 1987 | Write-Warning "Please change your network now to run the test again - this will always create output CSVs" 1988 | } 1989 | Start-Brienmode 1990 | Build-OutputCSV 1991 | if ($ShowResults) { 1992 | $Script:FinalResultList | Out-GridView -Title "Intune Network test (Brien Mode) $($Script:MergeCSVComputername1) pass $i" 1993 | } 1994 | } 1995 | $MergeCSVs = (Get-ChildItem -Path $WorkingDirectory -Filter *.csv | Sort-Object -Property LastWriteTime -Top 2 -Descending).FullName 1996 | if (-not(Merge-ResultFiles -CSVInput $MergeCSVs)) { 1997 | Write-Log 'Something went wrong while comparing the files, please check the logs' -Component 'ProcessingResults' -Type 2 1998 | return $false 1999 | } 2000 | if (-not($Script:ComparedResults)) { 2001 | Write-Log 'The comparison found no differences between the two provided CSVs' -Component 'ProcessingResults' -Type 2 2002 | } else { 2003 | if ($ShowResults) { 2004 | $Script:ComparedResults | Sort-Object -Property url | Out-GridView -Title "Merge result between: $($Script:MergeCSVComputername1) and $($Script:MergeCSVComputername2)" -Wait 2005 | } 2006 | Build-OutputCSV -InputCSVs $MergeCSVs 2007 | } 2008 | } else { 2009 | if ($OutputCSV -and -not($MergeResults)) { 2010 | Build-OutputCSV 2011 | } 2012 | if ($MergeResults) { 2013 | if (-not(Merge-ResultFiles -CSVInput $MergeCSVs)) { 2014 | Write-Log 'The comparison found no differences between the two provided CSVs' -Component 'ProcessingResults' -Type 2 2015 | } else { 2016 | if ($ShowResults) { 2017 | $Script:ComparedResults | Sort-Object -Property url | Out-GridView -Title "Merge result between: $($Script:MergeCSVComputername1) and $($Script:MergeCSVComputername2)" -Wait 2018 | } 2019 | if ($OutputCSV) { 2020 | Build-OutputCSV -InputCSVs $MergeCSVs 2021 | } 2022 | } 2023 | } 2024 | if ($ShowResults) { 2025 | $Script:FinalResultList | Out-GridView -Title 'Intune Network test results' -Wait 2026 | } 2027 | } 2028 | } 2029 | 2030 | #Start coding! 2031 | Initialize-Script 2032 | if (-not($BrienMode)) { 2033 | Start-Tests 2034 | } 2035 | Start-ProcessingResults 2036 | Write-SettingsToLog 2037 | Write-Log 'Thanks for using INR' -Component 'INRMain' 2038 | Set-Location $CurrentLocation 2039 | Exit 0 2040 | -------------------------------------------------------------------------------- /Intune/IntuneNetworkRequirements/INRCustomList.csv: -------------------------------------------------------------------------------- 1 | "1.1.1.1", 53, UDP, 999 2 | "1.0.0.1", 53, UDP, 999 3 | "8.8.8.8", 53, UDP, 999 4 | "8.8.4.4", 53, UDP, 999 5 | "9.9.9.9", 53, UDP, 999 6 | "149.112.112.112", 53, UDP, 999 7 | "208.67.222.222", 53, UDP, 999 8 | "print.print.microsoft.com", 443, TCP,9982 9 | "register.print.microsoft.com", 443, TCP,9982 10 | "discovery.print.microsoft.com", 443, TCP,9982 11 | "notification.print.microsoft.com", 443, TCP,9982 12 | "graph.print.microsoft.com", 443, TCP,9982 13 | "graph.microsoft.com", 443, TCP,9982 14 | "login.microsoftonline.com", 443, TCP,9982 15 | "gcc-print.print.azure.us", 443, TCP,9981 16 | "gcc-discovery.print.azure.us", 443, TCP,9981 17 | "gcc-notification.print.azure.us", 443, TCP,9981 18 | "gcc-graph.print.azure.us", 443, TCP,9981 19 | "print.print.azure.us", 443, TCP,9981 20 | "discovery.print.azure.us", 443, TCP,9981 21 | "notification.print.azure.us", 443, TCP,9981 22 | "graph.print.azure.us", 443, TCP,9981 23 | "graph.microsoft.us", 443, TCP,9981 24 | "login.microsoftonline.us", 443, TCP,9981 25 | "westeurope.in.applicationinsights.azure.com", 443, TCP,9980 26 | "northeurope.in.applicationinsights.azure.com", 443, TCP,9980 27 | "eastus.in.applicationinsights.azure.com", 443, TCP,9980 28 | "westus.in.applicationinsights.azure.com", 443, TCP,9980 29 | "japaneast.in.applicationinsights.azure.com", 443, TCP,9980 30 | "centralus.in.applicationinsights.azure.com", 443, TCP,9980 31 | "japanwest.in.applicationinsights.azure.com", 443, TCP,9980 32 | "eastasia.in.applicationinsights.azure.com", 443, TCP,9980 33 | "southindia.in.applicationinsights.azure.com", 443, TCP,9980 34 | "southafricawest.in.applicationinsights.azure.com", 443, TCP,9980 35 | "functional.events.data.microsoft.com", 443, TCP, 9983 36 | "browser.events.data.msn.com", 80, TCP, 9983 37 | "www.microsoft.com", 443, TCP, 9983 38 | "self.events.data.microsoft.com", 443, TCP, 9983 39 | "self.events.data.microsoft.com", 80, TCP, 9983 40 | "v10.events.data.microsoft.com", 443, TCP, 9983 41 | "v10.events.data.microsoft.com", 80, TCP, 9983 42 | "telecommand.telemetry.microsoft.com", 443, TCP, 9983 43 | "www.telecommandsvc.microsoft.com", 443, TCP, 9983 44 | "watson.telemetry.microsoft.com", 443, TCP, 9983 45 | "watson.telemetry.microsoft.com", 80, TCP, 9983 46 | "watson.events.data.microsoft.com", 443, TCP, 9983 47 | "watson.events.data.microsoft.com", 80, TCP, 9983 48 | "adl.windows.com", 80, TCP, 9984 49 | "checkin.dm.microsoft.com", 443, TCP, 9985 50 | "manage.microsoft.com", 443, TCP, 9985 51 | "manage.microsoft.com", 443, TCP, 9986 52 | "graph.microsoft.com", 443, TCP, 9986 53 | "something.azureedge.net", 443, TCP, 9986 54 | "ipv6.msftconnecttest.com", 443, TCP, 9987 55 | "lgmsapeweu.blob.core.windows.net", 443, TCP, 9989 56 | "lgmsapewus2.blob.core.windows.net", 443, TCP, 9989 57 | "lgmsapesea.blob.core.windows.net", 443, TCP, 9989 58 | "lgmsapeaus.blob.core.windows.net", 443, TCP, 9989 59 | "lgmsapeind.blob.core.windows.net", 443, TCP, 9989 60 | "crl.verisign.com", 80, TCP, 9993 61 | "orgc3-crl.verisign.com", 80, TCP, 9993 62 | "crl.entrust.net", 80, TCP, 9993 63 | "ocsp.entrust.net", 80, TCP, 9993 64 | "crl.sectigo.com", 80, TCP, 9993 65 | "graph.windows.net", 443, TCP, 9990 66 | "graph.windows.net", 443, TCP, 9988 67 | "secure.aadcdn.microsoftonline-p.com", 443, TCP, 9990 68 | "login.microsoftonline.com", 443, TCP, 9990 69 | "www.d-trust.net", 80, TCP, 9993 70 | "root-c3-ca2-2009.ocsp.d-trust.net", 80, TCP, 9993 71 | "oneocsp.microsoft.com", 80, TCP, 9993 72 | "go.microsoft.com", 443, TCP, 9991 73 | "go.microsoft.com", 80, TCP, 9991 74 | "login.live.com", 443, TCP, 9991 75 | "activation.sls.microsoft.com", 443, TCP, 9991 76 | "validation.sls.microsoft.com", 443, TCP, 9991 77 | "activation-v2.sls.microsoft.com", 443, TCP, 9991 78 | "validation-v2.sls.microsoft.com", 443, TCP, 9991 79 | "displaycatalog.mp.microsoft.com", 443, TCP, 9991 80 | "licensing.mp.microsoft.com", 443, TCP, 9991 81 | "purchase.mp.microsoft.com", 443, TCP, 9991 82 | "displaycatalog.md.mp.microsoft.com", 443, TCP, 9991 83 | "licensing.md.mp.microsoft.com", 443, TCP, 9991 84 | "purchase.md.mp.microsoft.com", 443, TCP, 9991 85 | "play.google.com", 443, TCP, 9992 86 | "android.com", 443, TCP, 9992 87 | "google-analytics.com", 443, TCP, 9992 88 | "googleusercontent.com", 443, TCP, 9992 89 | "gstatic.com", 443, TCP, 9992 90 | "redirector.gvt1.com", 443, TCP, 9992 91 | "yt3.ggpht.com", 443, TCP, 9992 92 | "dl.google.com", 443, TCP, 9992 93 | "dl-ssl.google.com", 443, TCP, 9992 94 | "android.clients.google.com", 443, TCP, 9992 95 | "beacons.gvt2.com", 443, TCP, 9992 96 | "beacons5.gvt3.com", 443, TCP, 9992 97 | "googleapis.com", 443, TCP, 9992 98 | "accounts.google.com", 443, TCP, 9992 99 | "clients2.google.com", 443, TCP, 9992 100 | "clients3.google.com", 443, TCP, 9992 101 | "clients4.google.com", 443, TCP, 9992 102 | "clients5.google.com", 443, TCP, 9992 103 | "clients6.google.com", 443, TCP, 9992 104 | "omahaproxy.appspot.com", 443, TCP, 9992 105 | "android.clients.google.com", 443, TCP, 9992 106 | "apple.com", 80, TCP, 9993 107 | "crl.apple.com", 80, TCP, 9993 108 | "ocsp.apple.com", 80, TCP, 9993 109 | "developer.apple.com", 80, TCP, 9993 110 | "pki.google.com", 80, TCP, 9993 111 | "c.pki.goog", 80, TCP, 9993 112 | "clients1.google.com", 80, TCP, 9993 113 | "win1910.ipv6.microsoft.com", 443, TCP, 9994 114 | "has.spserv.microsoft.com", 443, TCP, 9995 115 | "remoteassistance.support.services.microsoft.com", 443, TCP, 181 116 | "rdprelayv3eastusprod-0.support.services.microsoft.com", 443, TCP, 181 117 | "remoteassistanceprodacs.communication.azure.com", 443, TCP, 181 118 | "edge.skype.com", 443, TCP, 181 119 | "aadcdn.msftauth.net", 443, TCP, 181 120 | "aadcdn.msauth.net", 443, TCP, 181 121 | "alcdn.msauth.net", 443, TCP, 181 122 | "wcpstatic.microsoft.com", 443, TCP, 181 123 | "browser.pipe.aria.microsoft.com", 443, TCP, 181 124 | "v10.events.data.microsoft.com", 443, TCP, 181 125 | "js.monitor.azure.com", 443, TCP, 181 126 | "edge.microsoft.com", 443, TCP, 181 127 | "go.trouter.communication.microsoft.com", 443, TCP, 181 128 | "trouter2-usce-1-a.trouter.teams.microsoft.com", 443, TCP, 181 129 | "api.flightproxy.skype.com", 443, TCP, 181 130 | "ecs.communication.microsoft.com", 443, TCP, 181 131 | "remotehelp.microsoft.com", 443, TCP, 181 132 | "trouter-azsc-usea-0-a.trouter.skype.com", 443, TCP, 181 133 | "AMSUA0101-RemoteAssistService-pubsub.webpubsub.azure.com", 443, TCP, 187 134 | "remoteassistanceweb-gcc.usgov.communication.azure.us", 443, TCP, 188 135 | "gcc.remotehelp.microsoft.com", 443, TCP, 188 136 | "gcc.relay.remotehelp.microsoft.com", 443, TCP, 188 137 | "gov.teams.microsoft.us", 443, TCP, 188 138 | "ztd.dds.microsoft.com", 443, TCP, 9999 139 | "cs.dds.microsoft.com", 443, TCP, 9999 140 | "tsfe.trafficshaping.dsp.mp.microsoft.com", 443, TCP, 164 141 | "au.download.windowsupdate.com", 443, TCP, 164 142 | "2.dl.delivery.mp.microsoft.com", 443, TCP, 164 143 | "download.windowsupdate.com", 443, TCP, 164 144 | "dl.delivery.mp.microsoft.com", 443, TCP, 164 145 | "geo.prod.do.dsp.mp.microsoft.com", 443, TCP, 164 146 | "catalog.update.microsoft.com", 443, TCP, 164 147 | "lgmsapeweu.blob.core.windows.net", 443, TCP, 182 148 | "TPMTESTURL.microsoftaik.azure.net", 443, TCP, 9998 149 | "sin.notify.windows.com", 443, TCP, 171 150 | "sinwns1011421.wns.windows.com", 443, TCP, 171 151 | "clientconfig.passport.net", 443, TCP, 169 152 | "windowsphone.com", 443, TCP, 169 153 | "c.s-microsoft.com", 443, TCP, 169 154 | "intunemaape1.eus.attest.azure.net", 443, TCP, 186 155 | "intunemaape2.eus2.attest.azure.net", 443, TCP, 186 156 | "intunemaape3.cus.attest.azure.net", 443, TCP, 186 157 | "intunemaape4.wus.attest.azure.net", 443, TCP, 186 158 | "intunemaape5.scus.attest.azure.net", 443, TCP, 186 159 | "intunemaape6.ncus.attest.azure.net", 443, TCP, 186 160 | "intunemaape7.neu.attest.azure.net", 443, TCP, 186 161 | "intunemaape8.neu.attest.azure.net", 443, TCP, 186 162 | "intunemaape9.neu.attest.azure.net", 443, TCP, 186 163 | "intunemaape10.weu.attest.azure.net", 443, TCP, 186 164 | "intunemaape11.weu.attest.azure.net", 443, TCP, 186 165 | "intunemaape12.weu.attest.azure.net", 443, TCP, 186 166 | "intunemaape13.jpe.attest.azure.net", 443, TCP, 186 167 | "intunemaape17.jpe.attest.azure.net", 443, TCP, 186 168 | "intunemaape18.jpe.attest.azure.net", 443, TCP, 186 169 | "intunemaape19.jpe.attest.azure.net", 443, TCP, 186 170 | "kv801.prod.do.dsp.mp.microsoft.com", 443, TCP, 172 171 | "kv801.prod.do.dsp.mp.microsoft.com", 80, TCP, 172 172 | "2.dl.delivery.mp.microsoft.com", 443, TCP, 172 173 | "manage.microsoft.com", 443, TCP, 163 174 | "manage.microsoft.com", 80, TCP, 163 175 | "EnterpriseEnrollment-s.manage.microsoft.com", 443, TCP, 163 176 | "EnterpriseEnrollment-s.manage.microsoft.com", 80, TCP, 163 177 | "swda01-mscdn.azureedge.net", 443, TCP, 170 178 | "swda02-mscdn.azureedge.net", 443, TCP, 170 179 | "swdb01-mscdn.azureedge.net", 443, TCP, 170 180 | "swdb02-mscdn.azureedge.net", 443, TCP, 170 181 | "swdc01-mscdn.azureedge.net", 443, TCP, 170 182 | "swdc02-mscdn.azureedge.net", 443, TCP, 170 183 | "swdd01-mscdn.azureedge.net", 443, TCP, 170 184 | "swdd02-mscdn.azureedge.net", 443, TCP, 170 185 | "swdin01-mscdn.azureedge.net", 443, TCP, 170 186 | "swdin02-mscdn.azureedge.net", 443, TCP, 170 187 | "approdimedatahotfix.azureedge.net", 443, TCP, 170 188 | "approdimedatapri.azureedge.net", 443, TCP, 170 189 | "approdimedatasec.azureedge.net", 443, TCP, 170 190 | "euprodimedatahotfix.azureedge.net", 443, TCP, 170 191 | "euprodimedatapri.azureedge.net", 443, TCP, 170 192 | "euprodimedatasec.azureedge.net", 443, TCP, 170 193 | "naprodimedatahotfix.azureedge.net", 443, TCP, 170 194 | "naprodimedatapri.azureedge.net", 443, TCP, 170 195 | "naprodimedatasec.azureedge.net", 443, TCP, 170 196 | "imeswda-afd-primary.manage.microsoft.com", 443, TCP, 9979 197 | "imeswda-afd-secondary.manage.microsoft.com", 443, TCP, 9979 198 | "imeswda-afd-hotfix.manage.microsoft.com", 443, TCP, 9979 199 | "imeswdb-afd-primary.manage.microsoft.com", 443, TCP, 9979 200 | "imeswdb-afd-secondary.manage.microsoft.com", 443, TCP, 9979 201 | "imeswdb-afd-hotfix.manage.microsoft.com", 443, TCP, 9979 202 | "imeswdc-afd-primary.manage.microsoft.com", 443, TCP, 9979 203 | "imeswdc-afd-secondary.manage.microsoft.com", 443, TCP, 9979 204 | "imeswdc-afd-hotfix.manage.microsoft.com", 443, TCP, 9979 205 | "macsidecar.manage.microsoft.com", 443, TCP, 9979 206 | "macsidecareu.manage.microsoft.com", 443, TCP, 9979 207 | "macsidecarap.manage.microsoft.com ", 443, TCP, 9979 208 | "account.live.com", 443, TCP, 97 209 | "login.live.com", 443, TCP, 97 210 | "discovery.dm.microsoft.com", 443, TCP, 9997 211 | "login.microsoftonline.com", 443, TCP, 56 212 | "login.microsoftonline.com", 80, TCP, 56 213 | "graph.windows.net", 443, TCP, 56 214 | "graph.windows.net", 80, TCP, 56 215 | "config.office.com", 443, TCP, 150 216 | "config.office.com", 80, TCP, 150 217 | "enterpriseregistration.windows.net", 443, TCP, 59 218 | "enterpriseregistration.windows.net", 80, TCP, 59 219 | "itunes.apple.com", 443, TCP, 178 220 | "itunes.apple.com", 80, TCP, 178 221 | "phobos.itunes-apple.com.akadns.net", 443, TCP, 178 222 | "phobos.itunes-apple.com.akadns.net", 80, TCP, 178 223 | "5-courier.push.apple.com", 443, TCP, 178 224 | "5-courier.push.apple.com", 5223, TCP, 178 225 | "5-courier.push.apple.com", 80, TCP, 178 226 | "phobos.apple.com", 443, TCP, 178 227 | "phobos.apple.com", 80, TCP, 178 228 | "ocsp.apple.com", 443, TCP, 178 229 | "ocsp.apple.com", 80, TCP, 178 230 | "ax.itunes.apple.com", 443, TCP, 178 231 | "ax.itunes.apple.com", 80, TCP, 178 232 | "ax.itunes.apple.com.edgesuite.net", 443, TCP, 178 233 | "ax.itunes.apple.com.edgesuite.net", 80, TCP, 178 234 | "s.mzstatic.com", 443, TCP, 178 235 | "s.mzstatic.com", 80, TCP, 178 236 | "a1165.phobos.apple.com", 443, TCP, 178 237 | "a1165.phobos.apple.com", 80, TCP, 178 238 | "intunecdnpeasd.azureedge.net", 443, TCP, 179 239 | "download.windowsupdate.com", 80, TCP, 164 240 | "download.windowsupdate.com", 443, TCP, 164 241 | "update.microsoft.com", 443, TCP, 164 242 | "update.microsoft.com", 80, TCP, 164 243 | "img-prod-cms-rt-microsoft-com.akamaized.net", 443, TCP, 9996 244 | "img-s-msn-com.akamaized.net", 80, TCP, 9996 245 | "livetileedge.dsx.mp.microsoft.com", 443, TCP, 9996 246 | "storeedgefd.dsx.mp.microsoft.com", 80, TCP, 9996 247 | "storecatalogrevocation.storequality.microsoft.com", 443, TCP, 9996 248 | "storecatalogrevocation.storequality.microsoft.com", 80, TCP, 9996 249 | "manage.devcenter.microsoft.com", 443, TCP, 9996 250 | "displaycatalog.mp.microsoft.com", 443, TCP, 9996 251 | "displaycatalog.mp.microsoft.com", 80, TCP, 9996 252 | "share.microsoft.com", 80, TCP, 9996 253 | "manage.devcenter.microsoft.com", 443, TCP, 9996 254 | "manage.devcenter.microsoft.com", 80, TCP, 9996 255 | "purchase.md.mp.microsoft.com", 443, TCP, 9996 256 | "purchase.md.mp.microsoft.com", 80, TCP, 9996 257 | "licensing.mp.microsoft.com", 443, TCP, 9996 258 | "licensing.mp.microsoft.com", 80, TCP, 9996 259 | "time.windows.com", 123, UDP, 165 -------------------------------------------------------------------------------- /Intune/IntuneNetworkRequirements/README.MD: -------------------------------------------------------------------------------- 1 | # Intune Network Requirements 2 | 3 | [Here's the full blog related to the script](https://manima.de/2024/08/intune-network-requirements-everything-i-learned/) 4 | 5 | ## What the INR offers 6 | 7 | The following tests are performed by default and in this order. Details about each test and how to use this script are in the blog. 8 | 9 | - DNS 10 | - TCP(/UDP) connection 11 | - HTTP(S) 12 | - TLS/SSL 13 | 14 | Optionally the following tests will be performed: 15 | 16 | - CRL verification (not the actual revocation of a cert, that is done when establishing a TLS/SSL connection on a TCP port) 17 | -- TLS/SSL Inspection (more later, but yes these two are connected) 18 | - Custom URL list that I created using the knowledge gathered from my research 19 | - Custom list of URLs 20 | -------------------------------------------------------------------------------- /Intune/IntuneNetworkRequirements/changelog.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 28th of December - Version 1.2.2 4 | 5 | - New: Added AppAndScript switch 6 | - Add: Fixed domains from MC964310 7 | 8 | ## 10th of December - Version 1.2.1 9 | 10 | - New: Created a first release 11 | - Add: zScaler IPS should now be successfully detected 12 | 13 | ## 1th of October - Version 1.2 14 | 15 | - Add: BrienMode added - this allows you to run the script multiple times on the same machine. See parameter description in the script. 16 | - Fix: Several descriptions fixed 17 | - Fix: Added some more logging messages 18 | - Mod: Initialize-Script was adjusted 19 | 20 | ## 5th of September 2024 - Version 1.1 21 | 22 | - Add: Universal Print 23 | - Note: This wasn't published as its own version 24 | 25 | ## 21th of August 2024 - Version 1.1 26 | 27 | - Fix: Small error in handling the timestamp from the filename 28 | - Add: New function Write-SettingsToLog 29 | - Fix: Minor comment additions for an upcoming cleanup 30 | -------------------------------------------------------------------------------- /Intune/Platform Scripts/Reset-WindowsUpdateSettings.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script will nuke all Windows Update related keys - made with Intune and Autopatch in mind. 4 | .DESCRIPTION 5 | This is to clean a computer completely of any Windows Update related keys. Very handy if you're switching to something like Autopatch. 6 | * Will stop relevant services such as IME and Autopatch 7 | * Will delete legacy GPO/local policy paths (SOFTWARE\Policies\Windows\WindowsUpdate) 8 | * Will delete CSPs (SOFTWARE\Microsoft\PolicyManager\current\device\Update) 9 | * Will delete GPCache folder to get rid of stuck policies (SOFTWARE\Microsoft\WindowsUpdate\UpdatePolicy\GPCache) 10 | * Will do gpupdate (if applicable) and Intune sync 11 | * Will do ConfigMgr machine policy and software update scan actions (if applicable) 12 | Should get rid of most of your problems when dealing with sticky policies. 13 | ################################################# 14 | DO NOT RUN THIS ON A REGULAR BASIS. This script is a canonball, not a bullet. 15 | ################################################# 16 | .PARAMETER ResetLocalPolicies 17 | Enables or disables the reset of the local .pol file for machine settings as this can have unintended consequences 18 | .PARAMETER BackupDisabled 19 | ##NOT RECOMMENDED## Disables all backups 20 | .PARAMETER GPUpdateTimeout 21 | Defines how long the script should wait for the gpupdate 22 | .PARAMETER IntuneSyncTimeout 23 | Defines how long the script should wait for the Intune sync to run 24 | .PARAMETER WorkingDirectory 25 | Specifies the working directory where the script will be executed. The default value is "C:\RWU\". 26 | .PARAMETER LogDirectory 27 | Specifies the directory where log files will be stored. The default value is "C:\RWU\". 28 | .EXAMPLE 29 | This example will run the script with, create the registry key backup in C:\RWU and restart required services. 30 | 31 | .\Reset-WindowsUpdateSettings.ps1 32 | .EXAMPLE 33 | If you have a slower group policy update you might want to use this as it will increase the wait time from 60 seconds to 120 seconds. 34 | This will also prevent the deletion of the .pol file which hold the machines policies. 35 | 36 | .\Reset-WindowsUpdateSettings.ps1 -ResetLocalPolicies:$False -GPupdateTime 120 37 | .NOTES 38 | Version: 1.1 39 | Author: Martin Himken 40 | Original script name: Reset-WindowsUpdateSettings.ps1 41 | Initial creation date: 10.03.25 42 | Last change: 17.03.25 43 | Change: Added PARAMETER and EXAMPLE description 44 | #> 45 | 46 | param( 47 | [System.IO.DirectoryInfo]$WorkingDirectory = "C:\RWU\", 48 | [System.IO.DirectoryInfo]$LogDirectory = "C:\RWU\", 49 | [boolean]$ResetLocalPolicies=$true, 50 | [switch]$BackupDisabled, 51 | [int]$GPUpdateTimeout = 60, 52 | [int]$IntuneSyncTimeout = 60 53 | ) 54 | function Get-ScriptPath { 55 | <# 56 | .SYNOPSIS 57 | Get the current script path. 58 | #> 59 | if ($PSScriptRoot) { 60 | # Console or VS Code debug/run button/F5 temp console 61 | $ScriptRoot = $PSScriptRoot 62 | } else { 63 | if ($psISE) { 64 | Split-Path -Path $psISE.CurrentFile.FullPath 65 | } else { 66 | if ($profile -match 'VScode') { 67 | # VS Code "Run Code Selection" button/F8 in integrated console 68 | $ScriptRoot = Split-Path $psEditor.GetEditorContext().CurrentFile.Path 69 | } else { 70 | Write-Output 'unknown directory to set path variable. exiting script.' 71 | exit 72 | } 73 | } 74 | } 75 | $Script:PathToScript = $ScriptRoot 76 | } 77 | function Initialize-Script { 78 | <# 79 | .SYNOPSIS 80 | Will initialize most of the required variables throughout this script. 81 | #> 82 | if($Script:DateTime){ 83 | Clear-Variable DateTime 84 | } 85 | $Script:DateTime = Get-Date -Format yyyyMMdd_HHmmss 86 | if (-not($Script:CurrentLocation)) { 87 | $Script:CurrentLocation = Get-Location 88 | } 89 | if (-not(Test-Path $WorkingDirectory )) { New-Item $WorkingDirectory -ItemType Directory -Force | Out-Null } 90 | if ((Get-Location).path -ne $WorkingDirectory) { 91 | Set-Location $WorkingDirectory 92 | } 93 | Get-ScriptPath 94 | if (-not($Script:LogFile)) { 95 | $LogPrefix = 'RWU' #Reset Windows Update 96 | $Script:LogFile = Join-Path -Path $LogDirectory -ChildPath ('{0}_{1}.log' -f $LogPrefix, $Script:DateTime) 97 | if (-not(Test-Path $LogDirectory)) { New-Item $LogDirectory -ItemType Directory -Force | Out-Null } 98 | } 99 | Write-Log 'Script is initialized - gathering information and initializing directories' -Component 'InitializeScript' 100 | #####Custom content 101 | if (-not($BackupDisabled)) { 102 | Write-Log 'Creating Backup directory' -Component 'InitializeScript' 103 | $Script:BackupDirectory = $(Join-Path -Path $WorkingDirectory -ChildPath "$Script:DateTime") 104 | New-Item -Path $Script:BackupDirectory -ItemType Directory -Force | Out-Null 105 | } 106 | $Script:IsIntuneDevice = ($null -ne (Get-Service IntuneManagementExtension -ErrorAction SilentlyContinue)) 107 | if ($Script:IsIntuneDevice) { 108 | Write-Log 'Device is managed by Intune' -Component 'InitializeScript' 109 | $IsAutoPatchDevice = Test-Path "C:\Program Files\Windows Autopatch Client Broker\ClientBroker\ClientBroker.exe" 110 | if (-not($IsAutoPatchDevice)) { 111 | Write-Log 'Device is not managed by Autopatch' -Component 'InitializeScript' 112 | } else { 113 | Write-Log 'Device is managed by Autopatch' -Component 'InitializeScript' 114 | } 115 | } else { 116 | Write-Log 'Device is not managed by Intune' -Component 'InitializeScript' 117 | } 118 | $IsActiveDirectoryDevice = (Get-CimInstance Win32_ComputerSystem).PartOfDomain 119 | if ($IsActiveDirectoryDevice) { 120 | Write-Log 'Device is domain joined' -Component 'InitializeScript' 121 | } else { 122 | Write-Log 'Device is not domain joined' -Component 'InitializeScript' 123 | } 124 | $IsConfigMgrDevice = ($null -ne (Get-Service ccmexec -ErrorAction SilentlyContinue)) 125 | if ($IsConfigMgrDevice) { 126 | Write-Log 'Device is running ConfigMgr client' -Component 'InitializeScript' 127 | } else { 128 | Write-Log 'Device is not running ConfigMgr client' -Component 'InitializeScript' 129 | } 130 | } 131 | function Write-Log { 132 | <# 133 | .DESCRIPTION 134 | This is a modified version of the script by Ryan Ephgrave. 135 | .LINK 136 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 137 | #> 138 | Param ( 139 | [Parameter(Mandatory = $false)] 140 | $Message, 141 | $Component, 142 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 143 | [ValidateSet('1', '2', '3')][int]$Type 144 | ) 145 | if (-not($NoLog)) { 146 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 147 | $Date = Get-Date -Format 'MM-dd-yyyy' 148 | if (-not($Component)) { $Component = 'Runner' } 149 | if (-not($ToConsole)) { 150 | $LogMessage = "" 151 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 152 | } elseif ($ToConsole) { 153 | switch ($type) { 154 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 155 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 156 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 157 | default { Write-Host "T:$Type C:$Component M:$Message" } 158 | } 159 | } 160 | } 161 | } 162 | function Reset-LocalPolicies { 163 | $MachinePolicyFile = "C:\Windows\System32\GroupPolicy\Machine\Registry.pol" 164 | if(-not(Test-Path -Path $MachinePolicyFile)){ 165 | return $false 166 | } 167 | if (-not($BackupDisabled)) { 168 | Write-Log "Creating backup of machine registry.pol to $($Script:BackupDirectory)" -Component 'ResetLocalPolicies' 169 | Copy-Item $MachinePolicyFile -Destination $Script:BackupDirectory -Force 170 | } 171 | Write-Log 'Removing registry.pol' -Component 'ResetLocalPolicies' 172 | Remove-Item -Path $MachinePolicyFile -Force 173 | if ($IsActiveDirectoryDevice -and (Test-ComputerSecureChannel)) { 174 | Write-Log 'Computer is domain joined and domain is reachable - forcing gpupdate with target computer' -Component 'ResetLocalPolicies' 175 | $GPupdatePath = Join-Path -Path "$Env:SystemRoot" -ChildPath "\System32\gpupdate.exe" 176 | $GPUpdateArguments = "/target:computer /wait:$GPUpdateTimeout /force" 177 | Write-Log "Starting gpupdate with a wait time of $GPUpdateTimeout" -Component 'ResetLocalPolicies' 178 | $GPUpdate = Start-Process -FilePath $GPupdatePath -ArgumentList $GPUpdateArguments -PassThru -WindowStyle Hidden -Wait 179 | if ($GPUpdate.ExitCode -eq 0) { 180 | Write-Log "Sync successful" -Component 'ResetLocalPolicies' 181 | } else { 182 | Write-Log "Sync unsuccessful - maybe the wait time was not enough." -Component 'ResetLocalPolicies' -Type 2 183 | } 184 | } 185 | if ($IsConfigMgrDevice) { 186 | Write-Log "Starting ConfigMgr client actions" -Component 'ResetLocalPolicies' 187 | try { 188 | Invoke-CimMethod -Namespace 'root\CCM' -ClassName SMS_Client -MethodName TriggerSchedule -Arguments @{sScheduleID='{00000000-0000-0000-0000-000000000021}'} -ErrorAction Stop 189 | Write-Log "ConfigMgr Machine Policy client action successful" -Component 'ResetLocalPolicies' 190 | } 191 | catch { 192 | Write-Log "ConfigMgr Machine Policy client action unsuccessful" -Component 'ResetLocalPolicies' -Type 2 193 | } 194 | try { 195 | Invoke-CimMethod -Namespace 'root\CCM' -ClassName SMS_Client -MethodName TriggerSchedule -Arguments @{sScheduleID='{00000000-0000-0000-0000-000000000113}'} -ErrorAction Stop 196 | Write-Log "ConfigMgr Software Update Scan client action successful" -Component 'ResetLocalPolicies' 197 | } 198 | catch { 199 | Write-Log "ConfigMgr Software Update Scan client action unsuccessful" -Component 'ResetLocalPolicies' -Type 2 200 | } 201 | } 202 | } 203 | function Restart-IMEService { 204 | Write-Log 'Attempting to restart the IME service' -Component 'RestartIME' 205 | Get-Service IntuneManagementExtension | Restart-Service -Force 206 | } 207 | function Start-IMESync { 208 | Write-Log "Starting IME sync and waiting $IntuneSyncTimeout seconds" -Component 'StartSync' 209 | $Shell = New-Object -ComObject Shell.Application 210 | $Shell.open("intunemanagementextension://syncapp") 211 | $GUIDs = (Get-ChildItem HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\ -Depth 0).PSChildName 212 | foreach($GUID in $GUIDs){ 213 | $IsIntuneGUID = (Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Enrollments\$GUID" -Name AADResourceID) -eq "https://manage.microsoft.com/" 214 | if($IsIntuneGUID){ 215 | $DeviceEnrollerPath = "C:\Windows\System32\DeviceEnroller.exe" 216 | $DeviceEnrollerArgs = "/o $GUID /c /b" 217 | Write-Log 'Running devicecontroller to enforce the CSPs from Intune' -Component 'StartSync' 218 | $DeviceEnroller = Start-Process -FilePath $DeviceEnrollerPath -ArgumentList $DeviceEnrollerArgs -PassThru -WindowStyle Hidden -Wait 219 | if($DeviceEnroller.ExitCode -ne 0){ 220 | Write-Log 'You managed to make the Deviceenroller return with a non-zero return code somehow...' -Component 'StartSync' -Type 3 221 | } 222 | } 223 | } 224 | Start-Sleep -Seconds $IntuneSyncTimeout 225 | } 226 | function Backup-RegistryToReg { 227 | param( 228 | $Path, 229 | [Parameter()][ValidateSet('HKEY_LOCAL_MACHINE', 'HKEY_CURRENT_USER', 'HKEY_USERS')] 230 | $Root, 231 | $RegName 232 | ) 233 | $BackupTarget = $(Join-Path -Path $Script:BackupDirectory -ChildPath $RegName) 234 | $FullRegistryPath = $(Join-Path -Path $Root -ChildPath $Path) 235 | $RegPath = Join-Path -Path "$Env:SystemRoot" -ChildPath "\System32\reg.exe" 236 | $RegArguments = "export $FullRegistryPath $BackupTarget /y" 237 | $Reg = Start-Process -FilePath $RegPath -ArgumentList $RegArguments -PassThru -WindowStyle Hidden -Wait 238 | if ($Reg.ExitCode -eq 0) { 239 | Write-Log "Backup of $FullRegistryPath to $BackupTarget successfull" -Component 'BackupRegistry' 240 | } else { 241 | Write-Log 'Backup failed!' -Component 'BackupRegistry' -Type 3 242 | throw 243 | } 244 | } 245 | function Reset-KnowRWURegistryPaths { 246 | param( 247 | $path 248 | ) 249 | $CurrentRegPathPSDrive = $(Join-Path -Path "HKLM:\" -ChildPath $path) 250 | $CurrentRegPathExists = Test-Path $CurrentRegPathPSDrive 251 | if (-not($CurrentRegPathExists)) { 252 | Write-Log 'This registry path already seems to be removed - continue' -Component 'ResetKnownRegistryPaths' 253 | return $false 254 | } 255 | if (-not($BackupDisabled)) { 256 | Write-Log "Creating backup of registry path related to windows update to $($Script:BackupDirectory)" -Component 'ResetKnownRegistryPaths' 257 | Backup-RegistryToReg -Path $path -Root HKEY_LOCAL_MACHINE -RegName "$((Get-Item $CurrentRegPathPSDrive).PSChildName).reg" 258 | } 259 | Write-Log "Removing $CurrentRegPathPSDrive registry paths" -Component 'ResetKnownRegistryPaths' 260 | Remove-Item -Path $CurrentRegPathPSDrive -Force -Recurse 261 | return $true 262 | } 263 | function Stop-UpdateServices { 264 | if ($IsAutoPatchDevice) { 265 | Write-Log 'Attempting to stop the Autopatch service' -Component 'StopUpdateServices' 266 | Get-Service ClientBrokerUpgrader | Stop-Service -Force 267 | } 268 | Write-Log 'Attempting to stop the Windows Update service' -Component 'StopUpdateServices' 269 | Get-Service wuauserv | Stop-Service -Force 270 | } 271 | function Start-UpdateServices { 272 | Write-Log 'Attempting to start the Windows Update service' -Component 'StartUpdateServices' 273 | Get-Service wuauserv | Start-Service -ErrorAction Continue 274 | Start-Sleep -Seconds 10 275 | if ($IsAutoPatchDevice) { 276 | Write-Log 'Attempting to start the Autopatch service' -Component 'StartUpdateServices' 277 | Get-Service ClientBrokerUpgrader | Start-Service 278 | } 279 | } 280 | 281 | #Start Coding! 282 | Initialize-Script 283 | try { 284 | Stop-UpdateServices 285 | Reset-KnowRWURegistryPaths -path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" | Out-Null 286 | Reset-KnowRWURegistryPaths -path "SOFTWARE\Microsoft\WindowsUpdate\UpdatePolicy\GPCache" | Out-Null 287 | $CSPUpdatePath = Reset-KnowRWURegistryPaths -path "SOFTWARE\Microsoft\PolicyManager\current\device\Update" 288 | if ($Script:IsIntuneDevice -and $CSPUpdatePath) { 289 | Write-Log 'Since we removed the current CSP configuration we should try to trigger a resync' -Component 'RSUMain' 290 | Restart-IMEService 291 | Start-IMESync 292 | } 293 | if ($ResetLocalPolicies) { 294 | if(-not(Reset-LocalPolicies)){ 295 | Write-Log 'No local .pol file' -Component 'RSUMain' 296 | } 297 | } 298 | Start-UpdateServices 299 | } catch { 300 | Write-Log 'Something went wrong! Please consult the log' -Component 'Errorhandling' 301 | Write-Log "$($Error[0].Exception.Message)" -Component 'Errorhandling' 302 | Exit 1 303 | } 304 | Set-Location $Script:CurrentLocation 305 | Exit 0 306 | -------------------------------------------------------------------------------- /Intune/README.MD: -------------------------------------------------------------------------------- 1 | # Warning notice 2 | 3 | Tools around Intune - use at your own risk! 4 | -------------------------------------------------------------------------------- /Intune/Remediations/Detect-NewOutlookDEVICE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script detects new Outlook installation for DEVICE assignments 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Detect-NewOutlookDevice 8 | 9 | Run this script using the logged-on credentials: No 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | $DetectNewOutlookDEVICE = Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -eq "Microsoft.OutlookForWindows" } 14 | $DetectNewOutlookAllUsers = Get-AppxPackage -AllUsers | Where-Object { $_.Name -eq "Microsoft.OutlookForWindows" } 15 | 16 | if ($DetectNewOutlookDEVICE -or $DetectNewOutlookAllUsers) { 17 | Exit 1 18 | } else { 19 | Write-Host "New Outlook seems to not be installed for this device or all users" 20 | Exit 0 21 | } -------------------------------------------------------------------------------- /Intune/Remediations/Detect-NewOutlookUSER.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script detects new Outlook installation for USER assignments 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Detect-NewOutlookUSER 8 | 9 | Run this script using the logged-on credentials: Yes 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | $DetectNewOutlook = Get-AppxPackage | Where-Object { $_.Name -eq "Microsoft.OutlookForWindows" } -ErrorAction SilentlyContinue 14 | 15 | if ($DetectNewOutlook) { 16 | Exit 1 17 | } else { 18 | Write-Host "New Outlook seems to not be installed for this user" 19 | Exit 0 20 | } -------------------------------------------------------------------------------- /Intune/Remediations/Detect-PrintToPDFandXPS.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script detects a common blocker for feature Upgrades with Windows 11. 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Detect-PrintToPDForXPS 8 | 9 | Run this script using the logged-on credentials: No 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | $PrintToPDF = (Get-WindowsOptionalFeature -Online -FeatureName "Printing-PrintToPDFServices-Features").State -eq "Enabled" 14 | $XPS = (Get-WindowsOptionalFeature -Online -FeatureName "Printing-XPSServices-Features").State -eq "Enabled" 15 | 16 | if ($PrintToPDF -or $XPS) { 17 | Exit 1 18 | } else { 19 | Write-Host "Features PrintToPDF and XPS not installed" 20 | Exit 0 21 | } -------------------------------------------------------------------------------- /Intune/Remediations/Remediate-NewOutlookDEVICE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script removes new Outlook installation for DEVICE assignments 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Remediate-NewOutlookDEVICE 8 | 9 | Run this script using the logged-on credentials: No 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | 14 | try { 15 | Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -eq "Microsoft.OutlookForWindows" } | Remove-AppxProvisionedPackage -Online -ErrorAction Stop 16 | Get-AppxPackage -AllUsers | Where-Object { $_.Name -eq "Microsoft.OutlookForWindows" } | Remove-AppxPackage -ErrorAction Stop 17 | Write-Host "Removal of new Outlook app successful" 18 | 19 | } catch { 20 | Write-Error "Removal of new Outlook app failed" 21 | } 22 | -------------------------------------------------------------------------------- /Intune/Remediations/Remediate-NewOutlookUSER.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script removes new Outlook installation for USER assignments 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Remediate-NewOutlookUSER 8 | 9 | Run this script using the logged-on credentials: Yes 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | 14 | try { 15 | Get-AppxPackage | Where-Object { $_.Name -eq "Microsoft.OutlookForWindows" } | Remove-AppxPackage -ErrorAction SilentlyContinue 16 | Write-Host "Removal of new Outlook app successful" 17 | 18 | } catch { 19 | Write-Error "Removal of new Outlook app failed" 20 | } 21 | -------------------------------------------------------------------------------- /Intune/Remediations/Remediate-PrintToPDFandXPS.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script remediates a common blocker for feature Upgrades with Windows 11. 4 | .NOTES 5 | Version: 1.0 6 | Author: Martin Himken 7 | Original script name: Remediate-PrintToPDForXPS 8 | 9 | Run this script using the logged-on credentials: No 10 | Enforce script signature check: No 11 | Run script in 64-bit PowerShell: Yes 12 | #> 13 | 14 | try { 15 | Disable-WindowsOptionalFeature -FeatureName "Printing-PrintToPDFServices-Features" -ErrorAction Stop 16 | Disable-WindowsOptionalFeature -FeatureName "Printing-XPSServices-Features" -ErrorAction Stop 17 | Write-Host "Removal of features successful" 18 | 19 | } catch { 20 | Write-Error "Removal of features failed" 21 | } -------------------------------------------------------------------------------- /Intune/ScriptWrapper.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script can be used to launch other scripts and only allow them a specific amount of time to run. 4 | .DESCRIPTION 5 | This script is specifcally written to run Powershell scripts. The script will 6 | * Use Start-Job that uses Start-Process to launch a PowerShell.exe 7 | * Start-Process is using -WorkingDirectory W -FilePath X -NoNewWindow -Wait -RedirectStandardOutput `"scriptoutput.log`" -ArgumentList Y 8 | * PowerShell.exe is started using -NonInteractive -NoProfile -WindowStyle Hidden -ExecutionPolicy bypass -File ScriptPath ArgumentsForScript 9 | !!Attention!! 10 | By default this will use the powershell.exe located in system32 _not_ sysWOW64! If required, change it. 11 | Every output from the launched script will be written to "Scriptoutput.log" in the same location as this wrapper 12 | .PARAMETER Usex86PowerShell 13 | By default the script will use the x64-PowerShell executable. 14 | .PARAMETER ScriptPath 15 | The exact path to the .ps1 file - no other files are supported at this time. 16 | .PARAMETER TimeOutInSeconds 17 | Specify the timeout for the wrapper to wait until the sub-script is stopped. 18 | .PARAMETER ArgumentsForScript 19 | Pass the arguments exactly as you would when running the script as a string. 20 | .EXAMPLE 21 | .\ScriptWrapper.ps1 -ScriptPath C:\temp\Runner.ps1 -TimeOutInSeconds 9 -ArgumentsForScript "-Time 10" 22 | This example would start the .ps1 file in C:\temp\ and wait for a maximum of 9 seconds. 23 | .NOTES 24 | Version: 1.1 25 | Intial creation date: 01.05.2024 26 | Last change date: 02.05.2024 27 | Latest changes: https://github.com/MHimken/toolbox/tree/main/Intune 28 | #> 29 | [CmdletBinding()] 30 | param( 31 | [Parameter(Mandatory = $true)] 32 | [string]$ScriptPath, 33 | [Parameter(Mandatory = $true)] 34 | [int]$TimeOutInSeconds, 35 | [string]$ArgumentsForScript, 36 | [switch]$Usex86PowerShell 37 | ) 38 | if($Usex86PowerShell){ 39 | $ChildPath = "\sysWOW64\WindowsPowerShell\v1.0\" 40 | } 41 | else{ 42 | $ChildPath = "\system32\WindowsPowerShell\v1.0\" 43 | } 44 | $JobName = New-Guid 45 | $TimeoutInMilliseconds = [timespan]::FromSeconds($TimeOutInSeconds).TotalMilliseconds 46 | $ExecutePath = Join-Path $env:Systemroot -ChildPath $ChildPath 47 | $Executable = "powershell.exe" 48 | $ArgumentListForPowerShell = "-NonInteractive -NoProfile -WindowStyle Hidden -ExecutionPolicy bypass -File $ScriptPath " + $ArgumentsForScript 49 | $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() 50 | $ScriptBlockInit = [scriptblock]::Create("Start-Process -WorkingDirectory $ExecutePath -FilePath $Executable -NoNewWindow -Wait -RedirectStandardOutput `"scriptoutput.log`" -ArgumentList `"$ArgumentListForPowerShell`"") 51 | $parametersForJob = @{ 52 | Name = $JobName 53 | ScriptBlock = $ScriptBlockInit 54 | } 55 | Start-Job @parametersForJob | Out-Null 56 | while ($Stopwatch.ElapsedMilliseconds -le $TimeoutInMilliseconds -and (Get-Job -Name $JobName).State -eq 'Running') { 57 | Start-Sleep -Milliseconds 1000 58 | } 59 | 60 | if ((Get-Job -Name $JobName).State -ne 'Running') { 61 | Write-Information 'Script not running anymore' 62 | $ExitCode = 0 63 | } else { 64 | Write-Warning 'Script is still running and will not be forcefully stopped' 65 | $ExitCode = 1 66 | } 67 | #Cleanup Jobs 68 | Get-Job -Name $JobName | ForEach-Object { Stop-Job $_; Remove-Job $_ -Force } 69 | $Stopwatch.Stop() 70 | $ElapsedTime = [timespan]::FromMilliseconds($Stopwatch.ElapsedMilliseconds).Seconds 71 | Write-Information "Time elapsed according to timer: $ElapsedTime" 72 | Exit $ExitCode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Martin Himken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Other/List-EAMApplications.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script will provide the current list of all EAM applications 4 | .DESCRIPTION 5 | Will return the list in an Out-Gridview. This script requires the Graph module to be installed. 6 | .EXAMPLE 7 | .\List-EAMApplications.ps1 -TenantAPIToUse /beta 8 | This example is all you need - it will request an interactive auth and get the applications. 9 | NOTE THAT THIS REQUIRES A USER WITH AN ACTIVE EAM LICENSE AND INTERACTIVE AUTHENTICATION. A custom application will _not_ work! 10 | .NOTES 11 | Version: 1.0 12 | Versionname: Initial 13 | Intial creation date: 07.03.2025 14 | Last change date: 08.03.2025 15 | Latest changes: https://github.com/MHimken/toolbox/tree/main/Autopilot/MEMNetworkRequirements/changelog.md 16 | #> 17 | param( 18 | [Parameter(Mandatory = $false)] 19 | [System.IO.DirectoryInfo]$WorkingDirectory = "$env:SystemDrive\EAMApps\", 20 | 21 | [Parameter(Mandatory = $false)] 22 | [System.IO.DirectoryInfo]$LogDirectory = "$WorkingDirectory\Logs\", 23 | 24 | [Parameter(Mandatory = $false)] 25 | [ValidateSet('/beta', '/v1.0')] 26 | [String]$TenantAPIToUse = '/beta', 27 | 28 | [Parameter(Mandatory = $True, ParameterSetName = 'CertificateAuth')] 29 | [String]$CertificateThumbprint, 30 | 31 | [Parameter(Mandatory = $True, ParameterSetName = 'CertificateAuth')] 32 | [Parameter(Mandatory = $True, ParameterSetName = 'SignInAuthCustom')] 33 | [String]$ClientID, 34 | 35 | [Parameter(Mandatory = $True, ParameterSetName = 'CertificateAuth')] 36 | [Parameter(Mandatory = $False, ParameterSetName = 'SignInAuth')] 37 | [Parameter(Mandatory = $True, ParameterSetName = 'SignInAuthCustom')] 38 | [String]$TenantID, 39 | 40 | [Parameter(Mandatory = $true, ParameterSetName = 'AccessTokenAuth')] 41 | [Securestring]$AccessToken 42 | ) 43 | 44 | function Get-ScriptPath { 45 | <# 46 | .SYNOPSIS 47 | Get the current script path. 48 | #> 49 | if ($PSScriptRoot) { 50 | # Console or VS Code debug/run button/F5 temp console 51 | $ScriptRoot = $PSScriptRoot 52 | } else { 53 | if ($psISE) { 54 | Split-Path -Path $psISE.CurrentFile.FullPath 55 | } else { 56 | if ($profile -match 'VScode') { 57 | # VS Code "Run Code Selection" button/F8 in integrated console 58 | $ScriptRoot = Split-Path $psEditor.GetEditorContext().CurrentFile.Path 59 | } else { 60 | Write-Output 'unknown directory to set path variable. exiting script.' 61 | exit 62 | } 63 | } 64 | } 65 | $Script:PathToScript = $ScriptRoot 66 | } 67 | function Initialize-Script { 68 | <# 69 | .SYNOPSIS 70 | Will initialize most of the required variables throughout this script. 71 | #> 72 | $Script:DateTime = Get-Date -Format yyyyMMdd_HHmmss 73 | if (-not($Script:CurrentLocation)) { 74 | $Script:CurrentLocation = Get-Location 75 | } 76 | if (-not(Test-Path $WorkingDirectory )) { New-Item $WorkingDirectory -ItemType Directory -Force | Out-Null } 77 | if ((Get-Location).path -ne $WorkingDirectory) { 78 | Set-Location $WorkingDirectory 79 | } 80 | Get-ScriptPath 81 | if (-not($Script:LogFile)) { 82 | $LogPrefix = 'EAML' #Enterprise App Management List 83 | $Script:LogFile = Join-Path -Path $LogDirectory -ChildPath ('{0}_{1}.log' -f $LogPrefix, $Script:DateTime) 84 | if (-not(Test-Path $LogDirectory)) { New-Item $LogDirectory -ItemType Directory -Force | Out-Null } 85 | } 86 | } 87 | function Write-Log { 88 | <# 89 | .DESCRIPTION 90 | This is a modified version of the script by Ryan Ephgrave. 91 | .LINK 92 | https://www.ephingadmin.com/powershell-cmtrace-log-function/ 93 | #> 94 | Param ( 95 | [Parameter(Mandatory = $false)] 96 | $Message, 97 | $Component, 98 | # Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) 99 | [ValidateSet('1', '2', '3')][int]$Type 100 | ) 101 | if (-not($NoLog)) { 102 | $Time = Get-Date -Format 'HH:mm:ss.ffffff' 103 | $Date = Get-Date -Format 'MM-dd-yyyy' 104 | if (-not($Component)) { $Component = 'Runner' } 105 | if (-not($ToConsole)) { 106 | $LogMessage = "" 107 | $LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LogFile 108 | } elseif ($ToConsole) { 109 | switch ($type) { 110 | 1 { Write-Host "T:$Type C:$Component M:$Message" } 111 | 2 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Yellow -ForegroundColor Black } 112 | 3 { Write-Host "T:$Type C:$Component M:$Message" -BackgroundColor Red -ForegroundColor White } 113 | default { Write-Host "T:$Type C:$Component M:$Message" } 114 | } 115 | } 116 | } 117 | } 118 | function Connect-ToGraph { 119 | param( 120 | [string]$AuthMethod 121 | ) 122 | switch -Exact ($AuthMethod) { 123 | 'SignInAuth' { 124 | $Splat = @{} 125 | if (-not([string]::IsNullOrEmpty($TenantID))) { 126 | $Splat['TenantId'] = $TenantID 127 | } 128 | $Splat['Scopes'] = $RequiredGraphScopes 129 | } 130 | 'SignInAuthCustom' { 131 | $Splat = @{ 132 | 'TenantId' = $TenantID 133 | 'ClientId' = $ClientID 134 | } 135 | } 136 | 'CertificateAuth' { 137 | $Splat = @{ 138 | 'TenantId' = $TenantID 139 | 'ClientId' = $ClientID 140 | 'CertificateThumbPrint' = $CertificateThumbprint 141 | } 142 | } 143 | 'AccessTokenAuth' { 144 | $Splat = @{ 145 | 'AccessToken' = $AccessToken 146 | } 147 | } 148 | } 149 | Connect-MgGraph @Splat 150 | $Splat = $Null 151 | $MgContext = Get-MgContext 152 | if ($null -eq $($MgContext)) { 153 | Write-Log -Message 'The connection could not be established, please verify you can connect by using Connect-MgGraph' -Component 'GFDConnectToGraph' -Type 3 154 | Return $false 155 | } 156 | if ($Null -ne ($RequiredGraphScopes | Where-Object { $_ -notin $MgContext.Scopes })) { 157 | Write-Log -Message 'The required Microsoft Graph scopes are not present in the authentication context. Please use Disconnect-MgGraph and try again' -Component 'GFDConnectToGraph' -Type 3 158 | Return $false 159 | } 160 | 161 | Return $true 162 | } 163 | function Get-nextLinkData { 164 | param( 165 | $OriginalObject 166 | ) 167 | $nextLink = $OriginalObject.'@odata.nextLink' 168 | $Results = $OriginalObject 169 | while ($nextLink) { 170 | $Request = Invoke-MgGraphRequest -Uri $nextLink 171 | $Results.value += $Request.value 172 | $nextLink = '' 173 | $nextLink = $Request.'@odata.nextLink' 174 | } 175 | return $Results 176 | } 177 | 178 | #Start Coding! 179 | Initialize-Script 180 | 181 | $MgContext = Get-MgContext 182 | if ($null -eq $($MgContext)) { 183 | if (Connect-ToGraph -AuthMethod $PSCmdlet.ParameterSetName) { 184 | Write-Log -Message 'Connection failed - please consult the logs' -Component 'GFDCore' -Type 2 185 | } 186 | } else { 187 | if ($Null -ne ($RequiredGraphScopes | Where-Object { $_ -notin $MgContext.Scopes })) { 188 | #throw [System.Security.Authentication.AuthenticationException]::New('The required Microsoft Graph scopes are not present in the authentication context. Please use Disconnect-MgGraph and try again') 189 | Write-Log -Message 'The required Microsoft Graph scopes are not present in the authentication context. Please use Disconnect-MgGraph and try again' -Component 'GFDCore' -Type 3 190 | } 191 | } 192 | $EAMAppsQuery = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppCatalogPackages" 193 | $AllEAEMApps = Get-nextLinkData -OriginalObject $EAMAppsQuery 194 | $ResultList = [System.Collections.ArrayList]::new() 195 | $Counter = 0 196 | foreach ($EAMApp in $AllEAEMApps.value) { 197 | $EAMAppObject = [PSCustomObject]@{ 198 | Counter = $Counter 199 | DisplayName = $EAMApp.productDisplayName 200 | BranchDisplayName = $EAMApp.branchDisplayName 201 | Version = $EAMApp.versionDisplayName 202 | SelfUpdate = $EAMApp.packageAutoUpdateCapable 203 | } 204 | $ResultList.add($EAMAppObject) | Out-Null 205 | $Counter++ 206 | } 207 | $ResultList | Out-GridView 208 | Disconnect-MgGraph 209 | Set-Location $Script:CurrentLocation -------------------------------------------------------------------------------- /Other/Rename-ComputerRandomizer.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Randomize the computername and reboot. This script is not done yet! 4 | .DESCRIPTION 5 | Randomize the computername and reboot - keep in mind, that this was tested on an Entra-Joined device. 6 | #################################################### 7 | DO NOT TRY THIS ON AN ACTIVE DIRECTORY JOINED DEVICE 8 | #################################################### 9 | .PARAMETER CurseLevel 10 | Use an int to describe how cursed you want your computer name to be. 1 and 2 will avoid any explictely forbidden characters or names 11 | 1 Takes a part of a GUID for the new name - very tame 12 | 2 Will use random strings and letters - very tame 13 | ######### 14 | I can't guarantee what will break if you use these! 15 | ######### 16 | 666 These are very cursed, but they don't seem to break anything (yet) 17 | 999 These will definitely break things. I don't recommend you use these ever. 18 | .NOTES 19 | Initial Creation date: 30.11.24 20 | Last Update: 10.12.24 21 | Version: 1.0 22 | #> 23 | [CmdletBinding()] 24 | param( 25 | $CurseLevel 26 | ) 27 | #Rules of the game 28 | #https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/naming-conventions-for-computer-domain-site-ou#computer-names 29 | #Disallowed names are _suggestions_ - you can absolutely name your computer "BATCH" 30 | $Script:DisallowedNames = @("ANONYMOUS", "BATCH", "BUILTIN", "DIALUP", "DOMAIN", "ENTERPRISE", "INTERACTIVE", "INTERNET", "LOCAL", "NETWORK", "NULL", "PROXY", "RESTRICTED", "SELF", "SERVER", "SERVICE", "SYSTEM", "USERS", "WORLD") 31 | #While not state explictily a " " (whitespace) or "." (dot) or $ (dollar) aren't allowed either 32 | $Script:DisallowedCharacters = @("\", "/", ":", "*", "?", "`"", "<", ">", "|", " ", ".", "$") 33 | #WARNING: I can't guarantee what these will do - I DID test them very briefly 34 | $Script:CursedNames = @("INTERACTIVE", "SELF", "SYSTEM", "LOCALHOST", "PRINTER", "NUL", "CON", "PRN", "AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "💩") 35 | $Script:DoNotUse = @("AzureAD", "BUILTIN") 36 | $MaximumLength = 15 37 | function New-RandomString{ 38 | 39 | [char](Get-SecureRandom -Minimum 1 -Maximum 1250) 40 | } 41 | function New-ComputerNameObject { 42 | param( 43 | $Level 44 | ) 45 | switch ($Level) { 46 | "1" { $Script:NewComputerName = (New-Guid).Guid.Substring(0, $MaximumLength) } 47 | "2" {$Script:NewComputerName = New-RandomString} 48 | "666" {$Script:NewComputerName =$Script:CursedNames[(Get-SecureRandom -Minimum 0 -Maximum $Script:CursedNames.count)]}#Create Emojis + hard to type stuff to hammer the point down 49 | "999" {$Script:NewComputerName = $Script:DoNotUse[(Get-SecureRandom -Minimum 0 -Maximum $Script:DoNotUse.count)]}#Use an explicitely disallowed/cursed name 50 | Default { $Script:NewComputerName = (New-Guid).Guid.Substring(0, 15) } 51 | } 52 | } 53 | 54 | function Confirm-ComputerNameIsValid { 55 | param( 56 | $ComputerName 57 | ) 58 | $IsNumbersOnly = [int]::TryParse($Script:NewComputerName, [ref]$null) 59 | } 60 | 61 | New-ComputerNameObject -Level $CurseLevel 62 | if (Confirm-ComputerNameIsValid) { 63 | Rename-Computer -NewName ($Script:NewComputerName) -Restart -Force 64 | } else {} -------------------------------------------------------------------------------- /Other/Win32App-Wait.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | This script can be used (not just in Intune tbh) to make something/someone wait. 4 | .DESCRIPTION 5 | This was created to make a Win32 app that can wait a specified amount of time. 6 | Don't forget that IME runs as a 32bit process and the registry check must therefore run as follows: 7 | Key Path: HKEY_LOCAL_MACHINE\Software\MHimken 8 | Value Name: Win32AppWaited 9 | Detection Method: Value exists 10 | Associated with a 32-bit app on 64-bit clients: Yes 11 | "Uninstall" can be done using: 12 | powershell.exe -executionpolicy bypass Win32App-Wait.ps1 -Uninstall 13 | .EXAMPLE 14 | The following example would run for 5 minutes and set the registry key. 15 | .\Win32App-Wait.ps1 -Length 5 16 | 17 | In Intune that'd be: 18 | powershell.exe -executionpolicy bypass -file Win32App-Wait.ps1 -Length 5 19 | .EXAMPLE 20 | The following example would run for 5 minutes and set the registry key. 21 | .\Win32App-Wait.ps1 -Length 5 -Unit Hours 22 | In Intune that'd be: 23 | powershell.exe -executionpolicy bypass -file Win32App-Wait.ps1 -Length 5 -Unit Hours 24 | .NOTES 25 | Version: 1.0 26 | Author: Martin Himken 27 | Original script name: Win32App-Wait.ps1 28 | Initial 15.04.2025 29 | Last Update: 17.04.2025 30 | #> 31 | [CmdletBinding()] 32 | param ( 33 | [Parameter(Mandatory)] 34 | [int]$Length, 35 | $Unit = "Minutes", 36 | [switch]$Uninstall 37 | ) 38 | function Start-CustomSleep { 39 | switch ($Unit) { 40 | "Seconds" { $SecondsCalculated = $Length } 41 | "Minutes" { $SecondsCalculated = $Length * 60 } 42 | "Hours" { $SecondsCalculated = $Length * 3600 } 43 | "Milliseconds" { $SecondsCalculated = $Length / 1000 } 44 | } 45 | Start-Sleep -Seconds $SecondsCalculated #Would love to use -Duration, but that's PS7+) 46 | } 47 | if (-not($Uninstall)) { 48 | Start-CustomSleep 49 | New-Item -Path "HKLM:\SOFTWARE\MHimken\" -Force | Out-Null 50 | New-ItemProperty -Path "HKLM:\SOFTWARE\MHimken\" -Name "Win32AppWaited" -PropertyType DWORD -Value 1 | Out-Null 51 | } else { 52 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\MHimken\" -Name "Win32AppWaited" -Force | Out-Null 53 | } 54 | Exit 0 -------------------------------------------------------------------------------- /Windows Migration/Windows11BasicInformationCollection.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | <# 3 | .SYNOPSIS 4 | This script collects basic information to verify the readiness for a Windows 11 installation. 5 | .DESCRIPTION 6 | The script is _not_ intended to verify anything. The idea being that you have a database that can then verify compatibility against. 7 | TPM and SecureBoot are requested to see, if they're already enabled, not to verify if they're available. 8 | .NOTES 9 | Initial Creation date: 05.12.24 10 | Last Update: 05.12.24 11 | Version: 1.0 12 | #> 13 | $ModelManufacturer = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object Model,Manufacturer 14 | $SKU = (Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('EditionID') 15 | $OSVersion = (Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('LCUVer') 16 | $TPM = -not (Get-Tpm).ManufacturerVersionFull20.Contains('not supported') 17 | $SecureBoot = Confirm-SecureBootUEFI 18 | 19 | $ResultObject = [PSCustomObject]@{ 20 | Manufacturer = $ModelManufacturer.Manufacturer 21 | Model = $ModelManufacturer.Model 22 | SKU = $SKU 23 | OSVersion = $OSVersion 24 | TPM = $TPM 25 | 'Secure Boot' = $SecureBoot 26 | } 27 | 28 | return $ResultObject --------------------------------------------------------------------------------