Due to replacement of the Office 365 IP list with new web service, I wrote a new, improved script that returns all IPs needed for all services of O365 in format for Cisco ASA:
# CHANGE PATH $Path = 'E:' $Report = "$Path\Report.txt" $date = Get-Date -format "yyyyMMdd" $dateReport = Get-Date -format "dd.MM.yyyy" $o365SavedGUID = "$Env:TEMP\o365_Guid.txt" if (Test-Path $o365SavedGUID ) { $GUID = Get-Content $o365SavedGUID } else { $GUID = [GUID]::NewGuid().Guid; $GUID | Out-File $o365SavedGUID } [bool]$NoIpv6 = 1 # 1 eq True; 0 eq False $Link = "https://endpoints.office.com/endpoints/Worldwide?NoIPv6=$NoIpv6&ClientRequestId=$GUID" try { $AllIPs = Invoke-RestMethod -Method Get -Uri $Link } catch { $_ | Out-File "$Path\$date - Errors.txt" Add-Content $Report "$dateReport Error!!!" } if (!(Test-Path "$Path\$date - Errors.txt")) { $Result = @{} $IPGroupsTcp = $AllIPs | ? {($PSItem.Ips) -and ($PSItem.tcpPorts)} | select Ips, tcpPorts | Group-Object tcpPorts $IPGroupsUdp = $AllIPs | ? {($PSItem.Ips) -and ($PSItem.udpPorts)} | select Ips, udpPorts | Group-Object udpPorts foreach ($IPgrp in $IPGroupsTcp) { $ErrorList = @() $Result += @{"PortsTcp_$($IPgrp.Name)" = @()} $List = $IPgrp.Group.Ips | sort | select -Unique foreach ($Address in $List) { try { [string]$Parse = $Address [string]$IPAddress = $Parse.Substring(0,$Parse.IndexOf("/")) [string]$Cidr = $Parse.Substring($Parse.IndexOf("/")+1,$Parse.Length - $Parse.IndexOf("/")-1) [int]$Prefix = $Cidr if ($Prefix -eq 32) { [string]$Value = "network-object host $IPAddress" } else { $mask = ([Math]::Pow(2,$Prefix)-1) * [Math]::Pow(2,(32-$Prefix)) $bytes = [BitConverter]::GetBytes([UInt32] $mask) $IPMask = (($bytes.Count-1)..0 | ForEach-Object {[String]$bytes[$_]}) -join "." [string]$Value = "network-object $IPAddress $IPMask" } # End if & else $Result."PortsTcp_$($IPgrp.Name)" += $Value.Trim() } # End Try catch { $ErrorList += "$Address --> $_" } # End Try & catch } # End foreach Address } foreach ($IPgrp in $IPGroupsUdp) { $ErrorList = @() $Result += @{"PortsUdp_$($IPgrp.Name)" = @()} $List = $IPgrp.Group.Ips | sort | select -Unique foreach ($Address in $List) { try { [string]$Parse = $Address [string]$IPAddress = $Parse.Substring(0,$Parse.IndexOf("/")) [string]$Cidr = $Parse.Substring($Parse.IndexOf("/")+1,$Parse.Length - $Parse.IndexOf("/")-1) [int]$Prefix = $Cidr if ($Prefix -eq 32) { [string]$Value = "network-object host $IPAddress" } else { $mask = ([Math]::Pow(2,$Prefix)-1) * [Math]::Pow(2,(32-$Prefix)) $bytes = [BitConverter]::GetBytes([UInt32] $mask) $IPMask = (($bytes.Count-1)..0 | ForEach-Object {[String]$bytes[$_]}) -join "." [string]$Value = "network-object $IPAddress $IPMask" } # End if & else $Result."PortsUdp_$($IPgrp.Name)" += $Value.Trim() } # End Try catch { $ErrorList += "$Address --> $_" } # End Try & catch } # End foreach Address } if ($ErrorList) { $ErrorList | Out-File "$Path\$date - Errors.txt" Add-Content $Report "$dateReport Error!!!" } else { $Result.Keys | % { $Result.Item($PSItem) | Out-File "$Path\$date - $_.txt" } Add-Content $Report "$dateReport OK" $Result.Keys | % { $Missing = @() $LatestRules = Get-Content "$Path\$date - $_.txt" if (Test-Path "$Path\$_.txt") { $LastFile = Get-Content "$Path\$_.txt" foreach ($line in $LatestRules) { if ($LastFile -notcontains $line) {$Missing += $line} } foreach ($line in $LastFile) { if ($LatestRules -notcontains $line) {$Missing += "no $line"} } if ($Missing) {$Missing | Out-File "$Path\$date - $_ Changes.txt"} } Copy-Item "$Path\$date - $_.txt" "$Path\$_.txt" } $ChangesToday = Get-ChildItem -Path $Path -Filter "*$date*Changes.txt" if ($ChangesToday) { foreach ($chg in $ChangesToday) { Add-Content -Value $chg.Name -Path "$Path\Report - Changes.txt" } } } }
You need to change path in script (first line) to your custom folder.
You can run this script via Task Scheduler once per day.
Script will create multiple files (date is in format YYYYMMDD):
Report – Script logging (when was script run & result)
Report – Changes – List of changes (which list has changed)
It will also create file for each port group – currently there are 9 port groups (script will search for all port groups (TCP & UDP) and will create new files if needed). “- Changes” file will generate only if there are any changes)
– PortsTcp_25 (& [DATE] – PortsTcp_25, [DATE] – PortsTcp_25 – Changes)
– PortsTcp_80,443 (& [DATE] – PortsTcp_80,443, [DATE] – PortsTcp_80,443 – Changes)
– PortsTcp_143,993 (& [DATE] – PortsTcp_143,993, [DATE] – PortsTcp_143,993 – Changes)
– PortsTcp_443 (& [DATE] – PortsTcp_443, [DATE] – PortsTcp_443 – Changes)
– PortsTcp_587 (& [DATE] – PortsTcp_587, [DATE] – PortsTcp_587 – Changes)
– PortsTcp_995 (& [DATE] – PortsTcp_995, [DATE] – PortsTcp_995 – Changes)
– PortsTcp_50000-59999 (& [DATE] – PortsTcp_50000-59999, [DATE] – PortsTcp_50000-59999 – Changes)
– PortsUdp_3478,3479,3480,3481 (& [DATE] – PortsUdp_3478,3479,3480,3481, [DATE] – PortsUdp_3478,3479,3480,3481 – Changes)
– PortsUdp_50000-59999 (& [DATE] – PortsUdp_50000-59999, [DATE] – PortsUdp_50000-59999 – Changes)
Example of files generated (changes only in TCP ports 143,993 & 587 & UDP ports 50000-59999):
Great to see this. Super useful. One minor ask I have is to use this to get the client identifier which retains the same GUID across calls. Since the web services are anonymous this is how we identify the number of machines that connect to it, it also may be required if there is a support request related to the web service.
Simply replace this line, with the following two lines:
$GUID = ([guid]::NewGuid()).Guid
$d = $Env:TEMP + “\endpoints_guid.txt”
if (Test-Path $d) { $GUID = Get-Content $d } else { $GUID = [GUID]::NewGuid().Guid; $GUID | Out-File $d }
Paul Andrew
Thanks Paul. Script updated!
Thank you. great job
Hello Halis,
Thank you for sharing your script.
A minor point, but the “$NoIpv6” variable is never assigned a value. Based on some quick web browser tests, “NoIPv6=$NoIpv6” appears to be able to be shortened to just “NoIPv6”, i.e. the revised “$Link” assigned would be:
$Link = “https://endpoints.office.com/endpoints/Worldwide?NoIPv6&ClientRequestId=$GUID”
As Paul Andrew appears to be from Microsoft, I will add that if anyone from Microsoft happens to read this comment:
It would be great for Microsoft to add the concept of “traffic direction” between on-premises systems and cloud to their REST API. It would be simpler to automatically update ASA rules knowing which IP blocks and ports needed to be opened in each direction. Some of the returned ranges will be for inbound traffic to on-prem (e.g. a hybrid Exchange set-up), some for outbound traffic, and some for traffic in both directions.
Also beneficial would be a more clear definition of which ranges are for components such as “Exchange Online Protection”, which is only a subset of the “Exchange Online” service offerings. Relying on parsing for “urls” values of “*.mail.protection.outlook.com” and “*.protection.outlook.com” isn’t as clean as parsing on a dedicated “serviceAreaDisplayName” value such as “Exchange Online Protection” would be.
Thank you. I am not sure where I lost this variable :(. Script is now updated. Thanks again.
Thanks for this script, very helpful. There are a couple of entries in the Skype for Business/Teams section that use UDP ports 3478, 3479, 3480, 3481 so might need to generate one more file for these?
Hi. Initially, this script only searched for TCP ports. Now it’s updated – it generates lists for both, TCP & UDP.
Excellent, thanks for the quick update.
do you have the script to upload to CISCO ASA
You can just run commands generated from the script (*.txt files).
Hello Halis,
Thank you for the updated script, we had been using the old version previously. When I run the new script, I receive this error message:
Invoke-RestMethod : Valid ClientRequestId Guid is required in the query string. Documentation is available at http://aka.ms/ipurlws
At Drive:\Path\CreateASArules.ps1:13 char:17
+ try { $AllIPs = Invoke-RestMethod -Method Get -Uri $Link }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Any idea how we obtain a valid GUID?
WordPress update changed how code is displayed on site (& became “& ;”). Now it’s fixed.
Halis,
For someone who hasn’t used powershell and tips on how to use this script? Is it pretty straightforward?
I was able to do the powershell by asking one of my friends but how do I upload into the Cisco ASA?
This still works great! Thanks for the script.
Hi Mark,
What about the FQDN for office 365
Kind Regards,
Bala
Hi this is amazing, I am not a script guy and we use Palo Altos.. My question is would it be hard to have it dump all the IP’s to one txt file with the CIDR in tact? Like x.x.x.x/32
replace $IPMask with /$Cidr
I did this aswell so I can import into my watchgaurd.
heres a snip of the code
foreach ($IPgrp in $IPGroupsTcp) {
$ErrorList = @()
$Result += @{“PortsTcp_$($IPgrp.Name)” = @()}
$List = $IPgrp.Group.Ips | sort | select -Unique
foreach ($Address in $List) {
try {
[string]$Parse = $Address
[string]$IPAddress = $Parse.Substring(0,$Parse.IndexOf(“/”))
[string]$Cidr = $Parse.Substring($Parse.IndexOf(“/”)+1,$Parse.Length – $Parse.IndexOf(“/”)-1)
[int]$Prefix = $Cidr
if ($Prefix -eq 32) { [string]$Value = “ipv4,$IPAddress” }
else {
$mask = ([Math]::Pow(2,$Prefix)-1) * [Math]::Pow(2,(32-$Prefix))
$bytes = [BitConverter]::GetBytes([UInt32] $mask)
$IPMask = (($bytes.Count-1)..0 | ForEach-Object {[String]$bytes[$_]}) -join “.”
[string]$Value = “ipv4,$IPAddress/$Cidr”
} # End if & else
$Result.”PortsTcp_$($IPgrp.Name)” += $Value.Trim()
} # End Try
catch { $ErrorList += “$Address –> $_” } # End Try & catch
} # End foreach Address
PortsUdp_50000-59999 file is not generated by the script anymore?
Script is dynamically generating files (if Microsoft add/remove ports, script will produce more/less files)
Hi, what is the licensing surrounding this? I think it should be posted on github.
thank you
Can someone modify to list with mask?
Just ran the script, it is great except that it is not outputting the port groups. it names the files for each port by group, but it retrieves only network objects
Hi All.
I have used the script to get the ip addresses from Microsoft, and have written Python code to deliver it to the firewall.
I ran into a problem with the generated files, not being formattet as utf8. If anyone else has problems, you can add “-Encoding utf8” to this line: “$Result.Keys | % { $Result.Item($PSItem) | Out-File “$Path\$date – $_.txt” }”