It all started with a customer who wanted to know which servers in the DAG setup have the copies for a specific Exchange database. There were other properties like Activation Preferences that he was interested in too.
The easiest way to get the details about the copies for a database is by using the command (Get-MailboxDatabase "Database Name").DatabaseCopies. When I did this on the CAS Server using Exchange Management Shell, I got the following output:
[PS] C:\Windows\system32>$copies = (Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopies[PS] C:\Windows\system32>$copiesDatabaseName : Mailbox Database 1056078029HostServerName : AKAS23474121ActivationPreference : 1ParentObjectClass : msExchPrivateMDBReplayLagTime : 00:00:00TruncationLagTime : 00:00:00AdminDisplayName :ExchangeVersion : 0.10 (14.0.100.0)DistinguishedName : CN=AKAS23474121,CN=Mailbox Database 1056078029,CN=Databases,CN=Exchange Administrative Group (FY DIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services, CN=Configuration,DC=DOM234741,DC=LOCALIdentity : Mailbox Database 1056078029\AKAS23474121Guid : 562cb271-7cee-4130-9582-e3ce0f08cb3dObjectCategory : DOM234741.LOCAL/Configuration/Schema/ms-Exch-MDB-CopyObjectClass : {top, msExchMDBCopy}WhenChanged : 7/21/2011 4:29:06 PMWhenCreated : 7/21/2011 4:29:06 PMWhenChangedUTC : 7/21/2011 10:59:06 AMWhenCreatedUTC : 7/21/2011 10:59:06 AMOrganizationId :OriginatingServer : AKAS23474118.DOM234741.LOCALIsValid : TrueDatabaseName : Mailbox Database 1056078029HostServerName : AKAS23474120ActivationPreference : 2ParentObjectClass : msExchPrivateMDBReplayLagTime : 00:00:00TruncationLagTime : 00:00:00AdminDisplayName :ExchangeVersion : 0.10 (14.0.100.0)...
[PS] C:\Windows\system32>$copies = (Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopies[PS] C:\Windows\system32>$copiesDatabaseName : Mailbox Database 1056078029HostServerName : AKAS23474121ActivationPreference : 1ParentObjectClass : msExchPrivateMDBReplayLagTime : 00:00:00TruncationLagTime : 00:00:00AdminDisplayName :ExchangeVersion : 0.10 (14.0.100.0)DistinguishedName : CN=AKAS23474121,CN=Mailbox Database 1056078029,CN=Databases,CN=Exchange Administrative Group (FY DIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services, CN=Configuration,DC=DOM234741,DC=LOCALIdentity : Mailbox Database 1056078029\AKAS23474121Guid : 562cb271-7cee-4130-9582-e3ce0f08cb3dObjectCategory : DOM234741.LOCAL/Configuration/Schema/ms-Exch-MDB-CopyObjectClass : {top, msExchMDBCopy}WhenChanged : 7/21/2011 4:29:06 PMWhenCreated : 7/21/2011 4:29:06 PMWhenChangedUTC : 7/21/2011 10:59:06 AMWhenCreatedUTC : 7/21/2011 10:59:06 AMOrganizationId :OriginatingServer : AKAS23474118.DOM234741.LOCALIsValid : True
DatabaseName : Mailbox Database 1056078029HostServerName : AKAS23474120ActivationPreference : 2ParentObjectClass : msExchPrivateMDBReplayLagTime : 00:00:00TruncationLagTime : 00:00:00AdminDisplayName :ExchangeVersion : 0.10 (14.0.100.0)...
PS C:\Users\Superman> $copies=(Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopiesPS C:\Users\Superman> $copiesMailbox Database 1056078029\AKAS23474121Mailbox Database 1056078029\AKAS23474120
I then did a Get-Member on the $copies variable first on the local CAS box and below is what I see
[PS] C:\Windows\system32>$copies | Get-Member -MemberType Property TypeName: Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopyName MemberType Definition---- ---------- ----------ActivationPreference Property System.Int32 ActivationPreference {get;}AdminDisplayName Property System.String AdminDisplayName {get;}DatabaseName Property System.String DatabaseName {get;}DistinguishedName Property System.String DistinguishedName {get;}ExchangeVersion Property Microsoft.Exchange.Data.ExchangeObjectVersion ExchangeVersion {get;}Guid Property System.Guid Guid {get;}HostServerName Property System.String HostServerName {get;}Identity Property Microsoft.Exchange.Data.ObjectId Identity {get;}IsValid Property System.Boolean IsValid {get;}ObjectCategory Property Microsoft.Exchange.Data.Directory.ADObjectId ObjectCategory {get;}...
[PS] C:\Windows\system32>$copies | Get-Member -MemberType Property TypeName: Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopyName MemberType Definition---- ---------- ----------ActivationPreference Property System.Int32 ActivationPreference {get;}AdminDisplayName Property System.String AdminDisplayName {get;}DatabaseName Property System.String DatabaseName {get;}DistinguishedName Property System.String DistinguishedName {get;}ExchangeVersion Property Microsoft.Exchange.Data.ExchangeObjectVersion ExchangeVersion {get;}Guid Property System.Guid Guid {get;}HostServerName Property System.String HostServerName {get;}Identity Property Microsoft.Exchange.Data.ObjectId Identity {get;}IsValid Property System.Boolean IsValid {get;}ObjectCategory Property Microsoft.Exchange.Data.Directory.ADObjectId ObjectCategory {get;}
...
When I do Get-Member on the $copies variable on the Remote machine below is what I see
PS C:\Users\Superman> $copies | Get-Member -MemberType Properties TypeName: System.StringName MemberType Definition---- ---------- ----------Length Property System.Int32 Length {get;}
Notice the difference in TypeName. On the Local box it is “Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy” and on the Remote box it is “System.String”. Why does this happen?
When you run remote commands that generate output, the command output is transmitted across the network back to the local computer. Because most live Microsoft .NET Framework objects (such as the objects that Windows PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network. On the local computer, Windows PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object. However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized. This is how an MSDN article defines serialization in .NET framework. More details can be found in the article “How objects are sent to and from remote sessions”.
Luckily the data that we needed was available buy just using the Get-MailboxDatabase cmd-let by accessing the DatabaseCopies Property. To prove what is said above and show how to do it in C#, below is the code that I wrote:
using System;using System.Collections.Generic;using System.Text;using System.Management.Automation;using System.Management.Automation.Runspaces;using System.Management.Automation.Remoting;using System.Collections.ObjectModel;using System.Security;using System.Collections;namespace CallingPSCmdlet{ class Program { static void Main(string[] args) { string password = "Passowrd"; string userName = "Domain\\UserName"; System.Uri uri = new Uri(“http://CAS-SERVER/powershell?serializationLevel=Full”); System.Security.SecureString securePassword = String2SecureString(password); System.Management.Automation.PSCredential creds = new System.Management.Automation.PSCredential(userName, securePassword); Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(); PowerShell powershell = PowerShell.Create(); PSCommand command = new PSCommand(); command.AddCommand("New-PSSession"); command.AddParameter("ConfigurationName", "Microsoft.Exchange"); command.AddParameter("ConnectionUri", uri); command.AddParameter("Credential", creds); command.AddParameter("Authentication", "Default"); PSSessionOption sessionOption = new PSSessionOption(); sessionOption.SkipCACheck = true; sessionOption.SkipCNCheck = true; sessionOption.SkipRevocationCheck = true; command.AddParameter("SessionOption", sessionOption); powershell.Commands = command; try { // open the remote runspace runspace.Open(); // associate the runspace with powershell powershell.Runspace = runspace; // invoke the powershell to obtain the results Collection<PSSession> result = powershell.Invoke<PSSession>(); foreach (ErrorRecord current in powershell.Streams.Error) Console.WriteLine("The following Error happen when opening the remote Runspace: " + current.Exception.ToString() + " | InnerException: " + current.Exception.InnerException); if (result.Count != 1) throw new Exception("Unexpected number of Remote Runspace connections returned."); // Set the runspace as a local variable on the runspace powershell = PowerShell.Create(); command = new PSCommand(); command.AddCommand("Set-Variable"); command.AddParameter("Name", "ra"); command.AddParameter("Value", result[0]); powershell.Commands = command; powershell.Runspace = runspace; powershell.Invoke(); // First import the cmdlets in the current runspace (using Import-PSSession) command = new PSCommand(); command.AddScript("Import-PSSession -Session $ra"); powershell.Commands = command; powershell.Runspace = runspace; powershell.Invoke(); // Now call the Get-MaiboxDatabase command = new PSCommand(); command.AddCommand("Get-MailboxDatabase"); //Change the name of the database command.AddParameter("Identity", "Mailbox Database XXXXXXXX"); powershell.Commands = command; powershell.Runspace = runspace; Collection<PSObject> results = new Collection<PSObject>(); results = powershell.Invoke(); PSMemberInfo Member = null; foreach (PSObject oResult in results) { foreach (PSMemberInfo psMember in oResult.Members) { Member = psMember; DumpProperties(ref Member); } } results = null; Member = null; } finally { // dispose the runspace and enable garbage collection runspace.Dispose(); runspace = null; // Finally dispose the powershell and set all variables to null to free up any resources. powershell.Dispose(); powershell = null; } } //Method to Dump out the Properties public static void DumpProperties(ref PSMemberInfo psMember) { // Only look at Properties if (psMember.MemberType.ToString() == "Property") { switch (psMember.Name) { case "ActivationPreference": case "DatabaseCopies": if (psMember.Value != null) { PSObject oPSObject; ArrayList oArrayList; oPSObject = (PSObject)psMember.Value; oArrayList = (ArrayList)oPSObject.BaseObject; Console.WriteLine("Member Name:" + psMember.Name); Console.WriteLine("Member Type:" + psMember.TypeNameOfValue); Console.WriteLine("----------------------"); foreach (string item in oArrayList) { Console.WriteLine(item); } Console.WriteLine("----------------------"); } break; } } } private static SecureString String2SecureString(string password) { SecureString remotePassword = new SecureString(); for (int i = 0; i < password.Length; i++) remotePassword.AppendChar(password[i]); return remotePassword; } }}
using System;using System.Collections.Generic;using System.Text;using System.Management.Automation;using System.Management.Automation.Runspaces;using System.Management.Automation.Remoting;using System.Collections.ObjectModel;using System.Security;using System.Collections;namespace CallingPSCmdlet{ class Program { static void Main(string[] args) { string password = "Passowrd"; string userName = "Domain\\UserName";
System.Uri uri = new Uri(“http://CAS-SERVER/powershell?serializationLevel=Full”); System.Security.SecureString securePassword = String2SecureString(password); System.Management.Automation.PSCredential creds = new System.Management.Automation.PSCredential(userName, securePassword);
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace();
PowerShell powershell = PowerShell.Create(); PSCommand command = new PSCommand(); command.AddCommand("New-PSSession"); command.AddParameter("ConfigurationName", "Microsoft.Exchange"); command.AddParameter("ConnectionUri", uri); command.AddParameter("Credential", creds); command.AddParameter("Authentication", "Default"); PSSessionOption sessionOption = new PSSessionOption(); sessionOption.SkipCACheck = true; sessionOption.SkipCNCheck = true; sessionOption.SkipRevocationCheck = true; command.AddParameter("SessionOption", sessionOption); powershell.Commands = command;
try { // open the remote runspace runspace.Open(); // associate the runspace with powershell powershell.Runspace = runspace; // invoke the powershell to obtain the results Collection<PSSession> result = powershell.Invoke<PSSession>(); foreach (ErrorRecord current in powershell.Streams.Error) Console.WriteLine("The following Error happen when opening the remote Runspace: " + current.Exception.ToString() + " | InnerException: " + current.Exception.InnerException); if (result.Count != 1) throw new Exception("Unexpected number of Remote Runspace connections returned.");
// Set the runspace as a local variable on the runspace powershell = PowerShell.Create(); command = new PSCommand(); command.AddCommand("Set-Variable"); command.AddParameter("Name", "ra"); command.AddParameter("Value", result[0]); powershell.Commands = command; powershell.Runspace = runspace; powershell.Invoke();
// First import the cmdlets in the current runspace (using Import-PSSession) command = new PSCommand(); command.AddScript("Import-PSSession -Session $ra"); powershell.Commands = command; powershell.Runspace = runspace; powershell.Invoke();
// Now call the Get-MaiboxDatabase
command = new PSCommand(); command.AddCommand("Get-MailboxDatabase"); //Change the name of the database command.AddParameter("Identity", "Mailbox Database XXXXXXXX"); powershell.Commands = command; powershell.Runspace = runspace; Collection<PSObject> results = new Collection<PSObject>(); results = powershell.Invoke(); PSMemberInfo Member = null;
foreach (PSObject oResult in results) { foreach (PSMemberInfo psMember in oResult.Members) { Member = psMember; DumpProperties(ref Member); } } results = null; Member = null; } finally { // dispose the runspace and enable garbage collection runspace.Dispose(); runspace = null;
// Finally dispose the powershell and set all variables to null to free up any resources. powershell.Dispose(); powershell = null; } }
//Method to Dump out the Properties public static void DumpProperties(ref PSMemberInfo psMember) { // Only look at Properties if (psMember.MemberType.ToString() == "Property") { switch (psMember.Name) { case "ActivationPreference": case "DatabaseCopies": if (psMember.Value != null) { PSObject oPSObject; ArrayList oArrayList; oPSObject = (PSObject)psMember.Value; oArrayList = (ArrayList)oPSObject.BaseObject; Console.WriteLine("Member Name:" + psMember.Name); Console.WriteLine("Member Type:" + psMember.TypeNameOfValue); Console.WriteLine("----------------------"); foreach (string item in oArrayList) { Console.WriteLine(item); } Console.WriteLine("----------------------"); } break; } } } private static SecureString String2SecureString(string password) { SecureString remotePassword = new SecureString(); for (int i = 0; i < password.Length; i++) remotePassword.AppendChar(password[i]); return remotePassword; } }}
Member Name:DatabaseCopiesMember Type:Deserialized.Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy[]----------------------Mailbox Database 1056078029\AKAS23474121Mailbox Database 1056078029\AKAS23474120----------------------Member Name:ActivationPreferenceMember Type:Deserialized.System.Collections.Generic.KeyValuePair`2[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=14.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]][]----------------------[AKAS23474121, 1][AKAS23474120, 2]----------------------
In this case the customer wanted to know the Item count, size of all the item in the Inbox that were older than 5 years. As usual, the requirement was to run the script against multiple mailboxes. I could have easily written this in C# in no time but preferred to do this in PowerShell as:
The downside is that you do not get the IntelliSense that you get with Visual Studio. With all that said lets get down straight to code. This example shows the use of Impersonation, SearchFilter, FindItems, Paging in Exchange Web Services. Create a .PS1 as below:
# The script requires the EWS managed API, which can be downloaded here:# http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1# This also requires PowerShell 2.0# Make sure the Import-Module command below matches the DLL location of the API.# This path must match the install location of the EWS managed API. Change it if needed.[string]$info = "White" # Color for informational messages[string]$warning = "Yellow" # Color for warning messages[string]$errorMessage = "Red" # Color for error messages[string]$LogFile = "C:\Temp\Log.txt" # Path of the Log Filefunction GetItemCount($MailboxName){ Add-Content $LogFile ("-------------------------------------------------" ) Write-host "Searching Inbox for Mailbox Name:" $MailboxName -foregroundcolor $info Add-Content $LogFile ("Searching Inbox for Mailbox Name:" + $MailboxName) #Change the user to Impersonate $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName); #Number of Items to Get $pageSize =50 $Offset = 0 $MoreItems =$true $ItemCount=0 $ItemSize=0 while ($MoreItems) { #Setup the View to get a limited number of Items at one time $itemView = new-object Microsoft.Exchange.WebServices.Data.ItemView($pageSize,$Offset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning) $itemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow #Get the Size of the Items $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size); #Create the Search Filter - Subtract 5 Years from todays date. $oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, ([System.Datetime]::now).AddMonths(-60)) $oFindItems = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$oSearchFilter,$itemView) $ItemCount += $oFindItems.Items.Count $i=0 #Loop through the items returned and add up the size foreach ($Item in $oFindItems.Items) { $i++ $ItemSize += $Item.Size Write-Progress -activity "Adding Item Sizes" -status "Percent added: " -PercentComplete (($i /$oFindItems.Items.Count) * 100) } if ($oFindItems.MoreAvailable -eq $false) {$MoreItems = $false} if ($MoreItems) {$Offset += $pageSize} } Write-host "Items Found in Inbox:" $ItemCount -foregroundcolor $info Add-Content $LogFile ("Items Found in Inbox:" + $ItemCount ) Write-host "Size of Items:" ($ItemSize / 1Mb) "MB" -foregroundcolor $info Add-Content $LogFile ("Size of Items:" + ($ItemSize / 1Mb) + " MB" ) Add-Content $LogFile ("-------------------------------------------------" ) $service.ImpersonatedUserId = $null}#Change the path if neededImport-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)# Set the Credentials$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("serviceAccount","Password","Domain")# Change the URL to point to your cas server$service.Url= new-object Uri(“http://CAS-SERVER/EWS/Exchange.asmx”)# Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above$UseAutoDiscover = $falseGet-Mailbox | foreach-object { $WindowsEmailAddress = $_.WindowsEmailAddress.ToString() if ($UseAutoDiscover -eq $true) { Write-host "Autodiscovering.." -foregroundcolor $info $UseAutoDiscover = $false $service.AutodiscoverUrl($WindowsEmailAddress) Write-host "Autodiscovering Done!" -foregroundcolor $info Write-host "EWS URL set to :" $service.Url -foregroundcolor $info } #To catch the Exceptions generated trap [System.Exception] { Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $errorMessage; Add-Content $LogFile ("Error: " + $_.Exception.Message); continue; } GetItemCount($WindowsEmailAddress)}
# The script requires the EWS managed API, which can be downloaded here:# http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1# This also requires PowerShell 2.0# Make sure the Import-Module command below matches the DLL location of the API.# This path must match the install location of the EWS managed API. Change it if needed.
[string]$info = "White" # Color for informational messages[string]$warning = "Yellow" # Color for warning messages[string]$errorMessage = "Red" # Color for error messages[string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File
function GetItemCount($MailboxName){ Add-Content $LogFile ("-------------------------------------------------" ) Write-host "Searching Inbox for Mailbox Name:" $MailboxName -foregroundcolor $info Add-Content $LogFile ("Searching Inbox for Mailbox Name:" + $MailboxName)
#Change the user to Impersonate $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName);
#Number of Items to Get $pageSize =50 $Offset = 0 $MoreItems =$true $ItemCount=0 $ItemSize=0
while ($MoreItems) { #Setup the View to get a limited number of Items at one time $itemView = new-object Microsoft.Exchange.WebServices.Data.ItemView($pageSize,$Offset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning) $itemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow
#Get the Size of the Items $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size); #Create the Search Filter - Subtract 5 Years from todays date. $oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, ([System.Datetime]::now).AddMonths(-60)) $oFindItems = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$oSearchFilter,$itemView) $ItemCount += $oFindItems.Items.Count $i=0
#Loop through the items returned and add up the size foreach ($Item in $oFindItems.Items) { $i++ $ItemSize += $Item.Size Write-Progress -activity "Adding Item Sizes" -status "Percent added: " -PercentComplete (($i /$oFindItems.Items.Count) * 100) } if ($oFindItems.MoreAvailable -eq $false) {$MoreItems = $false} if ($MoreItems) {$Offset += $pageSize} }
Write-host "Items Found in Inbox:" $ItemCount -foregroundcolor $info Add-Content $LogFile ("Items Found in Inbox:" + $ItemCount ) Write-host "Size of Items:" ($ItemSize / 1Mb) "MB" -foregroundcolor $info Add-Content $LogFile ("Size of Items:" + ($ItemSize / 1Mb) + " MB" ) Add-Content $LogFile ("-------------------------------------------------" ) $service.ImpersonatedUserId = $null}
#Change the path if neededImport-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
# Set the Credentials$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("serviceAccount","Password","Domain")
# Change the URL to point to your cas server$service.Url= new-object Uri(“http://CAS-SERVER/EWS/Exchange.asmx”)
# Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above$UseAutoDiscover = $false
Get-Mailbox | foreach-object { $WindowsEmailAddress = $_.WindowsEmailAddress.ToString() if ($UseAutoDiscover -eq $true) { Write-host "Autodiscovering.." -foregroundcolor $info $UseAutoDiscover = $false $service.AutodiscoverUrl($WindowsEmailAddress) Write-host "Autodiscovering Done!" -foregroundcolor $info Write-host "EWS URL set to :" $service.Url -foregroundcolor $info } #To catch the Exceptions generated trap [System.Exception] { Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $errorMessage; Add-Content $LogFile ("Error: " + $_.Exception.Message); continue; } GetItemCount($WindowsEmailAddress)}
To Run the script: 1)Open the Exchange Management Shell 2)Navigate to the location where the script file is. 3)Type in .\ScriptName.PS1 and hit enter to execute the script
On Wednesday, 7/27, the Exchange Sustained Engineering team re-released Exchange 2010 SP1 RU4. This release is being tracked by KB 2579150 and will contain a download file that is unique from the previous release. This updated rollup contains all of the changes that were in the original rollup with the exception of the regression because if which the previous RU4 was pulled back.
For customers who have the previous version of RU4 and the IU 2581545 installed, there is technically no need to install this updated version of RU4 as the combination of those two releases is functionally equivalent to the updated RU4 dated Wednesday, 7/27. The only thing a customer would gain by installing the updated rollup 4 would be performing the IU maintenance now that they would need to perform later when installing RU5.
More details on the Exchange 2010 SP1 RU4 re-release can be found here and here. As for RU5, it is expected to be released by the end of August 2011.
Enjoy!
A customer of mine wanted to create a folder under Inbox for thousands of mailboxes. He also wanted to use PowerShell to do it. What better way that using Exchange Web Service(Managed API) with Impersonation to do the job.
The list of the users are available in a text file. The First row denotes the Field Names.The file is named UserAccounts.txt. Format of the text file is as below:
WindowsEmailAddressakasha@contoso.comakashb@contoso.comakashc@contoso.com
# The script requires the EWS managed API, which can be downloaded here:# http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1# This also requires PowerShell 2.0# Make sure the Import-Module command below matches the DLL location of the API.# This path must match the install location of the EWS managed API. Change it if needed.[string]$info = "White" # Color for informational messages[string]$warning = "Yellow" # Color for warning messages[string]$error = "Red" # Color for error messages[string]$LogFile = "C:\Temp\Log.txt" # Path of the Log Filefunction CreateFolder($MailboxName){ Write-host "Creating Folder for Mailbox Name:" $MailboxName -foregroundcolor $info Add-Content $LogFile ("Creating Folder for Mailbox Name:" + $MailboxName) #Change the user to Impersonate $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName); #Create the folder object $oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service) $oFolder.DisplayName = $FolderName #Call Save to actually create the folder $oFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox) Write-host "Folder Created for " $MailboxName -foregroundcolor $warning Add-Content $LogFile ("Folder Created for " + $MailboxName) $service.ImpersonatedUserId = $null}#Change the name of the folder$FolderName = "My Folder"Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)# Set the Credentials$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("serviceAccount","Password","Domain")# Change the URL to point to your cas server$service.Url= new-object Uri(http://CAS-Server/EWS/Exchange.asmx)# Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above$UseAutoDiscover = $false#Read data from the UserAccounts.txtimport-csv UserAccounts.txt | foreach-object { $WindowsEmailAddress = $_.WindowsEmailAddress.ToString() if ($UseAutoDiscover -eq $true) { Write-host "Autodiscovering.." -foregroundcolor $info $UseAutoDiscover = $false $service.AutodiscoverUrl($WindowsEmailAddress) Write-host "Autodiscovering Done!" -foregroundcolor $info Write-host "EWS URL set to :" $service.Url -foregroundcolor $info } #To catch the Exceptions generated trap [System.Exception] { Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $error; Add-Content $LogFile ("Error: " + $_.Exception.Message); continue; } CreateFolder($WindowsEmailAddress)}
[string]$info = "White" # Color for informational messages[string]$warning = "Yellow" # Color for warning messages[string]$error = "Red" # Color for error messages[string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File
function CreateFolder($MailboxName){ Write-host "Creating Folder for Mailbox Name:" $MailboxName -foregroundcolor $info Add-Content $LogFile ("Creating Folder for Mailbox Name:" + $MailboxName)
#Create the folder object
$oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service) $oFolder.DisplayName = $FolderName
#Call Save to actually create the folder $oFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox) Write-host "Folder Created for " $MailboxName -foregroundcolor $warning Add-Content $LogFile ("Folder Created for " + $MailboxName)
$service.ImpersonatedUserId = $null}
#Change the name of the folder$FolderName = "My Folder"
Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
# Change the URL to point to your cas server$service.Url= new-object Uri(http://CAS-Server/EWS/Exchange.asmx)
#Read data from the UserAccounts.txtimport-csv UserAccounts.txt | foreach-object { $WindowsEmailAddress = $_.WindowsEmailAddress.ToString() if ($UseAutoDiscover -eq $true) { Write-host "Autodiscovering.." -foregroundcolor $info $UseAutoDiscover = $false $service.AutodiscoverUrl($WindowsEmailAddress) Write-host "Autodiscovering Done!" -foregroundcolor $info Write-host "EWS URL set to :" $service.Url -foregroundcolor $info } #To catch the Exceptions generated trap [System.Exception] { Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $error; Add-Content $LogFile ("Error: " + $_.Exception.Message); continue; } CreateFolder($WindowsEmailAddress)}
A few articles on working with Exchange Online (Office 365 for enterprises, Live@edu) with Windows Powershell.
Install and Configure Windows PowerShellhttp://technet.microsoft.com/en-us/exchangelabshelp/cc952756
Connect Windows PowerShell to the Servicehttp://technet.microsoft.com/en-us/exchangelabshelp/cc952755
Reference to Available PowerShell Cmdlets in Exchange Onlinehttp://technet.microsoft.com/en-us/exchangelabshelp/dd575549
Administrator Role Groups in Exchange Onlinehttp://technet.microsoft.com/en-us/exchangelabshelp/ee441216
Role Based Access Control in Exchange Onlinehttp://technet.microsoft.com/en-us/exchangelabshelp/dd207274
Role Assignment Policies in Exchange Onlinehttp://technet.microsoft.com/en-us/exchangelabshelp/ee424427