Active Directory Password Expiration Script

strangeluck
New Contributor

Not sure how many people out there have this problem, but many of our users rarely log off of their machines. This is a problem in an environment that has Mac's bound to Active Directory which need to abide by certain password expiration policies. Essentially, if a user doesn't log off or reboot, they don't get the notification that their password is going to expire and then eventually end up with a locked account.

There are a lot of different ways to handle this kind of issue, here are a few I can think of of the top of my head:

  1. Use Casper to Force log off / reboot machines at set intervals
  2. Utilize a script in Casper to kick of an email notification to the user
  3. Write an Applescript that prompts a user to change their password if it detects certain parameters

Using Casper, we were looking for a more elegant solution than a brute force log off (option 1). And we know from experience with a few other systems that email notifications about passwords expiring are frequently ignored (option 2). So that led me to the third option.

Those of us that have to deal with the Windows world (though I try to avoid it) know that Windows 7 has a nice little balloon that pops up above the system try by your clock which notifies you when your AD password is getting close to expiring.

I wanted something that would be as elegant as that (maybe leveraging Growl notifications). I have seen the ADPassMon application and unfortunately that does not work in our environment (we have a rather odd AD configuration). I also didn't like that it just put a countdown of the days until your password expires in the menu bar.

So I decided to write something myself (with the help of a few other code snippets out there). If anyone else has this issue, feel free to take a look at what I have below. We'll be rolling this out later this year for our users (so if you use any parts of it, make sure to test it throughly in your environment). I have some comments in the code explaining what each part of the script does, but in summary, it prompts a user if their password is going to expire in less than 14 days then offers them a dialog to change the password. When they click the button, it launches System Preferences and goes right to the "Change Password" section under the "Accounts" pane.

Also, if anyone has any comments about how they are handling this in their environment, I'd love to hear them. As well as any suggestions to make the script better. It's far from perfect, but I wanted to get it out on the forums in the event it might help some other people deal with this issue as well.

# Password Expiration Script
# Written By: Pete Johnson with some help from the following sources:
# https://secure.macscripter.net/viewtopic.php?pid=112613
# http://hints.macworld.com/article.php?story=20060925114138223
# Date: 05/30/2012

# Description: Uses DSCL to query how many days are left until a users password expires and prompts the user to change it if less than 14 days.
# This script is designed to be called from a Casper policy once per day. Compile the script then deploy it to a Shared location on each machine.
# Then create a daily policy in Casper to run a command that runs the script

# Prequisite: Access for Assistive Devices must be enabled for this script to launch System Preference properly.

# Password Expiration Policy in days (typically 90, I chose 89 to make sure the user changes it before the password expires on the 90th day)
set pwPolicy to "89"

# Prompt user if password expires in less than 14 days
set pwNotification to "14"

# Get logged in user
set user to do shell script "whoami"

if user is not "admin" then

# Query Directory Service and get cryptic password last set value. This may be called pwdLastSet in most cases. In ours it was SMBPasswordLastSet. set lastpwdMS to do shell script "dscl localhost -read /Search/Users/" & user & " grep -i SMBPasswordLastSet | cut -d ' ' -f 2 | sed q"

# Get the current date in Unix so we can calculate how many days are left set todayUnix to do shell script "date "+%s""

# First part of formula to decode password last set value from directory service. set lastpwdUnix to do shell script "expr " & lastpwdMS & " / 10000000 - 11644473600"

# Subtract that value from todays date set diffUnix to do shell script "expr " & todayUnix & " - " & lastpwdUnix

# Convert to days set diffdays to do shell script "expr " & diffUnix & " / 86400"

# Subtract password policy from days to get our final value set passwordExpiration to do shell script "expr " & pwPolicy & " - " & diffdays

if passwordExpiration is less than pwNotification then tell application "System Events" activate # Prompt a user to change their password if there is less than 14 days remaining. If this value is less than 14, script will exit gracefully without prompting user. display dialog "Your network password will expire in less than " & passwordExpiration & " days." buttons {"Change Now", "Change Later"} default button 1 with title "Network Password Expiration" with icon caution

# If user elects to change their password, this section will bring up System Preferences -> Accounts and launch the change password dialog. if result = {button returned:"Change Now"} then tell application "Finder" # Check version number of OS. This is important for when we call the System Preferences pane, the commands are slightly different depending # on OS version set os_version to version

# Parse out OS Version value so it's just the first four characters (eg. 10.6, 10.7, 10.8) set os_version_clean to do shell script "echo " & os_version & " | sed 's/(.*)../\1/'" end tell tell application "System Preferences" activate # Launch System Preferences and go into the "Accounts / Users" pane reveal anchor "passwordPref" of pane id "com.apple.preferences.users" end tell tell application "System Events" tell process "System Preferences" if os_version_clean is "10.7" then # In version 10.7 and 10.8 the Accounts pane was renamed to "Users & Groups". This will click the "Change Password" button. click button "Change Password…" of tab group 1 of window "Users & Groups" end if if os_version_clean is "10.6" then # In version 10.6 this will click the Change Password button. click button "Change Password…" of tab group 1 of window "Accounts" end if

end tell end tell end if end tell end if
end if

24 REPLIES 24

eftech
New Contributor

nice script, currently won't compile though.. crapping out here:

set os_version_clean to do shell script "echo " & os_version & " | sed 's/(.*)../1/'"
end tell

expect that pasting into JN has screwed some of the rather fun escaping...

maybe a few 'trys' would be good, just in case dscl goes funny?

nice work though... despite educating users to use the Mac to change the password, they still wait, then get it reset via support and then scream the keychain is bust... you know the drill..

jarednichols
Honored Contributor

eftech
New Contributor

this compiles:

set os_version_clean to do shell script "echo " & os_version & " | sed 's/(.*../\1/'"

(you have to put 3 in when posting here as it strips them out, maybe JAMF can sort this out as code posing is fundamental on JN..)

strangeluck
New Contributor

Thanks eftech. I was not aware of the 3 issue. I've updated the code above to fix that. So it should compile now.

Hey Jared, we did take a look at ADPassMon. It looks like a pretty slick application but we could never get it working right in our environment (due to how our AD is configured). I also wanted something that let you seamlessly launch the System Prefs -> Accounts pane and change the password immediately. That helps to ensure that the Keychain gets updated as well.

solomonacquah
New Contributor

This is great, I created a LaunchAgent to run this script everyday.

nkalister
Valued Contributor

+1 on ADPassMon if your AD environment works with it, otherwise Pete's script works as well.
Also, HI SOLOMON!! :) Nice to see you again.

tlarkin
Honored Contributor

You could try to look at the pwpolicy binary, but I think leveraging directory services (with our with out a third party) would probably be the best route

tlarkin$ pwpolicy
Warning: applying command to user <tlarkin>
Usage: pwpolicy [-h]
Usage: pwpolicy [-v] [-a authenticator] [-p password] [-u username | -c computername]
                [-n nodename] command command-arg
Usage: pwpolicy [-v] [-a authenticator] [-p password] [-u username | -c computername]
                [-n nodename] command "policy1=value1 policy2=value2 ..."

  -a       name of the authenticator
  -c       name of the computer account to modify
  -p       password (omit this option for a secure prompt)
  -u       name of the user account to modify
  -h       help
  -n       directory-node to search, uses search node by default
  -v       verbose

          getglobalpolicy   Get global policies.
          setglobalpolicy   Set global policies
                getpolicy   Get policies for a user
     get-effective-policy   Gets the combination of global and user policies that apply to the user.
                setpolicy   Set policies for a user
          setpolicyglobal   Set a user account to use global policies
              setpassword   Set a new password for a user
               enableuser   Enable a user account that was disabled
              disableuser   Disable a user account
       getglobalhashtypes   Returns a list of password hashes stored on disk by default.
       setglobalhashtypes   Edits the list of password hashes stored on disk by default.
             gethashtypes   Returns a list of password hashes stored on disk for
                            a user account.
             sethashtypes   Edits the list of password hashes stored on disk for
                            a user account.

jhbush
Valued Contributor II

Pete, thanks for sharing the script. I'm getting the wrong expiration date for some reason. I get the proper date code from AD, but the shell math seems to calculate wrong. Any ideas?

mbezzo
Contributor III

I'm getting weird math too - saying -351 days. :)

bentoms
Release Candidate Programs Tester

Been testing it myself & am also getting weird math. :(

I have had this working when querying AD & not the local directory, but that doesn't work with my use case

bentoms
Release Candidate Programs Tester

Looks like the issue is to do with getting the number from the macs local directory & not AD.

The SMBPasswordLastSet variables values stored are different, here is an example on my account:

AD: 130147428870730467
Local: 129938107100568531

Aaron
Contributor II

The ADPassMon isn't working for me, I get the stupid "-15776" bug.

The script works me for with a couple of changes. Since our AD login name are "surname[space]firstname", I had to enclose the dscl query in quotes

set lastpwdMS to do shell script "dscl localhost -read "/Search/Users/" & user & "" | grep -i SMBPasswordLastSet | cut -d ' ' -f 2 | sed q"

Also, the bit that says

set os_version_clean to do shell script "echo " & os_version & " | sed 's/\(.*\)../\1/'"

is coming up with an "Expected """ but found unknown token" error. Removing the triple backslashes altogether gets it to run. Is there a difference between running it within the AppleScript editor, and just running the script? Maybe because I'm running Mountain Lion?

Aaron
Contributor II

Actually, the sed command kept returning the full "10.8.3" string, instead of just "10.8", so I changed it to:

set os_version_clean to do shell script "echo " & os_version & " | sed 's:.[[:digit:]]*.$::g'"

Also, the logic to bring up the change password box didn't include 10.8, so I made it:

if os_version_clean is "10.7" or os_version_clean is "10.8" then

Seems to work fine now.

So, just to confirm (I've never tried it this way)... changing your password within the Users & Groups in OSX changes and updates Kerberos and AD as well? I would think this would update the local (keychain) copy and not push through to AD?

perrycj
Contributor III

Hey,

So this script is great and it works. I've managed to edit it to our environment based on the feedback in the comments and when I run in manually, it works. If I run it from terminal on a machine with the scripted stored in the /Users/Shared folder, it works beautifully.

However, I can't get it to work through casper. If I use this command in terminal on the machine, it works:

osascript /Users/Shared/ChangeResetPWAD.app

It pops up, I can change the password, wonderful.

If I deploy that same command either through a policy or through casper remote, nothing happens. Most of the time through remote I get this error:

Verifying /Library/Preferences/com.jamfsoftware.jamf.plist...
Preparing Policy...
Executing Policy 2013-06-17 at 4:25 PM | abcdef | 1 Computer...
Running command osascript /Users/Shared/ChangeResetPWAD.app...
Result of command:
/Users/Shared/ChangeResetPWAD.app: execution error: expr: syntax error (2)

Otherwise, no results, no change. Even if I push the script itself to a machine through remote, no error but nothing happens. Any ideas? We're using Casper 8.62. Any help would be greatly appreciated.

Aaron
Contributor II

You'll get this if the dscl read command fails - I got this a lot at first because I have usernames with spaces in it, and it would run it for "/Search/Users/surname" instead of "/Search/Users/surname firstname", which is why I enclosed it in quotes (above).

I also had an issue where the username was not being passed through to the script properly, as "whoami" will not return the name of the logged in user (when run via Casper). After a bit of wrassling with AppleScript (really not my forte) I got it working by getting Casper to run a bash script, which then calls the AppleScript:

#!/bin/sh

user=`stat -f%Su /dev/console`

if [ ! "$user" = "admin" ] && [ ! "$user" = "root" ]; then
    if [ -f /Library/Petermac/CheckADPassword.scpt ]; then
        loggedUser="$user" osascript /Library/Petermac/CheckADPassword.scpt
    fi
fi

What this does is it gets the currently logged in user and sets it as a bash variable for the current session, I then changed a line in the AppleScript to read it:

< set user to do shell script "whoami"
-
> set user to system attribute "loggedUser"

I played around with a few options to get the current user passed through, but I found this way to work without issue.

perrycj
Contributor III

Thanks for the response and help. If i make those changes to the Applescript, it doesn't compile because it doesn't like the < and >. Any ideas?

perrycj
Contributor III

also, for those of you who installed the script in a shared location, what are the terms of the policy you are using in casper to call the script and execute it inside the AD user's account?

Aaron
Contributor II

Oh sorry, that was just my way of showing the diff. You want to replace the first line with the second line.

I haven't rolled this out to the greater userbase yet, but I just have the policy run once a day, triggered by the every15 trigger.

jeffrey_fesunof
New Contributor

@ BenToms In this post above https://jamfnation.jamfsoftware.com/discussion.html?id=4619#responseChild39318

How did you use the dscl command to reach the actual Active Directory instead of the local AD cache on the Mac?

We have also seen some users with the negative number using the above script as it appears the local SMBPasswordLastSet setting is not updated or in sync with actual SMBPasswordLastSet in the AD. Not sure the reason why these two are not in sync. Anyone have any suggestions on how to sync the two up so local is matching the AD one?

tkimpton
Valued Contributor II

her you go in bash ;)

#!/bin/bash

###################### Get current user ########################

CurrentUser=`ls -l /dev/console | cut -d " " -f4`

############# Run the Command as the currently logged in user ################

su - "${CurrentUser}" -c /usr/bin/osascript <<'EOF'

# Password Expiration Script
# Written By: Pete Johnson with some help from the following sources:
# https://secure.macscripter.net/viewtopic.php?pid=112613
# http://hints.macworld.com/article.php?story=20060925114138223
# Date: 05/30/2012

# Description: Uses DSCL to query how many days are left until a users password expires and prompts the user to change it if less than 14 days.
# This script is designed to be called from a Casper policy once per day. Compile the script then deploy it to a Shared location on each machine.
# Then create a daily policy in Casper to run a command that runs the script

# Prequisite: Access for Assistive Devices must be enabled for this script to launch System Preference properly.

# Password Expiration Policy in days (typically 90, I chose 89 to make sure the user changes it before the password expires on the 90th day)
set pwPolicy to "89"

# Prompt user if password expires in less than 14 days
set pwNotification to "14"

# Get logged in user
set user to do shell script "whoami"

if user is not "admin" then

# Query Directory Service and get cryptic password last set value. This may be called pwdLastSet in most cases. In ours it was SMBPasswordLastSet.
set lastpwdMS to do shell script "dscl localhost -read /Search/Users/" & user & " grep -i SMBPasswordLastSet | cut -d ' ' -f 2 | sed q"

# Get the current date in Unix so we can calculate how many days are left
set todayUnix to do shell script "date "+%s""

# First part of formula to decode password last set value from directory service.
set lastpwdUnix to do shell script "expr " & lastpwdMS & " / 10000000 - 11644473600"

# Subtract that value from todays date
set diffUnix to do shell script "expr " & todayUnix & " - " & lastpwdUnix

# Convert to days
set diffdays to do shell script "expr " & diffUnix & " / 86400"

# Subtract password policy from days to get our final value
set passwordExpiration to do shell script "expr " & pwPolicy & " - " & diffdays

if passwordExpiration is less than pwNotification then
tell application "System Events"
activate
# Prompt a user to change their password if there is less than 14 days remaining. If this value is less than 14, script will exit gracefully without prompting user.
display dialog "Your network password will expire in less than " & passwordExpiration & " days." buttons {"Change Now", "Change Later"} default button 1 with title "Network Password Expiration" with icon caution

# If user elects to change their password, this section will bring up System Preferences -> Accounts and launch the change password dialog.
if result = {button returned:"Change Now"} then
tell application "Finder"
# Check version number of OS. This is important for when we call the System Preferences pane, the commands are slightly different depending
# on OS version
set os_version to version

# Parse out OS Version value so it's just the first four characters (eg. 10.6, 10.7, 10.8)
set os_version_clean to do shell script "echo " & os_version & " | sed 's:.[[:digit:]]*.$::g'"
end tell
tell application "System Preferences"
activate
# Launch System Preferences and go into the "Accounts / Users" pane
reveal anchor "passwordPref" of pane id "com.apple.preferences.users"
end tell
tell application "System Events"
tell process "System Preferences"
if os_version_clean is "10.7" or os_version_clean is "10.8" then
# In version 10.7 and 10.8 the Accounts pane was renamed to "Users & Groups". This will click the "Change Password" button.
click button "Change Password…" of tab group 1 of window "Users & Groups"
end if
if os_version_clean is "10.6" then
# In version 10.6 this will click the Change Password button.
click button "Change Password…" of tab group 1 of window "Accounts"
end if

end tell
end tell
end if
end tell
end if
end if

EOF

exit 0

perrycj
Contributor III

@tkimpton thanks for the bash version! Will this work with Mavericks? Or is it just as simple as adding the variables within the script for 10.9?

tkimpton
Valued Contributor II

It's only checking up to 10.8. You will need to change it.

perrycj
Contributor III

Delayed response but yes, adding the variables for 10.9 works fine.

Just wanted to pose another question that was mentioned above about the SMBPasswordLastSet data. Has anyone found a concrete solution for negative days? For example, "Your AD Password expires in -3 days". Usually, a full restart on the same network our AD is on syncs it up but not always. Just wanted to see how others were tackling this issue.

samwit
New Contributor

Thanks for sharing the Script it helps to get notification for expiring password but I found this automate tool named Lepide Active Directory Self Service you can find here http://www.lepide.com/active-directory-self-service/ which automatically reminds users to change their passwords before expire.