I teach a three-day course for System Administrators. I call it "Pragmatic PowerShell" - and that's exactly what it is about. Teaching administrators what they need to know about using PowerShell.

Of course, discoverability is a large part of the process. So we cover the standard stuff: Active Directory, DNS, DHCP, Exchange, and Lync. But what I find is that while most SysAdmins are comfortable with executing "one command, one result", most of them don't understand the concepts behind looping, functions, and conditional statements. And that's fine - if they weren't mainframe SysAdmins, they probably never had to write scripts before.

Note: Mainframe SysAdmins have been writing scripts for many decades; Job Control Language (JCL) on IBM mainframes, Work Flow Language (WFL) on Burroughs mainframes, Symbolic Stream Generator (SSG) on Univac mainframes, etc. etc.

So, it is important to cover these things to the students in ways that they can understand, and hopefully, to have a little fun with it at the same time.

Therefore, I went back and found a few of the simple computer games I used to play. In the 1970's and 1980's, many BASIC-based computer games were available, and these were commonly published in books and magazines and on BBSs (Bulletin Board Systems). People would buy the books or the magazines and then type the games into their computer. YES, actually type them!

I was six years-old when Neil Armstrong landed on the moon. I remember watching the TV transmission, sitting at my grandmother's house. Like most boys of my age, I wanted to grow up and become an astronaut! Alas, it was not to be.

But playing computer games, I never lost that desire. And it took me, a decade later, to study Physics in college.

"Lunar Lander", or "Lunar Exploration Module", or "Rocket" were common versions of programs that simulated a landing on the moon. Usually with some ASCII graphics.

Here is a PowerShell version (with some minor enhancements) of "Rocket".

Would you like to play a game?

 


###
### Rocket.ps1
###
### Algorithm is from the original Jim Storer "Lunar Lander" Basic 
### program from 1969
###
### Adapted from
### 	http://www.vintage-basic.net/bcg/rocket.bas
###	Retrieved on 2014-09-15
###
###	with a couple of minor bugfixes.
###
### Original program source came from "Basic Computer Games",
### first published in 1978. ISBN-10: 0894800523.
###
### This was one of the very first computer games that I ever
### played, a version in the late 1970's. Until graphics became
### more common (early 1980's), text games were the prevalent
### versions of all games. I can remember playing this game on DEC
### PDP computers (loaded from paper tape) and from original Apple
### computers (loaded from cassette tape).
###
### Michael B. Smith
### michael at TheEssentialExchange dot com
### September, 2014
###

###
### Newtonian one-dimensional physics says:
###
###	x' = x + vt + (1/2) * a * t * t
###
### Deriving this equation is trivial and left as an
### exercise for the reader. (Hint: use induction.)
###
### This program implements that algorithm, adjusted for
### the gravitational attraction of the moon.
###
### On the Earth, the gross value of gravitational
### acceleration is 32 feet/second/second.  The value of
### the gravitational acceleration on the moon is about
### one-sixth of that amount ( 16.6% ). This program
### uses a value of 5 feet/second/second. (Which is 
### close - the calculated value is 5.31 ft/sec/sec,
### which is only a 6.2% difference).)
###
### When originally written, floating point math was
### much more expensive than integer math. So most of
### the calculations will provide integer results.
### Newer versions of this program (based on lunar.bas
### and on lem.bas) use floating point math and will
### provide slightly more accurate results.
###
### However - you may find that this game is more
### challenging than you expect. :)
###

function GetYorN( [string] $prompt )
{
	while( 1 )
	{
		$answer = Read-Host $prompt
		if( $answer -eq "n" -or $answer -eq "no" )
		{

			return "n"
		}
		if( $answer -eq "y" -or $answer -eq "yes" )
		{
			return "y"
		}
	}	
}

function print-header
{
	" " * 30 + "Rocket"
	" " * 15 + "Creative Computing  Morristown, New Jersey"
	" "
	" "
	" "
	"Apollo Lunar Landing Simulation"
	"------ ----- ------- ----------"
	" "
}

function print-instructions
{
	$a = GetYorN "Do You Want Instructions (Yes or No)?"
	if( $a -eq "n" )
	{
		return
	}

	"You are landing on the moon and have taken over manual"
	"control 1,000 feet above a good landing spot. You have"
	"a downward velocity of 50 feet/sec. 150 units of fuel"
	"remain."
	" "
	"Here are the rules that govern your Apollo space-craft:"
	" "
	"[1] After each second, the height, velocity, and remaining"
	"    fuel will be reported via Digby, your on-board computer."
	"[2] After the report, a '?' will appear. Enter the number"
	"    of units of fuel you wish to burn during the next"
	"    second. Each unit of fuel will slow your descent by"
	"    1 foot/sec."
	"[3] The maximum thrust of your engine is 30 feet/sec/sec"
	"    or 30 units of fuel per second. If you enter a value"
	"    which exceeds 30, your value will be set to 30. If"
	"    you enter a value less than zero, your value will be"
	"    set to zero."
	"[4] When you contact the lunar surface, your descent engine"
	"    will automatically shut down and you will be given a"
	"    report of your landing speed and remaining fuel."
	"[5] If you run out of fuel, the '?' will no longer appear"
	"    but your second-by-second report will continue until"
	"    you contact the lunar surface."
	" "
}

function print-introduction
{
	"Beginning landing procedure.........."
	" "
	"G o o d  L u c k ! ! !"
	" "
	" "
	"SEC  FEET      SPEED     FUEL     PLOT OF DISTANCE"
	" "
}

function process-contact
{
	"*** Moon Contact ***"       ## 670 580
	$h = $h + 0.5 * ( $v1 + $v ) ## 680
	if( $b -eq 5 )
	{
		## time delta = height / velocity
		$d = $h / $v         ## 720 690
	}
	else
	{
		## time delta = -v + ( Sqrt( v*v + ( h * ( 10 - 2 * b ) ) ) / ( 5 - b )
		$d = ( -$v + [Math]::Sqrt( ( $v * $v ) + $h * ( 10 - 2 * $b ) ) ) / ( 5 - $b ) ## 700
	}

	$v1 = $v + ( 5 - $b ) * $d   ## 730 710
	"Touchdown at " + ( $t + $d ).ToString( 'N1' ) + " seconds."  ## 760
	"Landing velocity " + $v1.ToString( 'N1' ) + " feet/second."  ## 770
	$f.ToString() + " units of fuel remaining."                   ## 780

	if( $v1 -eq 0 )
	{
		"Congratulations! A perfect landing!!"                      ## 800
		"Your license will be renewed........Later."                ## 805
	}

	if( [Math]::Abs( $v1 ) -ge 2 )
	{
		"***** Sorry, but you blew it!!!! *****"                    ## 820
		"Everyone on the Lunar Landing Module is dead."
		"Appropriate condolences will be sent to the next of kin."  ## 830
	}

	" "                                                                 ## 840 
	" "
	" "
}

function print-status
{
	$str = "{0,-4:N0} {1,-8:N0}  {2,-8:N0}  {3,-7:N0}  I" -f $t, $h, $v, $f
	$str += ( "*" * ( [int] ( $h / 15 / 2 ) ) ) + "*"
	$str
}

###
###	Main
###

	print-header
	print-instructions

	while( 1 )
	{
		print-introduction      ## 390   860

		$t = 0	   ## 455	## time in seconds, at beginning of simulation
		$h = 1000  ## 455	## height in feet, at beginning of simulation
		$v = 50    ## 455	## velocity in feet per second, at beginning of simulation
		$f = 150   ## 455	## available fuel in arbitrary units, at beginning of simulation

		[bool] $done = $false

		while( $f -gt 0 -or !$done )
		{
			if( $f -gt 0 )
			{
				print-status                 ## 490 610

				$b = [int] ( Read-Host '?' ) ## 500
				if( $b -lt 0 )  { $b = 0  }  ## 510
				if( $b -gt 30 ) { $b = 30 }  ## 520
				if( $b -gt $f ) { $b = $f }  ## 530
			}

			$v1 = $v - $b + 5               ## 540 660
			$f = $f - $b                    ## 560
			$h = $h - 0.5 * ( $v1 + $v )    ## 570

			if( $h -le 0 )                  ## 580
			{                               ## 580
				$done = $true           ## 580
				break                   ## 580
			}                               ## 580

			$t = $t + 1                     ## 590
			$v = $v1                        ## 600

			if( $f -gt 0 )                  ## 610
			{                               ## 610
				continue                ## 610
			}                               ## 610

			if( $b -ne 0 )                  ## 615
			{                               ## 620
				"*** Out of fuel. ***"  ## 620
			}                               ## 620

			print-status                    ## 640

			$b = 0	                        ## 650
		}

		process-contact                         ## 670 - 840

		$a = GetYorN "Another mission (Yes or No)?"
		if( $a -eq "n" -or $a -eq "no" )
		{
			" "
			"Apollo Launch Control, signing out."
			"------ ------ -------- ------- ----"
			" "

			break  ## exit while(1) loop
		}
	}

The line numbers are from rocket.bas available from the link shown in the script source.

Follow me on Twitter at @essentialexch 

"WOULD YOU LIKE TO PLAY A GAME?" is a quote from "WarGames", starring a very young Matthew Broderick and Ally Sheedy, released in 1983.

Posted by michael | 1 comment(s)
Filed under:

Exchange 2010 and Exchange 2013 are rich with cmdlets providing you access to information regarding your Exchange environment.

However, as of Exchange 2013 CU6, there are 956 (!!) cmdlets. Knowing which cmdlets to use can be challenging. It can also be difficult to decide what information from a cmdlet's output is relevant.

Here is my attempt to consolidate output for Client Access Servers. This script is in daily use, and it works for me. I hope you find it useful.

If this script is executed on an Exchange Server, it will by default generate information for that server. You can change that by specifying the "-server <servername>" switch. The "-location <location-name>" switch is used to specify a physical location where a particular server resides. For example "London" or "Charlotte". The script defaults to using "location". FInally, if you specify "-Verbose", the script will output timing information. That is, how long the script took to execute and how long each cmdlet took to execute.

Enjoy!


##
## Dump-CasInformation.ps1
##
## Michael B. Smith
## September 8, 2014
## michael at TheEssentialExchange dot com
## http://TheEssentialExchange.com/blogs/michael
##
## No warranties, express or implied. Use at your own risk.
##
[CmdletBinding(SupportsShouldProcess=$false, ConfirmImpact='None') ]

Param(
	[Parameter(Mandatory=$false)]
	[string] $location  = "location",

	[Parameter(Mandatory=$false)]
	[string] $server    = $env:ComputerName
)

Set-Strictmode -Version 2.0

$startScript = Get-Date
Write-Verbose "Dump-Casinformation script starts $($startScript | Get-Date -Format u)"

$location = $location.ToUpper()

if( ( Get-Command Get-ExchangeServer -EA 0 ) -eq $null )
{
	Write-Error "This script must be executed within an Exchange Management Shell"
	return
}

"****** GLOBAL  GLOBAL  GLOBAL ******" 

	$cmd = 'OutlookProvider'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-OutlookProvider

		$r | fl Name, CertPrincipalName, Server, TTL, 
			OutlookProviderFlags, 
			RequiredClientVersions, ExchangeVersion

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'ExchangeServer'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-ExchangeServer -Status

		$r | fl Name, Identity, Fqdn, Edition, 
			Site, OrganizationalUnit, ServerRole, 
			AdminDisplayVersion, ExchangeVersion, 
			Static*, Current*,
			Is*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

"****** $location  $location  $location ******"

	$cmd = 'ClientAccessServer'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-ClientAccessServer -Identity $server

		$r | fl Name, Identity, Fqdn,
			IsOutOfService,
			OutlookAnywhereEnabled,
			ClientAccessArray,
			ExchangeVersion,
			AlternateServiceAccountConfiguration,
			OutlookAny*, 
			AutoDiscover*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'OutlookAnywhere'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-OutlookAnywhere -server $server

		$r | fl Server, ServerName, Name, Identity, 
			MetabasePath, Path, 
			AdminDisplayVersion, ExchangeVersion,
			SSLOffloading,
			InternalHostname, InternalClientAuth*,
			InternalClientsRequireSSL,
			ExternalHostname, ExternalClientAuth*, 
			ExternalClientsRequireSSL,
			IISAuthenticationMethods,
			XropUrl,
			ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'RpcClientAccess'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-RpcClientAccess -server $server

		$r | fl Name, Server, Identity,
			Responsibility,
			MaximumConnections,
			EncryptionRequired,
			BlockedClientVersions,
			ExchangeVersion

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'ActiveSync'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-ActiveSyncVirtualDirectory -server $server

		$r | fl Name, Server, VirtualDirectoryName, 
			WebsiteName, WebsiteSSLEnabled, CompressionEnabled,
			MetabasePath, Path,
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*,
			ExternalUrl, ExternalAuth*,
			ActiveSyncServer,
			*AuthEnabled, ClientCertAuth,
			Mobile*,
			BadItemReportingEnabled, SendWatsonReport,
			Remote*,
			Extended*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'AutoDiscover'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-AutodiscoverVirtualDirectory -server $server

		if( -not [String]::IsNullOrEmpty( $r.InternalUrl ) )
		{
			Write-Warning "InternalUrl should be empty, instead is $($r.InternalUrl)"
		}
		if( -not [String]::IsNullOrEmpty( $r.ExternalUrl ) )
		{
			Write-Warning "ExternalUrl should be empty, instead is $($r.ExternalUrl)"
		}

		$r | fl Server, Name, Identity, 
			MetabasePath, Path, 
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			*Authentication, ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'ECP'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-EcpVirtualDirectory -server $server

		$r | fl Server, Name, Website, DisplayName, Identity, 
			MetabasePath, Path, 
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			DefaultDomain, GzipLevel, *Enabled,
			*Authentication, ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	if( ( Get-Command Get-MapiVirtualDirectory -EA 0 ) -ne $null )
	{
		## cmdlet not available in Exchange 2010

		$cmd = 'MAPI'
		"*** $cmd  $cmd  $cmd ***"
		$cmdStart = Get-Date
		Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

			$r = Get-MapiVirtualDirectory -server $server

			$r | fl Server, Name, Identity, 
				MetabasePath, Path, 
				AdminDisplayVersion, ExchangeVersion,
				InternalUrl, InternalAuth*, 
				ExternalUrl, ExternalAuth*, 
				IISAuth*,
				ExtendedPro*

		$cmdEnd = Get-Date
		$cmdDelta = $cmdEnd - $cmdStart
		Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
		Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"
	}
	elseif( $server -ne $env:ComputerName )
	{
		Write-Warning "Get-MapiVirtualDirectory is not available on this computer (so this computer is probably running Exchange 2010). If you are accessing a server running Exchange 2013 or higher, this information will be missing."
		" "
	}

#####

	$cmd = 'OAB'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-OabVirtualDirectory -server $server

		$r | fl Server, Name, Identity, RequireSSL,
			MetabasePath, Path, 
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			PollInterval, OfflineAddressBooks,
			*Authentication, ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'OWA'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

	## OWA is just too big to process effectively

		$r = Get-OwaVirtualDirectory -server $server

		$r | fl Server, ServerName, Name, Website, DisplayName, Identity, RequireSSL,
			MetabasePath, Path, 
			DefaultDomain, LogonFormat,
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			VirtualDirectoryType, GzipLevel,
			Exchange2003Url, FailbackUrl, 
			LegacyRedirectType, RedirectToOptimalOWAServer,
			*Authentication, ExtendedPro*
		"...attribute list truncated, use 'Get-OwaVirtualDirectory -server $server | fl *' for full information"
		" "

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'PowerShell'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

	## this will return 2 entries for each server.
	"Note: the PowerShell Virtual Directory results include two vDirs for each server."

		$r = Get-PowerShellVirtualDirectory -server $server

		$r | fl Server, Name, Identity, RequireSSL,
			MetabasePath, Path, 
			OwaVersion, AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			VirtualDirectoryType, 
			*Authentication, ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

#####

	$cmd = 'WebServices'
	"*** $cmd  $cmd  $cmd ***"
	$cmdStart = Get-Date
	Write-Verbose "Dump-Casinformation command $cmd starts $($cmdStart | Get-Date -Format u)"

		$r = Get-WebServicesVirtualDirectory -server $server

		$r | fl Server, Name, Identity,
			MetabasePath, Path, 
			AdminDisplayVersion, ExchangeVersion,
			InternalUrl, InternalAuth*, 
			ExternalUrl, ExternalAuth*, 
			InternalNLBBypassUrl, GzipLevel, MRSProxyEnabled,
			*Authentication, ExtendedPro*

	$cmdEnd = Get-Date
	$cmdDelta = $cmdEnd - $cmdStart
	Write-Verbose "Dump-CasInformation command $cmd ends $($cmdEnd | Get-Date -Format u)"
	Write-Verbose "Dump-CasInformation command $cmd took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

"****** DONE  DONE  DONE ******"

$scriptEnd = Get-Date
$cmdDelta = $scriptEnd - $startScript
Write-Verbose "Dump-CasInformation script ends $($scriptEnd | Get-Date -Format u)"
Write-Verbose "Dump-CasInformation script took $($cmdDelta.TotalSeconds.ToString( 'N2' )) seconds"

Follow me on Twitter at @essentialexch 

Posted by michael | with no comments

Just a quick note.... Exchange Server 2013 Service Pack 1 has been released.

Among other changes, this version of Exchange Server provides support for installation on Windows Server 2012 R2 and provides support for Windows Server 2012 R2 domain controllers.

The blog post announcing the release is here. You can download the release here.

At the same time were releases for two legacy versions of Exchange: Update Rollup 5 for Exchange 2010 Service Pack 3 and Update Rollup 13 for Exchange 2007 Service Pack 3.

The announcement for those releases is here. At the time of this writing, no information is available about the contents of those rollups.

More information coming soon!

...

Follow me on twitter: @essentialexchange

I apologize!

This blog was offline for a couple of weeks.

It was completely beyond my control.

I am hosted by GoDaddy.com and they spent several days saying "this is your problem" before they finally admitted "oh, ok, this is our problem."

I do plan to move to another hosting platform in the next few months... but honestly, I still had planned that it would hosted by GoDaddy. I may now need to re-evaluate that decision.

Again, I apologize.

I will try to ensure this does not happen again.

Regards,
Michael B.

.....

Please follow me on Twitter as @essentialexch

Posted by michael | with no comments
Filed under:

Most companies have a set of primary Internet domains (Exchange accepted domains) that they use to assign to users. However, a constant is that most companies also assign a secondary email address that has a domain which is identical for all of their users. That is true in my environment, and in that of all customers I have worked with in the past.

However, Exchange tends to generate (especially if you have migrated from legacy Exchange versions) far more email addresses than that for any given Exchange object (user, group, contact).

The script below reports on each user's primary SMTP address, plus a secondary SMTP address whose domain is specified as a parameter.

The script uses the Active Directory PowerShell module, which must be installed on the computer where this script is executed. The script does not use any Exchange specific features, thus the Exchange Management Shell is not required. I tested the script on Exchange 2010 and Exchange 2013, but it should work on Exchange 2007 as well.

A couple of techniques worth noting are used here. First, instead of a function, I use a filter. A filter is a very special kind of a function optimized for working with pipelines of objects. Inputs from the pipeline are passed to the filter using $_.

Second, I parse the proxyAddresses attribute. This attribute contains a list of all addresses that are assigned to a given user. It is an array (a collection) of all those addresses. Importantly, for each item in the collection, the address itself is prefixed by an address type. For SMTP addresses, the address type is "smtp". For FAX addresses, the address type is "fax". For X500 addresses, the address type is "x500". Etc. Also importantly, if the address type is capitalized, then a particular address is the primary address of that type - the default. If the address type is not capitalized, then the address is a secondary address. There may be any number of secondary addresses of a particular type. There may be only one primary address of a particular type.

In order to detect primary addresses, I use the "-ceq" operator in PowerShell. This is "cased equal". That means that the case of the letters is significant. By default, comparisons in PowerShell are not case significant.

Finally, Exchange objects will always have the proxyAddresses attribute populated. This fact is used to build LDAP query utilized to find Exchange objects.

I hope you find this useful!

##
## Get-PrimaryAndSecondary
##
## Michael B. Smith
## April, 2013
##
Param(
	[string]$secondaryDomain = "@TheEssentialExchange.com"
)

[int]$secondaryDomainLen = $secondaryDomain.Length

filter strip-Addresses
{
	$proxies = $_.proxyAddresses

	$primary   = ""
	$secondary = ""

	$object = "" | Select GivenName, Surname, sAMAccountName, PrimarySmtp, SecondarySmtp

	$object.GivenName      = $_.GivenName
	$object.SurName        = $_.SurName
	$object.sAMAccountName = $_.sAMAccountName

	foreach( $proxy in $proxies )
	{
		$len = $proxy.Length

		## note: "SMTP:".Length == 5

		## note: The primary SMTP address has a CAPITALIZED "SMTP:" prefix
		## all secondary SMTP addresses have a lowercase "smtp:" prefix

		## note: any interesting secondary proxy address will be longer than 
		## "SMTP:".Length + $secondaryDomainLen

		if( $len -gt 5 )
		{
			$prefix = $proxy.SubString( 0, 5 )
			$temp   = $proxy.SubString( 5 )	##strip off "smtp:", if present

			if( $prefix -ceq "SMTP:" )
			{
				$primary = $temp
				if( $secondary.Length -gt 0 )
				{
					break   ## we have both primary and secondary, 
						## we don't need to look any more
				}
			}
			elseif( $prefix -ceq "smtp:" -and $len -gt ( 5 + $secondaryDomainLen ) )
			{
				if( $temp.EndsWith( $secondaryDomain ) )
				{
					$secondary = $temp
					if( $primary.Length -gt 0 )
					{
						break   ## we have both primary and secondary, 
							## we don't need to look any more
					}
				}
			}
		}
	}

	$object.PrimarySmtp   = $primary
	$object.SecondarySmtp = $secondary

	$object
}

Import-Module ActiveDirectory

Get-AdUser -LDAPFilter "(&(objectCategory=user)(proxyAddresses=*))" `
	-Properties GivenName, SurName, proxyAddresses -ResultSetSize $null | 
	strip-Addresses

Follow me on twitter, @essentialexch

Posted by michael | with no comments

This is an update of my post Determining the Exchange Version - without using Get-ExchangeServer, from April 25, 2012. Since then, Exchange 2013 has been released! I've had several requests from people who are not PowerShell scripters to update that function. So here it is. I have repeated the text from the prior below.

If you write lots of Exchange scripts (as I do), and have several different versions of Exchange on which you need to run those scripts (as I do); you soon see the need for being able to determine what version of Exchange a particular server is running - and you may need to do this outside of the Exchange Management Shell.

This may be necessary because of different behaviors that are required with the Exchange Management Shell depending on the version of Exchange. It also may be required because the version of Exchange pre-dates the Exchange Management Shell (i.e., Exchange 2003). As an additional rationale, your script may need to load the Exchange Management Shell and cannot do that properly without knowing the version of Exchange that is being targeted (the process differs between Exchange 2007 and Exchange 2010 and Exchange 2013).

Thus, I've written a couple of functions that I wrap in a script to give me that information. Get-ExchangeServer, the function presented in this post, returns a simple object containing the name of the server examined, plus the version of Exchange that is running on that server. That is, in C pseudo-syntax:

struct result {
    string Name;
    string Version;
}

If the result of the function is $null, then the version could not be determined and the server targeted either does not exist or is (very likely) not running Exchange. If the server does not exist (or the firewall on the server prevents remote management via WMI) then an error is displayed.

The version information about Exchange is stored in the registry of each Exchange server. The script below shows some techniques for accessing the registry to obtain string (reg_sz) and 32-bit short integer (reg_dword) values while using PowerShell. Note that PowerShell has multiple mechanisms for accessing this information, including the so-called Registry Provider. I personally find using the WMI functions to be a bit easier to handle.

You can include these functions directly into your PowerShell profile, or you can dot-source the function on an as-needed basis.

Without further ado, enjoy!

###
### Get-ExchangeVersion
###
### Return the version of the specified Exchange server
###
### 2013-04-11
###	Updated to support Exchange Server 2013 and E16
###

Set-StrictMode -Version 2.0

$HKCR = 2147483648
$HKCU = 2147483649
$HKLM = 2147483650

function RegRead
{
	Param(
		[string]$computer,
		[int64] $hive,
		[string]$keyName,
		[string]$valueName,
		[ref]   $value,
		[string]$type = 'reg_sz'
	)

	[string]$fn = "RegRead:" ## function name

	try {
		$wmi = [wmiclass]"\\$computer\root\default:StdRegProv"
		if( $wmi -eq $null )
		{
			return 1
		}
	}
	catch {
		$error[0]
		write-error "$fn Could not open WMI access to $computer"
		return 1
	}

	switch ( $type )
	{
		'reg_sz'
			{
				$r = $wmi.GetStringValue( $hive, $keyName, $valueName )
				$value.Value = $r.sValue
			}
		'reg_dword'
			{
				$r = $wmi.GetDWORDValue( $hive, $keyName, $valueName )
				$value.Value = $r.uValue
			}
		default
			{
				write-error "$fn Unsupported type: $type"
			}
	}

	$wmi = $null

	return $r.ReturnValue
}

function Get-ExchangeVersion
{
	Param(
		[string]$computer = '.'
	)

	[string]$fn = "Get-ExchangeVersion:" ## function name

	if( $computer -eq '.' -or [String]::IsNullOrEmpty( $computer ) )
	{
		$computer = $env:ComputerName
	}

	## Exchange E16 (assumption!)
	## HKLM\Software\Microsoft\ExchangeServer\v16\Setup
	## MsiProductMajor (DWORD 16)

	## Exchange 2013
	## HKLM\Software\Microsoft\ExchangeServer\v15\Setup
	## MsiProductMajor (DWORD 15)

	## Exchange 2010
	## HKLM\Software\Microsoft\ExchangeServer\v14\Setup
	## MsiProductMajor (DWORD 14)

	## Exchange 2007
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## MsiProductMajor (DWORD 8)

	## Exchange 2003
	## HKLM\SOFTWARE\Microsoft\Exchange\Setup
	## Services Version (DWORD 65)

	$v = 0

	$i = RegRead $computer $HKLM 'Software\Microsoft\ExchangeServer\v16\Setup' 'MsiProductMajor' ( [ref] $v ) 'reg_dword'
	if( ( $i -eq 0 ) -and ( $v -eq 16 ) )
	{
		$obj = "" | Select Name, Version
		$obj.Name = $computer
		$obj.Version = 'E16'
		return $obj
	}

	$i = RegRead $computer $HKLM 'Software\Microsoft\ExchangeServer\v15\Setup' 'MsiProductMajor' ( [ref] $v ) 'reg_dword'
	if( ( $i -eq 0 ) -and ( $v -eq 15 ) )
	{
		$obj = "" | Select Name, Version
		$obj.Name = $computer
		$obj.Version = '2013'
		return $obj
	}

	$i = RegRead $computer $HKLM 'Software\Microsoft\ExchangeServer\v14\Setup' 'MsiProductMajor' ( [ref] $v ) 'reg_dword'
	if( ( $i -eq 0 ) -and ( $v -eq 14 ) )
	{
		$obj = "" | Select Name, Version
		$obj.Name = $computer
		$obj.Version = '2010'
		return $obj
	}

	$i = RegRead $computer $HKLM 'Software\Microsoft\Exchange\Setup' 'MsiProductMajor' ( [ref] $v ) 'reg_dword'
	if( ( $i -eq 0 ) -and ( $v -eq 8 ) )
	{
		$obj = "" | Select Name, Version
		$obj.Name = $computer
		$obj.Version = '2007'
		return $obj
	}

	$i = RegRead $computer $HKLM 'Software\Microsoft\Exchange\Setup' 'Services Version' ( [ref] $v ) 'reg_dword'
	if( ( $i -eq 0 ) -and ( $v -eq 65 ) )
	{
		$obj = "" | Select Name, Version
		$obj.Name = $computer
		$obj.Version = '2003'
		return $obj
	}

	### almost certainly not an Exchange server

	return $null
}

Please follow me on Twitter, @essentialexch

Posted by michael | with no comments

Every major Exchange release comes with updates to the Active Directory schema. In this case, "major release" means new major version (at RTM), every service pack, and (probably) every Cumulative Update with the new servicing model introduced for Exchange 2013.

Each update is unique to that particular release and, in general, they are cumulative. A notable exception to this was when Exchange Server 2007 SP3 had a higher schema version than that of Exchange Server 2010 RTM.

Over the lifetime of modern Exchange (since the integration to Active Directory with Exchange 2000), there have been a number of issues making it important to know the current schema version of Exchange. Most instructions on the web suggest using ADSIEdit to examine the relevant variable and value.

However, that is potentially risky (because ADSIEdit can be a dangerous tool) and can be a little confusing to use.

Here is a quick little PowerShell script to report on the proper value:

$root  = [ADSI]"LDAP://RootDSE"
$name  = "CN=ms-Exch-Schema-Version-Pt," + $root.schemaNamingContext
$value = [ADSI]( "LDAP://" + $name )
"Exchange Schema Version = $( $value.rangeUpper )"

The ms-Exch-Schema-Version-Pt attribute is never assigned to a class in the schema, it is used exclusively to identify the value of the Exchange Schema Version.

To anyone who has used ADSI in PowerShell or VBScript before, the little four-line script will appear very familiar. The PowerShell ADSI accelerator syntax allows for the corresponding PowerShell script to be shorter than the equivalent VBScript script.

In order for this script to work, it must be executed on a computer joined to an Active Directory domain. The execution context for the script (that is, the user account) requires no special privileges.

Oh, and if you prefer PowerShell one-liners, here is the same script as a one-liner for you:

"Exchange Schema Version = " + ([ADSI]("LDAP://CN=ms-Exch-Schema-Version-Pt," + ([ADSI]"LDAP://RootDSE").schemaNamingContext)).rangeUpper

Follow me on Twitter @essentialexch

Posted by michael | with no comments

One of the features added to PowerShell v2 (and of course continued with PowerShell v3) is splatting. Without going into extreme detail (there are plenty of other blogs and books that do that), splatting allows you to pass parameters to a PowerShell cmdlet via an associative array (that is a fancy name for a hash table).

The hash table contains the parameter name as a key and the parameter's value as a value. The hash table contains two matched arrays, the keys array and the values array. For example, if a hash table contains three values, then accessing $hash.keys[ 1 ] will provide the name of a given hash entry and $hash.values[ 1 ] will provide the corresponding value for that entry.

The big deal behind splatting is that it allows you to easily construct custom parameter lists that get passed to cmdlets based on the special needs of a calling script. That sounds dense. :) But it is a good thing. For example, if you want to call a cmdlet with different parameters based on the time of day, or on a particular computer, or whether your script was started with a switch parameter, or whether a particular string parameter has a value - then splatting is for you.

Splatting has a somewhat hidden advantage. Switch parameters require special handling under normal circumstances. With splatting, no special handling is required. You set a switch to $true or $false, without using special spacing and/or a colon.

I use splatting extensively in scripts I have developed since PowerShell v2 was released. Today, my friend Carl Webster, the Accidental Citrix Admin, asked about a way to simplify calling cmdlets in certain of his scripts. My answer was "use splatting". After a couple of back-and-forth emails, about the best way to use splatting, I concluded that the easiest thing to do was to generalize a routine to provide splatting for a cmdlet and pass that over to Carl.

After further thought and evolution on the basic concept of the routine I sent to Carl, I decided it could be a great deal more powerful (get it - powerful vs. PowerShell? HAHAHAHA) if I added in some logging capabilities and provided for the suppression of empty parameters. Thus was born Invoke-Splat. I have already taken Invoke-Splat and used it to simplify several of my own scripts.

Invoke-Splat takes the cmdlet to invoke as a parameter, plus all of the parameters for that cmdlet, as "parameter value" pairs, plus a couple of optional switch parameters to control Invoke-Splat itself. Invoke-Splat is dependent on PowerShell's named parameter matching, combined with positional parameter matching, and that everything that does not meet either of those, is stored in the special argument $args.

The splatNotEmpty switch ensures that parameters whose values evaluate as either null or empty strings are not passed as parameters to the called cmdlet. This is especially important for parameters that cannot normally be null or empty!

The splatDebug switch causes the cmdlet and each of the "parameter value" pairs to be output to the calling host.

The splatCmdlet string parameter is (obviously) the name of the cmdlet that will be executed with the other parameters.

Note that the other parameters may be empty!

For example:

Invoke-Splat Get-Process ID $PID FileVersionInfo $false -splatDebug

and

Invoke-Splat Get-Process ID $PID Name '' -splatDebug -splatNotEmpty

Without further ado, here is Invoke-Splat.

function Invoke-Splat
{
	Param(
		[string]$splatCmdlet,
		[switch]$splatNotEmpty,
		[switch]$splatDebug
	)

	## $args[ 0 .. n ] are matching "parameter argument" pairs

	$splats = @{}
	for( $i = 0; $i –lt $args.Length; $i = $i + 2 )
	{
		if( $splatNotEmpty )
		{
			## don’t pass arguments with empty strings
			if( ( $args[ $i + 1 ] –as [string] ).Length –gt 0 )  
			{
				$splats.$( $args[ $i ] ) = $args[ $i + 1 ]
			}
		}
		else
		{
			$splats.$( $args[ $i ] ) = $args[ $i + 1 ]
		}
	}
	if( $splatDebug )
	{
		write-host -ForeGroundColor Yellow "DEBUG: Invoke-Splat: cmdlet = $splatCmdlet"
		foreach( $entry in $splats.Keys )
		{
			write-host -ForeGroundColor Yellow "DEBUG: Invoke-Splat: entry: $entry = $($splats.$entry)"
		}
	}

	& $splatCmdlet @splats
}

Please follow me on Twitter: @essentialexch.

Thanks for your visit!

Posted by michael | with no comments
Filed under: ,

Exchange Server 2013 reached RTM a couple of months ago and has since reached General Availability (GA).

In my personal opinion, Exchange 2013 RTM is not ready for prime time. Microsoft made a decision to release all of Wave 15 (Office desktop applications and servers) at the same time; as well as release Windows 8, Windows RT, and Windows Server 2012 at the same time. I think this decision was seriously flawed. It is obvious that the products were not complete at RTM (witness Windows 2012 and Windows 8 having 300 MB of patches between RTM and GA, and Exchange 2013 not supporting any interop with prior versions of Exchange at either RTM or GA). It is easy to conclude that the RTM dates were artificially imposed.

I have prepared a class on Exchange 2013 for one of my clients and part of that class was to discuss the limitations associated with Exchange 2013 RTM when compared to Exchange 2010 SP2. Note that the rest of the class discussed many of the new features and capabilities that have been added to Exchange 2013. So... the story is not all bad.

But as a summary of my opinion, Exchange 2013 RTM is not ready for prime time. Right now, it can only be installed in a green-field environment (that is, an environment where Exchange did not previously exist), so it is a safe bet that the Exchange team agrees with that as well. We can hope that some updates will quickly come out to address some of the current deficiencies.

This list is by no means exhaustive. And, as always, whether a particular issue is important to your organization requires you to evaluate your environment.

OWA

  • Help -> About is gone
  • It's very slow.
  • No S/MIME support
  • No Public Folder support, either for legacy public folders or modern public folders.
  • No distribution list moderation
  • No way to move the reading pane
  • Built-in spell-check is gone. IE 10 provides spell-check natively, but earlier versions of IE do not. A third-party add-in or an alternate browser is required.
  • Other things are gone; don't waste too much time looking for them. 

Client Connectivity

  • No BES support
  • ...on a related note (and likely the primary reason BES is not yet available), the CDO/MAPI download is not yet available for Exchange 2013.
  • Outlook 2003 is no longer supported.
  • Direct MAPI access to the Exchange server is no longer supported.  RPC/HTTP (Outlook Anywhere) is required.
  • Outlook now reports that the server is it connected to is <<guid>>@<<active-directory-domain>>. This is intentional, if misguided.

Installation and Architecture

  • Cannot uninstall individual roles from a server, must uninstall all of Exchange
  • Install is painfully slow
  • The Hub Transport role is gone. There is now a Front End Transport service on CAS servers and Mailbox Transport services on Mailbox servers.
  • The Unified Messaging role is gone. There is a now a Unified Messaging Call Router service on CAS servers and a Unified Messaging service on Mailbox servers.
  • The CAS consists of three pieces: CAFE' (Client Access Front End), which proxies all end-user protocols to the appropriate mailbox server (completing the decoupling of the MAPI endpoint started in Exchange 2010) and handles Outlook Web App; FET (Front End Transport) which proxies SMTP protocols to the mailbox server and is responsible for TLS setup; and Unified Messaging Call Router.
  • After an installation or an upgrade, services not starting is an endemic problem. You will likely need to increase ServicesPipeTimeout on your Exchange servers.
  • Documentation is minimal at best
  • Deployment and sizing guidance is non-existent.
  • Cannot be installed along with Exchange 2007 or Exchange 2010
  • Exchange 2013 Edge server is not available
  • Forefront Protection for Exchange is gone
  • For both Exchange 2010 and Exchange 2013, applying updates can often screw up the winrm configuration. If you get errors in EMS or EAC regarding "The WS-Management service cannot process the request", try this first:

      winrm quickconfig
      iisreset
  • Since you cannot interop with legacy public folders in RTM, if you need an Organizational Forms Library, you must create it yourself. To create an Organizational Forms Library:

      1. Create "Organizational Forms Library" folder under the Eforms Registry:

          New-publicfolder "Organizational Forms Library" -path "\non_ipm_subtree\Eforms Registry"

      2. Set the locale ID for the Org Forms Library:

      Set-PublicFolder "\non_ipm_subtree\Eforms Registry\Organizational Forms Library" -EformsLocaleID EN-US

    It is no longer necessary to set the PR_URL_NAME property.

Exchange Management

  • The Exchange Management Console is gone as is the Exchange Control Panel. They are mainly replaced by the Exchange Administration Center (EAC); which is completely web based.
  • If you are attempting to use EAC with IE 10, you need KB2761465 (released on December 11, 2012).
  • The Exchange Best Practices analyzer is no more.
  • The Exchange Mail Flow Troubleshooter is no more.
  • The Exchange Performance Troubleshooter is no more.
  • The Exchange Routing Log Viewer is no more.
  • The EAC does not provide a preview (or an after-view for that matter) of the PowerShell it executed.
  • Antispam and antimalware is crippled compared to earlier releases

      The E15 AV does not offer a quarantine
      The E15 AS does offer a quarantine (for the administrator, not per-user)
  • Antispam cannot be managed from the Exchange Administration Center; it must be managed using PowerShell in the Exchange Management Shell
  • Kerberos Constrained Delegation (KCD) is not supported for OWA
  • This isn't new, but should be reinforced: DO NOT TURN OFF IPV6. Microsoft does not perform any testing to determine the effects of disabling IPv6. Therefore, Microsoft recommends that you leave IPv6 enabled, even if you do not have an IPv6-enabled network, either native or tunneled. See http://technet.microsoft.com/en-us/network/cc987595.aspx.
  • System Center Data Protection Manager (DPM) version required for backups of Exchange 2013 is SC DPM 2012 SP1

Mailboxes and Databases

  • Mailbox sizes MAY appear to increase substantially when moving a mailbox to an Exchange 2013 mailbox server. In Exchange 2010 and before, only select properties of a particular mailbox item were assigned as part of the mailboxes diskspace allocation, causing under-reporting. Now, all item properties for a particular mailbox item are assigned to the mailboxes disk space allocation. However, some items in Exchange 2013 are now compressed which were not before. This can lead to a reduction in reported and allocated diskspace. So, prediction is basically impossible. Just be aware that it may happen.
  • Corrupt PropertyTags during a mailbox move are common. Using (Get-MoveRequestStatistics -IncludeReport <<alias-name>>).Report.Failures you can find the rule or message that is causing the problem and remove it.
  • Changes made to improve Office 365 and hybrid deployments had an unintended consequence (this is my conclusion). When you are performing impersonation (e.g., to open a different user's mailbox via EWS), you should always impersonate using the email address.
  • As a corollary, it is recommended that the account UPN match the primary email address.
  • In a change that you won't know about until you need to know it - MRS Proxy is not enabled by default in Exchange 2013. Use Set-WebServicesVirtualDirectory to enable it.
  • Clean-MailboxDatabase is gone

      Update-StoreMailboxState is designed to replace it
      Requires that you know the guid of the deleted mailbox
      No on-premises cmdlets allow you to find those out!
  • Get-LogonStatistics is non-operational. The cmdlet is still present, but it doesn't work.
  • Exchange 2013 Enterprise Edition supports only 50 mailbox databases instead of the 100 supported in Exchange 2010
  • MRM 1.0 (Messaging Record Management - Managed Folders) is non-operational on Exchange 2013 mailbox servers. The cmdlets are still present, and will affect down-level servers (which you can't use right now), but they don't work with Exchange 2013 servers.
  • Moving mailboxes using the migration wizard in EAC can generate large amounts of log files for the database which hosts the arbitration mailbox. Use New-MoveRequest instead.
  • In a positive change, Office Filter Packs are no longer required. There is a new search technology used in all Wave 15 (Office 2013) products and it knows how to search all the Office file formats. This also includes the PDF format, so a separate iFilter installation for PDF is no longer required.
  • When using Database Availability Groups (DAGs) on Windows Server 2012, you must manually create the Cluster Network Object (CNO) and provision the CNO by assigning permissions to it.
  • While Windows Server 2012 provides support for large sectors (4 KB), Exchange 2013 does not support large sectors. Emulation of large sectors (512 E) is supported provided that all database copies are on 512 E.
  • The above statement is, in general, true. Additional capabilities of Windows Server 2012 are not supported by Exchange Server 2013. This specifically includes but is not limited to Hyper-V Replica.

Good luck!

[Edit at 19:55 on 6-January-2013 to clarify why you may need an organizational forms library and to add the note regarding lack of spell-check in OWA (hat-tips to Ben Winzenz and Tim Robichaux).]

[Edit at 21:24 on 8-January-2013 to fix several grammar and spelling errors. Oops.]

Posted by michael | with no comments

In the last few days, Windows Management Framework 3.0 (WMF 3.0) has begun appearing in Microsoft Update (MU), Windows Update (WU), Windows Software Update Services (WSUS), and on Configuration Manager Software Update Points. This basically means that Microsoft is now suggesting that WMF 3.0 be installed on all of your servers where the update is applicable.

This update is released as KB 2506146 and KB 2506143.

DON'T DO IT.

WMF 3.0 includes PowerShell 3.0.

PowerShell 3.0 is a great improvement to PowerShell. No question about it.

However, Exchange 2010 is NOT currently qualified to work with PowerShell 3.0. And, in fact, it doesn't. It will break. PowerShell 3.0 compatibility will come with Exchange 2010 Service Pack 3, due sometime in the first half of calendar year 2013 (word on the street says first quarter).

If you have installed WMF 3.0, you will also find that Exchange Update Rollups will fail to install.

Exchange 2007 is also not qualified to work with PowerShell 3.0. And, as far as I know, never will be.

You absolutely, positively, do not want to install the update on your Exchange servers.

You also do not want to install the update on workstations or utility servers where you have Exchange Management Tools installed.

I have also heard reports that SharePoint 2010 also has problems with the WMF 3.0 release. I can believe it. You should avoid that as well.

Good luck!

P.S. Exchange 2013 does work with WMF 3.0 and in fact, WMF 3.0 is required to install Exchange 2013. If you are one of the rare few running Exchange 2013, you do not need to be concerned about this.

Wave 15 of Office products reached the Release To Manufacturing (RTM) stage today, October 11, 2012 (10/11/12 - heh).

These products include Exchange Server 2013, Lync Server 2013, SharePoint Server 2013, and Microsoft Office 2013.

Note that RTM, also known as "code-complete", means that development is done on the products and they will now be written to ISOs, DVDs burned, retail boxes built, etc.

This does NOT mean that the releases are currently available. As of today, releases are still only available to TAP and RDP participants.

General Availability (GA) defines the timeframe when the releases will be available to everyone. This is targeted for first quarter calendar-year 2013 for all the Wave 15 products.

However, volume license customers will be able to download these products on the Volume Licensing Service Center (VLSC) by mid-November and the products will be on the VL price lists starting December 1, 2012.

Specifically for Exchange, the build number for the RTM release is 15.0.516.32. Exchange 2013 was code-named E15 throughout its development cycle.

For your information, "Wave 15" refers to the fact that version numbers of all Office product lines have been synchronized. The major version number for all products is "15".

For more information about Exchange, refer to the Exchangae team blog: The New Exchange Reaches RTM!

For more information about the other Office products, refer to the Office blog: Office Reaches RTM.

It is interesting to note that both releases refer to "the New Exchange" and "the new Office" - taking a cue from Apple, I presume; in not tying the announcement to a specific release of the product.

 

Posted by michael | with no comments

Today Microsoft released Security Advisory 2749655, Compatibility Issues Affecting Signed Microsoft Binaries. I encourage you to read the security advisory. We also saw that Exchange team post the following blog entry Re-released Exchange 2010 and Exchange 2007 update rollups. Microsoft re-released the rollups in order to ensure that third party programs are not impacted by the security advisory.

That is, Microsoft re-released the updates as a pre-emptive measure - just in case.

If you have installed the security advisory, you really don't need to re-install the updates. Quoting from the security advisory:

What does this update do?
This update will help to ensure the continued functionality of all software that was signed with a specific certificate that did not use a timestamp Enhanced Key Usage (EKU) extension. To extend their functionality, WinVerifyTrust will ignore the lack of a timestamp EKU for these specific X.509 signatures.
......

Note regarding the impact of not installing a rereleased update
Customers who installed the original updates are protected from the vulnerabilities addressed by the updates. However, because improperly signed files, such as executable images, would not be considered correctly signed after the expiration of the CodeSign certificate used in the signing process of the original updates, Microsoft Update may not install some security updates after the expiration date. Other effects include, for example, that an application installer may display an error message. Third-party application whitelisting solutions may also be impacted. Installing the rereleased updates remediates the issue for the affected updates.

Long story short - Microsoft has released the updates "just in case".

Given that Exchange 2010 SP2 Update Rollups can take upwards of an hour to install and your Exchange server is offline while you doing the installation - you may just want to wait for the next UR.

Please note: the Exchange 2010 SP2 UR4 re-release does include one new patch. You can install that separately in a very short timeframe: Outlook only returns one result after you click the "Your search returned a large number of results. Narrow your search, or click here to view all results" message.

Posted by michael | with no comments
Filed under: ,

Well, in case you haven’t seen it, an Exchange Security advisory was released today.

“Vulnerabilities in Microsoft Exchange and FAST Search Server 2010 for SharePoint Parsing Could Allow Remote Code Execution”

http://technet.microsoft.com/en-us/security/advisory/2737111

And yes, it also affects the Exchange 2013 Preview.

It wasn’t immediately obvious to me, but every Exchange CAS/CAFE has these libraries installed. Microsoft licenses them from Oracle.

I’m guessing that that will change the workaround to:

Get-OwaVirtualDirectory |? {
$_.OwaVersion -eq 'Exchange2007' -or $_.OwaVersion -eq 'Exchange2010' –or $_.OwaVersion –eq 'Exchange2013' } |
Set-OwaVirtualDirectory -WebReadyDocumentViewingOnPublicComputersEnabled:$False `
    -WebReadyDocumentViewingOnPrivateComputersEnabled:$False

But I’m on the road and don’t have access to my E15 test lab at the moment…

Thanks to Susan Bradley, the SBS Diva, for pointing out to me that this doesn't require third party add-ins.

[Edit]

More information:

"In Microsoft Exchange Server 2007 and Exchange Server 2010, Outlook Web App (OWA) users are provided with a feature called WebReady Document Viewing that allows users to view certain attachments as a web page instead of relying on local applications to open/view it. Oracle Outside In is used by the conversion process in the server backend to support the WebReady feature. Microsoft licenses this library from Oracle."

In the Exchange Server 2007/2010 scenario, the conversion process that uses Oracle Outside In, TranscodingService.exe, runs as LocalService.

http://blogs.technet.com/b/srd/archive/2012/07/24/more-information-on-security-advisory-2737111.aspx

Posted by michael | with no comments

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.

Posted by michael | with no comments

I do bit shifting in PowerShell all the time. It's quite necessary when you are working with option values in Active Directory, WMI, and various Win32 interfaces. .NET tends to use enumerations, which are tad easier to deal with in PowerShell.

I had never felt it necessary to break my routines out before, but I saw a posting on this topic by another individual today and I just thought he was making it harder than it had to be. I wanted to present another option to the community for these types of routines. I'm not putting down his work in any way - it was quite ingenious. I never would've thought of doing it that way.

Just FYI, 'shr' is an abbreviation for 'shift right' and correspondingly 'shl' is an abbreviation for 'shift left'. When I was in college (in the stone age) those function names were used in "Pascal extension libraries" and in "Fortran libraries" for performing shift operations. Shift operations are built-in as native operations to many (most?) compiled languages such as C and its various dialects.

Enjoy.

###
### Bit-shift operations
### bit-shift.ps1
###
### for bytes and short integers
###
### Michael B. Smith
### michael at TheEssentialExchange.com
### May, 2012
###

$bitsPerByte = 8	## [byte]
$bitsperWord = 16	## [int16] or [uint16]

function shift-left( [int]$valuesize, [int]$mask, $val, [int]$bits )
{
	if( $bits -ge $valuesize )
	{
		return 0
	}
	if( $bits -eq 0 )
	{
		return $val
	}
	if( $bits -lt 0 )
	{
		write-error "Can't shift by a negative value of bits"
		return -1
	}

	### it's possible to write this so that you never
	### overshift and generate an overflow. it's easier
	### to use a larger variable and mask at the end.

	[int]$result = $val
	for( $i = 0; $i -lt $bits; $i++ )
	{
		$result *= 2
	}

	return ( $result -band $mask )
}

function shift-right( [int]$valuesize, [int]$mask, $val, [int]$bits )
{
	if( $bits -ge $valuesize )
	{
		return 0
	}
	if( $bits -eq 0 )
	{
		return $val
	}
	if( $bits -lt 0 )
	{
		write-error "Can't shift by a negative value of bits"
		return -1
	}

	for( $i = 0; $i -lt $bits; $i++ )
	{
		## normally PowerShell does banker's rounding (well, .NET does)
		## we have to override that here to get true integer division.
		$val = [Math]::Floor( $val / 2 )
	}

	return $val
}

function shl-byte( [byte]$val, [int]$bits )
{
	$result = shift-left $bitsPerByte 0xff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [byte]$result )
}

function shr-byte( [byte]$val, [int]$bits )
{
	$result = shift-right $bitsPerByte 0xff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [byte]$result )
}

function shl-word( [uint16]$val, [int]$bits )
{
	$result = shift-left $bitsPerWord 0xffff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [uint16]$result )
}

function shr-word( [uint16]$val, [int]$bits )
{
	$result = shift-right $bitsPerWord 0xffff $val $bits
	if( $result -lt 0 )
	{
		return $result
	}

	return ( [uint16]$result )
}

function shl( $val, [int]$bits )
{
	if( $val -is [byte] )
	{
		return ( shl-byte $val $bits )
	}
	elseif( $val -is [int16] )
	{
		return [int16]( shl-word $val $bits )
	}
	elseif( $val -is [uint16] )
	{
		return ( shl-word $val $bits )
	}
	elseif( ( $val -lt 65536 ) -and ( $val -ge 0 ) ) ### pretend it's uint16
	{
		return ( shl-word ( [uint16]$val ) $bits )
	}

	write-error "value is an invalid type"
	return -1
}

function shr( $val, [int]$bits )
{
	if( $val -is [byte] )
	{
		return ( shr-byte $val $bits )
	}
	elseif( $val -is [int16] )
	{
		return [int16]( shr-word $val $bits )
	}
	elseif( $val -is [uint16] )
	{
		return ( shr-word $val $bits )
	}
	elseif( ( $val -lt 65536 ) -and ( $val -ge 0 ) ) ### pretend it's uint16
	{
		return ( shr-word ( [uint16]$val ) $bits )
	}

	write-error "value is an invalid type"
	return -1
}
Posted by michael | with no comments
Filed under: ,
More Posts Next page »