• Akash Blogging......

    Stamping Retention Policy Tag on Items using EWS Managed API 1.1 from PowerShell(Exchange 2010)–Part 2

    • 0 Comments

    Yes, this is similar to the script in my previous post but is also different in the following ways:

    • This script searches the entire mailbox and looks for items in folders that contain mail items or in other words where the folders class is IPF.Note
    • This scripts also shows how to use the Not, Exists the AND logical operator to build up a Search Filter collection

    I would suggest you read my previous post as it covers some fundamental used in this post.

    To summarize what the script below does:

    • We start of by passing the SMTP address of the mailbox we want to search. We then do a deep folder traversal to get a list of all the folder where the FolderClass = “IPF.Note”.
    • We then pass to folder id to the StampPolicyOnItems function which then looks for Items which have a specific message class AND  does not have the PR_POLICY_TAG property stamped on the item. This is done using Search Filter Collection.
    • Finally we loop through all the items that meet the criteria above and stamp the PR_POLICY_TAG and the PR_RETENTION_PERIOD properties on the item and update it.

    Straight to code now. Create a .PS1 file with the code 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]$error = "Red" # Color for error messages
    [string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File

    function SearchFolders($MailboxName)
    {
    Write-host "Searching folders in Mailbox Name:" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Searching folders in 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
    $FpageSize =50
    $FOffset = 0
    $MoreFolders =$true

    while ($MoreFolders)
    {

    #Setup the View to get a limited number of Items at one time
    $folderView = new-object Microsoft.Exchange.WebServices.Data.FolderView($FpageSize,$FOffset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning)
    $folderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
    $folderView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet(
    [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
    [Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,
    [Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass);

    #Create the Search Filter.
    $FoSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass, "IPF.Note")

    $oFindFolders = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$FoSearchFilter,$folderView)

    foreach ($folder in $oFindFolders.Folders)
    {

    Write-host "Begining to stamp policy on Items in folder:" $folder.DisplayName -foregroundcolor $info;
    Add-Content $LogFile ("Begining to stamp policy on Items in folder:" + $folder.DisplayName);

    &{

    StampPolicyOnItems($folder.Id.UniqueId)

    Write-host "Retention policy stamped on Items in folder:" $folder.DisplayName -foregroundcolor $info
    Add-Content $LogFile ("Retention policy stamped on Items in folder:" + $folder.DisplayName)

    }
    trap [System.Exception]
    {
    $IsFailure = $true;
    Write-host ("Error in StampPolicyOnItems: " + $_.Exception.Message) -foregroundcolor $error;
    Add-Content $LogFile ("Error in StampPolicyOnItems: " + $_.Exception.Message);
    Write-host "Failure in stamping Retention policy on Items in folder:" $folder.DisplayName -foregroundcolor $info
    Add-Content $LogFile ("Failure in stamping Retention policy on Items in folder:" + $folder.DisplayName)

    continue;
    }

    }


    if ($oFindFolders.MoreAvailable -eq $false)
    {$MoreFolders = $false}

    if ($MoreFolders)
    {$FOffset += $FpageSize}

    }

    Write-host "Finished with Mailbox Name:" $MailboxName "Done!" -foregroundcolor $info
    Write-host "-------------------------------------------------" -foregroundcolor $info

    Add-Content $LogFile ("Done!")
    Add-Content $LogFile ("-------------------------------------------------" )

    $service.ImpersonatedUserId = $null
    }



    function StampPolicyOnItems($fId)
    {
    #Number of Items to Get
    $pageSize =100
    $Offset = 0
    $MoreItems =$true
    $ItemCount=0

    #PR_POLICY_TAG 0x3019
    $PolicyTag = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3019,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);

    #PR_RETENTION_PERIOD 0x301A
    $RetentionPeriod = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301A,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);

    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
    $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet(
    [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
    [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::ItemClass,
    [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::Subject);

    #Create the Search Filter.
    $oSearchFilters = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection(
    [Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)


    $oSearchFilterEA1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::ItemClass, "IPM.My-ARCHIVE")
    $oSearchFilterEA2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Not(New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists($PolicyTag))

    $oSearchFilters.add($oSearchFilterEA1)
    $oSearchFilters.add($oSearchFilterEA2)


    $uniqueId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fId);

    $oFindItems = $service.FindItems($uniqueId,$oSearchFilters,$itemView)


    foreach ($Item in $oFindItems.Items)
    {
    $ItemCount++

    Write-host "Item Number:" $ItemCount
    Write-host "Message Class:" $Item.ItemClass
    Write-host "Subject:" $Item.Subject


    #Same as that on the policy
    $Item.SetExtendedProperty($RetentionPeriod, 1095)

    #Change the GUID based on your policy tag
    $PolicyTagGUID = new-Object Guid("{92186ff7-7f4d-4efa-a09b-bbdc5aee3908}");

    $Item.SetExtendedProperty($PolicyTag, $PolicyTagGUID.ToByteArray())

    $Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
    Write-host "Retention policy stamped on " $Item.ID -foregroundcolor $info
    Add-Content $LogFile ("Retention policy stamped on " + $Item.ID)

    }



    if ($oFindItems.MoreAvailable -eq $false)
    {$MoreItems = $false}

    if ($MoreItems)
    {$Offset += $pageSize}

    }

    }

    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://YOUR-CAS/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.txt
    import-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;
    }
    SearchFolders($WindowsEmailAddress)
    }




    UserAccounts.txt contains the list of the users mailboxes in which the items needs to be stamped. This file should exist in the same directory as the .PS1 file.The First row denotes the Field Names. Format of the text file is as below:

    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    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
    Note: This script creates a log file in C:\Temp. Make sure that directory exists.

    Enjoy!

  • Akash Blogging......

    Stamping Retention Policy Tag on Items using EWS Managed API 1.1 from PowerShell(Exchange 2010)

    • 0 Comments

    This post deals with a scenario where we need to stamp Retention Policy on specific items. In this case we wanted to stamp a policy on items with a specific message class. In my previous post I had explained about Messaging records management and how to stamp the policy on a folder. Please do read my previous post if you have not yet done so.

    The process of stamping Retention Policy on items is not much different, when applying the Retention Policy to an item, we need to stamp it with two important properties:

    • PR_POLICY_TAG – The data type for this property is binary . The value that it contains is the GUID of the retention tag that you create. Do a Get-RetentionPolicyTag YourTagName |fl and copy the value in the GUID property. This value will be uses in the script later on.
    • PR_RETENTION_PERIOD – The data type for this property is Integer. This is the same value that you specify for the AgeLimitForRetention while creating or updating the retention policy tag.

    Before you begin:
    You will need to download and install the EWS Managed API on the box you run this script on from the link below:
    Exchange Web Services Managed API
    http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1

    Read about setting up Impersonation in Exchange 2010 as the code below uses it.
    Configuring Exchange Impersonation (Exchange Web Services)
    http://msdn.microsoft.com/en-us/library/bb204095.aspx

    Using Exchange Impersonation XML Request/Response
    http://msdn.microsoft.com/en-us/library/bb204088.aspx

    Using Impersonation in EWS Managed API
    http://msdn.microsoft.com/en-us/library/dd633680(EXCHG.80).aspx

    We will stamp the properties using Exchange Web Services Managed API 1.1 in PowerShell 2.0 and below is how it is done. Create a .PS1 file with the code below:

    # This script 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 File

    function StampPolicyOnItems($MailboxName)
    {
    Write-host "Stamping Policy on Items for Mailbox Name:" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Stamping Policy on Items for Mailbox Name:" + $MailboxName)

    #Change the user to Impersonate. Comment the line below if impersonation is not needed
    $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

    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
    $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet(
    [Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,
    [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::ItemClass,
    [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::Subject);

    #Create the Search Filter and look for items with the message class IPM.My-ARCHIVE
    $oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::ItemClass, "IPM.My-ARCHIVE")

    #Look for items in the Inbox


    $oFindItems = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$oSearchFilter,$itemView)

    foreach ($Item in $oFindItems.Items)
    {
    $ItemCount++

    Write-host "Item Number:" $ItemCount
    Write-host "Message Class:" $Item.ItemClass
    Write-host "Subject:" $Item.Subject

    #PR_POLICY_TAG 0x3019
    $PolicyTag = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3019,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)

    #PR_RETENTION_PERIOD 0x301A
    $RetentionPeriod = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301A,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)

    #Same as that on the policy
    $Item.SetExtendedProperty($RetentionPeriod, 1095)

    #Change the GUID based on your policy tag
    $PolicyTagGUID = new-Object Guid("{92186ff7-7f4d-4efa-a09b-bbdc5aee3908}")

    $Item.SetExtendedProperty($PolicyTag, $PolicyTagGUID.ToByteArray())

    $Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)

    Write-host "Retention policy stamped on " $Item.ID -foregroundcolor $info
    Add-Content $LogFile ("Retention policy stamped on " + $Item.ID)

    }


    if ($oFindItems.MoreAvailable -eq $false)
    {$MoreItems = $false}

    if ($MoreItems)
    {$Offset += $pageSize}

    }

    Write-host "Stamping Policy on Items for Mailbox Name:" $MailboxName "Done!" -foregroundcolor $info
    Add-Content $LogFile ("Done!")
    Add-Content $LogFile ("-------------------------------------------------" )

    #Comment the line below if impersonation is not needed

    $service.ImpersonatedUserId = $null
    }

    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,Use the account that has been granted Impersonation rights
    # If impersonation is not used, use the credentials required to access the mailbox
    $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://Your-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.txt
    import-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;
    }
    StampPolicyOnItems($WindowsEmailAddress)
    }


    UserAccounts.txt contains the list of the users mailboxes in which the items needs to be stamped. This file should exist in the same directory as the .PS1 file.The First row denotes the Field Names. Format of the text file is as below:
    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    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

    Note: This script creates a log file in C:\Temp. Make sure that directory exists.

    Enjoy!

  • Akash Blogging......

    Searching contacts using EWS Managed API 1.1 from PowerShell (Impersonation, SearchFilterCollection, ContainsSubstring)

    • 0 Comments

    Here is another example of using EWS Managed API with PowerShell 2.0. In this case the requirement was to be able to find out all contacts that have Hotmail email address in any of the Email Address fields. This provided me to opportunity to demonstrate the use SearchFilterCollection and ContainsSubstring search filter from a PowerShell script. In addition to SearchFilterCollection and ContainsSubstring, this sample also shows the use of Impersonation. The use of Impersonation is necessary because the requirement was to search in multiple mailboxes.

    Details on how to configure Exchange Impersonation can be found in the links below:
    Exchange 2010:
    Configuring Exchange Impersonation (Exchange Web Services)
    http://msdn.microsoft.com/en-us/library/bb204095.aspx

    Using Exchange Impersonation XML Request/Response
    http://msdn.microsoft.com/en-us/library/bb204088.aspx

    Using Impersonation in EWS Managed API
    http://msdn.microsoft.com/en-us/library/dd633680(EXCHG.80).aspx

    Exchange 2007:
    Configuring Exchange Impersonation (Exchange Web Services)
    http://msdn.microsoft.com/en-us/library/bb204095(EXCHG.80).aspx

    Using Exchange Impersonation (Exchange Web Services)
    http://msdn.microsoft.com/en-us/library/bb204088(EXCHG.80).aspx

    You will also need to download and install the EWS Managed API on the box you run this script on from the link below:

    Exchange Web Services Managed API
    http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1

    Now for the code. Create a .PS1 file with the code below:

    # This script 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 File

    function SearchContacts($MailboxName)
    {
    Write-host "Searching Contacts in Mailbox :" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Searching Contacts in Mailbox :" + $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 =40
    $Offset = 0
    $MoreItems =$true
    $ItemCount=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

    $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ContactSchema]::GivenName,
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::Surname,
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress1,
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress2,
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress3)

    #Create the Search Filter.
    $searchFilterEA1 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring(
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress1,"@hotmail.com")

    $searchFilterEA2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring(
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress2,"@hotmail.com")

    $searchFilterEA3 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring(
    [Microsoft.Exchange.WebServices.Data.ContactSchema]::EmailAddress3,"@hotmail.com")

    $oSearchFilters = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection(
    [Microsoft.Exchange.WebServices.Data.LogicalOperator]::Or)

    $oSearchFilters.add($searchFilterEA1)
    $oSearchFilters.add($searchFilterEA2)
    $oSearchFilters.add($searchFilterEA3)

    $oFindItems = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$oSearchFilters,$itemView)

    foreach ($Item in $oFindItems.Items)
    {

    if ($Item.GetType().FullName -eq "Microsoft.Exchange.WebServices.Data.Contact")
    {
    Write-host "Given Name:" $Item.GivenName
    Write-host "Surname:" $Item.Surname
    Write-host "Email Address 1:" $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]
    Write-host "Email Address 2:" $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress2]
    Write-host "Email Address 3:" $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress3]

    Add-Content $LogFile("Given Name :" + $Item.GivenName)
    Add-Content $LogFile("Surname :" + $Item.Surname)
    Add-Content $LogFile("Email Address 1:" + $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1])
    Add-Content $LogFile("Email Address 2:" + $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress2])
    Add-Content $LogFile("Email Address 3:" + $Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress3])

    }
    }

    if ($oFindItems.MoreAvailable -eq $false)
    {$MoreItems = $false}

    if ($MoreItems)
    {$Offset += $pageSize}

    }

    Write-host "Searching Contacts in Mailbox :" $MailboxName "Done!" -foregroundcolor $info
    Add-Content $LogFile ("Done!")
    Add-Content $LogFile ("-------------------------------------------------" )

    $service.ImpersonatedUserId = $null
    }

    Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"

    # This script also works on Exchange 2007. Just change the Version Header to Exchange2007_SP1
    $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://Your-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.txt
    import-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;
    }
    SearchContacts($WindowsEmailAddress)
    }

    UserAccounts.txt contains the list of the users mailboxes that need to be searched. This file should exist in the same directory as the .PS1 file.The First row denotes the Field Names. Format of the text file is as below:

    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    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

    Note: This script creates a log file in C:\Temp. Make sure that directory exists.

    Enjoy!

  • Akash Blogging......

    Unable to set the Folder Property in Outlook View Control–Getting error 8002801d (Library Not Registered)

    • 0 Comments

    One of my customer ran into this issue with the Outlook View Control. All we were doing was trying to set the Folder property to the Contacts folder so that when the control loads it shows the contacts in that folder by default. In our case it gave the error 8002801d (Library Not Registered) and displayed the Inbox folder which is the default. This problem was not happening on all the machines.

    The strange thing was that it showed the Inbox in the View control, to me it means that the library is registered and that is why it was able to show Inbox. Why is it not showing the contacts? What could be going wrong? Which library are we talking about? After a little debugging, as expected we found out that the LoadLibrary call was failing. I was able to get the GUID it was looking for and it was {0006F062-0000-0000-C000-000000000046} which is for OUTLCTL.DLL.

    This is where Process Monitor came to our help. I decided to take a Process Monitor log on a non-working and a working machine. Below is what I see:

    On the non-working machine( looking for {0006F062-0000-0000-C000-000000000046} ) this is how it looked:

    OUTLOOK.EXE    RegQueryKey    HKCU\Software\Classes                                                        SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib SUCCESS
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\409 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\409 NAME NOT FOUND
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\9 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\9 NAME NOT FOUND
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\0 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0\0 NAME NOT FOUND
    OUTLOOK.EXE RegCloseKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 SUCCESS
    OUTLOOK.EXE RegCloseKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegCloseKey HKCU\Software\Classes\TypeLib SUCCESS

    On the working machine( looking for {0006F062-0000-0000-C000-000000000046} ) this is how it looked:
    OUTLOOK.EXE    RegCloseKey    HKCU\Software\Classes                                                                SUCCESS
    OUTLOOK.EXE RegQueryKey HKCU\Software\Classes\TypeLib SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046} NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.0 NAME NOT FOUND
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046} NAME NOT FOUND
    OUTLOOK.EXE RegEnumKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegEnumKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} NO MORE ENTRIES
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046} SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\409 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\409 NAME NOT FOUND
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\9 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\9 NAME NOT FOUND
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 SUCCESS
    OUTLOOK.EXE RegCloseKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 SUCCESS
    OUTLOOK.EXE RegCloseKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 NAME NOT FOUND
    OUTLOOK.EXE RegOpenKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 SUCCESS
    OUTLOOK.EXE RegQueryKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 SUCCESS
    OUTLOOK.EXE RegOpenKey HKCU\Software\Classes\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 NAME NOT FOUND
    OUTLOOK.EXE RegQueryValue HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32\(Default) SUCCESS
    OUTLOOK.EXE RegCloseKey HKCR\TypeLib\{0006F062-0000-0000-C000-000000000046}\1.1\0\win32 SUCCESS
    OUTLOOK.EXE CreateFile C:\Program Files\Microsoft Office\Office14\OUTLCTL.DLL SUCCESS
    OUTLOOK.EXE ReadFile C:\Program Files\Microsoft Office\Office14\OUTLCTL.DLL SUCCESS
    OUTLOOK.EXE ReadFile C:\Program Files\Microsoft Office\Office14\OUTLCTL.DLL SUCCESS

    Now, comparing the entries of the working and non-working machine, we can clearly see:
    1) That the keys ending with 1.0 don’t exist on the working machine but they do on the non-working machine.
    2) On the working machine it continues to search for keys ending with 1.1 (Version 1.1 of OVC) and goes on to find the OUTLCTL.DLL, This does not happen on the non-working machine.
    So, I decided to take a look at the registry of the non-working machine and below is how it looks:
    image
     
    And on the working machine, this is how it looks:
    image

    What’s the difference? On the working machine there is no 1.0 key! If you look at the “PrimaryInteropAssemblyName” key on the non-working machine, it points to version 10.0.4504.0 which is a build from Office XP. This is a left over key from one of the previous installs of Office and the Interops.
    We decided to take a backup of the key and delete it. As soon as we deleted the 1.0 key and tested the code again, everything WORKED!

    Moral of the story? Comparing Process Monitor logs can give you answers Smile.

    Enjoy!

  • Akash Blogging......

    Stamping Retention Policy Tag using EWS Managed API 1.1 from PowerShell(Exchange 2010)

    • 2 Comments

    Exchange Web Services with PowerShell! In the last few days I have written quite a few scripts that use Exchange Web Services Managed API 1.1 with PowerShell, you will agree that it is not as easy as writing Exchange Web Services Managed API 1.1 with C# and it takes a bit of getting used to it. One of my customer wanted to stamp a Retention Policy Tag on a folder in the Archived mailbox in Exchange 2010. What is a Retention Policy Tag?

    Messaging records management (MRM) is the records management technology in Microsoft Exchange Server 2010 that helps organizations reduce the legal risks associated with e-mail. MRM makes it easier to keep the messages needed to comply with company policy, government regulations, or legal needs, and to remove content that has no legal or business value. This is accomplished through the use of retention policies or managed folders.

    Retention policies, use retention tags to apply retention settings. You create retention tags, and link them to a retention policy. Mailboxes that have a retention policy applied to them are processed by the Managed Folder Assistant, a mailbox assistant that runs on a schedule and provisions retention tags in mailboxes. More details about understanding and creating retention policies and tags can be found in the following articles on MSDN:

    Now that you understand what a Retention Policy Tag is, when applied to a folder stamps it with three important properties:

    • PR_POLICY_TAG – The data type for this property is binary . The value that it contains is the GUID of the retention tag that you create. Do a Get-RetentionPolicyTag YourTagName |fl and copy the value in the GUID property. This value will be uses in the script later on.
    • PR_RETENTION_FLAGS – The data type for this property is Integer. The easiest would be to get the values using MFCMAPI once the policy is applied to the Mailbox and the policy tag to the folder(screenshot below). “Test Policy” is the folder where this retention policy tag is applied. If you don’t see these properties that means that there is no policy tag on the folder.
    • PR_RETENTION_PERIOD – The data type for this property is Integer. This is the same value that you specify for the AgeLimitForRetention while creating or updating the retention policy tag.

    MFCMAPI

    We will stamp the same properties using Exchange Web Services Managed API 1.1 in PowerShell and this is how it is done:

    # 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 File

    function StampPolicyOnFolder($MailboxName)
    {
    Write-host "Stamping Policy on folder for Mailbox Name:" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Stamping Policy on 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);

    #Search for the folder you want to stamp the property on
    $oFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
    $oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName)

    #Uncomment the line below if the folder is in the regular mailbox
    #$oFindFolderResults = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$oSearchFilter,$oFolderView)

    #Comment the line below and uncomment the line above if the folder is in the regular mailbox
    $oFindFolderResults = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveMsgFolderRoot,$oSearchFilter,$oFolderView)

    if ($oFindFolderResults.TotalCount -eq 0)
    {
    Write-host "Folder does not exist in Mailbox:" $MailboxName -foregroundcolor $warning
    Add-Content $LogFile ("Folder does not exist in Mailbox:" + $MailboxName)
    }
    else
    {
    Write-host "Folder found in Mailbox:" $MailboxName -foregroundcolor $info

    #PR_POLICY_TAG 0x3019
    $PolicyTag = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x3019,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);

    #PR_RETENTION_FLAGS 0x301D
    $RetentionFlags = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);

    #PR_RETENTION_PERIOD 0x301A
    $RetentionPeriod = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x301A,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);

    #Bind to the folder found
    $oFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$oFindFolderResults.Folders[0].Id)

    #Same as the value in the PR_RETENTION_FLAGS property
    $oFolder.SetExtendedProperty($RetentionFlags, 137)

    #Same as the value in the PR_RETENTION_PERIOD property
    $oFolder.SetExtendedProperty($RetentionPeriod, 1095)

    #Change the GUID based on your policy tag
    $PolicyTagGUID = new-Object Guid("{92186ff7-7f4d-4efa-a09b-bbdc5aee3908}");

    $oFolder.SetExtendedProperty($PolicyTag, $PolicyTagGUID.ToByteArray())

    $oFolder.Update()

    Write-host "Retention policy stamped!" -foregroundcolor $info
    Add-Content $LogFile ("Retention policy stamped!")

    }

    $service.ImpersonatedUserId = $null
    }

    #Change the name of the folder. This is the folder the properties will be stamped on.
    $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("UserName","Password","Domain")

    # Change the URL to point to your cas server
    $service.Url= new-object Uri(http://YOUR-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.txt.
    #This file must exist in the same location as the script.

    import-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;
    }
    StampPolicyOnFolder($WindowsEmailAddress)
    }

    UserAccounts.txt contains the list of the users mailboxes that contain the folder that is set in the $FolderName variable in the script above.The First row denotes the Field Names. Format of the text file is as below:

    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    To Run the script:
    1)Open the Exchange Management Shell
    2)Navigate to the location where the script and the UserAccounts.txt file is.
    3)Type in .\ScriptName.PS1 and hit enter to execute the script
     
    Thank you Vikas Soundade(Exchange) for helping me with the MRM piece!
     
    Enjoy!
  • Akash Blogging......

    How to get information on Database Copies using Managed code and Remote Powershell(Exchange 2010)

    • 2 Comments

    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>$copies

    DatabaseName : Mailbox Database 1056078029
    HostServerName : AKAS23474121
    ActivationPreference : 1
    ParentObjectClass : msExchPrivateMDB
    ReplayLagTime : 00:00:00
    TruncationLagTime : 00:00:00
    AdminDisplayName :
    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=LOCAL
    Identity : Mailbox Database 1056078029\AKAS23474121
    Guid : 562cb271-7cee-4130-9582-e3ce0f08cb3d
    ObjectCategory : DOM234741.LOCAL/Configuration/Schema/ms-Exch-MDB-Copy
    ObjectClass : {top, msExchMDBCopy}
    WhenChanged : 7/21/2011 4:29:06 PM
    WhenCreated : 7/21/2011 4:29:06 PM
    WhenChangedUTC : 7/21/2011 10:59:06 AM
    WhenCreatedUTC : 7/21/2011 10:59:06 AM
    OrganizationId :
    OriginatingServer : AKAS23474118.DOM234741.LOCAL
    IsValid : True

    DatabaseName : Mailbox Database 1056078029
    HostServerName : AKAS23474120
    ActivationPreference : 2
    ParentObjectClass : msExchPrivateMDB
    ReplayLagTime : 00:00:00
    TruncationLagTime : 00:00:00
    AdminDisplayName :
    ExchangeVersion : 0.10 (14.0.100.0)
    ...


    The $copies variable contains live objects, we can then pipe it into a foreach-object and print out the selected properties we want to. The reason I am saying this is because there is a difference which I will talk about later. For now, I connect to the same CAS Server from a remote machine and run the exact same commands and following is the output:

    PS C:\Users\Superman> $copies=(Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopies
    PS C:\Users\Superman> $copies
    Mailbox Database 1056078029\AKAS23474121
    Mailbox Database 1056078029\AKAS23474120

    Why is this difference? Why do I see just the Database name? What happened to all the other properties?

    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.DatabaseCopy

    Name 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.String

    Name 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;
    }

    }
    }


    Following is the output that I get from running the above code:
    Member Name:DatabaseCopies
    Member Type:Deserialized.Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy[]
    ----------------------
    Mailbox Database 1056078029\AKAS23474121
    Mailbox Database 1056078029\AKAS23474120
    ----------------------

    Member Name:ActivationPreference
    Member 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]
    ----------------------

    Notice the word “Deserialized” in the values of the “Member Type” field.
    Enjoy!

  • Akash Blogging......

    Another example of using EWS Managed API 1.1 from PowerShell (Impersonation, SearchFilter, FindItems, Paging)

    • 0 Comments

    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:

    • I would not have to compile the code every time I make change
    • It would also be easier for the Administrator who don’t have access to Development Environment
    • Getting the list of mailboxes is easy in Exchange PowerShell

    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 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 needed
    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

    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


    Enjoy!
  • Akash Blogging......

    Exchange 2010 SP1 Rollup 4 re-released

    • 0 Comments

    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!

  • Akash Blogging......

    Creating folder using EWS Managed API 1.1 from PowerShell

    • 0 Comments

    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:

    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    Create a .PS1 file 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]$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)

    #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.txt
    import-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)
    }


    To Run the script:
    1)Open the Exchange Management Shell
    2)Navigate to the location where you have stored the Data and the script file.
    3)Type in .\ScriptName.PS1 and hit enter to execute the script

    Enjoy!

  • Akash Blogging......

    Use Windows PowerShell in Exchange Online

    • 0 Comments

    A few articles on working with Exchange Online (Office 365 for enterprises, Live@edu) with Windows Powershell.

    Install and Configure Windows PowerShell
    http://technet.microsoft.com/en-us/exchangelabshelp/cc952756

    Connect Windows PowerShell to the Service
    http://technet.microsoft.com/en-us/exchangelabshelp/cc952755

    Reference to Available PowerShell Cmdlets in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/dd575549

    Administrator Role Groups in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/ee441216

    Role Based Access Control in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/dd207274

    Role Assignment Policies in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/ee424427

    Enjoy!

  • Akash Blogging......

    Error: “Object reference not set to an instance of an object” when sending out mails using System.Net.Mail via Exchange 2007

    • 0 Comments

    This was a fun issue to work on! Some background, the customer was using System.Net.Mail to send out mails via Exchange 2007 using port 25 and port 587, all was working fine and then some updates were installed and he was no longer able to send out mails via port 587. He started to get the following exception:

    Exception:
    Object reference not set to an instance of an object.
    System.Net Error: at System.Net.Mail.SmtpConnection.GetConnection(String host, Int32 port)
       at System.Net.Mail.SmtpTransport.GetConnection(String host, Int32 port)
       at System.Net.Mail.SmtpClient.GetConnection()
       at System.Net.Mail.SmtpClient.Send(MailMessage message)

    The code was very simple:

    static void Main(string[] args)
    {
    SmtpClient _client = new SmtpClient();

    using (MailMessage _message = new MailMessage())
    {
    _message.Subject = "Test message";
    _message.Body = "Test message";

    _message.From = new MailAddress("support@xyz.com");
    _message.To.Add("akashb@xyz.com");

    _client.Send(_message);
    }
    }

    The host, port and the credentials were being picked up from the App.Config:

    <network host="EX-01.xyz.COM" port="587" userName="support@xyz.com" password="Password"/>

    What we were also told was that if in the code the SmtpClient.TargetName Property was set to blank then everything worked fine! What is this property? This property gets or sets the Service Provider Name (SPN) to use for authentication when using extended protection.

    We made sure we have the exact same setting on the Exchange 2007 Receive Connectors, updated the client with all the updates that were installed in the customer’s environment but we initially could not reproduce the issue. It did not matter if we set the TargetName to blank or not. We finally did reproduce the issue after I put the client machine where the code was running in the same domain as the Exchange Server and then setting the TargetName to blank did matter. Now the next step was to find out the update that caused the code to fail. We did get a hint from the SmtpClient.TargetName property and started to look for the patch that could cause the issue.

    Luckily, the search did not last long and we were able to identify the patch that caused the failure!

    Description of the rollup update for the .NET Framework 3.5 Service Pack 1 and the .NET Framework 2.0 Service Pack 2 on Windows XP and on Windows Server 2003 (976765 and 980773): June 8, 2010
    http://support.microsoft.com/kb/982167

    This updates the NLC on the system to support Extended Protection. When we uninstalling the update for KB 980773 from the system the code started to work. But now we had a few questions that we needed answer for:

    1)Is there something wrong with Exchange 2007?
    2)Does Exchange 2007 not understand the request for Authentication?
    3)Why does it still work with port 25 and fails only with port 587?

    We then decide to take a verbose log on the Exchange Receive connector and below is what we find:

    >,"220 EX-01.internal.xyz.com Microsoft ESMTP MAIL Service ready at Mon, 15 May 2011 08:59:37 -0500",
    <,EHLO akashb,
    >,250-EX-01.internal.xyz.com Hello [192.168.111.111],
    >,250-SIZE 10485760,
    >,250-PIPELINING,
    >,250-DSN,
    >,250-ENHANCEDSTATUSCODES,
    >,250-STARTTLS,
    >,250-AUTH GSSAPI NTLM LOGIN,
    >,250-8BITMIME,
    >,250-BINARYMIME,
    >,250 CHUNKING,
    <,AUTH gssapi,
    >,334 <authentication response>,
    >,334 <authentication response>,
    *,,Inbound Negotiate failed because of IllegalMessage
    >,535 5.7.3 Authentication unsuccessful,
    -,,Remote

    Looking at the verbose log it looks like Exchange does not understand the message, but why?

    Firstly the OS needs to be configured to support Extended Protection and secondly, support for Extended protection was introduced in Exchange 2007 SP 3. The best would be to have Exchange 2007 SP 3 RU 3 v2 installed. In this case it turns out that neither the customer nor I had Exchange 2007 SP 3 RU 3 v2. Make sure you have your SPN’s right. The below article talks about Extended Protection.

    Extended Protection for Authentication
    http://support.microsoft.com/kb/968389

    After Exchange 2007 SP 3 RU 3 v2 is installed you get a new property “ExtendedProtectionPolicy” for the Receive connector. You can then use the Set-ReceiveConnector  cmd-let and set the value for ExtendedProtectionPolicy to “Allow”. This also requires that RequireTLS is set to true on the connector and you must also set EnableSsl = true in the .Net code. So this answers the questions 1 & 2.

    Now for the question 3, why does it still work with port 25 and fails only with port 587?

    That was easy, the answer lies in EHLO. The Default receive connector (Port 25) did not have GSSAPI enabled and hence it was defaulting to NTLM(which works) instead using Negotiate. In the failing case, Negotiate was used with Extended protection, Exchange did not return the expected data and hence the error.

    -Enjoy

  • Akash Blogging......

    Pull Subscription request fails with error “The Mailbox database is temporarily unavailable” on Exchange 2010

    • 0 Comments

    This is the first time I ran into this issue with Exchange Web Services while sending out a Pull Subscription request! The confusing part was that apart from the subscribe request everything else was working as expected. We could connect to the mailbox using EWS Editor, browse the Inbox and other folders, Delete Items and so on…

    Below is the Request that was sent out:

    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
    xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
    <t:RequestServerVersion Version="Exchange2010" />
    </soap:Header>
    <soap:Body>
    <m:Subscribe>
    <m:PullSubscriptionRequest>
    <t:FolderIds>
    <t:DistinguishedFolderId Id="inbox" />
    </t:FolderIds>
    <t:EventTypes>
    <t:EventType>CopiedEvent</t:EventType>
    <t:EventType>CreatedEvent</t:EventType>
    <t:EventType>DeletedEvent</t:EventType>
    <t:EventType>ModifiedEvent</t:EventType>
    <t:EventType>MovedEvent</t:EventType>
    <t:EventType>NewMailEvent</t:EventType>
    </t:EventTypes>
    <t:Timeout>90</t:Timeout>
    </m:PullSubscriptionRequest>
    </m:Subscribe>
    </soap:Body>
    </soap:Envelope>

    Below is the Response we got:
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
    <h:ServerVersionInfo MajorVersion="14" MinorVersion="1" MajorBuildNumber="289" MinorBuildNumber="6" Version="Exchange2010_SP1"
    xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:SubscribeResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
    <m:ResponseMessages>
    <m:SubscribeResponseMessage ResponseClass="Error">
    <m:MessageText>The mailbox database is temporarily unavailable.</m:MessageText>
    <m:ResponseCode>ErrorMailboxStoreUnavailable</m:ResponseCode>
    <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
    </m:SubscribeResponseMessage>
    </m:ResponseMessages>
    </m:SubscribeResponse>
    </s:Body>
    </s:Envelope>

    Looking at the ETL trace it was found that the underlying error was MAPI_E_NETWORK_ERROR (0x80040115) and we also got to the function that was failing. This helped in searching the Source Code for Exchange 2010, the intent was to find out if there was a way we could reproduce a similar error using the Exchange Management Shell. Luckily we discovered that Move-Mailbox cmd-let would also do something similar!  We discovered that the customer was unable to move mailboxes from one database to another on the same Mailbox Server and was getting the error MapiExceptionNetworkError: Unable to make connection to the server. (hr=0x800004005, ec=2423). Which server are we taking about?

    In this case it turns out that the CAS was not able to contact the Mailbox Server via NetBIOS name. Pinging the Mailbox with the NetBIOS name failed! Pinging the Mailbox with the FQDN worked! What was done to fix the problem?

    In this case a DNS suffix was added (DNS tab in the Advanced TCP/IP Settings) and now pinging the Mailbox with the NetBIOS name WORKED! Did the Move-Mailbox work? YES! Did the Subscribe Request work? YES!

    Enjoy!

  • Akash Blogging......

    Saving during send breaks relation between sent item and Voting responses in Outlook 2010–Updated

    • 0 Comments

    Do you use the voting feature in Outlook 2010? Do you “Save” the item in the Application_ItemSend event? Does everything work as expected?

    Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
    Item.Save
    End Sub

    I don’t think so. If you have not yet noticed, you loose the ability to track the voting responses that you have received. It also breaks the relationship between the Read Receipt(if you have requested one) and the sent mail message.

    We now have a hotfix that addresses this issue and it can now we download from:

    Description of the Outlook 2010 hotfix package (outlook-x-none)
    http://support.microsoft.com/kb/2547227

    However we have a scenario where this is still broken! If the user right clicks on a file and does a Send To – Mail Recipient and then adds the voting options, we loose the ability to track voting responses. We have identified the problem and the fix is in progress and should be out soon.

    Note: If the mail item is not saved OR you do not hook into the Application_ItemSend then don’t worry, everything will work as expected.

    Update:

    What we found out was that the Voting Response tracking was still broken when the user Right clicks on an file on the desktop/Hard drive and select "Send To" -> Mail Recipient and then added the voting options to the message. Fortunately this issue has also been resolved in the July 11th update.

    Description of the Outlook 2010 hotfix package (Outlook-x-none.msp): July 11, 2011
    http://support.microsoft.com/kb/2544027

    Enjoy!

  • Akash Blogging......

    ExpandGroup method returns no results for Dynamic Distribution Group in Exchange 2010

    • 5 Comments

    Strange but TRUE! This work in Exchange 2007 but does not in Exchange 2010. Below is the request and response that I get from a Exchange 2010 server for the ExpandGroup Exchange Web Services call:

    Request:

    <?xml version="1.0" encoding="utf-8"?>
     <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
    xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
       <soap:Header>
         <t:RequestServerVersion Version="Exchange2010" />
       </soap:Header>
       <soap:Body>
         <m:ExpandDL>
           <m:Mailbox>
             <t:EmailAddress>TestDDL@bhargava.com</t:EmailAddress>
           </m:Mailbox>
         </m:ExpandDL>
       </soap:Body>
     </soap:Envelope>

    Response:
    <?xml version="1.0" encoding="utf-8"?>
     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
       <s:Header>
         <h:ServerVersionInfo MajorVersion="14" MinorVersion="1" MajorBuildNumber="269" MinorBuildNumber="0" Version="Exchange2010_SP1" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"
    xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
       </s:Header>
       <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
         <m:ExpandDLResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
           <m:ResponseMessages>
             <m:ExpandDLResponseMessage ResponseClass="Success">
               <m:ResponseCode>NoError</m:ResponseCode>
               <m:DLExpansion TotalItemsInView="0" IncludesLastItemInRange="true" />
             </m:ExpandDLResponseMessage>
           </m:ResponseMessages>
         </m:ExpandDLResponse>
       </s:Body>
     </s:Envelope>

    In this case the TestDDL group had members but we got no results. It turns out that this broke as a result of a change trying to synchronize behavior between various Mail Clients. Fortunately, we have decided to fix this and the fix should be available in one of the upcoming Rollup Updates for Exchange 2010 Service Pack 1.

    The only workaround I know is of using LDAP to get the membership of a Dynamic Distribution Group if it fits your requirements.

    Update:

    This issue has been fixed in Exchange 2010 SP 1 RU 5. Exchange 2010 SP 1 RU 5 should be available in August 2011.Unfortunately this has been pushed to Exchange 2010 SP 1 RU 6. However, in case you need an Interim Update for SP 1 RU 4 OR RU 5 when it becomes available, open up a Service Request with Microsoft Support to obtain one.

    Enjoy!

  • Akash Blogging......

    Office 365 - Getting Started!

    • 0 Comments

    Below are a few articles that will help you get started with developing solution for Office 365

    Exchange Online Overview
    http://msdn.microsoft.com/en-us/Office365TrainingCourse_1V_3

    Developing Messaging Solutions for Exchange Online
    http://msdn.microsoft.com/en-us/office365trainingcourse_session-10_unit

    Exchange Online Technical Articles
    http://msdn.microsoft.com/en-us/library/gg193994(EXCHG.140).aspx

    Office 365 Developer Training Course
    http://msdn.microsoft.com/en-us/Office365TrainingCourse

    Enjoy!

  • Akash Blogging......

    "Internal Server Error" exception while updating Contact item on Exchange 2010 Mailbox using EWS

    • 0 Comments

    This was an interesting issue I ran into so thought of sharing it. One of my customer had written and application to synchronize contacts using Exchange Web Services, on some mailboxes he was getting an “Internal Server Error” while trying to update contacts.

    Error:
    Encountered unhandled exception: Microsoft.Exchange.Services.Core.Types.InternalServerErrorException: An internal server error occurred. The operation failed. ---> System.ArgumentNullException: Value cannot be null.
    Parameter name: address
       at Microsoft.Exchange.Data.SmtpAddress.IsValidSmtpAddress(String address)
       at Microsoft.Exchange.Data.Storage.JunkEmailRule.AddTrustedContact(String email, ADRecipientSession recipSession)
       at Microsoft.Exchange.Data.Storage.Contact.UpdateJunkEmailContacts()
       at Microsoft.Exchange.Data.Storage.Item.SaveInternal(SaveMode saveMode, Boolean commit)
       at Microsoft.Exchange.Services.Core.ServiceCommandBase.<>c__DisplayClass7.<SaveXsoItem>b__6(SaveMode saveModeDelegate)
       at Microsoft.Exchange.Services.Core.ServiceCommandBase.<>c__DisplayClass4.<ExecuteItemSave>b__3()
       at Microsoft.Exchange.Services.Core.ServiceCommandBase.ExecuteStoreObjectSave(SaveStoreObject saveStoreObject, Boolean useItemError)
       at Microsoft.Exchange.Services.Core.ServiceCommandBase.ExecuteItemSave(SaveItem saveItem, ConflictResolutionType conflictResolutionType)
       at Microsoft.Exchange.Services.Core.ServiceCommandBase.SaveXsoItem(Item xsoItem, SaveItem saveItem, ConflictResolutionType conflictResolutionType, PropertyDefinition[] propsToLoad)
    ...

    The exception did mention about  “Value cannot be null”  for the parameter “address” so we made sure that the email address was not blank.We tried to reproduce the issue with the same soap request but we were unable to. Although the format of the email address given in the contact was not correct we had no issue updating the contact in our test server. The strange part was that it was happening on only on a few mailboxes! What could be different?

    The call stack above gave me what I needed! Why are we trying to add a Trusted Contact? What has the Junk Email Rule got to do with it?

    Digging on the exchange 2010 side I came across cmd-let’s called “Get-MailboxJunkEmailConfiguration” and “Set-MailboxJunkEmailConfiguration”. To my joy the “Set-MailboxJunkEmailConfiguration” had a parameter called ContactsTrusted(System.Boolean) and it was sent to False in my environment. I set to true and then ran the test again and guess what?

    I get the same exception the customer was getting. The next step was to alter the email addresses in the SOAP request to valid ones and then, yes you guessed it right! Everything worked fine now!

    So you can basically have a crappy email address for a contact and get away with it if the ContactsTrusted is set to false, but if it’s set to true – You better have you email addresses for your contact right!

    -Enjoy

  • Akash Blogging......

    Office 2010 PIA installer does not install the Word PIA if only Outlook 2010 is installed

    • 0 Comments

    A few days ago I ran into an issue where the customer had written an add-in for Outlook 2010 and was working with the WordEditor property of the Inspector. The add-in was to be deployed to machines which had ONLY Outlook 2010 installed. Would this work?

    Yes, It should! In this case we got an exception that says that the file Microsoft.Office.Interop.Word.dll could not be found. Digging deeper into the issue we found out that it was an issue with the PIA Installer for Office 2010. The Installed did not install the Microsoft.Office.Interop.Word.dll if Word was not installed on the machine.

    Is that what the installer should be doing? NO. The Office 2007 PIA installed worked just fine and even if only Outlook was installed on the machine it installed the PIA for Outlook as well as for Word. Yes, this is an issue with the Office 2010 PIA installer and will be fixed!

    Now what? What are my options to solve the problem?

    The only two that I see are:

    1) Distribute the Microsoft.Office.Interop.Word.dll along with the setup: This is not something that we recommend. It is recommended to always install the PIA using the Official Installer. Now since the installed is not doing it’s job, look at option 2

    2) Use Late Binding: I was able to get the add-in to work without the word PIA. Writing late binding code could be a pain but it is the only other solution that worked!

    In the sample code below we are setting the subject and the body of the message to “This text was set by the add-in”. Notice the code to insert the text “This text was set by the add-in” into the body of the message late binding and WordEditor object.

    if (activeInspector.CurrentItem is Outlook.MailItem)
    {
    Outlook.MailItem mail = ((Outlook.MailItem)activeInspector.CurrentItem);
    mail.Subject = "This text was set by the add-in";

    if (activeInspector.EditorType == Outlook.OlEditorType.olEditorWord)
    {
    object wordDoc = activeInspector.WordEditor;
    object wordWin = null;

    IEnumerator wordDocEnumerator = null;

    if (null != wordDoc)
    {
    object oWindows = wordDoc.GetType().InvokeMember("Windows", System.Reflection.BindingFlags.GetProperty, null, wordDoc, null);

    if (null != oWindows)
    {
    wordDocEnumerator = (IEnumerator)oWindows.GetType().InvokeMember("GetEnumerator", System.Reflection.BindingFlags.InvokeMethod, null, oWindows, null);
    }

    if (wordDocEnumerator.MoveNext())
    {
    wordWin = wordDocEnumerator.Current;

    object oSelection = wordWin.GetType().InvokeMember("Selection", System.Reflection.BindingFlags.GetProperty, null, wordWin, null);
    oSelection.GetType().InvokeMember("TypeText", System.Reflection.BindingFlags.InvokeMethod, null, oSelection, new object[] { "This text was set by the add-in" });
    }
    else
    {
    MessageBox.Show("Could not find the word editor window to insert the text body into");
    }
    }
    }
    else
    {
    MessageBox.Show("The editor type was not word, so the add-in cannot continue");
    }
    }

    Some good articles on binding

    I will let you know when the issue with the Office 2010 PIA installed is fixed. Enjoy!

  • Akash Blogging......

    Updating Recipients using Outlook Object Model

    • 0 Comments

    One of my customers was migrating his Exchange Client Extension code to an Outlook add-in for Outlook 2010 and wanted to modify the recipient using Outlook Object Model(OOM) and ran into issues. What is the problem? In Exchange Client Extension you could hook into events like IExchExtMessageEvents::OnCheckNames and IExchExtMessageEvents::OnChecknameComplete and then from this event you would get an IExchExtCallBack interface. Using the IExchExtCallBack interface you could use the functions GetRecipients and SetRecipients to modify the recipient collection.

    The BIG question is, how can this be done using OOM?

    Unfortunately there is nothing in the Outlook Object Model that allows you to modify a recipient. We do have a workaround of first reading the existing recipients, removing the existing recipients, building a new recipient list with the changes and then adding it back to the message.

    Below is some sample C# code that demonstrated this. In this sample I first delete all the recipients from the message, build a fresh list and then add them back to the message and finally a tweak to get the UI to update.

    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
    this.Application.Inspectors.NewInspector +=
    new Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
    }

    private Outlook::MailItem mailItem = null;

    void Inspectors_NewInspector(Outlook.Inspector Inspector)
    {
    mailItem = Inspector.CurrentItem as Outlook::MailItem;
    if (mailItem != null) {
    mailItem.BeforeCheckNames += new Outlook.ItemEvents_10_BeforeCheckNamesEventHandler(mailItem_BeforeCheckNames);
    }
    }

    void mailItem_BeforeCheckNames(ref bool Cancel)
    {
    // In order to be able to add recipients, set their addressEntries, and have them display, we need to
    // 1) Remove ALL recipients from the message
    // 2) Re-add them, and set their address entries
    // 3) To get the UI to update, add and remove a final recipient

    // Step 1: remove all recipients
    int Count = mailItem.Recipients.Count;
    while (Count > 0) {
    mailItem.Recipients.Remove(1);
    Count = mailItem.Recipients.Count;
    }

    // Step 2: re-add them, and set their address entries
    string[] RecipentAddresses = { "test1", "testdl", "test2" };

    for (int i = 0; i < RecipentAddresses.Length; i++)
    {
    Outlook::Recipient recip = mailItem.Recipients.Add("test2");
    int addrType = recip.Type;

    Outlook::AddressEntry ae = Application.Session.AddressLists["Global Address List"].AddressEntries[RecipentAddresses[i]];
    recip.AddressEntry = ae;

    // need to set recipient type back to old value after re-adding
    recip.Type = addrType;
    recip.Resolve();
    }

    // Step 3: (only needed in some event callbacks)
    // Now addresses have changed, but sometimes the UI doesn't update until
    // we add and remove a final recipient.
    // This workaround may not be needed in the BeforeCheckNames event, but is
    // needed in some other events if updating the recipients

    mailItem.Recipients.Add("pleaseIgnore");
    mailItem.Recipients.Remove(mailItem.Recipients.Count);
    }

    Enjoy!

  • Akash Blogging......

    Problems installing the Office 2010 PIA’s using the Microsoft Office 2010 Primary Interop Assemblies Bootstrapper Package

    • 0 Comments

    Have you tried using the Microsoft Office 2010 Primary Interop Assemblies Bootstrapper Package? Did you not run into any issues? Were you able to install the PIA’s with the default package? I was not so lucky!

    One of my customer had created an add-in for Outlook 2010 and had included the Microsoft Office 2010 Primary Interop Assemblies Bootstrapper Package as part of his prerequisites for the add-in.The machine on which the add-in was being installed on did not have the PIA’s for any of the Office product installed. When the Setup.exe for the add-in was run, it still did not install the PIA’s. Why?

    After banging my head for quite a while I discovered that the componentcheck.exe was causing the issue. It always returned a value of 1. What does that mean? It means that componentcheck.exe was finding a PIA component on the machine! What was it finding when none of the PIA’s were installed?

    Componentcheck.exe checks for the following components and if it finds any one of them it returns 1.

    • Excel
    • InfoPath
    • Outlook
    • PowerPoint
    • Visio
    • Word
    • Project
    • Forms 2.0
    • Graph
    • Smart Tag
    • Office Shared

    In my case Office.dll was always there on the machine and hence componentcheck.exe was returning a value of 1. How do I get my add-in setup to install the PIA’s?

    The easiest solution is not to use the componentcheck.exe and always force the installation of the PIA’s. How can that be done? Easy…just modify the product.xml and remove the nodes for 1) PackageFile that relates to ComponentCheck.exe 2) The entire InstallChecks section as it is not needed any more 3) The BypassIf  install Condition related to PIAInstallAction and then rebuild you setup.

    Below is how the altered product.xml would look after the modification:

    <?xml version="1.0" encoding="utf-8" ?>
    <!--
    ***********************************************************************

    Copyright (C) Microsoft Corporation. All rights reserved.

    THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    PARTICULAR PURPOSE.
    ***********************************************************************
    -->
    <Product xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper" ProductCode="Microsoft.Office.PIARedist.2010">
    <RelatedProducts>
    <EitherProducts>
    <DependsOnProduct Code="Microsoft.Net.Client.3.5" />
    <DependsOnProduct Code=".NETFramework,Version=v4.0,Profile=Client" />
    </EitherProducts>
    </RelatedProducts>
    <PackageFiles>
    <PackageFile Name="o2010pia.msi" HomeSite="Office2010PIARedistMSI"
    PublicKey="3082010A0282010100A2DB0A8DCFC2C1499BCDAA3A34AD23596BDB6CBE2122B794C8EAAEBFC6D526C232118BBCDA5D2CFB36561E152BAE8F0DDD14A36E284C
    7F163F41AC8D40B146880DD98194AD9706D05744765CEAF1FC0EE27F74A333CB74E5EFE361A17E03B745FFD53E12D5B0CA5E0DD07BF2B7130DFC606A288575
    8CB7ADBC85E817B490BEF516B6625DED11DF3AEE215B8BAF8073C345E3958977609BE7AD77C1378D33142F13DB62C9AE1AA94F9867ADD420393071E08D6746
    E2C61CF40D5074412FE805246A216B49B092C4B239C742A56D5C184AAB8FD78E833E780A47D8A4B28423C3E2F27B66B14A74BD26414B9C6114604E30C882F3
    D00B707CEE554D77D2085576810203010001"
    />
    </PackageFiles>
    <Commands Reboot="Defer">
    <Command PackageFile="o2010pia.msi" Arguments="/quiet" EstimatedInstalledBytes="7000000"
    EstimatedInstallSeconds="60">
    <InstallConditions>
    <FailIf Property="AdminUser" Compare="ValueEqualTo" Value="false" String="AdminRequired" />
    </InstallConditions>
    <ExitCodes>
    <ExitCode Value="0" Result="Success" />
    <ExitCode Value="1641" Result="SuccessReboot" />
    <ExitCode Value="3010" Result="SuccessReboot" />
    <DefaultExitCode Result="Fail" FormatMessageFromSystem="true" String="GeneralFailure" />
    </ExitCodes>
    </Command>
    </Commands>
    </Product>

    The not so easy way to do it would be to write your own componentcheck.exe and build your own logic to return 1 or 0. The source for the componentcheck.exe for office 2010 is not available but as a starting point you can use something that was available for Office 2007. The Component IDs of the Redistributable Primary Interop Assemblies for Microsoft Office 2010 can be found here.
     
    Enjoy!

  • Akash Blogging......

    Exchange 2007:Transport error when sending emails using TLS (0x80040213)

    • 0 Comments

    Error: “The transport failed to connect to the server”, Code:80040213, Source:CDO.Message.1. Does that look familiar? This was something that I kept running into while trying to send mail using CDOSYS with SSL/TLS enabled. Vikas already has a post explaining some of the details that you could refer to but there are some more finer points that need to be kept in mind for you to successfully send out mails. Below is what you should check:

    1) Do a Telnet to the SMTP server on port 25 and then issue an ehlo command. This should list out the verbs that are supported by the SMTP server. If you do not see “250-STARTTLS” in the list then there is a problem with the certificate and you should fix that first. What can you do to fix it?
       
    That really depends on the resources that are available to you. You could purchase a certificate from a Trusted Root Certification Authority or generate one on you own. Assuming that we want to generate one, we could use the New-ExchangeCertificate cmdlet to generate one and associate it with SMTP. In my case my machine was called Ex200701 and my domain was “mycomany.com”. The FQDN for my machine was Ex200701.mycompany.com. Below is how I generated the certificate for my use:

    Step 1: New-ExhcangeCertificate –DomainName “Ex200701, Ex200701.mycompany.com” .
    The FQDN specified in the above command should match the one specified in the Receive Connector. This generates a new certificate and shows you the thumbprint for the certificate. Note that thumbprint down.

    Step 2: Enable-ExchangeCertificate – thumbprint “Thumbprint that you noted in the pervious Step 1” –Services SMTP
    This steps associates the certificate with the SMTP service.

    Step 3: Restart the Transport service.

    Step 4: Do a Telnet to the SMTP server on port 25 and then issue an ehlo command. You should now see “250-STARTTLS” in verbs listed.

    2) When you to a Telnet to the SMTP server on port 25 and then issue an ehlo command and you see the “250-STARTTLS” in the list then most likely the certificate is ok but be sure to verify the certificate by using the certificates snap-in and specifically looking to the “Subject Alternative Name” in the certificate details. In case you have more that one certificates installed and you want to know which one is being used by the SMTP, the simplest way to find that out is to enable Verbose logging on the Receive Connector and then looking at the Verbose logs. Below is how the logs would look like:

    >,"220 Ex200701.mycompany.com Microsoft ESMTP MAIL Service ready at Fri, 25 Feb 2011 14:06:20 -0600",
    <,EHLO AKASHB,
    >,250-Ex200701.mycompany.com Hello [10.171.79.38],
    >,250-SIZE,
    >,250-PIPELINING,
    >,250-DSN,
    >,250-ENHANCEDSTATUSCODES,
    >,250-STARTTLS,
    >,250-X-ANONYMOUSTLS,
    >,250-AUTH GSSAPI NTLM,
    >,250-X-EXPS GSSAPI NTLM,
    >,250-8BITMIME,
    >,250-BINARYMIME,
    >,250-CHUNKING,
    >,250-XEXCH50,
    >,250 XRDST,
    <,STARTTLS,
    >,220 2.0.0 SMTP server ready,
    *,,Sending certificate
    *,CN=Ex200701,Certificate subject
    *,CN=Ex200701,Certificate issuer name
    *,76B8F8FF8379FABD49B1A3E486CE0B31,Certificate serial number
    *,480F5316242A7D40E25CF0F0D63827D3A02B5B15,Certificate thumbprint
    *,Ex200701;Ex200701.mycompany.com,Certificate alternate names

    Note down the Certificate thumbprint, open all the existing certificates and find the one that has a matching thumbprint.

    3) Export the certificate to a .cer file. How do you do that? That’s easy – Open the certificate by double clicking it, go to the “Details” tab and click on the “Copy to File” button and keep clicking Next with the default options selected, enter a file name to save the file as and you are done.

    Certificate

    4) Why do I need to export the certificate? The certificate that we generated in Step 1 is a not from a Trusted Root Certification Authority and hence cannot be vaildate. The certificate will now have to be placed in the “Personal”, “Trusted Root Certification Authorities”, “Intermediate Certification Authorities” folders in certificate store on the client machine from where you are trying to send the mail from(just to be absolutely sure it is found). Please note that the pervious step and this step would not be necessary had you purchased the certificate from Trusted Root Certification Authority.

    5) Now while sending the mail, make sure that you specify one of the names specified in the Domain Name parameter in Step 1 as the Server Name in the code and set the smtpusessl configuration to true. Below is the sample VBScript code I used to send mails out:

    Dim iMsg
    Dim iConf
    Dim Flds

    Const cdoSendUsingPort = 2
    Const cdoBasic = 1
    Const cdoNTLM = 2

    set iMsg = CreateObject("CDO.Message")
    set iConf = CreateObject("CDO.Configuration")

    Set Flds = iConf.Fields
    With Flds
    .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = cdoSendUsingPort
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "Ex200701.mycompany.com"
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = 30
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport")=25
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate")=cdoBasic
    .Item("http://schemas.microsoft.com/cdo/configuration/sendusername")="akashb@mycompany.com"
    .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword")="Password123"
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl")=true
    .Update
    End With


    With iMsg
    Set .Configuration = iConf
    .From = "akashb@mycompany.com"
    .To = "akashb@mycompany.com"
    .Subject = "This is a test CDOSYS message"
    .HTMLBody = "Test Message"
    .Send
    End With


    ' Clean up variables.

    Set iMsg = Nothing
    Set iConf = Nothing
    Set Flds = Nothing

    MsgBox "Mail Sent!"

    6) You should be successfully able to send out mails using SSL. If you still cannot send out mails, configure Outlook Express using the server & user details and remember to enable SSL for Outgoing mail.

    SMTP

    If Outlook Express send out a test mail fine then you code should also be able to do it. If not, Outlook Express will give you the exact error you are running into and will help you trouble shoot the problem further. OOPS! I just let out a secret!

    Enjoy!

  • Akash Blogging......

    Are you developing Solutions for Office 2010 solutions with Visual Studio 2010 and Targeting .NET 4.0?

    • 0 Comments

    Deployment of VSTO add-ins have always been tricky. In Office 2007 we could deploy a VSTO Add-in for All users(after the hotfix) but if the assembly is not signed with a Trusted Publisher’s Certificate, each user sees the Microsoft Office Customization Installer dialog box (also know as the trust prompt) asking them if they want to install the add-in the very first time the add-in is loaded. If they choose to install the add-in, the add-in will run and they will not be prompted again. If they choose to not install the add-in, the add-in will not load and they will continue to see this trust prompt every time they open up the Office application and the add-in tries to load.

    We could also avoid the Trust Prompts by creating a Inclusion List entry on a per user basis.The Registry propagation mechanism could be used to replicate the Inclusion List registry key to HKCU hive.

    If you are developing your solution with Visual Studio 2010 and targeting .NET 4, an alternative to signing with a Trusted Publisher certificate and using Inclusion List is to install the add-in into the machines Program Files location. This location needs administrative privilege to write to and will be inherently trusted by VSTO, so there will be no trust prompt. We don’t have to worry about signing with a Trusted Publisher certificate and Inclusion Lists.

    If you ask me? This is the BEST change in the Office Deployment space!

    Enjoy!

  • Akash Blogging......

    Getting an error when trying to install a Transport Agent that is built with Exchange 2010 pre SP 1 binaries on Exchange 2010 SP 1?

    • 0 Comments

    Have you tried install Installing a Transport Agent that is built with Exchange 2010 pre SP 1 binaries on Exchange 2010 SP 1? If not try it and you will get a error:

    The TransportAgentFactory type "YourNamespace.YourAgentFactory" doesn't exist. The TransportAgentFactory type must be the Microsoft .NET class type of the transport agent factory.
    Parameter name: TransportAgentFactory
        + CategoryInfo          : InvalidArgument: (:) [Install-TransportAgent], ArgumentException
        + FullyQualifiedErrorId : 6F5E297B,Microsoft.Exchange.Management.AgentTasks.InstallTransportAgent

    This error is happening because while installing when the agent assembly is loaded, we check for all the Types associated with it and that where it fails as it is looking for the old build of the Microsoft.Exchange.Data.Common.dll and Microsoft.Exchange.Data.Transport.dll which is not there on the system. I tried copying the Microsoft.Exchange.Data.Common.dll and Microsoft.Exchange.Data.Transport.dll to the folder where the agent is installed and then I get a different error:

    There is no known TransportAgentFactory interface implemented by the specified TransportAgentFactory "YourNamespace.YourAgentFactory".
    Parameter name: TransportAgentFactory
        + CategoryInfo          : InvalidArgument: (:) [Install-TransportAgent], ArgumentException
        + FullyQualifiedErrorId : 7804297B,Microsoft.Exchange.Management.AgentTasks.InstallTransportAgent

    The resolution is simple, recompile the Agent after adding references to the Microsoft.Exchange.Data.Common.dll and Microsoft.Exchange.Data.Transport.dll from the SP 1 build(14.1.214.0) and then installing the latest build.

    Surprisingly an Agent compiled with Exchange 2007 binaries installs without an issue! Why?

    This happens because we have a redirection policy in place for Exchange 2007. If you look into the GAC, there are two files named policy.8.0.Microsoft.Exchange.Data.Common and policy.8.0.Microsoft.Exchange.Data.Transport that redirect all old assembly(Exchange 2007) references to the latest build(14.1.214.0) and it just works. Unfortunately, we do not have one for the pre SP 1 and that the reason it fails.

    The only solution as of now is to recompile the Transport Agent with the latest binaries from Exchange 2010 SP 1. The issue is being worked upon and we might have a FIX in the near future!

    Enjoy!

  • Akash Blogging......

    Error:CDO.Message.1 (0x80040220) The "SendUsing" configuration value is invalid on IIS 7.5

    • 6 Comments

    The error CDO.Message.1 (0x80040220) -The "SendUsing" configuration value is invalid is back again on IIS 7.5 and the reasons are also the same! inadequate permissions on the IIS Metabase due to which CDOSYS is unable to read the location of the Pickup Directory from the IIS Metabase.

    What has changed? The default Application Pool Identity in IIS 7.5 changed from NetworkService to AppPoolIdentity. For every Application Pool you create, the IIS Admin Process (WAS) will create a virtual account with the name of the new Application Pool and run the Application Pool's worker processes under this account. It is this account that is not able to read the location of the Pickup Directory from the IIS Metabase. You can read more about AppPoolIdentity here.

    What are the available options to resolve the error? AFAIK there are 3 ways to resolve the error:

    1) This is the easiest one, configure the application pool identity under which the site is running to NetworkService instead of AppPoolIdentity. From what I have seen the NetworkService account has “Special Permissions” on the /LM/SmtpSvc/ and /LM/SmtpSvc/1/ nodes in the IIS Metabase.

    2) In case you do not want to change any permissions and are ok with code changes, then you can specify the smtpserverpickupdirectory and set its value to the path of pickup directory. Below is how we do it on VB Script.

    'Send using the Pickup directory on the IIS server.
    Dim iMsg
    Dim iConf
    Dim Flds
    Dim strHTML

    Const cdoSendUsingPickup = 1

    set iMsg = CreateObject("CDO.Message")
    set iConf = CreateObject("CDO.Configuration")

    Set Flds = iConf.Fields
    With Flds
    .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = cdoSendUsingPickup
    'The path may differ in your environment
    .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverpickupdirectory")="c:\Inetpub\mailroot\pickup"
    .Update
    End With

    ' Build HTML for message body.
    strHTML = "<HTML>"
    strHTML = strHTML & "<HEAD>"
    strHTML = strHTML & "<BODY>"
    strHTML = strHTML & "<b> This is the test HTML message body</b></br>"
    strHTML = strHTML & "</BODY>"
    strHTML = strHTML & "</HTML>"

    With iMsg
    Set .Configuration = iConf
    .To = "test@test.com"
    .From = "test@test.com"
    .Subject = "This is a test CDOSYS message (Sent via Pickup)"
    .HTMLBody = strHTML
    .Send
    End With


    ' Clean up variables.
    Set iMsg = Nothing
    Set iConf = Nothing
    Set Flds = Nothing

    MsgBox "Mail Sent!"

    3) Grant the IIS_IUSRS group read access to the /LM/SmtpSvc/ and /LM/SmtpSvc/1/ nodes in the IIS Metabase. How can you do it? You can download the Internet Information Services (IIS) 6.0 Resource Kit Tools at http://go.microsoft.com/fwlink/?LinkId=67351. This resource kit includes a Metabase Explorer that works with IIS 6.0 or IIS 7.0 with IIS 6 Management Compatibility role services installed. On IIS 7.0, the IIS Admin Service must be running for the Metabase Explorer tool to work correctly.
    Why will this work? It will work because IIS 7 automatically adds the IIS_IUSRS membership to the worker processes token at runtime. By doing this, accounts that have been defined to run as 'application pool identities' no longer need to explicitly be part of the IIS_IUSRS group.

    Enjoy!

  • Akash Blogging......

    HOW TO: Call .PS1 script from Managed code using Remote Powershell(Exchange 2010)

    • 0 Comments

    As promised below is the code to call a .PS1 script and pass parameters to it. Most of the code is very similar to my previous post that showed how to call Exchange & PowerShell cmdlet.

    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;

    namespace CallingScriptPS
    {
    class Program
    {
    static void Main(string[] args)
    {
    string password = "Password";
    string userName = "Domain\\Administrator";
    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("Exception: " + current.Exception.ToString());
    Console.WriteLine("Inner Exception: " + 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)
    powershell = PowerShell.Create();
    command = new PSCommand();
    command.AddScript("Import-PSSession -Session $ra");
    powershell.Commands = command;
    powershell.Runspace = runspace;
    powershell.Invoke();



    // Now run get-ExchangeServer
    System.Collections.ObjectModel.Collection<PSObject> results = new System.Collections.ObjectModel.Collection<PSObject>();

    powershell = PowerShell.Create();
    powershell.Runspace = runspace;



    //Change the Path to the Script to suit your needs
    System.IO.StreamReader sr = new System.IO.StreamReader("..\\..\\Script.ps1");
    powershell.AddScript(sr.ReadToEnd());
    powershell.Runspace.SessionStateProxy.SetVariable("proc", "C*");
    powershell.Runspace.SessionStateProxy.SetVariable("mbx", "*MBX");

    results = powershell.Invoke();

    if (powershell.Streams.Error.Count > 1)
    {
    foreach (ErrorRecord er in powershell.Streams.Error)
    Console.WriteLine(er.ErrorDetails);
    }
    else
    {
    foreach (PSObject ps in results)
    {
    Console.WriteLine(ps.Properties["Name"].Value.ToString());
    }
    }
    }
    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;
    }
    }

    private static SecureString String2SecureString(string password)
    {
    SecureString remotePassword = new SecureString();
    for (int i = 0; i < password.Length; i++)
    remotePassword.AppendChar(password[i]);

    return remotePassword;
    }

    }
    }

    Below is what the Script.PS1 looks like:

    get-process |where {$_.Name -like $proc}
    get-exchangeserver | Where {$_.Name -like $mbx}

    The two parameters that we pass in are called “proc” & “mbx”.

    Note:This code will not work if we do not call the New-PSSession & the Import-PSSession cmdlet’s for reasons explained in my previous post.

    Enjoy!

  • Akash Blogging......

    HOW TO: Migrating Exchange 2007 PowerShell Managed code to work with Exchange 2010

    • 3 Comments

    Will my Exchange 2007 PowerShell Managed code work with Exchange 2010 as is?

    Unfortunately the answer is NO, fortunately there are not many changes that you will have to make. The management experience given by Exchange 2010 through PowerShell has been moved all the way from Local to Remote. Dave Vespa has a detailed post that explains the difference between the Runspace’s.

    Only exchange cmdlets will work in this remoting scenario, you will not be able to run most of the powershell cmdlets. The powershell cmdlets that work in the remoting scenario are Out-Default, Get-FormatData, Select-Object, Measure-Object, Exit-PSSession. If you find more please do let me know...

    Yes, this does mean that you will not be able to run cmdlets like Where-Object and .PS1 scripts in the Remote Runspace. Is that a limitation? I don’t think so. We can very easily get around it by create a new Session and Importing it.

    For Exchange 2007 we had to add the Microsoft.Exchange.Management.PowerShell.Admin, now we will have to use New-PSSession and Import-PSSession and then we can do what ever we want to. Below is a code sample that shows how to use New-PSSession, Import-PSSession and using Where-Object in the now Local Runspace :

    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;

    namespace CallingPSCmdlet
    {
    class Program
    {
    static void Main(string[] args)
    {
    string password = "Password!";
    string userName = "Domain\\Administrator";
    System.Uri uri = new Uri("http://Exchange-Server/powershell?serializationLevel=Full");
    System.Security.SecureString securePassword = String2SecureString(password);

    PSCredential creds = new PSCredential(userName, securePassword);

    Runspace runspace = 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("Exception: " + current.Exception.ToString());
    Console.WriteLine("Inner Exception: " + 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)
    powershell = PowerShell.Create();
    command = new PSCommand();
    command.AddScript("Import-PSSession -Session $ra");
    powershell.Commands = command;
    powershell.Runspace = runspace;
    powershell.Invoke();


    // Now run get-ExchangeServer
    powershell = PowerShell.Create();
    command = new PSCommand();
    command.AddScript("Get-ExchangeServer | where-object{$_.Name -like \"*MBX\"}");
    powershell.Commands = command;
    powershell.Runspace = runspace;

    Collection<PSObject> results = new Collection<PSObject>();
    results = powershell.Invoke();

    foreach (PSObject PSresult in results)
    {
    Console.WriteLine(PSresult.Properties["Name"].Value.ToString());
    }

    }
    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;
    }
    }

    private static SecureString String2SecureString(string password)
    {
    SecureString remotePassword = new SecureString();
    for (int i = 0; i < password.Length; i++)
    remotePassword.AppendChar(password[i]);

    return remotePassword;
    }

    }
    }

    In my next post I will show how to call and pass parameter to a .PS1 script. Enjoy!
Page 1 of 3 (60 items) 123