Exchange 2010 Scripting Agent

#MSExchange

So SteveM showed me this a while back, and yeah I know I am at the back of the queue, but boy is this a pain in the butt!

So I want to set some standard stuff when you run New-MailboxDatabase.  Standard properties are kinda simple, but i discovered that AD Replication, even in a small environment can break the scripting agent, especially when you want to set AD Permissions on a mailbox.  Powershell rocks, but it runs so fast that AD just doesn’t update quick enough!. [So when is Exchange going to break out from the AD and go back to it’s own directory like the good olde days?]

So, the resolution was to add a function that checks to see if the cmdlet value was actually set.  It checks 60 times (60 seconds) then exits the loop.

So here is my current ScriptingAgentConfig.xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration version="1.0">
  <!-- 
    NAME: ScriptingAgentConfig.xml
    AUTHOR: Paul Flaherty
    Last Edit: v1.0 [24 July 2012]
    ScriptingAgentConfig.xml uses the Exchange Server 2010 Scritpting Agent Cmdlet Extension.
    It allows additional configuration to take place when certain Exchange 2010 cmdlets are executed.
    Feature: NewMailboxDatabase
    The portion of code runs after New-MailboxDatabase and sets a number of things:
    - Removes all the database limits
    - Sets deleted items and mailbox retention limits to 31 days
    - Assigned Send-As, Receive-As and ms-Exch-Store-Admin rights
    The Function Check-AdReplications is used to ensure the AD object changes have taken place
    OUTPUT from the script is stored in C:PSScriptingAgent_New-MailboxDatabase_<datetime>.txt
  -->
 
    <Feature Name="NewMailboxDatabase" Cmdlets="New-MailboxDatabase">
      <ApiCall Name="OnComplete">
        If ($Succeeded) {
 
          $xNow          = Get-Date -format "yyyy-MM-dd_HH_mm_ss"
          $tmpOutFile    = "C:PSScriptingAgent_" + $provisioningHandler.TaskName + "_" + $xNow + ".txt"
          $NewDatabase   = $provisioningHandler.UserSpecifiedParameters["Name"]
          $OriginatingDC = "abc123"
 
          Function Check-ADReplication([String]$incmdlet){
            #This functions takes an cmdlet string as a parameter and uses it to check if the result of the cmdlet
            #is not NULL.  However, after 60 attempts (Approx 60 seconds) the function with exit.
 
            "Check-ADReplication" >> $tmpOutFile 
            $incmdlet = $incmdlet.replace("##","_.")
            $incmdlet >> $tmpoutfile
 
            #Loop round 30 times
            For($i=0; $i -le 60; $i++ ){
              $now = Get-Date
              "Check $i" >> $tmpOutFile
              $now  >> $tmpOutFile
 
              #Invoke the incoming cmdlet
              $tmpExpression = Invoke-Expression $incmdlet #get-MailboxDatabase $NewDatabase -DomainController $OriginatingDC 
 
              If ($tmpExpression -eq $null){
                #if the result is null, sleep for 1 second and loop
                "Null" >> $tmpOutFile; Start-Sleep 1
              }ELSE{
                #If the result is NOT null, set the look to exit
                #On first run, extect the Domain Controller for later use
                "OK" >> $tmpOutFile;
                IF($script:OriginatingDC -eq "abc123"){$script:OriginatingDC = $tmpExpression.originatingserver}
                 $i=99; 
              }#IF
            }#For
          } #Function
 
          #Write stuff out
          $now = Get-Date
          $now  >> $tmpOutFile 
          "ScriptingAgent[NewMailboxDatabase]" >> $tmpOutFile 
          $provisioningHandler                 >> $tmpOutFile 
          $provisioningHandler.Userscope | fl  >> $tmpOutFile 
          $readOnlyIConfigurable               >> $tmpoutfile
          "Mailbox Database: $NewDatabase"     >> $tmpOutFile 
 
          #Check if the database has replicated to the AD yet
          $cmdlet2Check = "get-MailboxDatabase " + $NewDatabase
          Check-ADReplication $cmdlet2Check 
          $OriginatingDC  >> $tmpOutFile 
 
          #Remove database limits
          "-Remove database limits" >> $tmpOutFile 
          Get-MailboxDatabase $NewDatabase | Set-MailboxDatabase -ProhibitSendReceiveQuota 'unlimited' -ProhibitSendQuota 'unlimited' -IssueWarningQuota 'unlimited' -RecoverableItemsQuota 'unlimited' -RecoverableItemsWarningQuota 'unlimited' -DomainController $OriginatingDC 
          $cmdlet2Check = "get-MailboxDatabase " + $NewDatabase + "  -DomainController " + $OriginatingDC + " | Where{$##ProhibitSendReceiveQuota -eq 'unlimited'}" 
          Check-ADReplication $cmdlet2Check 
 
 
          #Set Deleted items retention
          "-Set Deleted items retention" >> $tmpOutFile 
          Get-MailboxDatabase $NewDatabase | Set-MailboxDatabase -DeletedItemRetention 31 -MailboxRetention 31 -DomainController $OriginatingDC 
          $cmdlet2Check = "get-MailboxDatabase " + $NewDatabase + "  -DomainController " + $OriginatingDC + " | Where{$##DeletedItemRetention -eq 31}"
          Check-ADReplication $cmdlet2Check 
 
 
          "-Set AD Permissions" >> $tmpOutFile 
          $cmdlet2Check = "get-MailboxDatabase " + $NewDatabase + "  -DomainController " + $OriginatingDC + " | Get-AdPermission |  Where{$##User -like '*Organization Management'}"
          Check-ADReplication $cmdlet2Check 
 
          #Groups Names for the permissions
          $SendAs     = "UG_MGT_msExchSendAs"
          $ReceiveAs  = "UG_MGT_msExchReceiveAs"
          $StoreAdmin = "UG_MGT_msExchStoreAdmin"
 
          #Grant the above groups the required rights
          Get-MailboxDatabase $NewDatabase | Add-ADPermission -user $SendAs     -AccessRights extendedright -ExtendedRight Send-As              -DomainController $OriginatingDC 
          Get-MailboxDatabase $NewDatabase | Add-ADPermission -user $ReceiveAs  -AccessRights extendedright -ExtendedRight Receive-As           -DomainController $OriginatingDC 
          Get-MailboxDatabase $NewDatabase | Add-ADPermission -user $StoreAdmin -AccessRights extendedright -ExtendedRight ms-Exch-Store-Admin  -DomainController $OriginatingDC
        }
      </ApiCall>
    </Feature>
 
</Configuration>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

if you want more then check these out:

Enjoy

Get-DatabaseDistribution.ps1

#msexchange #powershell

How cr@p am I? I totally forgot about this baby.  So this is rough and ready Disappointed smile not had a chance to sort the help out at the beginning, but I’m sure you will get the idea.  Basically run this with one of the switches ByMailboxSize or ByMailboxCount and it will look at your exchange 2010 mailboxes and try and load balance the users.  It doesn’t actually DO anything, what it will do is generate a CSV that you use as input for Move-RequestFromCSV that is here: http://flaphead.dns2go.com/?p=2945

Let me know what you think.  In the test I have done so far ByMailboxCount is shweet, ByMailboxSize isn’t perfect.  Enjoy

PARAM([String]$Database="", [String]$BatchName, [Switch]$ByMailboxCount, [Switch]$ByMailboxSize, [String]$SizePercentage=2, [String]$ExportCSV="$pwdmiguserlist.csv")

#http://stackoverflow.com/questions/5863772/powershell-round-down-to-nearest-whole-number
function round( $value, [MidpointRounding]$mode = ‘AwayFromZero’ ) {   [Math]::Round( $value, $mode ) }

#$DB=@();Get-MailboxDatabase * | ForEach{$db += $_.name}

$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}

Write-Host "Database:……."$Database
Write-Host "ByMailboxCount:."$ByMailboxCount
Write-Host "ByMailboxSize:.."$ByMailboxSize
Write-Host "SizePercentage:."$SizePercentage
Write-Host "ExportCSV:……"$ExportCSV
Write-Host ""

If ($Database -ne ""){
  $tmpDatabaseArray = $database.split(" ")
  $i=0
  $DatabaseHashTable = @{}
  ForEach($item in $tmpDatabaseArray){; $DatabaseHashTable.Add($i,$item); $i++}
}

$tmpDatabaseArray

$MailboxArray=@()
ForEach($item in $tmpDatabaseArray){
  Write-Host "`nChecking Database" $item -Foregroundcolor Green
  $position=$host.ui.rawui.cursorposition
  $tmpMailbox = Get-Mailbox -Database $item -ResultSize unlimited
  $mbxcnt   = 0
  $mbxItems = 0
  $mbxSize  = 0
  ForEach($xMailbox in $tmpMailbox){
    $tmpUser = $xmailbox.PrimarySmtpAddress.toString()
    $host.ui.rawui.cursorposition=$position;
    Write-Host "-" $tmpuser "                                                                          "
    $tmpMailboxArray = "" | Select DisplayName, Email, ServerName, Database, Items, Size, Target, Status 
    $tmpMailboxArray.DisplayName = $xMailbox.DisplayName
    $tmpMailboxArray.Email       = $tmpUser
    $tmpMailboxArray.ServerName  = $xMailbox.ServerName
    $tmpMailboxArray.Database    = $xMailbox.Database
    $tmpMailboxArray.Target      = $xMailbox.Database
    $tmpMailboxStatistics        = Get-Mailbox $tmpUser | Get-MailboxStatistics
    $tmpMailboxArray.Items       = $tmpMailboxStatistics.ItemCount
    $tmpMailboxArray.Size        = $tmpMailboxStatistics.TotalItemSize.Value.ToMB()
    $MailboxArray += $tmpMailboxArray
    $mbxcnt ++
    $mbxItems += $tmpMailboxArray.Items
    $mbxSize  += $tmpMailboxArray.Size
  }
  $host.ui.rawui.cursorposition=$position;
  Write-Host "-> Complete! " -NoNewLIne
  Write-Host $mbxcnt -NoNewLine -ForegroundColor Yellow
  Write-Host " Mailboxes Found with " -NoNewline
  Write-Host $mbxItems -NoNewLine -ForegroundColor Yellow
  Write-host " Items and " -NoNewLine
  Write-Host $mbxSize -ForegroundColor Yellow -NoNewLine
  Write-Host "MB            "
}

#Okay lets count the number of databases
$DatabaseCount = $tmpdatabasearray.count
$MailboxCount  = $MailboxArray.count
$MailboxSize   = ($MailboxArray | Measure-Object Size -sum).Sum
$MailboxesPerDatabase = round ($MailboxCount / $DatabaseCount )
$MBPerDatabase = round ($MailboxSize / $DatabaseCount )

Write-Host "Database Count:……………."$DatabaseCount
Write-Host "Mailbox Count:…………….."$MailboxCount
Write-Host "Ideal Mailboxes Per Database:.."$MailboxesPerDatabase
Write-Host "Total Size:……………….."$MailboxSize
Write-Host "Ideal MB Per Database:………"$MBPerDatabase
$MBPerDatabase = round ($MBPerDatabase + (($MBPerDatabase /100)*$SizePercentage))
Write-Host "`nMB Per Database (+"$SizePercentage"%) = "$MBPerDatabase

#Group MailboxArray by Database
$DatabaseCountArray = $MailboxArray | Group Database
$DatabaseMatrix = @()
ForEach($Item in $DatabaseCountArray){
  $tmpDatabaseMatrix = "" | Select Name, Count, Difference1, Difference2, MB , Difference3, Difference4
  $tmpDatabaseMatrix.Name        = $Item.Name
  $tmpDatabaseMatrix.Count       = $Item.Count
  $tmpDatabaseMatrix.Difference1 = $MailboxesPerDatabase – $Item.Count
  $tmpDatabaseMatrix.Difference2 = $Item.Count – $MailboxesPerDatabase
  $tmpDatabaseMatrix.MB    &#160
;     = ($Item.Group | Measure-Object Size -Sum).Sum
  $tmpDatabaseMatrix.Difference3 = $MBPerDatabase – $tmpDatabaseMatrix.MB
  $tmpDatabaseMatrix.Difference4 = $tmpDatabaseMatrix.MB – $MBPerDatabase

  $DatabaseMatrix += $tmpDatabaseMatrix
}
$DatabaseMatrix | ft

If ($ByMailboxSize){
  Write-Host "`nBY MAILBOX SIZE" -Foregroundcolor Red
  Write-Host "—————"
  Write-Host "`nFinding databases with excess mailboxes"
  $Database2MoveOff = $DatabaseMatrix | Where {$_.Difference4 -ge 0}
  ForEach($db in $Database2MoveOff){
    Write-Host $db.Name -Foregroundcolor Blue
    $tmpExcess = $MailboxArray | where {$_.Database -eq $db.Name} | Sort Size -Descending
    $targetMB=0
    ForEach($yUser in $MailboxArray | where {$_.Database -eq $db.Name} | Sort Size -Descending){
      $tmpMB = $yUser.Size
      If ($tmpMB + $targetMB -gt $MBPerDatabase){
        $yUser.Target = ""
      } ELSE {
        $yuser.Status = "Keep"
        $targetMB += $tmpMB
      } #If ($tmpMB + $targetMB -gt $MBPerDatabase)
    } #ForEach $yUser
  } #ForEach $db

  Write-Host "`nAssigning excess mailboxes"
  ForEach($item in $DatabaseMatrix | Where {$_.Difference3 -gt 0}){
    $Mailboxes2Move =  $DatabaseMatrix | Where {$_.Target -eq ""}  
    $targetMB=$item.MB
    ForEach($yUser in $MailboxArray | where {$_.Target -eq ""} | Sort Size -Descending){
      $tmpMB = $yUser.Size
      If ($tmpMB + $targetMB -gt $MBPerDatabase){
        $yUser.Target = ""
      } ELSE {
        $yuser.Target = $item.Name
        $yuser.Status = "Move"
        $targetMB += $tmpMB
      } #If ($tmpMB + $targetMB -gt $MBPerDatabase)
    } #ForEach $yUser
  } #ForEach $item

  Write-Host "`nSUMMARY" -Foregroundcolor Green
  Write-Host "Ideal MB Per Database:………"$MBPerDatabase
  $MailboxArray | Group Target | %{
    New-Object psobject -Property @{
        Item = $_.Name
        Sum = ($_.Group | Measure-Object Size -Sum).Sum
    }
  }
} #If ($ByMailboxSize)

If ($ByMailboxCount){
  Write-Host "`nBY MAILBOX COUNT" -Foregroundcolor Red
  Write-Host "—————-"
  Write-Host "Check to see if the Excess Mailboxes = Required " -NoNewLine
  $tmpcheck = ($DatabaseMatrix | Measure-Object Difference1 -Sum).sum
  Write-Host $tmpcheck -ForegroundColor Green

  Write-Host "`nFinding databases with excess mailboxes"
  $Database2MoveOff = $DatabaseMatrix | Where {$_.Difference2 -ge 0}
  $ExcessTotal = 0
  ForEach($db in $Database2MoveOff){
    Write-Host $db.Name -Foregroundcolor Blue
    Write-Host "Removing"$db.Difference2 "users`n"
    $ExcessTotal += $db.Difference2
    $MailboxArray | Where {$_.Database.Name -eq $db.name} | Select -First $db.Difference2 | ForEach{$_.Target = "";$_.Status = "Move"}
  }

  Write-Host "Total Excess Mailbox " $ExcessTotal
  $i=0
  ForEach($item in $DatabaseMatrix | Where {$_.Difference1 -gt 0}){
    $Mailboxes2Add = $item.Difference1
    $MailboxArray | Where {$_.Target -eq ""} | Select -First $Mailboxes2Add | ForEach{$_.Target = $item.Name}
  }

  Write-Host "`nSUMMARY" -Foregroundcolor Green
  $MailboxArray | Group Target
}#ByMailboxCount

#Foreach($line in $MailboxArray){Write-Host $line.Email"`t"$line.database}

Write-Host "`nMove Mailboxes"
$outCSV = @()
$ExcessMailboxes = $MailboxArray | where {$_.Status -eq "Move"}

ForEach($item in $ExcessMailboxes){
  $tmpOutCsv                = "" | Select email, TargetDatabase
  $tmpOutCSV.email          = $item.email
  $tmpOutCsv.TargetDatabase = $Item.Target
  $outCSV += $tmpOutCSV
}

ForEach($line in $outCSV){
  $position=$host.ui.rawui.cursorposition
  Write-Host $line.email
  $position.x = 50
  $host.ui.rawui.cursorposition=$position;
  Write-host $line.target
}
$outCSV | Export-csv $ExportCSV -NoTypeInformation

Monitor-MoveRequest.ps1 v1.8

#MSExchange #PowerShell

I know, I know I’m a slacker .. w h a t e v e r

So here is the in final script in a my 3 part Exchange 2010 move mailbox process. Change the bits that say “<CHANGE THIS>” .. let me know what you think

<#
.NOTES
NAME: Monitor-MoveRequest.ps1
AUTHOR: Paul Flaherty
Last Edit: v1.8 [30 March 2011]
v1.0 sometime    : A script it born
v1.1 09 Jun 2011 : Added more Move-Request status checking
v1.2 16 Jun 2011 : Changed the loop ot use email address instead of name
v1.3 21 Jun 2011 : Updated to collect overall duration properly
v1.4 30 Jul 2011 : Updated with last item in report
v1.5  4 Aug 2011 : Updated so Moverequest report is generated and emailed
v1.6 22 Nov 2011 : Updated the output to put the summary at the top
                  : Updated with colours on output
v1.7 23 Nov 2011 : Update summary to include GB Transferred
v1.7 30 Mar 2012 : Cleaned up screen output
.LINK
blogs.flaphead.com
.SYNOPSIS
This script performs some preflight checks for mailboxes moves
and should be run before a mailbox move
.DESCRIPTION
This script Monitors Exchange Server 2010 move-requests by generating a webpage
and sending an email once it is complete.
.OUTPUTS
  None
.EXAMPLE
Monitor-MoveRequest.ps1
Run the checks using the default csv file
#>
##########################################################################################
PARAM([String]$BatchName = $args[0],
      [String]$HTMLOutFolder = "E:PsMonHTML",
      [String]$Mailbox="")
$Error.Clear()
#########################################################################################
$AppName    = "Monitor-MoveRequest.ps1"
$AppVer     = "v1.8 [30 March 2011]"
$Today      = Get-Date
$ServerName = hostname
$RunUser    = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
##########################################################################################
#Display script name and version
##########################################################################################
CLS
Write-host " " $AppName -NoNewLine -foregroundcolor Green
Write-Host ": " $AppVer -foregroundcolor Green
Write-host "`n Run on $ServerName at $Today by $RunUser" -foregroundcolor Yellow
Write-Host "|——————————————————————-|`n"
##########################################################################################
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}
$xBatchName =$BatchName
If ($xBatchName -eq ""){
  #Check Environment Variables
  Write-Host "Checking Environment Variables .. " -NoNewLine
  $envBatchName = [environment]::GetEnvironmentVariable(‘BatchName’,’User’)
  Write-Host $envBatchName -Foregroundcolor Green
  $xBatchName = $envBatchName
}
Write-Host "Batch Name:….."$xBatchName
Write-Host "Mailbox:…….."$Mailbox
Write-Host "Output Folder:.."$HTMLOutFolder
$xHTMLoutFolder = $HTMLOutFolder
$UserList      = @()
$users2Migrate = @()
$htmlHeader = "
<META HTTP-EQUIV=’refresh’ CONTENT=’60’>
<Style>
  TABLE{border-width: 1px;padding: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
  TD{border-width: 1px;padding: 1px;border-style: solid;border-color: black;}
  TH{font-family:’Arial’;font-size:12px;border-width: 1px;padding: 1px;border-style: solid;border-color: black;background-color:peachpuff;}
  TR{font-family:’Arial’;font-size:10px}
  P{font-family:’Arial’;}
</Style>"
$x="<META HTTP-EQUIV=’refresh’ CONTENT=’300′><Style>TABLE{font-family:VERDANA;font-size: 10pt; border-width: 1px;padding: 1px;border-style: solid;border-color: black;border-collapse: collapse;}TD{border-width: 1px;padding: 1px;border-style: solid;border-color: black;}TH{border-width: 1px;padding: 1px;border-style: solid;border-color: black;background-color:peachpuff;}</Style>";
If ($xBatchName -ne ""){
  Write-Host "Batch Name: " $xBatchName
  Write-Host "`nGetting users from move request " -noNewLine
  $GMR = Get-MoveRequest -BatchName $xBatchName -ResultSize unlimited
  $msgSubject1 = "From Batch $xBatchName"
  $xattachdir = $xBatchname
} #If ($xBatchName -ne "")
If ($Mailbox -ne ""){
  Write-Host "Mailbox Name: " $Mailbox
  Write-Host "`nGetting users from move request " -noNewLine
  $GMR = Get-MoveRequest $Mailbox -ResultSize unlimited
  $msgSubject1 = "for $Mailbox "
  $xattachdir = $Mailbox
} #If ($Mailbox -ne "")
IF ($GMR -eq $Null){
  Write-Host "No Users in BatchName, Exiting" -foregroundcolor red
  Exit
}ELSE{
  Write-Host $GMR.count "Found" -Foregroundcolor Green
} #IF ($GMR -eq $Null)
Write-Host "Getting mailbox information for users"
$GMBX = $GMR | Get-Mailbox
$GMBX | ForEach{
  $xUser = "" | Select Email
  $xUser.EMail+= $_.PrimarySmtpAddress.ToString()
  $users2Migrate += $xUser
} #ForEach
Write-Host "Checking Users .."
ForEach($xUser in $Users2Migrate){
  $tmpemail = $xUser.email
  Write-Host "-" $tmpemail
  $xUser = "" | Select Name, Email, IsValid, MoveStatus, ActualStatus, Pct, Duration, MailboxSize, ItemCount, ItemDone, BadItemsEncountered, SourceDatabase, TargetDatabase, MRSServerName, Transferred, TransferredPerMinute, PiQ, Message, Status
  #Check User is Valid
  $tmpmbx = Get-Mailbox $tmpemail
  $xUser.Name = $tmpmbx.name
  $xUser.Email = $tmpemail
  $xUser.IsValid =  $tmpmbx.isvalid
  if ($tmpmbx.isvalid -eq $True){$xUser.MoveStatus = "Unknown"}ELSE{$xUser.MoveStatus = "ERR";$xUser.IsValid = $False}
  $UserList += $xUser
} #ForEach($xUser
$tmpLoop  = $true
$tmpcount = $UserList.count
$position = $host.ui.rawui.cursorposition
While ($tmploop){
  $now = Get-Date;
  $host.ui.rawui.cursorposition=$position;
  Write-Host "–> REFRESH                 " -Foregroundcolor Green
  For($i=0;$i -le $TmpCount-1;$i++){
    $tmpname
= $UserList[$i].email #v1.2 <- $UserList[$i].name
    $tmpgmr = Get-MoveRequestStatistics $tmpname -IncludeReport
    $tmpstatus = $tmpgmr.Status
    $UserList[$i].ActualStatus    = $tmpstatus
    $UserList[$i].Pct = $tmpgmr.PercentComplete
    switch ($tmpstatus) {
      "AutoSuspended"         {$UserList[$i].MoveStatus = $tmpstatus}
      "Completed"             {$UserList[$i].MoveStatus = $tmpstatus}
      "CompletedWithWarning"  {$UserList[$i].MoveStatus = $tmpstatus}
      "Failed"                {$UserList[$i].MoveStatus = $tmpstatus}
      "Suspended"             {$UserList[$i].MoveStatus = $tmpstatus}
      default                 {$UserList[$i].MoveStatus = "Unknown"}
    } #Switch
    $tmpDur  = $tmpgmr | select OverallDuration
    $UserList[$i].Duration       = $tmpDur.OverallDuration.ToString()
    $UserList[$i].BadItemsEncountered = $tmpgmr.BadItemsEncountered
    $UserList[$i].ItemDone       = $tmpgmr.ItemsTransferred
    $UserList[$i].MailboxSize    = $tmpgmr.TotalMailboxSize
    $UserList[$i].ItemCount      = $tmpgmr.TotalMailboxItemCount
    $UserList[$i].SourceDatabase = $tmpgmr.SourceDatabase
    $UserList[$i].TargetDatabase = $tmpgmr.TargetDatabase
    $UserList[$i].MRSServerName  = $tmpgmr.MRSServerName
    $UserList[$i].Transferred    = $tmpgmr.BytesTransferred
    $UserList[$i].TransferredPerMinute = $tmpgmr.BytesTransferredPerMinute
    $UserList[$i].Message = $tmpgmr.Message
    $tmpPiQ = ($tmpgmr.PositionInQueue).ToString()
    $tmpLog = $tmpgmr.report.Entries | ForEach{$_.ToString()}
    $UserList[$i].Status =  $tmpLog[-1]
    $UserList[$i].PiQ = ($tmpPiQ.Split(" "))[0]
  } #UserList
  $host.ui.rawui.cursorposition=$position;
  Write-Host "–>              " -Foregroundcolor Green
  $tmpchk = 0
  $UserList | where{$_.MoveStatus -eq "Unknown"} | ForEach {$tmpchk ++}
  $xsumHTML = $UserList | group ActualStatus | Select @{Expression={$Now};Label="Now"}, Name, @{Expression={"!AR!" + $_.Count};Label="Count"}, @{Expression={"!AR!" + (($_.Group | Measure-Object Transferred -Sum).sum/1024/1024).ToString("n")};Label="MBTransferred"},  @{Expression={"!AR!" + (($_.Group | Measure-Object MailboxSize -Sum).sum/1024/1024).ToString("n")};Label="TotalMB"} | Sort Name
  $xsum     = $UserList | group ActualStatus | Select @{Expression={$Now};Label="Now"}, Name, Count, @{Expression={(($_.Group | Measure-Object Transferred -Sum).sum/1024/1024).ToString("n")};Label="MBTransferred"},  @{Expression={ (($_.Group | Measure-Object MailboxSize -Sum).sum/1024/1024).ToString("n")};Label="TotalMB"}
  $xsum | ft *
  Write-host "<– WAITING:          <–" -Foregroundcolor Red
  $position=$host.ui.rawui.cursorposition
  $position.y = $position.y -1
  $xhtml += "
<hr><Style>
  TABLE{border-width: 1px;padding: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
  TD{border-width: 1px;padding: 1px;border-style: solid;border-color: black;}
  TH{font-family:’Arial’;font-size:12px;border-width: 1px;padding: 1px;border-style: solid;border-color: black;background-color:peachpuff;}
  TR{font-family:’Arial’;font-size:10px}
  P{font-family:’Arial’;}
</Style>" 
  $xhtml2  = $xsumHTML | ConvertTo-Html -Fragment
  $today = Get-Date
  $z="<B><FONT size=’2′ face=’VERDANA’>Move Requests ($xBatchName)</B></FONT><BR><FONT size=’1′ face=’VERDANA’>Last updated: $today</FONT></FONT><HR size=6 color=Green>[INSERTHERE]<br>"
  $xhtml = $userlist | Select Name, Email, IsValid, MoveStatus, ActualStatus, Pct, MailboxSize, ItemCount, ItemDone, SourceDatabase, TargetDatabase, MRSServerName, Transferred, TransferredPerMinute, @{Expression={$_.BadItemsEncountered};Label="BadItems"}, PiQ, Status | sort @{Expression="ActualStatus";Descending=$False}, @{Expression="Pct";Descending=$True}, Name | ConvertTo-Html -head $HtmlHeader  -Title "Move Requests" -body $z
  #Colour the HTML table up
  $txtFAILED   = @()
  $i=0; $xHTML | foreach{IF ($_ -like "*<td>Failed*"){ $txtFAILED+= $i}; $i++}
  $txtFAILED   | ForEach{$xHTML[$_] = $xHTML[$_].Replace("<tr>","<tr bgcolor=Red>")}
  $txtINPROGRESS = @()
  $i=0; $xHTML   | foreach{IF ($_ -like "*<td>InProgress*"){ $txtINPROGRESS += $i}; $i++}
  $txtINPROGRESS | ForEach{$xHTML[$_] = $xHTML[$_].Replace("<tr>","<tr bgcolor=ORANGE>")}
  $txtCOMPLETED = @()
  $i=0; $xHTML  | foreach{IF ($_ -like "*<td>Completed*"){ $txtCOMPLETED += $i}; $i++}
  $txtCOMPLETED | ForEach{$xHTML[$_] = $xHTML[$_].Replace("<tr>","<tr bgcolor=Lime>")}
  $txtCOMPLETION = @()
  $i=0; $xHTML  | foreach{IF ($_ -like "*<td>CompletionInProgress*"){ $txtCOMPLETION  += $i}; $i++}
  $txtCOMPLETION  | ForEach{$xHTML[$_] = $xHTML[$_].Replace("<tr>","<tr bgcolor=Yellow>")}
  #Colour the Summary HTML table up
  $txtFAILED   = @()
  $i=0; $xhtml2 | foreach{IF ($_ -like "*<td>Failed*"){ $txtFAILED+= $i}; $i++}
  $txtFAILED   | ForEach{$xHTML2[$_] = $xHTML2[$_].Replace("<tr>","<tr bgcolor=Red>")}
  $txtINPROGRESS = @()
  $i=0; $xHTML2   | foreach{IF ($_ -like "*<td>InProgress*"){ $txtINPROGRESS += $i}; $i++}
  $txtINPROGRESS | ForEach{$xHTML2[$_] = $xHTML2[$_].Replace("<tr>","<tr bgcolor=ORANGE>")}
  $txtCOMPLETED = @()
  $i=0; $xHTML2  | foreach{IF ($_ -like "*<td>Completed*"){ $txtCOMPLETED += $i}; $i++}
  $txtCOMPLETED | ForEach{$xHTML2[$_] = $xHTML2[$_].Replace("<tr>","<tr bgcolor=Li
me>")}
  $txtCOMPLETION = @()
  $i=0; $xHTML2  | foreach{IF ($_ -like "*<td>CompletionInProgress*"){ $txtCOMPLETION  += $i}; $i++}
  $txtCOMPLETION  | ForEach{$xHTML2[$_] = $xHTML2[$_].Replace("<tr>","<tr bgcolor=Yellow>")}
  $txtAR = @()
  $i=0; $xHTML2 | foreach{IF ($_ -like "*>!AR!*"){ $txtAR += $i;}; $i++}
  $txtAR | ForEach{$xHTML2[$_] = $xHTML2[$_].Replace(">!AR!"," align=right>")}
  $tmpHere = @()
  $i=0; $xHTML | foreach{IF ($_ -like "*[INSERTHERE]*"){ $tmpHere += $i}; $i++}
  $tmpHere | ForEach{$xHTML[$_] = $xHTML[$_].Replace("[INSERTHERE]",$xhtml2)}
  $xhtml| Out-File $xHTMLoutFolderMoveRequestinProgress.html
  $tmpPosition = $position;
  $tmpPosition.x = $tmpPosition.x + 12
  If($tmpChk -eq 0){
    $tmpLoop = $False
  }ELSE{
    FOR($i=0;$i -le 10;$i++){
      Sleep 2
      $host.ui.rawui.cursorposition=$tmpposition; 
      Write-Host "#" -foregroundcolor Red
      $tmpPosition.x = $tmpPosition.x + 1
    } #FOR($i=0;$i -le 10;$i++)
  } #If($tmpChk -eq 0)
} #While ($tmploop)
$xsum = $UserList | group ActualStatus | Select Name, Count, @{Expression={(($_.Group | Measure-Object Transferred -Sum).sum/1024/1024/1024).ToString("n")};Label="GB Transferred"}
$xhtml2 = $xsum | ConvertTo-Html -Fragment
$today = Get-Date; $z="<B><FONT size=’2′ face=’VERDANA’>Move Requests ($xBatchName)</B></FONT><BR><FONT size=’1′ face=’VERDANA’>Last updated: $today</FONT></FONT><HR size=6 color=Green>"  + $xhtml2 + "<br>"
$OutHTML = $UserList | Select Name, Email, IsValid, MoveStatus, ActualStatus, Pct, Duration, MailboxSize, ItemCount, BadItemsEncountered, SourceDatabase, TargetDatabase, MRSServerName, message | ConvertTo-Html -head $HtmlHeader  -Title "Move Requests" -body $z
$xEnd = get-Date
$OutHTML += "
<HR size=6 color=Green>
<FONT size=’1′ face=’VERDANA’>Run by $CurrentUser. Script Completed: $xEnd</FONT>
</FONT><BR></BODY>
</HTML>
"
$OutHTML | Out-File $xHTMLoutFolderMoveRequestinProgress.html
$txtRED    = @()
$i=0; $OutHTML | foreach{IF ($_ -like "*<td>Failed*"){ $txtRED += $i}; $i++}
$txtRED | ForEach{$OutHTML[$_] = $OutHTML[$_].Replace("<tr>","<tr bgcolor=Red>")}
$logfiles=@()
ForEach($yUser in $UserList){
  $logfile = $Datefolder+""+$xBatchname+"_MoveRequestReport_" + $yuser.email + ".txt"
  $movereport=(Get-MoveRequestStatistics $yUser.email -IncludeReport).report
  $log = $movereport.Entries | ForEach{$_.ToString()}
  $log | Out-File $logfile
  $logfiles += $logfile
}
  Write-Host "`nSending Email" -Foregroundcolor Red
  $emailTo    = "<CHANGE THIS>"
  $emailFrom  = "<CHANGE THIS>"
  $SMTPServer = "<CHANGE THIS>"
  $msgsubject  = "PSM: Move Request Information " + $msgSubject1
  $message = New-Object Net.Mail.MailMessage($emailFrom, $emailTo, $msgsubject, $outHTML)
  $message.IsBodyHTML = $True
  Write-Host "- Adding log attachments" -Foregroundcolor Green
  $logfiles | ForEach{
    $_
    $attachment = New-Object System.Net.Mail.Attachment $_
    $message.Attachments.Add($attachment)
  }
  $smtp = New-Object Net.Mail.SmtpClient($SMTPServer)
  $smtp.Send($message)
  [environment]::SetEnvironmentVariable(‘BatchName’, ”,’User’)
#End

Move-RequestFromCSV.ps1 v1.5

#MSExchange

I know, I should have posted this a few weeks back .. w h a t e v e r

So this is Script #2 of my 3 script process to move mailboxes.

The process is here: Moving Mailboxes the easy way and script #1 is here: Pre-FlightChecks.ps1 v1.4

<#
.NOTES
NAME: Move-RequestFromCSV.ps1
AUTHOR: Paul Flaherty
Last Edit: v1.5 [8 April 2012]
v1.0 sometime    : A script it born
v1.1 20 May 2011 : Remove monitoring bit from script
v1.2 01 Jul 2011 : Updated to take the BatchName and CSVin as Parameters
v1.3 12 Oct 2011 : Updated with auto switch to skip the read-host question
v1.4 13 Jan 2012 : Updated with BadItemLimit and MRSServer switch
v1.5 08 Apr 2012 : Added Database switch
                    Due to some issue running the monitor script at the end, the script now sets
                    an Environment Variable that the monitor script can use.
.LINK
blogs.flaphead.com
.SYNOPSIS
This script creates Move Requests from a csv file
.DESCRIPTION
This script imports a list of smtp addresses from a CSV file and generates new move requests.
The move requests have either an auto generated or manual batch name assosciated with them.
If the CSV file that is being used has two columns: Email and TargetDatabase, the script
will use the TargetDatabase as the destination of the move request.
If no TargetDatabase is specified it will let Exchange do what it does best an decide
.OUTPUTS
  None
.EXAMPLE
Move-RequestFromCSV.ps1
Run the checks using the default csv file
.EXAMPLE
Move-RequestFromCSV.ps1 -Auto
Run the checks using the default csv file, but skips the "press any key" prompt once the
user validation is complete
This is specially useful if the script is used from either a batch file or scheduled task
.EXAMPLE
Move-RequestFromCSV.ps1 -CSVin <csvfile>
Uses the provided CSV file for the list of users
.EXAMPLE
$DB=@();Get-MailboxDatabase DATABASE.UK07* | ForEach{$db += $_.name}
Move-RequestFromCSV.ps1 -Database $db
This will use the Databases found in $db and round robin the move request to the databases
.PARAMETER CSVin
Input csv file that contains a list of email addresses and has a single heading of email
Optionally, if the CSV file contains an additional heading of TargetDatabase the script
will use this value to specify the target database for the move
.PARAMETER BatchName
This switch will remove existing move requests if they already exist
.PARAMETER Auto
Enabling this switch stops the need to "press any key" once the users have  been checked
.PARAMETER BadItemCount
This is the BadItemCount for MoveRequests
Default value is 30
.PARAMETER MRSServer
If needed you can specify a specific MRSServer to use
.PARAMETER Database
Use this parameter to select the databases you want the moverequest to use.
The script will use the list of databases in round robin stylie for each of the
move requests
#>
##########################################################################################
PARAM([String]$CSVin="C:MigrationListmiguserlist.csv",
      [String]$BatchName,
      [Switch]$Auto=$False,
      [String]$BadItemLimit=30,
      [String]$MRSServer="",
      [String]$Database="")
$Error.Clear()
CLS
#########################################################################################
$AppName = "Move-RequestFromCSV.ps1"
$AppVer  = "v1.5 [8 April 2012]"
##########################################################################################
#Load Common Variables
$today      = Get-Date
$RunUser    = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
##########################################################################################
#Display script name and version
##########################################################################################
Write-host " " $AppName -NoNewLine -foregroundcolor Green
Write-Host ": " $AppVer -foregroundcolor Green
Write-host "`n Run on $ServerName at $Today by $RunUser" -foregroundcolor Yellow
Write-Host "|——————————————————————-|`n"
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}
If ($BatchName -ne ""){
  $xBatchName = $BatchName
} ELSE {
  $xBatchName = get-date -Format "yyyy-MMM-dd_HH-mmm-ss"
  $xBatchName = "UK-" + $xBatchName
}
Write-Host "Batch Name:….$xBatchName"
Write-Host "BadItemLimit:..$BadItemLimit"
Write-Host "MRSServer:…..$MRSServer"
Write-Host "Database:……$Database"
$UserList   = @()
$doMove     = $True
Write-Host "Checking to see if $csvin exists"
If(test-path $csvin){
  Write-host "$csvin found" -foregroundcolor Green
}else{
  Write-host "$csvin not found" -foregroundcolor red
  exit
}
$users2Migrate = import-csv $csvin
Write-Host "Checking Users .."
ForEach($yUser in $Users2Migrate){
  $tmpemail = $yUser.email
  $xUser = "" | Select Name, Email, IsValid, MoveStatus, ActualStatus, PercentComplete, Duration, MailboxSize, ItemCount, BadItemsEncountered, SourceDatabase, TargetDatabase, MRSServerName, Transferred, TransferredPerMinute
  #Check User is Valid
  $tmpmbx = Get-Mailbox $tmpemail
  $tmpmbx
  $xUser.Name = $tmpmbx.name
  $xUser.Email = $tmpemail
  $xUser.IsValid =  $tmpmbx.isvalid
  $xUser.TargetDatabase = $yUser.TargetDatabase
  if ($tmpmbx.isvalid -eq $True){$xUser.MoveStatus = "Unknown"}ELSE{$xUser.MoveStatus = "ERR";$xUser.IsValid = $False}
  $UserList += $xUser
}
$tmpInvalid = $UserList | Where {$_.IsValid -eq $False}
$tmpValid   = $UserList | Where {$_.IsValid -ne $False}
Write-Host "`nThe following users are InValid and will not be migrated" -Foregroundcolor Red
$tmpInvalid | Select Email
Write-Host "`nThe following users will be migrated" -Foregroundcolor Green
$tmpValid | Select Email, TargetDatabase
If($aut
o -eq $False){read-host "Press Enter to Generate Move Requests or CTRL C to exit"}
If ($Database -ne ""){
  $tmpDatabaseArray = $database.split(" ")
  $i=0
  $DatabaseHashTable = @{}
  ForEach($item in $tmpDatabaseArray){; $DatabaseHashTable.Add($i,$item); $i++}
}
Write-Host "Creating Move Requests"
Write-Host "BadItemLimit: "$BadItemLimit
$i=0
$UserList | Where {$_.IsValid} | ForEach{
  $tmpemail = $_.Email
  $tmpTargetDatabase = $_.TargetDatabase
  Write-Host $tmpname " – " $tmpemail
  $tmpcmdlet = "Get-Mailbox $tmpemail | New-MoveRequest -BatchName $xBatchName -BadItemLimit:$BadItemLimit"
  If ($MRSServer -ne ""){Write-Host "MRSServer: $MRSServer"; $tmpcmdlet += " -MRSServer $MRSServer "}
  If ($Database -ne ""){
    $tmpDatabase = $tmpDatabaseArray[$i]
    Write-Host "Database: $tmpDatabase";
    $tmpcmdlet += " -TargetDatabase $tmpDatabase"
    If($i -ge ($DatabaseHashTable.count)-1){$i=0}ELSE{$i++}
  }ELSE{
    If($tmpTargetDatabase -ne $null){    $tmpcmdlet += " -TargetDatabase $tmpTargetDatabase "}
  }
  Invoke-Expression $tmpcmdlet
}
Get-MoveRequest -BatchName $xBatchName
$csvRename = ($csvin.split("."))[0] + "_" + $xBatchName + ".csv"
Write-host "Renaming csv files to $csvrename" -foregroundcolor yellow
Ren $csvin $csvRename
Write-Host "Waiting 30 Seconds while AD Catches up!"
Sleep 30
Write-Host "You can now run:"
Write-Host "C:PSMigrationMonitor-MoveRequest.ps1 "$xBatchName
Write-Host "-or-"
Write-Host "C:PSMigrationMonitor-MoveRequest.ps1"
[environment]::SetEnvironmentVariable(‘BatchName’, $xBatchName,’User’)
[environment]::GetEnvironmentVariable(‘BatchName’,’User’)
#End

Exchange Server 2010 MCM

#MSExchange #MCM

So I worked for Microsoft for 6 1/2 years.  Then I had my first contracting gig designing and installing Exchange 2007 for a UK Bank.  Since then I have moved along on to Exchange 2010.

The MCM or Ranger program as it used to known (to me anyway) is the best of the best, the top gun of Microsoft Exchange!  Even when at Microsoft and now being an independent contractor, it is something I have always wanted to do, but the sheer cost of the course, excluding flights, accommodation and the unpaid holiday I would have to take, make it too rich for my blood.I totally understand the course costing and are fine with that, I suspect the thing that gets me is that “some” Microsoft partners are able to get a discount on the course cost!  Hey wonder if it would be done over live meeting, to a global audience, sitting at home or in a Microsoft office?

So I thought, how about the “poor mans” version, it could be interesting, and exam and a qual lab are doable!  So I paid the $500 for 088-974 and booked it for 18th April at Prometric in London.  So I take a day off and rock up at the allotted time, only to find out that my exam has been cancelled.  They said they hadn’t had the “training” to be able to run the exam for me .. WTF i thought, I had a go and waited for them to call me to rearrange.

So yesterday 2nd May I took another day off and went for the exam.  It appears I was the first to take it at Prometrc in London, so they have to run around to try and work out what they needed to do.  This started with a biometric check, and the a hunt for passwords, but then I was off.

So 60 Questions and 3hrs (I need just over 2), with everything from DAG design to RBAC to Powershell cmdlets, Lync integration and UM. It wasn’t easy, some answers where calculated finger in air answers, but it wasn’t as hard as I thought.  I would probably give a 7/10.  What I was disappointed about is my exam had no mention of any RFCs and the documented readiness and exam preparation was over kill.  I reckon you could get away with a good read of Microsoft Exchange Server 2010 Inside Out.  I would be nice to have a more concise list of stuff to read!

If I am honest to myself, I would be surprised if I passed, but I am going to have to wait 30 days to find out.

Its funny,thinking about my expectations for the exam, I’m not sure what they were. I know a few Exchange MCM and one in particular is awesome.  I suppose I was expecting more real world questions, like a bunch of scenarios and design decisions to agree with or not, but that can be hard to articulate with multi-choice answers.  Maybe some of the questions where you drag and drop answers or arrange things in order would be good.  Some of questions, in the real world I would have just searched for the answer.

My impression of an MCM is someone that is like a Swiss army knife.  They can standing in front of customers and sell exchange, they can design it, they can support it.  They can also parachute in to hostile customers Winking smile and fix issues, all knowing they have the backing and support of the Exchange Product Team. .. Doesn’t that just make you want to be one?!  You could be one of the GI Joe or Jane of the Exchange world Disappointed smile

I’ll get off my soap box now.  If your up for it, give it a go! .. but be afraid very afraid, its not a walk in the park and you have to know your sh1t. The Exam and Lab rock in at $3,000 but just look at the rewards .. and I will keep dreaming of 3 weeks in Redmond doing it the “proper way”

Pre-FlightChecks.ps1 v1.4

#MsExchange

As promised script #1 of #3

<#
.NOTES
NAME: Pre-FlightChecks.ps1
AUTHOR: Paul Flaherty
Last Edit: v1.4 [11 April 2012]
v1.0 sometime    : A script it born
v1.1 22 Nov 2011 : Added OU to user list output
v1.2 07 Dec 2011 : Added console resize
v1.3 08 Mar 2012 : Added RemoveExistingMoveRequests switch
V1.4 11 Apr 2012 : Add autodiscovery of ADSite
.LINK
blogs.flaphead.com
.SYNOPSIS
This script performs some preflight checks for mailboxes moves
and should be run before a mailbox move
.DESCRIPTION
This script performs a number of checks getting ready for a mailbox move.
The first check is Test-MRSHealth for the a selected set of Hub/Cas servers that
may be involved in the mailbox moves
The second check is to make sure all the mailbox databases are homes on activation
preference #1
The final check read the CSVin file and checks:
  – The mailbox exists
  – If it is in a "valid" state
  – if it has any outstanding move requests
.OUTPUTS
  None
.EXAMPLE
pre-flightchecks.ps1
Run the checks using the default csv file
.EXAMPLE
pre-flightchecks.ps1 -CSVin <csvfile>
Uses the provided CSV file for the list of users
.EXAMPLE
pre-flightchecks.ps1 -RemoveExistingMoveRequests
Uses the default CSV and will remove any existing move requests
.PARAMETER CSVin
  Inpout csv file that contains a list of email addresses and has a single heading of email
.PARAMETER RemoveExistingMoveRequests
This switch will remove existing move requests if they already exist
#>
##########################################################################################
PARAM([String]$CSVin = "C:MigrationListmiguserlist.csv",
      [switch]$RemoveExistingMoveRequests = $False,
      [string]$ExchangeServerPrefix = "*",
      [string]$DatabaseFilter = "*")
$Error.Clear()
CLS
#########################################################################################
$AppName    = "Pre-FlightChecks.ps1"
$AppVer     = "v1.4 [11 Apr 2012]"
$ServerName = hostname
$today      = Get-Date
$RunUser    = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
##########################################################################################
#Display script name and version
##########################################################################################
Write-host " " $AppName -NoNewLine -foregroundcolor Green
Write-Host ": " $AppVer -foregroundcolor Green
Write-host "`n Run on $ServerName at $Today by $RunUser" -foregroundcolor Yellow
Write-Host "|——————————————————————-|`n"
$ADSite = ([System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()).Name
Write-Host "CSVin:………………….."$CSVin
Write-Host "RemoveExistingMoveRequests:.."$RemoveExistingMoveRequests
Write-Host "ExchangeServerPrefix:…….."$ExchangeServerPrefix
Write-Host "DatabaseFilter:………….."$DatabaseFilter
Write-Host "Current AD Site:…………."$ADSite
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}
Write-Host "`n`nChecking MRSHealth" -Foregroundcolor Green
Write-Host "Getting a list of Client Access Servers [$ExchangeServerPrefix]"
$tmpCmdLet = "Get-ExchangeServer $ExchangeServerPrefix | Where{(##_.IsClientAccessServer) -AND (##_.IsE14OrLater)} | Sort Name"
$tmpCmdLet = $tmpCmdLet.Replace("##","$")
$tmpCAS = Invoke-Expression $tmpCmdLet
IF($ExchangeServerPrefix -eq "*"){
  Write-Host "Using ADSite: "$ADSite
  $tmpCAS = $tmpCAS | where {$_.Site -like "*$AdSite*"}
}
ForEach($item in $tmpCAS){
  Test-MRSHealth $item.Name | select Identity, Check, Passed, IsValid, Message | ft -AutoSize -wrap
}
read-host "Review the text above and Press Enter to Continue"
Write-Host "`nChecking Databases [$DatabaseFilter]"  -Foregroundcolor Green
IF($DatabaseFilter  -eq "*"){
  Write-Host "Using ADSite: "$ADSite
  $tmpCmdLet = "Get-ExchangeServer $ExchangeServerPrefix | Where{(##_.IsMailboxServer) -AND (##_.IsE14OrLater)} | Sort Name"
  $tmpCmdLet = $tmpCmdLet.Replace("##","$")
  $tmpMBX = Invoke-Expression $tmpCmdLet
  $tmpMBX = $tmpMBX | where {$_.Site -like "*$AdSite*"}
  $tmpMDB = $tmpMBX | Get-MailboxDatabase | Sort Name -Unique
}ELSE{
  $tmpMDB = Get-MailboxDatabase $DatabaseFilter | Sort Name
}
$tmpMDB | FOREACH {$db=$_.Name; $xNow=$_.Server.Name ;$dbown=$_.ActivationPreference| Where {$_.Value -eq 1};  Write-Host $db "on" $xNow "Should be on" $dbOwn.Key -NoNewLine; If ( $xNow -ne $dbOwn.Key){Write-host " WRONG" -ForegroundColor Red; }ELSE{Write-Host " OK" -Foregroundcolor Green}}
read-host "Review the text above and make sure you have green OK.  Then Press Enter to Continue"
Write-Host "`nChecking Mailboxes"  -Foregroundcolor Green
Write-Host "Using " $csvin
If(test-path $csvin){
  Write-host "$csvin found" -foregroundcolor Green
  $users2Migrate = import-csv $csvin
}ELSE{
  Write-host "$csvin not found" -foregroundcolor red
  exit
} #If(test-path $csvin)
$users = @()
ForEach($xUser in $Users2Migrate){
  $tmpemail = $xUser.email
  Write-Host $tmpemail
  $tUser = Get-Mailbox $tmpemail
  $tmpusers = $tUser | Select PrimarySmtpAddress, @{Expression={$_.Database.Name};Label="Database"}, IsValid, @{Expression={$_.OrganizationalUnit.replace("ad.bgep.co.uk/Offices/","")};Label="OrganizationalUnit"}
  $MR = Get-MoveRequest $tmpemail  -ErrorAction silentlycontinue
  IF($MR -ne $Null){
    Write-Host "!!Outstanding MoveRequest!! .. Run Remove-MoveRequest $tmpemail BEFORE you continue" -foregroundcolor red
    IF($RemoveExistingMoveRequests){Remove-MoveRequest $tmpemail -confirm:$False}
  }
  $users += $tmpUsers
  If ($tmpUsers.IsValid -eq $False){
    Write-Host "`n## Invalid User ##" -Foregroundcolor Red;
    $invalid=$tUser.validate();
    write-host "Property Name:.."$inValid
[0].PropertyName;
    write-host "Description:…."$inValid[0].Description;
    Write-host "######`n"-Foregroundcolor Red
  }
}
$users | ft –auto

#END
#########################################################################################

Enable-PersonalArchive.ps1 v1.2

#MSExchange

During migrations to Exchange 2010,  we are also using a tool call Transvault to export data from Enterprise Vault in to Exchange.  As you would expect this is a massive task.  Originally we moved the data in to the primary mailbox, but with laptop rebuilds and slow links while syncing the data we switched Transvault to move the data in to an Exchange Personal Archive, and it works like a dream.

This results in me having to enable the personal archive for different users and as expected I scripted it and here it is.  The help text at the top should explain everything you need to know .. Enjoy

<#
.NOTES
  NAME: Enable-PersonalArchive.ps1
  AUTHOR: Paul Flaherty
  Last Edit: v1.2 [4 April 2012]
  v1.0 21 Feb 2012 : A script is born
  v1.1 02 Mar 2012 : Added switches to allow only applying of policies
  v1.2 04 Apr 2012 : Added mailbox switch
.LINK
  blogs.flaphead.com 
.SYNOPSIS
  Enable Exchange 2010 Personal Archive from a CSV file
.DESCRIPTION
  This Script gets reads a CSV file that contains email addresses and enabled
  the Exchange 2010 Personal Archive.
.PARAMETER CSVIN
  Location of a csv file that contains a single column for email
  Default file is C:MigrationListArchivelist.csv
.PARAMETER RetentionPolicy
  Name of the Retention Policy to apply
.PARAMETER CreateArchive
  Create the archive
.PARAMETER ApplyPolicy
  Specified Retention Policy
.PARAMETER StartMFA
  Start the ManageFolderAssistant for the users
  Automatically enabled if the ApplyPolicy switch is selected
.PARAMETER ListRetentionPolicies
  Shows a list of available retention Policies
.PARAMETER Mailbox
  Specify an individual mailbox instead of using the CSV file
.EXAMPLE
  Enable-PersonalArchive.ps1
  Enable the Personal Archive for all the users in C:MigrationListArchivelist.csv
  and applies the "Default Archive and Retention Policy" retention policy
.EXAMPLE
  Enable-PersonalArchive.ps1 -CreateArchive
  Create personal archives using the default retention policy for all the users in C:MigrationListArchivelist.csv
.EXAMPLE
  Enable-PersonalArchive.ps1 -ListRetentionPolicies
  Displays a list of the available retention policies
#>
#########################################################################################
PARAM([String]$CSVin="C:MigrationListArchivelist.csv",
      [String]$RetentionPolicy="Default Archive and Retention Policy",
      [Switch]$ListRetentionPolicies=$false,
      [Switch]$EnableArchive,
      [Switch]$ApplyPolicy,
      [Switch]$StartMFA,
      [String]$Mailbox)
$Error.Clear()
#########################################################################################
$AppName    = "Enable-PersonalArchive.ps1"
$AppVer     = "v1.2 [4 April 2012]"
$ServerName = hostname
$today      = Get-Date
$RunUser    = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
##########################################################################################
#Display script name and version
##########################################################################################
Write-host " " $AppName -NoNewLine -foregroundcolor Green
Write-Host ": " $AppVer -foregroundcolor Green
Write-host "`n Run on $ServerName at $Today by $RunUser" -foregroundcolor Yellow
Write-Host "|——————————————————————-|`n"
Write-Host "PARAMETERS:" -Foregroundcolor Green
Write-host "ListRetentionPolicies:.. " $ListRetentionPolicies
Write-host "EnableArchive:……….." $EnableArchive
Write-host "ApplyPolicy:…………." $ApplyPolicy
Write-Host "StartMFA:……………." $StartMFA
Write-host "CSVin: ………………" $CSVin
Write-host "RetentionPolicy:………" $RetentionPolicy
write-Host "Mailbox:…………….." $Mailbox
##########################################################################################
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}
If($ListRetentionPolicies){
  Write-Host "Available Retention Policies" -Foregroundcolor Blue
  Get-RetentionPolicy | select Name
  Exit
} #If($ListRetentionPolicies)
Write-Host ""
If ($Mailbox -eq ""){
  Write-Host "Reading $CSVin"
  $tmpUsers = Import-Csv $CSVin
  $tmpUserCnt =0;$tmpUsers | ForEach{$tmpUserCnt ++}
  Write-Host $tmpUserCnt -Foregroundcolor Green -NoNewLine
  Write-Host " Users found"
}ELSE{
  Write-Host "Individual Mailbox spelected [$Mailbox]"
  $tmpusers = "" | Select email
  $tmpusers.email = $mailbox
} #If ($Mailbox -eq "")
If($EnableArchive -eq $True){
  #First Loop to enable archive
  Write-Host "`nEnabling Archive" -foregroundcolor Yellow
  ForEach($user in $tmpUsers){Write-Host "`n"$user.email; Get-Mailbox $user.email | Enable-Mailbox -Archive}
  Write-Host "`nWaiting 30 Seconds while AD Catches up!`n" -Foregroundcolor Blue
  Sleep 30
} #If($EnableArchive -eq $True)
#Loop to enable policy
IF($ApplyPolicy -eq $True){
  $StartMFA = $True
  Write-Host "`nSetting Retention Policy: " -NoNewLine
  Write-Host $RetentionPolicy -foregroundcolor Yellow
  ForEach($user in $tmpUsers){
    Write-Host $user.email
    Get-Mailbox $user.email | Set-Mailbox -RetentionPolicy $RetentionPolicy
  } #ForEach($user
  Write-Host "`nWaiting 30 Seconds while AD Catches up!`n" -Foregroundcolor Blue
  Sleep 30
} #IF($ApplyPolicy -eq $True)
#Loop to Start the managedfolderassistant
IF($StartMFA -eq $True){
  Write-Host "`nStart-ManagedFolderAssistant: " -foregroundcolor Yellow
  ForEach($user in $tmpUsers){
    Write-Host $user.email
    Get-Mailbox $user.email | Start-ManagedFolderAssistant
  } #ForEach($user
} #IF($StartMFA -eq $True)
#Loop for the results
Write-Host "`n`nResults" -foregroundcolor Yellow
$tmpResults =@()
ForEach($user in $tmpUsers){
  #Write-Host
$user.email
  $tmpResults += Get-Mailbox $user.email | select PrimarySmtpAddress, Database, RetentionPolicy, ArchiveDatabase
} #ForEach($user
$tmpResults | ft -auto
If ($Mailbox -eq ""){
  If ($EnableArchive -or $ApplyPolicy){
    $xBatchName = get-date -Format "yyyy-MMM-dd_HH-mmm-ss"
    $csvRename = ($csvin.split("."))[0] + "_" + $xBatchName + ".csv"
    Write-host "Renaming csv files to $csvrename" -foregroundcolor yellow
    Ren $csvin $csvRename
  } #If ($EnableArchive -or $ApplyPolicy)
} #If ($Mailbox -eq "")
#END
#########################################################################################

Moving Mailboxes the easy way

#MSExchange

So I have moved quite a few mailboxes from Exchange 2003 to Exchange 2010.  I have shared some of my pain (http://flaphead.dns2go.com/?p=2917) and wanted to share what I actually do.

Those that know me, would know that I am not too bad with Powershell Winking smile and the obvious thing to do is to script these moves.  So probably on Friday, I will post the scripts, but this is how things work for me.

So before we start, the reason for the different scripts is #1 so they can be run by themselves, and #2 automation is a great thing, but I am a control freak and want to be able to control things just in case things go pear shaped!

It all starts with a list of email address. I put them in a csv file with a single heading of email

I then run my first script: pre-flightchecks.ps1

This script checks the AD site that I am running the script from, enumerated the CAS servers and runs Test-MRSHealth on each CAS server to make sure its happy. 

Next it checks to make sure all the databases in the AD site are mounted on the server that has an Activation Preference of 1.  A bit of qdos for this, is that the code can be found on page 491 of Microsoft Exchange 2010 Inside Out by Tony Redmond [Thanks Tony for the mention Open-mouthed smile]

Finally the script reads the CSV file and checks to make sure all the mailboxes have IsValid = True.  Due to the age of the system I am migrating at the moment, we are find a lot of mailboxes, groups and contacts that are “invalid” as far as Exchange 2007 & 2010 are concerned.  The most common issues are bad Alias Names (having commas in), database names that don’t exist anymore (don’t ask!), invalid UPNs and display names with trailing whitespace.  Oh the other thing it checks is to see if the mailboxes already have move requests associated with them!

So once complete we know that MRS is cool, the database are where they are supposed to be and the mailboxes are valid

Script #2 is Move-RequestFromCSV.ps1

As the name suggests it takes the CSV file I have with email addresses and then generates move requests.  It does a few other smart things, but the kinda cool thing is that is autogenerates a BatchName and all the moves are given the same batchname.

Now if you have not played with move requests before, the batchname is excellent and Devon put me on this a a long while back.  What is cool is that you can then reference all the move requests in a batch and treat them as one .. really cool

So the move requests are in flight, what next

Well Script #3 – Monitor-MoveRequest.ps1

I just love this script.  In an nutshell, this uses the BatchName from the previous script and just sits and monitors the progress.

Something Ari has been beating me up about for a while, is that where I am now we don’t have SCOM, so I have written a poor man monitoring website with static html pages that are updated by a handful of powershell scripts.  I must admit it is rather bloody cool.  Maybe one day I will get round to doc’ing it!

So the monitor script generates a html page every 20 seconds or so until the moves are complete.  All you have to do it open the html page and watch.  It is a bit like watching paint dry.  [On a side note what is interesting is how tollerate move requests are to bad slow network links.  I move 60 odd mailboxes over the weekend where it peaked at a massive 3MB per min!  Took over 8hrs to move a 1.9TB mailbox!]

Once complete it sends the html page in an email as the message body as well as the reports from the move requests.  These reports are golden, especially if you the move has failed for any reason.  If you want to see normally you want view a move request in the Exchange Management Console, or run somethng like (Get-MoveRequestStatistics <user email> -IncludeReport).report

An that’s it, only another 7000 odd to go now!

Oh I almost forgot!  So driving home last night I was thinking about how to distribute mailboxes across databases [Coding in  my head is scary].  Yeah I know this is old skool and that plenty of people have done this before.  Steve’s example is good [http://www.stevieg.org/2010/09/balancing-exchange-databases/] BUT I couldn’t resist, and Mitch has been on at me to do something, so I did.

I got my Wife involved too, and started with an explanation of pots filled with coins and that I wanted the same number of coins in each pot.  She came up with a idea (after I got the WTF are you taking about look) and so last night v0.1 of  Get-DatabaseDistribution.ps1 was born

So you think coding while ur driving is bad, well last night instead of counting sheep I was working on part 2.  So I explained this to my wife this morning, as the same pots filled with coins, but now I want to make sure all the pots has the same coin value in them. I think I burned her out of ideas last night and only got a WTF this morning.

BUT I think I have cracked it.  Been testing it today with both Distribute by Count and MB and it looks good.  What is even better is that is generates a CSV file, and I can use the combination of the 3 scripts above.

Looking to share the scripting love probably Friday

l8trs

Microsoft #Forefront Protection for Exchange Server detected a virus

#MSExchange

Been seeing a lot of these this week, all with different senders

Microsoft Forefront Protection for Exchange Server has detected a virus.
Virus name: "Trojan-Spy.HTML.Fraud.gen"
File name: "Body of Message"
State: "Removed"
Subject line: "FW: Receipt for Your Payment to AU-AdCommerce-EOM@ebay.com"
Sender: "Jacki Seers"
Scan job: "Transport"

Thankfully Winking smile forefront removes it!