Enable RDS Remote App Self-Service Password Reset and Notifications

One of the challenges introduced during the remote work revolution has been user password changes. When your worker population is directly connecting to your network on a regular basis, Active Directory password policies do the job nicely. VPN password resets can be difficult master, but it’s nothing some training can’t overcome.

What happens when the remote worker solution is based on Microsoft’s Remote Desktop Services? If your people are connecting to full remote desktops, the answer is easy. Teach them to press CTRL-ALT-END. If you publish remote applications instead of desktops, two issues present themselves pretty early after the rollout. It is possible to overcome the challenges using only native OS functions.

The first problem we’ll need to address is the actual method a user will employ to change their password. Lucky for us, the RDWeb feature of the Remote Desktop Services Role includes this functionality; it just has to be enabled. Log on to a server that hosts the RDWeb site. If you aren’t sure, log on to any member of the RDS farm, open an elevated PowerShell console, and run the one-liner below.

Get-RDServer -Role "RDS-WEB-ACCESS"

Open the IIS MMC on the RDWeb server. If you are load balancing, perform the following instructions on each RDWeb host to enable the hidden password reset page. First expand the chevrons until you get to RDWeb, then one more time and click Pages. Next double click “Application Settings” and set the “PasswordChangeEnabled” attribute to True. Restart IIS by opening an elevated PowerShell or CMD console and running IISRESET (add /Force to end sessions immediately).

Set the yellow attribute to true

Once completed, a hidden password reset page will be available at a variation of the URL you already use for RDWeb. The default for a US English server is https://yourdomain.com/RDWeb/Pages/en-US/password.aspx. All that is left is to address notification. There’s no prompt in a published application that effectively tells a user when their password is about to expire. We’ll use a PowerShell script to send an email with the relevant information like complexity rules and include a link to the reset page we just created.

Connectivity to SMTP is your responsibility. If you have an internal email relay server already established, then target the notification script at that by changing the $SMTPServer variable. You may also need to authenticate to your email server with a licensed account to send mail or configure an Office365 mail connector. The script can be modified to fit those situations but is not configured for it now.

Schedule the script below to run as a task from a server or workstation that is running RSAT for the appropriate version of Windows. Specifically, you’ll need the Active Directory PowerShell modules. Schedule the task with an appropriate service account, read-only access to Active Directory is required. Adjust the variables to fit your environment and needs.

#Send password expiration password and link to reset for RDS Published Apps"
#Author: techbloggingfool.com

#Variables, populate with values for your environment and goals. 
$DaysToWarn = 7
$SupportTeam = "Support at XXX-XXX-XXXX"
$From = "Password AutoBot <noreply@yourdomainname.com>"
$Subject = "Reminder - Your Domain user account password will expire soon"
$SMTPServer = "Your Email Server's FQDN Goes between these quote marks"
$MailDomain = "Your email domain name goes here"
$RDWebResetURL = "The URL to your RDS password reset page goes here" 

function PreparePasswordPolicyMail ($ComplexityEnabled,$MaxPasswordAge,$MinPasswordAge,$MinPasswordLength,$PasswordHistoryCount)            
    $verbosemailBody = "<p class=MsoNormal>&nbsp;</p><p class=MsoNormal>Below is a summary of the requirements for your new password:</p>`r`n<ul>`r`n"            
    $verbosemailBody += "<li class=MsoNormal>Your password must be changed every <b>" + $MaxPasswordAge + "</b> days.</li>`r`n"            
    If ($ComplexityEnabled) {
        $verbosemailBody += "<li class=MsoNormal>Your new password cannot contain any part of your name or username and must contain 3 of the 4 character types:<ul><li class=MsoNormal>Uppercase letters</li><li class=MsoNormal>Lowercase letters</li><li class=MsoNormal>Numbers</li><li class=MsoNormal>Symbols</li></ul>`r`n"
    If ($MinPasswordLength -gt 0) {
        $verbosemailBody += "<li class=MsoNormal>Your new password must be at least <b>" + $MinPasswordLength + "</b> characters long.</li>`r`n"
    If ($PasswordHistoryCount -gt 0) {
        $verbosemailBody += "<li class=MsoNormal>Your new password cannot be the same as the last <b>" + $PasswordHistoryCount + "</b> passwords that you have used.</li>`r`n"
    If ($MinPasswordAge -eq 1) {
        $verbosemailBody += "<li class=MsoNormal>You must wait <b>" + $MinPasswordAge + "</b> days before you can change your password again.</li>`r`n"
    If ($MinPasswordAge -gt 1) {
        $verbosemailBody += "<li class=MsoNormal>You must wait <b>" + $MinPasswordAge + "</b> days before you can change your password again.</li>`r`n"
    $verbosemailBody += "</ul>`r`n"
    return $verbosemailBody            

#HTML Email Header and Footer Formatting
$header = '<html>

<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
 /* Font Definitions */
	panose-1:5 0 0 0 0 0 0 0 0 0;}
	{font-family:"Cambria Math";
	panose-1:2 4 5 3 5 4 6 3 2 4;}
	panose-1:2 15 5 2 2 2 4 3 2 4;}
 /* Style Definitions */
 p.MsoNormal, li.MsoNormal, div.MsoNormal
@page WordSection1
	{size:8.5in 11.0in;
	margin:1.0in 1.0in 1.0in 1.0in;}
 /* List Definitions */


<body lang=EN-US style=''word-wrap:break-word''>

<div class=WordSection1>

$footer = "</div>


#Import AD Module and obtain data from it
Import-Module ActiveDirectory -Verbose:$false

$domainPolicy = Get-ADDefaultDomainPasswordPolicy            
$passwordexpirydefaultdomainpolicy = $domainPolicy.MaxPasswordAge.Days -ne 0            
    $defaultdomainpolicyMaxPasswordAge = $domainPolicy.MaxPasswordAge.Days            
        $defaultdomainpolicyverbosemailBody = PreparePasswordPolicyMail $PSOpolicy.ComplexityEnabled $PSOpolicy.MaxPasswordAge.Days $PSOpolicy.MinPasswordAge.Days $PSOpolicy.MinPasswordLength $PSOpolicy.PasswordHistoryCount            

#Find accounts that are enabled and have expiring passwords
$users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0} `
 -Properties "Name", "UserPrincipalName", "msDS-UserPasswordExpiryTimeComputed", "mS-DS-ConsistencyGuid" `
 | Where-Object {$_."ms-DS-ConsistencyGuid" -ne $null} | Select-Object -Property "Name", "UserPrincipalName", "SAMAccountName", `
 @{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").ToLongDateString() + " " + [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").ToLongTimeString() }}

If ($null -eq $Users) {
    Write-Error "No users found with the selected search criteria."
#check password expiration date and send email on match
foreach ($user in $users) {

    $DaysRemaining = (New-TimeSpan -Start $(Get-Date) -End $user.PasswordExpiry).Days

    if ($DaysRemaining -le $DaysToWarn) {

        $EmailBody = $header
        $EmailBody += "<p class=MsoNormal>Greetings $($user.Name),</p>`r`n"
        $EmailBody += "<p class=MsoNormal>&nbsp;</p><p class=MsoNormal>This is an automated password expiration warning.&nbsp; Your password will expire in <b>$DaysRemaining</b> days on <b>$($User.PasswordExpiry)</b>.</p>`r`n"

        $PSO= Get-ADUserResultantPasswordPolicy -Identity $user.SAMAccountName            
        if ($null -ne $PSO) {
            $EmailBody += PreparePasswordPolicyMail $PSO.ComplexityEnabled $PSO.MaxPasswordAge.Days $PSO.MinPasswordAge.Days $PSO.MinPasswordLength $PSO.PasswordHistoryCount            
        else {
            $EmailBody += PreparePasswordPolicyMail $domainPolicy.ComplexityEnabled $domainPolicy.MaxPasswordAge.Days $domainPolicy.MinPasswordAge.Days $domainPolicy.MinPasswordLength $domainPolicy.PasswordHistoryCount            
        $EmailBody += "<p class=MsoNormal>&nbsp;</p><p class=MsoNormal>Office workers, press the Ctrl+Alt+Delete keys on your keyboard and select ""Change a password"".</p>`r`n"
        $EmailBody += "<p class=MsoNormal><b>Note:</b> If you are a remote worker, first connect to the VPN.</p>`r`n"
        $EmailBody += "<p class=MsoNormal><b>Note:</b> Then go to ""$RDWebResetURL"" and reset your password.</p>`r`n"
        $EmailBody += "<p class=MsoNormal><b>Note:</b> Finally, reboot your computer and reconnect to the VPN with your new password.</p>`r`n"
        $EmailBody += "<p class=MsoNormal>&nbsp;</p><p class=MsoNormal>Please contact $SupportTeam if you need assistance changing your password.</p>`r`n"
        $EmailBody += "<p class=MsoNormal>&nbsp;</p><p class=MsoNormal>DO NOT REPLY TO THIS EMAIL. This is an unattended mailbox.</p>`r`n"
        $EmailBody += $footer
        $Recipient = $user.SAMAccountName + '@' + $MailDomain
        Send-MailMessage -To $Recipient -From $From -SmtpServer $SMTPServer -Subject $Subject -BodyAsHtml $EmailBody

        Write-Verbose "Server: $SMTPServer`r`nFrom: $From`r`nTo: $Recipient`r`nSubject: $Subject`r`nBody:`r`n$EmailBody"

The task should be scheduled to run at least once per day. User’s will be able to reset their own passwords. The notification currently includes a line that suggests remote users should connect to the VPN, you may need to remove it if you utilize a different type of tunnel.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s