Client Management Suite

 View Only

VBScript to Wrap a Windows Firewall Dump through Netsh 

Mar 24, 2015 12:30 PM

When documenting our server installations, one of the time-consuming documentation line-items is the Windows Firewall. For our service development flexibility and speed, our Windows firewall management is not centralised. This has the disadvantage that firewall auditing is a local operation.

Historically, documenting the firewall configuration meant going into the Windows firewall control panel applet and ennumerating the rules by hand. Later, we used the power of netsh to display the firewall config which has worked well, but still involved a lot of manual editing of the output. 

Today's I'll demonstrate the script we now use to give a unified output for our firewall rules across 2003/2008 and 2012 servers.  It's a vbscript that parses netsh output to provide firewall rules in a standardised, delimited format. It's not a powershell script as the required firewall cmdlets are only present in Windows 2012 Server and above, rendering a cross-platform solution impossible.

The script provides,

  1. Single line, delimited rules.
    Netsh output has the scope definitions listed as an entry on a new line which means it's fiddly to import the output into a table.
     
  2. Aliasing
    The scope fields care often unreadable in environments where multiple IP and subnet are present. This script allows you to create aliases for common subnets and IPs to make your rules more readable
     
  3. Standardised Output
    Netsh output can change with OS. The script understands that Windows 2003 server output differs slightly from 2008/2012 and hides this. 

It also provided a bit of fun, as working with regular expressions is always a joy. 

 

The Firewall_Audit.VBS Script

The script detailed below should be pasted into a notepad and saved (or use the zipped download attached to this article). When the script is executed, it will create a txt file in the same location as the script called Firewall_Audit-%COMPUTERNAME%.txt. This text file will contain the system's enumerated Windows firewall rules.

 

'#####################################################################################
'#
'# Script: FirewallAudit.vbs
'#
'# Description: Script to provide simplified windows firewall configuration across 
'#              2003,2008,2012 servers.
'#
'# Author:    Ian Atkin
'#
'# Version:    1.0
'#
'#
'#####################################################################################

'_____________________________________________________
'    Declarations
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 

Dim myAliases()

Class IPtoName
       Public IP_or_Subnet
       Public Name
End Class


'_____________________________________________________
'    Set the column delimeter
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
'sDelim=vbTab
sDelim=","

'_____________________________________________________
'    Configure IP and subnet shortform replacements for easier rule viewing
'    For example, to change the subnet 10.0.0.0/255.255.255.0 in the netsh
'    output to [PRIVATE_1] set the alias as follows,
'
'      SetAlias "10.0.0.0/255.255.255.0","[PRIVATE_1]"

'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 

SetAlias "*","ALL"


'_____________________________________________________
'    Ensure we run this using cscript.exe
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
forceCScriptExecution

'_____________________________________________________
'    Set name of output file
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ 
Set objShell = WScript.CreateObject("WScript.Shell")
sCompname=objShell.ExpandEnvironmentStrings( "%COMPUTERNAME%" )

set ofso=CreateObject("Scripting.FileSystemObject")
StrCurDir=ofso.GetParentFolderName(Wscript.ScriptFullName)
sOutFile=StrCurDir & "\Firewall_Audit_" & sCompname & ".txt"
snetshFile=StrCurDir & "\Firewall_Audit_" & sCompname & ".log"

'make sure we don't have this file existing already
if ofso.FileExists(sOutFile) then ofso.DeleteFile(sOutFile)

call AppendTxtToFile("Created by Firewall_Audit.vbs "& NOW(),sOutfile)
call AppendTxtToFile("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",sOutfile)
call AppendTxtToFile("",sOutfile)


'_____________________________________________________
'    Prepare Regular Expression
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Set myRegExp = New RegExp
myRegExp.IgnoreCase = True
myRegExp.Global = false
myRegExp.Multiline = true

'_____________________________________________________
'    GetOSVersion
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

OSVersionExpression="^\s*([0-9]+).([0-9]+).([0-9]+)$"
myRegExp.Pattern = OSVersionExpression 

Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set oss = objWMIService.ExecQuery ("Select * from Win32_OperatingSystem")

For Each os in oss
     sVersion=os.version
     If myRegExp.Test(os.version) Then
       Set myMatches = myRegExp.Execute(os.version)
       set myMatch =myMatches(0)
       myosmajorversion=mymatch.submatches(0)
     end if
Next

'_____________________________________________________
'    Set NetSh regular expressions according to OS
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

'In order to get firewall rules, here we define our regular expressions for the
'service, program and port firewall exceptions.

ServiceExpression = "^\s*Enable\s+(\S+)\s+(\S.+\S)\s*Scope:\s+(\S+)\s*$"

ProgramExpression="^\s*Enable\s+(\S+..?bound)\s+(\S.*\S)\s+/\s+(\S.*\S)\s*Scope:\s+(\S+)\s*$"
ProgramExpression_NT5="^\s*Enable\s+(\S+.*)\s+/\s+(\S.*\S)\s*Scope:\s+(\S+)\s*$"

PortExpression="^\s*([0-9]+)\s+(\S+)\s+Enable\s+(\S+)\s+(\S.*\S)\s*Scope:\s+(\S.*\S)\s*$"
PortExpression_NT5="^\s*([0-9]+)\s+(\S+)\s+Enable\s+(\S+.*\S)\s*Scope:\s+(\S.*\S)\s*$"

'_____________________________________________________
'    Get Raw Firewall config using netsh
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
StrNetshOutput=""
Set objExecObject = objShell.Exec("cmd /c netsh firewall show config verbose=ENABLE")
Do While Not objExecObject.StdOut.AtEndOfStream
    StrNetshOutput = StrNetshOutput & vbcrlf & objExecObject.StdOut.ReadLine()
Loop

'call writefile(snetshfile,StrNetshOutput)

'_____________________________________________________
'    Parse Raw Config and extract rules
'¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

For ProfileLoop=1 to 2

if ProfileLoop=1 then ProfileStr="domain"
if ProfileLoop=2 then ProfileStr="standard"

'Step through each line of netsh output looking for service configuration information
i=instr(1,lcase(StrNetshOutput),"service configuration for " & ProfileStr & " profile")
j=instr(1, lcase(StrNetshOutput),"allowed programs configuration for " & ProfileStr & " profile")
k=instr(1,lcase(StrNetshOutput),"port configuration for " & ProfileStr & " profile")
l=instr(1,lcase(StrNetshOutput),"icmp configuration for " & ProfileStr & " profile")


'First I want to see if there are any enabled service configuration rules. If present, 
'these will appear between i and j

m=i
count=0
Do while m<j 

  'grab two lines...
  mystr=GrabLine(m,StrNetshOutput) & GrabLine(instr(m,lcase(StrNetshOutput),vbcrlf) + 2,StrNetshOutput)
  myRegExp.Pattern = ServiceExpression 
  
  If myRegExp.Test(mystr) Then
    if count=0 then 
      wscript.echo vbcrlf & "Service Exceptions for " & ProfileStr & " profile" & vbcrlf & "---------------------------------------"
      call AppendTxtToFile(vbcrlf & "Service Exceptions for " & ProfileStr & " profile" & vbcrlf & "---------------------------------------",sOutfile)
    end if
    count=count+1
    Set myMatches = myRegExp.Execute(mystr)
    set myMatch =myMatches(0)

    myservice=mymatch.submatches(1)
    myscope=replacealiases(mymatch.submatches(2))

    wscript.echo "Service:" & myservice & sDelim & "Scope:" & myscope 
    call AppendTxtToFile(myservice & sDelim & myscope,sOutfile)
    'as we have a match move two lines on
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
  else
    'No match. Move one line on.
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
  end if

  
Loop

'Now search for enabled program exceptions
m=j
count=0
Do while m<k 
  mystr=GrabLine(m,StrNetshOutput) & GrabLine(instr(m,lcase(StrNetshOutput),vbcrlf) + 2,StrNetshOutput)
  if myosmajorversion="5" then
    myRegExp.Pattern = ProgramExpression_NT5 
  else
    myRegExp.Pattern = ProgramExpression 
  end if

  If myRegExp.Test(mystr) Then
    if count=0 then 
      wscript.echo vbcrlf & "Program Exceptions for " & ProfileStr & " profile" & vbcrlf & "---------------------------------------"
            call AppendTxtToFile(vbcrlf & "Program Exceptions for " & ProfileStr & " profile" & vbcrlf & "---------------------------------------",sOutfile)
    end if
    count=count+1
    Set myMatches = myRegExp.Execute(mystr)
    set myMatch =myMatches(0)

   if myosmajorversion="5" then
    mydir="In"
    myprogname=mymatch.submatches(1)
    myprogpath=mymatch.submatches(2)
    myscope=replacealiases(mymatch.submatches(2))
   else
    mydir=replace(mymatch.submatches(0),"bound","")
    myprogname=mymatch.submatches(1)
    myprogpath=mymatch.submatches(2)
    myscope=replacealiases(mymatch.submatches(3))
   end if

    
    wscript.echo mydir & sDelim & "Name:" & myprogname & sDelim & "Path:" & myprogpath & sDelim & "Scope:" & myscope
    call AppendTxtToFile(mydir & sDelim & myprogname & sDelim &  myprogpath & sDelim & myscope,sOutfile)

    'as we have a match move two lines on
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
  else
    'No match. Move one line on.
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
  end if
  
Loop

'Now search for port exceptions
m=k
count=0
Do while m<l
  'grab two lines...
  mystr=GrabLine(m,StrNetshOutput) & GrabLine(instr(m,lcase(StrNetshOutput),vbcrlf) + 2,StrNetshOutput)
  
  'now test to see if these match our regular expression

  if myosmajorversion="5" then
    myRegExp.Pattern = PortExpression_NT5 
  else
    myRegExp.Pattern = PortExpression 
  end if

  If myRegExp.Test(mystr) Then
    'We have a match!
    if count=0 then 
      wscript.echo vbcrlf & "Port Exceptions for " & ProfileStr & " profile" & vbcrlf & "------------------------------------"
      call AppendTxtToFile(vbcrlf & "Port Exceptions for " & ProfileStr & " profile" & vbcrlf & "------------------------------------",sOutfile)
    end if 
    count=count+1
    Set myMatches = myRegExp.Execute(mystr)
    set myMatch =myMatches(0)

   if myosmajorversion="5" then
    myport=mymatch.submatches(0)
    myprotocol=mymatch.submatches(1)
    mydir="In"
    myname=mymatch.submatches(2)
    myscope=replacealiases(mymatch.submatches(3))
   else
    myport=mymatch.submatches(0)
    myprotocol=mymatch.submatches(1)
    mydir=replace(mymatch.submatches(2),"bound","")
    myname=mymatch.submatches(3)
    myscope=replacealiases(mymatch.submatches(4))
   end if


    'as we have a match move two lines on
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
    wscript.echo "Port:" & myport & sDelim & myprotocol & sDelim & mydir & sDelim & myname & sDelim & "Scope:" & myscope
    call AppendTxtToFile(myprotocol & ":" & myport & sDelim & mydir & sDelim & myname & sDelim & myscope,sOutfile)

  else
    'No match. Move one line on.
    m=instr(m,lcase(StrNetshOutput),vbcrlf) + 2
  end if

Loop

wscript.echo
wscript.echo
call AppendTxtToFile("",sOutfile)
call AppendTxtToFile("",sOutfile)

next


'#############################################################
'#
'# FUNCTIONS AND SUBS
'# 
'############################################################# 

'###############################
'# Get a whole line from a multi-line string,
'# starting from a specific character position
'###############################

Function GrabLine(index,InputStr)

  'This function returns as a string all characters leading up to the line break
  'from the string index provided

  'wscript.echo "Hello-IN"
  myIndex=Instr(index,InputStr,vbcrlf)
  'wscript.echo "myIndex:" & myIndex
  'wscript.echo "Index:" & index

  GrabLine=Mid(InputStr,index,myIndex-index)
  'wscript.echo myIndex
  'wscript.echo "*" & GrabLine & "*"
  'wscript.echo "Hello-OUT"
  
End Function


'###############################
'# Write line to txt file 
'###############################

Sub AppendTxtToFile(sTxt,sFilename)
    Dim sFile, fso, ts
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set ts = fso.OpenTextFile(sFilename, 8, True)
    ts.WriteLine sTxt
    ts.close
    Set ts = Nothing

    Set fso = Nothing
End Sub

'###############################
'# Read text file
'###############################
function GetFile(FileName)
  If FileName<>"" Then
    Dim FS, FileStream
    Set FS = CreateObject("Scripting.FileSystemObject")
      on error resume next
      Set FileStream = FS.OpenTextFile(FileName)
      GetFile = FileStream.ReadAll
      if Err.Number<>0 then 
         'msgbox "Can't read File"
          wscript.quit ERR_FILEREAD
      End If
  End If


End Function

'###############################
'# Write string As a text file.
'###############################
function WriteFile(FileName, Contents)
  Dim OutStream, FS

  on error resume Next
  Set FS = CreateObject("Scripting.FileSystemObject")
  Set OutStream = FS.OpenTextFile(FileName, 2, True)
  OutStream.Write Contents
  if Err.Number<>0 then 
      'msgbox "Can't write File"
      wscript.quit ERR_FILEWRITE
  End If

End Function

'###############################
'# Alias routines
'###############################
Sub SetAlias(StrIP,StrName)

  on error resume next
  i=ubound(myAliases)
  on error goto 0
  redim preserve myAliases(i+1)
  set myAliases(i+1)=New IPtoName
  myAliases(i+1).IP_or_Subnet=StrIP
  myAliases(i+1).Name=StrName

End Sub

Function ReplaceAliases(myString)

  for i=1 to ubound(myAliases)

    myString=replace(myString,myAliases(i).IP_or_Subnet,myAliases(i).Name)
  next

  ReplaceAliases=myString
End Function


'###############################
'# Force run using cscript
'# From http://stackoverflow.com/questions/4692542/force-a-vbs-to-run-using-cscript-instead-of-wscript
'###############################
Sub forceCScriptExecution
    Dim Arg, Str
    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 _
            "cscript //nologo """ & _
            WScript.ScriptFullName & _
            """ " & Str
        WScript.Quit
    End If
End Sub

 

How the Script works

This script is simply a wrapper for netsh. It launches the command,

netsh firewall show config verbose=ENABLE

and grabs the output courtesy of the StdOut.AtEndOfStream property. This output is then parsed to figure out where each section in the netsh output begins.The sections we are interested in are those that detail the service, program and port firewall execeptions (for both the standard and domain profiles).

Once we have identified where each of these sections lies in the output string, we can then use regular expression matching (tuned to each section) to extract the rule properies we need so that we can output them in a more standardised way. We omit at this stage the ICMP and logfile config sections, and for the scope entries perform a search and replace so humanise the output. 

 

Adding IP and Subnet Aliases to the Script

We keep this script on a central file server, and within it we keep updated our favorite server IPs and site subnets. These IPs and subnets are automatically replaced in the scope output of netsh to provide a more human-readable output.

To add these, just locate the SetAlias call in vbscript which has the following single entry 

SetAlias "*","ALL"

Now expand this to cater for your environment. So, for example you might end up with something like,

SetAlias "10.0.0.0/255.255.255.0","[PRIVATE_1]"
SetAlias "10.0.1.0/255.255.255.0","[PRIVATE_2]"
SetAlias "10.0.0.1/255.255.255.255","SITE-SERVER1"
SetAlias "10.0.1.1/255.255.255.255","SITE-SERVER2"
SetAlias "10.0.0.2/255.255.255.255","TEST-SMP76"

 

When you next run the script, any scopes which match the above definititions will automagically be replaced.

 

Further Work

I have been tempted to split out the aliasing piece in this script so that the aliases could be defined separately in another comma delimeted pair text file. That way, the script would not need to be edited directly to taylor it to new environments.

One idea was to automatically create this alias file by exporting our server IP and subnet details from our Altiris SMP directly. It would be neat, but likely a bit more trouble than it's worth.... ;-)

 

 

 

 

 

 

 

Statistics
0 Favorited
1 Views
1 Files
0 Shares
1 Downloads
Attachment(s)
zip file
firewall_audit_v1.0.zip   3 KB   1 version
Uploaded - Feb 25, 2020

Tags and Keywords

Related Entries and Links

No Related Resource entered.