I have a work requirement to look up zip codes based on incomplete addresses (street, city, state, etc) – this could be accomplished by going into Google Maps and plugging in the partial address which will then give you the completed address with the zip code. But what if you had a list of hundreds of incomplete addresses that you need the zip code for?
Below is a demonstration of how to leverage Google’s geocode API using Powershell to perform the zip code lookup.
First off, Google lets you make a handful of calls to their APIs for free, anything over that handful will require an API key. If you get an API key, it is free as long as you stay under a certain limit of calls made per day. If you go over that limit, then you will have to create a Google Developer account and pay for the service as you go. I went ahead by creating a free API key as I do not plan on making more than the limited calls per day. I suggest you look at the API limits and read their documentation as it is very informative and overall beneficial even if you do not end up creating an API key for yourself.
The API call works by using the following URL in your browser – this is Google’s example:
https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=YOUR_API_KEY
The result of the API call, if it is legitimate and well formed, will return output in JSON format. In Powershell, we have to convert this JSON format:
Invoke-WebRequest $APICall | ConvertFrom-Json
If you set the above Invoke-WebRequest (alias is wget) results to a variable, that variable will now hold the entire read-able json file in a format that can be manipulated by Powershell.
#Convert JSON to read-able format $APIResults = Invoke-WebRequest $APICall | ConvertFrom-Json #Select the portion of the json file that holds the address results $APIResults | Select Results #Return only the results without an output header $APIResults | Select Results).Results #From the Results output, get only the formatted_address output and print without the header $APIResults | Select Results | Select formatted_address).formatted_address
<# PS H:\Code> h Id CommandLine -- ----------- 1 $APICall = "5XX6 TUJXXXA BLVD@NORTH XXXXX%CA" 2 cls 3 .\FindZipCodeBulk.ps1 4 cls 5 $APICall 6 cls 7 $APICall = "https://maps.googleapis.com/maps/api/geocode/json?address=5XX6 TUJXXXA BLVD@NORTH XXXXX%CA&key=XXXXXXXXXXXXXXXXXXXXXXX" 8 Invoke-WebRequest $APICall | ConvertFrom-Json 9 Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results 10 (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results 11 (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address 12 ((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address).Formatted_Address 13 dfs #> PS H:\Code> $APICall = "https://maps.googleapis.com/maps/api/geocode/json?address=5XX6+TUJXXGA+BLXD,XXXX+HOLXXXOD,+CA&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" PS H:\Code> Invoke-WebRequest $APICall | ConvertFrom-Json results status ------- ------ {@{address_components=System.Object[]; formatted_address=5XX6 XujXXga Ave, XXXX HolXXX, XX 91601, USA; geometry=; partial_match=True; place_id=XXXXXXXXXXXXXXXXX; types=System.Object[]}} OK PS H:\Code> Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results results ------- {@{address_components=System.Object[]; formatted_address=5XX6 XujXXga Ave, XXXX HolXXX, XX 91601, USA; geometry=; partial_match=True; place_id=XXXXXXX; types=System.Object[]}} PS H:\Code> (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results address_components : {@{long_name=XXX; short_name=XXX; types=System.Object[]}, @{long_name=XXXX Avenue; short_name=XXXX Ave; types=System.Object[]}, @{long_name=XXX XXX; short_name=NoHo; types=System.Object[]}, @{long_name=Los Angeles; short_name=Los Angeles; types=System.Object[]}...} formatted_address : 5XX6 XujXXga Ave, XXXX HolXXX, XX 91601 geometry : @{bounds=; location=; location_type=ROOFTOP; viewport=} partial_match : True place_id : XXXXXXX types : {premise} PS H:\Code> (Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address formatted_address ----------------- 5XX6 XujXXga Ave, XXXX HolXXX, XX 91601 PS H:\Code> ((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select Formatted_Address).Formatted_Address 5XX6 XujXXga Ave, XXXX HolXXX, XX 91601
Great, now we know how to call the API, get data from it, convert the data to a useable format and extract the exact data we want from it – but how do we form the URL from just a plain address automatically?
PS H:\Code> .\FindZipCode.ps1 "XXXX XXXXX BLVD@XXXXX HOLLYWOOD%CA" 5XX6 XujXXga Ave, XXXX HolXXX, XX XXX+XXXXX+BLVD,+XXXXX+HOLLYWOOD,+CA https://maps.googleapis.com/maps/api/geocode/json?address=XXX+XXXXX+BLVD,+XXXX+HOLLYWOOD,+CA&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 5XX6 XujXXga Ave, XXXX HolXXX, XX 91601 PS H:\Code>
I took the original address and added some symbols to be utilized as markers to easily split the strings and extract the values I want into separate variables. After that, I can use the individual variables and concatenate them into a single string to be used in the API call itself.
param ( [Parameter(Mandatory=$true)] [string] $Address ) #Here I am setting my API key as a global variable $Global:APIKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" #These two variables are the front and rear parts of the API URL we need to form $Global:APILink1 = "https://maps.googleapis.com/maps/api/geocode/json?address=" $Global:APILink2 = ("&key=" + $APIKey) #This function actually performs the wget to Google's API #The function needs a parameter when invokved function PerformAPICall ($FormedAPIAddress) { #This forms the API URL and stores it in a variable $APICall = ($APILink1 + $FormedAPIAddress + $APILink2) #Attempt to put the input string into the formed address portion of the API URL try { #Extract Zip code portion from results of APICall $Zip = (((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select formatted_address).formatted_address -Split ("$State")) #Format results to extract only zip code $Zip = $Zip[1] $Zip = $Zip -Split (",") $Zip = $Zip[0] $Zip = $Zip.TrimStart() $Zip = $Zip.TrimEnd() #If contents of $Zip code is not empty #Output results if ($Zip) { $ExtractedAddress = ($Street + ", " + $City + "," + " " + $State + " " + $Zip) Write-Host $ExtractedAddress } #If contents of $Zip is empty #Output error message else { Write-Host "$FullAPIAddress - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red } } #Catch condition if API call to Google fails catch { Write-Host "Error occurred! - " $APICall " <==== Control+Left Click To View" -ForegroundColor Red } } #Attempt to convert string received during script execution into required API URL try { #The URL must be edited to include "*@*" and "*%*" #This helps to split the string accordingly, any symbol could be used if desired #just change the symbols accordingly below if ($Address -like "*@*" -or $Address -like "*%*") { #Extract Street only and store into variable $Street = $Address -Split "@" $Street = $Street[0] #Extract City only and store into variable $City = $Address -Split "@" $City = $City -Split "%" $City = $City[1] #Extract State only and store into variable $State = $Address -Split "@" $State = $State -Split "%" $State = $State[2] #Store Street, City, and State into a single variable $ExtractedAddress = ($Street + $City + ", " + $State) #Store Street, City, and State into a single variable with necessary + symbols as required by API URL call $FormedAPIAddress = (($Street -Replace " ", "+") + ",+" + ($City -Replace " ", "+") + ",+" + ($State)) #Store the first part of the API URL, the actual address, and the end part of the URL into one string variable $APICall = ($APILink1 + $FormedAPIAddress + $APILink2) #Output results #These are not actually needed but show the progression in each step leading up to the API call Write-Host $ExtractedAddress Write-Host $FormedAPIAddress Write-Host $APICall #Call function that actually performs API call PerformAPICall $FormedAPIAddress } #Condition to handle if string received during execution does not have the necessary "*@*" and "*%*" else { Write-Host "Address is not formatted correctly!" -ForegroundColor Red } } #Condition to handle of try condition fails catch { Write-Host "Address not provided or incomplete!" -ForegroundColor Red }
Now we can perform API calls on a single address but what about the earlier scenario where we have hundreds of addresses to lookup?
PS H:\Code> .\FindZipCodeBulk.ps1 XXXX NORTH HOLLYWOOD, CA 91601 XXXX LOS ANGELES, CA 90036 XXXX NEWARK, NJ 07114 XXXX FONTANA, CA 92335 XXXX SUITE 101, FONTANA, CA 92335 XXXX SUITE 105, GLENDORA, CA 91740 XXXX SUITE N, HARBOR CITY, CA 90710 XXXX X LOS ANGELES, CA 90029 XXXX PANORAMA CITY, CA 91402 XXXX PASADENA, CA 91101 XXXX, SEATTLE, WA 98103 XXXX, BIRMINGHAM, AL 35205 XXXX S., BIRMINGHAM, AL 35222 XXXX , BIRMINGHAM, AL 35233 5TH+AVE.+S.,XXXXX,+XXX - Error on this record! - https://maps.googleapis.com/maps/api/geocode/json?address=XXXX+AVE.+S.,XXXXX,+AL&key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX <==== Control+Left Click To View XXXX WASHINGTON DC, DC 20045 XXXX CANTON, GA 30114 XXXX BUS, GA 31901
This version of the script works the same way as the first one, the only difference is that I have plugged in my hundreds of addresses in the required format into a text file. This script then reads that text file and does a “for each” loop performing the same logic from the first script on each line of address within the text file.
#Global variables as they need to move to difference functions and contain their content at a wider scope $Global:APIKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX" $Global:APILink1 = "https://maps.googleapis.com/maps/api/geocode/json?address=" $Global:APILink2 = ("&key=" + $APIKey) $Global:FullAPIAddress $Global:APICall #This function actually performs the wget to Google's API #The function needs a parameter when invokved function PerformAPICall ($FormedAPIAddress) { #This forms the API URL and stores it in a variable $APICall = ($APILink1 + $FormedAPIAddress + $APILink2) #Attempt to put the input string into the formed address portion of the API URL try { #Extract Zip code portion from results of APICall $Zip = (((Invoke-WebRequest $APICall | ConvertFrom-Json | Select Results).Results | Select formatted_address).formatted_address -Split ("$State")) #Format results to extract only zip code $Zip = $Zip[1] $Zip = $Zip -Split (",") $Zip = $Zip[0] $Zip = $Zip.TrimStart() $Zip = $Zip.TrimEnd() #If contents of $Zip code is not empty #Output results if ($Zip) { $ExtractedAddress = ($Street + ", " + $City + "," + " " + $State + " " + $Zip) Write-Host $ExtractedAddress } #If contents of $Zip is empty #Output error message else { Write-Host "$FullAPIAddress - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red } } #Catch condition if API call to Google fails catch { Write-Host "Error occurred! - " $APICall " <==== Control+Left Click To View" -ForegroundColor Red } } #Checks if the addressList.txt file exists if (Test-Path .\addressList.txt) { #If it does exist, it will start the foreach loop logic foreach ($Line in (gc .\addressList.txt)) { #Take each line in text file and set it equal to $Address variable $Address = $Line #Take the end of the address and set it equal to $State variable(state abbreviations are usually stored at the end of an address) $State = $Address[($Address.Length -2)] + $Address[($Address.Length -1)] #Extract Street only and store into variable $Street = $Address -Split ("@") $Street = $Street[0] #Extract City only and store into variable $City = $Address -Split ("@") $City = $City[1] $City = $City -Split ("%") $City = $City[0] #Remove spaces from front and end of each string $Street = $Street.TrimEnd() $City = $City.TrimStart() $City = $City.TrimEnd() $State = $State.TrimStart() #Form the API URL string $APIStreet = ($Street -Replace " ", "+") $APICity = ($City -Replace " ", "+") $APIState = ("+" + $State) #Concatenate the string together to form the API call $FullAPIAddress = ($APIStreet + "," + $APICity + "," + $APIState) #Try to cal lthe function that performs the API call try { PerformAPICall $FullAPIAddress } #If the call is executed with a bad record that is not well-formed, then below error will output Catch { Write-Host "$Line - Error on this record! - $APICall <==== Control+Left Click To View" -ForegroundColor Red } } } #Condition to handle if there is not an input file holding list of addresses else { Write-Host "addressList.txt file not found!" -ForegroundColor Red }
Please note, I had to “XXXX” out a lot of content due to security reasons, but the script itself is unaffected. Please comment if you have any questions.