I saw this in someone’s signature on one of the Technet forums and though it was a really clever idea; nobody wants to put their email address in plaintext on the internet because it’ll just get hoovered up by spammers and most common obfuscation techniques are easily worked around by spambots, so how about…

[string](0..21|%{[char][int](23+("74778682874174878091987477868287237688239484").substring(($_*2),2))})-replace " "

Clever, isn’t it.

You can generate your own with the following code:

$out = $null
$email = "user@example.com"
foreach($char in $($email.tochararray())){$out += @([System.Convert]::ToUInt32($char)-23)}
$string = [string]$out -replace " "
"Your code is: [string](0..$($($out.length)-1)|%{[char][int](23+(`"$string`").substring((`$_*2),2))})-replace `" `""

Which should give an output that looks something like:

[string](0..15|%{[char][int](23+("94927891417897748689857823768886").substring(($_*2),2))})-replace " "

Email addresses with certain symbol characters may not encode properly.



Bit of an esoteric one this, but might be helpful to someone anyway. If you’ve worked with Out-Gridview before you may have also made use of the NoteProperty property to collate your results prior to output; today I ran into an issue where I knew some of my output objects had 2 NoteProperties and some had 2+x NoteProperties where x was more than 0. When output to Out-Gridview or Export-CSV it was only showing the number of properties that the first object had, irrespective of how many subsequent objects had.

For example:

User Property1 Property2 Property3
Dave 43
Steve 25 62 23
John 23 263

In this scenario, all results would be output with just the User & Property1 NoteProperty displayed because Dave’s Property2 and Property3 don’t exist. You could work around this by sorting on “Property 3” so that it’s the first object in the output and thus includes all columns like so:

User Property1 Property2 Property3
Steve 25 62 23
Dave 43
John 23 263

However, this obviously only works if you know how many properties there are and I didn’t. So, I had to find a further workaround and came up with this:

$count = @()
foreach($output in $useroutput){$count += $(($output | gm).count)-5}
$max = $($count | measure -maximum).maximum
$useroutput | Sort-Object "Property$max" | Out-GridView

This iterates through your array of objects ($useroutput), counts the properties on each (minus the 4 base methods on a System.Object and the known “User” property) and then finds the highest count and sorts the output based on that highest value, ensuring that it always outputs every column even when it’s empty for most of the results.

Obviously, even this method is only useful if your properties are titled numerically, if you’re just pulling an unknown list of properties with arbitrary names, you’re probably going to have to dig a bit deeper.



You know how it is, you don’t pay attention to the management of your domain for just 5 or 6 years and suddenly you have hundreds of GPOs with no idea what half of them do or even if they’re actually linked somewhere. For some reason, the Powershell GPO module doesn’t have a simple cmdlet or property that lets you tell if a GPO is linked or not, because that would be far too helpful, but it’s not too hard to do if you don’t mind parsing some XML.

This code is based on a much more complicated script from here, designed to let you search for individual settings within a GPO. It will accept a number of arguments, but run without any it will simply output to the console a list of all of the unlinked GPOs in the current domain.

<#
Copyright (c) 2014, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Find Unlinked Linked GPOs in a domain
#Adam Beardwood 20/05/2014
#v1.0 - Initial Release
 
param (
[Parameter(Mandatory=$false)]
[boolean] $outfile=$false,
[Parameter(Mandatory=$false)]
[string] $filename="UnlinkedGPO-$(get-date -f HHmmss).txt",
[Parameter(Mandatory=$false)]  
[string] $DomainName = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
)
 
Import-Module GroupPolicy;
 
[string] $Extension="Enabled"
 
$allGposInDomain = Get-GPO -All -Domain $DomainName | Sort DisplayName;
 
$xmlnsGpSettings = "http://www.microsoft.com/GroupPolicy/Settings";
$xmlnsSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance";
$xmlnsSchema = "http://www.w3.org/2001/XMLSchema";
 
$QueryString = "gp:LinksTo";
 
$host.UI.WriteLine();
 
foreach ($Gpo in $allGposInDomain)
{				
	$xmlDoc = [xml] (Get-GPOReport -Guid $Gpo.Id -ReportType xml -Domain $Gpo.DomainName);		
	$xmlNameSpaceMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable);
 
	$xmlNameSpaceMgr.AddNamespace("", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("gp", $xmlnsGpSettings);
	$xmlNameSpaceMgr.AddNamespace("xsi", $xmlnsSchemaInstance);
	$xmlNameSpaceMgr.AddNamespace("xsd", $xmlnsSchema);
 
	$extensionNodes = $xmlDoc.DocumentElement.SelectNodes($QueryString, $XmlNameSpaceMgr);
 
	$stringToPrint = $($Gpo.DisplayName) + " is not linked in this domain";
 
	if($extensionNodes[0] -eq $null){
		if($outfile -eq $true){
			$stringToPrint | Out-File $filename -Append
		}else{
			write-host $stringToPrint -foregroundcolor red
		}
	}
}


In light of today’s news that OpenSSL has been pretty broken for the best part of a year (see: here, amongst others), I decided to revisit the SSL configuration on my Forefront TMG servers with regard to their published websites. Digging around the internet I came across several useful pieces of information that I thought I’d share.

First up is Qualys’ SSL Server Test tool @ https://www.ssllabs.com/ssltest/index.html, which will give you a report on the state of your SSL setup, mine had some issues mostly due to oversights on my part.

Next, is this article on ISAServer.org which runs you through some non-obvious-but-crucial security tweaks you can make to your TMG server to improve its protocol support and avoid a couple of renegotiation issues.

Finally, I was directed to the IIS Crypto tool from Nartac Software, which doesn’t do anything you couldn’t do by hand (and does go back over a couple of protocol tweaks from the previous link), but does make it one hell of a lot easier. Don’t be fooled by the name, the windows crypto settings it changes are server-wide and don’t just affect IIS.

So, with a couple of hours of work I went from an F (Oops, that’ll teach me to forget about disabling SSLv2) to an A and now properly implement PFS for clients that support it.



Update: Please note that the up-to-date version of this blocklist is maintained here, this post will not be updated with any future changes to the Nobis ranges.

Just when you think you’ve finally blocked all of the Nobis Technology Group’s (aka Ubiquity Server Solutions) IP ranges they go and take over another range and carry on merrily spamming your comments section.

So, I’ve taken the nuclear option and blacklisted every IP range associated with AS15003 (which is their AS) as per the very helpful http://bgp.he.net/AS15003#_prefixes – yes, they really do have 867 IPv4 netblocks allocated to them.

I’ve collapsed the ranges down to a slightly more manageable 119 (there are a lot of contiguous blocks in there) and added it to my blocklist page, it’s also reproduced below, so help yourselves.

#Nobis/Ubiquity Ranges - http://bgp.he.net/AS15003#_prefixes
Deny from 23.19.0.0/16
Deny from 23.80.0.0/16
Deny from 23.81.0.0/17
Deny from 23.81.128.0/18
Deny from 23.81.192.0/20
Deny from 23.81.216.0/21
Deny from 23.81.224.0/19
Deny from 23.82.0.0/16
Deny from 23.83.0.0/17
Deny from 23.83.128.0/18
Deny from 23.83.192.0/20
Deny from 23.104.0.0/14
Deny from 23.108.0.0/15
Deny from 23.110.0.0/16
Deny from 23.111.251.0/24
Deny from 23.224.0.0/15
Deny from 23.226.48.0/20
Deny from 23.235.128.0/18
Deny from 23.248.192.0/18
Deny from 64.120.1.0/24
Deny from 64.120.2.0/23
Deny from 64.120.4.0/22
Deny from 64.120.8.0/21
Deny from 64.120.16.0/20
Deny from 64.120.32.0/19
Deny from 64.120.64.0/19
Deny from 64.120.96.0/20
Deny from 64.120.112.0/21
Deny from 64.120.122.0/23
Deny from 64.120.124.0/22
Deny from 67.201.0.0/21
Deny from 67.201.48.0/23
Deny from 69.31.107.0/24
Deny from 69.147.224.0/23
Deny from 69.147.227.0/24
Deny from 69.147.228.0/22
Deny from 69.147.232.0/21
Deny from 69.147.240.0/22
Deny from 69.147.244.0/24
Deny from 69.147.246.0/23
Deny from 69.147.248.0/21
Deny from 69.174.60.0/22
Deny from 70.32.32.0/20
Deny from 72.37.204.0/24
Deny from 72.37.221.0/24
Deny from 72.37.222.0/23
Deny from 72.37.224.0/21
Deny from 72.37.237.0/24
Deny from 72.37.242.0/23
Deny from 72.37.246.0/23
Deny from 74.113.144.0/22
Deny from 108.62.0.0/16
Deny from 108.171.33.0/24
Deny from 108.171.34.0/23
Deny from 108.171.36.0/23
Deny from 108.171.38.0/24
Deny from 108.171.43.0/24
Deny from 108.171.44.0/22
Deny from 108.171.53.0/24
Deny from 108.171.54.0/23
Deny from 108.171.56.0/24
Deny from 108.171.59.0/24
Deny from 108.171.60.0/23
Deny from 108.171.63.0/24
Deny from 108.177.128.0/17
Deny from 108.187.0.0/16
Deny from 142.91.0.0/16
Deny from 142.234.0.0/16
Deny from 147.255.0.0/16
Deny from 162.209.128.0/17
Deny from 162.222.68.0/22
Deny from 172.240.0.0/15
Deny from 172.247.0.0/16
Deny from 172.255.0.0/16
Deny from 173.208.0.0/18
Deny from 173.208.64.0/19
Deny from 173.208.96.0/21
Deny from 173.208.104.0/24
Deny from 173.208.106.0/23
Deny from 173.208.108.0/22
Deny from 173.208.112.0/20
Deny from 173.234.0.0/18
Deny from 173.234.64.0/20
Deny from 173.234.80.0/21
Deny from 173.234.88.0/23
Deny from 173.234.90.0/24
Deny from 173.234.92.0/22
Deny from 173.234.96.0/21
Deny from 173.234.105.0/24
Deny from 173.234.109.0/24
Deny from 173.234.110.0/23
Deny from 173.234.112.0/20
Deny from 173.234.128.0/19
Deny from 173.234.160.0/20
Deny from 173.234.176.0/21
Deny from 173.234.184.0/22
Deny from 173.234.188.0/24
Deny from 173.234.190.0/23
Deny from 173.234.192.0/18
Deny from 174.34.128.0/19
Deny from 174.34.160.0/20
Deny from 174.34.176.0/21
Deny from 174.34.184.0/22
Deny from 174.34.188.0/23
Deny from 174.34.190.0/24
Deny from 192.151.224.0/20
Deny from 192.151.248.0/21
Deny from 192.161.80.0/20
Deny from 192.163.160.0/19
Deny from 192.229.64.0/18
Deny from 192.238.128.0/17
Deny from 192.253.240.0/21
Deny from 196.45.112.0/22
Deny from 198.48.96.0/20
Deny from 198.48.112.0/22
Deny from 216.6.224.0/20
Deny from 216.151.31.0/24
Deny from 216.152.224.0/20
Deny from 216.193.237.0/24
#End of Nobis/Ubiquity Ranges

No doubt they’ll buy up more IP space as needed so they can continue to sell their services to spammers, malware distributors and other disgusting excuses for human beings.



Firstly, download the RSAT tools for Windows 7 or Windows 8 of the appropriate bittiness.

Then install them using: wusa <RSAT Installer Filename>.msu /quiet

You can then enable the Powershell AD cmdlets (or indeed any other RSAT components) from the command line using dism.exe thus (careful, the featurenames are case-sensitive):

dism /online /enable-feature /featurename:RemoteServerAdministrationTools /featurename:RemoteServerAdministrationTools-Roles /featurename:RemoteServerAdministrationTools-Roles-AD /featurename:RemoteServerAdministrationTools-Roles-AD-Powershell

For some reason it won’t install “dependent” parent components unless you explicitly tell it to install them, which is a pain but workaroundable.



Update: with appropriate irony I managed to bollocks up the formatting myself. Sorry, should be fixed now.

Courtesy of: http://www.orcsweb.com/blog/james/powershell-ing-on-windows-server-how-to-import-certificates-using-powershell/

However, the formatting is a bit borked so I’ve reproduced it here.

$certrootstore can be either LocalMachine or CurrentUser
$certstore can be any of: AddressBook, AuthRoot, CA, Disallowed, My, Root, TrustedPeople, TrustedPublisher

The script assumes the certs are in the same location as the script, if not you’ll need to modify the paths as well as the filenames.

function Import-Certificate{
     param([String]$certPath,[String]$certRootStore,[String]$certStore)
     $pfx=new-object System.Security.Cryptography.X509Certificates.X509Certificate2
     $pfx.import($certPath)
     $store= new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)
     $store.open("MaxAllowed")
     $store.add($pfx)
     $store.close()
}
 
Import-Certificate "$(Split-Path $MyInvocation.MyCommand.Path)\TrustedRoot.cer" "LocalMachine" "root"
 
Import-Certificate "$(Split-Path $MyInvocation.MyCommand.Path)\TrustedIssuingAuthorty.cer" "LocalMachine" "CA"


This script will leverage the Veeam Powershell modules in conjunction with Nagios’ check_nrpe plugin (or similar passive checking plugin) to check the status of Veeam backup jobs. You will probably need to have run the Install-VeeamToolkit.ps1 script in “C:\Program Files\Veeam\Backup and Replication” on any server you’re running these checks on otherwise it will be unable to load the Veeam snapin, unless you have Powershell 3 installed and have your fingers crossed.

You will also need to ensure that your agent allows NRPE arguments and don’t strip special characters (it needs to keep the “” quotes around the job name).

If your Veeam SQL database is off-box then your agent service will need to be running under an account that has access to the DB on the remote SQL server otherwise the lookups will fail.

Check command syntax for NSClient++ is something like the below, other agents may differ:
check_veeam = cmd /c echo scripts\check_veeam.ps1 "$ARG1$"; exit($lastexitcode) | powershell.exe -noninteractive -noprofile -command -

Check command syntax for the Nagios commands.cfg is something like the below:
$USER1$/check_nrpe -H $HOSTADDRESS$ -c check_veeam -a $ARG1$

<#
Copyright (c) 2013, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Check Veeam Backups
#Adam Beardwood 10/07/2013
#Based on a script by by Tytus Kurek
#v1.0 - Initial Release
 
$error.clear()
 
$snaps = Get-PSSnapin
foreach($snap in $snaps){if($snap.name -eq "VeeamPSSnapin"){$exflag = 1}}
if($exflag -ne 1){
	Add-PSSnapin -name VeeamPSSnapin -erroraction silentlycontinue
	if($error[0] -ne $null){write-host "CRITICAL - Could not load Veeam snapin";exit 2}
}
 
$name = $args[0]
 
if($args[0] -eq $null){write-host "CRITICAL - You must provide a job name.";exit 2}
 
$job = Get-VBRJob -Name $name
$name = $job.name
 
if ($job -eq $null){
	Write-Host "UNKNOWN - Could Not Find Job: $name."
	exit 3
}
 
$status = $job.GetLastResult()
$time = $($job.findlastsession()).EndTime
if($($job.findlastsession()).State -eq "Working"){
	Write-Host "OK - Job: $name is currently in progress."
	exit 0
}elseif($status -eq "Failed"){
	Write-Host "CRITICAL - Job: $name failed at $time."
	exit 2
}elseif ($status -ne "Success"){
	Write-Host "WARNING - Job: $name completed with warnings at $time."
	exit 1
}else{
	Write-Host "OK - Job: $name completed successfully at $time."
	exit 0
}
#End

Hope you find it helpful.



Sometimes you have badly written apps that need to run interactively on servers, which means you have to connect to the console session to manage them over RDP. If you need to be able to leave the console session unlocked it causes issues as disconnecting an RDP will lock it by default.

This can be changed by running the following command from within the RDP session:
tscon 0 /dest:console

Where “0” is the ID of the session you’re connected to – on 2K3 boxes this will be 0 if you’re connected to the console, but on 2K8+ it will vary depending on how many other users are connected. You can find out the session ID by running query session.



Along with their anti-spam Block Lists, Spamhaus also provide a DROP list, which is a list of stolen ‘hijacked’ netblocks and netblocks controlled entirely by criminals and professional spammers. As nothing good can come out of these ranges, it’s handy to block them outright at the firewall level, if that firewall happens to be TMG or ISA you can make use of the following script to keep the DROP list up-to-date.

The IP address conversion functions in this script are all courtesy of Chris Dent of www.indented.co.uk and were a godsend. To make use of this script you will need to create your Network object in ISA/TMG and update the network name in the following code to match $ipranges = $server.NetworkConfiguration.Networks.item("Spamhaus DROP List").IpRangeSet. You will also need to set $dropfile to match the location where you will be storing the downloaded DROP List, this needs to be retained so that updates can easily do a diff on the file without having to query ISA/TMG, which is much slower. Create a blank text file matching this filename & path before running the script for the first time.

<#
Copyright (c) 2013, Adam Beardwood
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 
 
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#>
 
#Grab Spamhaus DROP List and update TMG network sets based on changes
#IP Conversion Functions Courtesy of Chris Dent - http://www.indented.co.uk/index.php/2010/01/23/powershell-subnet-math/
#Adam Beardwood 26/03/2013
#v1.0 - Initial Release
#v1.1 - Considerable improvements to error checking
 
Function ConvertTo-DottedDecimalIP {
  <#
    .Synopsis
      Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
    .Description
      ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
    .Parameter IPAddress
      A string representation of an IP address from either UInt32 or dotted binary.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [String]$IPAddress
  )
 
  Process {
    Switch -RegEx ($IPAddress) {
      "([01]{8}\.){3}[01]{8}" {
        Return [String]::Join('.', $( $IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) } ))
      }
      "\d" {
        $IPAddress = [UInt32]$IPAddress
        $DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
          $Remainder = $IPAddress % [Math]::Pow(256, $i)
          ($IPAddress - $Remainder) / [Math]::Pow(256, $i)
          $IPAddress = $Remainder
         } )
 
        Return [String]::Join('.', $DottedIP)
      }
      default {
        Write-Error "Cannot convert this format"
      }
    }
  }
}
 
Function ConvertTo-DecimalIP {
  <#
    .Synopsis
      Converts a Decimal IP address into a 32-bit unsigned integer.
    .Description
      ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
    .Parameter IPAddress
      An IP Address to convert.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress
  )
 
  Process {
    $i = 3; $DecimalIP = 0;
    $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
 
    Return [UInt32]$DecimalIP
  }
}
 
Function Get-BroadcastAddress {
  <#
    .Synopsis
      Takes an IP address and subnet mask then calculates the broadcast address for the range.
    .Description
      Get-BroadcastAddress returns the broadcast address for a subnet by performing a bitwise AND 
      operation against the decimal forms of the IP address and inverted subnet mask. 
      Get-BroadcastAddress expects both the IP address and subnet mask in dotted decimal format.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter SubnetMask
      The subnet mask for the network.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress, 
 
    [Parameter(Mandatory = $True, Position = 1)]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask
  )
 
  Process {
    Return ConvertTo-DottedDecimalIP $((ConvertTo-DecimalIP $IPAddress) -BOr `
      ((-BNot (ConvertTo-DecimalIP $SubnetMask)) -BAnd [UInt32]::MaxValue))
  }
}
 
#Create ISA COM Objects
$root = new-object -comObject "FPC.Root" -strict
#Get our array (well, there is only one in this case)
$server = $root.Arrays | Select-Object -first 1
 
#Get IPRangeSet for the Spamhaus DROP List network
$ipranges = $server.NetworkConfiguration.Networks.item("Spamhaus DROP List").IpRangeSet
$dropfile = "c:\users\adam\desktop\drop.txt"
 
#If local copy of DROP file is missing, remove all addresses and start clean
if($(test-path $dropfile) -eq $false){
 
	$ipranges.removeall()
 
	#Download DROP List from webserver
	$source = "http://www.spamhaus.org/drop/drop.txt"
	$destination = $dropfile
	$wc = New-Object System.Net.WebClient
	$wc.DownloadFile($source, $destination)
 
	$drop = ""
	$difference = Compare-Object $drop $(gc $dropfile)
 
}else{
 
	$drop = gc $dropfile
	if($drop -eq $null){$drop = ";"}
 
	#Download DROP List from webserver
	$source = "http://www.spamhaus.org/drop/drop.txt"
	$destination = $dropfile
	$wc = New-Object System.Net.WebClient
	$wc.DownloadFile($source, $destination)
 
	$difference = Compare-Object $drop $(gc $dropfile)
}
 
if($difference -ne $null){
 
	$difference
 
	foreach($diff in $difference){
		if ($diff.inputobject.startswith(";")){
		}else{
			$ip = $($diff.inputobject.split(" ; ")[0]).split("/")[0]
			$cidr = $($diff.inputobject.split(" ; ")[0]).split("/")[1]
			$mask = ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(("1" * $cidr).PadRight(32, "0")), 2))
			$bcast = Get-BroadcastAddress $ip $mask
 
			Switch ($diff.SideIndicator){
				"=>" {if(!$ipranges.isipinset($ip)){$ipranges.add($ip,$bcast)}else{write-host "IP Address Range $ip - $bcast already in DROP List" -fore red}} #Not in local
				"<=" {if($ipranges.isipinset($ip)){$ipranges.remove($ip,$bcast)}else{write-host "IP Address Range $ip - $bcast not present in Network Object" -fore yellow}} #Not in remote
			}
		}
	}
 
}else{
	write-host "No Changes Since Last Update" -fore green
	}
 
$ipranges.save()
 
write-host "Update Complete" -fore green