• 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

    Note:This issue is now addressed in Exchange 2010 SP3 RU2 and it will be addressed for Exchange 2013 in the next cumulative update.

    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!
  • Akash Blogging......

    "Object reference not set to an instance of an object" error when trying to create object of System.Net.Mail.MailMessage

    • 0 Comments

    A few days ago I ran into an interesting problem. A customer who was trying to send a mail using System.Net.Mail was getting the error "Object reference not set to an instance of an object" when trying to create a Instance of the MailMessage class.

    Code that was failing

    MailMessage message = new MailMessage()

    The strange thing was that if I used any other variant of the constructor for MailMessage everything worked.

    Code that worked
    MailMessage message = new MailMessage("a@a.com", "a@a.com", "Test Message", "Test Message")
    MailMessage message1 = new MailMessage("a@a.com", "a@a.com")

    What could be the difference?

    Debugging the System.Net.Mail source using Visual Studio 2008 we found the following difference:
    When the default constructor is used, the below is the code for the MailMessage constructor:
    public MailMessage() {
    message = new Message();
    if(Logging.On)Logging.Associate(Logging.Web, this, message);

    // This call below is what I am talking about.
    string from = SmtpClient.MailConfiguration.Smtp.From;

    if (from != null && from.Length > 0) {
    message.From = new MailAddress(from);
    }
    }

    In the above code there is a call to read the "from" address from the mail configuration. This call is not there in any of the other constructors and therefore the other constructors work.

    What is causing "string from = SmtpClient.MailConfiguration.Smtp.From"  line to fail? The code is trying to read some setting from a configuration file. There is no web.Config or app.config file, then where is it trying to read from?

    The code tries to read from the machine.config and trying to get the mailSettings section. Is the mailSetting section not there?

    The mailSettings section was present but there was a typo in the name which the customer had accidentally made. It was typed as "mailfSettings". Correcting the spelling fixed the problem.

     
    Enjoy!
  • Akash Blogging......

    HOW TO: Read User Configuration for OWA in Exchange 2010 using EWS Managed API 1.0

    • 0 Comments

    The User Configuration operations enable clients to create, delete, retrieve, and update user configuration information. These operations are new in Exchange 2010.

    Configuration data consists of groups of related application settings. Each group of settings is stored together in separate stream properties that are set on FAI messages. The OWA configuration its stored in IPM.Configuration.OWA.UserOptions which is also a FAI message in the NON_IPM_SUBTREE.

    How do you get to the NON_IPM_SUBTREE?  NON_IPM_SUBTREE is the Parent folder of the root. Some of the properties that you can get are timezone, themeStorageId, autoaddsignature, signaturetext, navigationbarwidth, signaturehtml

    Below is a piece of sample code that retrieves the User Configuration for OWA in Exchange 2010.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Exchange.WebServices.Data;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Xml;
    using System.Collections;
    using System.Net;

    namespace ReadUserConfigurationOWA
    {
    class Program
    {
    static void Main(string[] args)
    {
    // Connect to Exchange Web Services
    ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);


    //Change the Credentials to suit you needs
    service.Credentials = new WebCredentials("user", "Password", "domain");


    //Use Autodiscover or Set the URL manually. Change the email address to match yours
    service.AutodiscoverUrl("user@domain.com");

    EnumUserConfig(service);

    Console.ReadLine();
    }


    static void EnumUserConfig(ExchangeService service)
    {
    Folder Root = Folder.Bind(service, WellKnownFolderName.Root);


    UserConfiguration OWAConfiguration = UserConfiguration.Bind(service, "OWA.UserOptions", Root.ParentFolderId, UserConfigurationProperties.All);


    IDictionaryEnumerator OWAEnum = (IDictionaryEnumerator)OWAConfiguration.Dictionary.GetEnumerator();


    while (OWAEnum.MoveNext())
    {
    Console.WriteLine("Key : " + OWAEnum.Key);
    Console.WriteLine("Value : " + OWAEnum.Value);
    }
    }
    }
    }

    Enjoy!
  • Akash Blogging......

    HOW TO: Configure Exchange 2007 and Exchange 2010 for using Impersonation

    • 3 Comments

    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

    ExchangeServiceBinding Class – Code Sample
    http://msdn.microsoft.com/en-us/library/exchangewebservices.exchangeservicebinding.aspx

    Enjoy!

  • Akash Blogging......

    HOW TO: Build a complex search using SearchFilter and SearchFilterCollection in EWS Managed API 1.0

    • 2 Comments
    Here is another sample for building a complex search criteria for Finding items. We use the SearchFilter and SearchFilterCollection to build the “Restriction”. In short we are finding items which have a User Property called X-State AND the value of X-State is not (3 OR 4 OR 5) AND the ItemClass is IPM.Note.Exchange AND the mail is received after a certain date.
     
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Exchange.WebServices.Data;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Xml;

    namespace ListTopLevelFolders
    {
    class Program
    {
    static void Main(string[] args)
    {
    ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);

    //Change the Credentials to suit you needs
    service.Credentials = new WebCredentials("akashb", "Password", "domain");

    //Use Autodiscover or Set the URL manually. Change the email address to match yours
    service.AutodiscoverUrl("user@domain.com");

    SearchItemsInAFolder(service);

    Console.ReadLine();
    }

    static void SearchItemsInAFolder(ExchangeService service)
    {
    // The User Property to Search
    ExtendedPropertyDefinition X_STATE =
    new ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "X-STATE", MapiPropertyType.String);

    // Condition for checking if X_STATE value is 3,4,5
    List<SearchFilter> searchORFilterCollection = new List<SearchFilter>();
    searchORFilterCollection.Add(new SearchFilter.IsEqualTo(X_STATE, 3));
    searchORFilterCollection.Add(new SearchFilter.IsEqualTo(X_STATE, 4));
    searchORFilterCollection.Add(new SearchFilter.IsEqualTo(X_STATE, 5));

    //Negating the above condition. Effectively X_STATE value is NOT 3 OR 4 OR 5
    SearchFilter searchNotFilter =
    new SearchFilter.Not(new SearchFilter.SearchFilterCollection(LogicalOperator.Or, searchORFilterCollection.ToArray()));

    // AND the ItemClass is IPM.Note.Exchange
    // We create a new Search Filter collection and add a new IsEqualTo and then AND it with the
    // previous search filter.
    List<SearchFilter> searchANDFilterCollection = new List<SearchFilter>();
    searchANDFilterCollection.Add(new SearchFilter.IsEqualTo(EmailMessageSchema.ItemClass, "IPM.Note.Exchange"));
    searchANDFilterCollection.Add(searchNotFilter);


    // X_STATE is Not 3 OR 4 OR 5 AND ItemClass is IPM.Note.Exchange
    SearchFilter searchANDFilter =
    new SearchFilter.SearchFilterCollection(LogicalOperator.And, searchANDFilterCollection.ToArray());


    // Condition for checking if X_STATE Exists in that Item
    // AND the User Property Exists on the items.
    // We create a new Search Filter collection and add a new Exists condition and then AND it with the
    // previous search filter.
    List<SearchFilter> searchANDFilterCollection2 = new List<SearchFilter>();
    searchANDFilterCollection2.Add(new SearchFilter.Exists(X_STATE));
    searchANDFilterCollection2.Add(searchANDFilter);

    SearchFilter searchANDFilter2 = new SearchFilter.SearchFilterCollection(LogicalOperator.And, searchANDFilterCollection2.ToArray());


    // Condition for checking if the mail has been recievd before a specific time.
    // AND the mails have been received after a certain time.
    // We create a new Search Filter collection and add a new IsGreaterThan condition and then AND it with the
    // previous search filter.
    List<SearchFilter> searchANDFilterCollection3 = new List<SearchFilter>();
    searchANDFilterCollection3.Add(new SearchFilter.IsGreaterThan(EmailMessageSchema.DateTimeReceived, DateTime.Parse("2010-03-01T18:30:00Z")));
    searchANDFilterCollection3.Add(searchANDFilter2);

    SearchFilter FinalsearchFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And, searchANDFilterCollection3.ToArray());


    // Create a view with a page size of 50.
    ItemView view = new ItemView(50);


    // Indicate that the base property and the User Property will be returned
    view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties, X_STATE);


    // Order the search results by the DateTimeReceived in descending order.
    view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Descending);


    // Set the traversal to shallow. (Shallow is the default option; other options are Associated and SoftDeleted.)
    view.Traversal = ItemTraversal.Shallow;


    // Send the request to search the Inbox and get the results.
    FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, FinalsearchFilter, view);



    // Process each item.
    if (findResults.Items.Count > 0)
    {
    foreach (Item myItem in findResults.Items)
    {
    if (myItem is EmailMessage)
    {
    Console.WriteLine((myItem as EmailMessage).Subject);
    }
    if (myItem.ExtendedProperties.Count > 0)
    {
    // Display the extended property's name and property.
    foreach (ExtendedProperty extendedProperty in myItem.ExtendedProperties)
    {
    Console.WriteLine(" Extended Property Name: " + extendedProperty.PropertyDefinition.Name);
    Console.WriteLine(" Extended Property Value: " + extendedProperty.Value);
    }
    }

    }
    }
    else
    {
    Console.WriteLine("No Items Found!");
    }

    }
    }
    }


    Enjoy!
  • Akash Blogging......

    HOW TO: List Top Level Folders in a Mailbox using EWS Managed API 1.0

    • 0 Comments

    Now that developers have started using Exchange Web Services Managed API, I thought it would be nice for me to post some samples to do simple things using the API. This sample shows:

    1)How to list the Top Level folders in a mailbox.
    2)Using Paging with the FolderView class.
    3)Enabling logging of the Request and Response.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Exchange.WebServices.Data;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Xml;

    namespace ListTopLevelFolders
    {
    class Program
    {
    static void Main(string[] args)
    {
    ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);

    // Enable Tracing
    service.TraceListener = new TraceListener();
    // Optional flags to indicate the requests and responses to trace.
    service.TraceFlags = TraceFlags.EwsRequest | TraceFlags.EwsResponse;
    service.TraceEnabled = true;

    //Change the Credentials to suit you needs
    service.Credentials = new WebCredentials("User", "Password", "domain");

    //Use Autodiscover or Set the URL manually. Change the email address to match yours
    service.AutodiscoverUrl("User@domain.com");

    //Uncomment to list the Top level folders
    ListTopLevelFolders(service,0);

    Console.ReadLine();
    }

    static void ListTopLevelFolders(ExchangeService service,int moffset)
    {
    int mPageSize =5;
    // Create a view with a page size of 5.
    FolderView view = new FolderView(mPageSize,moffset,OffsetBasePoint.Beginning);

    // Identify the properties to return in the results set.
    view.PropertySet = new PropertySet(BasePropertySet.IdOnly);
    view.PropertySet.Add(FolderSchema.DisplayName);
    view.PropertySet.Add(FolderSchema.ChildFolderCount);

    // Unlike FindItem searches, folder searches can be deep traversals.
    view.Traversal = FolderTraversal.Shallow;

    // Send the request to search the mailbox and get the results.
    FindFoldersResults findFolderResults = service.FindFolders(WellKnownFolderName.MsgFolderRoot, view);

    // Process each item.
    foreach (Folder myFolder in findFolderResults.Folders)
    {
    if (myFolder is SearchFolder)
    {
    Console.WriteLine("Search folder: " + (myFolder as SearchFolder).DisplayName);
    }

    else if (myFolder is ContactsFolder)
    {
    Console.WriteLine("Contacts folder: " + (myFolder as ContactsFolder).DisplayName);
    }

    else if (myFolder is TasksFolder)
    {
    Console.WriteLine("Tasks folder: " + (myFolder as TasksFolder).DisplayName);
    }

    else if (myFolder is CalendarFolder)
    {
    Console.WriteLine("Calendar folder: " + (myFolder as CalendarFolder).DisplayName);
    }
    else
    {
    // Handle a generic folder.
    Console.WriteLine("Folder: " + myFolder.DisplayName);
    }
    }

    // Determine whether there are more folders to return.
    if (findFolderResults.MoreAvailable)
    {
    // Make recursive calls with offsets set for the FolderView to get the remaining folders in the result set.
    moffset = moffset + mPageSize;
    ListTopLevelFolders(service, moffset);
    }
    else
    {
    Console.WriteLine("More Available: " + findFolderResults.MoreAvailable);
    }
    }
    }

    class TraceListener : ITraceListener
    {
    #region ITraceListener Members

    public void Trace(string traceType, string traceMessage)
    {
    CreateXMLTextFile(traceType, traceMessage.ToString());
    }

    #endregion

    private void CreateXMLTextFile(string fileName, string traceContent)
    {
    // Create a new XML file for the trace information.
    try
    {
    // If the trace data is valid XML, create an XmlDocument object and save.
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.Load(traceContent);
    xmlDoc.Save(fileName + ".xml");
    }
    catch
    {
    // If the trace data is not valid XML, save it as a text document.
    System.IO.File.WriteAllText(fileName + ".txt", traceContent);
    }
    }
    }
    }

    Enjoy!
  • Akash Blogging......

    HOW TO:Using Exchange Web Services Managed API 1.0 from PowerShell 2.0

    • 0 Comments

    Requirement: Setting the OOF setting for a user using PowerShell Script.

    The best and the easiest option I could think of was to use the Exchange Web Services Managed API 1.0 from with PowerShell 2.0. I finally got it to work!

    # 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.
    Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll"

    # Create a new Exchange Service Object
    $exchService = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)

    # Set the Credentials
    $exchService.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("Administrator","XYZ!","Mycompany")

    #Set the URL for the service
    $exchService.Url= new-object Uri("http://localhost/EWS/Exchange.asmx")

    $IoofReply =new-object Microsoft.Exchange.WebServices.Data.OofReply("I am OOF Internal")
    $EoofReply =new-object Microsoft.Exchange.WebServices.Data.OofReply("I am OOF External")

    # Create a new OofSettings object
    $OSettings = new-object Microsoft.Exchange.WebServices.Data.OofSettings

    # All,Known,None
    $OSettings.ExternalAudience =[Microsoft.Exchange.WebServices.Data.OofExternalAudience]::All

    # Disabled,Enabled,Scheduled
    $OSettings.State =[Microsoft.Exchange.WebServices.Data.OofState]::Enabled

    #Set The External and the Internal message
    $OSettings.ExternalReply =$EoofReply
    $OSettings.InternalReply =$IoofReply

    # The Duration parameter only need to be set if State is Scheduled
    $startTime = [DateTime]::Today
    $timeWindow = new-object Microsoft.Exchange.WebServices.Data.TimeWindow($startTime.AddDays(1), $startTime.AddDays(10))
    $OSettings.Duration =$timeWindow

    #call the actual method with the SMTP address of the mailbox for which OOF is to be set
    $exchService.SetUserOofSettings("akashb@mycompany.com",$OSettings)

    Write-Host "Done!"


    Enjoy!

  • Akash Blogging......

    He’s got the Looks!

    • 0 Comments

    Vikas is SUPER excited about the way something looks! Guess what?

    Naaaa… Its his BLOG. CHECK it OUT!

  • Akash Blogging......

    Office 2007 now supports loading VSTO add-in’s from HKEY_LOCAL_MACHINE

    • 0 Comments

    Tired of making tweaks to the install package(.msi) to support “All Users” deployment in Office 2007? The GOOD news is that Microsoft has released a hotfix that enables Office 2007 to load VSTO Add-in’s from HKEY_LOCAL_MACHINE also.

    Below is the KB article that has all the details

    A 2007 Office system application does not load an add-in that is developed by using VSTO
    http://support.microsoft.com/kb/976811

    Enjoy!

  • Akash Blogging......

    Did you ever have to re-sign the Application and Deployment manifest for a VSTO 3.0 ClickOnce add-in?

    • 0 Comments

    My customer had written a VSTO 3.0 add-in but could not publish(ClickOnce) it directly as he did not have access to the production environment. Further more there was a need to change a setting in the config file. He create a Web Setup project to deploy all the file and also altered the config file using a custom action in the setup project.

    Changing the config file invalidated the hash and now the application and deployment manifest for the add-in had to be resigned. In case you have never done it before, below are links to a very good blog post and a Channel9 video on how to re-sign the VSTO 3.0 manifests using MAGE.

    Re-signing ClickOnce Application and Deployment Manifests with MAGE
    http://channel9.msdn.com/posts/funkyonex/Resigning-ClickOnce-Application-and-Deployment-Manifests-with-MAGE/

    Signing and re-signing manifests in ClickOnce
    http://blogs.msdn.com/vsto/archive/2009/04/29/signing-and-re-signing-manifests-in-clickonce.aspx

    Enjoy!

  • Akash Blogging......

    The getVisible callback has no effect on Tabs with idMso when startFromScratch=true. Bug?

    • 0 Comments

    This is another issue that I bumped into when startFromScratch is set to true. What I wanted to do was to show/hide tabs based on Message Class. If my Message Class was IPM.Note.Custom then I wanted to just show the “My Tab” otherwise I wanted to show the “TabReadMessage”.

    Why did I set the startFromScratch to true? I had to customize the Quick Access Toolbar(QAT) also. Below is what my Ribbon XML looks like:

    <?xml version="1.0" encoding="utf-8" ?>
    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
    <ribbon startFromScratch="true">
    <tabs>
    <tab id="MyTab" label="My Tab" getVisible="MyTab_OnGetVisible">
    </tab>
    <tab idMso="TabReadMessage" getVisible="TabReadMessage_OnGetVisible">
    </tab>
    </tabs>
    </ribbon>
    </customUI>

    When I opened a message with the IPM.Note.Custom message class, my callbacks(MyTab_OnGetVisible,TabReadMessage_OnGetVisible) fire and I return True from the MyTab_OnGetVisible callback and False from the TabReadMessage_OnGetVisible callback. Things are good this point.
     
    Now I open a message with the IPM.Note(regular mail message) message class, my callbacks fire and I return False from the MyTab_OnGetVisible callback and True from the TabReadMessage_OnGetVisible callback and expect to the “My Tab” hidden and the “TabReadMessage” visible.. Right?
     
    To my surprise, the callbacks fires just fine but the “True” that I return from the TabReadMessage_OnGetVisible callback has no effect on the visibility of the tab and I am left with no tabs on the screen. Bug?

    From what I know this bug has been fixed in Office 14 and a fix for Office 12 has been requested for.

  • Akash Blogging......

    Customizing QAT for one Item Type disables the ability to customize QAT for other Item Types. Bug?

    • 0 Comments

    A few days ago I ran into an issue while trying to customize the Quick Access Toolbar(QAT) for a Item Type in Outlook. I was able to customize the QAT but then I realized that I had lost the ability to customize the QAT for other Item Types(Contacts, Appointments etc.).

    I customized the QAT for the RibbonID “Microsoft.Outlook.Mail.Read”, below is the Ribbon XML that I used:

    <?xml version="1.0" encoding="utf-8" ?>
    <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="Ribbon_OnLoad">
    <ribbon startFromScratch="true">
    <qat>
    </qat>
    </ribbon>
    </customUI>

    Below is a screenshot of a mail when I open it in Outlook. Yeh, I know its not useful to have no QAT and no buttons in the ribbon..that’s because of the startFromScratch=”true”. If startFromScratch is not set to “true” you will not be able to customize the QAT.

    Without QAT 

    Now lets take a look at the actual problem. I opened up a contact Item and wanted to customize the QAT from the UI but the option to customize is disabled. Below is what it looks like:

    image

    I had just customized the Ribbon for the RibbonID “Microsoft.Outlook.Mail.Read”, why should that have any effect on the QAT for a Contact?  Bug?

  • Akash Blogging......

    System.Net.Mail unable to Authenticate against some third party SMTP Servers

    • 2 Comments

    I recently ran into an issue where one of my customer was unable to send mail using System.Net.Mail(.Net 2.0), we always got the "Authentication failed" error.

    We tried sending mail using Microsoft CDO for Windows 2000 Library(Cdosys) and System.Web.Mail and the mails went just fine. if Cdosys works System.Web.Mail(SWM) would normally work because SWM is essentially a wrapper over Cdosys. How can you send mails using Cdosys? Click here

    Why is one API able to send mail and the other fails? We enabled Network Tracing in our .net code and below is what the logs show:

    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : 220 mail.XYZYZYZ
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : XYZXYZZ.com ESMT
    System.Net.Sockets Verbose: 0 : [7240] 00000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 : P Gday mate..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Receive() -> 45#45
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Send()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Send
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 45 48 4C 4F 20 41 4B 41-53 48 42 0D 0A : EHLO AKASHB..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Send() -> 13#13
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Receive()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Receive
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : 250-mail.XYZYZYZ
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : XYZXYZZ.com..250
    System.Net.Sockets Verbose: 0 : [7240] 00000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : -PIPELINING..250
    System.Net.Sockets Verbose: 0 : [7240] 00000030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : -SIZE 20971520..
    System.Net.Sockets Verbose: 0 : [7240] 00000040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : 250-VRFY..250-ET
    System.Net.Sockets Verbose: 0 : [7240] 00000050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : RN..250-AUTH PLA
    System.Net.Sockets Verbose: 0 : [7240] 00000060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : IN LOGIN..250-EN
    System.Net.Sockets Verbose: 0 : [7240] 00000070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : HANCEDSTATUSCODE
    System.Net.Sockets Verbose: 0 : [7240] 00000080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : S..250-8BITMIME.
    System.Net.Sockets Verbose: 0 : [7240] 00000090 : 00 00 00 00 00 00 00 00-00 00 : .250 DSN..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Receive() -> 154#154
    System.Net Verbose: 0 : [7240] SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net Verbose: 0 : [7240] Exiting SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Send()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Send
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : AUTH login Y2222
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : Wwwwwwwwwwwwwwww
    System.Net.Sockets Verbose: 0 : [7240] 00000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 : mmmmmmmmmmm..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Send() -> 45#45
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Receive()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Receive
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : 334 VXNlcm5hbWU6
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 0D 0A : ..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Receive() -> 18#18
    System.Net Verbose: 0 : [7240] SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net Verbose: 0 : [7240] Exiting SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Send()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Send
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 : xxxxxxxxxxxxxxxx=
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 0D 0A : ..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Send() -> 18#18
    System.Net.Sockets Verbose: 0 : [7240] Socket#32176063::Receive()
    System.Net.Sockets Verbose: 0 : [7240] Data from Socket#32176063::Receive
    System.Net.Sockets Verbose: 0 : [7240] 00000000 : 33 33 34 20 55 47 46 7A-63 33 64 76 63 6D 51 36 : 334 UGFzc3dvcmQ6
    System.Net.Sockets Verbose: 0 : [7240] 00000010 : 0D 0A : ..
    System.Net.Sockets Verbose: 0 : [7240] Exiting Socket#32176063::Receive() -> 18#18
    System.Net Verbose: 0 : [7240] SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net Verbose: 0 : [7240] Exiting SmtpLoginAuthenticationModule#61150033::Authenticate()
    System.Net Error: 0 : [7240] Exception in the SmtpClient#3888474::Send - Authentication failed.

    Note:Data in the logs has been altered to hide confidential information

    To request LOGIN authentication, the client issues the AUTH command with the parameter LOGIN and the user name to be used for authentication, base64-encoded as specified in [RFC4648]. For example, if the client's user name was "Charlie", then the client would initiate AUTH LOGIN as follows (AUTH_LOGIN_COMMAND_USER):

    AUTH LOGIN Q2hhcmxpZQ==<CR><LF>

    If AUTH LOGIN is not supported, then the server should responds with a 504 error Message as specified in [RFC4954] section 4. If AUTH LOGIN is supported on the server, then the server responds with the AUTH_LOGIN_Password_Challenge:

    334 UGFzc3dvcmQ6<CR><LF>

    The client then responds with the password to be used for authentication, base64-encoded as specified in [RFC4648]. For example, if the client's password was "password", then client would respond with the following Login_Password_Response:

    cGFzc3dvcmQ=<CR><LF>

    If the authentication is successful, then the server issues a LOGIN_Succeeded_Response or a LOGIN_Failed_Response, corresponding to a 235 reply for success or a 535 reply for a failure [RFC4954].

    In this case, even though the user name is passed with the AUTH LOGIN the server responds with the AUTH_LOGIN_Username_Challenge again:

    334 VXNlcm5hbWU6<CR><LF>

    Now since the client has already sent the user name, it instead of sending the user name, sends the password. The server then responds with the AUTH_LOGIN_Password_Challenge:

    334 UGFzc3dvcmQ6<CR><LF>

    The client then sends nothing and the Authentication fails! We can clearly see that the SMTP server is not respecting the user name sent with the AUTH LOGIN command. It is optional for the client to send the user name([initial-response]) with the AUTH LOGIN command, if sent the SMTP server should respect it. More details about the SMTP Service Extension for Authentication can be found in the [RFC4954].

    Why did Cdosys and SWM work? Both the API’s do not send the user name along with the AUTH LOGIN and therefore everything works fine.

    Is there a way we can change this behaviour of System.Net.Mail i.e. not send the user name along with the AUTH LOGIN? No.

    What are the alternatives?

    1)Use CDOSYS (Does not send the User Name with the AUTH Login)
    2)Use System.Web.Mail (Does not send the User Name with the AUTH Login)
    3)Contact the SMTP Server owner and have them fix the server.


    Enjoy!

  • Akash Blogging......

    Increasing the number of mails that can be processed(Asynchronously) by a SMTP Agent simultaneously

    • 0 Comments

    In continuation to my previous post, the customer also had a SMTP Agent that was installed on a Edge Server and did context sensitive email analysis for incoming SMTP messages. In this case only 20 email could be processed by the SMTP Agent.

    How were the SMTP messages being sent? The customer had a multithreaded application to send out mail. Fortunately, I was able to reproduce this too.In my case when I sent more than 20 mails using a multithreaded application, I ran into the following exception:

    System.Runtime.InteropServices.COMException (0x80040211): The message could not be sent to the SMTP server. The transport error code was 0x800ccc67. The server
    response was 421 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing transmission channel   at CDO.MessageClass.Send()
       at SendSMTPMails.MassMailer.SendMail() in C:\SendSMTPMails\SendSMTPMails\Program.cs:line 63 Exception caught.

    This is a restrictions on the Receive Connector in Exchange 2007. You will need to configure the receive connector to allow MaxInboundConnectionPerSource to a desired number. The default value is 20.

    You can set the value by running the Set-ReceiveConnector cmdlet.

    Cmdlet:Set-ReceiveConnector "Receive connector Name" -MaxInboundConnectionPerSource 100

    Enjoy!

  • Akash Blogging......

    Increasing the number of mails that can be processed(Asynchronously) by a Routing Agent simultaneously

    • 0 Comments

    I recently ran in an issue where one of my customer was developing a context sensitive email analysis software for MS Exchange 2007. The results of the analysis was used to decide how that email would be dealt with.

    Due to the complexity of the analysis, the estimated processing time per email was about 15 to 30 seconds. To realize an acceptable throughput of emails per minute, they needed to ensure that the email processing was strongly parallelized. Therefore they encapsulated the analysis itself into a separate web service which was being called from a Routing Agent.

    Using the asynchronous programming pattern(http://msdn.microsoft.com/en-us/library/cc720860.aspx), recommended by Microsoft for long I/O waiting times,they encountered a limitation in parallel processing. They noticed that at one time they could only process 6 email at a time. The processing of the seventh email would begin only when one of the mails submitted earlier was done.

    In case of the Hub role this was double throughput compared to the synchronous programming pattern but still nowhere near the required rate of parallel processing. At the existing rate they would only be able to process a maximum of 24 emails per minute. The requirement was to process a minimum of 100 emails per minute.

    Fortunately I was able to reproduce the issue in my lab. Below is the code I used to reproduce the behaviour:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Threading;
    using System.Xml;
    using System.Reflection;
    using Microsoft.Exchange.Data.Mime;
    using Microsoft.Exchange.Data.Transport;
    using Microsoft.Exchange.Data.Transport.Email;
    using Microsoft.Exchange.Data.Transport.Routing;

    namespace AsyncRoutingAgent
    {
    /// <summary>
    /// Agent factory
    /// </summary>
    public class MyRoutingAgentFactory :RoutingAgentFactory
    {
    /// <summary>
    /// Create a new RoutingAgent.
    /// </summary>
    /// <param name="server">Exchange Hub server</param>
    /// <returns>a new agent</returns>
    public override RoutingAgent CreateAgent(SmtpServer server)
    {
    return new MyRoutingAgent();
    }
    }

    public class MyRoutingAgent : RoutingAgent
    {
    private object fileLock = new object();

    public MyRoutingAgent()
    {
    base.OnSubmittedMessage += new SubmittedMessageEventHandler(MyRoutingAgent_OnSubmittedMessage);
    }

    void MyRoutingAgent_OnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs e)
    {
    WriteLog("Entering OnSubmittedMessage");

    // Create an instance of the Web Service via the proxy class

    WriteLog("Creating Instance of Web Service");
    AsyncRoutingAgent.localhost.StockService objWebService = new AsyncRoutingAgent.localhost.StockService();

    // Get the async context so that the server knows that this event should be handled asynchronously.
    // Also save the event source and event args so that they can be accessed from the callback.
    WriteLog("Calling GetAgentAsyncContext");

    AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs> asyncState =
    new AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs>(
    source,
    e,
    this.GetAgentAsyncContext());

    WriteLog("Hooking the Event Handler");

    objWebService.GetStockQuoteCompleted += new AsyncRoutingAgent.localhost.GetStockQuoteCompletedEventHandler(objWebService_GetStockQuoteCompleted);

    WriteLog("Calling the Service");

    objWebService.GetStockQuoteAsync("MSFT", 15, asyncState);
    WriteLog("Just before Returning");
    return;
    }

    void objWebService_GetStockQuoteCompleted(object sender, AsyncRoutingAgent.localhost.GetStockQuoteCompletedEventArgs e)
    {
    AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs> asyncState =
    (AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs>)e.UserState;

    WriteLog(e.Result);
    // Handle the results of the asynchronous I/O here.
    // The event source and event args are available from the asyncState.
    // Tell the server that this event is complete.
    asyncState.Complete();

    WriteLog("Call Completed");
    }

    public void WriteLog(String Comment)
    {
    lock (fileLock)
    {
    try
    {
    string logDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\Log";
    string logFile = logDir + @"\log.txt";

    if (!Directory.Exists(logDir))
    {
    Directory.CreateDirectory(logDir);
    }

    if (!File.Exists(logFile))
    {
    File.CreateText(logFile).Close();
    }

    using (StreamWriter logWriter = File.AppendText(logFile))
    {
    logWriter.Write(DateTime.Now.ToString()+ ":" + Comment+ Environment.NewLine);
    logWriter.Flush();
    }
    }
    catch (System.IO.IOException ex)
    {
    Debug.WriteLine(ex.ToString());
    }
    }

    }

    /// <summary>
    /// This class wraps all the state that has to be saved
    /// for an asynchronous event.
    /// </summary>
    private class AsyncState<SourceType, ArgsType>
    {
    private SourceType source;
    private ArgsType args;
    private AgentAsyncContext asyncContext;

    public AsyncState(
    SourceType source,
    ArgsType args,
    AgentAsyncContext asyncContext)
    {
    this.source = source;
    this.args = args;
    this.asyncContext = asyncContext;
    }

    public SourceType Source
    {
    get { return this.source; }
    }

    public ArgsType Args
    {
    get { return this.args; }
    }

    public void Complete()
    {
    this.asyncContext.Complete();

    this.source = default(SourceType);
    this.args = default(ArgsType);
    this.asyncContext = null;
    }
    }
    }
    }

    The above code generates a log which clearly shows the problem.

    09/04/2009 1:41:25 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:26 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:27 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:28 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:28 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:30 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:40 AM:Call Completed
    09/04/2009 1:41:40 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:42 AM:Call Completed
    09/04/2009 1:41:42 AM:Entering OnSubmittedMessage
    09/04/2009 1:41:42 AM:Call Completed 

    In the logs above you can see that "Entering OnSubmittedMessage" is fired 6 times between 1:41:25 Am - 1:41:30 and the next call to "Entering OnSubmittedMessage" is made at 1:41:40 AM only after one of the call from the Web Service returned.The web service has a delay of 15 seconds to simulated a time consuming service.

    Now to the solution. The Exchange Transport creates multiple instances of the agent if it has multiple messages to process, and the number of instances is limited by the following settings:

    MaxExecutingJobs: Range 1 to 50
    MaxJobThreads: Range 1-25 (Default value 3)

    These setting have to be defined in the edgetransport.exe.config under the appSettings section. In case when the agent is run asynchronously we will only need to alter the value of MaxExecutingJobs and increase its value.

    Add the following lines to the edgetransport.exe.config under the appSettings section. You will need to restart the Microsoft Transport and the EdgeTransport.exe
        <add key="MaxExecutingJobs" value="50"/>
        <add key="MaxJobThreads" value="3"/>

    In case you need more that 50 jobs to be executed at the same time, you will need to run the code on a machine that has more than 1 processors, since the actual MaxExecutingJobs value is calculated by multiplying it with the number of processors your machine has.

    Enjoy!

    Note:In a Routing Agent we should not be doing stuff that takes a long time to execute. We did advise the customer to take a alternative approach to the entire solution.

Page 2 of 3 (71 items) 123