Full Disclosure

I truly stink at scripting and rather dread it.  I knew early on in my career that programming was not a strength of mine, so I chose to gravitate towards networking.  I will admit that PowerShell has been a bit easier to grasp than C#, or C++, or VBScript, but even so…I am no master.  All that being said, in this fast-paced IT world it is nearly impossible to avoid some level of PowerShell scripting, so I have been forced to learn by doing.  Even so, it was a fool’s errand to go things alone and I have been fortunate and unbelievably grateful to have some ridiculously smart people – such as Kendra Thorpe, Mark Gossard, and Jeff Brown – teach me new tricks along the way.

The Headache

Office365 is a beast.  There are hundreds of IPv4 blocks that Microsoft operates the service under.  There are hundreds of DNS domains that Microsoft operates the service under.  It changes on a regular basis, typically once a month, with IP addresses and URLs changing to support new features or ongoing maintenance and optimization of the service.  Microsoft publishes nearly all of the operational information in a fairly nice-and-tidy Office Support article:

Office 365 URLS and IP address ranges

That single reference also contains references to an RSS feed that contains changes that will (or have) occurred to the service, and an XML file that mirrors the IP/Domain information that’s already on the support site.  Consumers of the service are expected to monitor the page(s) and keep up with their internal change processes to make sure that things like firewall rules and proxy servers (if used) get updated to support the changes going on within Office 365.  This expectation is often where things break down as many IT organizations simply can’t keep up with the scope of data and the amount of changes.  Without a doubt, it is a difficult task to achieve without some level of automation to assist in gathering the data.  That’s where PowerShell comes in…

Existing Solutions

There are several solutions out there that attempt to solve this problem:

  • ZScaler actually automates the entire process if you are consuming their cloud-proxy service, but they only automate it within the confines of their solution…you can’t have them update your firewalls, for instance.
  • MindMeld automates the process if you are using Palo Alto firewalls.
  • Azure Range provides an API where you can request IP information for all Microsoft online services, but for folks like me…API calls are a bit out of reach.
    • Alternatively, you can manually request things like Cisco ACLs from the website but it requires manual effort to do so.

The lowest common denominator are PowerShell based solutions that provide a bit more flexibility to integrate with non-heterogeneous IT environments.  While several existing scripts are out there, the one I initially homed-in on was from Jeremy Bradshaw on TechNet:

Get Office 365 IP (v4) Ranged from Published XML

While the script itself is useful, it misses out on gathering IPv6 and URLs.  So I did what most people would do…use his code to start and then add additional pieces to fit the bill.

The Resulting Script

Given that I had never created a PowerShell module before, this was a learning curve.  I was able, however, to accomplish all my goals and end up with a PowerShell module that could easily retrieve information from the Office 365 XML without much trouble.  As a result, Get-O365Endpoints was born.  I share this module with the greater world to help folks who may need it for more “simplistic purposes”, and also because someone – somewhere – will inevitably make it better.  A few usage examples below:

Get-O365Endpoints -Products LYO
Get-O365Endpoints -Products Teams -AddressType IPv4
Get-O365Endpoints -Products LYO,EXO -AddressType URL

Download?

Version 1 of this module available for download here:

Get-O365Endpoints-v1.0

Function Get-O365Endpoints
{
<# 
    .Synopsis 
    Powershell Method for pulling updated list of IP ranges for Office 365 endpoints from Microsoft's published xml file. Initial reference came from existing script on TechNet available here:
    https://gallery.technet.microsoft.com/Get-Office-365-IP-v4-562987d5
     
    .Description 
    Explanation: 
    https://support.office.com/en-us/article/Office-365-URLs-and-IP-address-ranges-8548a211-3fe7-47cb-abb1-355ea5aa88a2 
    XML file: 
    https://support.content.office.net/en-us/static/O365IPAddresses.xml 
  
    .Parameter Products 
    One or more Office 365 products by their abbreviation in the xml file: EOP, EXO, Identity, LYO, o365, OneNote, Planner,  
    ProPlus, RCA, SPO, Sway, WAC, Yammer.

    Note: Parameter will need to be maintained as products are added and removed by Microsoft, at which point the parameter 
    should be updated to match the current list of products in the xml file.
 
    .Parameter AddressType
    The desired information regarding the product: IPv4, IPv6, or URL.
        
    .Example 
    Get-O365EndPoints -Products LYO -AddressType IPv4

    .Example 
    Get-O365EndPoints -Products LYO -AddressType URL
 
    .Example 
    Get-O365EndPoints -Products LYO -AddressType IPv6 | Export-Csv Office365IPRanges.csv -NoTypeInformation 
#> 
[CmdletBinding()] 
Param 
( 
    [Parameter(Mandatory = $true)] 
    [ValidateSet("o365","LYO","Planner","Teams","ProPlus","OneNote","Yammer","EXO","Identity","Office365Video","WAC","SPO","RCA","Sway","EX-Fed","OfficeMobile","CRLs","OfficeiPad","EOP")] 
    [string[]]$Products = @(),
	
    [Parameter(Mandatory = $false)] 
    [ValidateSet("IPv4","IPv6","URL")] 
    [string[]]$AddressType = @()
) 

Begin{
	try { 
		$Office365IPsXml = New-Object System.Xml.XmlDocument 
		$Office365IPsXml.Load("https://support.content.office.net/en-us/static/O365IPAddresses.xml") 
    } 
	catch { 
		Write-Warning -Message "Failed to load xml file https://support.content.office.net/en-us/static/O365IPAddresses.xml" 
		break 
    } 
	if (-not ($Office365IPsXml.ChildNodes.NextSibling)) 
    { 
		Write-Warning -Message "Data from xml is either missing or not in the expected format." 
		break 
    } 
} 

Process{
    foreach ($Product in ($Office365IPsXml.products.product | Where-Object ({$Products -match $_.Name}) | Sort-Object Name)) 
            {If($AddressType -contains "IPv4")
                {
                    $IPv4Ranges = $Product | Select-Object -ExpandProperty Addresslist | Where-Object {$_.Type -match "IPv4"} 
                    $IPv4Ranges = $IPv4Ranges | Where-Object {$_.address -ne $null} | Select-Object -ExpandProperty address 
                    foreach ($Range in $IPv4Ranges) 
                    { 
                        $ProductIPv4Range = New-Object -TypeName psobject -Property @{ 
                            'Product'=$Product.Name; 
                            'IPv4Range'=$Range; 
                        } 
                        Write-Output $ProductIPv4Range | Select-Object Product, IPv4Range 
                    } 
                }
            ElseIf($AddressType -contains "IPv6")
                {
                    $IPv6Ranges = $Product | Select-Object -ExpandProperty Addresslist | Where-Object {$_.Type -match "IPv6"} 
                    $IPv6Ranges = $IPv6Ranges | Where-Object {$_.address -ne $null} | Select-Object -ExpandProperty address 
                    foreach ($Range in $IPv6Ranges) 
                    { 
                        $ProductIPv6Range = New-Object -TypeName psobject -Property @{ 
                            'Product'=$Product.Name; 
                            'IPv6Range'=$Range; 
                        } 
                        Write-Output $ProductIPv6Range | Select-Object Product, IPv6Range 
                    } 
                } 
            ElseIf($AddressType -contains "URL")
                { 
                    $URLRanges = $Product | Select-Object -ExpandProperty Addresslist | Where-Object {$_.Type -match "URL"} 
                    $URLRanges = $URLRanges | Where-Object {$_.address -ne $null} | Select-Object -ExpandProperty address 
                    foreach ($Range in $URLRanges) 
                    { 
                        $ProductURLRange = New-Object -TypeName psobject -Property @{ 
                            'Product'=$Product.Name; 
                            'URLRange'=$Range; 
                        } 
                        Write-Output $ProductURLRange | Select-Object Product, URLRange 
                    } 
                }
            Else
                { 
                    $AllRanges = $Product | Select-Object -ExpandProperty Addresslist 
                    $AllRanges = $AllRanges | Where-Object {$_.address -ne $null} | Select-Object -ExpandProperty address 
                    foreach ($Range in $AllRanges) 
                    { 
                        $AllProductRange = New-Object -TypeName psobject -Property @{ 
                            'Product'=$Product.Name; 
                            'Address'=$Range; 
                        } 
                        Write-Output $AllProductRange | Select-Object Product, Address
                    } 
                    
                }
        }
    }
}

Final Notes

Again, I fully acknowledge the Gallery script where I got the start and other helpful bits of information from Kendra, Mark, and Jeff to get this thing running.  I am fully expecting optimizations can be made so please be gentle as you provide feedback, but hopefully this will help out folks who need a better method of pulling this information than continuously copy/pasting from the Office support site!

4 thoughts on “Retrieving Office365 IPs and URLs via PowerShell

Comments are closed.