Windows 7 Password Notifications

Situation:

Users who use Windows 7 virtual desktops (in my case, VMWARE View desktops) are not able to see password expiration notifications when logging in.  In Windows 7 instead of getting a pop up box you just get a little flash down in lower right hand corner, and if you aren’t paying attention you can miss it very easily.  So I got tired of having to reset user passwords and decided to do something about it.

Solution:


Step 1: Create a Windows GPO to display a pop up box when password is close to expiration
Step 2: Create a Windows Powershell script to run as a scheduled task that emails users when their password is expiring

Create a new Windows GPO.  Credit to this guy.  I called mine Password Expiration Notification.  Set WMI Filtering to Windows 7.  Set Security Filtering to Authenticated Users and Domain Computers.

On the settings tab, configure the settings to look like this.

The script location can be whatever you want of course, so change that accordingly.  The passexpiry.vbs script looks like this. 
On Error Resume Next
set objNetwork=CreateObject(“Wscript.Network”)
Set objUser=GetObject(“WinNT://” & objNetwork.Userdomain & “/” & objNetwork.Username & “,user”) 
Dim objUser 
PassExp=INT(objUser.MaxPasswordAge/86400)-INT(objUser.PasswordAge/86400) – 1
if (PassExp=<14) and (PassExp>0) then
    wscript.echo “Your password is due to expire in ” & PassExp & ” day(s)”
end If

After that is all done, just attach it to the correct OU’s and wait for propagation to do its thing!
Some might think this is overkill, but as another layer you can also send the users an email saying their password is expiring!
First, create the Powershell script:
# PSPwdExpires.ps1
# PowerShell script to find all user accounts where the password
# is about to expire in a specified number of days.
#
# ———————————————————————-
# Copyright (c) 2011 Richard L. Mueller
# Hilltop Lab web site – http://www.rlmueller.net
# Version 1.0 – March 23, 2011
# Version 1.1 – April 6, 2011 – Added email function.
#
# This program assumes there is one password policy for the domain. The
# program finds all users whose password will expire in the specified
# period.
#
# You have a royalty-free right to use, modify, reproduce, and
# distribute this script file in any way you find useful, provided that
# you agree that the copyright owner above has no warranty, obligations,
# or liability for such use.
Trap {“Error: $_”; Break;}
# Specify number of days. Any users whose passwords expire within
# this many days after today will be processed.
$intDays = 14
# Email settings.
$Script:From = “username@domain.com
$Script:Subject = “Password Expiration Notice”
$Server = “exchangeserver.domain.com”
$Port = 25
$Client = New-Object System.Net.Mail.SmtpClient $Server, $Port
# You may need to provide credentials.
$Client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
Function SendEmail($To, $Body)
{
    $Message = New-Object System.Net.Mail.MailMessage `
        $Script:From, $To, $Script:Subject, $Body
    $Client.Send($Message)
}
# Retrieve Domain maximum password age policy, in days.
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]”LDAP://$D”
$MPA = $Domain.maxPwdAge.Value
# Convert to Int64 ticks (100-nanosecond intervals).
$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)
# Convert to days.
$MaxPwdAge = -$lngMaxPwdAge/(600000000 * 1440)
# Determine the password last changed date such that the password
# would just now be expired. We will not process any users whose
# password has already expired.
$Now = Get-Date
$Date1 = $Now.AddDays(-$MaxPwdAge)
# Determine the password last changed date such the password
# will expire $intDays in the future.
$Date2 = $Now.AddDays($intDays – $MaxPwdAge)
# Convert from PowerShell ticks to Active Directory ticks.
$64Bit1 = $Date1.Ticks – 504911232000000000
$64Bit2 = $Date2.Ticks – 504911232000000000
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = “subtree”
# Filter on user objects where the password expires between the
# dates specified, the account is not disabled, password never
# expires is not set, password not required is not set.
# and password cannot change is not set.
$Searcher.Filter = “(&(objectCategory=person)(objectClass=user)” `
    + “(pwdLastSet>=” + $($64Bit1) + “)” `
    + “(pwdLastSet<=" + $($64Bit2) + ")" `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=2)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=65536)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=32)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=48))”
$Searcher.PropertiesToLoad.Add(“sAMAccountName”) > $Null
$Searcher.PropertiesToLoad.Add(“pwdLastSet”) > $Null
$Searcher.PropertiesToLoad.Add(“mail”) > $Null
$Searcher.PropertiesToLoad.Add(“proxyAddresses”) > $Null
$Searcher.SearchRoot = “LDAP://” + $Domain.distinguishedName
$Results = $Searcher.FindAll()
ForEach ($Result In $Results)
{
    $Name = $Result.Properties.Item(“sAMAccountName”)
    $PLS = $Result.Properties.Item(“pwdLastSet”)
    $Mail = $Result.Properties.Item(“mail”)
    $Addresses = $Result.Properties.Item(“proxyAddresses”)
    If ($PLS.Count -eq 0)
    {
        $Date = [DateTime]0
    }
    Else
    {
        # Interpret 64-bit integer as a date.
        $Date = [DateTime]$PLS.Item(0)
    }
    # Convert from .NET ticks to Active Directory Integer8 ticks.
    # Also, convert from UTC to local time.
    $PwdLastSet = $Date.AddYears(1600).ToLocalTime()
    # Determine when password expires.
    $PwdExpires = $PwdLastSet.AddDays($MaxPwdAge)
    # Determine email address.
    If (“$Mail” -eq “”)
    {
        ForEach ($Address In $Addresses)
        {
            $Prefix = $Address.SubString(0, 5)
            If (($Prefix -ceq “SMTP:”) -or ($Prefix -ceq “X400:”))
            {
                $Mail = $Address.SubString(5)
                Break
            }
        }
    }
    If (“$Mail” -ne “”)
    {
        $Notice = “Password for user $Name must be changed by $PwdExpires”
        SendEmail $Mail $Notice
        “Email sent to $Name ($Mail), password expires $PwdExpires”
    }
    Else
    {
        “$Name has no email, but password expires $PwdExpires”
        “DN: $DN”
    }
}

Once this has been done save it somewhere locally on the machine you will be running the scheduled task from.
Next, create the scheduled task.  On the actions tab, you want to start a program:
%SystemRoot%system32WindowsPowerShellv1.0powershell.exe
In the add arguments field, you want to put the path to the script.  In my case:
C:ScriptsPSPwdExpires.ps1
Set it to run at a scheduled time every day, and you are good to go.