Script Updates

powershellA small heads-up for those not following me on Twitter of one of the other social media channels. Last week I made updates to the following three scripts:

Install-Exchange2013.ps1, version 1.72

  • Added CU5 support
  • Added KB2971467 (CU5 Disable Shared Cache Service Managed Availability probes)

Remove-DuplicateItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well.
  • Added parameter Credentials.
  • Added item class and size for certain duplication checks.
  • Changed item removal process
  • Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.

Remove-MessageClassItems.ps1, version 1.3

  • Changed parameter Mailbox, you can now use an e-mail address as well
  • Added parameter Credentials
  • Added parameter PartialMatching for partial class name matching.
  • Changed item removal process. Remove items after, not while processing folder. Avoids asynchronous deletion issues.
  • Works against Office 365.
  • Deleted Items folder will be processed, unless MoveToDeletedItems is used.
  • Changed EWS DLL loading, can now be in current folder as well.

Be advised I keep am overview of the scripts and their current versions with publish dates here.

 

Jetstress 2013

Ex2013 LogoIn the list of expected products to accompany Exchange 2013, Microsoft today released JetStress 2013, version 15.0.651.0.

Jetstress is your tool of choice to check the performance and stability of your storage design under load. It simulates Exchange I/O behaviour using Exchange 2013 patterns allowing you to verify dimensioning and validate performance expectations from a database perspective.

Capture

To run JetStress 2013 you need:

  • .NET Framework 4.5
  • ESE.DLL, ESEPerf.dll, ESEPerf.ini, ESEPerf.hxx (copy these from an Exchange 2013 installation source)

Note: The installer currently installs a shortcut named “Exchange JetStress 2010″, but it really is JetStress 2013.

You can download the Jetstress 2013 here.

NGN Exchange Event, Tips & Tricks Presentation

On October 31st, the NGN – a Dutch society for IT professionals – held its 3rd Exchange themed event, this time at The Reehorst in Ede (NL). Because of the recently released Exchange 2013 and all the news and related questions, we planned for a whole day of sessions and it was nice to see the turn up was nearly 100 IT professionals.

Since all people would still be on pre-2013 versions of Exchange, I figured a presentation using real-world Exchange 2010 Tips and Tricks might be more appropriate. I was glad a quick poll amongst the attendees showed a significant increase in Exchange 2010 deployments (around 80%) when compared to last year’s event, but as expected there’s still some Exchange 2007 and few Exchange 2003 out there.

I decided to stick with two deep-dive topics, which were Message Trackings Logs and Cmdlet Extension Agents. On those topics I went from basics to more advanced examples, hoping it would ignite people with no experience and people with experience could still pick up a thing or two.I’m still waiting for evaluation results, the only way to get feedback from these sessions apart from the occasional e-mail or tweet.

(picture by Dave Stork)

You can find my presentation here (partially Dutch) and the accompanying sample script on Message Tracking Logs here and the one on Cmdlet Extension Agents here (script); the ScriptingAgent.xml file can be downloaded here.

As always, these events are also a time to catch up with fellow Exchange people and discuss topics with attendees during the breaks. There were even Exchange fellows present who didn’t have a session, like Johan Veldhuis (MVP) and Maarten Piederiet (MCM); they did join in on the Q&A Panel.

The sessions and speakers were:

  • Introduction (Jaap Wesselius, MVP)
  • Building with Exchange 2013: Architecture (Dave Stork)
  • Exchange and Virtualisation (Jetze Mellema)
  • Exchange 2010 Tips & Tricks (Ashley Flentge, MCM & Michel de Rooij)
  • Exchange 2013 Coexistence and Migrations (Kay Sellenrode, MCM and MCA)
  • Exchange and Load Balancing (Jetze Mellema)
  • Q&A Panel

The NGN published all presentations in a single ZIP file which can be downloaded here. Unfortunately, NGN didn’t record the sessions so I can’t share those with you. They did record the Q&A Panel session; you can view it here (in Dutch):


PS: When you see references to “exchangedag”, like in the Twitter hashtag, you need to know “dag” means day in Dutch; it’s no form of professional deformation.

Adding Exchange Shell items to PowerShell ISE

I’ve become a fan of using the PowerShell Integrated Scripting Environment (PowerShell ISE) for creating, testing and debugging scripts, using breakpoints and step-by-step execution; features found in many development environments. Depending on the script I’m working on and for what customer or environment, I may need to add snap-ins or switch contexts, like connecting to Exchange Online.

One of the powerful features of ISE is that it allows customizing through the ISE object model. For example, you can explore ISE through the $psise object:

image

To add custom menu options to ISE, we’re going to add items to the submenu of $psISE.CurrentPowerShellTab.AddOnsMenu, which is “Add-ons”. An item needs to consist of:

  • Display Name, which is used for displaying the menu item;
  • Action, which can be a PowerShell cmdlet, scriptblock or function;
  • Optionally, you can assign a keyboard shortcut to the menu option.

To automatically load the custom entries after starting up ISE, we’re going to define the entries in our default ISE profile file, Microsoft.PowerShellISE_profile.ps1, which location is stored in $profile. The file doesn’t exist by default, so when required you can simply create the file in the proper location using notepad $profile.

In our example, we’ll add three entries:

  • Implicit Remoting to connect to Exchange using a static FQDN;
  • Loading the Exchange 2010 Snap-in and connecting to Exchange using Autodiscover (unsupported, will bypass RBAC);
  • Connecting to Exchange Online.

Note that the example won’t be using stored credentials and will let ISE prompt the user for credentials when required, which is perfectly fine if you need to access different Office 365 tenants for example.

Now, in the Microsoft.PowerShellISE_profile.ps1 file, add the following contents:

$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
    "Connect to Exchange @ Contoso", {
        $ExSession= New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exserver.contoso.com/PowerShell/ -Authentication Kerberos
        Import-PSSession $ExSession
    },
    "Control+Alt+1"
)
$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
    "Connect to Exchange On-Premise", {
        Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
        . $env:ExchangeInstallPath\bin\RemoteExchange.ps1
        Connect-ExchangeServer –auto
            },
    "Control+Alt+2"
)
$psISE.CurrentPowerShellTab.AddOnsMenu.SubMenus.Add(
    "Connect to Exchange Online", {
        $o365Cred= Get-Credential
        $o365Session= New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $o365Cred -Authentication Basic -AllowRedirection
        Import-PSSession $o365Session
    },
    "Control+Alt+3"
)

After starting ISE you’ll see the Add-ons menu now contains three extra items:

image

When selecting “Connect to Exchange Online” (or pressing the configured keyboard shortcut), ISE will execute the associated code block; the progress is displayed in the output window. You will be prompted for credentials after which ISE will connect to Exchange Online and import the remote session.

image

After the session has been imported, you have the additional commands at your disposal in ISE and you can work on your scripts since they’ll be running in the context of the environment you’ve connected to.

Of course, this is just an example of what you can customize in ISE (pink background anyone?). For more information on customizing PowerShell ISE check here. If you’re new to PowerShell ISE, check here.

Retrieving DCs functional capabilities

While constructing a page for the forest and domain functional levels, and the maximum functional level for domain controllers, I wanted to show an example of how to retrieve this Active Directory attribute for all domain controllers. You could for example incorporate this in your automated procedures to check for any Windows 2003 servers and take further actions when needed.

The script is below. It outputs object so you use the pipe for further processing. For information on the possible values for msDS-Behavior-Version check out the new AD Functional Levels page here.

#--------------------------------------------------------------------------------
# Name         : Get-DCMSDSBehaviorVersion.ps1
# Created By   : Michel de Rooij
# E-mail       : michel@eightwone.com
# Date         : 20120307
# Source       : http://eightwone.com
# Version      : 1.0
#
# THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE RISK
# OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
#--------------------------------------------------------------------------------

$RootDSE= [ADSI]'LDAP://RootDSE'
$objSearchRoot= New-Object DirectoryServices.DirectoryEntry( "LDAP://"+ $RootDSE.configurationNamingContext)
$objSearch= New-Object DirectoryServices.DirectorySearcher
$objSearch.SearchRoot= $objSearchRoot
$objSearch.Filter= "(objectClass=nTDSDSA)"
$objSearch.SearchScope = "SubTree"
$Results= @()
$objSearch.FindAll() | ForEach {
    $objItem = $_.getDirectoryEntry()
    $obj= New-Object PSObject -Property @{
        Servername= $objParent = $objItem.psbase.parent.DNSHostName.ToString()
        MSDSBehaviorVersion= $objItem.Get("MSDS-Behavior-Version")
    }
    $Results+= $obj
}
$Results

Note that if you have over 1000 domain controllers, you need to set the $objSearch.Pagesize, e.g. 1000. It may also encounter problems in forests with multiple roots.

Exchange PST Capture Tool released

It took a while, but today the Exchange Team released the long awaited Microsoft Exchange PST Capture Tool (initial version 14.3.16.4). The tool can be used to discover and inject PST files in an Exchange 2010 Exchange Online mailbox or archive.

The tool was originally from Red Gate and known as PST Importer. It’s architecture consists of three components: the central service, (optional) agents for PST discovery, registration and collecting PST files and an administrative console (image by Red Gate):

The online documentation can be found here.

Note that although it’s only supported for Exchange 2010 and Exchange Online, you can use it with Exchange 2007; it’s only untested (and probably unsupported) with that product.

You can read the official announcement here; you can download the tool and the agents here.

Exchange Environment Report

A quick post on Exchange fellow Steve Goodman who created a nice PowerShell script which generates a basic HTML report on your Exchange environment. When required, you can also e-mail the report, which is nice if you want to schedule the script to run on a daily basis for example.

The script is provided as-is so you can tailor it to your needs. It’s still work in progress, so if you got any requests just send Steve a message.

You can find the post and script here.

Bulk configuring & enabling OCS users

Not Exchange related, but something I’d like to share with you is a script to bulk configure and enable users for OCS with enterprise voice. In the process, the telephone numbers are also changed, since often the customer is moving to a new range of numbers as well.

The information was provided by the customer in an Excel sheet, which I exported to a CSV file. Since the script was to be run on an Windows Server 2003 box, I opted for a simple VB script, which you can find below.

A short explanation:

  • When the telephone number has a value, the user is configured for enterprise voice (intOptionFlags 896). If the telephone number is empty, the user is configured for IM and presence (intOptionFlags 256);
  • You can expand the sheet (CSV) with extra columns. When you need to create colums before the current ones, don’t forget to modify the index of the arrFields() references accordingly;
  • Change the OCSHomeServer to the proper pool value;
  • Change the OCSLocationProfile to the proper value;
  • Change the OCSPhoneContext to the proper value;
  • If you want to see what it will do first, set TestMode to True;
  • Use it in a lab environment first; test, test, test!

Note: If you have problems finding out the value of the OCSHomeServer, OCSLocationProfile or OCSPhoneContext settings, configure one user with the proper settings using ADUC, and inspect the values of those settings by using ADSIEdit or LDP.

users.csv

name;samaccountname;telephonenumber
Francis Blake;francis.blake;+31 (0) 30 123 45 11
Philip Mortimer;philip.mortimer;+31 (0) 30 123 45 22

OCSEnableUsers.vbs

'*--------------------------------------------------------------------------------
'* Name         : OCSEnableUsers
'* Created By   : Michel de Rooij
'* E-mail       : michel@eightwone.com
'* Date         : 20101118
'* Version      : 0.1
'*--------------------------------------------------------------------------------
'* Changes:
'* 0.1 Initial version
'*--------------------------------------------------------------------------------

On Error Resume Next

dim oConn, strQry, rs, objUser, strVal, objFSO, objFile, strLine, arrFields, i, line
dim strSAM, strTel
dim strServerURI, strLineURI, strSIP, strExtension, intOptionFlags

Const ADS_PROPERTY_APPEND = 3

Const OCSHomeServer     = "CN=LC Services,CN=Microsoft,CN=OCSPOOL1,CN=Pools,CN=RTC Service,CN=Services,CN=Configuration,DC=contoso,DC=com"
Const OCSLocationProfile= "CN={820ADF85-B64C-4F32-92F0-E4AA37267677},CN=Location Profiles,CN=RTC Service,CN=Services,CN=Configuration,DC=contoso,DC=com"
Const OCSPhoneContext   = "L0CDP.L1UDP@OCS2.contoso.com"

Const TestMode        = False

set oConn= createObject("Adodb.Connection")
oConn.provider = "AdsDSOObject"
oConn.open "ADs Provider"

set objFSO= createObject("Scripting.FileSystemObject")
set objFile= objFSO.OpenTextFile("users.csv", 1, True)

wscript.echo "RUNNING IN TESTMODE IS "& TestMode

line= 1
while not objFile.AtEndOfStream
 strLine= trim(objFile.readline)
 if Line> 1 Then
 arrFields= split(strLine, ";")
 strSAM= arrFields(1)
 strTel= normalizePhone( arrFields(2))
 strTelNew= replace( replace( arrFields(3), "(0)", ""), "  ", " ")

 wscript.echo strSAM&" "& strTel
 strQry= "<LDAP://dc=contoso,dc=com>;(samAccountName="& strSAM& ");adspath;subtree"
 set rs= oConn.execute( strQry)
 if rs.recordCount > 0 Then
 while not rs.EOF
 set objUser= getObject( rs.fields(0).value)
 wscript.echo "User found: "& objUser.distinguishedName
 wscript.echo "Previous Phone No: "& objUser.TelephoneNumber

 strSIP= "sip:"& objUser.mail
 strExtension= right( strTel, 3)
 strLineURI= "tel:"& strTel& ";ext="& strExtension
 strServerURI= "sip:"& strExtension& ";phone-context="& OCSPhoneContext

 If strTel= "" Then
 intOptionFlags= 256
 Else
 setAttr objUser, "msRTCSIP-Line", strLineURI
 setAttr objUser, "msRTCSIP-LineServer", strServerURI
 intOptionFlags= 896
 End If

' Set AD fields
 setAttr objUser, "telephoneNumber", strTelNew

 ' Set OCS props
 setAttr objUser, "msRTCSIP-UserEnabled", True
 setAttr objUser, "msRTCSIP-PrimaryHomeServer", OCSHomeServer
 setAttr objUser, "msRTCSIP-PrimaryUserAddress", strSIP
 setAttr objUser, "msRTCSIP-UserLocationProfile", OCSLocationProfile
 setAttr objUser, "msRTCSIP-OptionFlags", intOptionFlags

 addAttr objUser, "proxyAddresses", strSIP

 If Not TestMode Then
 objUser.SetInfo
 End If

 rs.moveNext

 wend
 Else
 wscript.echo "*** WARN: User not found in AD: "& strSAM
 End If

 Else
 ' Skip header
 End If
 line= line+ 1
wend 

objFile.close
set objFSO= Nothing

Function setAttr( objUser, strAttr, strVal)
 wscript.echo "Setting "& strAttr& " to "& strVal
 If TestMode Then
 ' ...
 Else
 objUser.put strAttr, strVal
 End If
End Function

Function addAttr( objUser, strAttr, strVal)
 wscript.echo "Adding "& strVal& " to "& strAttr
 If TestMode Then
 ' ...
 Else
 objUser.PutEx ADS_PROPERTY_APPEND, strAttr, array(strVal)
 End If
End Function

Function NormalizePhone( Tel)
 NormalizePhone= replace( replace( tel, " ", ""), "(0)", "")
End Function

JetStress updated

A quick note to inform you the the JetStress tool has been updated to version 14.1.225.17.

The online documentation on TechNet is currently being revised (article 706601). Currently on the following sentence has been removed from the article:

“You should test Jetstress 2010 with Exchange 2010 ESE binaries, and use Jetstress 2007 for testing legacy ESE versions of Exchange 2007 and Exchange 2003″

The JetStress tool can be used to simulate disk I/O load on a test server running Exchange to test and validate performance and stability of the disk subsystem, prior to moving them into a production environment.

The toolkit page has been updated accordingly.

You can download it here; x86 version is located here.

Retrieving Exchange version information

At some time you may want to create an overview of the current Exchange servers in your organisation and the current product levels. The attribute you initially might look at is AdminDisplayVersion, but unfortunately AdminDisplayVersion doesn’t reflect installed roll-ups.

The location that does contain update information is in the registry, more specific the installer subkey related to the installed product. The exact key you should be looking for is depends on whether Exchange Server 2007 or Exchange 2010 is installed. The path is HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$PRODID\Patches\, where $PRODID is:

  • 461C2B4266EDEF444B864AD6D9E5B613 for Exchange 2007;
  • AE1D439464EB1B8488741FFA028E291C for Exchange 2010 or Exchange 2013;

Here subkeys may exist for each applied roll-up.

Looking at the DisplayName we see it contains a full description of the roll-up, prepended with the related Exchange version. Distilling that information using a Powershell script should provide us with the required information.

Below you will find the script, Get-ExchangeVersions.ps1. When running the script, it will show all Exchange 2007 ( v8), Exchange 2010 (v14) and Exchange 2013 (v15) servers with version information, but it will skip Edge server (due to potential firewall issues) or ProvisionedServer (‘server’ is a placeholder).

The output ($output) is sent to the console. You can easily make the script report to a CSV file by removing the comment in front of the line containing the export-CSV cmdlet. The output of Get-ExchangeVersions.ps1 looks something like this:

Feedback
Feedback is welcomed through the comments. If you got scripting suggestions or questions, do not hesitate using the contact form.

Download
You can download the script from the TechNet Gallery here.

Revision History
1.0: Initial release
1.1: Added support for Exchange Server 2013, renamed script to Get-ExchangeVersions.ps1
1.11: Small typo fix
1.2: Added connectivity test, fixed patchless Exchange 2010 output issue