My Solution: August 2015 PowerShell Scripting Games Puzzle
A couple of months ago, PowerShell.org announced that the PowerShell Scripting Games had been re-imagined as a monthly puzzle. In August, the second puzzle was published.
The instructions stated that a one-liner could be used if you were using a newer version of
PowerShell. A public JSON endpoint can be found at https://www.telize.com/geoip
and your goal is
to write some PowerShell code to display output similar to the following:
1longitude latitude continent_code timezone
2--------- -------- -------------- --------
3-115.1685 36.2212 NA America/Los_Angeles
Try to accomplish this with a one-liner but use full cmdlet and parameter names. Write an advanced function that's a wrapper for this endpoint.
Here's my one-liner solution. It requires PowerShell version 3 or higher:
1Invoke-RestMethod -Uri 'www.telize.com/geoip' |
2Select-Object -Property longitude, latitude, continent_code, timezone
I decided to write a reusable tool and create a script module out of it named "MrGeo" that I could add additional Geolocation related functions to in the future:
1#Requires -Version 3.0
2function Get-MrGeoInformation {
3
4<#
5.SYNOPSIS
6 Queries www.telize.com for Geolocation information based on IP Address.
7
8.DESCRIPTION
9 Get-MrGeoInformation is a PowerShell function that is designed to query
10 www.telize.com for Geolocation information for one or more IPv4 or IPv6 IP
11 Addresses. If an IP Address is not specified, your public IP Address is used.
12
13.PARAMETER IPAddress
14 The IPAddress(es) to return the Geolocation information for.
15
16.EXAMPLE
17 Get-MrGeoInformation
18
19.EXAMPLE
20 Get-MrGeoInformation -IPAddress '46.19.37.108', '2a02:2770::21a:4aff:feb3:2ee'
21
22.EXAMPLE
23 '46.19.37.108', '2a02:2770::21a:4aff:feb3:2ee' | Get-MrGeoInformation
24
25.INPUTS
26 IPAddress
27
28.OUTPUTS
29 GeoInfo
30
31.NOTES
32 Author: Mike F Robbins
33 Website: http://mikefrobbins.com
34 Twitter: @mikefrobbins
35#>
36
37 [CmdletBinding()]
38 param (
39 [Parameter(ValueFromPipeline)]
40 [ipaddress[]]$IPAddress
41 )
42
43 PROCESS {
44
45 if (-not($PSBoundParameters.IPAddress)) {
46 Write-Verbose -Message 'Attempting to retrieve Geolocation information for your public IP Address'
47 $Results = Invoke-RestMethod -Uri 'http://www.telize.com/geoip' -TimeoutSec 30
48 }
49 else {
50 $Results = foreach ($IP in $IPAddress) {
51 Write-Verbose -Message "Attempting to retrieving Geolocation information for IP Address: '$IP'"
52 Invoke-RestMethod -Uri "http://www.telize.com/geoip/$IP" -TimeoutSec 30
53 }
54 }
55
56 foreach ($Result in $Results) {
57 $Result.PSTypeNames.Insert(0,'Mr.GeoInfo')
58 Write-Output $Result
59 }
60
61 }
62
63}
Notice that in the previous code I added my initials (Mr) as a prefix for the noun to help prevent
name collisions with other people's functions that are named the same thing. I also added additional
functionality so that in addition to retrieving the information for your current public IP address,
that one or more public IPv4 or IPv6 addresses could be specified. Comment based help has been
included along with verbose output. Pipeline input is accepted for the IPAddress parameter and the
[ipaddress]
type accelerator is used to perform parameter validation for both IPv4 and IPv6
addresses for that parameter.
I created custom formating for the module to display the required output plus the IP address by
default for both table and list output but additional data can retrieved by simply piping to
Select-Object
, Format-Table,
or Format-List
and specifying -Property * or specific properties
without having to modify the function itself.
1<?xml version="1.0" encoding="utf-8" ?>
2<Configuration>
3 <ViewDefinitions>
4 <View>
5 <Name>Mr.GeoInfo</Name>
6 <ViewSelectedBy>
7 <TypeName>Mr.GeoInfo</TypeName>
8 </ViewSelectedBy>
9 <TableControl>
10 <TableHeaders>
11 <TableColumnHeader>
12 <Width>30</Width>
13 </TableColumnHeader>
14 <TableColumnHeader>
15 <Width>11</Width>
16 </TableColumnHeader>
17 <TableColumnHeader>
18 <Width>10</Width>
19 </TableColumnHeader>
20 <TableColumnHeader>
21 <Width>16</Width>
22 </TableColumnHeader>
23 <TableColumnHeader>
24 <Width>20</Width>
25 </TableColumnHeader>
26 </TableHeaders>
27 <TableRowEntries>
28 <TableRowEntry>
29 <TableColumnItems>
30 <TableColumnItem>
31 <PropertyName>ip</PropertyName>
32 </TableColumnItem>
33 <TableColumnItem>
34 <PropertyName>longitude</PropertyName>
35 </TableColumnItem>
36 <TableColumnItem>
37 <PropertyName>latitude</PropertyName>
38 </TableColumnItem>
39 <TableColumnItem>
40 <PropertyName>continent_code</PropertyName>
41 </TableColumnItem>
42 <TableColumnItem>
43 <PropertyName>timezone</PropertyName>
44 </TableColumnItem>
45 </TableColumnItems>
46 </TableRowEntry>
47 </TableRowEntries>
48 </TableControl>
49 </View>
50 <View>
51 <Name>Mr.GeoInfo</Name>
52 <ViewSelectedBy>
53 <TypeName>Mr.GeoInfo</TypeName>
54 </ViewSelectedBy>
55 <ListControl>
56 <ListEntries>
57 <ListEntry>
58 <ListItems>
59 <ListItem>
60 <PropertyName>ip</PropertyName>
61 </ListItem>
62 <ListItem>
63 <PropertyName>longitude</PropertyName>
64 </ListItem>
65 <ListItem>
66 <PropertyName>latitude</PropertyName>
67 </ListItem>
68 <ListItem>
69 <PropertyName>continent_code</PropertyName>
70 </ListItem>
71 <ListItem>
72 <PropertyName>timezone</PropertyName>
73 </ListItem>
74 </ListItems>
75 </ListEntry>
76 </ListEntries>
77 </ListControl>
78 </View>
79 </ViewDefinitions>
80</Configuration>
As a best practice, always create a module manifest when creating a PowerShell module:
1#
2# Module manifest for module 'MrGeo'
3#
4# Generated by: Mike F Robbins
5#
6# Generated on: 8/8/2015
7#
8
9@{
10
11# Script module or binary module file associated with this manifest.
12RootModule = 'MrGeo'
13
14# Version number of this module.
15ModuleVersion = '1.0'
16
17# ID used to uniquely identify this module
18GUID = '942fa453-a5c2-4bbd-9e6e-a2783bdb48e8'
19
20# Author of this module
21Author = 'Mike F Robbins'
22
23# Company or vendor of this module
24CompanyName = 'mikefrobbins.com'
25
26# Copyright statement for this module
27Copyright = '(c) 2015 Mike F Robbins. All rights reserved.'
28
29# Description of the functionality provided by this module
30Description = 'Mike F Robbins Geo PowerShell Module'
31
32# Minimum version of the Windows PowerShell engine required by this module
33PowerShellVersion = '3.0'
34
35# Name of the Windows PowerShell host required by this module
36# PowerShellHostName = ''
37
38# Minimum version of the Windows PowerShell host required by this module
39# PowerShellHostVersion = ''
40
41# Minimum version of Microsoft .NET Framework required by this module
42# DotNetFrameworkVersion = ''
43
44# Minimum version of the common language runtime (CLR) required by this module
45# CLRVersion = ''
46
47# Processor architecture (None, X86, Amd64) required by this module
48# ProcessorArchitecture = ''
49
50# Modules that must be imported into the global environment prior to importing this module
51# RequiredModules = @()
52
53# Assemblies that must be loaded prior to importing this module
54# RequiredAssemblies = @()
55
56# Script files (.ps1) that are run in the caller's environment prior to importing this module.
57# ScriptsToProcess = @()
58
59# Type files (.ps1xml) to be loaded when importing this module
60# TypesToProcess = @()
61
62# Format files (.ps1xml) to be loaded when importing this module
63FormatsToProcess = 'MrGeo.ps1xml'
64
65# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
66# NestedModules = @()
67
68# Functions to export from this module
69FunctionsToExport = '*'
70
71# Cmdlets to export from this module
72CmdletsToExport = '*'
73
74# Variables to export from this module
75VariablesToExport = '*'
76
77# Aliases to export from this module
78AliasesToExport = '*'
79
80# DSC resources to export from this module
81# DscResourcesToExport = @()
82
83# List of all modules packaged with this module
84# ModuleList = @()
85
86# List of all files packaged with this module
87# FileList = @()
88
89# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
90PrivateData = @{
91
92 PSData = @{
93
94 # Tags applied to this module. These help with module discovery in online galleries.
95 # Tags = @()
96
97 # A URL to the license for this module.
98 # LicenseUri = ''
99
100 # A URL to the main website for this project.
101 # ProjectUri = ''
102
103 # A URL to an icon representing this module.
104 # IconUri = ''
105
106 # ReleaseNotes of this module
107 # ReleaseNotes = ''
108
109 } # End of PSData hashtable
110
111} # End of PrivateData hashtable
112
113# HelpInfo URI of this module
114# HelpInfoURI = ''
115
116# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
117# DefaultCommandPrefix = ''
118
119}
Notice that PowerShell version 3 is specified in the manifest as the minimum version required by this module. The custom format ps1xml file is also specified in the manifest. If you plan to publish your module in a NuGet repository with PowerShellGet that ships in PowerShell version 5, you'll need to specify an author and description in the manifest.
Give it a try with both IPv4 and IPv6 addresses and both pipeline and parameter input:
1'46.19.37.108', '2a02:2770::21a:4aff:feb3:2ee' | Get-MrGeoInformation
2Get-MrGeoInformation -IPAddress '46.19.37.108', '2a02:2770::21a:4aff:feb3:2ee'
One of the reasons I prefer to place functions like this in a PowerShell script module is that with
PowerShell version 3 and higher, you can simply call the function and the module will auto-load as
long as it exists in the $env:PSModulePath
. No need to remember or figure out where you saved that
ps1 file that contains the function and no need to dot source it.
The MrGeo PowerShell script module shown in this blog article can be downloaded from my Scripting Games repository on GitHub.
Update February 20th, 2019:
The public API this functions uses has been shutdown. I've updated the function to use a different API and moved it to my PowerShell repository on GitHub.
µ