Showing posts with label Scheduled Tasks. Show all posts
Showing posts with label Scheduled Tasks. Show all posts

Monday, November 28, 2011

Exchange Mailbox Report

Whether in an environment I am familiar or one that is new to me, information about it is important to my management of it. In a large environment, having timely information readily available is key to making good decisions. It can take hours to generate that data and that decision might be time sensitive enough that I can't wait. That is why I run daily, weekly and monthly audits of key systems and store them away for later retrieval. Microsoft Exchange is just one of those systems where I am constantly making business decisions based on daily audits. Who are the employees with the biggest mailboxes? Which server has the most messages? How active is a mailbox? Which mailbox server is under-utilized and which is overloaded? What is the combined storage used by all messages in the organization?

Below is a PowerShell script that I have developed over time to provide relevant information about Exchange mailbox servers and the employee mailboxes contained within. It assumes that you have at least the Exchange 2007 PSSnapin available to execute but will detect if the Exchange 2010 PSSnapin is available and load it. Because I run into diverse installations of Exchange, the script can report on Exchange 2003, Exchange 2007 and Exchange 2010 mailbox servers. The largest environment I have successfully tested had all three versions in an Exchange organization, 40 mailbox servers and covered installations in multiple countries worldwide. The end result will be a nicely formatted Excel spreadsheet with an overview worksheet hyperlinking to a worksheet for each of the mailbox servers with at least 1 mailbox in the workbook. Because I can run into new and unknown environments, the PowerShell script has fairly detailed logging. You need the permission to read mailboxes on each of the Exchange mailbox servers with security context you execute the script. The logging will catch permission errors and log them. Unmodified, it will store the logs, work files and final spreadsheet in child directories of the root of the executed script. Review the Set-Variable statements after the Functions and update them to your needs. In its current state, it will not delete work files allowing you to see the raw product used to produce the Excel spreadsheet.
#--------------------------------------------------------------------------------------------------#
Function Get-LocalDomainController($objectDomain) {
 return ([System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()).Servers | Where-Object { $_.Domain.Name -eq $objectDomain } | ForEach-Object { $_.Name } | Select-Object -first 1
}
#--------------------------------------------------------------------------------------------------#
Function Get-ObjectADDomain($distinguishedName) {
 return ((($distinguishedName -replace "(.*?)DC=(.*)",'$2') -replace "DC=","") -replace ",",".")
}
#--------------------------------------------------------------------------------------------------#
Function Get-ActiveDirectoryObject($distinguishedName) {
 return [ADSI]("LDAP://" + (Get-LocalDomainController (Get-ObjectADDomain $distinguishedName)) + "/" + ($distinguishedName -replace "/","\/"))
}
#--------------------------------------------------------------------------------------------------#
Function Write-LogEntry($logEntry,$logFile) {
 Add-Content -Path $logFile -Value ((Get-TimeStamp) + " $logEntry")
}
#--------------------------------------------------------------------------------------------------#
Function Write-LogEntryLineBreak($logFile) {
 Write-LogEntry "#-----------------------------------------------------------------------------------#" $logFile
}
#--------------------------------------------------------------------------------------------------#
Function Get-TimeStamp {
 return (Get-Date -format yyyyMMddHHmmss)
}
#--------------------------------------------------------------------------------------------------#
Function Get-DayStamp {
 return (Get-Date -format yyyyMMdd)
}
#--------------------------------------------------------------------------------------------------#
Function Get-RunTime($runTime) {
 $runTimeHours = ($runTime.Hours).ToString()
 $runTimeMinutes = ($runTime.Minutes).ToString()
 $runTimeSeconds = ($runTime.Seconds).ToString()
 if($runTimeHours.Length -eq 1) {
  $runTimeHours = "0$runTimeHours"
 }
 if($runTimeMinutes.Length -eq 1) {
  $runTimeMinutes = "0$runTimeMinutes"
 }
 if($runTimeSeconds.Length -eq 1) {
  $runTimeSeconds = "0$runTimeSeconds"
 }
 return ($runTimeHours + ":" + $runTimeMinutes + ":" + $runTimeSeconds)
}
#--------------------------------------------------------------------------------------------------#
Function Initialize-Directory($directoryPath) {
 if(!(Test-Path -Path $directoryPath)) {
  New-Item -Path $directoryPath -Type Directory | Out-Null
 }
 if(Test-Path -Path $directoryPath) {
  $success = $true
 } else {
  $success = $false
 }
 return $success
}
#--------------------------------------------------------------------------------------------------#
Function Load-ExchangeSnapin {
 $registeredPsSnapins = @(Get-PSSnapin -registered | ForEach-Object { $_.Name })
 $loadedPsSnapins = Get-PSSnapin
 if($registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.E2010") {
  $snapinLoaded = $false
  foreach($snapin in $loadedPsSnapins) {
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.E2010") {
    $snapinLoaded = $true
   }
  }
  if($snapinLoaded -eq $false) {
   Add-PSSnapin -Name 'Microsoft.Exchange.Management.PowerShell.E2010'
  }
 } elseif($registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.Admin" -and $registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.Support") {
  $adminPsSnapinLoaded = $false
  $supportPsSnapinLoaded = $false
  foreach($snapin in $loadedPsSnapins) {
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.Admin") {
    $adminPsSnapinLoaded = $true
   }
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.Support") {
    $supportPsSnapinLoaded = $true
   }
  }
  if($adminPsSnapinLoaded -eq $false) {
   Add-PSSnapin -Name "Microsoft.Exchange.Management.PowerShell.Admin"
  }
  if($supportPsSnapinLoaded -eq $false) {
   Add-PSSnapin -Name "Microsoft.Exchange.Management.PowerShell.Support"
  }
 }
}
#--------------------------------------------------------------------------------------------------#
Function Unload-ExchangeSnapin {
 $registeredPsSnapins = @(Get-PSSnapin -registered | ForEach-Object { $_.Name })
 $loadedPsSnapins = Get-PSSnapin
 if($registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.E2010") {
  $snapinLoaded = $false
  foreach($snapin in $loadedPsSnapins) {
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.E2010") {
    $snapinLoaded = $true
   }
  }
  if($snapinLoaded -eq $true) {
   Remove-PSSnapin -Name 'Microsoft.Exchange.Management.PowerShell.E2010'
  }
 } elseif($registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.Admin" -and $registeredPsSnapins -contains "Microsoft.Exchange.Management.PowerShell.Support") {
  $adminPsSnapinLoaded = $false
  $supportPsSnapinLoaded = $false
  foreach($snapin in $loadedPsSnapins) {
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.Admin") {
    $adminPsSnapinLoaded = $true
   }
   if($snapin.name -eq "Microsoft.Exchange.Management.PowerShell.Support") {
    $supportPsSnapinLoaded = $true
   }
  }
  if($adminPsSnapinLoaded -eq $true) {
   Remove-PSSnapin -Name "Microsoft.Exchange.Management.PowerShell.Admin"
  }
  if($supportPsSnapinLoaded -eq $true) {
   Remove-PSSnapin -Name "Microsoft.Exchange.Management.PowerShell.Support"
  }
 }
}
#--------------------------------------------------------------------------------------------------#
Function Get-UserInformation($legacyExchangeDn) {
 $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
 $directorySearcher.SearchRoot = (New-Object System.DirectoryServices.DirectoryEntry("GC://$forestRootDn"))
 $directorySearcher.Filter = ("(&(objectclass=user)(objectCategory=person)(legacyExchangeDN=$legacyExchangeDn))")
 $directorySearcher.PropertiesToLoad.Clear()
 $directorySearcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
 $searchResult = $directorySearcher.FindOne()
 $directorySearcher.Dispose()
 if($searchResult) {
  return $searchResult.Properties.Item("distinguishedName")[0]
 } else {
  return $null
 }
}
#--------------------------------------------------------------------------------------------------#
Function Get-StorageLimitInfo($storageLimitInfo, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes) {
 if($storageLimitInfo -eq 1) {
  $storageLimitInfo = "Below Limit"
 } elseif($storageLimitInfo -eq 2) {
  $storageLimitInfo = "Issued Warning"
  $issuedWarning++
 } elseif($storageLimitInfo -eq 4) {
  $storageLimitInfo = "Prohibited Send"
  $prohibitedSend++
 } elseif($storageLimitInfo -eq 8) {
  $storageLimitInfo = "Unlimited Mailbox"
  $unlimitedMailboxes++
 } elseif($storageLimitInfo -eq 16) {
  $storageLimitInfo = "Mailbox Disabled"
  $mailboxDisabled++
 } else {
  $storageLimitInfo = "Disabled Account w/ No Permissions"
  $orphanedMailboxes++
 }
 return $storageLimitInfo, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes, $orphanedMailboxes
}
#--------------------------------------------------------------------------------------------------#
Function Get-StorageLimitStatus($storageLimitStatus, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes) {
 if($storageLimitStatus -eq  "BelowLimit") {
  $storageLimitStatus = "Below Limit"
 } elseif($storageLimitStatus -eq  "IssueWarning") {
  $storageLimitStatus = "Issued Warning"
  $issuedWarning++
 } elseif($storageLimitStatus -eq  "ProhibitSend") {
  $storageLimitStatus = "Prohibited Send"
  $prohibitedSend++
 } elseif($storageLimitStatus -eq  "NoChecking") {
  $storageLimitStatus = "Unlimited Mailbox"
  $unlimitedMailboxes++
 } elseif($storageLimitStatus -eq  "MailboxDisabled") {
  $storageLimitStatus = "Mailbox Disabled"
  $mailboxDisabled++
 } else {
  $storageLimitStatus = "Disabled Account w/ No Permissions"
  $orphanedMailboxes++
 }
 return $storageLimitStatus, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes, $orphanedMailboxes
}
#--------------------------------------------------------------------------------------------------#
Function Get-ExcelTableStyle($current,$tableStyles) {
 $tableStyle = $tableStyles[$current]
 if($current -ge ($tableStyles.Count - 1)) {
  $current = 0
 } else {
  $current++
 }
 return $current, $tableStyle
}
#--------------------------------------------------------------------------------------------------#
Set-Variable -name forestRootDn -option Constant -value ([ADSI]("LDAP://" + (([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).name) + "/rootDSE")).defaultNamingContext
Set-Variable -name ping -option Constant -value (New-Object -typeName System.Net.NetworkInformation.Ping)
#--------------------------------------------------------------------------------------------------#
# Typically I set to 2x larger than the standard mailbox size.
Set-Variable -name largeMailboxSize -option Constant -value (1GB / 1KB)
#--------------------------------------------------------------------------------------------------#
Set-Variable -name logDirectory -option Constant -value "$pwd\Logs"
#--------------------------------------------------------------------------------------------------#
# Must be an absolute path for Excel.
Set-Variable -name workDirectory -option Constant -value ($env:temp + "\Work")
#--------------------------------------------------------------------------------------------------#
# Must be an absolute path for Excel.
Set-Variable -name reportDirectory -option Constant -value "$pwd\Reports"
#--------------------------------------------------------------------------------------------------#
Set-Variable -name logFilename -option Constant -value ($logDirectory + "\"  + (Get-DayStamp) + "-" + ($myinvocation.mycommand.name -Replace ".ps1",".txt"))
Set-Variable -name masterExchangeServerFilename -option Constant -value ($workDirectory + "\Master Exchange Servers List.csv")
Set-Variable -name filteredExchangeServerFilename -option Constant -value ($workDirectory + "\Filtered Exchange Servers List.csv")
Set-Variable -name excelFilename -option Constant -value ($reportDirectory + "\Exchange Mailbox Report-" + (Get-DayStamp) + ".xlsx") # Assuming Excel 2007 or better
#--------------------------------------------------------------------------------------------------#
# Style Cheat Sheet in French/English: http://msdn.microsoft.com/fr-fr/library/documentformat.openxml.spreadsheet.tablestyle.aspx
Set-Variable -name excelTableStyles -option Constant -value @("TableStyleMedium16","TableStyleMedium17","TableStyleMedium18","TableStyleMedium19","TableStyleMedium20","TableStyleMedium21")
#--------------------------------------------------------------------------------------------------#
Set-Variable -name excelCurrentTableStyle -value 0
#--------------------------------------------------------------------------------------------------#
# Set to $false to review data generated by the mailbox scans.
# Set to $true to delete the work directory at completion.
Set-Variable -name cleanupWorkDirectory -option Constant -value $false
#--------------------------------------------------------------------------------------------------#
# If $true, no screen output will be displayed. Best for a scheduled task.
# $false if you like staring at progress bars along with superfluous and gratuitous information.
Set-Variable -name beQuiet -option Constant -value $false
#--------------------------------------------------------------------------------------------------#
Set-Variable -name sheetNumber -value 1
Set-Variable -name serverCount -value 1
#--------------------------------------------------------------------------------------------------#
$startTime = Get-Date
if(!$beQuiet) { Clear-Host }
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Setup Environment" -percentComplete "50" }
if((Initialize-Directory $logDirectory) -eq $false) {
 Write-Host "Unable to access $logDirectory for writing logs. Exiting." -Foregroundcolor Red
 Set-Content -path ("$pwd\" + ($myinvocation.mycommand.name -Replace ".ps1","") + " MAJOR ERROR.txt") -value "Unable to access $logDirectory for writing logs. Exiting."
 exit
}
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Setup Environment" -percentComplete "75" }
if((Initialize-Directory $workDirectory) -eq $false) {
 Write-Host "Unable to access $workDirectory for writing temp files. Exiting." -Foregroundcolor Red
 Write-LogEntry "[ERROR] Unable to access $workDirectory for writing temp files. Exiting." $logFilename
 exit
}
if((Initialize-Directory $reportDirectory) -eq $false) {
 Write-Host "Unable to access $reportDirectory for writing the Excel file. Exiting." -Foregroundcolor Red
 Write-LogEntry "[ERROR] Unable to access $reportDirectory for writing the Excel file. Exiting." $logFilename
 exit
}
Write-LogEntry ("Log Directory: " + $logDirectory) $logFilename
Write-LogEntry ("Work Directory: " + $workDirectory) $logFilename
Write-LogEntry ("Report Directory: " + $reportDirectory) $logFilename
Write-LogEntry ("Log Filename: " + $logFilename) $logFilename
Write-LogEntry ("Master Server List Filename: " + $masterExchangeServerFilename) $logFilename
Write-LogEntry ("Filtered Server List Filename: " + $filteredExchangeServerFilename) $logFilename
Write-LogEntry ("Excel Filename: " + $excelFilename) $logFilename
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Setup Environment" -percentComplete "100" }
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Loading Exchange Snapin" -percentComplete "50" }
Write-LogEntry "Loading Exchange Snapin" $logFilename
Load-ExchangeSnapin
Write-LogEntry "Exchange Snapin loaded" $logFilename
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Loading Exchange Snapin" -percentComplete "100" }
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Finding all Exchange Servers" -percentComplete "50" }
Write-LogEntry "Discover Exchange Servers" $logFilename
$exchangeServers = @((Get-ExchangeServer | Where-Object { $_.ServerRole -match "Mailbox" -or $_.ServerRole -eq "None" } ) | Select-Object -property Name,Fqdn,AdminDisplayVersion,Edition,ExchangeVersion,DistinguishedName,WhenChanged,WhenCreated | Sort-Object -property Name)
Write-LogEntry ($exchangeServers.Count.ToString() + " Exchange Servers found") $logFilename
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Finding all Exchange Servers" -percentComplete "100" }
$serversScanned = 0
foreach($exchangeServer in $exchangeServers) {
 $serversScanned++
 $exchangeServer.Name = ($exchangeServer.Name).ToUpper()
 $exchangeServer.Fqdn = ($exchangeServer.Fqdn).ToLower()
 Write-LogEntry $exchangeServer.Fqdn $logFilename
 if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Gathering information on " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
 Write-LogEntry "IPV4 Address Discovery" $logFilename
 try {
  $hostAddresses = [System.Net.Dns]::GetHostAddresses($exchangeServer.Fqdn)
     Add-Member -inputObject $exchangeServer -type NoteProperty -name "ipV4Address" -value $hostAddresses[0].IPAddressToString
 } catch {
  Add-Member -inputObject $exchangeServer -type NoteProperty -name "ipV4Address" -value "0.0.0.0"
 }
 Write-LogEntry ("IPV4 Address:" + $exchangeServer.ipV4Address) $logFilename
 Write-LogEntry "Testing if server online" $logFilename
 if($exchangeServer.ipV4Address -ne "0.0.0.0") {
  $onlineStatus = $ping.send($exchangeServer.ipV4Address)
  if($onlineStatus.Status -eq "Success") {
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "online" -value $true
  } else {
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "online" -value $false
  }
 } else {
  Add-Member -inputObject $exchangeServer -type NoteProperty -name "online" -value $false
 }
 Write-LogEntry ("Online status:" + $exchangeServer.online) $logFilename
 Write-LogEntry "Discovering number of databases" $logFilename
 if($exchangeServer.online -eq $true) {# -and $exchangeServer.configurationDistinguishedName -ne "Unknown" -and $exchangeServer.version -ne "Unknown") {
  if($exchangeServer.AdminDisplayVersion -match "Version 6.") {
   $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher
   $directorySearcher.SearchRoot = (New-Object System.DirectoryServices.DirectoryEntry("GC://" + $exchangeServer.DistinguishedName))
   $directorySearcher.Filter = "(&(objectclass=msExchStorageGroup))"
   $directorySearcher.PropertiesToLoad.Clear()
   $directorySearcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
   $exchangeSearchResults = $directorySearcher.FindAll()
   $directorySearcher.Dispose()
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "numberOfStores" -value $exchangeSearchResults.Count
  } else {
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "numberOfStores" -value @(Get-MailboxDatabase -server $exchangeServer.Fqdn).Count
  }
 } else {
  Add-Member -inputObject $exchangeServer -type NoteProperty -name "numberOfStores" -value "0"
 }
 if($exchangeServer.numberOfStores -eq "" -or $exchangeServer.numberOfStores -eq $null) { $exchangeServer.numberOfStores = 0 }
 Write-LogEntry ("Databases:" + $exchangeServer.numberOfStores) $logFilename
 Write-LogEntryLineBreak $logFilename
}
$mailboxes = @()
$serversScanned = 0
foreach($exchangeServer in $exchangeServers) {
 Write-LogEntry ("Scanning Mailboxes on " + $exchangeServer.Fqdn) $logFilename
 $serversScanned++
 if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Scanning Mailboxes: " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
 $mailboxesScanned = 0
 $mailboxCount = 0
 $totalStorage = 0
 $totalMessages = 0
 $issuedWarning = 0
 $prohibitedSend = 0
 $mailboxDisabled = 0
 $unlimitedMailboxes = 0
 $orphanedMailboxes = 0
 $largeMailboxes = 0
 $userMailboxes = @()
 if($exchangeServer.numberOfStores -eq 0) {
  Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxCount" -value $mailboxCount
  Write-LogEntry ("There are no mailboxes on " + $exchangeServer.Fqdn) $logFilename
  Write-LogEntryLineBreak $logFilename
  continue
 }
 if($exchangeServer.AdminDisplayVersion -match "Version 6.") {
  Write-LogEntry "Exchange 2003 Server" $logFilename
  if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Loading Mailboxes on " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
  $error.clear()
  try {
   $mailboxes = @(Get-WmiObject -ComputerName $exchangeServer.Fqdn -Namespace "root\MicrosoftExchangeV2" -Class Exchange_Mailbox)
  } catch {
   Write-LogEntry ("[ERROR] Unable to obtain mailboxes on " + $exchangeServer.Fqdn) $logFilename
   foreach($errorEntry in $error) {
    Write-LogEntry $errorEntry $logFilename
   }
   $mailboxes = @()
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalMessages" -value $totalMessages
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalStorage" -value $totalStorage
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxCount" -value $mailboxCount
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "issuedWarning" -value $issuedWarning
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "prohibitedSend" -value $prohibitedSend
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxDisabled" -value $mailboxDisabled
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "unlimitedMailboxes" -value $unlimitedMailboxes
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "orphanedMailboxes" -value $orphanedMailboxes
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "largeMailboxes" -value $largeMailboxes
   Write-LogEntryLineBreak $logFilename
   continue
  }
  if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Reviewing Mailboxes on " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
  Write-LogEntry ($mailboxes.Count.ToString() + " mailboxes found") $logFilename
  foreach($mailbox in $mailboxes) {
   $error.clear()
   $mailboxesScanned++
   if($mailbox.LegacyDN -and $mailbox.LegacyDN -notmatch "CN=MICROSOFT SYSTEM ATTENDANT" -and $mailbox.LegacyDN -notmatch "CN=SMTP" -and $mailbox.LegacyDN -notmatch "CN=SYSTEMMAILBOX") {
    if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity (($mailboxes.Count).ToString() + " Mailboxes to Review") -status ("Scanning Mailbox: " + $mailbox.MailboxDisplayName) -percentComplete (($mailboxesScanned / $mailboxes.Count) * 100) }
    if($mailbox.LastLogonTime -ne $null) {
     $lastLogonTime = Get-Date -year $mailbox.LastLogonTime.ToString().Substring(0,4) -month $mailbox.LastLogonTime.ToString().Substring(4,2) -day $mailbox.LastLogonTime.ToString().Substring(6,2) -hour $mailbox.LastLogonTime.ToString().Substring(8,2) -minute $mailbox.LastLogonTime.ToString().Substring(10,2) -second $mailbox.LastLogonTime.ToString().Substring(12,2) -format G
    } else {
     $lastLogonTime = ""
    }
    $storageLimitInfo, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes, $orphanedMailboxes = Get-StorageLimitInfo $mailbox.StorageLimitInfo $issuedWarning $prohibitedSend $mailboxDisabled $unlimitedMailboxes $orphanedMailboxes
    if($mailbox.Size -gt $largeMailboxSize) {
     $largeMailboxes++
    }
    $userObject = Get-ActiveDirectoryObject (Get-UserInformation $mailbox.LegacyDN)
    $mailboxCount++
    if($userObject.givenName) {
     $userGivenName = $userObject.givenName.ToString()
    } else {
     $userGivenName = ""
    }
    if($userObject.sn) {
     $userSn = $userObject.sn.ToString()
    } else {
     $userSn = ""
    }
    if($userObject.displayName) {
     $userDisplayName = $userObject.displayName.ToString()
    } else {
     $userDisplayName = $mailbox.MailboxDisplayName
    }
    if($userObject -ne $null) {
     $userDomain = (Get-ObjectADDomain $userObject.distinguishedName).Split(".")[0]
    } else {
     $userDomain = ""
    }
    if($userObject.sAMAccountName) {
     $userSAMAccountName = $userObject.sAMAccountName.ToString()
    } else {
     $userSAMAccountName = ""
    }
    if($userObject.mail) {
     $userMail = $userObject.mail.ToString()
    } else {
     $userMail = ""
    }
    $totalMessages += $mailbox.TotalItems
    $totalStorage += $mailbox.Size
    $userMailboxInformation = New-Object -typeName PSObject
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Mailbox Server" -value $exchangeServer.Name
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Storage Group" -value  $mailbox.StorageGroupName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Store Name" -value  $mailbox.StoreName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Display Name" -value  $userDisplayName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "First Name" -value  $userGivenName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last Name" -value  $userSn
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Domain" -value  $userDomain
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "User ID" -value  $userSAMAccountName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "E-Mail Address" -value  $userMail
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Size (KB)" -value ("{0:N0}" -f $mailbox.Size)
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Messages" -value ("{0:N0}" -f $mailbox.TotalItems)
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Storage Limit" -value  $storageLimitInfo
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last Logon" -value  $lastLogonTime
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last User Access" -value  $mailbox.LastLoggedOnUserAccount
    $userMailboxes += $userMailboxInformation
    if($error) {
     Write-LogEntry ("[ERROR] Problems found scanning mailbox:" + $userDisplayName) $logFilename
     foreach($errorEntry in $error) {
      Write-LogEntry $errorEntry $logFilename
     }
    }
   } else {
    Write-LogEntry ("Skipping:" + $mailbox.LegacyDN) $logFilename
   }
   if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Mailbox review" -status "Completed" -Completed }
  }
 } else {
  if($exchangeServer.AdminDisplayVersion -match "Version 8.") {
   Write-LogEntry "Exchange 2007 Server" $logFilename
  } elseif($exchangeServer.AdminDisplayVersion -match "Version 14.") {
   Write-LogEntry "Exchange 2010 Server" $logFilename
  }
  if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Loading Mailboxes on " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
  $error.clear()
  try {
   $mailboxes = @(Get-MailboxStatistics -server $exchangeServer.Fqdn | Where-Object { $_.ObjectClass -ne "Mailbox, ExOleDbSystemMailbox" -and $_.DisconnectDate -eq $null } | Select-Object -property DisplayName,ItemCount,LegacyDN,LastLoggedOnUserAccount,LastLogoffTime,LastLogonTime,StorageLimitStatus,TotalDeletedItemSize,TotalItemSize,MailboxTableIdentifier,DatabaseName,OriginatingServer)
  } catch {
   Write-LogEntry ("[ERROR] Unable to obtain mailboxes on " + $exchangeServer.Fqdn)
   foreach($errorEntry in $error) {
    Write-LogEntry $errorEntry $logFilename
   }
   $mailboxes = @()
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalMessages" -value $totalMessages
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalStorage" -value $totalStorage
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxCount" -value $mailboxCount
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "issuedWarning" -value $issuedWarning
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "prohibitedSend" -value $prohibitedSend
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxDisabled" -value $mailboxDisabled
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "unlimitedMailboxes" -value $unlimitedMailboxes
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "orphanedMailboxes" -value $orphanedMailboxes
   Add-Member -inputObject $exchangeServer -type NoteProperty -name "largeMailboxes" -value $largeMailboxes
   Write-LogEntryLineBreak $logFilename
   continue
  }
  Write-LogEntry ($mailboxes.Count.ToString() + " mailboxes found") $logFilename
  if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status ("Scanning Mailboxes: " + $exchangeServer.Fqdn) -percentComplete (($serversScanned / $exchangeServers.Count) * 100) }
  foreach($mailbox in $mailboxes) {
   $error.clear()
   $mailboxesScanned++
   if($mailbox.LegacyDN -and $mailbox.LegacyDN -notmatch "CN=MICROSOFT SYSTEM ATTENDANT" -and $mailbox.LegacyDN -notmatch "CN=SMTP" -and $mailbox.LegacyDN -notmatch "CN=SYSTEMMAILBOX") {
    if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity (($mailboxes.Count).ToString() + " Mailboxes to Review") -status ("Scanning Mailbox of " + $mailbox.DisplayName) -percentComplete (($mailboxesScanned / $mailboxes.Count) * 100) }
    if($mailbox.LastLogonTime) {
     $lastLogonTime = Get-Date $mailbox.LastLogonTime -format G
    } else {
     $lastLogonTime = ""
    }
    $storageLimitStatus, $issuedWarning, $prohibitedSend, $mailboxDisabled, $unlimitedMailboxes, $orphanedMailboxes = Get-StorageLimitInfo $mailbox.storageLimitStatus $issuedWarning $prohibitedSend $mailboxDisabled $unlimitedMailboxes $orphanedMailboxes
    $userObject = Get-ActiveDirectoryObject (Get-UserInformation $mailbox.LegacyDN)
    $mailboxCount++
    if($userObject.givenName) {
     $userGivenName = $userObject.givenName.ToString()
    } else {
     $userGivenName = ""
    }
    if($userObject.sn) {
     $userSn = $userObject.sn.ToString()
    } else {
     $userSn = ""
    }
    if($userObject.displayName) {
     $userDisplayName = $userObject.displayName.ToString()
    } else {
     $userDisplayName = $mailbox.DisplayName
    }
    if($userObject -ne $null) {
     $userDomain = (Get-ObjectADDomain $userObject.distinguishedName).Split(".")[0]
    } else {
     $userDomain = ""
    }
    if($userObject.sAMAccountName) {
     $userSAMAccountName = $userObject.sAMAccountName.ToString()
    } else {
     $userSAMAccountName = ""
    }
    if($userObject.mail) {
     $userMail = $userObject.mail.ToString()
    } else {
     $userMail = ""
    }
    if($mailbox.TotalItemSize.Value.ToKB() -gt $largeMailboxSize) {
     $largeMailboxes++
    }
    $totalMessages += $mailbox.ItemCount
    $totalStorage += $mailbox.TotalItemSize.Value.ToKB()
    $userMailboxInformation = New-Object -typeName PSObject
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Mailbox Server" -value $exchangeServer.Name
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Database Name" -value  $mailbox.DatabaseName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Display Name" -value  $userDisplayName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "First Name" -value  $userGivenName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last Name" -value  $userSn
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Domain" -value  $userDomain
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "User ID" -value  $userSAMAccountName
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "E-Mail Address" -value  $userMail
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Size (KB)" -value ("{0:N0}" -f $mailbox.TotalItemSize.Value.ToKB())
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Messages" -value ("{0:N0}" -f $mailbox.ItemCount)
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Storage Limit" -value $storageLimitStatus
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last Logon" -value $lastLogonTime
    Add-Member -inputObject $userMailboxInformation -type NoteProperty -name "Last User Access" -value $mailbox.LastLoggedOnUserAccount
    $userMailboxes += $userMailboxInformation
    if($error) {
     Write-LogEntry ("[ERROR] Problems found scanning mailbox:" + $userDisplayName) $logFilename
     foreach($errorEntry in $error) {
      Write-LogEntry $errorEntry $logFilename
     }
    }
   } else {
    Write-LogEntry ("Skipping:" + $mailbox.LegacyDN) $logFilename
   }
   if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Mailbox review" -status "Completed" -Completed }
  }
 }
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalMessages" -value $totalMessages
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "totalStorage" -value $totalStorage
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxCount" -value $mailboxCount
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "issuedWarning" -value $issuedWarning
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "prohibitedSend" -value $prohibitedSend
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "mailboxDisabled" -value $mailboxDisabled
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "unlimitedMailboxes" -value $unlimitedMailboxes
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "orphanedMailboxes" -value $orphanedMailboxes
 Add-Member -inputObject $exchangeServer -type NoteProperty -name "largeMailboxes" -value $largeMailboxes
 Write-LogEntry ("Total Messages:" + $totalMessages) $logFilename
 Write-LogEntry ("Total Storage:" + $totalStorage) $logFilename
 Write-LogEntry ("Mailboxes Reported count:" + $mailboxCount) $logFilename
 Write-LogEntry ("Issued Warning count:" + $issuedWarning) $logFilename
 Write-LogEntry ("Prohibited Send count:" + $prohibitedSend) $logFilename
 Write-LogEntry ("Mailbox Disabled count:" + $mailboxDisabled) $logFilename
 Write-LogEntry ("Unlimited Mailbox count:" + $unlimitedMailboxes) $logFilename
 Write-LogEntry ("Orphaned Mailbox count:" + $orphanedMailboxes) $logFilename
 Write-LogEntry ("Large Mailbox count:" + $largeMailboxes) $logFilename
 $userMailboxes | Export-Csv -path ($workDirectory + "\" + $exchangeServer.Name + ".csv") -noTypeInformation
 Write-LogEntryLineBreak $logFilename
}
Write-LogEntry "Saving Master Exchange Server List: $masterExchangeServerFilename" $logFilename
$exchangeServers | Export-Csv -path $masterExchangeServerFilename -noTypeInformation
Write-LogEntry ("Unloading Exchange SnapIn") $logFilename
Unload-ExchangeSnapin
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "1" }
Write-LogEntry "Loading Master Exchange Server List: $masterExchangeServerFilename" $logFilename
$allExchangeServers =  $exchangeServers #Import-Csv -path $masterExchangeServerFilename
$exchangeServers = @()
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "20" }
$progressBar = 0
Write-LogEntry ("Filtering Exchange Servers") $logFilename
foreach($exchangeServer in $allExchangeServers) {
 $progressBar++
 if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Filtering Exchange Servers" -status ($exchangeServer.Fqdn) -percentComplete (($progressBar / $allExchangeServers.Count) * 100) }
 if($exchangeServer.mailboxCount -eq 0) {
  Write-LogEntry ($exchangeServer.fqdn + " has no mailboxes. Skipping.") $logFilename
  continue
 }
 $filteredExchangeServer = New-Object -typeName PSObject
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Name" -value $exchangeServer.Name
 if($exchangeServer.AdminDisplayVersion -match "Version 14") {
  $exchangeVersion = ("Exchange 2010 " + $exchangeServer.Edition)
 } elseif($exchangeServer.AdminDisplayVersion -match "Version 8") {
  $exchangeVersion = ("Exchange 2007 " + $exchangeServer.Edition)
 } elseif($exchangeServer.AdminDisplayVersion -match "Version 6") {
  $exchangeVersion = ("Exchange 2003 " + $exchangeServer.Edition)
 } else {
  $exchangeVersion = "Unknown"
 }
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "IP Address" -value $exchangeServer.ipV4Address
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Exchange Version" -value $exchangeVersion
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Online Since" -value $exchangeServer.WhenCreated
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Total Mailboxes" -value ("{0:N0}" -f [int]$exchangeServer.mailboxCount)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Total Messages" -value ("{0:N0}" -f [int]$exchangeServer.totalMessages)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Total Storage (MB)" -value ("{0:N0}" -f ($exchangeServer.totalStorage / 1KB))
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Space Warning" -value ("{0:N0}" -f [int]$exchangeServer.issuedWarning)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Prohibited Send" -value ("{0:N0}" -f [int]$exchangeServer.prohibitedSend)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Disabled Mailboxes" -value ("{0:N0}" -f [int]$exchangeServer.mailboxDisabled)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Unlimited Mailboxes" -value ("{0:N0}" -f [int]$exchangeServer.unlimitedMailboxes)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Orphaned Mailboxes" -value ("{0:N0}" -f [int]$exchangeServer.orphanedMailboxes)
 Add-Member -inputObject $filteredExchangeServer -type NoteProperty -name "Large Mailboxes" -value ("{0:N0}" -f [int]$exchangeServer.largeMailboxes)
 $exchangeServers += $filteredExchangeServer
 Write-LogEntry ($exchangeServer.fqdn + " will be added to the report.") $logFilename
}
Write-LogEntryLineBreak $logFilename
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "40" }
Write-LogEntry ("Saving Filtered List: $filteredExchangeServerFilename") $logFilename
$exchangeServers | Export-Csv -path $filteredExchangeServerFilename -noTypeInformation
if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Creating Worksheets" -status "Overview" -percentComplete "25" }
if(Test-Path -path $excelFilename) { Remove-Item -path $excelFilename }
$excelObject = New-Object -comObject Excel.Application
$excelObject.Visible = $false
$excelObject.DisplayAlerts = $false
if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Creating Worksheets" -status "Overview" -percentComplete "50" }
Write-LogEntry "Injecting Filtered List, $filteredExchangeServerFilename, into first worksheet" $logFilename
$workbookObject = $excelObject.Workbooks.Open($filteredExchangeServerFilename)
$workbookObject.Title = ("Exchange Server Report for " + (Get-Date -Format D))
$workbookObject.Author = "Robert M. Toups, Jr."
$worksheetObject = $workbookObject.Worksheets.Item($sheetNumber)
$worksheetObject.UsedRange.Columns.Autofit() | Out-Null
$worksheetObject.Name = "Exchange Servers"
if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Creating Worksheets" -status "Overview" -percentComplete "75" }
$listObject = $worksheetObject.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheetObject.UsedRange, $null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes,$null)
$listObject.Name = "Exchange Servers Table"
$excelCurrentTableStyle, $excelTableStyle = Get-ExcelTableStyle $excelCurrentTableStyle $excelTableStyles
$listObject.TableStyle = $excelTableStyle
if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Creating Worksheets" -status "Overview" -percentComplete "100" }
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "60" }
$progressBar = 0
foreach($exchangeServer in $exchangeServers) {
 $progressBar++
 if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Creating Worksheets" -status ($exchangeServer.Name) -percentComplete (($progressBar / $exchangeServers.Count) * 100) }
 $temporaryWorkFilename = ($workDirectory + "\" + $exchangeServer.Name + ".csv")
 Write-LogEntry ("Loading $temporaryWorkFilename into a temporary Workbook") $logFilename
 if(Test-Path -path $temporaryWorkFilename) {
  Write-LogEntry ("I found $temporaryWorkFilename. That's a good thing.") $logFilename
 } else {
  Write-LogEntry ("Can't find $temporaryWorkFilename. That is not expected. I am skipping " + $exchangeServer.name) $logFilename
  continue
 }
 $tempWorkbookObject = $excelObject.Workbooks.Open($temporaryWorkFilename)
 $tempWorksheetObject = $tempWorkbookObject.Worksheets.Item(1)
 Write-LogEntry "Copying all information to the clipboard" $logFilename
 $tempWorksheetObject.UsedRange.Copy() | Out-Null
 $sheetNumber++
 Write-LogEntry "Adding Worksheet #$sheetnumber to $excelFilename" $logFilename
 $workbookObject.WorkSheets.Add([System.Reflection.Missing]::Value, $workbookObject.Worksheets.Item($workbookObject.Sheets.Count)) | Out-Null
 $worksheetObject = $workbookObject.Worksheets.Item($sheetNumber)
 $worksheetObject.Activate()
 Write-LogEntry ("Naming Worksheet #$sheetnumber to " + $exchangeServer.Name) $logFilename
 $worksheetObject.Name = ($exchangeServer.Name)
 Write-LogEntry "Pasting clipboard to newly added worksheet" $logFilename
 $worksheetObject.Paste()
 $lastRow = $worksheetObject.UsedRange.Rows.Count
 $lastColumn = $worksheetObject.UsedRange.Columns.Count
 Write-LogEntry "Worksheet last row: $lastRow" $logFilename
 Write-LogEntry "Worksheet last column: $lastColumn" $logFilename
 $worksheetObject.UsedRange.Columns.Autofit() | Out-Null
 Write-LogEntry "Getting rid of the Select All from the paste" $logFilename
 $worksheetObject.Cells.Item(1,1).Select() | Out-Null
 $listObject = $worksheetObject.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $worksheetObject.UsedRange, $null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes,$null)
 $listObject.Name = ($exchangeServer.Name + " Table")
 $excelCurrentTableStyle, $excelTableStyle = Get-ExcelTableStyle $excelCurrentTableStyle $excelTableStyles
 Write-LogEntry "Using table style: $ExcelTableStyle ($excelCurrentTableStyle)" $logFilename
 $listObject.TableStyle = $excelTableStyle
 if($lastColumn -eq 14) {
  Write-LogEntry "Adding totals to Columns J & K" $logFilename
  $worksheetObject.Cells.Item($lastRow+1,10) = "=SUM(J2:J" + $lastRow + ")"
  $worksheetObject.Cells.Item($lastRow+1,11) = "=SUM(K2:K" + $lastRow + ")"
  $worksheetObject.Cells.Item($lastRow+1,10).NumberFormat = "#,##0"
  $worksheetObject.Cells.Item($lastRow+1,11).NumberFormat = "#,##0"
 } else {
  Write-LogEntry "Adding totals to Columns I & J" $logFilename
  $worksheetObject.Cells.Item($lastRow+1,9) = "=SUM(I2:I" + $lastRow + ")"
  $worksheetObject.Cells.Item($lastRow+1,10) = "=SUM(J2:J" + $lastRow + ")"
  $worksheetObject.Cells.Item($lastRow+1,9).NumberFormat = "#,##0"
  $worksheetObject.Cells.Item($lastRow+1,10).NumberFormat = "#,##0"
 }
 Write-LogEntry "Destroying temporary workbook" $logFilename
 $tempWorkbookObject.Close()
 Write-LogEntryLineBreak $logFilename
}
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "80" }
$worksheetObject = $workbookObject.Worksheets.Item(1)
$worksheetObject.Activate()
Write-LogEntry "Formatting Overview page" $logFilename
$lastRow = $worksheetObject.UsedRange.Rows.Count
Write-LogEntry "Last row: $lastRow" $logFilename
$worksheetObject.Cells.Item($lastRow+1,5) = "=SUM(E2:E" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,6) = "=SUM(F2:F" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,7) = "=SUM(G2:G" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,8) = "=SUM(H2:H" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,9) = "=SUM(I2:I" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,10) = "=SUM(J2:J" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,11) = "=SUM(K2:K" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,12) = "=SUM(L2:L" + $lastRow + ")"
$worksheetObject.Cells.Item($lastRow+1,13) = "=SUM(M2:M" + $lastRow + ")"
Write-LogEntry "Added total rows" $logFilename
for($y=5; $y -le 13; $y++) {
 $worksheetObject.Cells.Item(($lastRow+1),$y).NumberFormat = "#,##0"
}
Write-LogEntry "Formatted Totals" $logFilename
foreach($exchangeServer in $exchangeServers) {
 if(!$beQuiet) { Write-Progress  -id 2 -parentId 1 -activity "Hyperlinking Worksheets" -status ($exchangeServer.Name) -percentComplete (($serverCount / $exchangeServers.Count) * 100) }
 Write-LogEntry ("Hyperlinked " + $exchangeServer.name + " worksheet to Overview page") $logFilename
 $serverCount++
 $selectedRange = $worksheetObject.Range("A" + $serverCount)
 $worksheetObject.Hyperlinks.Add($selectedRange, ("#`'" + $exchangeServer.Name + "`'!A1")) | Out-Null
 $selectedRange.Font.Bold = $true
}
Write-LogEntryLineBreak $logFilename
Write-LogEntry "Saving $excelFilename" $logFilename
$workbookObject.SaveAs($excelFilename,51) # http://msdn.microsoft.com/en-us/library/bb241279.aspx
$workbookObject.Saved = $true
$workbookObject.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbookObject) | Out-Null
Write-LogEntry "Quiting Excel" $logFilename
$excelObject.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelObject) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
if(Test-Path -path $excelFilename) {
 Write-LogEntry "Successfully saved $excelFilename"
} else {
 Write-LogEntry "This is embarassing, I failed to save $excelFilename. You might want to look into that."
}
if(!$beQuiet) { Write-Progress  -id 1 -activity "Exchange Server Report" -status "Build Excel Spreadsheet" -percentComplete "100" }
Write-LogEntryLineBreak $logFilename
if($cleanupWorkDirectory -eq $true) {
 Write-LogEntry "Removing the work directory: $workDirectory" $logFilename
 Remove-Item -literalPath $workDirectory -Recurse
 if(Test-Path -path $workDirectory) {
  Write-LogEntry "Failed to clean the work directory: $workDirectory" $logFilename
 } else {
  Write-LogEntry "Successfully cleaned the work directory: $workDirectory" $logFilename
 }
} else {
 Write-LogEntry "There still is work data located here: $workDirectory" $logFilename
}
Write-LogEntry ("Run Time: " + (Get-RunTime ((Get-Date) - $startTime))) $logFilename
Write-LogEntry "WHEW! I am done!" $logFilename
Write-LogEntryLineBreak $logFilename

Friday, June 24, 2011

Quick One-Time Scheduled Task

While you can set one-time Schedule Tasks, it is cumbersome whether you do it via the GUI interface or through a PowerShell script. Unless you might use it again at a future date, you will need to clean it up later. A quick way to obtain the same result is to use Start-Sleep. Simply open a PowerShell session under the security context you want to execute the commandlet or script and use the syntax below. The semicolon separates commandlets allowing for this technique. The date math performed in the command returns a time span in total seconds that Start-Sleep uses. The example waits till 10:55 PM of the same day then restarts a TSM backup client service using Restart-Service. This is a great method if you need to kick-off a commandlet on the weekend before you leave the office Friday night. Just adjust the date and time of the first Get-Date commandlet.
Start-Sleep -Seconds ((Get-Date "10:55 PM") - (Get-Date)).TotalSeconds;Restart-Service -DisplayName "TSM Client Scheduler"

Tuesday, March 22, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 3: Daily Tasks

In my previous blog posts, "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task" and "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 2: Weekly Tasks", I explored the various settings required for scheduled tasks for those given date ranges. This post deals with the daily reoccurring tasks that administrators need to schedule, sometimes more than once a day. In the code sample below, I schedule a fictitious log cleanup PowerShell script to run three times a day; midnight, 8 am and 4 pm. For security reasons, after running this script, you should close the PowerShell command window as the password submitted for the Task Security Principal will remain in the command buffer allowing anyone with access to your keyboard to "up arrow" and reveal it.
# Parameters to modify
$taskName = "Start-TriDailyLogCleanup"
$taskWorkingDirectory = "C:\PowerShell"
$taskPath = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskArguments = "-command `"$taskWorkingDirectory\$taskName.ps1`""
$taskAuthor = "ad\myaccount"
$taskDescription = "The Tri-Daily Log Cleanup on the servers."
$taskSecurityPrincipal = "ad\maintenance"
$taskShedulerTaskFolder = "\MyTasks"
$startTimes = @((Get-Date "03/23/2011 00:00:00" -Format s),(Get-Date "03/23/2011 08:00:00" -Format s),(Get-Date "03/23/2011 16:00:00" -Format s))

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskShedulerTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

foreach($startTime in $startTimes) {
	$executionTrigger = $taskTriggers.Create(2) 
	$executionTrigger.StartBoundary = $startTime
}

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = $taskPath
$taskAction.Arguments = $taskArguments
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security. Close the PowerShell command window to increase security.
Clear-Variable -name password

Tuesday, February 22, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 2: Weekly Tasks

In my previous blog post, "Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task", I laid the groundwork for the various ways you can schedule tasks in Windows Server 2008. This installment of the series shows how to create a weekly task with multiple triggers. In the code sample below, I schedule a report to run on Monday (2), Wednesday (8) and Thursday (16) at 4:30 pm local time to the server. To extend this example further, you could use a hash table to establish a different time of day for each of the day of the week task triggers.
# Parameters to modify
$taskName = "Export-TriWeeklyDataDeobfuscationReport.ps1"
$taskWorkingDirectory = "C:\PowerShell"
$taskAuthor = "ad\myaccount"
$taskDescription = "The Tri-Weekly Data Deobfuscation Report"
$taskSecurityPrincipal = "ad\myaccount"
$taskShedulerTaskFolder = "\MyTasks"
$startTime = (Get-Date "02/28/2011 16:30:00" -Format s)
$daysOfWeek = @("2","8","16") # http://msdn.microsoft.com/en-us/library/aa381905(v=VS.85).aspx

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskShedulerTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

foreach($dayOfWeek in $daysOfWeek) {
 $executionTrigger = $taskTriggers.Create(3) 
 $executionTrigger.DaysOfWeek = $dayOfWeek
 $executionTrigger.StartBoundary = $startTime
}

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskAction.Arguments = "-command `"$taskWorkingDirectory\$taskName`""
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security. Close the PowerShell command window.
Clear-Variable -name password

Tuesday, February 1, 2011

Scheduling Tasks in PowerShell for Windows Server 2008 -- Part 1: Monthly Task

Windows Server 2008 updates one of my biggest complaints in Windows Server 2003 relating to scheduling automated tasks programatically. I have always appreciated the ease of doing this in Linux/UNIX via crontab. That ease was just not there in Windows Server 2003 as at is very clunky. However, I feel that creating a scheduled task in Windows Server 2003 manually is much easier and faster than Windows Server 2008 -- but that's me. In the example below, I create a monthly scheduled task that occurs at 6 am on the 15th of the month starting in February 2011. I am storing the task in a subfolder off the root in Scheduled Tasks that I manually created to segregate the task from other application registrations. The task will run under another security context than the one that created the task. I would prefer to capture the password securely for that context and supply it that way to the RegisterTaskDefinition but I have not discovered how to perform this with the provider. To provide a modicum of security, I clear the $password variable at the end of the script. To reduce security risks in your environment, always execute scripts with the least security privilege required to perform the function.

In future posts, I will provide further examples that show the different intervals that you can schedule tasks. This example is a basic task as there are many areas where you can fine tune the task.
# Parameters to modify
$taskName = "Export-InterestingInformation.ps1"
$taskWorkingDirectory = "C:\PowerShell"
$taskAuthor = "ad\myaccount"
$taskDescription = "The Monthly Interesting Information Report"
$taskSecurityPrincipal = "ad\reporting"
$taskSheduledTaskFolder = "\MyTasks"
$startTime = (Get-Date "02/15/2011 06:00:00" -Format s)

# Would like to use -asSecureString but RegisterTaskDefinition does not accept it
# Look over your shoulder before typing
$password = Read-Host -prompt "$taskSecurityPrincipal Password"

# The meaty parts

$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()

$rootFolder = $taskService.GetFolder($taskSheduledTaskFolder)

$taskDefinition = $taskService.NewTask(0)

$registrationInformation = $taskDefinition.RegistrationInfo
$registrationInformation.Description = $taskDescription
$registrationInformation.Author = $taskAuthor

$taskPrincipal = $taskDefinition.Principal
$taskPrincipal.LogonType = 1
$taskPrincipal.UserID = $taskSecurityPrincipal
$taskPrincipal.RunLevel = 0

$taskSettings = $taskDefinition.Settings
$taskSettings.StartWhenAvailable = $true
$taskSettings.RunOnlyIfNetworkAvailable = $true
$taskSettings.Priority = 7
$taskSettings.ExecutionTimeLimit = "PT2H"

$taskTriggers = $taskDefinition.Triggers

$executionTrigger = $taskTriggers.Create(4) 
$executionTrigger.DaysOfMonth = 16384 # http://msdn.microsoft.com/en-us/library/aa380735(v=vs.85).aspx
$executionTrigger.StartBoundary = $startTime

$taskAction = $taskDefinition.Actions.Create(0)
$taskAction.Path = "%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe"
$taskAction.Arguments = "-command `"$taskWorkingDirectory\$taskName`""
$taskAction.WorkingDirectory = $taskWorkingDirectory

# 6 == Task Create or Update
# 1 == Password must be supplied at registration
$rootFolder.RegisterTaskDefinition($taskName, $taskDefinition, 6, $taskSecurityPrincipal, $password, 1)

# Since we captured this in plain text I am going to nuke the value
# Not 100% security
Clear-Variable -name password