Use Remote Powershell to install Exchange 2013 Prereqs on Server 2012

So I’ve been working with Exchange 2013 for a little while now, and figured I’d share a nice little shortcut to installing the Exchange 2013 PreReqs on multiple servers remotely. Keep in mind if you are not using Windows Server 2012 you will need to configure remote powershell and substitute the windows features cmdlet as necessary the following link can be used to determine the appropriate scriptblocks:

http://technet.microsoft.com/en-us/library/bb691354.aspx

so back to the main point:

# Install Pre-Requisites on all CAS (replace CAS1,CAS2 with your CAS Server names)
$session = New-PSSession -cn CAS1,CAS2 
Invoke-Command -Session $session -ScriptBlock {Install-WindowsFeature AS-HTTP-Activation, Desktop-Experience, NET-Framework-45-Features, RPC-over-HTTP-proxy, RSAT-Clustering, Web-Mgmt-Console, WAS-Process-Model, Web-Asp-Net45, Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, Web-Http-Logging, Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Lgcy-Mgmt-Console, Web-Metabase, Web-Mgmt-Console, Web-Mgmt-Service, Web-Net-Ext45, Web-Request-Monitor, Web-Server, Web-Stat-Compression, Web-Static-Content, Web-Windows-Auth, Web-WMI, Windows-Identity-Foundation -restart}
# Install Pre-requisites on all MBX (replace MBX1,MBX2 with your MBX Server names
$session = New-PSSession -cn MBX1,MBX2 
Invoke-Command -Session $session -ScriptBlock {Install-WindowsFeature AS-HTTP-Activation, Desktop-Experience, NET-Framework-45-Features, RPC-over-HTTP-proxy, RSAT-Clustering, RSAT-Clustering-CmdInterface, Web-Mgmt-Console, WAS-Process-Model, Web-Asp-Net45, Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, Web-Http-Logging, Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Lgcy-Mgmt-Console, Web-Metabase, Web-Mgmt-Console, Web-Mgmt-Service, Web-Net-Ext45, Web-Request-Monitor, Web-Server, Web-Stat-Compression, Web-Static-Content, Web-Windows-Auth, Web-WMI, Windows-Identity-Foundation -restart}

If you need to use alternate credentials its as simple as creating a PSCredential object in adding it to the session:

$cred = get-credential
 $session = New-PSSession -cn abc,def -credential $cred
Advertisements

Leave a comment

Script to make UPN match email in On Premises and Office 365

I was recently performing an Office 365 migration from On-Premises Exchange and needed to change the UPN’s to match the email address.

One would assume that Dirsync would update the Office 365 environment for unlicensed users, but for whatever reason in this environment this proccess does not work. After spending some time on the phone with Microsoft Support it was determined it would be best to do one of the following:

1. Use the SetUPN VBS script to change the UPN’s locally in AD then Disable and Enable Dirsync (potentially taking up to 72 hours where no migrations or testing would be done)
2. Write a script to change the UPN in both local AD and Office 365

I chose to go with the second path to avoid the extra time. I was not able to find any current scripts out there to do this so I wrote one from scratch to handle it, sharing this for anyone else who may end up in the same boat. The script is powershell based and you will need to update a few portions to match your environment. As always USE AT YOUR OWN RISK and Test in a lab first.

 

# *************************************************************************************************
# * Script to change UPN to match email address for both On Premises AD and Microsoft Online *
# * Prior to running this make sure you have added the UPN suffixes to your local active director *
# * Author: Bryan Sprowls (bsprowls at gmail.com) *
# * Author Date: 8/24/2012 *
# * Logging functions by Axel Kara *
# *************************************************************************************************

Import-Module msonline

$LogFile = “C:\UPNTool\log.log”

$o365Cred = Get-Credential

Connect-MsolService -credential $o365Cred

# Edit this line to match your organization including the @
$CloudDomain = “@domain.onmicrosoft.com”

# Edit this line to match your organization
$FederatedDomains = “Domain 1.com”, “Domain 2.com”, “Domain 3.com”, “domain 4.net”

# LDAP query for users with mail attribute set
$SrchrFilter = “(&(objectclass=user)(mail=*))”

# Setup our directory search and base on RootDSE
$Srchr = New-Object system.DirectoryServices.DirectorySearcher(“LDAP://RootDSE”)
$Srchr.PageSize = 1000
$Srchr.Filter = $SrchrFilter
$Srchr.SearchScope = “Subtree”

$props = “name”, “distinguishedname”, “mail”, “userprincipalname”

foreach ($prop in $props) {$Srchr.PropertiesToLoad.Add($prop) | Out-Null}

# Execute our search
$SrchrResults = $Srchr.FindAll()

foreach ($Result in $SrchrResults) {

$ADUser = $Result.Properties

$UserDN = $ADUser.distinguishedname

# Dont do this for users in an OU for disabled users – this can be removed if not needed for your environment
if ($UserDN -notlike “*Disabled*”) {

$OldUPN = [string]$ADUser.userprincipalname
$NewUPN = [string]$ADUser.mail
$InterumUPN = $NewUPN.Split(“@”)[0] + $CloudDomain
$NewUPNDomain = $NewUPN.split(“@”)[1]

# only run the changes if the UPN domain is a federated domain
if ($FederatedDomains -contains $NewUPNDomain){

try {

Write-Host $InterumUPN
Write-Host $NewUPNDomain

# change UPN in AD
$user = system.DirectoryServices.DirectoryEntry(“LDAP://$UserDN”)
$user.psbase.invokeset(‘userprincipalname’,$NewUPN)
$user.psbase.commitchanges()

# Change office 365 old UPN to interum UPN
Set-MsolUserPrincipalName -UserPrincipalName $OldUPN -NewUserPrincipalName $InterumUPN

# Wait a second for execution
Start-Sleep -Seconds 1

# Change office 365 interum UPN to new UPN
Set-MsolUserPrincipalName -UserPrincipalName $InterumUPN -NewUserPrincipalName $NewUPN

}
Catch {

write-log -msg $_

}
}

}

}

function Write-Log($Msg, [System.Boolean]$LogTime=$true){
#
# .SYNOPSIS
# Creates a log entry
# .DESCRIPTION
# By default a time stamp will be logged too. This can be
# disabled with the -LogTime $false parameter
# .NOTES
# Author: Axel Kara, axel.kara@gmx.de
# .EXAMPLE
# Write-Log -Msg ‘Log entry created successfull.’ [-LogTime $false]
#
if($LogTime){
$date = Get-Date -format dd.MM.yyyy
$time = Get-Date -format HH:mm:ss
Add-Content -Path $LogFile -Value ($date + ” ” + $time + ” ” + $Msg)
}
else{
Add-Content -Path $LogFile -Value $Msg
}
}

function Initialize-LogFile($File, [System.Boolean]$reset=$false){
#
# .SYNOPSIS
# Initializes the log file
# .DESCRIPTION
# Creates the log file header
# Creates the folder structure on local drives if necessary
# Resets existing log if used with -reset $true
# .NOTES
# Author: Axel Kara, axel.kara@gmx.de
# .EXAMPLE
# Initialize-LogFile -File ‘C:\Logging\events.log’ [-reset $true]
#
try{
#Check if file exists
if(Test-Path -Path $File){
#Check if file should be reset
if($reset){
Clear-Content $File -ErrorAction SilentlyContinue
}
}
else{
#Check if file is a local file
if($File.Substring(1,1) -eq ‘:’){
#Check if drive exists
$driveInfo = [System.IO.DriveInfo]($File)
if($driveInfo.IsReady -eq $false){
Write-Log -Msg ($driveInfo.Name + ” not ready.”)
}

#Create folder structure if necessary
$Dir = [System.IO.Path]::GetDirectoryName($File)
if(([System.IO.Directory]::Exists($Dir)) -eq $false){
$objDir = [System.IO.Directory]::CreateDirectory($Dir)
Write-Log -Msg ($Dir + ” created.”)
}
}
}
#Write header
Write-Log -LogTime $false -Msg $LogSeparator
Write-Log -LogTime $false -Msg (((Get-ScriptName).PadRight($LogSeparator.Length – (” Version ” + $Version).Length,” “)) + ” Version ” + $Version)
Write-Log -LogTime $false -Msg $LogSeparator
}
catch{
Write-Log -Msg $_
}
}

 

Leave a comment

Script to balance exchange 2010 mailboxes across databases

I’ve modified a script that I used quite frequently for exchange 2007 to load balance mailboxes across databases to work on exchange 2010. Figured i would share the script

I plan to add the following enhancements so keep your eyes pealed for updates:

1. Add some logic to determining what mailbox to move as opposed to a random selection

2. ability to exclude databases


# Based on DBBalancer script originally written by BoerLowie (sammyboggmail.com)
# Rewritten to work on Exchange 2010 by Bryan Sprowls (bsprowlsgmail.com)
#
# This script is used to maintain a balance between your different Exchange 2010 Mailbox Databases.
#
# Configuration
# -------------
#
# Make sure your account is at least an Exchange Server Administrator on the Mailbox Server.
#
# The following values must be changed to reflect your environment
# - $Global:intThresholdInGB: The script will start to balance you databases if the difference between the biggest
# DB and the smallest DB exceeds this value. This should be set to something like 5 GB.
# Processing
# ----------
#
# The script will move a RANDOM mailbox from the Biggest DB to the Smallest DB until all DB are within the Threshold limit. So if you
# set the Threshold to 5 GB, all DB's will be equally loaded with a maximum difference of 5 GB.
# Only one mailbox at a time will be moved to minimize user downtime.
#
# Logging
# -------
#
# You can follow the progress of the script in the Application Log. Look for events of Source DBBalancer.
# Every mailbox move will be listed in the eventviewer.
#
# When all Mailbox Databases are balanced, the following will be displayed in the Event Viewer:
# All Mailbox databases are balanced within the Threshold limit.

$ErrorActionPreference = "SilentlyContinue"

# CHANGE THESE VARIABLES TO REFLECT YOUR ENVIRONMENT
$Global:intThresholdInGB = 1

# Do not change these variables
$Global:strBiggestDB = ""
$Global:strSmallestDB = ""
$Global:blnExecute = $True

Function Start-Init
{
$Global:objEventLog = New-Object System.Diagnostics.EventLog("Application")
$Global:objEventLog.MachineName = "."
$Global:objEventLog.Source = "DBBalancer"
$Global:objEventLog.WriteEntry("Application Started." + [System.Environment]::NewLine + [System.Environment]::NewLine + `
"Threshold in GB :" + $Global:intThresholdInGB)
}

Function Check-Prerequisites
{
[System.Int] $lintCount = 0

$lintCount += (Get-MailboxDatabase | Measure-Object).Count

If ($lintCount -gt 1)
{
Return $true
}
Else
{
Return $false
}
}

Function Get-BiggestandSmallestDB
{
Process
{
$DBs = Get-MailboxDatabase
$blnFirstRun = $true
foreach ($DB in $DBs)
{
$lonDBSize = (Get-MailboxDatabase $DB.Identity -status).DatabaseSize.ToMB() - (Get-MailboxDatabase $DB.Identity -status).AvailableNewMailboxSpace.ToMB()
If ($blnFirstRun -eq $true)
{
$Global:strBiggestDB = $DB.Name + ";" + $lonDBSize
$Global:strSmallestDB = $DB.Name + ";" + $lonDBSize
$blnFirstRun = $false
}
Else
{
If ($lonDBSize -lt $strSmallestDB.Split(";")[1])
{
$Global:strSmallestDB = $DB.Name + ";" + $lonDBSize
}
If ($lonDBSize -gt $strBiggestDB.Split(";")[1])
{
$Global:strBiggestDB = $DB.Name + ";" + $lonDBSize
}
}
}
}
}

Function Check-Threshold
{
If (([Long]$Global:strBiggestDB.Split(";")[1] - [Long]$Global:strSmallestDB.Split(";")[1]) -gt ($Global:intThresholdInGB * 1024))
{
Return $True
}
Else
{
Return $False
}
}

Function Do-MainLoop
{
While ($Global:blnExecute -eq $True)
{
Get-BiggestandSmallestDB
If (Check-Threshold -eq $True)
{
[Long]$lonDataToMove = ([Long]$Global:strBiggestDB.Split(";")[1] - [Long]$Global:strSmallestDB.Split(";")[1]) / 2
$Mailboxes = Get-Mailbox -Database $Global:strBiggestDB.Split(";")[0] -ResultSize "Unlimited" | Where-Object {$_.DisplayName -notlike "zz*"}
$intRandomMailbox = New-Object System.Random
$intRandomMailbox = $intRandomMailbox.Next(0, ($Mailboxes.Count - 1))
$Global:objEventLog.WriteEntry("Moving " + $Mailboxes[$intRandomMailbox] + " from database " + $Global:strBiggestDB.Split(";")[0] + `
" to database " + $Global:strSmallestDB.Split(";")[0] + "." + [System.Environment]::NewLine + [System.Environment]::NewLine + `
"The mailbox has a size of " + ((Get-MailboxStatistics -Identity $Mailboxes[$intRandomMailbox]).TotalItemSize))
get-moverequest | remove-moverequest -confirm:$false
new-moverequest -Identity $Mailboxes[$intRandomMailbox] -TargetDatabase $Global:strSmallestDB.Split(";")[0] -Confirm:$false

while ((get-moverequest -identity $Mailboxes[$intRandomMailbox]).Status -ne "Completed")
{
Start-Sleep -Seconds 20
}

}
Else
{
$Global:blnExecute = $False
}
}
Return $True
}

Start-Init
If (Check-Prerequisites -eq $true)
{
If (Do-MainLoop -eq $True)
{
$Global:objEventLog.WriteEntry("All Mailbox databases are balanced within the Threshold limit.")
}
}
Else
{
$Global:objEventLog.WriteEntry("Prerequisites check failed. You probably have only 1 Mailbox Database and therefor we can't move mailboxes around.")
}

, , ,

13 Comments

Upgrading Exchange 2003 FE/BE to Exchange 2010 (The environment) Part II

I’ve made a few Visio drawings to represent the environment at different stages in the migration. The first diagram below shows the legacy Exchange 2003 environment:

 

Diagram 1: Legacy Environment

As you can see it is a fairly straight forward setup. There is a 2003 Domain Controller/Global Catalog, 2 Exchange 2003 servers in a Front-End/Back-End configuration and a Windows 7 client running Outlook 2010.

The Forest and Domain functional levels will be set to Server 2003. It is important to note that prior to preparing the environment for Exchange 2010 the functional levels must be 2003 or higher. You will not be able to move forward if the functional level is any lower. This also means that you cannot have any Server 2000 or NT4 Domain controllers in the environment.

After preparing the environment for Exchange 2010 we will begin to introduce a few Exchange 2010 servers. When deploying the servers they will need to be deployed in a certain order. The proper order to introduce Exchange 2010 servers into a legacy exchange environment is the following:

  1. 1.       Client Access Server
  2. 2.       Hub Transport Server
  3. 3.       Unified Messaging Server
  4. 4.       Mailbox Server

There will be some configuration changes that need to be made but we will start getting into the technical discussions in the next parts.

Below is a drawing of what the intermediate environment will look like before we start moving users to the new environment.

 

Diagram 2: Intermediate Environment

Once we’ve completed any configurations we can start moving users using the Exchange 2010 new-moverequest cmdlet. Since we are moving from 2003 to 2010 we will be unable to online moves which means users cannot be in their mailbox while the move is in progress.

After all of the users have been moved we can begin to decommission the Exchange 2003 environment. And we may also decide to take advantage of the High Availability functions in Exchange 2010 so we will implement a DAG and a Load Balanced CAS Array. Our final environment will look like Diagram 3.

 

Diagram 3: Final Environment

 

Leave a comment

Upgrading Exchange 2003 FE/BE to Exchange 2010 (Intro)

In the next few posts I am going to walk through a couple options for upgrading from Exchange 2003 to Exchange 2010. The main purpose for this is for me to keep things straight as I work towards the MCITP: Exchange 2010 and ultimately I hope to go for MCM/MCA. The first part of this post is to just lay out high level what’s going to be done.

The first migration approach will be standing up new exchange servers in the same forest and moving users over time. The second migration approach will be installing new exchange server in a new forest to be used as a resource forest then moving users over.

I will not be walking through installation screens as I’m sure most people can figure those out. Also I will be using PowerShell in all areas that it is feasible.

Leave a comment

Event ID 1309

This is an error that I’m seeing in the event log on my WSUS server that I recently upgraded to WSUS 3. I’m in the process of troubleshooting and will update with the steps that I’ve taken to resolve the issue

Event Type: Warning
Event Source: ASP.NET 2.0.50727.0
Event Category: Web Event
Event ID: 1309
Date: 7/24/2007
Time: 12:08:25 PM
User: N/A
Computer: servername
Description:
Event code: 3001
Event message: The request has been aborted.
Event time: 7/24/2007 12:08:25 PM
Event time (UTC): 7/24/2007 4:08:25 PM
Event ID: cc076c634e9045dbba3df51a5f1fa53d
Event sequence: 3428
Event occurrence: 1
Event detail code: 0

Application information:
Application domain: /LM/W3SVC/1/ROOT/ClientWebService-2-128297177928432975
Trust level: Full
Application Virtual Path: /ClientWebService
Application Path: C:\Program Files\Update Services\WebServices\ClientWebService\
Machine name: servername

Process information:
Process ID: 4332
Process name: w3wp.exe
Account name: NT AUTHORITY\NETWORK SERVICE

Exception information:
Exception type: HttpException
Exception message: Request timed out.

Request information:
Request URL: http://servername/ClientWebService/client.asmx
Request path: /ClientWebService/client.asmx
User host address: x.x.x.x
User:
Is authenticated: False
Authentication Type:
Thread account name: NT AUTHORITY\NETWORK SERVICE

Thread information:
Thread ID: 161
Thread account name: NT AUTHORITY\NETWORK SERVICE
Is impersonating: False
Stack trace:

Custom event details:

after further investigation i found the following in the windowsupdate.log.

Note: error code 0×80072ee2 is a timeout.


2007-07-20 11:01:31 1080 4f0 AU #############
2007-07-20 11:01:31 1080 4f0 AU ## START ## AU: Search for updates
2007-07-20 11:01:31 1080 4f0 AU #########
2007-07-20 11:01:31 1080 4f0 AU <<## SUBMITTED ## AU: Search for updates [CallId = {7AB2E049-BD93-431D-B344-AD91269BD414}]
2007-07-20 11:01:31 1080 63c Agent *************
2007-07-20 11:01:31 1080 63c Agent ** START ** Agent: Finding updates [CallerId = AutomaticUpdates]
2007-07-20 11:01:31 1080 63c Agent *********
2007-07-20 11:03:37 1080 63c Misc WARNING: Send failed with hr = 80072ee2.
2007-07-20 11:03:37 1080 63c Misc WARNING: SendRequest failed with hr = 80072ee2. Proxy List used: <(null)> Bypass List used : <(null)> Auth Schemes used : <>
2007-07-20 11:03:37 1080 63c Misc WARNING: WinHttp: SendRequestUsingProxy failed for . error 0×80072ee2
2007-07-20 11:03:37 1080 63c Misc WARNING: WinHttp: DoFileDownload MakeRequest failed. error 0×80072ee2
2007-07-20 11:05:38 1080 63c Misc WARNING: Send failed with hr = 80072ee2.
2007-07-20 11:05:38 1080 63c Misc WARNING: SendRequest failed with hr = 80072ee2. Proxy List used: <(null)> Bypass List used : <(null)> Auth Schemes used : <>
2007-07-20 11:05:38 1080 63c Misc WARNING: WinHttp: SendRequestUsingProxy failed for . error 0×80072ee2
2007-07-20 11:05:38 1080 63c Misc WARNING: WinHttp: DoFileDownload MakeRequest failed. error 0×80072ee2
2007-07-20 11:07:39 1080 63c Misc WARNING: Send failed with hr = 80072ee2.
2007-07-20 11:07:39 1080 63c Misc WARNING: SendRequest failed with hr = 80072ee2. Proxy List used: <(null)> Bypass List used : <(null)> Auth Schemes used : <>
2007-07-20 11:07:39 1080 63c Misc WARNING: WinHttp: SendRequestUsingProxy failed for . error 0×80072ee2
2007-07-20 11:07:39 1080 63c Misc WARNING: WinHttp: DoFileDownload MakeRequest failed. error 0×80072ee2
2007-07-20 11:09:39 1080 63c Misc WARNING: Send failed with hr = 80072ee2.
2007-07-20 11:09:39 1080 63c Misc WARNING: SendRequest failed with hr = 80072ee2. Proxy List used: <(null)> Bypass List used : <(null)> Auth Schemes used : <>
2007-07-20 11:09:39 1080 63c Misc WARNING: WinHttp: SendRequestUsingProxy failed for . error 0×80072ee2
2007-07-20 11:09:39 1080 63c Misc WARNING: WinHttp: DoFileDownload MakeRequest failed. error 0×80072ee2
2007-07-20 11:09:39 1080 63c Misc WARNING: DownloadFileInternal failed for http://servername/selfupdate/wuident.cab: error 0×80072ee2
2007-07-20 11:09:39 1080 63c Setup FATAL: IsUpdateRequired failed with error 0×80072ee2
2007-07-20 11:09:39 1080 63c Setup WARNING: SelfUpdate: Default Service: IsUpdateRequired failed: 0×80072ee2
2007-07-20 11:09:39 1080 63c Setup WARNING: SelfUpdate: Default Service: IsUpdateRequired failed, error = 0×80072EE2
2007-07-20 11:09:39 1080 63c Agent * WARNING: Skipping scan, self-update check returned 0×80072EE2
2007-07-20 11:09:41 1080 63c Agent * WARNING: Exit code = 0×80072EE2
2007-07-20 11:09:41 1080 63c Agent *********
2007-07-20 11:09:41 1080 63c Agent ** END ** Agent: Finding updates [CallerId = AutomaticUpdates]
2007-07-20 11:09:41 1080 63c Agent *************
2007-07-20 11:09:41 1080 63c Agent WARNING: WU client failed Searching for update with error 0×80072ee2
2007-07-20 11:09:41 1080 63c AU >>## RESUMED ## AU: Search for updates [CallId = {7AB2E049-BD93-431D-B344-AD91269BD414}]
2007-07-20 11:09:41 1080 63c AU # WARNING: Search callback failed, result = 0×80072EE2
2007-07-20 11:09:41 1080 63c AU #########
2007-07-20 11:09:41 1080 63c AU ## END ## AU: Search for updates [CallId = {7AB2E049-BD93-431D-B344-AD91269BD414}]
2007-07-20 11:09:41 1080 63c AU #############

after seeing this I ran the WSUS Client Diag utility, which can be found here.

when running the clientdiag it showed the dll version to be 5.8.0.2469, and WSUS 3 AU agent should be at 7.0.6000.374. A manual install of the 3.0 client fixed all the issues. The 3.0 client can be found here

Leave a comment

Batch file to disable AD user and add Date to Description

Here is a simple batch file that I’ve created to disable a user and add the date that the user was disabled to the description. another nice thing about the script is that you do not need to know the DN of the user because it will first use dsquery to output the user DN to the input for the dsmod command.
If your AD uses Last name, First name format the most accurate way of putting the name in to the input would be lastname*partoffirstname

@ECHO OFF
CLS
:terminate
SET /p PARTNAME=Enter the partial name of the person.
DSQUERY USER -name *%PARTNAME%* | DSMOD USER -desc “disabled %date%” -disabled yes
SET /p AGAIN=Would you like to disable another(y/n)?
IF %AGAIN% == y GOTO terminate
IF %AGAIN% == n GOTO end
ECHO Invalid option

:end
EXIT

Leave a comment

%d bloggers like this: