Intune Network Requirements – everything I learned

Intune Network Requirements – everything I learned

When I started writing this script in February 2024, I never thought it would take me 6 months to get to a point where I’m satisfied. PowerShell never ceases to amaze me. Now it’s in your hands to prove whether or not and endpoint is really available as it should be. This post will go into a lot of detail about the script and hopefully give you a tip or two (or just read the Takeaways).

Get the latest version of this solution on GitHub

Introduction

Probably the biggest lie told in my industry right now is “Our network team made sure we had all the endpoints we needed”. Well, most of the time I can’t really blame “the network team”. They usually do what they’re told, or at least try to help. However, the discussion quickly gets heated or escalates when I point them to this page. It’s a bunch of tables, and it’s hard to process.

Fortunately, Microsoft provides a way to get a current version of this list as JSON or CSV – in theory. You can even limit these to specific “service areas”. These lists are then consumed by other vendors as well. That’s what you get when you use the built-in “M365 Endpoints” with vendors like Palo Alto Networks or zScaler. What if I told you that these lists are incomplete, and sometimes downright wrong? We will look at that perspective later in this article.

The INR works using artificial service areas (ASAs), the opposite of official service areas (OSAs). That is something I invented to deal with different services and they’re artificial, because Microsoft doesn’t offer them. For example if you wanted to just check for all required Autopilot URLs, this script can do that.

Prerequisites

  • Make sure you’re running the script as an admin – this is only required once when checking for a registry value and could easily be modified.
  • PowerShell 7 – and believe me when I say I tried to make this work in PowerShell 5 to make it more compatible (it didn’t work out)
  • At least two (2) computers, one inside your network and one outside of any restrictions. It is highly recommended that the “outside” machine use the same ISP as that can make a difference. If you trust your network team, use an “ultra whitelist”. “bypass deluxe” or “free proxy” (yes, I have heard of all of these) can also be used.
  • Internet connection – the INR doesn’t currently handle proxies or proxy authentication (except for detecting them).
  • Time – running -Intune or -TestAllServiceAreas will take quite a while (think 15+ minutes). You can monitor the log or use -ToConsole to make it a bit more interactive.
  • Optional: You can run this script using PSexec to run it in system context.

How to run the script

Easy, RTFM. This is the manual? Oh… ok. Right. The whole idea, as explained in the prerequisites, is to run the script twice. You take a command, build it the way you want/need it, and run it on two machines on different networks. You then take the two resulting .CVSs and merge them, using the script again. It will detect differences in the two runs and show them to you. That’s really it. Let’s look at one example. The following is the recommended approach, but you’re free to mix most switches. Keep in mind that using ShowResults will keep the script from closing until the grid view has been closed.

Example test run using Autopilot

.\Get-IntuneNetworkRequirements.ps1 -UseMSJSON -CustomURLFile '.\INRCustomList.csv' -AllowBestEffort -CheckCertRevocation -OutputCSV -ShowResults -Autopilot

This example would import the official Microsoft Intune JSON and use my custom URL list. It allows adding wildcards to the list by removing the wildcard itself, it would check if the CRLs are well known, output the results to a CSV and give an Out-Gridview of the results. The ASA tested was Autopilot, which contains many other ASAs (see below). However, this does not test all of Intune. If you also specify -Intune, it wouldn’t re-run the same tests it already did.

    $resultlist = @{
        TestWindowsActivation = Test-WindowsActivation
        EntraIDTest           = Test-EntraID
        DiagnosticsDataUTest  = Test-DiagnosticsDataUpload 
        WUTest                = Test-WindowsUpdate
        DOTest                = Test-DeliveryOptimization
        NTPTest               = Test-NTP
        DNSTest               = Test-DNSServers #Log only output!
        DiagnosticsDataTest   = Test-DiagnosticsData
        NCSITest              = Test-NCSI
        WNSTest               = Test-WNS
        StoreTest             = Test-MicrosoftStore
        CRLTest               = Test-CRL
        LegacyTest            = Test-Legacy
        SelfDeployingTest     = Test-SelfDeploying
        TPMAttTest            = Test-TPMAttestation
    }

Example merge

Let’s say you ran the previous command on two computers, first on OH-NB-TK-220 and then on Some-PC. Now you want to have a difference map between the two to verify the differences (more on this in the next chapter). Use the following command to merge the two CSVs, output the result to a new CSV, and display the results in an Out-Gridview.

.\Get-IntuneNetworkRequirements.ps1 -MergeResults -MergeCSVs "ResultList_20240807_094550_OH-NB-TK-220.csv","ResultList_20240807_093303_Some-PC.csv" -OutputCSV -ShowResults

How to interpret results

Great, you’ve got a merged result! Now, before you throw the results at your network team, try to understand what exactly might be wrong. Network teams love it when you approach them with as much detail as possible (another reason why the script is written this way). Using the same example as before, we’ll zoom in on some of the differences and if/when they’re important.

DNS

If this is false, either the name couldn’t be resolved at all, or it was determined that the resolved name wasn’t a valid IP. Further tests are not performed.

TCP/UDP

If this is false, a connection on the specified port could not be established. Further tests are not performed.

HTTP status code

This is an interesting one, because not every page has it, because not every service has a web server. So while a 503 may look bad, it may be perfectly acceptable. Graph, for example, will always return a 401. However, 403 (access denied) or 401 (auth required) can be a sign of proxy interception. Returns false if no HTTP request could be made.

SSL test, Issuer and Auth exception

Regardless of the HTTP test result, we will perform a secure connection test. Don’t be confused by the name, this will always use the highest available method (TLS1.3 > TLS 1.2 > TLS 1.1 etc.) in order of preference by the operating system. If no secure connection could be established using TCP and the specified port, this will return false. When establishing the secured connection fails for any reason, the auth exception field will be filled with the error reason (PS7 is required for this). On success the issuer field contains the issuing certificate common name field.

Known CRL

If this is false, you may be looking at TLS interception. Verify that the certificate given in the issuer field is a publicly valid certificate and not from an internal appliance or PKI.

SSL Interception

If this is true, you can assume there is TLS interception happening. If you want to know how exactly this works, please scroll down to the according chapter.

A word on completeness

Dear reader, you’re already aware, that network requirements change all the time. Due to the sheer amount of domains used I cannot and will never guarantee completeness in my tests. I also took the liberty to directly correct some wrong entries (see further below) in Microsoft’ official list. If you find something, that you think should be corrected or is missing, please, feel free to submit a PR to the repo or contact me on twitter X.

Why did you even develop a script? There is XYZ already?

After looking at the JSON for all of 5 minutes, I already knew that my script was going to extend quite a bit from what most people pointed out to me as already existing scripts. Most of the ones I found (and I did research some of them before I started) didn’t even use the JSON, but a hardcoded list. The Microsoft list is also lacking domains. Where are the Autopilot endpoints? Why are the ports for CRLs wrong? What TLS-version is being used? I needed more ways to deal with these domains than were available as a ready-made cmdlet in PowerShell. So I had to create my own.

What the INR offers

The following tests are performed by default and in this order – more on each of these in the following chapters.

  1. DNS
  2. TCP(/UDP) connection
  3. HTTP(S)
  4. TLS/SSL

Optionally the following tests will be performed:

  • CRL verification (not the actual revocation of a cert, that is done when establishing a TLS/SSL connection on a TCP port)
    • TLS/SSL Inspection (more later, but yes these two are connected)
  • Custom URL list that I created using the knowledge gathered from my research
  • Custom list of URLs

Name resolution test

Simply put, this function will use Resolve-DnsName to check a few things. First, it will determine if a given domain resolves at all. Then it will check if the IP is legitimate, meaning it shouldn’t point to something like 0.0.0.0 (like a pi-hole would). The function also uses a cache so that domains that have already been checked are not checked again. DNS “sinkholes” will be logged and the return would be a $false result, just as if the resolution failed.

TCP(/UDP) connection test

First of all, while you can technically test a UDP connection, this only works if you know exactly what data the other end is expecting. So while I worked on a function that could successfully test NTP, I never found any other service that was documented well enough to do such a test. In theory, Delivery Optimization has a good blog about it, but it’s P2P and we can’t easily identify other potential peers.

The TCP connection itself is only attempted if the DNS test was successful. It uses a specified port and has an adjustable MaxDelayInMs. Depending on your location and expected connection delays, you may want to adjust the default of 300 milliseconds to something higher. This number was chosen based on the slowest service I could find – update.microsoft.com. Again, the results are cached and not tested twice.

HTTP(S) connection test

If both the DNS and TCP results are $true, we move on to what I call the “HTTP” test. What this does is run Invoke-Webrequest to see if we get a connection and what the HTTP status code we get is. This will not use a specific port, just 80 or 443. The purpose of this test is to see if there is proxy intervention. That is, if you get a 401 or 403 back, a proxy (auth) may be involved. There is no deterministic way to know, what should be the expected HTTP status code. Some services will happily return a 200, others won’t. Keep in mind that IWR does not show redirections, so a 301 and 302 will still be hidden. (Shoutout to httpstat.us)

TLS/SSL connection test

This is probably the test I struggled with the most. I will say that what is now 75 lines (minus comments, plus the CheckCertRevocation function in the next chapter) took me a lot of nights and gray hairs to get right – probably about a month. This function opens a TCP socket, just like the TCP test, and then establishes a secure connection (called “SSLStream” in PS). The secure connection will fail if the certificate revocation cannot be verified. If the TLS connection fails, it returns an exact error reason and writes it to the log. However, this is only true for PowerShell 7. PowerShell 5 always returns the same generic error. The amount of detail that is returned in both cases, success and failure to establish a connection, is very different. (Shoutout to badssl.com)

CRL verification, but with a twist

As I said, a lot of time went into this, but I’ll try to be brief. Note: This does not check the validity of the endpoint certificate against its CRL. This is already done when the TCP connection is established. If CheckCertRevocation is set, the script will use the connection established in the TLS connection test and analyze the certificate returned by the tested endpoint. It will check if there is a CRL in the certificate at all, and then test if the CRL is from a list of “known good CRLs”. Now, this list is already provided by Microsoft in their JSON/CSV, but it is incomplete. This is one of the main reasons why I recommend using the custom URL list.

My custom URL list – 6 months of research condensed

One of the big advantages, at least that’s what I like to tell myself, of using the INR script is my custom URL list called “INRCustomList.csv”. It is a collection of everything I have found that is missing from Microsoft’s official JSON/CSV. This will add URLs needed for ASAs like Android, Autopilot or Apple. It will also correct some like time.windows.com (more on that later). My personal tests will always use this custom domain list, especially if I feel they need to be adjusted or expanded.

Note that this also gives you the ability to add your own endpoints and use my test method(s) for endpoints other than Intune. I have already added the ability to check all M365 endpoints (a completely different OSA mind you) if needed. However, the scope of this script is Intune. I will not go into the same level of detail with every other OSA (like Exchange or SharePoint Online).

How does it work?

How did you handle the wild cards?

I was asked this question a lot. The network requirements list a lot of wildcards and very large IP subnets. The IP ranges are not really interesting, unless you have old(er) network equipment that can only handle those. In that case please upgrade to something that can handle DNS. Wildcards are only handled, if you use the -AllowBestEffort switch. That will trigger the Find-WildcardURL function, when importing the official M365 endpoint list. The following describes that workflow.

How does the TLS inspection detection work?

Very simple – after staring at documentations for a couple of weeks. How does this help in determining TLS validation? Well, if the certificate of the public endpoint, which should be the last leaf in a chain, doesn’t have a CRL at all, that’s a big red flag. If it does, but it’s not on the list of known CRLs, that’s also a red flag. Both cases indicate an intermediate certificate exchange, aka TLS interception.

Takeaways

TL;DR? Sure, here you go.

  • The Microsoft JSON/CSV does not deliver a full picture of endpoints
  • The Microsoft JSON/CSV is wrong in some places (wrong ports, wrong URLs)
  • AllowBestEffort will make *.something.microsoft.com into something.microsoft.com if no other URL is using that domain.
  • The longest test will take about 15-20 minutes and can be done by using -TestAllServiceAreas
  • Read the examples section to better understand how the script is used
  • Even with this script, you will need someone to interpret the results

Complaint department

That should cover the most interesting parts of the script, but I want to take this opportunity to point out some bugs I found that I think need to be addressed by Microsoft. One of the biggest complaints I have is that there should be one ID for domains that don’t support TLS inspection and one ID for domains that don’t support proxy authentication. While writing my script the “No TLS inspection” domains changed three times.

List of errors in the Microsoft JSON/CSV

In general, a lot of these bugs probably happen because the list doesn’t allow for a certain level of granularity. Sometimes it feels like a lack of understanding of the underlying service. Finally, sometimes things feel wrong because they’re on the list for legacy reasons (but are no longer in use or offline). This is not meant to blame anyone, but rather to offer suggestions for possible fixes. Most of these fixes are applied automatically through my custom domain list and some magic within the functions. I’m strictly relying on the list provided through Microsoft that is using the (undocumented) OSA called MEM.

  • Autopilot URLs are completely missing
    • Add the URLs from here preferably to their own ID
  • TPM Attestation URLs are incomplete
    • Add *.microsoftaik.azure.net to ID 173 (see)
  • Multiple IDs use the wrong ports – this is probably because the list isn’t granular enough to separate single Domain:Port relations.
    • (Apple) ID 178 gives TCP port 5223, which is required for APNS to work. Fails to mention 2197
    • (CRL) ID 84 and 125 list port 443 for certificate revocation lists. These should only ever be reachable through port 80
    • (Delivery Optimization) ID 172 (lists 7680 and 3544 as needed ports. 7680 is only used for incoming connections and 3544 (Teredo) is a UDP port. 3544 is also not related to the domains listed. This is also stated wrong here (this was fixed – thanks Carmen) but documented correctly here.
    • (Time) ID 165 This was fixed 🙂
  • Why is Apple on the list, but Google/Android is not? Please add the domains according to this
  • Some important CRLs are missing from ID 125 – and those are just the ones I stumbled across.
  • Most Windows Activation URLs are missing (except activation.sls.microsoft.com), add them from here (why is there a CRL on that list?) or at least from here (these are incomplete too, when compared to the first list!)
  • Diagnostic Upload (ID 182) requires much more than one domain, but instead has a list of what appear to be localized domains.
  • (Microsoft Azure attestation) ID 186 is missing intunemaape6.ncus.attest.azure.net
  • I’m not sure if this is a hard requirement, but the NCSI URLs (MSFTconnecttest for IPv4 and IPv6) used to be on the list under ID 165, and now they are completely missing. Although I admit I don’t see a hard relationship to Intune here.
  • A lot of the domains don’t seem to make sense or be used. Examples: ID 49,51
  • There are some inconsistencies between the website for Intune network requirements and the JSON, however I have not documented all of the cases

Odd ones

  • something.azureedge.net exists, nothing.azureedge.net doesn’t. The first domain is used for testing in the custom list.
  • *.microsoftaik.azure.net is a catch-all address. It would usually put the TPM certificate name in front, but you can just use whatever. I went for TPMTESTURL.microsoftaik.azure.net in my custom list.
  • windowsphone.com is on the official list, but only redirects to a support page that is notifying people about the EoS for Windows Mobile 10.
  • The slowest URL in my tests to establish a secure connection to was update.microsoft.com
  • clientconfig.passport.net returns a “specified resource not found” (but this might be due to missing parameters
  • windowsupdate.com doesn’t resolve – a lot of subdomains do
  • WufB-DS requires an “in between wildcard” for payloadprod*.blob.core.windows.net. I don’t know a good way to handle this, so I left it out.
  • Yes, you can tell your network admin that *.microsoft.com is on the official list (ID 50 and 78)
  • intune.microsoft.com is not explicitly on the list
  • ID 84 is a Microsoft CRL, every other CRL lives in ID 125
  • There is only one special URL only available for GCC for remote help (use the -GCC switch)

Closing words

Thanks to everyone who helped me build this. From the bottom of my heart, I couldn’t have kept my sanity otherwise. This includes my wife :). I really hope this helps you find problems in your network configuration. Don’t hesitate to open issues on GitHub – even updates to the custom list are welcome. As always, creating something like this alone is hard and any help is appreciated.