c# - How can I run a macro from a VBE add-in, without Application.Run? -


i'm writing com add-in vbe, , 1 of core features involves executing existing vba code upon clicking commandbar button.

the code unit testing code written user, in standard (.bas) module looks this:

option explicit option private module  '@testmodule private assert new rubberduck.assertclass  '@testmethod public sub testmethod1() 'todo: rename test     on error goto testfail      'arrange:      'act:      'assert:     assert.inconclusive  testexit:     exit sub testfail:     assert.fail "test raised error: #" & err.number & " - " & err.description end sub 

so have code gets current instance of host application object:

protected hostapplicationbase(string applicationname) {     application = (tapplication)marshal.getactiveobject(applicationname + ".application"); } 

here's excelapp class:

public class excelapp : hostapplicationbase<microsoft.office.interop.excel.application> {     public excelapp() : base("excel") { }      public override void run(qualifiedmembername qualifiedmembername)     {         var call = generatemethodcall(qualifiedmembername);         application.run(call);     }      protected virtual string generatemethodcall(qualifiedmembername qualifiedmembername)     {         return qualifiedmembername.tostring();     } } 

works charm. have similar code wordapp, powerpointapp , accessapp, too.

the problem outlook's application object doesn't expose run method, i'm, well, stuck.


how can execute vba code com add-in vbe, without application.run?

this answer links blog post on msdn looks promising, tried this:

public class outlookapp : hostapplicationbase<microsoft.office.interop.outlook.application> {     public outlookapp() : base("outlook") { }      public override void run(qualifiedmembername qualifiedmembername)     {         var app = application.gettype();         app.invokemember(qualifiedmembername.membername, bindingflags.invokemethod, null, application, null);     } } 

but best i'm getting comexception says "unknown name", , outlook.exe process exiting code -1073741819 (0xc0000005) 'access violation' - , blows nicely excel, too.


update

this vba code works, if put testmethod1 inside thisoutlooksession:

outlook.application.testmethod1 

note testmethod1 isn't listed member of outlook.application in vba intellisense.. somehow happens work.

the question is, how make work reflection?

update 3:

i found post on msdn forums: call outlook vba sub vsto.

obviously uses vsto , tried converting vbe addin, ran issues @ work x64 windows register class issue:

comexception (0x80040154): retrieving com class factory component clsid {55f88893-7708-11d1-aceb-006008961da5} failed due following error: 80040154 class not registered

anyway guys answer reckons got working:

start of msdn forum post

i found way! triggered both vsto , vba? clipboard!!

so used clipboard pass messages 1 environment other. here few codes explain trick:

vsto:

'p_procedure procedure name call in vba within outlook  'mobj_ou_userproperty create custom property pass argument vba procedure  private sub p_call_vba(p_procedure string)     dim mobj_of_commandbars microsoft.office.core.commandbars, mobj_ou_explorer outlook.explorer, mobj_ou_mailitem outlook.mailitem, mobj_ou_userproperty outlook.userproperty      mobj_ou_explorer = globals.menu_addin.application.activeexplorer     'i want run when 1 item selected      if mobj_ou_explorer.selection.count = 1         mobj_ou_mailitem = mobj_ou_explorer.selection(1)         mobj_ou_userproperty = mobj_ou_mailitem.userproperties.add("com addin-azimuth", outlook.oluserpropertytype.oltext)         mobj_ou_userproperty.value = p_procedure         mobj_of_commandbars = mobj_ou_explorer.commandbars          'call clipboard event copy         mobj_of_commandbars.executemso("copy")     end if end sub 

vba:

create class explorer events , trap event:

public withevents mpubobj_explorer explorer  'trap clipboard event copy private sub mpubobj_explorer_beforeitemcopy(cancel boolean) dim mobj_mi mailitem, mobj_userproperty userproperty      'make sure 1 item selected , of type mail      if mpubobj_explorer.selection.count = 1 , mpubobj_explorer.selection(1).class = olmail         set mobj_mi = mpubobj_explorer.selection(1)         'check see if custom property present in mail selected         each mobj_userproperty in mobj_mi.userproperties             if mobj_userproperty.name = "com addin-azimuth"                 select case mobj_userproperty.value                     case "example_add_project"                         '...                     case "example_modify_planning"                         '...                 end select                 'remove custom property, keep things clean                 mobj_userproperty.delete                  'cancel copy event.  makes call transparent user                 cancel = true                 exit             end if         next         set mobj_userproperty = nothing         set mobj_mi = nothing     end if end sub 

end of msdn forum post

so author of code adds userproperty mail item , passes function name way. again require boiler plate code in outlook , @ least 1 mail item.

update 3a:

the 80040154 class not registered getting because despite targeting x86 platform when translated code vsto vb.net vbe c# instantiating items, eg:

microsoft.office.core.commandbars mobj_of_commandbars = new microsoft.office.core.commandbars(); 

after wasting several more hours on it, came code, ran!!!

enter image description here

the vbe c# code (from answer make vbe addin answer here):

namespace vbeaddin {     [comvisible(true), guid("3599862b-ff92-42df-bb55-dbd37cc13565"), progid("vbeaddin.connect")]     public class connect : idtextensibility2     {         private vbe _vbe;         private addin _addin;          #region "idtextensibility2 members"          public void onconnection(object application, ext_connectmode connectmode, object addininst, ref array custom)         {             try             {                 _vbe = (vbe)application;                 _addin = (addin)addininst;                  switch (connectmode)                 {                     case extensibility.ext_connectmode.ext_cm_startup:                         break;                     case extensibility.ext_connectmode.ext_cm_afterstartup:                         initializeaddin();                          break;                 }             }             catch (exception ex)             {                 messagebox.show(ex.tostring());             }         }          private void onreferenceitemadded(reference reference)         {             //todo: map types found in assembly using reference.         }          private void onreferenceitemremoved(reference reference)         {             //todo: remove types found in assembly using reference.         }          public void ondisconnection(ext_disconnectmode disconnectmode, ref array custom)         {         }          public void onaddinsupdate(ref array custom)         {         }          public void onstartupcomplete(ref array custom)         {             initializeaddin();         }          private void initializeaddin()         {             messagebox.show(_addin.progid + " loaded in vba editor version " + _vbe.version);             form1 frm = new form1();             frm.show();   //<-- here instantiating form when addin loads vbe ide!         }          public void onbeginshutdown(ref array custom)         {         }          #endregion     } } 

the form1 code instantiate , load vbe ide initializeaddin() method:

namespace vbeaddin {     public partial class form1 : form     {         public form1()         {             initializecomponent();         }          private void button1_click(object sender, eventargs e)         {             call_vba("test");         }          private void call_vba(string p_procedure)         {             var olapp = new microsoft.office.interop.outlook.application();             microsoft.office.core.commandbars mobj_of_commandbars;              microsoft.office.core.commandbars mobj_of_commandbars = new microsoft.office.core.commandbars();             microsoft.office.interop.outlook.explorer mobj_ou_explorer;             microsoft.office.interop.outlook.mailitem mobj_ou_mailitem;             microsoft.office.interop.outlook.userproperty mobj_ou_userproperty;              //mobj_ou_explorer = globals.menu_addin.application.activeexplorer             mobj_ou_explorer = olapp.activeexplorer();              //i want run when 1 item selected             if (mobj_ou_explorer.selection.count == 1)             {                 mobj_ou_mailitem = mobj_ou_explorer.selection[1];                 mobj_ou_userproperty = mobj_ou_mailitem.userproperties.add("jt", microsoft.office.interop.outlook.oluserpropertytype.oltext);                 mobj_ou_userproperty.value = p_procedure;                 mobj_of_commandbars = mobj_ou_explorer.commandbars;                  //call clipboard event copy                 mobj_of_commandbars.executemso("copy");             }         }     } } 

the thisoutlooksession code:

public withevents mpubobj_explorer explorer  'trap clipboard event copy private sub mpubobj_explorer_beforeitemcopy(cancel boolean) dim mobj_mi mailitem, mobj_userproperty userproperty  msgbox ("the mpubobj_explorer_beforeitemcopy event worked!")     'make sure 1 item selected , of type mail      if mpubobj_explorer.selection.count = 1 , mpubobj_explorer.selection(1).class = olmail         set mobj_mi = mpubobj_explorer.selection(1)         'check see if custom property present in mail selected         each mobj_userproperty in mobj_mi.userproperties             if mobj_userproperty.name = "jt"                  'will magic happen?!                 outlook.application.test                  'remove custom property, keep things clean                 mobj_userproperty.delete                  'cancel copy event.  makes call transparent user                 cancel = true                 exit             end if         next         set mobj_userproperty = nothing         set mobj_mi = nothing     end if end sub 

the outlook vba method:

public sub test() msgbox ("will called?") end sub 

very sadly, regret inform efforts unsuccessful. maybe work vsto (i haven't tried) after trying dog fetching bone, willing give up!

never less consolation can find crazy idea in revision history of answer (it shows way of mocking office object model) run office vba unit tests private parameters.

i speak offline contributing rubberduck github project, wrote code same thing prodiance's workbook relationship diagram before microsoft bought them out , included product in office audit , version control server.

you may wish examine code before dismissing entirely, couldn't mpubobj_explorer_beforeitemcopy event work, if can working in outlook might fare better. (i'm using outlook 2013 @ home, 2010 might different).

ps think after hopping on 1 leg in anti-clockwise direction, clicking fingers while rubbing head clockwise like workaround method 2 in kb article have nailed it... nup lost more hair!


update 2:

inside outlook.application.testmethod1 can't use vb classics callbyname method dont need reflection? you'd need set string property "sub/functionnametocall" before calling method containing callbyname specify sub/function call.

unfortunately users required insert boiler plate code in 1 of module's.


update 1:

this going sound really dodgy, since outlooks' object model has clamped down run method resort to... sendkeys (yeah know, work).

unfortunately oapp.gettype().invokemember("run"...) method described below works all office apps except outlook - based on properties section in kb article: https://support.microsoft.com/en-us/kb/306683, sorry didn't know until , found frustrating trying , the msdn article misleading, microsoft has locked it:

enter image description here ** note sendkeys supported , other known way using thisoutlooksession not: https://groups.google.com/forum/?hl=en#!topic/microsoft.public.outlook.program_vba/cq8gf9ssn3g - though sue isn't microsoft pss she would've asked , found out unsupported.


old... below method works office apps except outlook

the problem outlook's application object doesn't expose run method, i'm, well, stuck. answer links blog post on msdn looks promising, tried ... outlook.exe process exits code -1073741819 (0xc0000005) 'access violation'

the question is, how make work reflection?

1) here code use works excel (should work outlook same), using .net reference: microsoft.office.interop.excel v14 (not activex com reference):

using system; using microsoft.office.interop.excel;  namespace consoleapplication5 { class program { static void main(string[] args) {     runvbatest(); }  public static void runvbatest() {     application oexcel = new application();     oexcel.visible = true;     workbooks obooks = oexcel.workbooks;     _workbook obook = null;     obook = obooks.open("c:\\temp\\book1.xlsm");      // run macro.     runmacro(oexcel, new object[] { "testmsg" });      // quit excel , clean (its better use vstocontrib jake ginnivan).     obook.saved = true;     obook.close(false);     system.runtime.interopservices.marshal.releasecomobject(obook);     system.runtime.interopservices.marshal.releasecomobject(obooks);     system.runtime.interopservices.marshal.releasecomobject(oexcel); }  private static void runmacro(object oapp, object[] orunargs) {     oapp.gettype().invokemember("run",         system.reflection.bindingflags.default |         system.reflection.bindingflags.invokemethod,         null, oapp, orunargs);      //your call looks little bit wack in comparison, using instance of app?     //application.gettype().invokemember(qualifiedmembername.membername, bindingflags.invokemethod, null, application, null); } } } } 

2) make sure put macro code in module (a global bas file)..

public sub testmsg()  msgbox ("hello stackoverflow")  end sub 

3) make sure enable macro security , trust access vba project object model:

enter image description here


Comments

Popular posts from this blog

python - pip install -U PySide error -

apache - setting document root in antoher partition on ubuntu -

cytoscape.js - How to add nodes to Dagre layout with Cytoscape -