Archive

Posts Tagged ‘Tips’

Powershell Scripting for Exchange Server – Some Tips

December 30th, 2012 No comments

Here is a consolidation of few features and tips from my scripting practice for Exchange Server.

First thing any scripter need is a script editor, and when you talk about script editors for Powershell, there is a minimum feature requirement. Of them are Tab completion, Testing and debugging the script from the editor itself.

Getting the Exchange cmdlets inside the editor, for this first thing required is to have Exchange Management Tools Installed.  After that we open Powershell ISE and check the loaded PS Snapins

PS C:\> Get-PSSnapin | Format-Table Name

Name                                                                                                                                  
----                                                                                                                                  
Microsoft.PowerShell.Diagnostics                                                                                                      
Microsoft.WSMan.Management                                                                                                            
Microsoft.PowerShell.Core                                                                                                             
Microsoft.PowerShell.Utility                                                                                                          
Microsoft.PowerShell.Host                                                                                                             
Microsoft.PowerShell.Management                                                                                                       
Microsoft.PowerShell.Security                                                                                                         

You can see from above all the loaded snapins, and you wont see Exchange Snapins.

Now, lets check for the Registered but not loaded Snapins

PS C:\> Get-PSSnapin -Registered | Format-Table Name

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

You can see above there are three snapins registered by Exchange Server 2010

Now load the Registered Snapins to the current session

PS C:\> Get-PSSnapin -Registered | Add-PSSnapin

PS C:\> Get-PSSnapin | Format-Table Name

Name                                                                                                                                  
----                                                                                                                                  
Microsoft.PowerShell.Diagnostics                                                                                                      
Microsoft.WSMan.Management                                                                                                            
Microsoft.PowerShell.Core                                                                                                             
Microsoft.PowerShell.Utility                                                                                                          
Microsoft.PowerShell.Host                                                                                                             
Microsoft.PowerShell.Management                                                                                                       
Microsoft.PowerShell.Security                                                                                                         
Microsoft.Exchange.Management.PowerShell.E2010                                                                                        
Microsoft.Exchange.Management.PowerShell.Setup                                                                                        
Microsoft.Exchange.Management.Powershell.Support                                                

                                      

Now we have all the exchange cmdlets loaded in our session, good to go.

Lets try a simple Exchange 2010 Cmdlet.

PS C:\> Get-Mailbox
Get-Mailbox : Value cannot be null.
Parameter name: serverSettings
At line:1 char:12
+ Get-Mailbox <<<< 
    + CategoryInfo          : NotSpecified: (:) [Get-Mailbox], ArgumentNullException
    + FullyQualifiedErrorId : System.ArgumentNullException,Microsoft.Exchange.Management.RecipientTasks.GetMailbox

Why is this error, whenever you start Exchange Management Console or Exchange Management Shell, it initiates a Powershell Remoting Session, and sets some default settings such as the Domain Controller, the Exchange Server to connect etc. In above case these things didnt happen as we have just loaded the raw cmdlets using PSSnapin.

Another important point to note is RBAC, when you load just the snapins, you are loading the raw cmdlets to the session.  Exchange is not intended to be managed like that. There is Robust Access Control layer built into the management tools, the Role Based Access Control (RBAC). When we do execute cmdlets by just loading PSSnapin it DOESNT RESPECT RBAC, and when RBAC is not respected the cmdlets may give unexpected results.

PS: But this doenst block us from using the snapins to easily write or edit scripts, only thing is that you cannot run the scripts here.

The solution is to use Implicit Remoting:

In this method, we are not loading any Snapins, we just create an implicit remoting session using Import-PSSession and that session loads all the cmdlets we need,  it also respects the RBAC (So you only get access to the Cmdlets you have been assigned inside the RBAC Roles).

Creating an Implicit Remoting session to Exchange Server 2010

#If you want to pass an alternate credential to connect to exchange you can use -Credential parameter

#To interactively pass the credentials use -Credential (Get-Credential) to the New-PSSession Cmdlet

$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri <a href="http://EXSRV1/Powershell&lt;/a&gt;Import-PSSession">http://EXSRV1/Powershell&lt;/a&gt; Import-PSSession</a> -Session $session
WARNING: Some imported command names include unapproved verbs which might make them less discoverable.  Use the Verbose parameter for mo
re detail or type Get-Verb to see the list of approved verbs.

ModuleType Name                      ExportedCommands                                                          
---------- ----                      ----------------                                                          
Script     tmp_819a88a7-c59d-464e... {Get-IRMConfiguration, New-MailUser, Enable-CmdletExtensionAgent, New-Ac...

There is one warning always comes as powershell checks for the cmdlet verbs used, its due to the cmdlets Clean-MailboxDatabase, and Retry-Queue, both Clean and Retry are not in the Approved verbs you get when executing the cmdlet Get-Verb

Now lets try Get-Mailbox

PS C:\> Get-Mailbox

Name                      Alias                ServerName       ProhibitSendQuota                                                     
----                      -----                ----------       -----------------                                                     
Administrator             Administrator        ex2010mbx11      unlimited                                                             
DiscoverySearchMailbox... DiscoverySearchMa... ex2010mbx11      50 GB (53,687,091,200 bytes)  

All is well!

Now lets see another difference to keep in mind while scripting for Exchange

Lets see some examples from Exchange Management Shell

[PS] C:\>$mbx = Get-Mailbox -Identity Administrator
[PS] C:\>$mbx.EmailAddresses

SmtpAddress        : admin@get-exchange.local
AddressString      : admin@get-exchange.local
ProxyAddressString : smtp:admin@get-exchange.local
Prefix             : SMTP
IsPrimaryAddress   : False
PrefixString       : smtp
SmtpAddress        : Administrator@get-exchange.local
AddressString      : Administrator@get-exchange.local
ProxyAddressString : SMTP:Administrator@get-exchange.local
Prefix             : SMTP
IsPrimaryAddress   : True
PrefixString       : SMTP

you can see from above that you are getting a complete object of email address which has properties like prefixstring, IsPrimaryAddress etc. see the members of email address object

[PS] C:\>$mbx.emailaddresses | Get-Member
   TypeName: Microsoft.Exchange.Data.SmtpProxyAddress

Name               MemberType Definition
----               ---------- ----------
CompareTo          Method     int CompareTo(Microsoft.Exchange.Data.ProxyAddress othe
Equals             Method     bool Equals(System.Object obj), bool Equals(Microsoft.E
GetHashCode        Method     int GetHashCode()
GetSimilarProxy    Method     Microsoft.Exchange.Data.ProxyAddressBase GetSimilarProx
GetType            Method     type GetType()
ToPrimary          Method     Microsoft.Exchange.Data.ProxyAddressBase ToPrimary()
ToSecondary        Method     Microsoft.Exchange.Data.ProxyAddressBase ToSecondary()
ToString           Method     string ToString()
TryDeencapsulate   Method     bool TryDeencapsulate(Microsoft.Exchange.Data.ProxyAddr
AddressString      Property   System.String AddressString {get;}
IsPrimaryAddress   Property   System.Boolean IsPrimaryAddress {get;}
Prefix             Property   Microsoft.Exchange.Data.ProxyAddressPrefix Prefix {get;
PrefixString       Property   System.String PrefixString {get;}
ProxyAddressString Property   System.String ProxyAddressString {get;}
SmtpAddress        Property   System.String SmtpAddress {get;}

Similarly you can see the members of the Mailbox itself, for readability i have remove most of them. The relevant member to our discussion is EmailAddresses properyt, you can see here that its a of ProxyAddressCollection

[PS] C:\>$mbx | Get-Member
   TypeName: Microsoft.Exchange.Data.Directory.Management.Mailbox

Name                                   MemberType   Definition
----                                   ----------   ----------
Clone                                  Method       System.Object Clone()
Equals                                 Method       bool Equals(System.Object obj)
GetHashCode                            Method       int GetHashCode()
GetProperties                          Method       System.Object[] GetProperties(Syst
GetType                                Method       type GetType()
ToString                               Method       string ToString()
...
EmailAddresses                         Property     Microsoft.Exchange.Data.ProxyAddressCollection EmailAddresses {g...

Now lets observe the same from Powershell Console or the ISE

PS C:\> $mbx = Get-Mailbox -Identity Administrator
PS C:\> $mbx.EmailAddresses
smtp:admin@get-exchange.local
SMTP:Administrator@get-exchange.local

you can see here, that you are not getting anything like prefix or IsPrimary etc. you are just getting the data as string.

To prove that lets use Get-Member on the EmailAddresses property or you can also use

# $mbx.EmailAddresses.GetType()
PS C:\> $mbx.EmailAddresses | Get-Member
   TypeName: System.String

Name             MemberType            Definition                                                                                     
----             ----------            ----------                                                                                     
Clone            Method                System.Object Clone()                                                                          
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB)                                 
Contains         Method                bool Contains(string value)                                                                    
CopyTo           Method                System.Void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)       
EndsWith         Method                bool EndsWith(string value), bool          
So, having looked at all those details, just see if we see any difference in the Retried Object itself in the $mbx variable

PS C:\> $mbx | Get-Member
   TypeName: Deserialized.Microsoft.Exchange.Data.Directory.Management.Mailbox

Name                                   MemberType   Definition                                                                        
----                                   ----------   ----------                                                                        
ToString                               Method       string ToString(), string ToString(string format, System.IFormatProvider formatP...
PSComputerName                         NoteProperty System.String PSComputerName=ex2010mbx11                                          
RunspaceId                             NoteProperty System.Guid RunspaceId=1c8913bb-b202-4b52-8a13-17da41b77056                       
AcceptMessagesOnlyFrom                 Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AcceptMessagesOnlyFromDLMembers        Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AcceptMessagesOnlyFromSendersOrMembers Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AddressBookPolicy                      Property      {get;set;}                                                                       
AddressListMembership                  Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
Alias                                  Property     System.String {get;set;}                                                          
AntispamBypassEnabled                  Property     System.Boolean {get;set;}                                                         
ArbitrationMailbox                     Property      {get;set;}                                                                       
ArchiveDatabase                        Property      {get;set;}                                                                       
ArchiveDomain                          Property      {get;set;}             
....
EmailAddresses                         Property     Deserialized.Microsoft.Exchange.Data.ProxyAddressCollection {get;set;}

Important information to look here is the word "Deserialized" in the beginning of Type Name and also in the definition of EmailAddresses property.  This brings us another concept to understand, "How Powershell Remoting perform Data Transfer". This discussion is to not dig into that area, i will provide some reference links to read abou it at the end of this post. Its already explained by Powershell Experts out there.

Deserialized Objects

In short, it does somthing called serialization and deserializaion - a form of transporting data or live objects using XML. When objects get deserialized they loose their dynamic nature, so you cannot perform most of the method calls or explore dynamic nature of the Object.

Lets understand this by looking at another example:

Exploring maibox database object inside the script, lets first look at from Powershell Console or ISe

$Db = Get-MailboxDatabase MBX11-DB01

PS C:\> $Db.EdbFilePath
C:\MBX11-DB01\mbx11-db01.edb

the property EdbFilePath gives you the complete path to the Database File.

you can see the type of this property is string

PS C:\> $Db.EdbFilePath.GetType()

IsPublic IsSerial Name                                     BaseType                                     
-------- -------- ----                                     --------                       
True     True     String                                   System.Object

Also if you execute below you get no output:

PS C:\&gt; $Db.EdbFilePath.PathName
PS C:\&gt;

Now, perform the same steps from Exchange Management Shell

[PS] C:\>$Db = Get-MailboxDatabase MBX11-DB01

[PS] C:\>$Db.EdbFilePath
IsPathInRootDirectory : False
PathName              : C:\MBX11-DB01\mbx11-db01.edb
IsLocalFull           : True
IsUnc                 : False
DriveName             : C:
ServerName            :

You can see here the property EdbFilePath is not just a string here its a complete object with more classified information, such as the DriveName property, PathName property etc.

[PS] C:\>$Db.EdbFilePath.PathName
C:\MBX11-DB01\mbx11-db01.edb

here you get an output from the EdbFilePath.PathName, notice this failed in the pervious example in ISE.

This is because EdbFilePath is not a string object here.

[PS] C:\>$Db.EdbFilePath | gm
   TypeName: Microsoft.Exchange.Data.EdbFilePath

Name                        MemberType Definition
----                        ---------- ----------
CompareTo                   Method     int CompareTo(Microsoft.Exchange.Data.Lo
Equals                      Method     bool Equals(System.Object value), bool E
GetHashCode                 Method     int GetHashCode()
GetType                     Method     type GetType()
ToString                    Method     string ToString()
ValidateDirectoryPathLength Method     System.Void ValidateDirectoryPathLength(
ValidateEdbFileExtension    Method     System.Void ValidateEdbFileExtension()
ValidateFilePathLength      Method     System.Void ValidateFilePathLength()
DriveName                   Property   System.String DriveName {get;}
IsLocalFull                 Property   System.Boolean IsLocalFull {get;}
IsPathInRootDirectory       Property   System.Boolean IsPathInRootDirectory {ge
IsUnc                       Property   System.Boolean IsUnc {get;}
PathName                    Property   System.String PathName {get;}
ServerName                  Property   System.String ServerName {get;}
[PS] C:\>

What is the take way from this is while writing the script keep in mind the differences, such that in EMS $Db.EdbFilePath is an object by itself and if you requirement is to just get the path you can use $Db.EdbFilePath.PathName But this will not work when you are executing from PowerShell ISE.

Next thing to consider is testing and executing your Scripts for Exchange Environment, you can use PowerShell ISE to write scripts, but for testing run them from Exchange Management Shell, and keep in mind the difference like above and due to that the intellisence or tab completion will not show the nested properties.

Scheduling Exchange Server Scripts

Now a simple tip on how to schedule Exchange Server Scripts using Task Scheduler. Most of the times, you need to run scripts on a repeated intervel without user intervention. you can schedule your Exchange scripts and still use the EMS as the execution platform. i will just show the Action to trigger while creating a Scheuled Task for Exchange Server 2010 Script.

The Program to Run will be Powershell.exe

You should pass optional arguments to Powershell.exe to do things like running it in non-interactive Mode and/or hide the window etc. you can use -Command to say which command to run once powershell.exe is loaded

Field – Program to Run: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Field – Optional Arguments:

-NonInteractive -WindowStyle Hidden -command “. ‘C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1′; Connect-ExchangeServer -auto; “& ‘C:\Scripts\Report-ServerHealth.ps1′”

There different aspects in the optional arguments field:

1. -NonInteractive – Non Interactive Window

2. -WindowStyle Hidden  – The Powershell windows is hidden (you can still the process in task manager)

3. -command – the command to execute after loading powershell.exe, this can be one or multiple cmdlets(seperated by “;”)

4. The first cmdlet “. ‘C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1′; -> the is the default script loaded when you open EMS

5. Connect-ExchangeServer -auto; -> this is the function which was loaded by the previous script called RemoteExchange.ps1

6. “& ‘C:\Scripts\Report-ServerHealth.ps1′” -> now the real script we have created (finally!!)

 

Its the era of Powershell v3 now, which bring more interesting features for writing scripts, such as Intellisense, auto module loading, show command feature etc.

PS v3 is not yet supported by Exchange server 2010 (that is at version SP2 RU5) so DO NOT go and install it on your Exchange Server 2010 Server, you will breat it.

but you can use it for script editing, on your desktop or scripting machine. below are few useful features:

The Improved Intellisense feature, this will help to discover cmdlets and parameter and even parameter values

Intellisense Examples:

Exploring Objects made easy (remember “Deserialization” EdbFilePath is a String here as you can see):

 

The Show-Command cmdlet to find out the syntax to use before you start scripting, you can fill out the GUI Form and then click on Copy to copy the syntax to clipboard.

Paste it back in the editor and you just got a complete syntax to use.

Some PSRemoting References:

 About_Remot_Output

 How objects are send to and from remote sessions

What else to look for while Writing or Editing Scripts for Exchange Server, do give your comments!

 

 

Categories: Exchange, Powershell Tags: ,