I am developing a simple Add-In for Outlook with C#. Now as I was testing the release, suddenly Outlook print an error message and disables my Add-In:
This add-in caused Outlook to start slowly. (1.594 seconds)
I am not sure what causes this. All I do Onload are these this:
Ribbon Button
I am using a single ribbon button which I initialize in my AddIn as follows:
protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
return new MyRibbonButton();
}
Ribbon Button constructor
The ribbon button initialize an object. So nothing special yet.
public MyRibbonButton()
{
this.guiSettings = new AppSettingsManager(root.localmachine, "GUI", false);
}
...
public AppSettingsManager(root type, string subpath, bool writable)
{
if (subpath != "")
{
this.PATH += #"\" + subpath;
}
this.type = type;
this.writable = writable;
}
Icon
In the same MyRibbonButton class I declare the icon I want to use for my button depending on a value in the registry.
public Bitmap imageSuper_GetNotifyImage(IRibbonControl control)
{
switch (guiSettings.GetValueInt32("Icon", 1))
{
case 1:
return Properties.Resources.icon1;
case 2:
return Properties.Resources.icon2;
case 3:
return Properties.Resources.icon3;
default:
return Properties.Resources.icon1;
}
}
As you can see I don't do anyting special except maybe the Registry Read in the last part. Do you think this causes Outlook to start slowly? If so, how can I optimize it.
You get penalized for loading the .Net system. There used to be a "warmup" registry key which could be used to force Outlook to load the right version of the .Net run-time without being penalized, but that key no longer works.
The only way I was able to work around that problem is to create a stub addin in Delphi (C++ would work just as well if not better) that did nothing but get loaded by Outlook (in about 20ms) and start a timer. When the timer fires (Outlook would be idle and not monitoring the addin), it would load the slave .Net addin using the IManagedAddin interface.
Related
A client of my outlook add-in started complaining that the add-in does not start at Outlook startup. I took a look, and each time that Outlook is started, the LoadBehaviour is set to 0.
The add-in is written as a managed COM Add-in (sorry, can't change it - legacy tool) with C# 4.0.
It is registered on HKLM for all users.
The client is running Outlook 365 ProPlus Version 1708 (build 8431.22.42 click-to-run) 32 bit (16.0.84.31.2110)
The client machine is Windows 10 64bit.
The HKCU level LoadBehaviour key that keeps getting reset to 0 is generated on the Software\Microsoft\Office.. node (as opposed to under the Software\Wow6432Node...)
The add-in appears under the 'Inactive Application Add-ins' list on Outlook but will not enable through the interface (you can set it, but it does not enable).
Add-in marked as 'always enable' in "slow and disabled COM-add-ins" list.
This is the only Add-in loading through mscoree.dll listed on the Add-in list (I know if there was another mscoree.dll Add-in and that gets disabled, then ours would be disabled as well).
I change the HKCU LoadBehaviour to 3 manually through RegEdit, and it loads the Add-in the next time I open Outlook, but on next restart of Outlook the Add-in is disabled and LoadBehaviour set to 0 once more.
No other client seems to be experiencing the issue, and I am not able to replicate the issue on a local instance of Outlook 365 either.
As always, any help is greatly appreciated!
Cheers!
------EDIT----------
As requested, following is an outline of the actions happening on the OnStartupComplete method of the add-in.
The add-in is meant to provide an Outlook interface for users using the mail-like correspondence exchange component of our larger SaaS service. The add-in connects the user to our server, where they have mail-like items (html formatted) received from other users. The add-in will create a local PST file and create mail items within that PST with the HTML & attachments set as the content. The add-in will also detect if the user is attempting to reply/fwd one of the created 'mails' and override the default process in order to display a web page from the server. The server sync process is triggered at the same frequency as the Outlook Send/receive by adding a handler to the Session.SyncObjects[1].SyncStart event. the sync process itself is handled in a separate BackgroundWorker process.
public void OnStartupComplete()
{
if (_applicationObject == null)
return;
try
{
if (_applicationObject.ActiveExplorer() == null)
return;
_version = _applicationObject.Version.Split(new string[] { "." }, System.StringSplitOptions.RemoveEmptyEntries);
StaticGlobals.OutlookVersionString = _applicationObject.Version;
StaticGlobals.OLIVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version ;
StaticGlobals.OLIFileVersionString = FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).ProductVersion;
//This class has all the processes of my add-in process.
_addinControlObj = new AddInControlerClass();
//Get references to application, explorer objects
_addinControlObj.ApplicationObject = _applicationObject;
_explorers = _addinControlObj.ApplicationObject.Explorers;
//tracks creation of new explorer & inspector windows in order to override reply/fwd actions on specific scenarios (it provides extra options for the user before opening mail editor)
_explorers.NewExplorer += this.Open_NewExplorer;
_currentExplorer = _addinControlObj.ApplicationObject.ActiveExplorer();
_addinControlObj.ApplicationObject.Inspectors.NewInspector += this.Open_NewInspector;
//Create redemption object
_rdoSession = Redemption.RedemptionLoader.new_RDOSession();
_rdoSession.MAPIOBJECT = _addinControlObj.ApplicationObject.Session.MAPIOBJECT;
((Microsoft.Office.Interop.Outlook.ApplicationEvents_11_Event)_applicationObject).Quit += ReleaseComObjects;
((Microsoft.Office.Interop.Outlook.ApplicationEvents_11_Event)_applicationObject).ItemSend += SendMail;
}
catch (System.Exception ex)
{
Common.HandleErrors(ex, "On Connection", false, null, "", StaticGlobals.OutlookVersionString);
}
try
{
OutlookCommands olCommands = new OutlookCommands();
//Get PST file name with Outlook user name suffixed. Common.GetAppPath() will point to a sub folder within either %UserProfile%\AppData\Local or user pre-defined location
_addinControlObj.FilePath = Path.Combine(Common.GetAppPath(), olCommands.GetFileNameUserSpecific(_addinControlObj.ApplicationObject, "addinCreatedDataFileName.pst"));
_addinControlObj.CreateProjectConnections(); // connects to the server to retreive access token for API
}
catch (System.Exception ex)
{
Common.HandleErrors(ex, "On startup complete - Get and validate proj details", true, null, "", StaticGlobals.OutlookVersionString);
}
try
{
//region create PST store and sub-folders
_addinControlObj.CheckAndCreateFolders();
}
catch (System.Exception ex)
{
Common.HandleErrors(ex, "On startup complete - Check and create folders", true, null, "", StaticGlobals.OutlookVersionString);
}
#region add add-in sync method to Outlook send/receive all object
try
{
if (_addinControlObj.ApplicationObject.Session.SyncObjects.Count > 0)
{
_syncObject = _addinControlObj.ApplicationObject.Session.SyncObjects[1];
_syncObject.SyncStart += _syncObject_SyncStart;
}
else
{
Common.HandleErrors(null, "On startup complete - Warning - No SyncObjects accounts found.", false, null, "", StaticGlobals.OutlookVersionString);
}
}
catch (System.Exception ex)
{
Common.HandleErrors(ex, "On startup complete - Add to sync objects", true, null, Properties.Resources.SyncAttachFailMessage, StaticGlobals.OutlookVersionString);
}
#endregion
#region Define the functions for the attachment size limitation.
try
{
_attachmentSizeLimitation.application = _applicationObject;
_attachmentSizeLimitation.sessionRDO = _rdoSession;
_attachmentSizeLimitation.connect = this;
_attachmentSizeLimitation.DefineFunctions();
}
catch (System.Exception ex)
{
Common.HandleErrors(ex, "On startup complete - Initialize attachment limitation.", false, null, "", StaticGlobals.OutlookVersionString);
}
#endregion
}
To start off, I don't think anyone can give a direct answer without some code or error. But maybe some extra checks can help you out.
Add-ins are disabled here in the registry, under this node:
Computer\HKEY_CURRENT_USER\Software\Microsoft\Office\1x.0\Outlook\Resiliency\DisabledItems
(your add-in should not be listed here!)
What might happen is that the add-in crashes while closing.
Sometimes Outlook does not show you a crash/error, to change that behavior this is really helpful:
Set 'VSTO_SUPPRESSDISPLAYALERTS' with value '0' as a system variable
under system > advanced > environment variables,.
https://www.oneplacesolutions.com/support/0053.html
In case the add-in is slow at start-up take a look at, as the Outlook resiliency logic might cause thing behavior.
https://blogs.msdn.microsoft.com/emeamsgdev/2017/08/02/outlooks-slow-add-ins-resiliency-logic-and-how-to-always-enable-slow-add-ins/
I am building a PowerPoint 2010 C# add-in using Visual Studio 2010. One of the functions of the add-in is to add a shape to the current slide. Once the shape is added to the slide though, I need to prevent it from being copied. That is where I am running into issues. I have looked at all the application level events and am not seeing any sort of beforeCopy or beforePaste type of events.
The only option I can think of right now is to add a keydown event listener to listen for "ctrl+c" and block that if my shape is selected and then create a custom right click menu (not even sure if I can yet) to remove the "Copy" option if my shape is selected. There has to be simpler option though.
Anyone have any ideas how I would prevent a user from copying a shape?
The commands executed by built-in ribbon buttons Microsoft Office can be disabled or re-routed. Microsoft calls this "Repurposing", an introduction can be found here.
So another approach could be to "repurpose" the built-in Copy button with something like this. (Needs to be returned by GetCustomUI to customize the ribbon, see the link above.) This modifies the action executed by the Copy button and the callback method that determines whether the button is enabled or not.
<command idMso="Copy" onAction="copyAction" getEnabled="copyEnabled" />
Implement copyAction to return cancelDefault = true when your shape is selected so it will not be copied.
Implement copyEnabled to return false if your shape is selected. Remember to invalidate the button on selection change events.
Actually, one of both approaches should be sufficient. I guess onAction is easier to implement.
Just to close the loop on this, I am sharing my work-around in the hopes that someone else who has this issue will not waste as much time as I have on this. I ended up just using the SlideSelectionChanged and WindowSelectionChange events and a dictionary to delete my objects that have been coppied.
First, when my shape is added to the stage I add a new entry into the dictionary containing the shape name (in my case it was actually a group of shapes) and its ID.
itemIDDictionary.Add(myGroup.Name, myGroup.Id);
WindowSelectionChange is a fairly simple check. It just looks to see if the newly selected item is in the dictionary already. If it is, it then checks to see if the ID matches. If not, it deletes the item. This works because when you copy and paste an item, the newly pasted item is automatically selected on the slide.
public void itemSelectionChange(PowerPoint.Selection SelectedItem)
{
try
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SelectedItem.ShapeRange.Name))
{
for (int shapeIDCount = 0; shapeIDCount < Globals.Ribbons.Ribbon2.itemIDDictionary.Count; shapeIDCount++)
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SelectedItem.ShapeRange[1].Id))
{
SelectedItem.Delete();
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
}
catch {}
SlideSelectionChanged is just a little bit more complicated as I have to loop through all the shapes on the slide.
try
{
if (SldRange.Count > 0)
{
var showWarning = false;
for (int slideCount = 1; slideCount <= SldRange.Count; slideCount++)
{
int shapeCount = 1;
while (shapeCount <= SldRange[slideCount].Shapes.Count)
{
if (Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsKey(SldRange[slideCount].Shapes[shapeCount].Name))
{
if (!Globals.Ribbons.Ribbon2.itemIDDictionary.ContainsValue(SldRange[slideCount].Shapes[shapeCount].Id))
{
SldRange[slideCount].Shapes[shapeCount].Delete();
showWarning = true;
}
else
{
shapeCount++;
}
}
else
{
shapeCount++;
}
}
}
if(showWarning == true)
{
MessageBox.Show("You can not copy the browser object.\nAdd a new one using the ribbon bar");
}
}
}
catch { }
As I said in my initial post, I am sure there is a cleaner way to do this. I just couldn't find one to save my life.
In fact, I have a C# application that is runned.
When it's launched, it runs an excel instance and save it to a variable :
excelApp = new Excel.Application();
Then I cycle through each of the excel's addins to find my own created added :
private void getAddin()
{
const string addinName = "myAddInName";
foreach (Office.COMAddIn addin in excelApp.COMAddIns)
if (addin.Description.ToUpper().Contains(addinName.ToUpper()))
{
myAddin = addin;
return;
}
}
The problem is that, it may happens that my plugin is in the deactivated elements list. (Due to some crash)
Then I must go to (on excel's frame) :
File >Options >AddIns >Manage >Deactivated elements >Achieve >"myAddin" >Activate
to get my addin working again (after excel's restart)...
I tried using an addin's property to reactivate it but may app crashes again in that case.
myAddin.Connect = loadAddin;
Someone has an idea on how to resolve it / auto-reactivate a disabled plugin using C# ?
This should help
http://msdn.microsoft.com/en-us/library/aa662931(v=office.11).aspx
I believe you need to do
if(!myAddin.Connect)
{
myAddin.Connect =true;
}
I'd like to create an Excel Add In. But I want it to have a special behavior.
Then problem is that the Excel Add In I developed stays within Excel. Even when I run a normal Excel instance from Windows...
So what I want, to be more precise, is to have an Excel add in that will only appear in the Excel's ribbon when launched by my own C#_made application.
How should I do that ?
The behavior would be :
Run my C# application
My C# application calls a new Excel's instance
My C# application adds a new tab (and its elements) to the ribbon of this Excel's instance
My C# application adds actions binded to the tab's elements
Excel's instance is being closed > Remove all added components, functions, methods, ...
Close my C# appliclation
Here's a nice tutorial for you: http://www.add-in-express.com/free-addins/net-excel-addin.php
Edit:
Have you considered just disabling the addin, then reenabling it whenever you launch the app with a server that runs in the background and when excel is closed, disables the addin?
Here's some unload code I found here:
private void UnloadBadAddins(bool unloadAddin)
{
const string badAddin = "iManage Excel2000 integration (Ver 1.3)";
foreach(Office.COMAddIn addin in this.ExcelApp.COMAddIns)
{
if (addin.Description.ToUpper().Contains(badAddin.ToUpper()))
{
if (addin.Connect == unloadAddin)
{
addin.Connect = !unloadAddin;
return;
}
}
}
}
I have found the following two properties on the Microsoft.Office.Interop.Excel.Application class:
var excel = new Application();
excel.AddIns
excel.AddIns2
Maybe these can help you programmatically add/remove addins during your application run?
I know that I can display a PDF file in my c# executable (not web app) with:
private AxAcroPDFLib.AxAcroPDF axAcroPDF1;
axAcroPDF1.LoadFile(#"somefile.pdf");
axAcroPDF1.Show();
But that is the regular pdf viewer like in the browser. I don't want that. I want full Adobe Standard or Professional functionality in my C# application using the Adobe controls. For example, if I use the code above, it loads in the C# app and I can see the adobe toolbar (print, save, etc.) But it is useless to me because I need things like save which cannot be done with the activex viewer above. Specifically, you cannot save, just as you cannot within the broswer.
So, I referenced the acrobat.dll and am trying to use:
Acrobat.AcroAVDocClass _acroDoc = new Acrobat.AcroAVDocClass();
Acrobat.AcroApp _myAdobe = new Acrobat.AcroApp();
Acrobat.AcroPDDoc _pdDoc = null;
_acroDoc.Open(myPath, "test");
pdDoc = (Acrobat.AcroPDDoc)(_acroDoc.GetPDDoc());
_acroDoc.SetViewMode(2);
_myAdobe.Show();
It opens adobe acrobat but it opens it outside of my c# application. I need it to open in my c# application like the activex library does. Can it be done with these libraries?
If I cannot open it in my c# application I would like to be able to "hold" my c# app tied to it so the c# app knows when I close the adobe app. At least that way I'd have some measure of control. This means I would hit open, the adobe app opens. I close the adobe app, my C# app is aware of this and loads the newly changed doc with the activex library (because I don't need change ability anymore, just displaying.)
I have the full versions of adobe acrobat installed on my computer. It is not the reader.
Thank you for any help.
edit:
There is an example in vb in the adobe acrobat sdk. I believe it is called activeview.
you can check out ABCpdf. I dont know if it has this capability but we have used it for several of our apps
Using a webbrowser control would be an option to display the content.
IText# may help you out.
You can create PDF's and I believe you can use it to read and modify them.
As for displaying in the app..... I am not sure how to display them with iText or if it is possible (have not tried this yet), sorry. iText does let you convert to RTF which may be one approach.
Best option is to write a listener which tells your calling code when Adobe.exe is no longer running. Something like the following (with tweaks for your uses) should work:
public void Open(string myPath)
{
Acrobat.AcroAVDocClass _acroDoc = new Acrobat.AcroAVDocClass();
Acrobat.AcroApp _myAdobe = new Acrobat.AcroApp();
Acrobat.AcroPDDoc _pdDoc = null;
_acroDoc.Open(myPath, "test");
_pdDoc = (Acrobat.AcroPDDoc) (_acroDoc.GetPDDoc());
_acroDoc.SetViewMode(2);
_myAdobe.Show();
NotifyAdobeClosed += new EventHandler(Monitor_NotifyAdobeClosed);
MonitorAdobe();
}
private void Monitor_NotifyAdobeClosed(object sender, EventArgs e)
{
NotifyAdobeClosed -= Monitor_NotifyAdobeClosed;
//Do whatever it is you want to do when adobe is closed.
}
private void MonitorAdobe()
{
while(true)
{
var adcount = (from p in Process.GetProcesses()
where p.ProcessName.ToLower() == "acrobat"
select p).Count();
if (adcount == 0)
{
OnNotifyAdobeClosed();
break;
}
}
}
public event EventHandler NotifyAdobeClosed;
public void OnNotifyAdobeClosed()
{
if (NotifyAdobeClosed != null)
NotifyAdobeClosed(this, null);
}