Welcome to MSDN Blogs Sign in | Join | Help
System.Net.Mail unable to Authenticate against some third party SMTP Servers

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!

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

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!

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

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.

Changing the position of the “ReplyAll” button in Outlook 2007 Ribbon

Do you want to make “ReplyAll” the last button in the Ribbon while reading mail(Inspector) in Outlook 2007?

Yes, it can be done. All you have to do is make a simple COM Add-in that implements IRibbonExtensibility or a VSTO Add-in and provide it with the appropriate Ribbon XML.

You cannot hide/remove the buttons in the built in groups using the “visible” attribute but you can definitely hide the entire group. This is exactly what i did!

The group where the “ReplyAll” button is placed is named “GroupRespond”. I altered its visibility and then created two new groups. The first group had just the “Reply” and “Forward” buttons and was placed before the “GroupActions”, the second group had the “ReplyAll” button and by default got added to the end of the Ribbon.

Below is the Ribbon XML that I used:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="Ribbon_OnLoad">
<ribbon>
    <tabs>
      <tab idMso="TabReadMessage">
        <group idMso="GroupRespond" visible="false">
        </group>
        <group id="Respond" label="Respond" insertBeforeMso="GroupActions">
            <button  idMso="Reply" size="large" />
            <button  idMso="Forward" size="large" />
        </group>
        <group id="ReplyAllOnly" label="Respond">
            <button idMso="ReplyAll" size="large" />
         </group>
      </tab>
    </tabs>
  </ribbon>
</customUI>

Hope this helps!

Running WebDAV code against Windows 2008(IIS7) and getting HTTP Error 404.11?

I ran into this same issue and then discovered the following KB:

Error message when you visit a Web site that is hosted on IIS 7.0: "HTTP Error 404.11 – URL_DOUBLE_ESCAPED"
http://support.microsoft.com/kb/942076

Setting the allowDoubleEscaping="true" did resolve my problem but this approach slightly compromises the server security by leaving it open to “Double Encoding” attacks.

The cause of the problem was the “+” sign in the name of some of the .eml files. In fact any file that has a “+” in the file name + is hosted on IIS7 + does not have the allowDoubleEscaping set to true will give you the HTTP Error 404.11.

IIS7 rejecting URLs containing +
http://blogs.iis.net/thomad/archive/2007/12/17/iis7-rejecting-urls-containing.aspx

HOW TO:Rewrite the To address in Transport Agents on a Hub Server

Have you ever tried sending a mail to someone and it end up with someone else! Beware there could be a Transport Agent that's doing this:-).

I ran into an issue where I needed to rewrite the address the mail was being sent to. Not going into too much as to why somebody would want to do this,  I wrote a Transport Agent that would do the needful.

The below sample C# code written in Visual Studio 2005 changes the To address of the mail that is being sent out. There is no conditional logic as of now and the code assumes that there is only one recipient.

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

namespace Samples.Agents.MyRoutingAgent
{
    public class MyRoutingAgentFactory :RoutingAgentFactory
    {
        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)
        {
            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(Environment.NewLine + "-------------------------------------------------------------------------------" + Environment.NewLine);

                        //Alter the P1 headers.The P1 address is used for routing.
                        for(int intCounter=e.MailItem.Recipients.Count-1;intCounter>=0; intCounter--)
                        {
                            logWriter.WriteLine("Original Recipient:" + e.MailItem.Recipients[intCounter].OriginalRecipient);

                            if (e.MailItem.Recipients[intCounter].Address.IsValid)
                            {
                                e.MailItem.Recipients.Remove(e.MailItem.Recipients[intCounter]);

                                if (e.MailItem.Recipients.CanAdd)
                                {
                                    e.MailItem.Recipients.Add(new RoutingAddress("administrator@mycompany.com"));
                                }
                            }
                        }

                        //Alter the P2 headers so that the mail displays the correct recipient display Name
                        EmailRecipientCollection erToRecipientCollection;
                        erToRecipientCollection = e.MailItem.Message.To;

                        logWriter.Write(Environment.NewLine + "-------------------------------------------------------------------------------" + Environment.NewLine);
                        foreach (EmailRecipient rec in erToRecipientCollection)
                        {
                            logWriter.WriteLine("Original Display Name:" + rec.DisplayName);
                            logWriter.WriteLine("Original SMTP address:" + rec.SmtpAddress);

                            rec.DisplayName = "Administrator";
                            rec.SmtpAddress = "administrator@mycompany.com";

                            logWriter.WriteLine("Changed to <Administrator>administrator@mycompany.com");

                            }
                        }
                        logWriter.Write(Environment.NewLine + "-------------------------------------------------------------------------------" + Environment.NewLine);

                        logWriter.Flush();
                    }
                }
                catch (System.IO.IOException ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
            }
            return;
        }

    }
}

It is IMPORTANT that you change both the P1 and the P2 headers for this to work correctly. To build the Agent successfully you will need to add references to Microsoft.Exchange.Data.Transport and the Microsoft.Exchange.Data.Common namespaces. These dll's can be found in the Program Files\Microsoft\Exchange Server\Public folder.

To Install/Uninstall the Agent on the Hub Server I have created script files that do the job for me(makes life easier).

Install.PS1

#Copy the agent Dll to the C:\MyAgent folder.
$EXDIR="C:\MyAgent"
Net Stop MSExchangeTransport

Write-Output "Registering agent"
Install-TransportAgent -Name "My Routing Agent Sample" -AssemblyPath $EXDIR\RoutingAgent.dll -TransportAgentFactory Samples.Agents.MyRoutingAgent.MyRoutingAgentFactory

Write-Output "Enabling agent"
Enable-TransportAgent -Identity "My Routing Agent Sample"
Get-TransportAgent -Identity "My Routing Agent Sample"

Net Start MSExchangeTransport

Write-Output "Install Complete. Please exit the Exchange Management Shell."

UnInstall.PS1
$EXDIR="C:\MyAgent"
Net Stop MSExchangeTransport

Write-Output "Disabling Agent..."
Disable-TransportAgent -Identity "My Routing Agent Sample" -Confirm:$false

Write-Output "Uninstalling Agent.."
Uninstall-TransportAgent -Identity "My Routing Agent Sample" -Confirm:$false

Net Start MsExchangeTransport

Write-Output "Uninstall Complete."

Need more information on Transport Agents?

Transport Agents
http://msdn.microsoft.com/en-us/library/aa579185.aspx

Enjoy!

HOWTO: Using PowerShell in ASP.NET (.NET Framework 2.0)

The easiest solution is to create a COM component and register that component with component services (COM+) running the component under a specific user identity. Why do I say this? Read on...

In my case the Web site/Application was configured to run under the DefaultAppPool (Identity = Network Service) and you I wanted to use PowerShell to Enable a Mailbox on Exchange 2007. The code is running on the Exchange 2007 Server itself. What are the available options?

1) Impersonate the user who has the required permissions to Enable a Mailbox in code.
2) Create a new Application Pool, configure it's Identity to a user who has the required permissions to Enable a Mailbox.
3) Create a COM component and register that component with component services(COM+) running the component under a user who has the required permissions to Enable a Mailbox.

Lets discuss each of these options:

1) Impersonate the user who has the required permissions to Enable a Mailbox in code

This is how the ASP.NET code looks like. Thanks to Dan for the code.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;

public partial class _Default : System.Web.UI.Page
{
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    // Holds the new impersonation token.
    private IntPtr _userToken = IntPtr.Zero; 
    private WindowsImpersonationContext _impersonationContext = null;
    
   private void EnableMailbox(string domain, string domainController)
    {

        RunspaceConfiguration config = RunspaceConfiguration.Create();
        PSSnapInException warning;
       
        // Load Exchange PowerShell snap-in.
        config.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out warning);
        if (warning != null) throw warning;

        // This is where be begin the Impersonation
        BeginImpersonation(domain);
       
        using (Runspace thisRunspace = RunspaceFactory.CreateRunspace(config))
        {
            try
            {
                thisRunspace.Open();
                using (Pipeline thisPipeline = thisRunspace.CreatePipeline())
                {
                    //Please change parameter values.
                    thisPipeline.Commands.Add("Enable-Mailbox");
                    thisPipeline.Commands[0].Parameters.Add("Identity", @"DOM157711\xxxxxxx");
                    thisPipeline.Commands[0].Parameters.Add("Database", @"XXXXXXX-8\First Storage Group\Mailbox Database");
                    // Need the line below when impersonating. KB943937. Need rollup 1 for sp1.
                    thisPipeline.Commands[0].Parameters.Add("DomainController", domainController); 

                    try
                    {
                        thisPipeline.Invoke();
                    }
                    catch (Exception exx)
                    {
                        Response.Write("Error: " + exx.ToString());
                    }

                    // Check for errors in the pipeline and throw an exception if necessary.
                    if (thisPipeline.Error != null && thisPipeline.Error.Count > 0)
                    {
                        StringBuilder pipelineError = new StringBuilder();
                        pipelineError.AppendFormat("Error calling Enable-Mailbox.");
                        foreach (object item in thisPipeline.Error.ReadToEnd())
                        {
                            pipelineError.AppendFormat("{0}\n", item.ToString());
                        }

                        throw new Exception(pipelineError.ToString());
                    }
                }
            }

            finally
            {
                thisRunspace.Close();
                EndImpersonation();
            }
        }

    }

    private void BeginImpersonation(string domain)
    {
        //Please change the User Name and Password
        string UserName = "UserName";
        string Password = "Password";

        EndImpersonation();

        Response.Write("User Before Impersonation: " + WindowsIdentity.GetCurrent().Name + "</BR>");
                
        bool success = LogonUser(
                UserName,
                domain,
                Password,
                2,
                0,
                ref _userToken);

        // Did it work?
        if (!success) throw new Exception(string.Format("LogonUser returned error {0}", System.Runtime.InteropServices.Marshal.GetLastWin32Error()));

        WindowsIdentity fakeId = new WindowsIdentity(_userToken);
        _impersonationContext = fakeId.Impersonate();
        Response.Write("User After Impersonation: " + WindowsIdentity.GetCurrent().Name + "</BR>");

    }
    
    private void EndImpersonation()
    {
        if (_impersonationContext != null)
        {
            try
            {
                _impersonationContext.Undo();
            }
            finally
            {
                _impersonationContext.Dispose();
                _impersonationContext = null;
            }
        }
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        // Use the current domain and domain controller. The domain controller parameter is needed for Impersonation to work
        // Please change ualues to suit you needs.
        string domain = "DOM157711";
        string domainController = "XXXXX-8";
        try
        {
            EnableMailbox(domain, domainController);
        }
        catch (Exception ex)
        {
            Response.Write(ex.ToString());
        }
        finally
        {
            Response.Write("Done..");
        }

    }
}

This is how my Web.Config looks like:

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
        <compilation debug="false">
            <assemblies>
              <add assembly="System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
            </assemblies>
         </compilation>
        <authentication mode="None"/>
    </system.web>
</configuration>

This code works and does what I want! Now I decided to move this code off the Exchange 2007 server and deploy it on to a Windows 2003 box that has the Exchange 2007 Management tools(SP1 and RU 5) Installed. When I did this it no longer worked! I get an exception instead:

Error: Microsoft.Exchange.Configuration.Tasks.ThrowTerminatingErrorException: Database "XXXXXXX-8\First Storage Group\Mailbox Database" was not found. Please make sure you have typed it correctly. ---> Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException: Database "XXXXXXX-8\First Storage Group\Mailbox Database" was not found. Please make sure you have typed it correctly. at Microsoft.Exchange.Configuration.Tasks.DataAccessTask`1.GetDataObject[TObject](IIdentityParameter id, IConfigDataProvider session, ObjectId rootID, Nullable`1 notFoundError, LocalizedString multipleFoundError) at Microsoft.Exchange.Management.RecipientTasks.EnableMailbox.InternalBeginProcessing() at Microsoft.Exchange.Configuration.Tasks.Task.BeginProcessing() --- End of inner exception stack trace --- at Microsoft.Exchange.Configuration.Tasks.Task.ThrowTerminatingError(Exception exception, ErrorCategory category, Object target) at Microsoft.Exchange.Configuration.Tasks.Task.ProcessUnhandledException(Exception e) at Microsoft.Exchange.Configuration.Tasks.Task.BeginProcessing() at System.Management.Automation.Cmdlet.DoBeginProcessing() at System.Management.Automation.CommandProcessorBase.DoBegin()

When I alter the same code to run in a Windows Application, it works. This for sure has something to do with Impersonation and IIS being involved. Not going into details of why I got the error I decided to look for alternatives as I was running short on time.

2) Create a new Application Pool, configure it's Identity to a user who has the required permissions to Enable a Mailbox

The simplest way I could get my code to work from the Windows 2003 box was to create a new Application Pool in IIS and configure its Identity to a user who had permissions to Enable a mailbox. Now this leaves me with a very very big security hole, since my entire application is running under an account which is quite powerful, I can only imagine the risks.

If you can guarantee that security is taken care of, you could use this approach but I personally would never use it. having said that, what else could I do? I do not want to run my code on the Exchange 2007 Server.

3) Create a COM component and register that component with component services(COM+) running the component under a user who has the required permissions to Enable a Mailbox.

This is the only approach that worked for me in this case and also in the past. Here is how you can get started:

Creating the C# Component
Build and configure a COM+ component to host the PowerShell code and ensure that it is running with the credentials of a service account and not inheriting any credentials from the caller.

Creating the project…
1)Start Visual Studio 2005
2)Select File->New->Project…
3)Under C#, select Windows
4)In the “Template Types” select “Class Library”
5)In the Name box type “PowerShellComponent
6)Click OK.
7)Add references to System.EnterpriseServices, System.Management.Automation(browse to the C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\).

Signing the Assembly...
1)Right click on the PowerShellComponent project in the Solution Explorer and click properties. In the signing tab check the box to “Sign the assembly” and create a new strong name key called “PowerShellComponent.snk”.
2)Still in the project properties window, select the Application tab and click “Assembly Information…”, check the box that says, “Make assembly COM-Visible”.
3)Open the AssemblyInfo.cs and add “using System.EnterpriseServices;” to the top and the following lines at the bottom of the file…

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationName("PowerShellComponent")]
[assembly: Description("Simple PowerShell Component Sample")]
[assembly: ApplicationAccessControl(
           false,
           AccessChecksLevel = AccessChecksLevelOption.Application,
           Authentication = AuthenticationOption.None,
           ImpersonationLevel = ImpersonationLevelOption.Identify)]

Add the ManagementCommands class…
1)Select the “Solution Explorer” view tab.
   Rename the class1.cs file to “ManagementCommands.cs”.
2)Remove the code generated by the Wizard.
   Cut and paste the code provided below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation.Runspaces;
using System.EnterpriseServices;
using System.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;

namespace PowerShellComponent
{
    public class ManagementCommands : System.EnterpriseServices.ServicedComponent
    {
        public String EnableMailbox(string domain, string domainController)
        {
            String ErrorText="";
            RunspaceConfiguration config = RunspaceConfiguration.Create();
            PSSnapInException warning;

            // Load Exchange PowerShell snap-in.
            config.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out warning);
            if (warning != null) throw warning;

            using (Runspace thisRunspace = RunspaceFactory.CreateRunspace(config))
            {
                try
                {
                    thisRunspace.Open();
                    using (Pipeline thisPipeline = thisRunspace.CreatePipeline())
                    {
                        //Please change parameter values.
                        thisPipeline.Commands.Add("Enable-Mailbox");
                        thisPipeline.Commands[0].Parameters.Add("Identity", @"DOM157711\xxxxxxx");
                        thisPipeline.Commands[0].Parameters.Add("Database", @"XXXXXXX-8\First Storage Group\Mailbox Database");
                        // Need the line below when impersonating. KB943937. Need rollup 1 for sp1.
                        thisPipeline.Commands[0].Parameters.Add("DomainController", domainController);

                        try
                        {
                            thisPipeline.Invoke();
                        }
                        catch (Exception ex)
                        {
                            ErrorText = "Error: " + ex.ToString();
                        }

                        // Check for errors in the pipeline and throw an exception if necessary.
                        if (thisPipeline.Error != null && thisPipeline.Error.Count > 0)
                        {
                            StringBuilder pipelineError = new StringBuilder();
                            pipelineError.AppendFormat("Error calling Enable-Mailbox.");
                            foreach (object item in thisPipeline.Error.ReadToEnd())
                            {
                                pipelineError.AppendFormat("{0}\n", item.ToString());
                            }

                            ErrorText = ErrorText + "Error: " + pipelineError.ToString();
                        }
                    }
                }

                finally
                {
                    thisRunspace.Close();
                    EndImpersonation();
                }
            }
            if (ErrorText == "")
                return "Success";
            else
                return ErrorText;
        }
        public string GetIdentity()
        {
            AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.PrincipalPolicy.WindowsPrincipal);

            System.Security.Principal.WindowsPrincipal user = System.Threading.Thread.CurrentPrincipal  as System.Security.Principal.WindowsPrincipal;

            return user.Identity.Name;
        }
    }
}

Create and Configure the COM+ Component

1. Build the PowerShellComponent project in Visual Studio
2. From the Visual Studio command prompt run “regsvcs PowerShellComponent.dll”
3. Open “Component Services” from “Administrative Tools”
4. Under “Component Services”, “Computers”, “My Computer”, “COM+ Applications”, find “PowerShellComponent” and right click on it then select “Properties”
5. On the identity tab:
    a. Configure “This user” as an account that has Exchange Admin privileges on your Exchange server.
6. Click OK

Code for the Web Application(ASP.Net) to Consume PowerShellComponent

Below is the code that needs to be pasted in the Code Behind file(This code assumes we are using Default.aspx.cs)

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Security;
using System.Security.Principal;
using System.Runtime.InteropServices;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Use the current domain and domain controller
        // Please change ualues to suit you needs.
        string domain = "DOM157711";
        string domainController = "XXXXXXX-8";
        try
        {
            EnableMailbox(domain, domainController);
        }
        catch (Exception ex)
        {
            Response.Write(ex.ToString());
        }
        finally
        {
            Response.Write("Done..");
        }
    }
   private void EnableMailbox(string domain, string domainController)
   {
       Response.Write(string.Format("<b>Web Application Context:</b> {0}<br>", WindowsIdentity.GetCurrent().Name));

       // Create the COM+ component
       PowerShellComponent.ManagementCommands objManage = new PowerShellComponent.ManagementCommands();

       String Results;
       Results = objManage.EnableMailbox(domain1, domainController1);
       Response.Write(Results);

       // Print out the security context of the component
       Response.Write(string.Format("<br><b>Component Context:</b> {0}<br><br>", objManage.GetIdentity()));

       objManage = null;

   }
}

Copy PowerShellComponent.dll from the output directory of the PowerShellComponent project to the “bin” under you Web Application and build the Web Project and test.

Talking a look at the results

When you access this page from another machine as different user or anonymous user you will notice that the web application and the COM+ component are running in different security contexts. For example in my test, I access the page as anonymous user and when I hit the web I got the following result…

Web Application Context: NT AUTHORITY\NETWORK SERVICE
Success
Component Context: DOM157711\akashb

The key here is that Web Application is running under NT AUTHORITY\NETWORK SERVICE and has no permission to Enable a Mailbox. The web form makes the call to our COM+ component which always executes the PowerShellComponent code in the context of account that we configured in COM+, no matter who the calling process is running as.

Using the COM+ approach has always worked for me, it also help you avoid other issues as outlined by Matt in his post.

Enjoy!

HOW TO: Get details about a Exchange User in Outlook 2007 & Outlook 2003

I recently came across an issue where I needed to get the details about Exchange User from within an Outlook Add-in. Below is a screen shot of the details I am referring to.

 image

In Outlook 2007 its very simple, just use the ExchangeUser Object and you will get the details. More details can be found in the article below:

ExchangeUser Object Members
http://msdn.microsoft.com/en-us/library/bb176658.aspx

Below is sample C# code that shows how to use the ExchangeUser Object:

using System;
using System.Collections.Generic;
using System.Text;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace GetUserDetails
{
    class Program
    {
        static void Main(string[] args)
        {
            Outlook.Application oApp;
            Outlook.NameSpace oNameSpace;
            Outlook.SelectNamesDialog oSNDialog;
            Outlook.AddressEntry oAEntry;
            Outlook.ExchangeUser oEUser;
    
            oApp = new Outlook.Application();
            oNameSpace = oApp.GetNamespace("MAPI");
            oNameSpace.Logon(null, null, true, false);  

            oSNDialog = oNameSpace.GetSelectNamesDialog();
            oSNDialog.NumberOfRecipientSelectors = Outlook.OlRecipientSelectors.olShowTo;
            oSNDialog.Caption = "Select User";
            oSNDialog.ToLabel = "User Name";
            oSNDialog.ShowOnlyInitialAddressList = true;
            oSNDialog.AllowMultipleSelection = false;
            oSNDialog.Display();
    
             foreach(Outlook.Recipient oRecipient in oSNDialog.Recipients)
             {
                oAEntry = oRecipient.AddressEntry;
                if(oAEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry
                    || oAEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeRemoteUserAddressEntry)
                {
                    oEUser = oAEntry.GetExchangeUser();
                    Console.WriteLine(oEUser.Name);
                    Console.WriteLine(oEUser.BusinessTelephoneNumber);
                    Console.WriteLine(oEUser.MobileTelephoneNumber);
                    Console.WriteLine(oEUser.Department);

                    oEUser = null;
                }
                oAEntry = null;
             }

            oNameSpace.Logoff();
            oNameSpace = null;
            
        }
    }
}

However in Outlook 2003 the ExchangeUser Object is not there. The only way to do it is query the AD based on the AddressEntry.Address(LegacyexchangeDN). Below is sample C# code that uses the DirectoryServices namespace to query the AD based on LegacyexchangeDN.

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;

namespace GetUserDetailsBasedOnLegacyDN
{
    class Program
    {
        static void Main(string[] args)
        {
            //This is the legacy exchange DN
            string LEDN = "/O=ms/OU=east/cn=Recipients/cn=whatever";

            DirectoryEntry objRootDSE = new DirectoryEntry("GC://RootDSE");
            String strRoot = objRootDSE.Properties["rootDomainNamingContext"].Value.ToString();
            DirectoryEntry objRoot = new DirectoryEntry("GC://" + strRoot);
            
            objRoot.RefreshCache();

            DirectorySearcher objSrch = new DirectorySearcher(objRoot);
            objSrch.Filter = "(&(objectClass=user)(objectCategory=person)(legacyExchangeDN= " + LEDN + "))";
            objSrch.SearchScope = SearchScope.Subtree;
            DirectoryEntry objUser = new DirectoryEntry();
            objUser = objSrch.FindOne().GetDirectoryEntry();

            Console.WriteLine("Mobile:" + objUser.Properties["mobile"].Value);
            Console.WriteLine("Number:" + objUser.Properties["telephoneNumber"].Value);
            Console.WriteLine("Department:" + objUser.Properties["department"].Value);
            
            objUser = null;
            objSrch = null;
            objRoot = null;
            objRootDSE = null;
        }
    }
}

Note: I am not an AD expert, you might need to tweak the code depending on your environment and your needs.
HOWTO: Set Mailbox Quotas using CDOEXM

Are you someone who maintains Exchange 2003 servers? Did you ever have a requirement to set the Mailbox quotas for a lot of users at one go?  Here is your solution. The sample code below uses ADSI and Cdoexm to set mailbox quotas for multiple user.

The following sample is a simple VBScript code sample that sets the StoreQuota, OverQuotaLimit, Hardlimit for a mailbox. To use this sample, paste the following code in a new text file, and then name the file SetMailboxQuota.txt:

'This script sets the StoreQuota, OverQuotaLimit, Hardlimit for a mailbox.

' USAGE: cscript SetMailboxQuota.vbs DATAFILE
' This code has to be run on a box that has ESM or Exchange 2003 Installed.

Dim obArgs
Dim cArgs

Set obArgs = WScript.Arguments
cArgs = obArgs.Count


Main

Sub Main()
Dim FileSysObj
Dim DataFileName
Dim DataFile
Dim sAMAccoutname
Dim vDistinguishedName

Const ForReading = 1, ForWriting = 2, ForAppending = 8, TristateFalse = 0

On error Resume Next
If cArgs <> 1 Then
      WScript.Echo "Usage: cscript SetMailboxQuota.vbs DATAFILE"
      Exit Sub
End If

Set FileSysObj = CreateObject("Scripting.FileSystemObject")

DataFileName = obArgs.Item(0)

Set DataFile = FileSysObj.OpenTextFile(DataFileName, ForReading, False,0)

Do While Not DataFile.AtEndOfStream
    sAMAccoutname = DataFile.ReadLine

    vDistinguishedName = GetDNFromSam(sAMAccoutname)

    If vDistinguishedName <> "" then
        WScript.Echo "Sam Account Name:" & sAMAccoutname
        WScript.Echo "Distinguished Name:" & vDistinguishedName

        'Please change limits as per your needs
        'The Size Specified is in Kb
        Call SetLimits(vDistinguishedName, 18432,20480,25600)

        If Err.Number <> 0 Then
              sMsg = "Error Setting Limits"
           sMsg = sMsg & Err.Number & " " & Err.Description
           WScript.Echo sMsg
    End If
    else
       WScript.Echo "Could not get Distinguished Name for " & sAMAccoutname
    end if

    WScript.Echo 
Loop
   
DataFile.Close
Set DataFile = Nothing
Set FileSysObj = Nothing

End Sub

Function GetDNFromSam(strsAMAccountName)

Dim iAdCont
Dim iAdGC 
Dim Conn 
Dim Com 
Dim Rs 
Dim varGCAdsPath
Dim strQuery

   ' Get the Global Catalog
   Set iAdCont = GetObject("GC:")
  
   For Each iAdGC In iAdCont
      varGCAdsPath = iAdGC.ADsPath
   Next

   Set Conn = createobject("ADODB.Connection")
   Set Com = createobject("ADODB.Command")

   ' Open the Connection
   Conn.Provider = "ADsDSOObject"
   Conn.Open "ADs Provider"

   ' Build the query to find the user based on his Alias
   strQuery = "<" & varGCAdsPath & ">;(sAMAccountName=" & strsAMAccountName & ");mailNickName,distinguishedName;subtree"

   Com.ActiveConnection = Conn
   Com.CommandText = strQuery
   Set Rs = Com.Execute

   GetDNFromSam = Rs.Fields("distinguishedName")

   'Clean Up
   Rs.Close
   Conn.Close

   Set Rs = Nothing
   Set Com = Nothing
   Set Conn = Nothing
   Set iAdCont = Nothing
   Set iAdGC = Nothing

End Function


Sub SetLimits(strUserDN, vStoreQuota,vOverQuotaLimit,vHardLimit)

Const AD_MODE_READ_WRITE = 3
Dim strLDAP
Dim objPerson
Dim objMailbox

    strLDAP = "LDAP://" & strUserDN 

    Set objPerson = CreateObject("CDO.Person")
 
    objPerson.DataSource.Open strLDAP,,AD_MODE_READ_WRITE

    Set objMailbox = objPerson.GetInterface("IMailboxStore")

    'check if the user is mailbox enabled
    If objMailbox.HomeMDB = "" Then
        Wscript.Echo "No Mailbox found."
    Else
       'Give Warning at
        Wscript.Echo "Current StoreQuota:" & objMailbox.StoreQuota
        Wscript.Echo "Setting StoreQuota to:" & vStoreQuota & "(KB)"
        objMailbox.StoreQuota = vStoreQuota

       'Prevent Sending at
       Wscript.Echo "Current OverQuotaLimit:" & objMailbox.OverQuotaLimit & "(KB)"
       Wscript.Echo "Setting OverQuotaLimit to:" & vOverQuotaLimit & "(KB)"
       objMailbox.OverQuotaLimit = vOverQuotaLimit

       'Prevent Sending and Recieving at
       Wscript.Echo "Current Hardlimit:" & objMailbox.HardLimit & "(KB)"
       Wscript.Echo "Setting StoreQuota to:" & vHardLimit & "(KB)"
       objMailbox.HardLimit = vHardLimit

       ' Clearing defaults
       Wscript.Echo "Disabling Database Defaults"
       objMailbox.EnableStoreDefaults = false
       objPerson.DataSource.Save

    End If

    Set objMailbox = Nothing
    Set objPerson = Nothing

End Sub

The list of sAMAccountName's can be provided via a text file(Datafile). The Datafile contains the sAMAccountName's of the users(one on each line). So assuming your Datafile is called "sAMAccounts.txt" and is on the C:\, you would run the script as follows:

C:\>Cscript SetMailboxQuota.vbs C:\sAMAccounts.txt

The account that you are logged on to the computer must have permissions to change the limits on the mailboxes.

HOWTO: Dump out Contacts using CDOEX and ADO

The following sample is a simple VBScript code sample that that uses CDOEX and ADO to iterate through multiple mailboxes and dumps out the contact data to a text file. This code must be run on the Exchange server.

To use this sample, paste the following code in a new text file, and then name the file DumpContacts.vbs:

'This script must be run on an Exchange Server.
'USAGE: cscript DumpContacts.vbs DOMAIN Datafile(FullPath)

Dim obArgs
Dim cArgs

Set obArgs = WScript.Arguments
cArgs = obArgs.Count

Main

Sub Main()
Dim FileSysObj
Dim DataFileName
Dim DataFile
Dim alias

Const ForReading = 1, ForWriting = 2, ForAppending = 8, TristateFalse = 0

   If cArgs <> 2 Then
      WScript.Echo "Usage: cscript DumpContacts.vbs DOMAIN Datafile(FullPath)"
      Exit Sub
   End If

   Set FileSysObj = CreateObject("Scripting.FileSystemObject")

   DataFileName = obArgs.Item(1)

   Set DataFile = FileSysObj.OpenTextFile(DataFileName, ForReading, False,0)

   'Read line by line to get the mailbox alias from the Datafile
   Do While Not DataFile.AtEndOfStream
     alias = DataFile.ReadLine

     'Dump contact data to a csv file
     Call DumpContacts(obArgs.Item(0), alias)
   Loop
   
   DataFile.Close

   'Clean up
   Set DataFile = Nothing
   Set FileSysObj = Nothing

End Sub

Sub DumpContacts(Domain, Mailbox)

Dim Rs
Dim Rec
Dim strURL
Dim sMsg
Dim strLocalPath
Dim ContactData

   On Error Resume Next

  ' Specify the URL to a folder or an item.
  strLocalPath = "MBX/" & Mailbox &"/Contacts"

  Set Rec = CreateObject("ADODB.Record")
  Set Rs = CreateObject("ADODB.Recordset")
  strURL = "file://./backofficestorage/" & Domain & "/" & strLocalPath

  Rec.Open strURL

  If Err.Number <> 0 Then
      sMsg = "Error Connecting to the Contact folder for User " & Mailbox & " "
      sMsg = sMsg & Err.Number & " " & Err.Description
      WScript.Echo sMsg
      Exit Sub
  End If
  
  Set Rs.ActiveConnection = Rec.ActiveConnection

  Rs.Source = "SELECT ""DAV:href"", " & _
          " ""urn:schemas:contacts:email1"", " & _
              " ""urn:schemas:contacts:nickname"", " & _
              " ""urn:schemas:contacts:title"" " & _
              "FROM scope('shallow traversal of """ & strURL & """') "
  Rs.Open

  while Not Rs.EOF

    Dim iPerson
    Set iPerson = Createobject("CDO.Person")
    iPerson.DataSource.Open Rs.Fields("DAV:href").Value, Rec.ActiveConnection

    ContactData = iPerson.Company & ";"
    ContactData = ContactData & iPerson.Email & ";"
    ContactData = ContactData & iPerson.Email2 & ";"
    ContactData = ContactData & iPerson.Email3 & ";"
    ContactData = ContactData & iPerson.FileAs & ";"
    ContactData = ContactData & iPerson.FileAsMapping & ";"
    ContactData = ContactData & iPerson.FirstName & ";"
    ContactData = ContactData & iPerson.HomeCity & ";"
    ContactData = ContactData & iPerson.HomeCountry & ";"
    ContactData = ContactData & iPerson.HomeFax & ";"
    ContactData = ContactData & iPerson.HomePhone & ";"
    ContactData = ContactData & Replace(iPerson.HomePostalAddress, vbcrlf," ") & ";"
    ContactData = ContactData & iPerson.HomePostalCode & ";"
    ContactData = ContactData & iPerson.HomePostOfficeBox & ";"
    ContactData = ContactData & iPerson.HomeState & ";"
    ContactData = ContactData & iPerson.HomeStreet & ";"
    ContactData = ContactData & iPerson.Initials & ";"
    ContactData = ContactData & iPerson.LastName & ";"
    ContactData = ContactData & Replace(iPerson.MailingAddress, vbcrlf," ") & ";"
    ContactData = ContactData & iPerson.MailingAddressID & ";"
    ContactData = ContactData & iPerson.MiddleName & ";"
    ContactData = ContactData & iPerson.MobilePhone & ";"
    ContactData = ContactData & iPerson.NamePrefix & ";"
    ContactData = ContactData & iPerson.NameSuffix & ";"
    ContactData = ContactData & iPerson.Title & ";"
    ContactData = ContactData & iPerson.WorkCity & ";"
    ContactData = ContactData & iPerson.WorkCountry & ";"
    ContactData = ContactData & iPerson.WorkFax & ";"
    ContactData = ContactData & iPerson.WorkPager & ";"
    ContactData = ContactData & iPerson.WorkPhone & ";"
    ContactData = ContactData & Replace(iPerson.WorkPostalAddress, vbcrlf," ") & ";"
    ContactData = ContactData & iPerson.WorkPostalCode & ";"
    ContactData = ContactData & iPerson.WorkPostOfficeBox & ";"
    ContactData = ContactData & iPerson.WorkState & ";"
    ContactData = ContactData & iPerson.WorkStreet & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:weddinganniversary").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:account").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:bday").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:billinginformation").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:businesshomepage").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:co").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:cn").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:computernetworkname").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:dn").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:gender").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:weddinganniversary").Value & ";"
    ContactData = ContactData & iPerson.Fields("urn:schemas:contacts:manager").Value & ";"
    ContactData = ContactData & iPerson.Fields("http://schemas.microsoft.com/exchange/sensitivity").Value & “;”

    WriteFile ContactData 

    Rs.MoveNext

  wend
  
  Rs.Close
  Rec.Close

  'Clean Up  
  Set Rs = Nothing
  Set Rec = Nothing
    
End Sub

Sub WriteFile(Contents)   

Const ForWriting =2 
Const ForAppending = 8

  Set oFS = CreateObject("Scripting.FileSystemObject") 
  Set oFSFile = oFS.OpenTextFile("C:\Contacts.txt",ForAppending,True) 

  oFSFile.WriteLine(Contents) 
  oFSFile.Close 

  Set oFSFile = Nothing 
  Set oFS = Nothing 

End Sub 

The list of mailboxes to scan can be provided via a text file(Datafile). The Datafile contains the aliases of the users(one on each line). So assuming your Datafile is called "Aliases.txt" and is on the C:\, you would run the script as follows:

C:\>Cscript DumpContacts.vbs "MyDomain.com" "C:\Aliases.txt"

The account that you are logged on the computer with must have permissions on the mailboxes that you are trying to iterate through. You can give the permissions by following the steps in the article below:

How to assign service account access to all mailboxes in Exchange Server 2003
http://support.microsoft.com/kb/821897/

HOW TO:Set folder level permissions using CDO 1.21 and ACL.dll

This is not something everyone would want to do, but just in case below is the sample code that uses ACL.dll (found in the Platform SDK) to set "Reviewer" permissions on all the folders for a specific user.

The following sample is a simple VBScript code sample that iterates through all folders in multiple mailboxes and sets the "Reviewer" permissions. To use this sample, paste the following code in a new text file, and then name the file SetFolderPermissions.vbs:

'This script logs on to a server that is running Exchange Server and iterates through all the mailboxes
'recursively setting the "Reviewer" permission on each folder for a specific user.

' USAGE: cscript SetFolderPermissions.vbs SERVERNAME DATAFILE FullUserName
' This requires that CDO 1.21 and the Acl.dll is installed on the computer.

Dim obArgs
Dim cArgs
Dim objSession
Dim objInfoStores
Dim FullUserName

Set obArgs = WScript.Arguments
cArgs = obArgs.Count

Const CdoMsg = 3,ForReading = 1, ForWriting = 2, ForAppending = 8, TristateFalse = 0

Main

Sub Main()
Dim FileSysObj
Dim DataFileName
Dim DataFile
Dim alias

    If cArgs <> 3 Then
        WScript.Echo "Usage: cscript SetFolderPermissions.vbs SERVERNAME DATAFILE(Name and Path) FullUserName"
        Exit Sub
    End If

    Set FileSysObj = CreateObject("Scripting.FileSystemObject")

    DataFileName = obArgs.Item(1)
    FullUserName = obArgs.Item(2)


    Set DataFile = FileSysObj.OpenTextFile(DataFileName, ForReading, False,0)
    
    'Read line by line
    Do While Not DataFile.AtEndOfStream
         alias = DataFile.ReadLine
    
    'Loop through the mailboxes
        Call IterateInfoStores(obArgs.Item(0), alias)
    Loop
   
    DataFile.Close

    'Clean Up    
    Set DataFile = Nothing
    Set FileSysObj = Nothing

End Sub


Sub IterateInfoStores(ServerName,UserName)

Dim objFolder
Dim intCounter
Dim objInfoStore
Dim sMsg

    On Error Resume Next
    
    'Create the new Session Object
    Set objSession = CreateObject("MAPI.Session")

    If Err.Number <> 0 Then
      sMsg = "Error creating MAPI.Session."
      sMsg = sMsg & "Make sure CDO 1.21 is installed. "
      sMsg = sMsg & Err.Number & " " & Err.Description
      WScript.Echo sMsg
      Exit Sub
    End If

    'Logon to the Mailbox
    objSession.Logon "", "", False, True, 0, False, ServerName & vbLf & UserName
    
    If Err.Number <> 0 Then
      sMsg = "Error logging on: "
      sMsg = sMsg & Err.Number & " " & Err.Description
      WScript.Echo sMsg
      WScript.Echo "Server: " & ServerName
      WScript.Echo "Mailbox: " & UserName
      Set objSession = Nothing
      Exit Sub
    End If

    WScript.Echo "Logged On to:" & objSession.CurrentUser

    'Loop through the Infostores
    For intCounter = 1 To objSession.InfoStores.Count
        Set objInfoStore = objSession.InfoStores(intCounter)

    If Err.Number <> 0 Then
          sMsg = "Error retrieving InfoStore Object: "
          sMsg = sMsg & Err.Number & " " & Err.Description
          WScript.Echo sMsg
          WScript.Echo "Server: " & ServerName
          WScript.Echo "Mailbox: " & UserName
          Set objInfoStore = Nothing
          Set objSession = Nothing
          Exit Sub
    End If
    
        If objInfoStore.Name = "Mailbox - " & objSession.CurrentUser Then
            Exit For
        End If
    Next
    
    Set objFolder = objInfoStore.RootFolder
    
    If Err.Number <> 0 Then
    sMsg = "Error retrieving RootFolder Object: "
    sMsg = sMsg & Err.Number & " " & Err.Description
    WScript.Echo sMsg
    WScript.Echo "Server: " & ServerName
    WScript.Echo "Mailbox: " & UserName
    Set objInfoStore = Nothing
    Set objFolder = Nothing
    Set objSession = Nothing
    Exit Sub
    End If

    'Recurse through the sub-folders
    NavigateFolders objFolder

    If Err.Number <> 0 Then
    sMsg = "Error: "
    sMsg = sMsg & Err.Number & " " & Err.Description
    WScript.Echo sMsg
    WScript.Echo "Server: " & ServerName
    WScript.Echo "Mailbox: " & UserName
    End If
    
    'Logoff from the session
    objSession.Logoff

    'Clean Up
    Set objFolder = Nothing
    Set objInfoStore = Nothing
    Set objSession = Nothing
End Sub

Sub NavigateFolders(MAPIFolder)
Dim intCounter
Dim oDelegate
Dim oAddrBook
Dim oNewAce
Dim ACLObj
Dim FolderACEs
Dim objAce

Const ROLE_REVIEWER = &H401
Const ROLE_OWNER = &H5E3 
Const ROLE_PUBLISH_EDITOR = &H4E3 
Const ROLE_EDITOR = &H463 
Const ROLE_PUBLISH_AUTHOR = &H49B 
Const ROLE_AUTHOR = &H41B 
Const ROLE_NONEDITING_AUTHOR = &H413 
Const ROLE_CONTRIBUTOR = &H402 
Const ROLE_NONE = &H400 


    WScript.Echo "Folder Name:" & MAPIFolder.Name

    'Create the ACL object
    Set ACLObj = CreateObject("MSExchange.aclobject")
    
    ' Associate the ACLObject to the CDO Folder
    ACLObj.CDOItem = MAPIFolder
    Set FolderACEs = ACLObj.ACEs

    ' Create a MAPI object for UserA
    Set oAddrBook = objSession.AddressLists("Global Address List")

    Set oDelegate = oAddrBook.AddressEntries.Item(FullUserName)

    Set oNewAce = CreateObject("MSExchange.ACE")

    oNewAce.ID = oDelegate.ID
    oNewAce.Rights = ROLE_REVIEWER
    FolderACEs.Add oNewAce
    ACLObj.Update

    ' Loop through all of the ACEs for the folder and display them
    For each objAce in  FolderACEs
    WScript.Echo GetACLEntryName(objAce.ID) & " - " & DispACERules(objAce)
    Next
    WScript.Echo ""

    ' Clean up objects
    Set objAce = Nothing
    Set oNewAce = Nothing
    Set FolderACEs = Nothing
    Set ACLObj = Nothing

    If MAPIFolder.Folders.Count > 0 Then
        For intCounter = 1 To MAPIFolder.Folders.Count
            NavigateFolders MAPIFolder.Folders(intCounter)
        Next
    End If


End Sub

Function GetACLEntryName(ACLEntryID)
On Error resume Next
' This function finds the user that is listed as an ACE on the folder.
' It takes the ID that it is passed and uses the Session.GetAddressEntry method
' to find the name.
    
    Dim tmpEntry
    Dim tmpName

    Select Case ACLEntryID
    Case "ID_ACL_DEFAULT"
            GetACLEntryName = "Default"
    Case "ID_ACL_ANONYMOUS"
            GetACLEntryName = "Anonymous"
    Case else
       ' Get the name of the ACE
        Set tmpEntry = objSession.GetAddressEntry(ACLEntryID)
            tmpName = tmpEntry.Name
            GetACLEntryName = tmpName
    End Select
    
End Function

Function DispACERules(DisptmpACE)
' This function checks the roles of the ACE that is passed to it and    returns
' the Role back.
Const ROLE_NONE = 1024
Const ROLE_AUTHOR = 1051
Const ROLE_CONTRIBUTOR = 1026
Const ROLE_PUBLISH_AUTHOR = 1179
Const ROLE_NONEDITING_AUTHOR = 1043
Const ROLE_REVIEWER = 1025
Const ROLE_EDITOR = 1147
Const ROLE_OWNER = 2043
Const ROLE_PUBLISH_EDITOR = 1275  

    ' Check the roles on the folder
    Select Case DisptmpACE.Rights
        Case ROLE_NONE, 0  ' Checking in case the role has not been set on that entry.
                DispACERules = "None"
        Case ROLE_AUTHOR
                DispACERules = "Author"
        Case ROLE_CONTRIBUTOR
                DispACERules = "Contributor"
        Case ROLE_EDITOR
                DispACERules = "Editor"
        Case ROLE_NONEDITING_AUTHOR
                DispACERules = "Nonediting Author"
        Case ROLE_OWNER
                DispACERules = "Owner"
        Case ROLE_PUBLISH_AUTHOR
                DispACERules = "Publishing Author"
        Case ROLE_PUBLISH_EDITOR
                DispACERules = "Publishing Editor"
        Case ROLE_REVIEWER
                DispACERules = "Reviewer"
        Case Else
        ' This will grab all other custom permissions on the folder
                DispACERules = "Custom"
    End Select

End Function
 

The list of mailboxes can be provided via a text file(Datafile). The Datafile contains the aliases of the users(one on each line). So assuming your Datafile is called "Aliases.txt" and is on the C:\, you would run the script as follows:

C:\>Cscript SetFolderPermissions.vbs Exchange2003 C:\Aliases.txt "Akash Bhargava"

The script currently sets and also dumps out the permissions on each folder in the mailbox.

The account that you are logged on the computer with must have permissions on the mailboxes that you are trying to iterate through. You can give the permissions by following the steps in the article below:

How to assign service account access to all mailboxes in Exchange Server 2003
http://support.microsoft.com/kb/821897/

Enjoy!

Unable to Instantiate Outlook Object from Visual Studio 2008 on Vista with UAC ON?

Having problems in instantiating a new Outlook object from VS 2008(Windows Form Application) when running on Vista with UAC ON? Getting weird errors similar to the ones below?

System.Runtime.InteropServices.COMException was unhandled
  Message="Creating an instance of the COM component with CLSID {0006F03A-0000-0000-C000-000000000046} from the IClassFactory failed due to the following error: 80010001."
  Source="MyApp"
  ErrorCode=-2147418111

This error was when Outlook was NOT running and I tried to debug my application from Visual studio 2008.

System.Runtime.InteropServices.COMException was unhandled
  Message="Retrieving the COM class factory for component with CLSID {0006F03A-0000-0000-C000-000000000046} failed due to the following error: 80080005."
  Source="MyApp"
  ErrorCode=-2146959355

This error was when Outlook was already running and I tried to debug my application from Visual studio 2008. In both cases I got the error when I tried to instantiate a new object of the Outlook.Application.

oApp = new Outlook.Application

Using the Err.exe to get more details on the 80010001 and the 80080005 we get the following details:

# for hex 0x80010001 / decimal -2147418111 :
  DBG_EXCEPTION_NOT_HANDLED                                     ntstatus.h    
# Debugger did not handle the exception.
  RPC_E_CALL_REJECTED                                           winerror.h    
# Call was rejected by callee.
# 2 matches found for "80010001"

# for hex 0x80080005 / decimal -2146959355 :
  CO_E_SERVER_EXEC_FAILURE                                      winerror.h    
# Server execution failed
# 1 matches found for "80080005"

Why would we be getting a RPC_E_CALL_REJECTED or CO_E_SERVER_EXEC_FAILURE? In my case this was happening because of the difference in Integrity Level of the two processes(Outlook.exe and devenv.exe)

The Visual Studio 2008 was being "Run as administrator" which makes it run in High IL and Outlook was by default running in Medium IL and this was causing the errors. The solution is run both the processes in the same IL.

How can you check the Integrity Level of a process? Use Process Explorer(Click on the View menu -> Select Columns->Process Image Tab and check Integrity Level)

Hope this helps!

HOW TO:Iterating through Exchange Mailboxes using CDO 1.21

I have seen many developers wanting to loop through multiple mailboxes either to get the mailbox size or get the number of emails in different folders. Here is a sample that loops through multiple mailboxes recursively.

The following sample is a simple VBScript code sample that iterates through all folders in multiple mailboxes. To use this sample, paste the following code in a new text file, and then name the file Iteratemailboxes.vbs:

'This script logs on to a server that is running Exchange Server and
'iterates through all the mailboxes recursively.

' USAGE: cscript Iteratemailboxes.vbs SERVERNAME DATAFILE
' This requires that CDO 1.21 is installed on the computer.

Dim obArgs
Dim cArgs
Dim objSession
Dim objInfoStores

' Get command line arguments.
Set obArgs = WScript.Arguments
cArgs = obArgs.Count

Const CdoMsg = 3,ForReading = 1, ForWriting = 2, ForAppending = 8, TristateFalse = 0

Main

Sub Main()
Dim FileSysObj
Dim DataFileName
Dim DataFile
Dim alias

    If cArgs <> 2 Then
        WScript.Echo "Usage: cscript IterateMailboxes.vbs SERVERNAME DATAFILE(Name and Path)"
        Exit Sub
    End If

    Set FileSysObj = CreateObject("Scripting.FileSystemObject")

    DataFileName = obArgs.Item(1)

    Set DataFile = FileSysObj.OpenTextFile(DataFileName, ForReading, False,0)
    
    'Read line by line
    Do While Not DataFile.AtEndOfStream
         alias = DataFile.ReadLine
    
        'Loop through the mailboxes
        Call IterateInfoStores(obArgs.Item(0), alias)
    Loop
   
    DataFile.Close

    'Clean Up    
    Set DataFile = Nothing
    Set FileSysObj = Nothing

End Sub


Sub IterateInfoStores(ServerName,UserName)

Dim objFolder
Dim intCounter
Dim objInfoStore
Dim sMsg

    On Error Resume Next
    
    'Create the new Session Object
    Set objSession = CreateObject("MAPI.Session")

    If Err.Number <> 0 Then
      sMsg = "Error creating MAPI.Session."
      sMsg = sMsg & "Make sure CDO 1.21 is installed. "
      sMsg = sMsg & Err.Number & " " & Err.Description
      WScript.Echo sMsg
      Exit Sub
    End If

    'Logon to the Mailbox
    objSession.Logon "", "", False, True, 0, False, ServerName & vbLf & UserName
    
    If Err.Number <> 0 Then
      sMsg = "Error logging on: "
      sMsg = sMsg & Err.Number & " " & Err.Description
      WScript.Echo sMsg
      WScript.Echo "Server: " & ServerName
      WScript.Echo "Mailbox: " & UserName
      Set objSession = Nothing
      Exit Sub
    End If

    WScript.Echo "Logged On to:" & objSession.CurrentUser

    'Loop through the Infostores
    For intCounter = 1 To objSession.InfoStores.Count
        Set objInfoStore = objSession.InfoStores(intCounter)

        If Err.Number <> 0 Then
          sMsg = "Error retrieving InfoStore Object: "
          sMsg = sMsg & Err.Number & " " & Err.Description
          WScript.Echo sMsg
          WScript.Echo "Server: " & ServerName
          WScript.Echo "Mailbox: " & UserName
          Set objInfoStore = Nothing
          Set objSession = Nothing
          Exit Sub
        End If
    
        If objInfoStore.Name = "Mailbox - " & objSession.CurrentUser Then
            Exit For
        End If
    Next
    
    Set objFolder = objInfoStore.RootFolder
    
    If Err.Number <> 0 Then
        sMsg = "Error retrieving RootFolder Object: "
        sMsg = sMsg & Err.Number & " " & Err.Description
        WScript.Echo sMsg
        WScript.Echo "Server: " & ServerName
        WScript.Echo "Mailbox: " & UserName
        Set objInfoStore = Nothing
        Set objFolder = Nothing
        Set objSession = Nothing
        Exit Sub
    End If

    'Recurse through the sub-folders
    NavigateFolders objFolder
    
    'Logoff from the session
    objSession.Logoff

    'Clean Up
    Set objFolder = Nothing
    Set objInfoStore = Nothing
    Set objSession = Nothing
End Sub

Sub NavigateFolders(MAPIFolder)
Dim intCounter
Dim intMessageCounter
Dim objMessage

    On Error Resume Next

    WScript.Echo "Folder Name:" & MAPIFolder.Name

    For intMessageCounter = 1 To MAPIFolder.Messages.Count

        'Implement you own logic here
        Set objMessage = MAPIFolder.Messages(intMessageCounter)
      
        Select Case objMessage.Class
            Case CdoMsg
                 Select Case objMessage.Type
                    Case "IPM.Note"
                        WScript.Echo "Message:" & objMessage.Subject
                    Case "IPM.Appointment"
                        WScript.Echo "Appointment:" & objMessage.Subject
                End Select
        End Select
    Next
    
    If MAPIFolder.Folders.Count > 0 Then
        For intCounter = 1 To MAPIFolder.Folders.Count
            NavigateFolders MAPIFolder.Folders(intCounter)
        Next
    End If

    Set objMessage = Nothing
End Sub

The list of mailboxes can be provided via a text file(Datafile). The Datafile contains the aliases of the users(one on each line). So assuming your Datafile is called "Aliases.txt" and is on the C:\, you would run the script as follows:

C:\>Cscript iteratemailboxes.vbs Exchange2003 C:\Aliases.txt

The account that you are logged on the computer with must have permissions on the mailboxes that you are trying to iterate through. You can give the permissions by following the steps in the article below:

How to assign service account access to all mailboxes in Exchange Server 2003
http://support.microsoft.com/kb/821897/

Enjoy!

HOW TO:Alter the "SetSecurity" and "Setup" project to support deployment of a VSTO Add-In for All Users

Why do we need to alter the "SetSecurity" and the "Setup" project? I sincerely suggest you read the following Blog posts and article first, otherwise you will not be able to make sense out of this post.

Deploying your VSTO Add-In to All Users (Part I)

Deploying your VSTO Add-In to All Users (Part II)

Automating the Deployment of Outlook Add-ins by Using Visual Studio Tools for Office Second Edition

Assuming you have gone through the articles above I will now walk you through the changes that need to be made in the "Setup" and the "SetSecurity" project. I am only targeting Office 2007 as of now.

Changes to the "Setup" project:

1) Set InstallAllUsers to TRUE.
2) Set the Version Number to the product’s version number.
3) Type your company name in the Manufacturer property.
4) The completed Registry on Target Machine setting for Office 2007 should look like below:

Registry Settings

Note: The name of the add-in will differ.

Changes to the "SetSecurity" project:

1) Alter the "Install" method to make sure the CAS policy is configured at the Machine level and not at the User Level. Replace the following line:

bool allUsers = String.Equals(allUsersString, "1");

with

bool allUsers = true;


Setting the "allUsers" variable to true makes sure that the CAS policy is created at the Machine level and NOT at the User level.

2) Add the following code to the CaspolSecurityPolicyCreator class in the CaspolSecurityPolicyCreator.cs

//Change the name of the Add-in to suit your needs
private const string userAppLocation = @"Software\Microsoft\Office\12.0\User Settings\OutlookAddIn1";

internal static void IncrementCounter()
{
    RegistryKey instructionKey = null;
    int count = 1;

    try
    {
        instructionKey = Registry.LocalMachine.OpenSubKey(userAppLocation, true);
        object value = instructionKey.GetValue("Count");

        if (value != null)
        {
            if ((int)value != Int32.MaxValue)
                count = (int)value + 1;
        }
        instructionKey.SetValue("Count", count);
    }
    finally
    {
        if (instructionKey != null)
            instructionKey.Close();
    }
}

internal static void RegisterDeleteInstruction()
{
    RegistryKey instructionKey = null;
    try
    {
        instructionKey = Registry.LocalMachine.OpenSubKey(userAppLocation, true);

        if (instructionKey != null)
        {
            //Change the name of the Add-in to suit your needs
            instructionKey.CreateSubKey(@"Delete\Software\Microsoft\Office\Outlook\AddIns\OutlookAddIn1");
        }
    }
    finally
    {
        if (instructionKey != null)
            instructionKey.Close();
    }
}

3) Call the "IncrementCounter" method from the Install method.
4) Call the "IncrementCounter" and the "RegisterDeleteInstruction" methods from the Uninstall method.

That's all the changes that are needed. I have tried the setup on a Windows XP(Office 2007) machine and it WORKS!

HOW TO: Alter the "SetSecurity" project to grant full trust to the add-in installation folder instead of the add-in assembly

Its better late than never, I have been wanting to write this post for a long time now and looks like the time has finally come! This is about adapting the "SetSecurity" project to grant Full Trust to the add-in installation folder instead of just the add-in assembly.

What is "SetSecurity"? This is a sample project that comes with the Deploying Office Solutions Using Windows Installer Version 3 Sample. This project is used to Grant trust to the customization assembly using a custom action. Want to know more? Read the following articles about Deploying Visual Studio 2005 Tools for Office Second Edition Solutions and Granting Permissions.

Deploying Visual Studio 2005 Tools for Office Second Edition Solutions Using Windows Installer (Part 1 of 2)
http://msdn.microsoft.com/en-us/library/bb332051.aspx

Deploying Visual Studio 2005 Tools for Office Second Edition Solutions Using Windows Installer: Walkthroughs (Part 2 of 2)
http://msdn.microsoft.com/en-us/library/bb332052.aspx

How to: Grant Permissions to Folders and Assemblies
http://msdn.microsoft.com/en-us/library/zdc263t0(VS.80).aspx

Why do I need to grant full trust to an add-in installation folder instead of the assembly? If you add-in is simple and contains only one assembly then this is not required. You will need to do this in case you have other assemblies that are referenced in you project and they are deployed in the Installation folder along with your add-in assembly.

If only the add-in assembly is trusted you add-in with throw a security exception when it tries to load the other assemblies because they are not trusted. if we trust the installation folder all the assemblies in that folder are trusted and the add-in works fine.

There is very small change we that we need to make and it need to made to the AddSecurityPolicy method in the "CaspolSecurityPoliceCreator.cs" file.

Below are the changes:

1)Replace the following line:


string arguments = policyLevel + " -q -ag " + parentCodeGroup + " -url \"" + solutionInstallationUrl + "\" Nothing -n \"" + solutionCodeGroupName + "\" -d \"" + solutionCodeGroupDescription + "\"";

With
string arguments = policyLevel + " -q -ag " + parentCodeGroup + " -url \"" + solutionInstallationUrl + "\" FullTrust -n \"" + solutionCodeGroupName + "\" -d \"" + solutionCodeGroupDescription + "\"";

2) Comment out or delete the following try..catch block:

 
// Add the assembly code group. Grant FullTrust permissions to the main assembly.
try
{
    // Use the assembly strong name as the membership condition.
    // Ensure that the assembly is strong-named to give it full trust.
    AssemblyName assemblyName = Assembly.LoadFile(assemblyPath).GetName();
    arguments = policyLevel + " -q -ag \"" + solutionCodeGroupName + "\" -strong -file \"" + assemblyPath + "\" \"" 
           + assemblyName.Name + "\" \"" + assemblyName.Version.ToString(4) + "\" FullTrust -n \"" 
           + assemblyCodeGroupName + "\" -d \"" + assemblyCodeGroupDescription + "\"";

    RunCaspolCommand(frameworkFolder, arguments);
}
catch (Exception ex)
{
    try
    {
        // Clean the solutionCodeGroupName.
        RemoveSecurityPolicy(machinePolicyLevel, solutionCodeGroupName);
    }
    catch { }

    string error = String.Format("Cannot create the security code group '{0}'.", assemblyCodeGroupName);
    throw new Exception(error, ex);
}

These are the only two changes we need to make. The first change grants the solutioninstallationUrl "FullTrust" instead of "Nothing" and the second change deletes the code that grants trust to the add-in assembly.

The rest is covered in the Deploying Visual Studio 2005 Tools for Office Second Edition Solutions Using Windows Installer: Walkthroughs (Part 2 of 2) article mentioned above.

More Posts Next page »
Page view tracker