Windows Speculative Execution Client/Server Patches/Mitigations/Detection Summary

Not intended to be comprehensive, but as of the morning of 4-January, all the worthwhile information I can find published by Microsoft on the Speculative Execution issue:

Note that the “Install-Module” PowerShell cmdlet requires PowerShell v5 or above. 

Windows Client Guidance for IT Pros to protect against speculative execution side-channel vulnerabilities
https://support.microsoft.com/en-us/help/4073119/windows-client-guidance-for-it-pros-to-protect-against-speculative-exe 

Windows Server Guidance to protect against the speculative execution side-channel vulnerabilities
https://support.microsoft.com/en-us/help/4072698/windows-server-guidance-to-protect-against-the-speculative-execution-s 

Important information regarding the Windows security updates released on January 3, 2018 and anti-virus software
https://support.microsoft.com/en-us/help/4072699/important-information-regarding-the-windows-security-updates-released 

ADV180002 | Guidance to mitigate speculative execution side-channel vulnerabilities
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV180002 

Public disclosure
https://bugs.chromium.org/p/project-zero/issues/detail?id=1272 

SQL Server Guidance to protect against speculative execution side-channel vulnerabilities
https://support.microsoft.com/en-us/help/4073225 

Microsoft Cloud Protections Against Speculative Execution Side-Channel Vulnerabilities
https://support.microsoft.com/en-us/help/4073235

I have not found any evidence of a KB for Exchange Server Guidance.

Client Patch (Windows 10)
https://support.microsoft.com/en-us/help/4056892/windows-10-update-kb4056892

Server Patch (Server Core 1709)
https://support.microsoft.com/en-us/help/4056892 

Server Patch (Server 2016)
https://support.microsoft.com/en-us/help/4056890 

Server Patch (Server 2012R2)
https://support.microsoft.com/en-us/help/4056898 

Server Patch (Server 2008R2)
https://support.microsoft.com/en-us/help/4056897 


Follow me on twitter: @EssentialExch

Exchange Server 2013 on Windows Server 2012

Unless you've been living under a rock, you know that the public betas of all the Wave 15 products, including Office, Exchange, Lync, SharePoint, etc. were released earlier this week as "2013" products. These were all released on Monday July 16, 2012. This follows by a couple of weeks the "release previews" of Windows 8 and Windows Server 2012.

You can download the Exchange Server 2013 public beta/preview here.

In the original release, it was not supported to install the Exchange preview on the Server 2012 preview. Today, Microsoft has changed that guidance and now supports installing Exchange Server 2013 on Windows Server 2012.

At this time, it requires a couple of additional manual steps. You can find information about how to install Exchange Server 2013 on Windows Server 2012 here.

We can reasonably expect that this will be cleaned up before RTM.

 

Sending an email to users whose password is about to expire, a PowerShell Rewrite

In September of 2005, I wrote a blog post named “Sending an e-mail to users whose password is about to expire“. Written in VBScript, it was one of my most popular blog posts of all time. I still have clients of mine that use it and I get occaisional email questions regarding it.

However, it is certainly showing its age!

There are other solutions available now, for free. However, the other solutions don’t meet all of my needs. (As always, I encourage you to choose the solution that best meets your needs.)

In my case, I need to be able to support:

  • Fast and efficient searching of Active Directory
  • Support for Fine Grained Password Policies (FGPPs, also known as Password Settings Objects or PSOs)
  • Authenticated SMTP
  • SSL/TLS SMTP
  • Report to Administrative User only (via SMTP)
  • Report to Administrative User only (via console)
  • Report to end-user (via SMTP)

The script in this blog post meets all of those needs. And, it is now written in PowerShell instead of VBScript.

I also chose to use the .Net Framework (System.DirectoryServices) for access to Active Directory (as well as some ADSI), instead of using the Active Directory PowerShell cmdlets. This makes it possible to execute the script on pretty-much any domain-joined computer, instead of one that requires RSAT-ADDS to be installed. It also avoids some weirdness around certain values returned by the AD cmdlets not matching older cmdlets or the AD itself.

This script is designed to work with PowerShell v2.0. The only PowerShell v2.0 feature is use of the Send-MailMessage cmdlet and using splatting to call Send-MailMessage. If you need this on PowerShell v1.0, you must just write a replacement for Send-MailMessage (use System.Net.Mail – it’s not a big deal).

The script will work on any domain functional level (DFL). The DFL is relevant to whether Fine-Grained Password Policies (FGPP, also known as Password Settings Objects – PSOs) are in use or not. FGPPs can be used when the domain level is at Windows 2008 or higher.

Coming in at 767 lines, this is the longest single PowerShell script I believe I’ve posted. But it’s well documented and hopefully self-descriptive. There are some fairly advanced capabilities demonstrated in this script, so you may find it worthwhile to study it a bit. If you have questions, let me know.

###
### Send-MailToUsersWithExpiringPasswords
###
### The top third of the script is data acquisition (and well documented).
### The bottom two-thirds is simple email-sending and report writing.
###
### This is PowerShell v1 compatible EXCEPT for using Send-MailMessage. You can
### easily replace that using System.Net.Mail if you wish.
###
### Parameter information:
###	daysForEmail   - how many days before a password expires should a user receive warning emails
###	adminEmail     - the administrator's email address
###	adminEmailOnly - do not send email to users, only report to the administrator
###	SMTPfrom       - the From address for the SMTP message(s)
###	SMTPserver     - the server to be used for sending the SMTP message(s)
###	SMTPuser       - if credentials are required, the user for authenticating to the SMTP server
###	SMTPpassword   - if credentials are required, the password for the SMTPuser
###	anr            - instead of searching all users, only search for users matching the specified ANR string
###	SMTPuseSSL     - use an SSL/TLS connection, not a clear-text SMTP connection
###	Quiet          - if NOT set, a copy of the admin report is dumped to the pipeline as text
###	DontSendEmail  - Email is never sent to either users or admin
###

Param(
	[int]$daysForEmail = 14,
	[string]$adminEmail = "michael@smithcons.com",
	[switch]$adminEmailOnly,
	[string]$SMTPfrom,
	[string]$SMTPserver,
	[string]$SMTPuser,
	[string]$SMTPpassword,
	[string]$anr,
	[switch]$SMTPuseSSL,
	[switch]$Quiet,
	[switch]$DontSendEmail
)

### Using Set-StrictMode helps protect against wonky errors that get caught by the
### compiler in compiled languages. Specifically (from the helpfile for the cmdlet):
### -- Prohibits references to uninitialized variables (including uninitialized 
###    variables in strings).
### -- Prohibits references to non-existent properties of an object.
### -- Prohibits function calls that use the syntax for calling methods.
### -- Prohibits a variable without a name (${}).
###
### However, using strict mode means that extra care has to be taken when using
### hashtables and property value collections. You see that in this script every
### time you see the Item() accessor method being used.

Set-StrictMode -Version 2.0

### For information about ANR, see "Ambiguous Name Resolution" in
### http://technet.microsoft.com/en-us/library/cc978014.aspx

$domainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$domainName   = $domainObject.Name
$domainRoot   = "LDAP://" + $domainName
$domainADSI   = [ADSI]$domainRoot
$domainMode   = $domainADSI.'msDS-Behavior-Version' ## Windows2008Domain is 3.

### domainMode is a PITA. System.DirectoryServices.ActiveDirectory.Domain.DomainMode and
### Microsoft.ActiveDirectory.Management.ADDomainMode have different values for the same
### enums!
###
### The first type is returned by System.DirectoryServices, the second type by the
### Get-ADDomain PowerShell cmdlet.
###
### That is why I ignore both of those potential access methods and use ADSI to access the
### value directly from the domain object. For specific information about those values, see:
### http://msdn.microsoft.com/en-us/library/cc223742(v=prot.10).aspx

[System.Int64]$Script:MaxPasswordAge = 0

function GetMaximumPasswordAge
{
	###
	### GetMaximumPasswordAge
	###
	### Retrieve the maximum password age that is set on the domain object. This is
	### normally set by the "Default Domain Policy".
	###

	if( $Script:MaxPasswordAge )
	{
		### Cache the value so that it only has to be retrieved once, converted
		### to an int64 once, and converted to days once. Win-win-win.

		return $Script:MaxPasswordAge
	}

	### Dealing with ADSI unfortunately also means dealing with COM objects.
	### Using ConvertLargeIntegerToInt64 takes the COM object and converts 
	### it into a native .Net type.

	[System.Int64]$Script:MaxPasswordAge = $domainADSI.ConvertLargeIntegerToInt64( $domainADSI.maxPwdAge.Value )

	### Convert to days
	### 	there are 86,400 seconds per day (24 * 60 * 60)
	### 	there are 10,000,000 nanoseconds per second

	[System.Int64]$Script:MaxPasswordAge = ( -$Script:MaxPasswordAge / ( 86400 * 10000000 ) )

	return [System.Int64]$Script:MaxPasswordAge
}

function newSecurePassword( [string]$password )
{
	###
	### newSecurePassword
	###
	### Take the normal string password provided and turn it into a 
	### secure string that can be used to set credentials.
	###
	### In PowerShell v2.0, this can be done with ConvertTo-SecureString.
	### That cmdlet isn't available in v1.0 though.
	###

	$secure = New-Object System.Security.SecureString

	$password.ToCharArray() |% { $secure.AppendChar( $_ ) }

	return $secure
}

function newPSCredential( [string]$username, [string]$password )
{
	###
	### newPSCredential
	###
	### Create a new PSCredential object containing the provided
	### username and plain-text password.
	###

	$pass = newSecurePassword $password

	$cred = New-Object System.Management.Automation.PSCredential( $username, $pass )
	$pass = $null

	return $cred
}

###
### We need to find all those user's who:
###	Are normal users                       0x00000200 ADS_UF_NORMAL_ACCOUNT
###	Are not disabled                       0x00000002 ADS_UF_ACCOUNTDISABLE
###	Do not have "password never expires"   0x00010000 ADS_UF_DONT_EXPIRE_PASSWD
###	Do not have "no password required"     0x00000020 ADS_UF_PASSWD_NOTREQD
###
###	Once we have the users, determine whether the user has a PSO
###	by examining msDS-PSOApplied (if the domain mode of the executing domain
###	is at Windows2008Domain or higher).
###
###	If the user has a PSO, load the PSO (and store it to a hashtable)
###	and evaluate the user's password against the PSO.
###
###	If the user does not have a PSO, evalute the user's password
###	against the Default Domain Policy Maximum Password Age (which
###	is found by the function GetMaximumPasswordAge above).
###
### The most efficient way to find users is to do an LDAP search. But building the
### proper search isn't that easy. We need a number of filters:
###
###	objectCategory=Person
###	(userAccountControl & ADS_UF_NORMAL_ACCOUNT)     <> 0
###	(userAccountControl & ADS_UF_ACCOUNTDISABLE)     == 0
###	(userAccountControl & ADS_UF_DONT_EXPIRE_PASSWD) == 0
###	(userAccountControl & ADS_UF_PASSWD_NOTREQD)     == 0
###
### Since userAccountControl is a BIT-FLAG attribute (meaning that individuals bits
### of the value control these options) then we need to be able to do a bit-wise
### LDAP search. It's important to realize that with a bit-wise search, the result
### of a particular filter is either not-one (!1, which is false) or not-zero (!0,
### which is true).
###
### So the next step in building our query is to use the LDAP bit-wise AND filter:
###
###	1.2.840.113556.1.4.803
###
### This is used to do a bit-wise AND of the attribute on the left to the value
### on the right. For example:
###
###	attribute:1.2.840.113556.1.4.803:=1024
###
### This means a bit-wise AND is done of the value of the attribute to the value
### 1024 (which is 0x400 in hexadecimal). If the result of that bit-wise AND is
### zero, then the value of the filter is false. If the result is non-zero, then
### the value of the filter is true. Putting a "!" in front of a false result
### makes the result true. Putting a "!" in front of a true result, makes it false.
###
### A combination of these two techniques makes it possible to scan for zero and
### non-zero bits (that is, those which are set to one and those which are set to
### zero).
###
### LDAP also supports a bit-wise OR filter, using the special value of:
###
###	1.2.840.113556.1.4.804
###
### Given the presence of AND and OR filters, it is possible to build very complex
### combination filters.
###
### A combination filter is built up of individual filters combined with either a
### logical OR ("|") or a logical AND ("&") and surrounded by parentheses.
###
###	(&
###		(objectCategory=Person)
###		(userAccountControl:1.2.840.113556.1.4.803:=512)
###		(!userAccountControl:1.2.840.113556.1.4.803:=2)
###		(!userAccountControl:1.2.840.113556.1.4.803:=65536)
###		(!userAccountControl:1.2.840.113556.1.4.803:=32)
###	)
###
### So, in pseudo-C code this is:
###
###	if( ( objectCategory == Person ) AND
###	    ( ( userAccountControl & ADS_UF_NORMAL_ACCOUNT     ) != 0 ) AND
###	    ( ( userAccountControl & ADS_UF_ACCOUNTDISABLE     ) == 0 ) AND
###	    ( ( userAccountControl & ADS_UF_DONT_EXPIRE_PASSWD ) == 0 ) AND
###	    ( ( userAccountControl & ADS_UF_PASSWD_NOTREQD     ) == 0 ) )
###	{
###		### we've got a matching user!
###	}
###
$ldapFilter =   "(&"							      +
			"(objectCategory=Person)"                             +
			"(userAccountControl:1.2.840.113556.1.4.803:=512)"    +
			"(!userAccountControl:1.2.840.113556.1.4.803:=2)"     +
			"(!userAccountControl:1.2.840.113556.1.4.803:=65536)" +
			"(!userAccountControl:1.2.840.113556.1.4.803:=32)"

if( $anr )
{
	###
	### using an ANR subquery allows us to reduce the result set from the LDAP query
	###
	$ldapFilter += "(anr=$anr)"
}

$ldapFilter += ")"

###
### build the LDAP search
###

$directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
$directorySearcher.PageSize    = 1000
$directorySearcher.SearchRoot  = $domainRoot
$directorySearcher.SearchScope = "subtree"
$directorySearcher.Filter      = $ldapFilter

###
### load the properties we want
###

$directorySearcher.PropertiesToLoad.Add( "displayName"        ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( "mail"               ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( "pwdLastSet"         ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( "sAMAccountName"     ) | Out-Null
$directorySearcher.PropertiesToLoad.Add( "userAccountControl" ) | Out-Null

if( $domainMode -ge 3 )
{
	### this attribute is only valid on Windows2008Domain and above
	$directorySearcher.PropertiesToLoad.Add( "msDS-PSOApplied" ) | Out-Null
}

$users = $directorySearcher.FindAll()

###
### All the data is in $users (or will be paged into $users).
### Build the necessary reports and emails.
###

$crnl = "`r`n"
$script:adminReport = ""

function line
{
	foreach( $arg in $args )
	{
		$script:adminReport += $arg + $crnl
	}
}

$now = Get-Date
$maximumPasswordAge = GetMaximumPasswordAge

line ( "Admin Report - Send Mail to Users with Expiring Passwords - Run Date/Time: " + $now.ToString() )
line " "
line "Parameters:"
line "        Days warning for sending email: $daysForEmail"
line "        Administrator email: $adminEmail"

if( $DontSendEmail )
{
	line "        Email will not be sent to either the administrator email or to user's email"
}
elseif( $adminEmailOnly )
{
	line "        Only the administrator will be sent email"
}

if( ![System.String]::IsNullOrEmpty( $SMTPfrom ) )
{
	line "        SMTP From address: $SMTPfrom"
}

if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
{
	line "        SMTP server: $SMTPserver"
}

if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
{
	line "        SMTP user: $SMTPuser"
}

if( ![System.String]::IsNullOrEmpty( $SMTPpassword ) )
{
	line "        SMTP password: $SMTPpassword"
}

if( $SMTPuseSSL )
{
	line "        UseSSL with SMTP is set"
}

line "        Maximum Password Age in Default Domain Policy = $maximumPasswordAge"
line "        Domain Mode $($domainObject.DomainMode.ToString())"
line ( "        User count = " + $users.Count.ToString() )

if( [System.String]::IsNullOrEmpty( $SMTPserver ) -and [System.String]::IsNullOrEmpty( $PSEmailServer ) -and !$DontSendEmail )
{
	Write-Error "No email server was specified via the SMTPserver parameter or the `$PSEmailServer environment variable."
	exit
}

if( ![System.String]::IsNullOrEmpty( $SMTPuser ) -and [System.String]::IsNullOrEmpty( $SMTPpassword ) )
{
	Write-Error "No SMTPpassword was specified."
	exit
}

if( ![System.String]::IsNullOrEmpty( $SMTPpassword ) -and [System.String]::IsNullOrEmpty( $SMTPuser ) )
{
	Write-Error "No SMTPuser was specified."
	exit
}

if( [System.String]::IsNullOrEmpty( $SMTPfrom ) )
{
	$SMTPfrom = "Password Administrator "
}

###
### some of the bitflags attached to the userAccountControl attribute
###

$ADS_UF_NORMAL_ACCOUNT     = 0x00200
$ADS_UF_ACCOUNTDISABLE     = 0x00002
$ADS_UF_DONT_EXPIRE_PASSWD = 0x10000
$ADS_UF_PASSWD_NOTREQD     = 0x00020

$psoCache = @{}

foreach( $user in $users )
{
	###
	### we spend some time being pretty careful dealing with the properties. this
	### allows us to verify we get the attributes we want, and for those that were
	### not present, we can establish reasonable defaults.
	###

	line " "

	$propertyBag = $user.properties
	if( !$propertybag )
	{
		line "error! null propertybag!"
		continue
	}

	$mail = ""
	$disp = ""
	$sam  = ""
	$pls  = 0
	$pso  = 0
	$uac  = 0
	$pwdX = $null

	$dispObj = $propertyBag.Item( 'displayname' )
	if( $dispObj -and ( $dispObj.Count -gt 0 ) )
	{
		$disp = $dispObj.Item( 0 )
		if( !$disp ) { $disp = "" }
	}
	$dispObj = $null
	### line "displayname = $disp"

	$samObj = $propertyBag.Item( 'samaccountname' )
	if( $samObj -and ( $samObj.Count -gt 0 ) )
	{
		$sam = $samObj.Item( 0 )
		if( !$sam ) { $sam = "" }
	}
	else
	{
		$sam = ""
	}
	$samObj = $null
	### line "sam = $sam"

	$uacObj = $propertyBag.Item( 'useraccountcontrol' )
	if( $uacObj -and ( $uacObj.Count -gt 0 ) )
	{
		$uac = $uacObj.Item( 0 )
	}
	else
	{
		line "no uac for $sam, assumed 0x200"
		$uac = $ADS_UF_NORMAL_ACCOUNT
	}
	$uacObj = $null
	### line "uac = $uac"

	$plsObj = $propertyBag.Item( 'pwdlastset' )
	if( $plsObj -and ( $plsObj.Count -gt 0 ) )
	{
		$pls = $plsObj.Item( 0 )
	}
	else
	{
		### this can be a normal occurence if the password has never been set
		$pls = 0
	}
	$plsObj = $null
	### line "pls = $pls"

	line ( $sam.PadRight( 21 ) + $pls.ToString().PadRight( 21 ) + $uac.ToString().PadRight( 8 ) + '0x' + $uac.ToString('x').PadRight( 8 ) )

	if( $pls -eq 0 )
	{
		line ( " " * 8 + "The password has never been set for this user, skipped" )
		continue
	}

	[System.Int64]$localMaxAge = $maximumPasswordAge

	if( $domainMode -ge 3 )
	{
		###
		### PSOs can be created on Server 2003, but they don't work properly until
		### the domain mode is Windows2008Domain or higher.
		###
		### So if we find a PSO and the domain mode is Windows2008Domain or higher,
		### we first determine whether we've seen this PSO before. If we have seen
		### the PSO before, then we've stored the msDS-MaximumPasswordAge value for
		### the PSO into a hash table for quick retrieval. If we have not seen the
		### PSO before, then we use ADSI to load the PSO and retrieve the value for
		### msDS-MaximumPasswordAge and then cache the value for future access. By
		### using a cache, we only have to access Active Directory to obtain values
		### once per PSO, leading to a significant performance improvement compared
		### to using the native cmdlets or S.DS.
		###

		$psoObj = $propertyBag.Item( 'msds-psoapplied' )
		if( $psoObj -and ( $psoObj.Count -gt 0 ) )
		{
			$pso = $psoObj.Item( 0 )
			### line ( "PSO object/name" + $pso )

			if( $psoCache.Item( $pso ) )
			{
				[System.Int64]$localMaxAge = $psoCache.Item( $pso )
				### line ( " " * 8 + "Accessed PSO from cache = " + $pso )
			}
			else
			{
				$psoADSI = [ADSI]( "LDAP://" + $pso )
				$ageOBJ = $psoADSI.'msDS-MaximumPasswordAge'

				[System.Int64]$localMaxAge = $psoADSI.ConvertLargeIntegerToInt64( $ageOBJ.Value )

				### Convert to days
				### 	there are 86,400 seconds per day (24 * 60 * 60)
				### 	there are 10,000,000 nanoseconds per second
				$localMaxAge = ( -$localMaxAge / ( 86400 * 10000000 ) )

				$psoCache.$pso = $localMaxAge
				### line ( " " * 8 + "Stored PSO to cache = " + $pso )

				$ageOBJ  = $null
				$psoADSI = $null

				### line "localMaxAge = $localMaxAge"
			}
		}
		else
		{
			### completely normal to not have a PSO, in that case, use the maxPwdAge
			### from the default domain policy.
			$pso = $null
			### line "pso is null"
		}
		$psoObj = $null
	}

	### In an Exchange environment, the 'mail' attribute contains the primary SMTP
	### address for a user. In a non-Exchange environment, that should also be true,
	### but no system process validates it. We do presume that the mail address is
	### valid, if present.

	$mailObj = $propertyBag.Item( 'mail' )
	if( $mailObj -and ( $mailObj.Count -gt 0 ) )
	{
		$mail = $mailObj.Item( 0 )
	}
	else
	{
		$mail = ''
	}

	if( 0 )
	{
		### The conditions reported on below cannot occur based on our LDAP filter
		### But they helped me develop and test the LDAP filter. :-)

		if( $uac -band $ADS_UF_NORMAL_ACCOUNT )
		{
			"`t`tNormal account"
		}
		else
		{
			"`t`tNot a normal account"
		}
		if( $uac -band $ADS_UF_ACCOUNTDISABLE )
		{
			"`t`tAccount disabled"
		}
		else
		{
			"`t`tAccount enabled"
		}
		if( $uac -band $ADS_UF_DONT_EXPIRE_PASSWD )
		{
			"`t`tPassword doesn't expire"
		}
		else
		{
			"`t`tPassword expires"
		}
		if( $uac -band $ADS_UF_PASSWD_NOTREQD )
		{
			"`t`tPassword not required"
		}
		else
		{
			"`t`tPassword required"
		}
	}

	### If we get here, $pls is non-zero.
	###
	### $pls comes to us in FileTime format (the number of 100 nansecond ticks 
	### since Jan 1, 1601). So it must be converted to DateTime and adjusted 
	### for the normal clock in order for us to do our arithmetic on it. For
	### more information about FileTime, see:
	### msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx

	$date = [DateTime]$pls
	$passwordLastSet = $date.AddYears( 1600 ).ToLocalTime()
	$passwordExpires = $passwordLastSet.AddDays( $localMaxAge )

	line ( " " * 8 + "The password was last set on " + $passwordLastSet.ToString() )

	if( $now -gt $passwordExpires )
	{
		line ( " " * 8 + "The password already expired on $($passwordExpires.ToString()). No email will be sent.")
		continue
	}
	else
	{
		line ( " " * 8 + "The password will expire on "  + $passwordExpires.ToString() )
	}

	$difference = $passwordExpires - $now
 	$days       = $difference.Days.ToString()
	$hours      = $difference.Hours.ToString()
	$minutes    = $difference.Minutes.ToString()

	line ( " " * 8 + "(This is in $days days, $hours hours, $minutes minutes)" )

	if( $difference.Days -le $daysForEmail )
	{
		if( [System.String]::IsNullOrEmpty( $mail ) -and !$adminEmailOnly )
		{
			line ( " " * 8 + "Oops - user doesn't have an email address." )
			continue
		}

		if( $DontSendEmail )
		{
			line ( " " * 8 + "Oops - we aren't supposed to send an email." )
			continue
		}

		line ( " " * 8 + "This user will be sent an email for password change." )

		$hash = @{}
		$hash.To       = $adminEmail
		$hash.Priority = 'High'
		$hash.From     = $SMTPfrom

		if( ![System.String]::IsNullOrEmpty( $disp ) )
		{
			$hash.Subject = "Warning! The network password for $disp ($sam) is about to expire."
		}
		else
		{
			$hash.Subject  = "Warning! The network password for $sam is about to expire."
		}

		if( !$adminEmailOnly -and ![System.String]::IsNullOrEmpty( $mail ) )
		{
			$hash.CC = $mail
		}

		if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
		{
			$hash.SmtpServer = $SMTPserver
		}
		elseif( ![System.String]::IsNullOrEmpty( $PSEmailServer ) )
		{
			$hash.SmtpServer = $PSEmailServer
		}

		###
		### Send-MailMessage will default to using $PSEmailServer when no other SMTP server is specified.
		### We checked earlier to ensure that at least one of those was specified.
		###

		if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
		{
			###
			### If SMTPuser is specified then SMTPpassword is also specified.
			### We checked earlier to make certain that if one was specified,
			### then both were specified.
			###

			$hash.Credential = newPSCredential $SMTPuser $SMTPpassword
		}

		if( $SMTPuseSSL )
		{
			$hash.UseSSL = $true
		}

		$bodyHeader = @"
WARNING!

"@
		$bodyHeader += "`r`nFor network user id: "
		if( ![System.String]::IsNullOrEmpty( $disp ) )
		{
			$bodyHeader += $disp + " (" + $sam + ")"
		}
		else
		{
			$bodyHeader += $sam
		}

		$hash.Body = @"
$bodyHeader

Your password is about to expire in $days days, $hours hours, $minutes minutes. 

Please change it now!

Thank you,
Your System Administrator
"@

		###
		### This is V2.0 function. I should replace it with something v1.0 compatible.
		### (splatting is also V2.0 only, which is why I'm lazy and didn't replace it.)
		###

		Send-MailMessage @hash

		$hash = $null
	}
}

###
### Invidual report emails complete.
### Now send summaries to the console and/or to the administrator email address.
###

line " "

if( !$quiet )
{
	###
	### If $quiet is NOT set, then dump the report to the console, as well
	### as sending the email to the administrator.
	###
	$script:adminReport
}

if( !$DontSendEmail )
{
	$hash = @{}
	$hash.To       = $adminEmail
	$hash.Priority = 'High'
	$hash.From     = $SMTPfrom
	$hash.Subject  = "Admin Report - Send Mail to Users with Expiring Passwords - Run Date/Time: " + $now.ToString()
	$hash.Body     = $adminReport

	###
	### Send-MailMessage will default to using $PSEmailServer when no other SMTP server is specified.
	### We checked earlier to ensure that at least one of those was specified.
	###

	if( ![System.String]::IsNullOrEmpty( $SMTPserver ) )
	{
		$hash.SmtpServer = $SMTPserver
	}
	elseif( ![System.String]::IsNullOrEmpty( $PSEmailServer ) )
	{
		$hash.SmtpServer = $PSEmailServer
	}

	if( ![System.String]::IsNullOrEmpty( $SMTPuser ) )
	{
		###
		### If SMTPuser is specified then SMTPpassword is also specified.
		### We checked earlier to make certain that if one was specified,
		### then both were specified.
		###

		$hash.Credential = newPSCredential $SMTPuser $SMTPpassword
	}

	if( $SMTPuseSSL )
	{
		$hash.UseSSL = $true
	}

	Send-MailMessage @hash

	$hash = $null
}

###
### Clean up a bit.
###

$now   = $null
$users = $null

$psoCache        = $null
$directorySearch = $null
$domainADSI      = $null
$domainObject    = $null

$script:adminreport = $null

### Done.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter!  – @EssentialExch

A Brief History of Time…(ok ok, let’s go with “An Introduction to the Windows Time Service”)

Note: This article is written for “modern” versions of the Windows operating system – that is, Windows Server 2008, Windows Server 2008 R2, Windows Vista, and Windows 7. For older versions of the Windows operating system, the concepts still apply, but some of the command line parameters for w32tm have changed.

Windows, especially in an Active Directory environment, requires “good” time. For this discussion, having “good” time means that all members of a domain are capable of synchronizing their clock to a domain controller. Domain controllers synchronize their clocks with the domain controller which holds the PDCe (Primary Domain Controller emulator) role in their Active Directory domain. PDCe’s in child domains synchronize their clocks with the PDCe of the root domain of the Active Directory forest.

When Windows does not have good time, log file entries have incorrect timestamps, event logs have incorrect timestamps, database transaction logs have incorrect timestamps, etc. etc. When the time on a computer becomes too far off from that of a domain controller (more than five minutes above or below), the computer is no longer capable of acquiring Kerberos tickets – this means that a computer and/or a user will not be able to log in to the Active Directory domain, nor will they be able to access any resources on the Active Directory network.

This can happen to user workstations and to servers. Obviously, a server may effect more users than a single workstation; but that doesn’t mean you should pay any less attention to your user workstations.

The “Windows Time” service is responsible for keeping a computer’s clock synchronized. This service can be controlled and configured on each computer by a command line tool named w32tm. Modifying any parameters for the Windows Time service requires local administrator permissions (and if UAC is enabled on the computer, it also requires an elevated command prompt or PowerShell session).

Determining whether a computer can synchronize its clock is easy to test (this is irrespective of whether the correct time source is configured – just that the computer can synchronize to the configured time source). Open an elevated command prompt or PowerShell session, and then enter:

w32tm /resync

Does it work? If so, then this computer can synchronize its clock with its configured time source. If the clock on the computer is off (“skewed” is the typical term used for this situation), then further analysis is required. If the time on the clock is off by an even number of hours, you should probably be looking at the timezone configured for the user or computer, not at the time synchronization sources.

If there are other computers whose time is skewed, then enter the same command on the other computers. The command should work there too.

If the resync commands work, but the computers are getting the wrong time, you need to begin analyzing the configuration for the Windows Time service. From your shell or PowerShell session, enter:

w32tm /query /source

This allows you to determine where the particular computer is getting its time. There are a number of possible responses. These include:

Local CMOS Clock

In this case, the computer is using the hardware clock on the computer as its time source. If you are using VMware, this means that the virtual machine is synchronizing to the VMware host.

Free Running System Clock

In this case, the computer is not using any external source, but depending on the time tick generated by the System Idle Process running on the computer. This value will generate a skewed time more quickly than any other.

a hostname of a domain controller in the Active Directory forest

In this case, the computer is using a domain controller as either an NTP server or as the time source via Active Directory. To determine which, see “/query /configuration”, discussed later.

a hostname of a computer running a NTP server

In this case, the computer is using a non-Active Directory server running an NTP server as its time source.

VM IC Time Synchronization Provider

In this case, the computer is using Hyper-V virtualization services as its time source.

Best practices from Microsoft recommend that you never use virtualization services (regardless of your hypervisor provider) as a time source for domain-joined computer; instead, you should depend on typical Active Directory synchronization methods.

VMware recommends that, for domain-joined computers, you install an NTP server on the VMware host and you have the computers synchronize to that NTP server.

*** Edit on June 25, 2010 – A VMware employee contacted me at the end of May suggesting that the above line was not accurate. Indeed, VMware updated their documentation (the linked VMware KB 1318 article below) in March of 2010. Now, for most intents and purposes, the VMware recommendations match the Microsoft recommendations.

In my mind, you are better off starting with the Microsoft recommendations and then go from there.

Here are references to the above comments and best practices:

Virtual Domain Controllers and Time Synchronisation
Considerations when hosting Active Directory domain controller in virtual hosting environments
Deployment Considerations for Virtualized Domain Controllers
VMware KB: Timekeeping best practices for Windows

Now, if the initial resync command doesn’t work – that particular failure reason is what you need to figure out. The first thing I always check is the firewall configuration. By default, time synchronization requires that that a computer be capable of sending a UDP request to port 123 on the NTP server (and receiving the response). NTP servers also listen on port 123 for TCP requests. The Windows Advanced Firewall in the modern Windows operating systems will automatically have an entry opened for time synchronization on UDP port 123 to your domain controllers. However, if you are configuring your PDC emulator server, you also need to ensure that the external firewall also allows that request. If you have non-domain-joined computers, then you may need to globally allow port 123 requests in your firewall.

The command below will tell you the time source for a particular computer:

w32tm /query /configuration

You are initially most interested in the value of the Type variable which is displayed. There are a number of possible responses. These include:

NTP – the external time source is the NTP server(s) specified by the NtpServer variable
NT5DS – the external time source is the domain hierarchy (that is, time synchronization originates from a domain controller)
NoSync – there is no external time source
AllSync – the computer should use both the domain hierarchy and the manually specified NTP server(s) as external time sources

There may be multiple external NTP servers listed in the NtpServer variable.

To properly set up a time source synchronization hierarchy for your domain, you need to begin by locating the domain controller which holds the PDC emulator FSMO role (obviously, if you have a single domain controller, such as is normally the case in SBS 2008, this process can be shortcut). To determine the holders of the FSMO roles, at that earlier-opened command prompt or PowerShell session, enter:

netdom query fsmo

Next, on the domain controller which is revealed to hold the PDC emulator role, you should do something like this:

w32tm /config /manualpeerlist:pool.ntp.org /syncfromflags:manual
w32tm /config /update
net stop w32time
net start w32time
w32tm /resync /rediscover

This ensures that this particular domain controller will attempt to synchronize with an external source providing known good time. pool.ntp.org is a common source. Windows computers come configured by default to use time.windows.com, which sometimes works and sometimes doesn’t.

For all other domain-joined computers, the appropriate configuration is:

w32tm /config /syncfromflags:domhier
w32tm /config /update
net stop w32time
net start w32time
W32tm /resync /rediscover

That really should take care of it. /syncfromflags:domhier is the default for domain-joined workstations and should be for all DCs except for the one in the root domain holding the PDCe role.

When a computer is properly synchronizing from an external source (after the Windows Time service restarts or becomes capable of synchronizing after some interval where it can’t synchronize), the following entry is made to the System Event Log:

Log Name: System
Source: Microsoft-Windows-Time-Service
Date: 1/24/2010 1:01:27 AM
Event ID: 35
Task Category: None
Level: Information
Keywords:
User: LOCAL SERVICE
Computer: W2008R2-DC
Description:
The time service is now synchronizing the system time with the time source pool.ntp.org (ntp.m|0x0|0.0.0.0:123->69.26.112.120:123).

If the time source is a DC, the DC will be named and its IP address listed, just as if it were an external source.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

Monitoring Exchange Server 2007 with System Center Operations Manager 2007

Just a few days ago, my new book (of the subject title!) started shipping from Amazon. If it hasn’t already, it should start appearing in your local bookstores this week.

If you want to order from Amazon, you can get it here.

Eight months in the making, this book was a labor of love. Including information on installing and configuring OpsMgr 2007, it also includes much information about operating a reliable Exchange environment. Even if you don’t have OpsMgr in your organization, you can learn from this book the key areas of Exchange that need monitoring and how to do so.

A strong PowerShell component is provided in several chapters to assist you in the generation of synthetic transactions for testing your Exchange environment.

While the book is written toward a key audience of Exchange Server 2007 administrators, much material is also provided for the Exchange Server 2003 administrator. The book uses a virtualized environment to describe a test roll-out of an OpsMgr 2007 and Exchange 2003/2007 mixed environment.

Since Exchange Server depends on the health of Windows Server, Active Directory, DNS, and IIS; tracking the health and well-being of these key services is also covered.

Go buy it. You’ll like it. 🙂

The chapter titles are:

  1. An Evolution of Server Management
  2. Monitoring Exchange Server 2007
  3. Installing and Configuring OpsMgr 2007
  4. Deplying OpsMgr 2007
  5. The First Management Pack: WIndows Server
  6. The Active Directory Management Pack
  7. The Domain Name System (DNS) Management Pack
  8. The Internet Information Services Management Pack
  9. SQL Server: An Ancillary Management Pack
  10. Exchange Server 2003
  11. Exchange Server 2007
  12. Exchange Server 2007 Redundancy
  13. Exchange Server Operations
  14. Tracking Mail Flow

Until next time…

As always, if there are items you would like me to talk about, please drop me a line and let me know!


Follow me on twitter: @EssentialExch

SBS 2003 Hardware Upgrade

In May of 2008, I wrote the article SBS 2003 Hardware Migration/Upgrade. Since then, I’ve been asked many times about my process for doing a SBS 2003 hardware upgrade. In general, I just have to say “I follow the steps”.

As I recommended in my original article, if the Microsoft white paper isn’t detailed enough for you, I recommend SBS MVP Jeff Middleton’s SBS Migration Tools.

However, this weekend I had the opportunity to upgrade the hardware for another client of mine, and I wrote down each step as I did it. This list of steps may encourage you to NOT do this. 🙂 There are many opportunities for error. This is a simple list of steps – if you don’t know what the shorthand means – you probably shouldn’t be doing it! 😛 Some of these one-line steps can consume quite a bit of time (e.g., “move wss” – the process is an entire white paper all on its own).

I execute this process in three phases. Phase I is basically “install the software on the new hardware.” Phase II is “configure the software and prepare for migration.” Phase III is “complete the migration.”

Phase I

-1] basic SBS 2003 RTM install
0] Join to AD domain
1] Dcpromo
2] Install DNS and DHCP
3] Change to AD integrated DNS
4] Update NIC(s)
5] move fsmo roles
6] update setup.sdb per http://theessentialexchange.com/blogs/michael/archive/2008/05/18/sbs-2003-hardware-migration-upgrade.aspx
7] Complete the SBS 2003 install
8] install Server 2003 sp1 (WindowsServer2003-KB889101-SP1-x86-ENU.exe)
9] Install kb 930045 (WindowsServer2003-KB930045-v5-x86-ENU.exe)
10] install WSS 2.0 sp1 (WSS2003SP1-kb841876-fullfile-enu.exe)
11] install Exchange 2003 sp1 (E3SP1ENG.exe)
12] install Windows XP sp2 for client deployment (SBS2003-KB891193-X86-ENU.EXE)
13] install SBS 2003 sp1 (SBS2003-KB885918-SP1-X86-ENU.EXE)
14] install Server 2003 sp2 (WindowsServer2003-KB914961-SP2-x86-ENU.exe)
15] install Exchange 2003 sp2 (E3SP2ENG.EXE)
16] Install kb 943494 (WindowsServer2003-KB943494-v4-x86-ENU.exe)
17] Install kb 930045 (WindowsServer2003-KB930045-v5-x86-ENU.exe)
18] install Resource Kit Tools (rktools.exe)
19] install Server Support Tools (suptools.msi)
20] install kb 943545 (WindowsServer2003-KB943545-x86-ENU.exe)
21] install Windows-Update/Microsoft-Update patches
22] install OpenManage/Server management tools
23] install FileserverTweaks.reg
24] move fsmo back (only domain fsmo, leave forest alone)

Phase II

1] configure remote access (ie, vpn)
2] activate server
3] add client licenses
4] configure SBS monitoring
5] configure exchange server
Copy Database Size limit – http://technet.microsoft.com/en-us/library/aa998066.aspx
Verify server property configurations
Change Offline Address List server for all Offline Address Lists
Update Recipient Update Service Config
Copy SMTP Connector / Default SMTP Virtual Server
Replicate public folders
6] install AV Server
7] install AV Client
8] move ssl certs
9] verify RPC/HTTPs config
10] set up new backup
11] move printers
12] move shares
13] initial file copy

On old server:
1] Install kb 943494 (WindowsServer2003-KB943494-v4-x86-ENU.exe)
2] Install kb 930045 (WindowsServer2003-KB930045-v5-x86-ENU.exe)
3] install kb 943545 (WindowsServer2003-KB943545-x86-ENU.exe)

Phase III

1] move fsmo
2] move mailboxes
3] move DHCP
4] move a/v clients
5] move WSS/SharePoint
6] move websites
7] move wsus
8] check/move Scheduled Tasks
9] move files
10] cut-overUntil next time…

As always, if there are items you would like me to talk about, please drop me a line and let me know!


Follow me on twitter: @EssentialExch

The Final Step to Resolving Reboot Hangs

I’ve reported on a number of occaisions about attempting reboots and those reboots just “hanging” until the server was power-cycled (or until you could execute a “shutdown” command from another computer in the environment – not always easy when connecting remotely!).

If you want to read those articles, you can find them here, here, and here.

It has appeared that the Scalable Networking Pack (SNP) had a role to play, and that Small Business Server (SBS) may have gotten the worst end of this stick, but it apparently turns out to have been a race condition in NTFS driver shutdown code.

Microsoft has released a number of patches over the last year to address this, but I can say that I’m finally happy with the last iteration of the patch. You can find that patch in this KB article: A Windows Server 2003-based computer stops responding when you shut down the computer in a remote console session.

I certainly won’t promise you that it solves all of the issues – but I’ve not seen a hang since I installed the last version of this patch. A version of the hotfix is available for both Windows Server 2003 sp1 and sp2.

Until next time…

As always, if there are items you would like me to talk about, please drop me a line and let me know!


Follow me on twitter: @EssentialExch

Script for From The Field

The authors of the upcoming “Windows Server 2008 Terminal Services Resource Kit” asked me if I wanted to write a script for them for a section named “From The Field”. I said sure! I based the script on one I wrote a long time ago, which you can access here. So the readers have the EXACT script that was published I’m putting this on my blog.

The purpose for this script is to tie the script to a particular event that might occur from Terminal Services. This capability is a new feature of the Windows Server 2008 Event Log Subsystem. If that event occurs, then this script is executed to send an email.

Now, the Resource Kit goes into detail of the event id that happens and how you actually do that tying together. Go buy it! 🙂

Of course, there are multiple ways to skin this cat, but here is one for you to use.

Option Explicit

'''----- script configuration area
Const strSMTPServer = "arvon.alpineskihouse.com"
Const strFrom       = "alerts@alpineskihouse.com"
Const strTo         = "adam.barr@alpineskihouse.com "
'''----- end configuration area

Dim objMail               ' the CDO object
Dim objWSHNetwork         ' windows-script-host network object
Dim strNetBIOSComputer    ' the netbios name of our computer

''' get the NetBIOS computer name
Set objWSHNetwork  = CreateObject ("WScript.Network")
strNetBIOSComputer = objWSHNetwork.ComputerName
Set objWSHNetwork  = Nothing 

''' do the real work to send the message 
Set objMail = CreateObject ("CDO.Message")
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing")      = 2
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver")     = strSMTPServer
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objMail.Configuration.Fields.Update

objMail.From     = strFrom
objMail.To       = strTo
objMail.Subject  = "Critical error!! " & strNetBIOSComputer & " failed to reboot " & Now
objMail.Textbody = "Critical error!! " & strNetBIOSComputer & " failed to reboot " & Now & vbCRLF
objMail.Send

Set objMail = Nothing

Until next time…

As always, if there are items you would like me to talk about, please drop me a line and let me know!

—–

Edit on September 10, 2010 – The Microsoft Collaboration Data Objects (CDO) are not installed by default on Windows Server 2008 or above. You can download them from here.


Follow me on twitter: @EssentialExch

I Have a Logo!

In case anyone besides me is counting, I’ve been at this (consulting, writing, speaking, etc.) for 10 months now. I’m having a blast. I wish I’d done this years ago.

Two months ago I took on a partner and we formed Smith Consulting d/b/a The Essential Exchange as a legal entity (as opposed to the sole proprietership I was previously).

After several weeks of back-and-forth, we finally came up with a logo today, and I’m very pleased with it. It will replace the generic icons you see on this blog fairly soon (image manipulation is not my strong suit). Here it is:


Follow me on twitter: @EssentialExch