Being able to reset user passwords with an Active Directory self service portal can save you a ton of time! I have seen many options for handling this problem – most are not free and many can be a difficult to setup. By using Exchange and PowerShell, we are going to setup a free self service password reset tool for our Active Directory users.
We are going to start off with the password reset script, then configure it for your environment, and finally cover ways to get your users to use it. Give me 30 minutes and I’ll get you out of the “I forgot my password” business.
Self Service Password Reset PowerShell Script
Copy the script and paste it into a PowerShell ISE prompt. I’ll meet you at the bottom.
#Configuration Block $SmtpServer = "MAIL.DOMAIN.COM" $ResetEmail = "reset@DOMAIN.com" $Username = "DOMAIN\reset" $Password = "P@ssw0rd" $MailServer = "https://MAIL.DOMAIN.COM/ews/exchange.asmx" $ExchangeVersion = "Exchange2010_SP1" #User who shold recieve email notifications that a password was reset or an invalid request was sent. $LoggingUser = "YOUREMAIL@DOMAIN.com" #Download for file is here: http://www.microsoft.com/en-us/download/details.aspx?id=35371 [Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll") function Create-RandomString() { $aChars = @() $aChars = "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "C", "b", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "2", "3", "4", "5", "6", "7", "8", "9", "_", ";" $intUpperLimit = Get-Random -minimum 8 -maximum 10 $x = 0 $strString = "" while ($x -lt $intUpperLimit) { $a = Get-Random -minimum 0 -maximum $aChars.getupperbound(0) $strString += $aChars[$a] $x += 1 } return $strString } $email = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion) $email.Credentials = New-Object Net.NetworkCredential($Username, $Password) $uri=[system.URI] $MailServer $email.Url = $uri $inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($email,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox) if ($inbox.UnreadCount -gt 0) { $PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties) $PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text; # Set search criteria - unread only $SearchForUnRead = New-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead, $false) $items = $inbox.FindItems($SearchForUnRead,10) #return only 10 unread mail items Import-Module -Name ActiveDirectory foreach ($item in $items.Items) { # load the property set to allow us to view the body $item.load($PropertySet) if($item.Body.text -Like "*") { $Phone = $item.From.address $Phone = $item.From.address.substring(0, $Phone.IndexOf("@")) $user = Get-ADUser -Filter {MobilePhone -eq $phone} -Properties MobilePhone If ($user -ne $null) { $PW = Create-RandomString if ($PW.length -gt 1) { Set-ADAccountPassword -identity $user.samaccountname -Reset -NewPassword (ConvertTo-SecureString -AsPlainText $PW -Force) Unlock-ADAccount -identity $user.samaccountname $PasswordAge = (Get-ADUser $user -Properties PasswordLastSet | Select PasswordLastSet) if ($PasswordAge.PasswordLastSet -ge (Get-Date).AddMinutes(-1)){ $Body = "Password reset for " + $user.SamAccountName + "-" + $user.DistinguishedName send-mailmessage -to $LoggingUser -from $ResetEmail -subject "Password Reset - $PW" -body $Body -SmtpServer $SmtpServer send-mailmessage -to $item.From.address -from $ResetEmail -subject " " -body "Your password is now $PW" -SmtpServer $SmtpServer } } } else { send-mailmessage -to $LoggingUser -from $ResetEmail -subject "Invalid Phone number" -body "Phone number $Phone not found" -SmtpServer $SmtpServer send-mailmessage -to $item.From.address -from $ResetEmail -subject " " -body "Your phone number was not found." -SmtpServer $SmtpServer } } $item.Isread = $true $item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite) } }
This awesome script comes from Gerald Moody. If you use Office 365, see the script at the end of this post.
Configuring Password Reset Self Service with PowerShell
At the top of the script, you’ll find the Configuration Block. You will need to set each of those variables for your environment. You will also need a dedicated AD account to use with this script. The account should have just the reset password permission delegated to it in Active Directory. It should also have a simple email (ex: reset@yourdomain.com).
Next, you will need to download the Microsoft Exchange Web Services Managed API 2.0 installer from here. Install this on the machine that will be hosting the script above. It does not have to be installed on your mail server. The machine’s IP will need to be able to send emails externally. A relay connector should be configured on your Exchange server. The machine that will run the script also needs the Active Directory PowerShell module installed. On Server 2012 R2, this is found in Add Roles and Features under the Remote Server Administration Tools location.
The function Create-RandomString controls what the password will be reset to. In the version above, a user will receive a password between 8 and 10 characters. Each character is randomly chosen from the $aChars array. Here is an example password: KbSDTxNeX . The random passwords will never contain confusing characters. You will notice that o,O,(zero),(one),l, and L are not in $aChars. You can remove/add any character to that line to change the way a random password is generated. For fun, remove every character but “a” and “b”. Then it will be like a cheat code to log back in. Moving on now…
Completely random passwords might not be your cup of tea. Reader Mike Liebstein replace the Create-RandomString function with the version below. It relies on a dictionary file (.\dict.csv) that is located in the same directory as the script. If you do not want to create a wordlist yourself, you can download this one.
function Create-RandomString() { $rand = new-object System.Random $conjunction = "or", "after", "as", "if", "than", "since", "the","my","we","our","and","but" $words = get-content .\dict.csv $word1 = ($words[$rand.Next(0,$words.Count)]) $con = ($conjunction[$rand.Next(0,$conjunction.Count)]) $word2 = ($words[$rand.Next(0,$words.Count)]) $strString = $word1 + "-"+ $con + "-" + $word2 Return $strString }
Finally, two points on security. First, at no point is a user’s name delivered to the end user. For a user to reset their password, their phone number has to be pre-entered into their AD account. A malicious user would have to have the username and a way to intercept the password. If your management is skittish about this method, you can add a security question to the process. Simply modify the search body portion of this script to match another user attribute to AD. Then inform your users to text your reset email with that attribute (like a birthday). If the cell number exists and the birthday matches, it resets the password.
Testing Your Self Service Password Reset Script
Now that your script is configured, create a scheduled task. The scheduled task needs to run as a user that can reset AD passwords. Set the scheduled task execute once a minute. Don’t worry about AD performance, AD is only queried if the $ResetEmail user has an unread message in its inbox.
Let’s test this out now. Open up Active Directory Users and Computers. Find a test user and enter your cell phone number under that user’s mobile phone attribute.
From your mobile phone, text your $ResetEmail. Put something in the body of the text (ex: Reset). Wait a minute (or manually execute the script or scheduled task). You should get a text back that looks like:
To verify that the password was changed, just open CMD and type: net user /domain USERNAME . Scroll down to the password last set line and you should see that the password was changed! When the $LoggingEmail variable is set, you will also receive an email confirming that the password has been set. In testing mode, the subject includes the password. This can be removed on line 71.
If the password hasn’t changed, check these common issues:
- Is the exchange version right in the Configuration Block?
- If you didn’t receive a text back, make sure the reset text message reached the inbox of the reset user. Make sure it was marked a read (processed).
- If you received a text back but the password didn’t change – check user permissions? Can your $ResetUser manually reset the user’s password?
- Some readers reported issues with the (ConvertTo-SecureString -AsPlainText $PW -Force) line – if needed, you can remove this from the Set-ADUser line.
Convincing Users to Stop Bothering You
To distribute this to your users, you can configure a custom logon message showing the email address that automates the password reset. No need for a separate web page, no need for dedicated machines pointing to a self service portal! To do this, set the Group Policy settings Interactive Logon: Message text and Interactive Logon: Message title. Both of these settings are found under: Computer Configuration/Policies/Windows Settings/ Security Settings/Security Options.
You can take a few routes to populate Active Directory with mobile numbers. If you would like a user driven (as in you don’t do the work) approach read this How To Guide and automate that process! If you can’t trust your users to do this (or have security policies against this), you can use this PowerShell approach by reader Michael Zinn.
$ImportCSV = import-csv "\\server\share\importfile.csv" foreach ($line in $ImportCSV) { $Mobile = $line.Mobile $Mobile = "$Mobile" -Replace "[^\d]" Set-QADUser -Identity $line.Username -MobilePhone $Mobile $Date= Get-Date Get-QADUser -Identity $line.Username | Select-Object Name,UserPrincipalName,MobilePhone,@{N="Date";e={$Date}} $User= Get-QADUser -Identity $line.Username
To get users accustomed to self service, you have to have buy-in from your whole IT department. Encourage helpdesk staff to direct users to reset their own passwords. It might be a bit more difficult at first but the benefits are huge in the long run. One college IT administrator told me that 26,000 passwords have been reset with this method over two years! Talk about awesome!
Have you implemented a self service password reset tool? Tell me about your experiences in the comments below.
Office 365 Self Service Password Reset Script
[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll") function Create-RandomString() { $aChars = @() $aChars = "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "C", "b", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "2", "3", "4", "5", "6", "7", "8", "9", "_", ";" $intUpperLimit = Get-Random -minimum 8 -maximum 10 $x = 0 $strString = "" while ($x -lt $intUpperLimit) { $a = Get-Random -minimum 0 -maximum $aChars.getupperbound(0) $strString += $aChars[$a] $x += 1 } return $strString } $email = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010) $email.Credentials = New-Object Net.NetworkCredential('reset@YOURDOMAIN.com', 'someOldPassword') $uri=[system.URI] "https://bl2prd0711.outlook.com/ews/exchange.asmx" $email.Url = $uri $inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($email,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox) if ($inbox.UnreadCount -gt 0) { $sendMailCredentials = New-Object System.Management.Automation.PsCredential 'reset@YOURDOMAIN.com',(ConvertTo-SecureString -String 'YOURPASSWORDFORRESET' -AsPlainText -Force) $PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties) $PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text; # Set search criteria - unread only $SearchForUnRead = New-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead, $false) $items = $inbox.FindItems($SearchForUnRead,20) #return only 20 unread mail items foreach ($item in $items.Items) { # load the property set to allow us to view the body $item.load($PropertySet) if($item.Body.text -Like "*Reset my password*") { $Phone = $item.From.address.substring(0, $item.From.address.IndexOf("@")) if ($Phone.substring(0, 1) -eq "1") { $Phone = $Phone.substring(1) } $user = get-aduser -filter "mobilePhone -eq $Phone" If ($user -ne $null) { $PW = Create-RandomString if ($PW.length -gt 6) { Set-ADAccountPassword -identity $user.samaccountname -NewPassword (ConvertTo-SecureString -AsPlainText $PW -Force) Unlock-ADAccount -identity $user.samaccountname send-mailmessage -to "reset@YOURDOMAIN.com" -from "reset@YOURDOMAIN.com" -subject "Password Reset" -body "Password reset for $($user.SamAccountName) - $($user.DistinguishedName)" -SmtpServer pod51018.outlook.com -UseSsl -Port 587 -Credential $sendMailCredentials send-mailmessage -to $item.From.address -from "reset@YOURDOMAIN.com" -subject " " -body "Your password is now $PW" -SmtpServer pod51018.outlook.com -UseSsl -Port 587 -Credential $sendMailCredentials } } else { send-mailmessage -to "reset@YOURDOMAIN.com" -from "reset@YOURDOMAIN.com" -subject "Invalid Phone number" -body "Phone number $Phone not found" -SmtpServer pod51018.outlook.com -UseSsl -Port 587 -Credential $sendMailCredentials send-mailmessage -to $item.From.address -from "reset@YOURDOMAIN.com" -subject " " -body "Your phone number was not found." -SmtpServer pod51018.outlook.com -UseSsl -Port 587 -Credential $sendMailCredentials } } $item.Isread = $true $item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite) } }