sus42

A Blog on Wndows Server and Microsoft Exchange by Thomas Pätzold

Advanced Mailboxstatistics

leave a comment »


As an administrator of a large Exchange System you have configured a mailboxquota for each mailbox. But the interesting question is what is the ratio between the configured quota and the used mailboxstore. In order to get this value you have to create your own Windows Powershell function.

The following function enumerates all data and stores it in a custom object. So you are able to work with this object.

function get-mailboxquotausage {
<#
.SYNOPSIS
	This function enumerates the configured quota and used mailboxstore for each mailaddress
	of a maildomain. The user is able to specify the maildomain as a parameter to this function.
.DESCRIPTION
	This script enumerates all mailaddresses from one primary maildomain. Displayname, primary SMTP address, prohibit send quota and the used mailboxstore are displayed.
.PARAMETER Maildomain
	This parameter specifies the maildomain this function will work on.
.EXAMPLE
	get-mailboxquotausage -maildomain aixtest.de
	
	Description
	===========
	This command will show the displayname, primary SMTP address, prohibit send quota and used mailboxstore for the domaine @aixtest.de
	
.EXAMPLE
	get-mailboxquotausage -maildomain aixtest.de | select displayname, @{label="size (MB)"; expression={"{0,0:N0}" -f $_.size}}, @{label="quota (MB)"; expression={"{0,0:N0}" -f $_.quota}}
	

	Description
	============
	This command will show the displayname, primary SMTP address, prohibit send quota and used mailboxstore for the domaine @aixtest.de.
	The displayed values will be displayed with a decimal point according to the configured language setting.
#>




[CmdletBinding()]
param([Parameter(Mandatory=$True, ValueFromPipeline=$True)][string]$maildomain)

begin {
}


process{
$delete = 0
$result = @()
$users=get-mailbox |? {$_.primarysmtpaddress -like "*$maildomain*"}

foreach ($user in $users) {

	$userstats=Get-MailboxStatistics $user 

	$obj=new-object psobject
	$obj = @{}
	$obj.add("Displayname", $user.displayname)
	$obj.add("Mailaddress", $user.primarysmtpaddress)
	write-host $user.primarysmtpaddress
	if (-not $user.prohibitsendquota.isunlimited) {
		$obj.add("Quota", ($user.prohibitsendquota.value.toMB()))
	} else {
		$obj.add("Quota", "unlimited")
	}
	$obj.add("Size", ($userstats.totalitemsize.value.toMB()))
	$result += new-object -type PSObject -Property $obj
}
$result
}

end{}

}

Written by Thomas Pätzold

January 28, 2013 at 9:40 pm

UNIX2DOS using Powershell

leave a comment »


Today I was asked to convert a file from the UNIX fileformat to the DOS fileformat.
As you probably know the format is different in the last character of each line. The DOS format ends with a carriage return (Cr) line feed (Lf) character whereas the UNIX format uses the line feed (Lf) character.
So this is a very simple task using Powershell

get-content <filename> |% {$_.replace("`n", "`r`n")} | out-file -filepath <new filename>

Remember the “%” means foreach-object. So this oneliner runs through the whole file and replaces the Lf with the CrLf character.

If you need a solution to use in a pipeline like the UNIX2DOS Unix command write a function that accepts the input from pipeline. The function looks like

function unix2dos{
param([Parameter(Mandatory=$true, ValueFromPipeline=$True)][String]$line)
begin {}
process {
$result = [string]$line.replace("`n","`r`n")
$result
}
end {}
}

Call this function like:

get-content <filename> | unix2dos | out-file -filepath <new filename>

If you put this function in your Powershell profile, you are able to use is anytime anywhere.

Written by Thomas Pätzold

January 21, 2013 at 9:59 pm

Update Exchange 2010 Infrastructure

leave a comment »


If you are running an Exchange environment, it is necessary to plan the upgrade process of both the operating system and the Exchange system carefully in order to prevent data loss.

In order to do this you have to follow the steps below:

  • Virus scan tool, monitoring and Backup
    • First of all you have to check the prerequisits of the other software components such as the virusscanner, monitoring client or backup client. After you have assured that the components you use are working properly with the installed update you should start the upgrade process.
  • CAS Server
    • After you checked the prerequitsits you are able to start the upgrade process of your Exchange Environment. First of all you should start with the internet-facing Client Access Server roles. After this you can start the upgrade process of the intranet Client Access Server. During the upgrade process it might be possible that not all clients are able to connect to the infrastructure. In order to improve this you should consider using an hardware load balancing system.
  • HUB Server
    • After upgrading the Client Access Server you should upgrade your HUB Servers next.
  • Mailboxserver
    • Now it is time to upgrade your Mailbox servers. Please consider, it might be a diffrence, if you use a single server environment or a Mailboxcluster (Database Availability Group). Basically you should start upgrading the Databaseserver which doesn´t hold any active databases.
  • Edge Transport Server
    • After you have upgraded all other Exchangeservers you should upgrade the Edge Transport Server now.

In order to support the upgrade process of the members of a database availability group (DAG) there are two scripts in the installation folder of Exchange 2010 (usually c:\Program Files\Microsoft\Exchange Server\v14\scripts).

Starting the maintenance mode
In order to start the maintenance mode you should use the script StartDagServerMaintenance.ps1
This script

  • pauses the node so that it couldn´t become the Primary Active Manager
  • suspends database activation on the specified node
  • moves the active database of the designated server to another Mailboxserver in the same DAG

After maintenance is finished you must stop the maintenance mode manually in order to reactivate the specified mailboxserver.

Stopping the maintenance mode
In order to stop the maintenance mode you should use the script StopDagServerMaintenance.ps1. Consider that stopping the maintenance mode dosn´t move the active copy to the prefered Mailboxserver.

Redistribute Active Mailboxdatabase
If you create a new mailboxdatabase in the DAG then you are able to configure the ActivationPreference attribuite of each mailboxdatabase. Based on this setting you are able to move the active mailboxdatabase to its prefered mailboxserver. In order to redistribute the mailboxdatabase use the following script RedistributeActiveDatabases.ps1.

Written by Thomas Pätzold

January 24, 2012 at 9:59 pm

The Exchange 2010 Powershell SnapIns

leave a comment »


Applies to Exchange 2010 SP1

If you start the Exchange Management Shell, a Powershell instance is started and the Powershell remoting feature is used in order to establish a connection to the Exchange Server System. There is another option to load the Exchange 2010 specific commands in Powershell. In this case you have to start the Powershell and load the necessary Exchange 2010 management snapins.

The following three Exchange 2010 Snapins are available

Microsoft.Exchange.Management.PowerShell.E2010
Microsoft.Exchange.Management.PowerShell.Setup
Microsoft.Exchange.Management.Powershell.Support

In order to load the snapins you are able to use the following command

add-pssnapin -Name Microsoft.Exchange.Management.PowerShell.E2010

If you are writing a Powershell script, it might be useful to check if the plugin is loaded. In order to do this you are able to use the following code snipset.

if (! (Get-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction:SilentlyContinue) )
{
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction:Stop
}

What is the difference between the Exchange Management Shell and adding the Exchange snapin to the Powershell session?

  1. The Exchange commands using the Exchange Management Shell are implemented as functions. If you load the “Microsoft.Exchange.Management.PowerShell.E2010” snapin instead of using the Exchange Management Shell the commands are implemented as cmdlets.
  2. Using the Exchange Management Shell you are able to take a more detailed look at how the functions better called the wrapper functions are implemented by using the command.
    get-command get-mailbox -detailed | select-object scriptblock
    

  3. There is one more important diffrence. If you want to use the systemvariable $? it is better to use the loaded snapin instead of the Exchange Management Shell because these commands don´t work properly in the Exchange Managemnt Shell. A second problem is the use of the common parameters because they are not implemented for all functions in Exchange Management Shell.

If you want to start an Exchange Shell Session because you want to execute your command on a special Exchange Server then this is possible but keep the limitations of the Exchange Management Shell in mind.

Written by Thomas Pätzold

September 14, 2011 at 9:10 pm

Microsoft Community Contributor Award 2011

leave a comment »


This week I was suprised when I got an email from Microsoft saying that I earned the Microsoft Community Contributor Award for year 2011.

This is a great motivation for me to post more articles and working more on answering questions on Microsoft Technet forums…

Written by Thomas Pätzold

June 3, 2011 at 8:51 pm

Posted in Community News

Tagged with

Searching the Active Directory

leave a comment »


Since Windows 2000 Server the old Windows NT Directory service also known as SAM (Security Account Manager) is replaced by the Active Directory. The Active Directory is a hierarchical directory service optimized for searching objects. You need to know how to connect to your Active Directory and using the DirectorySearcher object in order to perform searches. It is important to know how searches are processed. Detailed information on this topic can be found on TechNet.

Before you are able to connect to your Active Directory using LDAP you have to determine your Active Directory Domain Controller. In order to get this information  you can use the RootDSE Object introduced in LDAPv3. In order to connect this object you can use e.g. LDP.exe on your Domain Controller. Each user is able to connect to the RootDES object without authentication, so you only need to bind to your domain name. If you want to read more information about the LDAPv3 protocol and the definition of the rootDSE object  please read RFC2251 a technical description you will find in RFC3377.

using LDP.EXE connecting to rootDSE

Now we use the Windows PowerShell in order to connect to the rootDSE object. If you start the command

$Root = [ADSI]LDAP://RootDSE

on your domain controller or a connected workstation you get the same information as using the LDP.EXE command.

Connecting RootDSE object using Windows Powershell

One of the most important attributes of the rootDSE object is the DefaultNamingContext. Because of the value of this property you are able to connect to your Active Directory from any workstation connected to it. The benefit of using this property instead of using the physical name of your Active Directory is one script can run against different Active Directories. The following script connects to the Active Directory running search on it and displays the result.

# Connecting to the Active Directory
# determine the name of the Active Directory
$Root = [ADSI]"LDAP://RootDSE"
$DomainLDAP= "LDAP://"+$Root.DefaultnamingContext
# Connecting to the Active Directory
$objDomain = New-Object System.DirectoryServices.DirectoryEntry($DomainLDAP)
# Creating the searcher object
$strFilter = "((objectCategory=user))"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.PageSize=1000
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope="Subtree"
# Create the property list which is included in the result
$colProplist = "sAMAccountName","userPrincipalName","homeDirectory","profilePath";"terminalservicesprofilepath","terminalserviceshomedirectory"
foreach ($i in $colPropList){$amount=$objSearcher.PropertiesToLoad.Add($i)}
$Results = $objSearcher.FindAll()
$anzahl=0
foreach ($obj in $Results){
   $item = $obj.Properties
   $user = [ADSI]([String]$item.adspath)
   # if the attribute isn´t set then you will get an error. So we use a try-catch statement
   try{$item_TerminalServicesHomeDirectory = $user.psbase.invokeget("TerminalServicesHomeDirectory")}
   catch {$item_TerminalServicesHomeDirectory = ""}
   # if the attribute isn´t set then you will get an error. So we use a try-catch statement
   try{$item_TerminalServicesProfilePath = $user.psbase.invokeget("TerminalServicesProfilePath")}
   catch {$item_TerminalServicesProfilePath = ""}
   write-output $item.adspath
   write-Output $item.samaccountname
   write-output $item_terminalserviceshomedirectory
   write-output $item.homedirectory
   write-Output $item_terminalservicesprofilepath
   write-output $item.profilepath
}

There are some parameters within the searcher object that should be explained further:

Paged Searches

In order to optimize your directory search you have to use a paged search. The default value for the parameter PageSize is 0 this meanS no paging is occuring and that only 1000 objects are returned as the result. This parameter specifies the maximum number of objects in each page that is returnd from the search commnd. You are able to configure this parameter by configuring the parameter PageSize. If you want to retrive more than 1000 objects you have to set the PageSize parameter to a value between 0 and 1000. The number of objects returnd by the search will be also affected by the sizeLimit parameter.

The Code looks like this:

$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 1000

After this you can use a foreach-loop in order to step through the resultant set.

SizeLimit

The parameter SizeLimit contains the maximum amount of objects returned by a search. So the parameter PageSize splits this amount in parts which are transferred to the client.

Search Scope

There are three different search scopes defined in the SearchScope Parameter:

  • Base – Only Objects within the Searchroot are returned by the search
  • One Level – Only Objects within the first Level of the directory hierarchy
  • Subtree – Objects with the search root, and all descendant objects

PropertiesToLoad

This collection contains all properties of the directory objects contained in the resultant set. The attribute “ADsPath” is always part of the result, nevertheless you have specified this parameter or not. If you don´t have specified this parameter you will get all attributes of a specific object which will mean that a lot of resources (network traffic, processor, memory) are required in order to execute the search request. So it is a good idea to limit the attributes to that, that are uses further in the script. A list of possible properties to include in the result, you will find in the schema of your Active Directory. If you want to look your own schema in Active Directory you have to install the schema.dll on your domain controller.

Terminalserver Properties

Some user properties are stored in a binary attribute called user Parameters. So the question is how to get the values of such Attributes.

foreach ($obj in $Results){
    $item = $obj.Properties
    $user = [ADSI]([String]$item.adspath)
    # if the attribute isn´t set then you will get an error. So we use a try-catch statement
    try{$item_TerminalServicesHomeDirectory = $user.psbase.invokeget("TerminalServicesHomeDirectory")}
    catch {$item_TerminalServicesHomeDirectory = ""}
    # if the attribute isn´t set then you will get an error. So we use a try-catch statement
    try{$item_TerminalServicesProfilePath = $user.psbase.invokeget("TerminalServicesProfilePath")}
    catch {$item_TerminalServicesProfilePath = ""}
}

The methods for reading and setting  the terrminalserver attributes are defined in the class System.DirectoryServices.DirectoryEntry (you can also tpye [ADSI] in order to generate an object of this type) but as you see at this point of script we only have the object $obj which type is System.DirectoryServices.SearchResult. So in order to get the attribute values we have to generate an object of type DirectoryEntry for the actual user-object stored in $obj. User Objects which are created during the setup process of Windows haven´t got these terminalserver attributes so the script generates an error if accessing these objects. So I used the try-catch block in order to do the error handling.

Written by Thomas Pätzold

February 11, 2011 at 2:32 pm

The Powershell Foreach Loop

leave a comment »


Have you ever thought about what happens when the collection is empty through wich you want to step with the foreach loop in Powershell 2.0?

Ok, but let´s start at the beginning.
What happens when the collection contains objects?
Think about a loop witch counts the files with the extension .txt in a given folder. The Code looks like

$path = "C:\temp"
$filter = "*.txt"
$count = 0

$files = get-childitem -path $path -filter $filter

foreach ($file in $files) {
        $count ++
}
Write-Host "There are $count files with the pattern $filer in folder $path"

What happens when the collection is empty?
The second example runs on an empty collection. Now run the same code with a filter witch doesn´t match a file or foldername. So Object $files should be empty. But what looks the output like?
foreach look with empty collection
In this example one file is found but why?
The reason is very simple because $files is a NULL Object. And a NULL Object is an object so there will be one cycle in the loop and at the end $count contains the value “1”. The NULL Object is a scalar but not a collection. But the foreach loop operates on a collection. So the NULL object has to be casted to a collection and now the collections contains one element (the Null object)

This is a real problem if the further scripts depends on this result.

Now it gets very strange. If you generate the collection within the header of the foreach loop you will get the correct result. The code looks like

$path = "C:\temp"
$filter = "*.txt"
$count = 0

foreach ($file in (get-childitem -path $path -filter $filter)) {
        $count ++
}
Write-Host "There are $count files with the pattern $filer in folder $path"

This is the output in Powershell in contrast to the first example.
The two examples with their Output

Now we look for a solution for this Problem:
There are two possible solutions.
1) You have to check with an if-statement if the collection is empty before stepping through the loop.
The Code looks like

$path = "C:\temp"
$filter = "*.txt"
$count = 0

$files = get-childitem -path $path -filter $filter
If ($files -ne $null) {
        foreach ($file in $files) {
                $count ++
        }
}
Write-Host "There are $count files with the pattern $filer in folder $path"

2) The second solution is to generate the collection within the header of the foreach loop. So you will get the correct result. The code looks like

$path = "C:\temp"
$filter = "*.txt"
$count = 0

foreach ($file in (get-childitem -path $path -filter $filter)) {
        $count ++
}
Write-Host "There are $count files with the pattern $filer in folder $path"

And this is the output in Powershell in contrast to the first example.
The two examples with their Output

3) The third solution is to generate explicit an array (collection) with the @() statement. If now the command generates a NULL object then the collection (array) is empty and your code will generate the expected result.
The code looks like:

$path = "C:\temp"
$filter = "*.txt"
$count = 0

$files = @(get-childitem -path $path -filter $filter)
If ($files -ne $null) {
        foreach ($file in $files) {
                $count ++
        }
}
Write-Host "There are $count files with the pattern $filer in folder $path"

Conclusion
I think this is an error in the implementation of the foreach loop. Perhaps we have to wait for the next version of the Windows Powershell to get this problem solved.
On Microsoft Connect you will get further infromation on this topic.

Written by Thomas Pätzold

January 24, 2011 at 10:36 pm

Posted in Powershell, Scripting

Tagged with ,

%d bloggers like this: