Enable IMAP on all Exchange 2013/2016 Servers

IMAP and POP3 are disabled on Exchange Server 2013 and 2016 servers by default. Chances are if you need to enable it, you’ll need to enable it on all your servers at once. Fortunately, PowerShell makes this easy!

Run Exchange Management Shell as administrator on an Exchange server and execute the following:

PowerShell Script to enable IMAP on all servers

$Servers = Get-ExchangeServer | ? IsE15OrLater | Select -exp Name
Get-Service -ComputerName $servers -Name “MSExchangeIMAP4”,”MSExchangeIMAP4BE” | Set-Service -StartupType Automatic
Get-Service -ComputerName $servers -Name “MSExchangeIMAP4”,”MSExchangeIMAP4BE” | Start-Service

Enable HSTS with NetScaler

HTTP Strict Transport Security, or HSTS, is a good way to help ensure visitors to your site do so using a secure connection. On top of this, it’s a great way to ensure you get that all-important A+ score on Qualys.

flamingkeys-aplus

If you’re running a NetScaler in front of your service, you may want to configure these headers to appear care of the Virtual Server serving the content, rather than the back-end service or service group. This is quite simple using a rewrite policy.

NetScaler Rewrite Policy to enable HSTS


add rewrite action RW_ACT_HSTS insert_http_header Strict-Transport-Security "\"max-age=157680000\""
add rewrite policy RW_POL_HSTS true RW_ACT_HSTS
bind lb vserver vs_remote -policy RW_POL_HSTS -priority 100 -gotoPriorityExpression END -type RESPONSE

All responses through this vServer will now have the HSTS header attached. You can (and should) change the max-age to your preferred value.

Thanks to Ivan Cacic for this tip!

SMS PASSCODE Authentication Failure Email Alerts

SMS PASSCODE is a good tool, but it does not provide functionality to alert in the event of an authentication failure. It does, however, log quite verbosely to Windows event logs. Built-in Windows functionality can be used to receive email alerts when a login fails:

Create Send-FailedLoginAlert.ps1 script

Copy the following to a known location (in this example, C:\Scripts\Send-FailedLoginAlert.ps1).

# Fired when a SMSSec 2000 (AuthN failure) occurs

$SmtpDetails = @{
    "SmtpServer" = "smtp.margiestravel.com"
    "To" = "admin@margiestravel.com"
    "Subject" = ""
    "Body" = ""
    "From" = "alerts@margiestravel.com"
    "BodyAsHtml" = $true
    "Priority" = "High"
    }

# Get the latest 2000 event from the SMSSec log
$Event = Get-EventLog -LogName "SMSSec" -Newest 1 -InstanceId 2000

# Tear the details of the event apart into a hashtable we can work with
$EventDetails = @{}
$event.ReplacementStrings.Split("`n") | % {
    try { $EventDetails.Add($_.Split(":")[0].Trim(), $_.split(":")[1].Trim()) } catch { }
    }
    
$SmtpDetails.Subject = "'$($EventDetails.Login)': SMS PASSCODE Login Failure! "
$SmtpDetails.Body = @"
<strong>SMS PASSCODE Authentication Failure!</strong><br /><br />
Timestamp: $($Event.TimeGenerated)<br />
Username: $($EventDetails.Login)<br />
End-User IP: $($EventDetails.'End-user IP')<br />
Reason: $($EventDetails.Reason)<br /><br />
Session ID: $($EventDetails.'Session ID')<br />
"@

Send-MailMessage @SmtpDetails

Create the Scheduled Task

Create a scheduled task, configured as follows:

  • General
    • Run whether user is logged on or not
    • Do not store password
  • Triggers
    • On an event:
      • Log: SMS PASSCODE Security
      • Source: Authentication Proxy
      • Event ID: 2000
  • Actions
    • Start a program
      • Program: powershell.exe
      • Arguments: -ExecutionPolicy Unrestricted -File C:\scripts\Send-FailedLoginAlert.ps1

Now, any time event 2000 is fired in the SMS PASSCODE Security log, an email will be trigged using the parameters in the script.

How to Customize AD FS Error Messages

adfs-invalid-username

Entering a username incorrectly in AD FS results in a reasonably useful error message. However, some folks desire to change this, which is perfectly OK too.

adfs-invalid-username-new-error

Poking through the HTML behind IdpInitiatedSignon.aspx (the page that is rendered for forms-based authentication to AD FS) shows that the error messages for a) invalid username format, and b) empty password, are both stored in a JavaScript function called LoginErrors.

function LoginErrors(){
    this.userNameFormatError = 'Enter your user ID in the format \u0026quot;domain\\user\u0026quot; or \u0026quot;user@domain\u0026quot;.'; 
    this.passwordEmpty = 'Enter your password.';
}

Fortunately, JavaScript provides great native functionality for overriding inbuilt functions, so we can simply redefine LoginErrors later on. The page will then utilise that in the event of either condition (username format error or empty password) being met. Be sure to follow the approach below that matches your environment:

Default AD FS theme (Create custom theme)

If you don’t already have a custom AD FS theme, why not? They’re a great way to customise the (somewhat bland) default AD FS interface. Let’s create one now! You can use this to apply the customisations here, as well as to update countless other display and functionality features of the AD FS interface.

New-AdfsWebTheme -Name customtheme -SourceName default

Once you’ve created your custom theme, follow the steps below.

Existing custom AD FS theme

If you already have a custom AD FS theme, you’ll want to perform the following steps:

  1. Download your custom theme (herein ‘customtheme’) to your local machine
    Export-AdfsWebTheme -name customtheme -directoryPath C:\adfs\customtheme
    
  2. Add the following code to the bottom of the onload.js file, modifying error messages as appropriate:
    function LoginErrors() {
        this.userNameFormatError = 'Please enter your E-mail address.';
        this.passwordEmpty = 'Enter your password.';
    }
    
  3. Upload the customised onload.js file to your custom theme:
    Set-AdfsWebTheme -TargetName customtheme -AdditionalFileResource @{Uri=’/adfs/portal/script/onload.js’;path="c:\adfs\customtheme\script\onload.js"}
    
  4. Apply the custom theme:
    Set-AdfsWebConfig -ActiveThemeName customtheme
    

Determine which users have logged into Outlook Web App (OWA)

Discovering which users have logged into Outlook Web App (OWA) compared to those who haven’t is a bit of a challenge in Exchange Online and Exchange Server 2013/2016. One method is to make use of the fact that prior to first login, a user won’t have selected their language. Using this piece of knowledge allows us to perform a (somewhat rudimentary) evaluation of which users probably* have logged into OWA.

The easy way

Connect to Exchange PowerShell and run the following:

Get-Mailbox -ResultSize:unlimited | Get-MailboxRegionalConfiguration

Easy! This will show you each mailbox and by evaluating the “Language” value for each, you can determine if a user’s logged into OWA or not. Let’s make it even easier:

The really easy way

# Show me all the mailboxes that have probably not logged into OWA
Get-Mailbox -ResultSize:unlimited | Get-MailboxRegionalConfiguration | Where-Object {$null -eq $_.Language} 

# Tell me how many people have logged into OWA versus those who
# haven't (Those with a language will have a value, blanks have not logged in)
Get-Mailbox -ResultSize:unlimited | Get-MailboxRegionalConfiguration | Group-Object Language

* You’ll note several occurrences of “probably” in this post. This is because some lovely Exchange Server admins like to preset regional settings for users as part of user deployment. This is a wonderful idea, and I strongly encourage it. Unfortunately, however, it breaks everything discussed above.

Cmdlets used in this post: