Bulk change date for user PW resets

Posted on 2016-08-05
Last Modified: 2016-08-11
Active Directory Functionality level is at 2012 R.  I need to do two things

1. Run a report for the date all users will need to change their PW

2.  Take Users that are bulk together on one day and spread them out.  So if I have 300 users set to reset their pw on same day I need to spread them out a bit.
Question by:Twhite0909
  • 6
  • 4
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41744684
Tech support getting flooded on reset day?  :)

What do you consider 'bulk'?

Author Comment

ID: 41744795
LOL Ya helpdesk got absolutely blown up w 400 users on day.  I still say 100 is bulk.  so I wanna generate a powershell that pipes to csv for Users reset days.  Filter by reset day so I can see anyone over 100 then take that group/groups and somehow tell them disperse......?
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41744883
Hmm..  this is trickier than I expected, but I'll work on something this afternoon.
Does Powershell have you tied up in knots?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why


Author Comment

ID: 41744933
Dude you are the man Dustin! LOL Now I already have something that shows me the list of users with expiry date which is

#Get-ADUser -SearchBase "OU=Employees,OU=Birch,DC=birch,DC=com" -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} –Properties “DisplayName”, “msDS-UserPasswordExpiryTimeComputed” |

#Select-Object -Property “Displayname”,@{Name=“ExpiryDate”;Expression={[datetime]::FromFileTime($_.“msDS-UserPasswordExpiryTimeComputed”)}} | Export-Csv

That worked great and I found our helpdesk exaggerates greatly bc we do not have hundreds of people expiring in 1 day.  We have blocks of 20,30,40 who all expire on the same day.  However management still wants us to get that number down.  So do you think you have anything that that:

A.  Finds all Users in my OU, lists expiry date

and then

B.  adds on top of their current expiry date to be a randomized number between 30-60 days

Does that make sense
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41744983
This expert suggested creating a Gigs project.
How many grand total users?  I don't think a random bump of 30-60 days is the way to go because potentially some users could get randomly reassigned expiry date and never have to reset password (making the reason for the expiry obsolete).

I think the idea would need to be something like:
Max expiry = 20
Find a day that is bad (40 expiry) or a weekend (40 on sat, 40 on sun).
Take the total number of users to be redistributed and crawl out each day until you can reassign them into days in the future keeping the expiry threshold down under 20.

40 expire Monday, 17 expire Tuesday, 15 expire Wed, 5 expire Thu.

Take the total on Monday and leave 20.  Then assign 3 users to Tuesday.  Then assign 5 users to Wed.  Then assign the remaining 12 to Thursday.

20 expire Monday, 20 expire Tuesday, 20 expire Wed, 17 expire Thu.

You could do that once per week.  Then people get pushed ahead, but not forgotten.  I can help with the code to set the password change date but the whole script (if done as above, which is what I'd recommend) is probably going to end up being an hour or so of work so you might consider posting a gig.

When I get a chance to test the pwdLastSet mod code I'll post that though.
LVL 80

Expert Comment

by:David Johnson, CD, MVP
ID: 41744997
You can't change the expiry date.  The days that a password is valid is set in the default domain policy. You can use fine grained password policies and separate OU's to change the time period/complexity requirements.
	Create a CSV and HTML Report on Expiring Passwords
	This script will create a Password Expiration report.  It creates it in 2 formats,
	CSV and HTML.  Both are then emailed to the specified user.
	Make sure to edit and change the PARAM section to match your environment
	Specify the path where you want to save the CSV report.  The script does not save
	the HTML report, but emails it as the body of the email.
	Tell the script who the script is coming "from".
	Tell the script where to send the email
	This needs to be the IP address or name of your SMTP relay server.
	CSV:	ExpirationReport.csv in the $Path location
	Email:	HTML version of the same report in the body of the email.  Also attaches
			the CSV to the email.
	Accepts all defaults as defined in the PARAM section
	.\Report-PasswordExpiration.ps1 -Path d:\myreports -From -To -SMTPServer
	Runs the report using D:\myreports as the path to save the CSV report.  Email will be sent
	from "" and sent to "" using
	as the SMTP relay server.
	Script:				Report-PasswordExpiration.ps1
	Author:				Martin Pugh
	Function Author:	M. Ali
	Twitter:			@thesurlyadm1n
	Spiceworks:			Martin9700
        1.01            Added loading of RSAT tools (if they're installed)
		1.0				Initial Version
	Source code:		
Param (
	[string]$Path = "c:\scripts",
	[string]$From = "",
	[string]$To = "",
	[string]$SMTPServer = "SMTPServerName"

Function Get-XADUserPasswordExpirationDate() {
	# Function written by M.Ali
	# Modified by Martin Pugh
    Param (
		[Parameter(Mandatory=$true,  Position=0,  ValueFromPipeline=$true, HelpMessage="Identity of the Account")]
		[Object] $accountObj

        If ($accountObj.PasswordExpired) 
		{	Return "Expired"
		{	If ($accountObj.PasswordNeverExpires) 
			{	Return "Password set to never expire"
			{	$passwordSetDate = $accountObj.PasswordLastSet
                If ($passwordSetDate -eq $null) 
				{	Return "Password has never been set"
				{	$maxPasswordAgeTimeSpan = $null
                    $dfl = (get-addomain).DomainMode
                    If ($dfl -ge 3) 
					{	## Greater than Windows2008 domain functional level
                        $accountFGPP = Get-ADUserResultantPasswordPolicy $accountObj
                        If ($accountFGPP -ne $null) 
						{	$maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge
						{	$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
					{	$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
                    If ($maxPasswordAgeTimeSpan -eq $null -or $maxPasswordAgeTimeSpan.TotalMilliseconds -eq 0) 
					{	Return "MaxPasswordAge is not set for the domain or is set to zero!"
					{	Return ($passwordSetDate + $maxPasswordAgeTimeSpan)

Try { Import-Module ActiveDirectory -ErrorAction Stop }
Catch { Write-Host "Unable to load Active Directory module, is RSAT installed?" -ForegroundColor Red; Exit }

$Result = @()
$Users = Get-ADUser -Filter * -Properties GivenName,sn,PasswordExpired,PasswordLastSet,PasswordneverExpires
ForEach ($User in $Users)
{	$Result += New-Object PSObject -Property @{
		'Last Name' = $
		'First Name' = $User.GivenName
		UserName = $User.SamAccountName
		Expiration = $($User | Get-XADUserPasswordExpirationDate)
$Result = $Result | Select 'Last Name','First Name',UserName,Expiration | Sort 'Last Name'

#Produce a CSV
$Result | Export-Csv $path\ExpirationReport.csv -NoTypeInformation

#Send HTML Email
$Header = @"
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
$splat = @{
	From = $From
	To = $To
	SMTPServer = $SMTPServer
	Subject = "Password Expiration Report"
$Body = $Result | ConvertTo-Html -Head $Header | Out-String
Send-MailMessage @splat -Body $Body -BodyAsHTML -Attachments $Path\ExpirationReport.csv

Open in new window
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41745137
Looks like David is correct, that attribute can't be modified to anything other than 0 or -1 by the system (my plan was to take the default domain policy and set it to the appropriate pwsLastSet after math from there).

That doesn't mean you're SOL entirely, because you can set/reset the expiration meaning you can manually reset the expiry then have a database file that gets parsed each day and expires then at a later stamped time.

Route 2 would use precedented password policies in ADAC and then have the script reset the passwords and temporarily put them in an x days expire group so when you reset you set the lifespan.  So, i.e. if they get pushed out 4 days, put them in a 4 day expire group and then reset the pwd expiration so it pulls 4 days.  They'd immediately get pulled out of that group so when the actual reset day occurs they're back in the normal policy window.

But to have the whole thing created is going to fall into the realm of gigs (it's a bit of a project), would probably take a couple of hours.  But if it's a huge problem someone would probably quote it at like $100ish (I think it'd take about 2 hours to write and test).

Author Comment

ID: 41745611

Thank you both very much for your help and suggestions.  I appreciate it very much! I will create a gigs project to get this done.
LVL 12

Accepted Solution

Dustin Saunders earned 500 total points
ID: 41749316
I saw no one had taken up the gig yet and your deadline is tomorrow.  I don't have time to see a gig through to completion, but here is some code to get you going.  I've tested small scale and it seems to be working fine, but set up a test environment or change $searchBase to small OU to test.

The variable $maxExtended is the total number of days you are willing to push out the expiration.  For each day here, you need to create a group called "x Day Expire" (i.e. 1 Day Expire, 2 Day Expire).  Then in ADAC, set a fine grained password policy, no min expiration and the expiration = to the number of days in X (see example for 2 days expiration).

$maxPerDay is the acceptable max per day that can have an expiration.

The script will get everyone's expiration day in the year (i.e. 1-9-2016 = 9, 2-1-2016 = 32) and look ahead for each $maxExtended days to distribute the resets.  If within the acceptable limit of days to push out there isn't room, it will retain it's default expiration date (recommend doing this so people don't have resets that potentially never expire).

Then for each move, we add the user into "x Day Expire" to get the password policy and then reset, and remove from the group.

I don't usually sink this much time into an EE problem, but you can take this and play with it (or open another question if there is an issue).

Import-Module ActiveDirectory

$searchBase = "DC=yourdomain,DC=local"  #root OU to search for users.
$maxPerDay = 20  #acceptable number of passwords to expire the same day.
$maxExtended = 7  #number of days maximum to push back expiration.

function ResetPasswordExpiry($days, $user)
    $ADUser = Get-ADUser -Filter {sAMAccountName -eq $user} -Properties pwdLastSet
    $expGroup = "$days Day Expire"
    Write-Host "Moving EXP for $user to $expGroup"
    Add-ADGroupMember $expGroup -Member $user -Confirm:$false
    Set-ADUser $ADUser -Replace @{pwdLastSet=0} -whatif
    Set-ADUser $ADUser -Replace @{pwdLastSet=-1} -whatif
    Remove-ADGroupMember $expGroup -Member $user -Confirm:$false

function ExpirationTable
    $dt = New-Object System.Data.DataTable
    $c1 = New-Object System.Data.DataColumn 'distinguishedName',([string])
    $c2 = New-Object System.Data.DataColumn 'sAMAccountName',([string])
    $c3 = New-Object System.Data.DataColumn 'expDOY',([int])
    return, $dt

function AddRow ($exp, $dn, $sam)
    $row = $expTable.NewRow()
    $row.sAMAccountName = $sam
    $row.distinguishedName = $dn
    $row.expDOY = $exp

$expTable = ExpirationTable

$expiringUsers = Get-ADUser -Properties msDS-UserPasswordExpiryTimeComputed -Filter * -SearchBase $searchBase | where {$_.Enabled -eq "True"}

foreach ($user in $expiringUsers)
    $expDate = [datetime]::FromFileTime($user."msDS-UserPasswordExpiryTimeComputed")
    AddRow $expDate.DayOfYear $user.DistinguishedName $user.SamAccountName

$expTable.DefaultView.Sort = "expDOY"
$expTable = $expTable.DefaultView.ToTable()

$doy = 1

while ($doy -lt 365)
    $thisExpires = $expTable.Select("expDOY = $doy")  #get expirations for this Day of Year

    if ($thisExpires.Length -gt $maxPerDay)
        $overload = $thisExpires.Length - $maxPerDay
        $lookrow = $maxPerDay + 1
        $nextDay = $doy + 1
        while ($overload -gt 0 -and $nextDay -le ($doy + $maxExtended))
            $expNextDay = $expTable.Select("expDOY = $nextDay")
            if ($expNextDay.Length -lt $maxPerDay)
                $available = $maxPerDay - $expNextDay.Length
                while ($available -gt 0)
                    $r = $thisExpires[$lookRow]
                    Write-Host $r.sAMAccountName
                    Write-Host $lookrow
                    ResetPasswordExpiry ($nextDay - $doy) $r.sAMAccountName
                    $r.expDOY = $nextDay
                    catch {
                    Write-Host "Row $lookRow was empty"
                    $available = $available - 1
                    $overload = $overload - 1


Open in new window

Note that due to this:
    Set-ADUser $ADUser -Replace @{pwdLastSet=0} -whatif
    Set-ADUser $ADUser -Replace @{pwdLastSet=-1} -whatif

Open in new window

it won't actually make any changes until you remove -whatif.  If you have issues you could also start a new gig to work on this existing code.

Author Closing Comment

ID: 41752578
if i could give more than an A I would.  Thanks alot for helping with this and spending so much of your time on it!
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41752613
No worries, good luck with this project!

Featured Post

Efficient way to get backups off site to Azure

This user guide provides instructions on how to deploy and configure both a StoneFly Scale Out NAS Enterprise Cloud Drive virtual machine and Veeam Cloud Connect in the Microsoft Azure Cloud.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article shows the method of using the Resultant Set of Policy Tool to locate Group Policy that applies a particular setting.
A company’s centralized system that manages user data, security, and distributed resources is often a focus of criminal attention. Active Directory (AD) is no exception. In truth, it’s even more likely to be targeted due to the number of companies …
This tutorial will walk an individual through the process of transferring the five major, necessary Active Directory Roles, commonly referred to as the FSMO roles from a Windows Server 2008 domain controller to a Windows Server 2012 domain controlle…
This tutorial will walk an individual through the process of transferring the five major, necessary Active Directory Roles, commonly referred to as the FSMO roles to another domain controller. Log onto the new domain controller with a user account t…

679 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question