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:
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!
Great stuff as always Trev!
😉
Awesome, thank you very much for sharing.
No problem. Hope it helps!