35

I need to verify if a string is a valid IPv4 or IPv6 address in a Batch script, but apparently Batch doesn't have an easy way to parse an IP address.

How can I do this in Batch without using external tools? By "external tools" I mean things that aren't already present on a normal Windows installation.

By "valid IPv4 or IPv6 address" I mean a string that is in the format of an IP address, false positives with strings like 999.999.999.999 are OK (even if some basic filtering is welcome). As a rule of thumb, a solution should at least be able to distinguish an error message from an address.

I know that a similar question already exists, but that question doesn't take IPv6 into consideration and it's more strict in its definition of "valid IPv4 address" (something like 999.999.999.999 isn't acceptable).

ThePirate42
  • 821
  • 6
  • 22
  • 7
    @PedroLobito In case you're not aware, this question is being discussed on [Meta](https://meta.stackoverflow.com/questions/405162) so that's where you should voice your opinions on whether this question should be closed or not. Also, lack of effort is *never* a reason to close a question. – cigien Feb 14 '21 at 21:10
  • 1
    Does this answer your question? [IP verification in batch script - first match by findstr, secondly verify by for loops (only using windows built in functinallity?](https://stackoverflow.com/questions/20299266/ip-verification-in-batch-script-first-match-by-findstr-secondly-verify-by-for) – SomethingDark Feb 15 '21 at 09:46
  • @SomethingDark No, that question is only about ipv4 and it's more strict in its definition of "valid IPv4 or IPv6 address" (something like `999.999.999.999` isn't acceptable) – ThePirate42 Feb 15 '21 at 09:52
  • 3
    It looks strange to open a new question with lower/incorrect requirements for an IP address. Without any context why that should be superior to an already correct answer. Looks like a question, build only to fit to a prepared answer – jeb Feb 15 '21 at 11:29
  • 1
    @jeb First, the original question doesn't take in consideration ipv6. Second, yes, this is a question build only to fit to a prepared answer. At least in my understanding, [this](https://stackoverflow.com/help/self-answer) page of the StackOverflow help center encourages this behavior. – ThePirate42 Feb 15 '21 at 11:37
  • 1
    Can you please [edit] your question to clarify what are your criteria for considering an address "valid"? So far the question only says that it accepts false positives, ruling at that the criteria is validity as per the spec, but gives only one example. For example, is ``999.999.999.9999``, ``127.127.127.127.127`` or ``...127`` valid? What are the criteria for IPv6? – MisterMiyagi Feb 15 '21 at 12:01
  • @MisterMiyagi I need something that can recognise an address in formats like this `2001:0db8:0000:0000:0000:0000:1428:57ab` or this `2001:0db8::1428:57ab`. It's ok if ipv6 addresses with the prefix notation (like `::1/128`) are not considered valid. The problem is I don't know ip specifications good enough to put this vague set of rules in a more formal and precise way. Isn't my last edit, "As a rule of thumb, a solution should at least be able to distinguish an error message from an address." good enough? – ThePirate42 Feb 15 '21 at 12:14
  • @MisterMiyagi Also, my original use case was to check if a string is a valid ip before sending it through the Clouflare api, for a dynamic DNS script. Do you think I should add that in the question? – ThePirate42 Feb 15 '21 at 12:15
  • 2
    Glad it was closed again, and even if it's reopen, I'm sure it will be closed again. **Title** : "_How can I verify that a string is a valid IPv4 or IPv6 address in batch?_" **Question**: "_false positives with strings like 999.999.999.999 are OK_" - You want to verify if an IP address is valid, but you consider `999.999.999.999` as "_OK_", that makes absolutely no sense to me. – Pedro Lobito Feb 18 '21 at 12:44

4 Answers4

15

Check for valid IPv4:

@if (@X)==(@Y) @end /* JScript comment
    @echo off
    cscript //E:JScript //nologo "%~f0"  %*
    exit /b %errorlevel%
@if (@X)==(@Y) @end JScript comment */
WScript.Quit(ValidateIPaddress(WScript.Arguments.Item(0)));
function ValidateIPaddress(ipaddress) {
 return !(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress))
}

For valid IPv6 address:

@if (@X)==(@Y) @end /* JScript comment
    @echo off
    cscript //E:JScript //nologo "%~f0"  %*
    exit /b %errorlevel%
@if (@X)==(@Y) @end JScript comment */
WScript.Quit(ValidateIPaddress(WScript.Arguments.Item(0)));
function ValidateIPaddress(ipaddress) {
 return !(/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gm.test(ipaddress))
}

Both are relying on the exit code, so here's how they can be used:

call validIPV4.bat 12.12.12.12 && (
   echo valid ipv4 
) || (
   echo invalid ipv4
)

call validIPV4.bat 12.12.12.6000 && (
  echo valid ipv4 
) || (
  echo invalid ipv4
)

The first will print valid ipv4 the second invalid ipv4.

Or you can check the errorlevel:

call validIPV6.bat "1200:0000:AB00:1234:0000:2552:7777:1313"
if %errorlevel% equ 0 ( 
  echo valid ipv6 
) else (
  echo invalid ipv6
)
Andrew Hardiman
  • 929
  • 1
  • 15
  • 30
npocmaka
  • 55,367
  • 18
  • 148
  • 187
  • 1
    Your regexes are oddly complex. Just for starters `{7,7}` is the same as `{7}`. – OrangeDog Feb 16 '21 at 10:24
  • @OrangeDog - thanks for the feedback. I'm not so handy with the regexes ,though usually I manage to bring them up to a working state. If you wish add an answer with a better expressions and I'll upvote. – npocmaka Feb 16 '21 at 12:56
  • 3
    For example, `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` would meet OP's criteria for IPv4. For v6 a simple group is probably required. – OrangeDog Feb 16 '21 at 13:05
  • 1
    e.g. `(\p{HexDigit}{4}:){7}\p{HexDigit}{4}` for a full one. A little more complex for omitting groups. – OrangeDog Feb 18 '21 at 10:41
12

After some experimenting (and googling) I managed to write a function that checks the validity of an address with the help of Powershell.

The function asks for a variable name and a string (enclosed in quotes). If the string is a valid IP, the value of the variable is set to True, else it's set to False.

set "_ip=127.0.0.1"
call :checkip _result "%_ip%"
echo %_result%

set "_ip=::1/128"
call :checkip _result "%_ip%"
echo %_result%

set "_ip=Not an ip address"
call :checkip _result "%_ip%"
echo %_result%

goto :eof

:checkip
setlocal
set _var=%1
set _ip=%~2
set _prefixlenght=%_ip:*/=%
call set _ip=%%_ip:/%_prefixlenght%=%%
for /F "usebackq tokens=*" %%g in (`powershell -c "$ipaddrobj = [ipaddress]::Any ; if (!([ipaddress]::TryParse('%_ip%', [ref]$ipaddrobj))){if (!([ipaddress]::TryParse('%_ip%'.split(':')[0], [ref]$ipaddrobj))){return $false}} ; return $true"`) do (set _ipvalid=%%g)
endlocal & set %_var%=%_ipvalid%
goto :eof

The Powershell line is a readaptation of a Powershell function that I found here in a comment.

ThePirate42
  • 821
  • 6
  • 22
  • 6
    This answer doesn't meet your own criteria of `999.999.999.999` being an acceptable input. – SomethingDark Feb 16 '21 at 02:44
  • 4
    @SomethingDark It isn't a criteria, I just said that it's OK if a solution has false positives like `999.999.999.999`. – ThePirate42 Feb 16 '21 at 09:18
  • 4
    In your last sentence, you explicitly listed not accepting `999.999.999.999` as one of the reasons that the suggested duplicate was an insufficient solution. – SomethingDark Feb 16 '21 at 13:18
  • 1
    @SomethingDark The difference is that the other question **requires** `999.999.999.999` and similar artifacts to be filtered, while mine accepts some false positives. This doesn't mean that recognising obviusly invalid addresses like `999.999.999.999` as valid addresses is required, it just means that an answer to my question doesn't have to be "human input proof". What I wanted was a quick way to check if some program/API/etc. that is normally supposed to output an address has actually produced an address, and not something like an error code. – ThePirate42 Feb 16 '21 at 17:02
6

Here is an attempt with Vbscript using RegEx to check both IPv4 or IPv6

RegEx Demo Here

Vbscript

Option Explicit
Dim  Title,IP,Array_IP
Title = "Test the validity of an IP address IPv4 Or IPv6"
ForceCScriptExecution(Title)

Array_IP = Array(_
    "1200:0000:AB00:1234:0000:2552:7777:1313",_
    "192.168.1.1",_
    "255.255.0.0",_
    "172.16.18.21",_
    "172.16.18.500",_
    "1600:0000:AB30:1234:0000:2552:7777:1313",_
    "172.16.300.21",_
    "172.16.18.23",_
    "172.256.18.21",_
    "255.255.255.0",_
    "255.255.255.255"_
)

For Each IP in Array_IP
    If Is_Valid(IP) = True Then
        Wscript.echo IP & " is a Valid IP Address"
    Else
        Wscript.echo IP & " is Not a Valid IP Address"
    End if
Next

Wscript.sleep 20000
'------------------------------------------------------------------------------------
Function Is_Valid(ip)
    Dim RegularExpressionObject
    Set RegularExpressionObject = New RegExp
    With RegularExpressionObject
        .Pattern = "(^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$)|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
        .IgnoreCase = False
        If .Test(ip)= True then
            Is_Valid = True
        end if
    End With
End Function
'------------------------------------------------------------------------------------
Sub ForceCScriptExecution(Title)
    Dim Arg, Str, cmd
    cmd = "CMD /C Title " & Title &" & color 0A & Mode 80,30 & "
    If Not LCase( Right( WScript.FullName, 12 ) ) = "\cscript.exe" Then
        For Each Arg In WScript.Arguments
            If InStr( Arg, " " ) Then Arg = """" & Arg & """"
            Str = Str & " " & Arg
        Next
        CreateObject( "WScript.Shell" ).Run _
           cmd & "cscript //nologo """ & _
            WScript.ScriptFullName & _
            """ " & Str
        WScript.Quit
    End If
End Sub
'-------------------------------------------------------------------------------------

Batch File :

@echo off
Title Test the validity of an IP address IPv4 Or IPv6
set "_IP=1200:0000:AB00:1234:0000:2552:7777:1313"
Call :CheckIp "%_IP%" Validity
if [%Validity%] equ [0] ( 
    color 0A
    echo "%_IP%" is a valid ip
) else (
    Color 0C
    echo "%_IP%" is not a valid ip
)
Pause
set "_IP=192.168.500.300"
Cls
Call :CheckIp "%_IP%" Validity
if [%Validity%] equ [0] ( 
    color 0A
    echo "%_IP%" is a valid ip
) else (
    Color 0C
    echo "%_IP%" is not a valid ip
)
Pause
Exit
::--------------------------------------------------------------------------------------------------
:CheckIP <IP> <Validity>
Set "VbsFile=%Temp%\%~n0.vbs"
(
    echo WScript.Echo(Is_Valid("%~1"^)^)
    echo Function Is_Valid(ip^)
    echo    Dim RegularExpressionObject
    echo    Set RegularExpressionObject = New RegExp
    echo    With RegularExpressionObject
    echo        .Pattern = "(^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$)|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"
    echo        .IgnoreCase = False
    echo        If .Test(ip^)= True then
    echo            Is_Valid = 0
    echo        Else
    echo            Is_Valid = 1
    echo        End if
    echo    End With
    echo End Function
)>"%VbsFile%"
@for /f "delims=" %%a in ('cscript //nologo "%VbsFile%"') do set "%2=%%a"
Exit /B
::--------------------------------------------------------------------------------------------------
Hackoo
  • 18,337
  • 3
  • 40
  • 70
6

How can I do this in Batch without using external tools? By "external tools" I mean things that aren't already present on a normal Windows installation.

You can use powershell, which is bundled with all windows computers, i.e.:

ip_validate.bat

@echo off

powershell -Command "[ipaddress]::TryParse('192.168.1.1',[ref][ipaddress]::Loopback)"
powershell -Command "[ipaddress]::TryParse('192.168.1.256',[ref][ipaddress]::Loopback)"
powershell -Command "[ipaddress]::TryParse('2001:0db8:85a3:0000:0000:8a2e:0370:7334',[ref][ipaddress]::Loopback)"
powershell -Command "[ipaddress]::TryParse('20ddd01:0db8:85a3:0000:0000:8a2e:0370:7334',[ref][ipaddress]::Loopback)"

Output:

True
False
True
False
ChatoPaka
  • 150
  • 6
  • This is ultimately the same code as what ThePirate42 posted in their own answer. – SomethingDark Feb 16 '21 at 17:18
  • ThePirate42 code: ```for /F "usebackq tokens=*" %%g in (`powershell -c "$ipaddrobj = [ipaddress]::Any ; if (!([ipaddress]::TryParse('%_ip%', [ref]$ipaddrobj))){if (!([ipaddress]::TryParse('%_ip%'.split(':')[0], [ref]$ipaddrobj))){return $false}} ; return $true"`) do (set _ipvalid=%%g)``` My code: ```powershell -Command "[ipaddress]::TryParse('192.168.1.1',[ref][ipaddress]::Loopback)"``` – ChatoPaka Feb 16 '21 at 17:31
  • Yes, both use `[ipaddress]::TryParse`, which is the core of the answer. – SomethingDark Feb 16 '21 at 18:05