Monday, March 26, 2012

Windows Network Load Balancing (NLB) and ICMP

If you're running Microsoft Network Load Balancing (NLB) you may not know it but all the nodes in your cluster are responding to ICMP request.  You wouldn't know this by pinging the cluster IP from a windows machine because the implementation of ping on windows ingores duplicated ICMP echo responses.  If you ping the cluster IP from a linux machine or OS X machine you'll see that you're receiving duplicate ICMP echo responses.  This isn't normally a big deal but if you're using third party monitoring, such as pingdom.com, they may report an error on their ping tests for your site.

So how does one stop all nodes in the NLB cluster from responding to ICMP?  I didn't think you could until a co-worker of mind stumbled apon the following link. The link doesn't tell you how to change how the cluster responds to ICMP messages but it show's that there is an option to.  There isn't much documentation that I could find on microsoft's site regarding this.  The only other thing I could find is this.

Now that I knew you could change how the nodes in the NLB cluster responded to ICMP messages, I needed to figure how to change the behavor.  Turns out it's actually just a simple registry setting.

To change it so that the NLB cluster will load balance ICMP traffic, all ICMP traffic will be filtered by the cluster and accepted by only a single host perform the following:

Change HKEY_LOCAL_MACHINE\System\CurrentControlSet\services\WLBS\Parameters\Interface\{GUID}\FilterICMP from 0 to 1 on all the nodes in the cluster and then reboot all the nodes.

Where {GUID} is a GUID that refers to the network involved in the NLB cluster you're wanting to change the ICMP filtering functionality on.

Additionally, you can use Windows PowerShell to see if the node is configure to filter ICMP requests or not (make sure you're on a computer that has NLB installed and you open the "Windows PowerShell Modules" so that the NLB commandlets are loaded):

Get-NlbClusterNode test01 | fl *

Outputs:

Cluster                : testcluster.domain.com
Name                   : test01
InterfaceName          : NLB
Host                   : test01.domain.com
State                  : Converged(default)
HostPriority           : 1
AdapterGuid            : {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}
InitialHostState       : Started
PersistSuspendOnReboot : False
MaskSourceMac          : True
FilterIcmp             : 1
GreDescriptorTimeout   : 10

Unfortunately there doesn't appear to be a way to use PowerShell to change the FilterIcmp setting, so you'll have to edit the registry.

Tuesday, March 20, 2012

Password Expiration Email Reminders

Below is a PowerShell script that will email password change reminders to users when their password is about to expire.  It uses built in windows functionality and doesn't require any third party software.  It's fully customizable with the ability to easily set email intervals.

The default email schedule will email users at 30 days, 15 days, 7 days, 5 days, 3 days, 2 days, 1 day and the day of password expiration.  If the user changes their password they will stop receiving email notifications until it becomes time to change their password again.

The script will ignore disabled user accounts and accounts that have the password never expires attribute set.  If the user has an associated email address in active directory it will send the reminder to that email address.  If the user does not have an associated email address an email will be sent to the address specified by the $adminEmail variable.  Thus allowing the admin to be proactive.  The script will also email the $adminEmail if a user's account has expired.  Again, so that the admin can be proactive.  The $adminEmail will receive only one email summary to avoid inbox clutter.  The summary will also show user's that are required to change their passwords but have not done so yet.  There's usually a good reason they're not logging on.  Usually these types of accounts were provision for an employee but that employee never started.

You will need an SMTP server that doesn't require authentication to send out the emails.  You will also need the active directory modules in order to run the script.  Information regarding that can be found here: http://technet.microsoft.com/en-us/library/dd378937(WS.10).aspx

In a nutshell, if you're running the script on a domain controller you should be set.  If you want to run it on a member server then execute the following PowerShell commands:

Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell

You should create a scheduled task to run the script shortly after midnight each night.  Before using it, be sure to set the five configuration variables in the Configurable Settings section at the top of the script.   

Note: The script is just using the default domain password policy but if you're using the AD DS Fine-Grained Password Policies you should be able to modify the script fairly easily.  You'll just have to invoke the Get-ADUserResultantPasswordPolicy commandlet while iterating through the user objects.  Also note that all time is done with GMT and time zones are not taken into account.  This shouldn't be an issue unless your enterprise spans the globe.  If you're enterprise only spans a few contiguous time zones, have the script run at midnight in the time zone that's closest to GMT.

You can download the script here or see below.

CODE:

#Create a nightly scheduled task that runs shortly after midnight and execute this script to generate password expiration email reminders

Import-Module ActiveDirectory

#----- Configurable Settings ------------------------------------------------------------------------------------------------------------------------

# Set the $warningInterval array to change when password expiration email reminders go out.
# The default below will email users at 30 days, 15 days, 7 days, 5 days, 3 days, 2 days, 1 day and the day of.
$warningIntervals = 30,15,7,5,3,2,1,0


# Set the $adminEmail to the email address that will receive password expiration reminders if the user does not have an associated mail address in AD
$adminEmail = 'admin@domain.com'


# Set the $fromEmail to the email address that will be used to send out password expiration reminders
$fromEmail = 'from@domain.com'


# Set the SMTP server used for sending out password expiration reminders, no authentication is used
$smtpServer = "smtp.domain.com"


# If $True, a summary will always be sent to $adminEmail. If $False, a summary will only be sent when there are user accounts that need attention.
$alwaysSendAdminSummary = $False

#----- End Configurable Settings -------------------------------------------------------------------------------------------------------------------


#Constant used to determine if password never expires flag is set
$ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000

#Constant used to determine if user must change password at next login
$REQUIRED_PASSWORD_CHANGE_LASTSET = 0

$CurrentDate = [datetime]::Now.Date
$adminEmailContent = ""

$DefaultDomainPasswordPoliy = Get-ADDefaultDomainPasswordPolicy 
$smtp = new-object Net.Mail.SmtpClient($smtpServer)

function GetDaysToExpire([datetime] $expireDate)
{
    $date =  New-TimeSpan $CurrentDate $expireDate
    return $date.Days
}

function GetPasswordExpireDate($user)
{
    return [datetime]::FromFileTimeUTC($user.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)
}

function IsInWarningIntervals([int] $daysToExpire)
{
    foreach( $interval in $warningIntervals )
    {
       if ( $daysToExpire -eq $interval )
       {
        return $True
       }
    }
    return $False
}

function EmailUser($user)
{
    $pwdExpires = GetPasswordExpireDate $user
    $daysToExpire = GetDaysToExpire $pwdExpires 

    if( $daysToExpire -eq 0 )
    {
        $emailContent = $user.DisplayName + ", your password will expire today at " + $pwdExpires.ToShortTimeString() + " GMT"
    }
    else
    {
        $emailContent = $user.DisplayName + ", your password will expire in " + $daysToExpire + " days."
    }
    $smtp.Send($fromEmail, $user.mail, "Password Expiration Reminder", $emailContent)
}

function EmailAdmin($content) 
{
    if( [string]::IsNullOrEmpty($content) -and $alwaysSendAdminSummary )
    {
        $content = "There are no user's with expired passwords or users that need to change their password."
    }
    if( [string]::IsNullOrEmpty($content) -ne $True )
    {
        $smtp.Send($fromEmail, $adminEmail, "Password Expiration Summary", $content)
    }
}

function AppendAdminEmailNoMail($user)
{
    $pwdExpires = GetPasswordExpireDate $user
    $daysToExpire = GetDaysToExpire $pwdExpires 

    return "The password for account """ + $user.samAccountName + """ will expire in " + $daysToExpire + " days at " + $pwdExpires + " and there is no associated email address to send a notification to" + [System.Environment]::NewLine + [System.Environment]::NewLine
}

function AppendAdminEmailExpiredAccount($user)
{
    $pwdExpires = GetPasswordExpireDate $user
    $daysToExpire = GetDaysToExpire $pwdExpires 

    if( $user.pwdLastSet -eq 0 )
    {
        return "The user account """ + $user.samAccountName + """ is set to require a password change at next logon and the user has not yet changed it" + [System.Environment]::NewLine + [System.Environment]::NewLine
    }
    else
    {
        return "The password for user account """ + $user.samAccountName + """ expired " + $daysToExpire + " days ago on " + $pwdExpires + [System.Environment]::NewLine + [System.Environment]::NewLine
    }
}


#get all users
$users = Get-AdUser -Filter * -Properties userAccountControl, pwdLastSet, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled  #|
# FT -Property @{Label="UAC";Expression={"0x{0:x}" -f $_.userAccountControl}}, userAccountControl, pwdLastSet, @{Label="pwdLastChanged";Expression={[datetime]::FromFileTimeUTC($_.pwdLastSet)}}, @{Label="pwdExpires";Expression={[datetime]::FromFileTimeUTC($_.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)}}, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled
#$users

foreach( $user in $users ) 
{  
    #if account is enabled and password never expire flag does not exist, then process user
    if ( ($user.enabled -eq $True) -and (($user.userAccountControl -band $ADS_UF_DONT_EXPIRE_PASSWD) -eq 0) ) 
    {        
        $pwdExpires = GetPasswordExpireDate $user 
        $daysToExpire = GetDaysToExpire $pwdExpires 

        #if day falls on warning interval
        if( IsInWarningIntervals $daysToExpire )
        {
            #if mail attribute is not found in AD, add to admin email
            if ( [string]::IsNullOrEmpty($user.mail) ) 
            {
                $adminEmailContent += AppendAdminEmailNoMail $user
            }
            #otherwise email user
            else
            {
                EmailUser $user     
            }
        }

        #if days to expire is negative, password has expired. add to admin email
        if( $daysToExpire -lt 0 )
        {
            $adminEmailContent += AppendAdminEmailExpiredAccount  $user
        }
    }
}

EmailAdmin $adminEmailContent

Monday, March 12, 2012

PowerShell Snippits

I'm relatively new to PowerShell, the following are PowerShell snippits that I wanted to keep around for my personal reference.  It's short sample code if you will.  I figured I'd post them so that I can always come back here to reference them.  Maybe someone else can find them useful too.  I can elaborate and expand on any of them in another posting if anyone would like.

Hash Tables
$states = @{"Washington" = "Olympia"; "Oregon" = "Salem"; California = "Sacramento"}

$states.Add("Alaska", "Fairbanks")
$states.Remove("Alaska")
$states.Add("Alaska", "Fairbanks")
$states.Set_Item("Alaska", "Juneau")

$states | Sort-Object Name

write-host " "
write-host "The above pipes the object to the sort commandlet, you need to pipe each item in the collect with GetEnumerator()"
write-host " "

$states.GetEnumerator() | Sort-Object Name


Display Hex
"0x{0:x}" -f 500


Format-Table Formating Expression
Get-Process | FT -Property Handles, @{Label="Handles in Hex";Expression={"0x{0:x}" -f $_.Handles}}


DateTime Conversion
[datetime]::FromFileTimeUTC(129760513630045921)



Bitwise AND (-band) with Where-Object Cmdlet
$UserAccountFlags = @{}
$UserAccountFlags.Add(0x00000001, "ADS_UF_SCRIPT")
$UserAccountFlags.Add(0x00000002, "ADS_UF_ACCOUNTDISABLE")
$UserAccountFlags.Add(0x00000008, "ADS_UF_HOMEDIR_REQUIRED")
$UserAccountFlags.Add(0x00000010, "ADS_UF_LOCKOUT")
$UserAccountFlags.Add(0x00000020, "ADS_UF_PASSWD_NOTREQD")
$UserAccountFlags.Add(0x00000040, "ADS_UF_PASSWD_CANT_CHANGE")
$UserAccountFlags.Add(0x00000080, "ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED")
$UserAccountFlags.Add(0x00000100, "ADS_UF_TEMP_DUPLICATE_ACCOUNT")
$UserAccountFlags.Add(0x00000200, "ADS_UF_NORMAL_ACCOUNT")
$UserAccountFlags.Add(0x00000800, "ADS_UF_INTERDOMAIN_TRUST_ACCOUNT")
$UserAccountFlags.Add(0x00001000, "ADS_UF_WORKSTATION_TRUST_ACCOUNT")
$UserAccountFlags.Add(0x00002000, "ADS_UF_SERVER_TRUST_ACCOUNT")
$UserAccountFlags.Add(0x00004000, "N/A")
$UserAccountFlags.Add(0x00008000, "N/A")
$UserAccountFlags.Add(0x00010000, "ADS_UF_DONT_EXPIRE_PASSWD")
$UserAccountFlags.Add(0x00020000, "ADS_UF_MNS_LOGON_ACCOUNT")
$UserAccountFlags.Add(0x00040000, "ADS_UF_SMARTCARD_REQUIRED")
$UserAccountFlags.Add(0x00080000, "ADS_UF_TRUSTED_FOR_DELEGATION")
$UserAccountFlags.Add(0x00100000, "ADS_UF_NOT_DELEGATED")
$UserAccountFlags.Add(0x00200000, "ADS_UF_USE_DES_KEY_ONLY")
$UserAccountFlags.Add(0x00400000, "ADS_UF_DONT_REQUIRE_PREAUTH")
$UserAccountFlags.Add(0x00800000, "ADS_UF_PASSWORD_EXPIRED")
$UserAccountFlags.Add(0x01000000, "ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION")

$value = 0x202
$UserAccountFlags.Keys | where { $_ -band $value } | foreach { $UserAccountFlags.Get_Item($_) }

Write-Host ""

$value = 0x10212
$UserAccountFlags.Keys | where { $_ -band $value } | foreach { $UserAccountFlags.Get_Item($_) }

Write-Host ""

$value = 0x200
$UserAccountFlags.Keys | where { $_ -band $value } | foreach { $UserAccountFlags.Get_Item($_) }

NewLine
Environment::NewLine

Operators

  • -lt -- Less than
  • -le -- Less than or equal to
  • -gt -- Greater than
  • -ge -- Greater than or equal to
  • -eq -- Equal to
  • -ne -- Not equal to
  • -like - Like; uses wildcards for pattern matching


I intend to continually update this post with more snippets.