Tag Archives: Scripts

Import & Update Spamhaus DROP List in ISA/TMG

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.
 
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies, 
either expressed or implied, of the FreeBSD Project.
#>
 
#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
$banned = $server.NetworkConfiguration.Networks.item("Banned Hosts").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)}elseif($banned.isipinset($ip)){write-host "IP Address Range $ip - $bcast already in Banned Hosts" -fore red}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

Perl IP Address Range Merger

If like me you maintain a number of IP address blacklists for various purposes (websites, firewalls, etc) then you probably find yourself thinking, from time to time, I wonder how many of these ranges overlap and could be merged down to clean these lists up a bit. I was hoping that there would be a nice, easy way to do this with Powershell but it doesn’t seem like there is.

Turns out there are some nice Perl libraries to help with this process in the form of Net::CIDR and its children. While my Perl is pretty dreadful, the following script will nonetheless take a collection of xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx style IP ranges, merge them down and output them to a file in the same format. You can also easily modify it to convert to/from CIDR-formatted address ranges, single addresses or any combination of the three; the full documentation for the Net::CIDR::Lite module is available on CPAN.

Obviously it requires Net::CIDR::Lite to be present on your system.

#!/usr/bin/perl -w
 
use Net::CIDR::Lite;
 
open(MYFILE, '>>data.txt');
 
my $cidr = Net::CIDR::Lite->new();
 
while (<DATA>) {
	chomp;
	$cidr->add_range($_);
}
 
for $r ( $cidr->list_range() ) {
	print MYFILE "$r\n";
}
 
close (MYFILE);
 
__DATA__
1.0.1.0 - 1.0.3.255
1.0.1.0 - 1.0.2.255
1.0.2.0 - 1.0.3.255

FMI: WMI Error 10 in Application Log

Courtesy of http://support.microsoft.com/default.aspx?scid=kb;en-US;2545227

This script fixes the issue with Windows 7 & 2008 R2 whereby you get a WMI error 10 logged in the application log on startup that says:

Event filter with query “SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA “Win32_Processor” AND TargetInstance.LoadPercentage > 99″ could not be reactivated in namespace “//./root/CIMV2″ because of error 0×80041003. Events cannot be delivered through this filter until the problem is corrected.

strComputer = "."
 
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\subscription")
Set obj1 = objWMIService.ExecQuery("select * from __eventfilter where name='BVTFilter' and query='SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA ""Win32_Processor"" AND TargetInstance.LoadPercentage > 99'")
 
For Each obj1elem in obj1
	set obj2set = obj1elem.Associators_("__FilterToConsumerBinding")
	set obj3set = obj1elem.References_("__FilterToConsumerBinding")
 
	For each obj2 in obj2set
		WScript.echo "Deleting the object"
		WScript.echo obj2.GetObjectText_
		obj2.Delete_
	next
 
	For each obj3 in obj3set
		WScript.echo "Deleting the object"
		WScript.echo obj3.GetObjectText_
		obj3.Delete_
	next
 
	WScript.echo "Deleting the object"
	WScript.echo obj1elem.GetObjectText_
	obj1elem.Delete_
Next

Identifying Static DNS Configuration

It’s very easy to use something like Win32_NetworkAdapterConfiguration to get information from a remote machine as to whether or not it’s using DHCP on an interface, but it’s much harder to establish whether or not a client is using statically assigned DNS servers, but a DHCP assigned IP address.

This is the best way I’ve found to do it so far, but please let me know if you’re aware of a better one. Note that this function will return True if any interface that’s using DHCP has statically assigned DNS servers; if you want to get more specific you’ll have to start doing some interface detection and that’s a pain given the way they’re stored in the registry.

function checkstatic ($computer) {
	$rootkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
	$key = $rootkey.OpenSubKey("System\CurrentControlSet\Services\TCPIP\Parameters\Interfaces")
	$subkeys = $key.getsubkeynames()
	foreach($subkey in $subkeys){
		$sub = $key.opensubkey($subkey, $true)
		$dhcp = $sub.getvalue("EnableDHCP")
		if($dhcp -eq 1){
			$nameserver = $sub.getvalue("Nameserver")
			if($nameserver -ne ""){
				return $true
			}
		}
	}
}

Recreating SharePoint 2007 SSP Jobs

So, if you have a SharePoint 2007 Shared Services Provider and are syncing user profiles with your AD then you might do something stupid like accidentally deleting the TimerJobs that do the profile sync and clean up and what have you.

So now, you’re getting An error has occurred while accessing the SQL Server database or the Office SharePoint Server Search service. errors in your SSP admin and your profiles aren’t syncing and it’s all something of a mess.

Now, it’s already documented in a couple of places how to fix the Full & Incremental profile import jobs, notably here: http://www.geekinthepink.info/2009/12/sharepoint-user-profiles-and-properties.html but what if you’ve been really dumb and deleted the other jobs too? Well I’ve included the required SQL below to recreate all 6 of the core jobs, but it does require a little work on your part; you need to look up the GUIDs from the MIPObjects table in the main SSP Database (e.g. SharedServices_DB), which is the same database that you need to run this script against. In it, you will hopefully find a record which has an XML data set that probably starts with something like:

<object><field name="canonicalMySitePortalUrl" type="string">...

If you copy the XML data set out, you will find it contains a number of “field” XML nodes that include GUIDs. Find the nodes that correspond to the name of the jobs i.e:

<field name="profileChangeJobId" type="guid">f4e31424-0361-45f2-98d6-0150b74e6345</field>
<field name="profileChangeCleanupJobId" type="guid">becd5baf-ca7e-4c1b-9c1a-0db941337b55</field>
<field name="profileFullImportJobId" type="guid">5c6d0eb4-1880-44e6-9b0a-6704f5393fde</field>
<field name="profileIncrementalImportJobId" type="guid">3128d33d-23e5-4d85-af00-26f90f760d0a</field>
<field name="DLImportJobId" type="guid">77b77902-4fb5-48b4-a709-6f0f8d167a1b</field>
<field name="audienceCompilationJobId" type="guid">7a1dcf14-fe06-40ab-82c7-d8f7e72f5fdf</field>

And insert the GUID values into the SQL below for each appropriate job, replacing the xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx placeholders. Do not just use the ones in this example, they won’t work as they’re unique to each SharePoint instance

You can also obviously change the schedules and the NextDueTime values to suit your own environment.

And next time, take backups ;)

INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.UserProfiles.UserProfileChangeJob',
'hourly between 0 and 0',
'',
'2012-10-31 15:00:00.000',
0,
'User Profile Change Job'
)
 
INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.UserProfiles.UserProfileChangeCleanupJob',
'daily between 21:00:00 and 21:00:00',
'',
'2012-10-31 15:00:00.000',
0,
'User Profile Change Cleanup Job'
)
 
INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.UserProfiles.DLImportJob',
'every 5 minutes between 0 and 0',
'',
'2012-10-31 15:00:00.000',
0,
'Distribution List Import Job'
)
 
INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.Audience.AudienceCompilationJob',
'daily between 01:00:00 and 01:00:00',
'NULL',
'2012-10-31 15:00:00.000',
0,
'Audience Compilation Job'
)
 
INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.UserProfiles.UserProfileImportJob',
'weekly between fri 15:00:00 and fri 15:00:00',
'IsIncremental#False',
'2012-10-31 15:00:00.000',
1,
'User Profile Full Import Job'
)
 
INSERT INTO MIPScheduledJob (JobId, Assembly, Class, Recurrence, JobData, NextDueTime, Disabled, DisplayName)
VALUES (
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx',
'Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
'Microsoft.Office.Server.UserProfiles.UserProfileImportJob',
'daily between 12:00:00 and 12:00:00',
'IsIncremental#True',
'2012-10-31 15:00:00.000',
1,
'User Profile Incremental Import Job'
)

DHCP-Multitool

The following script is an amalgamation of several tools I’ve built up over the last couple of years to work with multiple DHCP scopes on servers. It has 6 basic modes of operation, as detailed below, which allow you to list all scopes on a server, enable all scopes on a server, disable all scopes on a server, change the lease time for all scopes on a server, change the offer delay for all scopes on a server or add reservations to one or more servers. You can use help .\DHCP-Tool.ps1 to get more information on the various switches, though it’s worth pointing out that leasetimes are in seconds while delaytimes are in milliseconds.

Hopefully the new Server 2012 powershell modules for DHCP solve a lot of these problems, but I haven’t had a chance to dig into them yet.

.\DHCP-Tool.ps1 -Server <String> [-show]
.\DHCP-Tool.ps1 -Server <String> [-enable]
.\DHCP-Tool.ps1 -Server <String> [-disable]
.\DHCP-Tool.ps1 -Server <String> [-lease] -leasetime <String>
.\DHCP-Tool.ps1 -Server <String> [-delay] -delaytime <Int32>
.\DHCP-Tool.ps1 -Server <String> [-reserve] -ipscope <String> -ipaddr <String> -mac <String> [-altservers <String>] [-name <String>]

################################################################################################
#This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    #
#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT      #
#WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS PROVIDE THE SOFTWARE "AS IS" WITHOUT   #
#WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE         #
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK   #
#AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE      #
#DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT  #
#UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, BE       #
#LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES#
#ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS    #
#OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A    #
#FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER HAS BEEN     #
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.                                                   #
################################################################################################
 
<#
.SYNOPSIS
    DHCP Multitool
.DESCRIPTION
    Provides a variety of functions for making changes to multiple DHCP scopes and/or multiple DHCP servers
.PARAMETER Server
    The hostname or IP address of the DHCP server to work with
.PARAMETER Show
    Action: List current scope configuration
.PARAMETER Enable
	Action: Enable all scopes on server
.PARAMETER Disable
	Action: Disable all scopes on server
.PARAMETER Lease
	Action: Alters lease time on all scopes on server
.PARAMETER LeaseTime
	DHCP Lease time in seconds
.PARAMETER Delay
	Action: Alters offer delay on all scopes on server
.PARAMETER DelayTime
	Offer Delay time in miliseconds
.PARAMETER Reserve
	Action: Add reservation to server
.PARAMETER AltServer
	Additional servers to create reservation on
.PARAMETER IPScope
	Name of scope to work with
.PARAMETER IPAddr
	IP Address to reserve
.PARAMETER MAC
	MAC Address to associated reservation with
.PARAMETER Name
	Comment to add to reservation
.EXAMPLE
    C:\>dhcp-tool.ps1 -server 127.0.0.1 -enable
	Enables all scopes on server 127.0.0.1
.EXAMPLE
    C:\>dhcp-tool.ps1 -server 127.0.0.1 -delay -delaytime 1000
	Sets an offer delay of 1000ms on all scopes on server 127.0.0.1
.NOTES
    Author: Adam Beardwood
    Date: October 12, 2012
	v1.0: Initial Release
	v1.1: Added Better Error Handling and Command Feedback
#>
 
[CmdletBinding(DefaultParametersetName="showDHCP")]
Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)][String]$Server,
 
		[Parameter(ParameterSetName='showDHCP')][switch]$show,
 
		[Parameter(ParameterSetName='enableDHCP')][switch]$enable,
 
		[Parameter(ParameterSetName='disableDHCP')][switch]$disable,
 
		[Parameter(ParameterSetName='leaseDHCP')][switch]$lease,
		[Parameter(ParameterSetName='leaseDHCP',Mandatory=$true,ValueFromPipeline=$true)][string]$leasetime,
 
		[Parameter(ParameterSetName='delayDHCP')][switch]$delay,
		[Parameter(ParameterSetName='delayDHCP',Mandatory=$true,ValueFromPipeline=$true)][ValidateRange(0,1000)][int]$delaytime,
 
		[Parameter(ParameterSetName='reserveDHCP')][switch]$reserve,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$ipscope,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$ipaddr,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$true)][string]$mac,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$false)][string]$altservers = $null,
		[Parameter(ParameterSetName='reserveDHCP',Mandatory=$false)][string]$name
)
 
function getDHCPScope($servername, $type) {
 
	$ips = @()
 
	$scopes = netsh dhcp server $servername show scope
 
	if($type -eq "show"){
		foreach($scope in $scopes){
			write-host $scope -foregroundcolor green
		}
	}else{
 
		$regex = [regex]"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
 
		foreach($scope in $scopes){
 
			$addr = $scope | foreach {$regex.Matches($_) | foreach {$_.Captures[0].Value}}
			if($addr -ne $null){
				$ips += $addr[0]
			}
		}
 
		return $ips
	}
}
 
function enableDHCPScope($servername, $ip){
	netsh dhcp server $servername scope $ip set state 1 | out-null
}
 
function disableDHCPScope($servername, $ip){
	netsh dhcp server $servername scope $ip set state 0 | out-null
}
 
function leaseDHCPScope($servername, $ip, $leasetime){
	netsh dhcp server $servername scope $ip set optionvalue 51 DWORD $leasetime | out-null
	$leaseinfo = netsh dhcp server $servername scope $ip show optionvalue
	foreach($info in $leaseinfo){if($info -match "OptionId : 51"){write-host "Scope $ip lease length: $($($leaseinfo[$([array]::IndexOf($leaseinfo, $info))+4]).split('=')[1]) seconds" -foregroundcolor green}}
}
 
function delayDHCPScope($servername, $ip, $leasetime){
	netsh dhcp server $server scope $ip set delayoffer $delaytime | out-null
	$delayinfo = netsh dhcp server $servername scope $ip show delayoffer
	foreach($info in $delayinfo){if($info -match "DelayOffer"){write-host $info -foregroundcolor green}}
}
 
function reserveDHCPScope($servername, $altservers, $ipscope, $ipaddr, $mac, $name){
 
	$servers = @($servername)
 
	foreach($alt in $altservers){
		if(($alt -ne "") -and ($alt -ne $null)){$servers += "\\$alt"}
	}
 
	write-host "Servers: $servers"
 
	foreach($server in $servers){
 
		$result = netsh dhcp server $server scope $ipscope
		if($result -match "The command needs a valid Scope IP Address"){
			write-host "Scope Does Not Exist!"
			exit 1
		}
 
		$result = netsh dhcp server $server scope $ipscope show clients
		if($result -match $ipaddr){
			write-host "IP Address Already Reserved!"
			exit 1
		}
 
		$regex = [regex]"([0-9A-Fa-f]{2}[:-]*){5}([0-9A-Fa-f]{2})"
		$result = $mac | foreach {$regex.Matches($_) | foreach {$_.Captures[0].Value}}
		if($result -eq $null){
			write-host "Invalid MAC Address!"
			exit 1
		}
 
		netsh dhcp server $server scope $ipscope add reservedip $ipaddr $mac $name
	}
 
}
 
if($(Get-Service -ComputerName $server -Name DHCPServer -ErrorAction SilentlyContinue) -eq $null){
	write-host "The specified server does not appear to be running DHCP"
	exit 1
}
 
$servername = "\\$server"
 
switch ($PsCmdlet.ParameterSetName){
	"showDHCP"{getDHCPScope $servername "show" }
	"enableDHCP"{foreach($ip in $(getDHCPScope $servername)){enableDHCPScope $servername $ip};getDHCPScope $servername "show"}
	"disableDHCP"{foreach($ip in $(getDHCPScope $servername)){disableDHCPScope $servername $ip};getDHCPScope $servername "show"}
	"leaseDHCP"{foreach($ip in $(getDHCPScope $servername)){leaseDHCPScope $servername $ip $leasetime}}
	"delayDHCP"{foreach($ip in $(getDHCPScope $servername)){delayDHCPScope $servername $ip $delaytime}}
	"reserveDHCP"{reserveDHCPScope $servername $altservers $ipscope $ipaddr $mac $name}
}

Getting Last Logon Times For Members of A Group – ADSI Edition

This handy little script will pull all of the users from the specified AD group and then grab the LastLogon time from each specified DC (or you could use [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers to get all of them in the current domain) as well as grabbing the LastLogonTimeStamp for good measure (see my previous post for the caveats on that one). You can also specify which attribute you want to sort the results on; I recommend samaccountname because it’s usually the most useful.

Obviously it’s much quicker and simpler to do this with the ActiveDirectory cmdlets, but sometimes you’re stuck working with a bunch of 2003 DCs and have to make do with ADSI.

################################################################################################
#This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    #
#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT      #
#WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS PROVIDE THE SOFTWARE "AS IS" WITHOUT   #
#WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE         #
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK   #
#AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE      #
#DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT  #
#UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, BE       #
#LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES#
#ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS    #
#OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A    #
#FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER HAS BEEN     #
#ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.                                                   #
################################################################################################
 
#AD Group Last Logon Checker (ADSI Edition)
#Adam Beardwood 20/08/2012
#v1.0 - Initial Release
#v1.5 - Added Datagrid & CSV output
 
$dcs = ("DC1","DC2","DC3")
$group = "CN=Domain Admins,CN=Users,DC=domain,dc=local"
$basedn = "DC=domain,DC=local"
$sorton = "samaccountname"
$useroutput = @()
 
foreach($dc in $dcs){
	$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$dc/$basedn")
	$Search.Filter = "(&(memberof=$group)(objectclass=user))"
	$Search.Sort = New-Object System.DirectoryServices.SortOption($sorton,"Ascending")
	$SearchRes1 = $Search.Findall()
 
	foreach($result in $searchres1){
 
		$user = $result.Properties.samaccountname
		$Search.Filter = "(&(objectclass=user)(samaccountname=$user))"
		$SearchRes2 = $Search.Findall()
 
		New-Variable -Name "$user" -value (New-Object System.Object) -ErrorAction Silentlycontinue
		(Get-Variable "$user").value | Add-Member -type NoteProperty -name Name -value $user[0] -ErrorAction Silentlycontinue
		foreach($adc in $dcs){
			(Get-Variable "$user").value | Add-Member -type NoteProperty -name $adc -value "" -ErrorAction Silentlycontinue
		}
		(Get-Variable "$user").value| Add-Member -type NoteProperty -name LLTS -value "" -ErrorAction Silentlycontinue
 
		if($SearchRes2[0].Properties.lastlogon -ne $null){
			(Get-Variable "$user").value.$dc = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogon)))	
		}
 
		if($SearchRes2[0].Properties.lastlogontimestamp -ne $null){
			(Get-Variable "$user").value.LLTS = ([DateTime]::FromFileTime([Int64]::Parse($SearchRes2[0].Properties.lastlogontimestamp)))
		}
	}
}
 
foreach($result in $searchres1){
	$user = $result.Properties.samaccountname
	$useroutput += (Get-Variable "$user").value
}
 
$useroutput | Sort-Object Name | Out-GridView -Title "Last Logon Times"
#$useroutput | Sort-Object Name | Export-CSV C:\logons.csv

Things I Didn’t Know Until Today – LastLogonTimestamp Edition

So today I discovered that the LastLogonTimestamp attribute of an account in AD (also known as LLTS) is only updated on logon if the old value is more than 14 days in the past. That means the value can only be trusted if it is more than 14 days in the past.

This, as it turns out, is very annoying if you’re trying to do some semi-half-assed auditing. Now, you can use the LastLogon attribute, which is always up to date, but that isn’t replicated between DCs so you have to query all of them.

Note to my future self: Do not rely on LLTS for anything more recent than 14 days.

Disable NTFS Permissions Inheritance

The following is the Powershell equivalent of unticking the box below for the folder “C:\temp”:

NTFS Permissions Inheritance

$acl = get-acl "C:\temp"
$acl.SetAccessRuleProtection($true,$true)
$acl | set-acl

Note that SetAccessRuleProtection takes two boolean arguments; the first turns inheritance on ($False) or off ($True) and the second determines whether the previously inherited permissions are retained ($True) or removed ($False).

Test RDP Connectivity with Powershell

Very simply returns True or False for a given list of hostnames or IP addresses depending on whether or not it can connect to TCP/3389 – a successful connection does not mean that you will be able to login, of course. If you’re running RDP on a non-standard port, you’ll need to adjust the script appropriately.

param(
     [parameter(Mandatory=$true,ValueFromPipeline=$true)][string[]]$computername
     )
 
$results = @()
 
foreach($name in $computername){
 
        $result = "" | select Name,RDP
        $result.name = $name
 
        try{
           $socket = New-Object Net.Sockets.TcpClient($name, 3389)
           if($socket -eq $null){
                 $result.RDP = $false
           }else{
                 $result.RDP = $true
                 $socket.close()
           }
        }
        catch{
                 $result.RDP = $false
        }
        $results += $result
}
 
return $results