I wanted to know how often dynamic IP addresses change, and be alerted when they do. In fact, some colleagues have reported that even “static” IPs are sometimes changed without warning, so this script would address that as well.
The script saves two files, an .xml file with the latest public IP address, and a .log file listing the history of IP addresses. Specify as the first parameter where you want those files stored. If you don’t specify a log file path, the files will be stored in the same folder where the script is located. (Note the use of the %IT_Scripts% environment variable in the screen shot below—see this post for details.)
By default, the script uses whatismyip.akamai.com to retrieve the machine’s public IP address. You can change this with the second and third parameters. See the parameter notes in the script.
The script returns 0 if the IP address has not changed and 1001 if it has. Deploy this as a 24×7 Check with with Max Remote Management (MaxRM). Depending on your settings, that will run the script every 5-60 minutes. Each execution takes less than a second. If you want to receive an email when the IP changes, set the Alert Settings to email you on an “Outage”. Note that the first time the script is run, it will report an IP change because it doesn’t have any history to compare to.
Script output is available in the MaxRM dashboard:
Cut and paste the script below and save it e.g. as “MCB.TrackPublicIPHistory.ps1”.
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | <# .Synopsis Keep a history of the computer's public IP in a text file. Return "error" code 1001 when the IP changes so we can optionally set up monitoring software to send an email notification. Copyright (c) 2016 by MCB Systems. All rights reserved. Free for personal or commercial use. May not be sold. No warranties. Use at your own risk. .Notes Name: MCB.TrackPublicIPHistory.ps1 Author: Mark Berry, MCB Systems Created: 03/31/2016 Last Edit: 03/31/2016 .Changes 03/31/2016 Initial version. .Description Retrieve the public IP address. If it has changed, return 1001 and record the change to a text file. .Parameter ScriptLogFilesPath Where to store the status and log files for this script. If not specified, files go in the same directory as the script. Default: "". .Parameter PublicIPSource The HTTP address from which to retrieve an external (WAN) IP address. Default: http://whatismyip.akamai.com/ .Parameter PublicIPXMLXPath The optional XPATH to use for extracting the text block containing the IP address from the text returned by PublicIPSource. For example, if the IP is in the Body of an HTML page, use "/html/body". http://whatismyip.akamai.com/ does not return any HTML with the IP, so the default is empty. Default: "" Note: AFTER extracting this XML element, a regular expression will extract just the IP address, so it's okay if there is some text before and/or after the IP. .Parameter LogFile Path to a log file. Required by MaxRM script player. Not used here. Default: "". .Example MCB.TrackPublicIPHistory ` -ScriptLogFilesPath ($Env:IT_Scripts + 'LogFiles\') ` -PublicIPSource "http://whatismyip.akamai.com/" ` -PublicIPXMLXPath "" ` #> ################################################################################ # STEP 1: Get command line arguments. ################################################################################ # Must be the first statement in the script param ( [ Parameter ( Mandatory = $false , Position = 0, ValueFromPipelineByPropertyName = $true )] [String] $ScriptLogFilesPath ="", [ Parameter ( Mandatory = $false , Position = 1, ValueFromPipelineByPropertyName = $true )] [String] $PublicIPSource ="http://whatismyip.akamai.com/ ", [Parameter(Mandatory = $false, Position = 2, ValueFromPipelineByPropertyName = $true)] [String]$PublicIPXMLXPath="", [Parameter(Mandatory = $false, Position = 3, ValueFromPipelineByPropertyName = $true)] [String]$LogFile="" ) ################################################################################ # STEP 2: Script setup ################################################################################ # Set up and start stopwatch so we can print out how long it takes to run script # http://stackoverflow.com/questions/3513650/timing-a-commands-execution-in-powershell $StopWatch = [Diagnostics.Stopwatch]::StartNew() # In case this script was retrieved from Path, get its directory $executingScriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path # Also get its name, without extension--used to name the .xml and .log files $executingScriptBaseName = (Get-Item $MyInvocation.MyCommand.Name).BaseName # Override $executingScriptBaseName with a fixed name--comment out to use script's name $executingScriptBaseName = " MCB.TrackPublicIPHistory " if ($ScriptLogFilesPath -eq "") { # if $ScriptLogFilesPath parameter not specified $ScriptLogFilesPath = $executingScriptDirectory } elseif ( !(Test-Path $ScriptLogFilesPath) ) { # $ScriptLogFilesPath parameter was specified but folder doesn't exist $ScriptLogFilesPath = $executingScriptDirectory } # Use Join-Path to get full paths--adds " \ " if necessary $StatusFileFullPath = Join-Path -Path $ScriptLogFilesPath -ChildPath " $executingScriptBaseName .xml " $LogFileFullPath = Join-Path -Path $ScriptLogFilesPath -ChildPath " $executingScriptBaseName .log " # Look for $StatusFileFullPath file as determined above. # If the file is found, load the values into a hash. If not found, initialize an empty hash. if (Test-Path ($StatusFileFullPath)) { # Retrieve the hash table containing the status values $IPChangeStatusHash = Import-Clixml ($StatusFileFullPath) } else { # Status file doesn't exist yet. Create an empty hash. Assume IP not registered. $IPChangeStatusHash = @{" CurrentIPAddress "=""; " CurrentIPAddressChangedAt "=""} } $MainErrorFound = $false # Concatenate output to $Output so we can prepend a one-line $Status $Output = "" ################################################################################ # STEP 3: Get public IP ################################################################################ $Output += " `n====================================================== " $Output += " `nRetrieve public IP from a " "What is my IP?"" service" $Output += "`n======================================================" function GetAndDisplayIP( [String] $PublicIPSource =" ", [String]$PublicIPXMLXPath="") { # initialize the three objects we will return $FuncErrorFound = $false $PublicIPContent = "" $PublicIP = "" try { $PublicIPContent = (New-Object System.Net.WebClient).DownloadString($PublicIPSource) } catch { $Output += " `nFailed to download content from """ + $PublicIPSource + "" ". System message:" $Output += "`n " + $_ .Exception.Message.ToString() $FuncErrorFound = $true } # Extract string using XPath: see Karl Prosser's 11/16/2007 post here: # http://www.techtalkz.com/microsoft-windows-powershell/172318-xmldocument-selectsinglenode-doesnt-work.html if ( $PublicIPXMLXPath -ne " ") { try { # Treat $PublicIPContent as XML - use XPath to extract string [xml]$PublicIPXML = $PublicIPContent $PublicIPContent = $PublicIPXML.SelectSingleNode($PublicIPXMLXPath).InnerText } catch { $Output += " `nFailed to extract data from """ + $PublicIPSource + ` "" " using XPath "" " + $PublicIPXMLXPath + "" ". System message:" $Output += "`n " + $_ .Exception.Message.ToString() $FuncErrorFound = $true } } # Extract the IP only using a regular expression from this post: # http://stackoverflow.com/questions/2890896/extract-ip-address-from-an-html-string-python $RegEx = '[0-9]+(?:\.[0-9]+){3}' if ( $PublicIPContent -match $RegEx ) { $PublicIP = $matches [0] # $matches array automatically populated by -match } else { $PublicIP = " " $PublicIPContent = 'No IP address found in content " ' + $PublicIPContent + '".' } # Always return three objects $FuncErrorFound $PublicIPContent $PublicIP } # end of GetAndDisplayIP function if ( $PublicIPSource -ne "") { $ErrorFound , $PublicIPContent , $PublicIP = GetAndDisplayIP $PublicIPSource $PublicIPXMLXPath $Output += "`n " + $PublicIPSource + ': ' + $PublicIPContent # output results if ($ErrorFound) { $MainErrorFound = $true } } ################################################################################ # STEP 4: Check for and record IP change ################################################################################ $OldCurrentIPAddress = $IPChangeStatusHash.get_Item(" CurrentIPAddress ") # the _old_ CurrentIPAddress if ($PublicIP -eq "") { $IPComparisonResult = " No public IP found, so IP change not checked " $Output += " `n " + $IPComparisonResult } elseif ($PublicIP -eq $OldCurrentIPAddress) { # compare to the _old_ CurrentIPAddress $IPComparisonResult = " Current public IP $PublicIP matches previous IP, so no change recorded " $Output += " `n " + $IPComparisonResult } else { # new public IP found $IPComparisonResult = " Current IP $PublicIP does not match previous IP $OldCurrentIPAddress " $Output += " `n " + $IPComparisonResult # Call this an " error " to return 1001 so monitoring software can send email alert on IP change $MainErrorFound = $true $Output += " `n " $Output += " `n====================================================== " $Output += " `nRecord the IP address change " $Output += " `n====================================================== " $LineToAppend = (get-date -format " yyyy/MM/dd HH:mm:ss ") + " - " + $PublicIP # Append one line to text file Add-Content -Path $LogFileFullPath -Value ($LineToAppend) $Output += " `nAdded this line to $LogFileFullPath : " $Output += " `n " + $LineToAppend # Update the Current IP and time to the status hash (to be written out at end of script) $IPChangeStatusHash.set_Item(" CurrentIPAddress ", $PublicIP) $IPChangeStatusHash.set_Item(" CurrentIPAddressChangedAt ", (Get-Date -Format " G ")) # short date + long time with AM/PM } # new public IP found ################################################################################ # STEP 5: Output the IP history from the .log file ################################################################################ $Output += " `n " $Output += " `n====================================================== " $Output += " `nIP history from $LogFileFullPath " $Output += " `n====================================================== " $Output += " `n " # Get-Content returns a string array. Pipe to Out-String to convert to string and preserve line breaks. $Output += (Get-Content -Path $LogFileFullPath | Out-String) ################################################################################ # STEP 6: Wrap up ################################################################################ # Save the hash table to a file in $ScriptLogFilesPath as determined above $IPChangeStatusHash | Export-Clixml ($StatusFileFullPath) $Status = $IPComparisonResult #Prepare SummaryLine for display in monitoring system. Abbreviate date/time. $SummaryLine = $Status + " [ " + (Get-Date -Format " MM/dd HH:mm ") + " ] " $SummaryLine $Output " ====================================================== " " Local Machine Time: " + (Get-Date -Format G) # Stop the stopwatch and calculate the elapsed time in seconds, rounded to 0.1 seconds $StopWatch.Stop() $ElapsedSecondsRounded = [Math]::Round($StopWatch.Elapsed.TotalSeconds, 1) $ElapsedString = [string]::Format(" {0:0.0} second(s)", $ElapsedSecondsRounded ) 'Script execution took ' + $ElapsedString + '.' if ( $MainErrorFound ) { $ExitCode = 1001 } else { $ExitCode = 0 } 'Exit Code: ' + $ExitCode Exit $ExitCode |