Using Powershell to update Windows Firewall rule IP’s

I like to be careful to expose any service to the public Internet. Especially when running a Windows server. On Windows 2012, the firewall can be managed by the “Windows Firewall with Advanced Security” application.

Windows Firewall defaults

The default settings in the Windows Firewall seem strange to me. By default, the server does not answer to ping requests, but has the WMI (remote management) service open to the world. Another questionable defaults are the RPC port that are open. I don’t want to go too deep into the details on how to properly setup the Windows Firewall. But as a rule of thumb I recommend to block all incoming requests, allow all outgoing and then only open up the ports you need for your applications to function. This policy blocks everything by default and forced you to make “allow” rules to open the Windows Firewall for specific applications.

Don’t lock yourself out (important!)

There is one port you always need to keep open: TCP 3389. If you close it you will lose remote (RDP, Terminal Service Client) access to the server. In my case, that would mean I have to go to the data center to connect a screen, keyboard, and mouse.

Updating a Windows firewall rule using PowerShell

Now for the problem and the solution. I am a bit careful with the Windows Firewall rules, since the server has a public IP address. Each application gets its own rule with a corresponding “scope”. The scope determines which IP addresses are allowed to connect. When you have several people that need to maintain the list of IP addresses that are allowed to connect you may want to make a system that administers the entries. This post proposes a system where this list is maintained in a file (with comments) that is updatable by multiple users on the system. Then the “Task Scheduler” will compare the date in the “Description” field of the Windows Firewall rule with the “Last Modified” date of the file. If the file is modified later than the reload, the Task Scheduler will update the Windows Firewall rule (and the date stored in the description field).

Task Scheduler configuration

The system works with four files that I all load from “firewall” directory on the “H:” partition (home directory):

  • firewall.bat: Batch file invoked by the Task Scheduler that calls the PowerShell script
  • firewall.ps1: PowerShell script that actually reconfigures the firewall rule scope
  • firewall.log: Log file that allows you to keep track of proper operation
  • firewall.allow: List of IP addresses and subnets that are allowed to connect

firewall_task_action
The Task Scheduler should be configured to run the “firewall.bat” file in the “Edit Action” screen.

firewall_task_trigger
You could trigger the task every five minutes by setting the following in the “Edit Trigger” screen.

H:\firewall\firewall.bat

powershell -executionpolicy remotesigned -File H:\firewall\firewall.ps1 >> H:\firewall\firewall.log

H:\firewall\firewall.allow

# This is the firewall configuration file. It is scanned every 5 minutes for updates.

# This file may contain comment lines using hash signs (like this one). Other lines
# must consist of the following: IP address or range in CIDR notation

# Localhost
127.0.0.1/8

H:\firewall\firewall.ps1

$name = "World Wide Web Services (HTTP Traffic-In)"
$rule = Get-NetFirewallRule -DisplayName $name
$ruleTime = [datetime]::ParseExact($rule.Description, 'yyyy-MM-dd HH:mm:ss',$null)
$file = 'H:\firewall\firewall.allow'
$fileTime = (Get-Item $file).LastWriteTime
$time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
$tspan = New-TimeSpan $ruleTime $fileTime
$seconds = ($tspan).TotalSeconds
if ($seconds -lt 0) {
  echo "$time Firewall up-to-date, exitting"
  exit
}
echo "$time Loading new firewall rules"
$ips = New-Object Collections.Generic.List[String]
$lines = Get-Content $file
foreach ($line in $lines) {
  if ($line -match "^s*#") {
    continue
  } elseif ($line -match "^s*$") {
    continue
  } elseif ($line -match "^([0-9./]{7,18})s*(#.*)?$") {
    echo $matches[1]
    $ips.Add($line)
  } else {
    echo ("IGNORED: " + $line)
  }
}
# set new firewall rules
try {
  Set-NetFirewallRule -DisplayName $name -RemoteAddress $ips -Description $time
} catch {
  echo "ERROR: Firewall rules could not be loaded"
}

H:\firewall\firewall.log

2014-02-09 13:10:02 Firewall up-to-date, exitting
2014-02-09 13:15:02 Loading new firewall rules
127.0.0.1/8
2014-02-09 13:20:02 Firewall up-to-date, exitting
2014-02-09 13:25:02 Firewall up-to-date, exitting
Share

File name has � (invalid encoding) and CRLF issues

i_dont_understand

On Linux you sometimes you get a “�” in a file name and a trailing “(invalid encoding)” in the filename. This is something that can happen when moving files from Windows to Ubuntu Linux. When uploading files to a Linux box you basically need two Linux tools to “repair” any incompatibility: “convmv” and “dos2unix”. The following commands will install them (on a Debian based Linux):

sudo apt-get install convmv
sudo apt-get install dos2unix

Character encoding

To remove the “(invalid encoding)” you use the “convmv” tool. It is a tool that will convert the character encoding used in the file name. You can try the conversion of file names from different character set to UTF-8 using the following commands:

convmv -r -f windows-1252 -t UTF-8 .
convmv -r -f ISO-8859-1 -t UTF-8 .
convmv -r -f cp-850 -t UTF-8 .

These are the three most popular character encodings (for Western Europe). If you need another character encoding use the “locale -m” command for a full list of options. Check out the Wikipedia character encoding page to find the characteristics of each of them.  After you confirmed that the conversion is correct you can run the actual conversion by adding the “notest” flag. A typical run would look like this (use “-r”  for recursive):

$ convmv -r -f windows-1252 -t UTF-8 .
Your Perl version has fleas #37757 #49830
Starting a dry run without changes...
mv "./jag f�rst�r inte.txt"    "./jag förstår inte.txt"
No changes to your files done. Use --notest to finally rename the files.
$ convmv -r -f windows-1252 -t UTF-8 . --notest
Your Perl version has fleas #37757 #49830
mv "./jag f�rst�r inte.txt"    "./jag förstår inte.txt"
Ready!

Line endings

Different operating systems have different line endings. The line endings are marked by one or two ASCII characters. These are the common styles:

  • CRLF: for the DOS\Windows world
  • CR: for the pre-OSX Mac world
  • LF: for the Unix and Unix-like world (including OSX)

Where the CR and LF characters are defined as such:

  • CR: Carriage Return is ASCII character 13 (0x0D)
  • LF: Line Feed is ASCII character 10 (0x0A)

To detect what line endings a file has you can use “vi” and look for ^M (control-M) characters:

$ vi jag\ förstår\ inte.txt
Do you understand IT?^M
Yes I do!^M
~
~
"jag förstår inte.txt" 2 lines, 34 characters

Or you can use the “file” command:

$ file jag\ förstår\ inte.txt
jag förstår inte.txt: ASCII text, with CRLF line terminators
$ dos2unix jag\ förstår\ inte.txt
dos2unix: converting file jag förstår inte.txt to Unix format ...
$ file jag\ förstår\ inte.txt
jag förstår inte.txt: ASCII text

To do the conversion of line endings from Windows to Linux

$ file jag\ förstår\ inte.txt
jag förstår inte.txt: ASCII text, with CRLF line terminators
$ dos2unix jag\ förstår\ inte.txt
dos2unix: converting file jag förstår inte.txt to Unix format ...
$ file jag\ förstår\ inte.txt
jag förstår inte.txt: ASCII text

Alternatively you can use an editor that supports conversion of line endings. Examples of open source text editors that support conversion of line endings are:

  • “TextMate” on OSX
  • “Notepad++” on Windows
  • “Gedit” on Ubuntu Linux

When I committed files from my OSX laptop to the Git repo, the “git diff” command showed way too many lines (since the line endings were changed). My colleagues showed me how to use the above commands to avoid any problems.

Share