Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

Saturday, 7 March 2015

Azure AD Cloud silent Authentication with ADAL and TokenCache encryption

The ADAL library was launched a couple of years ago, luckily people from Microsoft like Vittorio Bertocci, have been working hard to have a library capable of doing authentication between native applications, web applications and web services (Web API). ADAL.js is in beta, it is a full working library but there are some issues need to be solved before we can have a robust library (This is when I am writing my article so March 2015) but Vittorio promised me we will have something working in this side by April 2015.
Unfortunately for me, and due to a project we are releasing before April, there is not choice, so I will have to write a wrapper around my Azure Web Api’s and skip Adal.js until we have v1.0. So you will be asking yourself “what is this guy talking about?”.
Ok, let’s go to start from a "simple" scenario, so you can have an  idea of what I am talking about. I have a Web Application (MVC 5) which is talking with few web services (Web API 2’s). All of them are register in Azure AD, so Azure AD acts as a Black Box, and manage the authentication. In this case Azure AD will take care of the MVC Web Application and the Web API’s Authentication.
So where is the problem? Well if I want to be sure that the Web Application and the Web APis share the same authentication, I will have to use refresh tokens, but because the hard work done by Vittorio’s team, now we can use Silent Tokens, so we don’t have to worry about building a whole system to refresh the tokens, but the only place where we need to worry it is about about where to store the Tokens.
It is quite common to store the tokens in Sessions, but there is a small problem with this, you lose the multi-farm factor plus WebApi’s don’t have sessions, so when you have to talk to them becomes an impossible job.
So what is the solution? store the tokens in a persistent area. If we have a native application we could store the token’s in the file system, but if we have a Web Application we could store them in Blob Storage or Databases.
Let’s go to a scenario, when someone, which is not really a user, and it is trying to get our token to access to our farm or MVC application, manage to access to the database or file system. Well, I think we will be in big trouble here. That person will have at least 20 minutes (Time when our token expires) to hack our system.
So… let’s go to make the things more difficult for this person, let’s go to inject encryption in the tokens, with double key encryption, String and Byte Array.
The following example it is a simple call from a MVC Application to a Web API 2 using silent Authentication.
...//## GETTING THE TOKEN TO BE AUTHORISE string userObjectID = ClaimsPrincipal.Current.FindFirst(userSchema).Value; AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new TokenEncryptedDatabaseCache(userObjectID, "Iamakey"); ClientCredential credential = new ClientCredential(clientId, appKey); AuthenticationResult result = authContext.AcquireTokenSilent(resourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));string authnHeader = "Authorization: Bearer " + result.AccessToken; ...


Now… check the parameter we are passing on  AcquireTokenSilent , it is a TokenCache.

This is the model we use to store the data in the database:


public class PerUserWebCache { [Key] public int EntryId { get; set; } public string WebUserUniqueId { get; set; } public byte[] CacheBits { get; set; } public DateTime LastWrite { get; set; } }
And finally this is the beauty! TokenEncryptedDatabaseCache, which encapsulates the encryption. You will need to create a dbcontext to store the model with the data…which I am going to add as well, so you can save some time.

using System;using System.Collections.Generic;using System.Data.Entity;using System.Linq;using System.Web;using System.Web.Caching;using Microsoft.IdentityModel.Clients.ActiveDirectory;using System.Security.Cryptography;using System.IO; public class TokenEncryptedDatabaseCache : TokenCache { private TokenCacheDataContext db = new TokenCacheDataContext(); string User; private PerUserWebCache Cache; private string Key; /// <summary> /// Contsructor for the TokenEncryptedDatabaseCache class /// </summary> /// <param name="user">Current User</param> /// <param name="key">Key for the encription</param> public TokenEncryptedDatabaseCache(string user, string key) { Key = key; User = user; this.AfterAccess = AfterAccessNotification; this.BeforeAccess = BeforeAccessNotification; this.BeforeWrite = BeforeWriteNotification; //## We check if the user is in our database Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); //## If that is the case we keep it in memory //## We decrypt the token this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> /// Method to clean the database /// </summary> public override void Clear() { base.Clear(); foreach (var cacheEntry in db.PerUserCacheList) db.PerUserCacheList.Remove(cacheEntry); db.SaveChanges(); } /// <summary> /// ADAL raise a notification before acces to the cache. /// Notification raised before ADAL accesses the cache. /// This is your chance to update the in-memory copy from the DB, /// if the in-memory version is stale. The token is decrypted. /// </summary> /// <param name="args"></param> void BeforeAccessNotification(TokenCacheNotificationArgs args) { if (Cache == null) { // first time access Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } else { // retrieve last write from the DB var status = from e in db.PerUserCacheList where (e.WebUserUniqueId == User) select new { LastWrite = e.LastWrite }; // if the in-memory copy is older than the persistent copy if (status.First().LastWrite > Cache.LastWrite) //// read from from storage, update in-memory copy { Cache = db.PerUserCacheList.FirstOrDefault(c => c.WebUserUniqueId == User); } } this.Deserialize((Cache == null) ? null : Cache.CacheBits!=null?Decrypt(Cache.CacheBits):null); } /// <summary> ///Notification raised after ADAL accessed the cache. ///If the HasStateChanged flag is set, ADAL changed the content of the cache, ///At this time we encrypt the token and save it. /// </summary> /// <param name="args"></param> void AfterAccessNotification(TokenCacheNotificationArgs args) { // if state changed if (this.HasStateChanged) { Cache = new PerUserWebCache { WebUserUniqueId = User, CacheBits = Encrypt(this.Serialize()), LastWrite = DateTime.Now }; //// update the DB and the lastwrite db.Entry(Cache).State = Cache.EntryId == 0 ? EntityState.Added : EntityState.Modified; db.SaveChanges(); this.HasStateChanged = false; } } void BeforeWriteNotification(TokenCacheNotificationArgs args) { // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry } /// <summary> /// Encription of the token. /// </summary> /// <param name="DataToEncrypt">Data to be encrypted</param> /// <returns>Data encrypted.</returns> private byte[] Encrypt(byte[] DataToEncrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream,aes.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToEncrypt, 0, DataToEncrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } /// <summary> /// Decryption of the token. /// </summary> /// <param name="DataToDecrypt">Data to be decrypted</param> /// <returns>Data decrypted</returns> private byte[] Decrypt(byte[] DataToDecrypt) { PasswordDeriveBytes passwordDeriveBytes = new PasswordDeriveBytes(Key, new byte[] { 0x43, 0x87, 0x23, 0x72 }); MemoryStream memoryStream = new MemoryStream(); Aes aes = new AesManaged(); aes.Key = passwordDeriveBytes.GetBytes(aes.KeySize / 8); aes.IV = passwordDeriveBytes.GetBytes(aes.BlockSize / 8); CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write); cryptoStream.Write(DataToDecrypt, 0, DataToDecrypt.Length); cryptoStream.Close(); return memoryStream.ToArray(); } }

This is the class where we have our DBContext


using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheDataContext : DbContext { public TokenCacheDataContext() : base("TokenCacheDataContext") { } public DbSet<PerUserWebCache> PerUserCacheList { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }


Initializer…

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; public class TokenCacheInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<TokenCacheDataContext> { }

Well, I hope you enjoy, any questions let me know.

Wednesday, 5 October 2011

Updating properties when you save a file from an external program and properties.ListItem.SystemUpdate();

The other day I came across, by accident, with one of my worst nightmares, update the properties of a file when the file is uploaded by an external program (Non Office 2010), in fact it was Adobe Acrobat Reader.

After spending a couple of hours trying to figure out how Adobe Reader works with Sharepoint 2010, I found out that before saving the file it does a checkin so it can add all the properties.

My first approach was to develop an Event Receiver and go ItemAdded(…) so I could add the properties without any problem. When I tried that the item was updated BUT Adobe Reader throw an error saying that the file didn’t exist. Quite weird. So I assumed that Adobe decided to do that because it lost control of the file, at the end of the day I was modifiying the properties with my lovely Event Receiver.

To avoid that error I decided to go to the ItemCheckedIn(…) event and modify my properties, so Adobe should be happy. I used the elegant method properties.ListItem.SystemUpdate() to update the properties, otherwise I was going to receive a similar error, well, it is the first time I use this method, but it worked really well.

properties.ListItem.SystemUpdate(): Updates the database with changes that are made to the list item without changing the Modified or Modified By fields.

Some important notes on using Update vs SystemUpdate.
For Update:  Once you update, the modified by and modified dates change to when the code execute and may show the item as being last modified by "System Account".  The code also works with the item like it has nothing at all to do with the previous version.  User updates, then Code updates.

For SystemUpdate(Optional Boolean value)
If you are stuck trying to update a field for a document library, please be aware of two things in Sharepoint 2007/WSS 3.0.
1. Even if you are NOT using Checked out/checked in, the document still has a SPCheckOutStatus.ShortTerm attached to it for a brief period of time.  Trying to SPListItem.Update or SPFile.Item.Update at this point will likely tell you that the file is locked for editing.  There is a .checkoutexpires property to check if you absolutely must wait for the item to be checked back in.  (e.g. Are they really done with that darn thing?)
2. Even if you do NOT have versioning turned on, you are, in fact, working with a new version of the document.  Use SPListItem.SystemUpdate(false) in order to bypass a short term locked document.

Full Example:
file.Item["Status"] = "Pending";
file.Item.Web.AllowUnsafeUpdates = true;
file.Item.Update() //fails miserably on ItemChanged due to the SPCheckOutStatus.ShortTerm lock.
file.Item.SystemUpdate(false); //This works while the user still has the document open. 
file.Item.Web.AllowUnsafeUpdates = false;

I attach the code just ion case you want to deliver solutions conected to external programs. Remember that this code comes from an Event Receiver:

/// <summary>
/// An item was checked in
/// </summary>
public override void ItemCheckedIn(SPItemEventProperties properties)
{
   try
   {              
      //## Propperty to be updated
      properties.ListItem["client"] = "222222";
      //## Updating with systemUpdate()
      properties.ListItem.SystemUpdate();        
   }
   catch (Exception ex)
   {
      Microsoft.SharePoint.Administration.SPDiagnosticsService diagSvc = Microsoft.SharePoint.Administration.SPDiagnosticsService.Local;
      diagSvc.WriteTrace(0, new SPDiagnosticsCategory("PDFFileUpdater", TraceSeverity.Monitorable, EventSeverity.Error), TraceSeverity.Monitorable, "Writing to the ULS log:  {0}", new object[] { ex.ToString() });
   }
   base.ItemCheckedIn(properties);
}

Sunday, 18 September 2011

How to create a complex Custom Site Definition with Visual Studio 2010 using SPWebProvisioningProvider

In my previous article (How to Create a simple Custom Site Definition with Visual Studio 2010) I built a really simple custom site definition with some code behind. I used a Microsoft article for that, so the project was almost the same. In this part I am going to use the same example, so if you want to follow exactly the same, I recommend you to download the example and work with it.

Having a Custom Site Definition that looks like this (see below) we are going to add a Provisioning Provider plus extra stuff.
image

The first thing we are going to do is to explain exactly what a SPWebProvisioningProvider class is and can do for us.
SPWebProvisioningProvider: Provides a handler for responding to Web site creation.
Microsoft give us a big advise in the MSDN documentation:

Be careful about calling the ApplyWebTemplate method within a Web site provisioning callback. Calling this method inside a provisioning callback that is defined within the same site definition configuration that is being applied can cause an infinite loop. Instead, create two similar site definition configurations within the site definition, one that is visible and one that is hidden. The configuration can then contain a provisioning assembly callback that applies the hidden configuration to Web sites.

I don’t think this definition was enough to understand what you can do with this class, but I am going to give my simple explanation. This class allows you to use existing templates for your new site.
For example if you have fallen in love with the Wiki Template and you don’t want to waste your time building a custom site definition, adding all the stuff that the Wiki template has, just because you want to go home or just want to play a football game with some friends, What do you do?, you call SPWebProvisioningProvider override it and add your code.
How do we do that? simple! by creating a simple class we will add in the Custom Site Definition.


Follow this step by step tutorial based in my previous article (you don’t need it but if you are new in this field it will make the things simpler for you, your dog and your family.


Step 1
Let’s go to create that famous class, so go to the project (netsourcecodeSiteDefinition) right click->Add->Class and call it ProvisioningNSC:
image


Step 2
Copy and paste the code below, as you can see we get the SPWebProvisioningProvider class as base and override one of the methods. As you can see the only thing we are doing is setting the Wiki template. (SPWebSPWebProvisioning.GetData)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace netsourcecodeSiteDefinition
{
    class ProvisioningNSC : SPWebProvisioningProvider
    {
        public override void Provision(SPWebProvisioningProperties props)
        {
            //## Getting the website object
            SPWeb _wWeb = props.Web;       
           
            //## Applying a nice Wiki template       
            _wWeb.ApplyWebTemplate(SPWebTemplate.WebTemplateWIKI);
        }
    }
}

Step 3
The next step will  be to register our class in the site definition file webtemp_netsourcecodeSiteDefinition.xml, if you have a look we have different options there, you can change the icon in the ImageURL, description, title etc. Be sure ProvisionAssembly has the local assembly name. ProvisionClass contains Namespace+YourProvisionFileName.

<?xml version="1.0" encoding="utf-8"?>
<Templates xmlns:ows="Microsoft SharePoint">
  <Template Name="netsourcecodeSiteDefinition" ID="10000">   
    <Configuration 
      ID="0"
      Title="netsourcecodeSiteDefinition"
      Hidden="FALSE"
      ImageUrl="/_layouts/images/CPVW.gif"
      Description="netsourcecodeSiteDefinition"
      DisplayCategory="SharePoint Customizations"
      ProvisionAssembly="$SharePoint.Project.AssemblyFullName$"
      ProvisionClass="netsourcecodeSiteDefinition.ProvisioningNSC">
    </Configuration>
  </Template>
</Templates>

Step 4
Let’s go to modify our onet.xml. We are going to add three more lists

<?xml version="1.0" encoding="utf-8"?>
<Project Title="netsourcecodeSiteDefinition" Revision="2" ListDir="" xmlns:ows="Microsoft SharePoint" xmlns="http://schemas.microsoft.com/sharepoint/">
  <NavBars>
    <NavBar Name="$Resources:core,category_Lists;" Prefix="&lt;table border='0' cellpadding='4' cellspacing='0'&gt;" Body="&lt;tr&gt;&lt;td&gt;&lt;table border='0' cellpadding='0' cellspacing='0'&gt;&lt;tr&gt;&lt;td&gt;&lt;img src='/_layouts/images/blank.gif' id='100' alt='' border='0'&gt;&amp;nbsp;&lt;/td&gt;&lt;td valign='top'&gt;&lt;a id='onetleftnavbar#LABEL_ID#' href='#URL#'&gt;#LABEL#&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;" Suffix="&lt;/table&gt;" ID="1003" />
  </NavBars>
  <Configurations>
    <Configuration ID="0" Name="netsourcecodeSiteDefinition">
      <Lists>
        <List
          FeatureId="00BFEA71-A83E-497E-9BA0-7A5C597D0107"
          Type="107"
          Title="Project Tasks"
          Url="$Resources:core,lists_Folder;/$Resources:core,tasks_Folder;"
          QuickLaunchUrl="$Resources:core,lists_Folder;/$Resources:core,tasks_Folder;/AllItems.aspx" />
        <List
          FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101"
          Type="101"
          Title="$Resources:core,shareddocuments_Title;"
          Url="$Resources:core,shareddocuments_Folder;"
          QuickLaunchUrl="$Resources:core,shareddocuments_Folder;/Forms/AllItems.aspx" />
        <List
          FeatureId="00BFEA71-D1CE-42de-9C63-A44004CE0104"
          Type="104"
          Title="$Resources:core,announceList;"
          Url="$Resources:core,lists_Folder;/$Resources:core,announce_Folder;">
              <Data>
                <Rows>
                  <Row>
                    <Field Name="Title">$Resources:onetid11;</Field>
                    <Field Name="Body">$Resources:onetid12;</Field>
                    <Field Name="Expires"><ows:TodayISO/></Field>
                  </Row>
                </Rows>
              </Data>
        </List>
        <List
          FeatureId="00BFEA71-EC85-4903-972D-EBE475780106"
          Type="106"
          Title="$Resources:core,calendarList;"
          Url="$Resources:core,lists_Folder;/$Resources:core,calendar_Folder;"
          QuickLaunchUrl="$Resources:core,lists_Folder;/$Resources:core,calendar_Folder;/Calendar.aspx"
          EmailAlias="$Resources:core,calendar_EmailAlias;" />
      </Lists>
      <SiteFeatures>
      </SiteFeatures>
      <WebFeatures>
      </WebFeatures>
      <Modules>
        <Module Name="DefaultBlank" />
      </Modules>
    </Configuration>
  </Configurations>
  <Modules>
    <Module Name="DefaultBlank" Url="" Path="">
      <File Url="default.aspx">
      </File>
    </Module>
  </Modules>
</Project>

Step 5
The whole solution should look like this now:
image
As soon as you create the site, this is what you will get!


image


Conclusion: As you can see when you provision a site you can specify anything, you can even add custom lists you already created.



Download the code!
image

Wednesday, 29 April 2009

System.IO.FileNotFoundException: The Web application at http://xxxxx could not be found. Verify that you have typed the URL correctly

Have you have this problem? probably because you try to call the object SPSite from a webservice. I assume you get this error:

System.IO.FileNotFoundException: The Web application at http://panshare-cl01 could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.
at Microsoft.SharePoint.SPSite..ctor(SPFarm farm, Uri requestUri, Boolean contextSite, SPUserToken userToken)
at Microsoft.SharePoint.SPSite..ctor(String requestUrl)
at Service.WebServiceDescription() in c:\Inetpub\wwwroot\SiteManager\App_Code\Service.cs:line 57

It is quite scary...but there is a easy way to fix it.

Basically if you doing this call SPSite mySite = new SPSite(http://panshare-cl01/);
Your webservice that in this case is located in he port 88, in a different pool it will fail because it doesn't have the authentification for SQL Server and as any sharepoint developer knows Sharepoint is a bunch of websites kept in Sql Server, and if this one doesn't run properly Sharepoint becomes useless.


Anyway, the issue is a satabase security problem so in order to solve it, just do the following;


1- Go to IIS
2- Web Sites
3- Default Web Site
4- If the webservice is located here...so to the webservice ,ie: SiteManager
5- Right click->Properties
6- Change the "Application pool" from "DefaultAppPool" to "Sharepoint - 80"


Have a look:

Wednesday, 3 December 2008

Dealing with email events with Outlook and Add-in s

Dealing with email events with Outlook and Add-in s

I have explained already how to place a button in the standard bar. The problem we have now, it is how to capture an evenet when we decide to open, close, send, reply... an email. Microsoft provides the InspectorsClass [Microsoft.Office.Interop.Outlook.InspectorsClass] , this class allows you to capture the "NewInspector" event [InspectorsEvents_NewInspectorEventHandler] . This event is one that is fired when we decide to mess around with one email item.

Once we enter in the event it is time to deal with the item, so basically we have to decide what to do.

I am going to paste 3 classes:
    • Connect.cs
    • OleCreateConverter.cs
    • OutlookMailItemEventArgs.cs
OutlookMailItemEventArgs contains the events that deal with Item itself. What I dod it is create a button main standard toolbar and when we open a new email I attach a button called "Send...". When you click it removes the subject of the Item.

Connect.cs

namespace WSSSaveAsOutlookAddIn
{
using System;
using Extensibility;
using System.Runtime.InteropServices;
using Microsoft.Office.Core;
using System.Reflection;
using System.Windows.Forms;
using System.Collections;



[GuidAttribute("0632A014-721A-4545-8B36-12D7CC4373B4"), ProgId("WSSSaveAsOutlookAddIn.Connect")]
public class Connect : Object, Extensibility.IDTExtensibility2
{
//private WSSSaveAsWord.SaveAs _fSaveAsForm;

private CommandBarButton _cbbToolBarButton;

//## Current email.
private Microsoft.Office.Interop.Outlook.MailItem _msgMailItem;

//## OutLook Explorer.
private Microsoft.Office.Interop.Outlook.Explorer _outlookExplorer;

//## Application
private Microsoft.Office.Interop.Outlook._Application _outlookApplication;

//## Email Item.
private Microsoft.Office.Interop.Outlook.MailItem _miMailItem;

//## InspectorsClass.
private Microsoft.Office.Interop.Outlook.InspectorsClass _insInspectors;

//## This Hashtable holds a reference to the active Inspectors
private Hashtable _ActiveItems = null;

public Connect()
{
//_fSaveAsForm.OfficeProduct = WSSSaveAsWord.SaveAs.OfficeProductType.Outlook;
//_fSaveAsForm = new WSSSaveAsWord.SaveAs();
//_fSaveAsForm.OfficeProduct = WSSSaveAsWord.SaveAs.OfficeProductType.Outlook;
}

public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
{
applicationObject = application;
addInInstance = addInInst;

_outlookApplication = application as Microsoft.Office.Interop.Outlook._Application;

if (connectMode != Extensibility.ext_ConnectMode.ext_cm_Startup)
{
OnStartupComplete(ref custom);
}


}

public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
{
if (disconnectMode != Extensibility.ext_DisconnectMode.ext_dm_HostShutdown)
{
OnBeginShutdown(ref custom);
}
applicationObject = null;

}


public void OnAddInsUpdate(ref System.Array custom)
{
}

private void Mail_Item_Closed(object sender, OutlookMailItemEventArgsConnect.OutlookMailItemEventArgs e)
{
// Remove Item from Collection
_ActiveItems.Remove(e.HashCode);
}


private void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
try
{
object Item = Inspector.CurrentItem;

//## Check the ItemsType
if (Item is Microsoft.Office.Interop.Outlook.MailItem)
{
//## Create a new Item wrapper Object
OutlookMailItemEventArgsConnect.OutlookMailItem _oiMail = new OutlookMailItemEventArgsConnect.OutlookMailItem(Item);

//## Register for the Item Close event
_oiMail.Item_Closed += new OutlookMailItemEventArgsConnect.OutlookMailItemEventHandler(Mail_Item_Closed);

//## remember the Item in Collection
_ActiveItems.Add(_oiMail.HashCode, _oiMail);
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}


public void OnStartupComplete(ref System.Array custom)
{

//## The toolbar is declared
CommandBars oCommandBars;

//## The toolbar where our button will be added is declared
CommandBar oStandardBar;

try
{
//## Selection event.
_outlookExplorer = _outlookApplication.ActiveExplorer();
_outlookExplorer.SelectionChange += new Microsoft.Office.Interop.Outlook.ExplorerEvents_10_SelectionChangeEventHandler(outlookExplorer_SelectionChange);

//### FROM HERE WE HANDLE THE ITEM EVENTS ###
//## Open Item
// Create a new Hashtable Object
_ActiveItems = new Hashtable(25);

// Get the Outlook Inspectors Collection
_insInspectors = (Microsoft.Office.Interop.Outlook.InspectorsClass)_outlookApplication.Inspectors;

// Register for the NewInspector event
_insInspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
//############################################
}
catch(Exception)
{

}

try
{
//## We try to get the bar,
oCommandBars = (CommandBars)applicationObject.GetType().InvokeMember("CommandBars", BindingFlags.GetProperty, null, applicationObject, null);
}
catch (Exception)
{
oCommandBars = (CommandBars)_outlookApplication.ActiveExplorer();

//## Outlook has the CommandBars collection on the Explorer object.
object oActiveExplorer;
oActiveExplorer = applicationObject.GetType().InvokeMember("ActiveExplorer", BindingFlags.GetProperty, null, applicationObject, null);
oCommandBars = (CommandBars)oActiveExplorer.GetType().InvokeMember("CommandBars", BindingFlags.GetProperty, null, oActiveExplorer, null);
}

//## Set up a custom button on the "Standard" commandbar.
try
{
//## Its main toolbar Standard.
oStandardBar = oCommandBars["Standard"];
}
catch (Exception)
{
//## Access names its main toolbar Database.
oStandardBar = oCommandBars["Database"];
}

//## In case the button was not deleted, use the exiting one.
try
{
_cbbToolBarButton = (CommandBarButton)oStandardBar.Controls["Sharepoint Save"];
}
catch (Exception)
{
object omissing = System.Reflection.Missing.Value;

//## The command bar is added into the standar bar.
_cbbToolBarButton = (CommandBarButton)oStandardBar.Controls.Add(1, omissing, omissing, omissing, omissing);

//## The caption is set
_cbbToolBarButton.Caption = "Sharepoint Save";

//## We set the style...We want text and one icon!
_cbbToolBarButton.Style = MsoButtonStyle.msoButtonIconAndCaption;
}

// The following items are optional, but recommended.
//The Tag property lets you quickly find the control
//and helps MSO keep track of it when more than
//one application window is visible. The property is required
//by some Office applications and should be provided.
_cbbToolBarButton.Tag = "Sharepoint Save";

// The OnAction property is optional but recommended.
//It should be set to the ProgID of the add-in, so that if
//the add-in is not loaded when a user presses the button,
//MSO loads the add-in automatically and then raises
//the Click event for the add-in to handle.
_cbbToolBarButton.OnAction = "!";

//## We grab the image from our resources
System.Drawing.Image _imgToolBarImage =Properties.Resources.ToolBarIcon;

//## We make our button visible.
_cbbToolBarButton.Visible = true;

//## We convert from image to IPictureDisp
_cbbToolBarButton.Picture = OleCreateConverter.ImageToPictureDisp(_imgToolBarImage);

//## We set the event
_cbbToolBarButton.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(this._cbbToolBarButton_Click);

object oName = applicationObject.GetType().InvokeMember("Name", BindingFlags.GetProperty, null, applicationObject, null);

// Display a simple message to show which application you started in.
// System.Windows.Forms.MessageBox.Show("This Addin is loaded by " + oName.ToString(), "MyCOMAddin");
oStandardBar = null;
oCommandBars = null;

}


public void OnBeginShutdown(ref System.Array custom)
{
//## Unloading our stuff...
object omissing = System.Reflection.Missing.Value;
_cbbToolBarButton.Delete(omissing);
_cbbToolBarButton = null;

}

private void _cbbToolBarButton_Click(CommandBarButton cmdBarbutton, ref bool cancel)
{
if (this._msgMailItem!=null)
{
//_fSaveAsForm.msgMail = this._msgMailItem;

//_fSaveAsForm.Show();
}

//## Button Clicked
System.Windows.Forms.MessageBox.Show("_cbbToolBarButton was Clicked");
}

private void outlookExplorer_SelectionChange()
{
//## Getting a spare temp file name.
string _sTempFileName = System.IO.Path.GetTempFileName();

//## We check if we have select anything.
if (_outlookExplorer.Selection.Count != 0)
{
try
{
//## Getting the selected email.
Microsoft.Office.Interop.Outlook.MailItem mailItem = _outlookExplorer.Selection[1] as Microsoft.Office.Interop.Outlook.MailItem;

//## It is time to save our stuff...
_msgMailItem = mailItem;
}
catch (Exception ex)
{

}
}
}

private object applicationObject;
private object addInInstance;
}
}

OleCreateConverter.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace WSSSaveAsOutlookAddIn
{
internal class OleCreateConverter
{

[DllImport("oleaut32.dll", EntryPoint = "OleCreatePictureIndirect",CharSet = CharSet.Ansi, ExactSpelling = true, PreserveSig = true)]
private static extern int OleCreatePictureIndirect([In] PictDescBitmap pictdesc, ref Guid iid, bool fOwn,[MarshalAs(UnmanagedType.Interface)] out object ppVoid);

const short _PictureTypeBitmap = 1;

[StructLayout(LayoutKind.Sequential)]
internal class PictDescBitmap
{
internal int cbSizeOfStruct = Marshal.SizeOf(typeof(PictDescBitmap));
internal int pictureType = _PictureTypeBitmap;
internal IntPtr hBitmap = IntPtr.Zero;
internal IntPtr hPalette = IntPtr.Zero;
internal int unused = 0;

internal PictDescBitmap(Bitmap bitmap)
{
this.hBitmap = bitmap.GetHbitmap();
}
}

public static stdole.IPictureDisp ImageToPictureDisp(Image image)
{
if (image == null || !(image is Bitmap))
{
return null;
}

PictDescBitmap pictDescBitmap = new PictDescBitmap((Bitmap)image);
object ppVoid = null;
Guid iPictureDispGuid = typeof(stdole.IPictureDisp).GUID;
OleCreatePictureIndirect(pictDescBitmap, ref iPictureDispGuid, true, out ppVoid);
stdole.IPictureDisp picture = (stdole.IPictureDisp)ppVoid;
return picture;
}


public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
{
Image image = null;
if (pictureDisp != null && pictureDisp.Type == _PictureTypeBitmap)
{
IntPtr paletteHandle = new IntPtr(pictureDisp.hPal);
IntPtr bitmapHandle = new IntPtr(pictureDisp.Handle);
image = Image.FromHbitmap(bitmapHandle, paletteHandle);
}
return image;
}

}
}
OutlookMailItemEventArgsConnect.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.Office.Core;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace OutlookMailItemEventArgsConnect
{

///
/// EventArgs for my event
///

public class OutlookMailItemEventArgs : EventArgs
{
private readonly int _iHashCode = 0;

public int HashCode
{
get { return _iHashCode; }
}

public OutlookMailItemEventArgs(int _iOutlookMailItemEventArgsHashCode)
{
_iHashCode = _iOutlookMailItemEventArgsHashCode;
}
}

///
/// A delegate for my events
///

public delegate void OutlookMailItemEventHandler(object sender, OutlookMailItemEventArgs e);
///
/// This is a wrapper for a MailItemObject
///

public class OutlookMailItem
{

///
/// The "DATA"
///

private Microsoft.Office.Interop.Outlook.MailItem _oiMailItem;

///
/// the Inspector for the Item
///

private Microsoft.Office.Interop.Outlook.InspectorClass _oinsInspector;

///
/// Event, raised when Item is closed
///

public event OutlookMailItemEventHandler Item_Closed;

private int _iID = 0;

///
/// Controls to be modified...
///

private Microsoft.Office.Core.CommandBar _cbarCommandBar = null;
private Microsoft.Office.Core.CommandBarButton _cbarcmdButton = null;

private object _oMissing = System.Reflection.Missing.Value;

///
/// Returns the Hashcode for the Item
///

public int HashCode
{
get {
return _oiMailItem.GetHashCode();
}
}
///
/// The Constructor for the OutlookMailItem
///

public OutlookMailItem(object Item)
{
//## Remember the Object
_oiMailItem = (Microsoft.Office.Interop.Outlook.MailItem)Item;

_iID = _oiMailItem.GetHashCode();

//## Register for Item Open Event
_oiMailItem.Open += new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(_oiMailItem_Open);


}
///
/// Eventhadler for a Mail Open event.
/// Remember this Item in ActiveItems
/// Create Button here and register for button click events
///

private void _oiMailItem_Open(ref bool Cancel)
{

// event isn't needed anymore
_oiMailItem.Open -= new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(_oiMailItem_Open);

// get the Inspector here
_oinsInspector = (Microsoft.Office.Interop.Outlook.InspectorClass)_oiMailItem.GetInspector;

// register for the Inspector events
_oinsInspector.InspectorEvents_Event_Close += new Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(_oinsInspector_InspectorEvents_Event_Close);

// Create the Menu
CreateMenu();

}

///
/// Eventhandler for the INspector close event
///

private void _oinsInspector_InspectorEvents_Event_Close()
{
try
{
// Raise event, to remove us from active items collection
if (Item_Closed != null)
{
Item_Closed(this, new OutlookMailItemEventArgs(_oiMailItem.GetHashCode()));
}

// Cleanup resources
_oinsInspector.InspectorEvents_Event_Close -= new Microsoft.Office.Interop.Outlook.InspectorEvents_CloseEventHandler(_oinsInspector_InspectorEvents_Event_Close);
Marshal.ReleaseComObject(_oinsInspector);

Marshal.ReleaseComObject(_oiMailItem);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}

///
/// Adds the Menu for the MailItem
///

private void CreateMenu()
{
try
{
if (_oinsInspector == null) return;

//## Add our Own CommandBar to MailItem
_cbarCommandBar = (Microsoft.Office.Core.CommandBar)_oinsInspector.CommandBars.Add("OutlookMailItemEventArgsConnect", _oMissing, _oMissing, true);


//## Add my button
_cbarcmdButton = (Microsoft.Office.Core.CommandBarButton)_cbarCommandBar.Controls.Add(Microsoft.Office.Core.MsoControlType.msoControlButton, _oMissing, _oMissing, 1, 1);

_cbarcmdButton.Caption = "Send...";
_cbarcmdButton.Tag = _iID.ToString();
_cbarcmdButton.Style = Microsoft.Office.Core.MsoButtonStyle.msoButtonIconAndCaption;
//## Use one off 2 zilliards Office Icons....
_cbarcmdButton.FaceId = 24;

//## Register for Click event
_cbarcmdButton.Click += new _CommandBarButtonEvents_ClickEventHandler(_cbarcmdButton_Click);

//## Make Menu Visible in TOP of Menus
_cbarCommandBar.Visible = true;
_cbarCommandBar.Enabled = true;

//## Setting the position...
_cbarCommandBar.Position = Microsoft.Office.Core.MsoBarPosition.msoBarTop;

}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}

///
/// Cleans up any resources for shutdown
///

private void CleanUp()
{
try
{

}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
}

///
/// The ResetButton Click eventhandler
///

private void _cbarcmdButton_Click(CommandBarButton Ctrl, ref bool CancelDefault)
{
// Check, if we are in the right MailItem
if (Ctrl.Tag != _iID.ToString()) return;

_oiMailItem.Subject = "";
}
}
}