简体   繁体   English

从PowerShell调用Excel VSTO加载项

[英]Calling Excel VSTO Addin from PowerShell

I have built a VSTO addin for Excel which refreshes a number of PowerQuery workbook connections. 我为Excel构建了一个VSTO加载项,该加载项刷新了许多PowerQuery工作簿连接。 So as to avoid an error blocking the main thread causing a "Cartridge not loaded" error I have to run the main code in another thread. 为了避免错误阻止主线程导致“未加载盒带”错误,我必须在另一个线程中运行主代码。

I am doing this via Async method. 我正在通过异步方法做到这一点。 I also need this to work from the command line so i have exposed the code as a COM visible interface and exposed it in ThisAddIn.vb 我还需要从命令行运行它,因此我将代码公开为COM可见接口,并在ThisAddIn.vb中公开了它。

 Protected Overrides Function RequestComAddInAutomationService() As Object
        If headless Is Nothing Then
            headless = New HeadlessExec()
        End If
        Return headless
    End Function

This is the interface class 这是接口类

Imports System.Data
Imports System.Runtime.InteropServices
Imports log4net
Imports System.Threading.Tasks

<ComVisible(True)>
Public Interface IHeadlessExec
    Function RefreshDIT() As Task(Of Boolean)
    Function GetState() As String
    Function GetStatusDetails() As String
End Interface


<ComVisible(True)>
<ClassInterface(ClassInterfaceType.None)>
Public Class HeadlessExec
    Implements IHeadlessExec

    Private log As ILog
    Private logdir As String = ThisAddIn.logdir


    Sub New()
        'Initialise here

        log = LogManager.GetLogger("HeadlessExec")
        log.Info("Constructor")

    End Sub

    Public Async Function RefreshDIT() As Task(Of Boolean) Implements IHeadlessExec.RefreshDIT

        log.Debug("Start")
        Dim pq As New PowerQueryRefresh

        Dim ExecDIT As Task(Of Boolean) = pq.ExecRefreshInNewThread()
        Dim status As Boolean = Await ExecDIT
        Return status
        log.Debug("End")

    End Function

Public Function GetState() As String Implements IHeadlessExec.GetState

        log.Debug("Start")
        Dim pq As New PowerQueryRefresh
        GetState = pq.GetState
        log.Debug("GetStateVSTO:" & GetState)
        log.Debug("End")
    End Function

    Public Function GetStatusDetails() As String Implements IHeadlessExec.GetStatusDetails

        log.Debug("Start")
        Dim pq As New PowerQueryRefresh
        GetStatusDetails = pq.GetStatusDetails
        log.Debug("GetStatusDetailsVSTO:" & GetStatusDetails)
        log.Debug("End")
    End Function

I am calling this from Powershell via COM as follows - the key part is ExecuteVSTOAdd_DITRefresh :- 我是通过Powershell通过COM调用的,如下所示-关键部分是ExecuteVSTOAdd_DITRefresh:-

Function RunVSTOProc() {

    $error.Clear()
    try {
        $FilePath = GetMostRecentFile($BASEDIR)
        OpenExcelWithFile($FilePath)
        $ret = ExecuteVSTOAdd_DITRefresh

    } catch {
        HandleError($_)
    }

    if ($vstostate -eq "Error"){
        CleanUpExcel
        Exit
    }
    if (!$error){
        # Only save it if we have no errrors
        $newname = NewName($FilePath)
        Write-Host "Saving as $newname"
        $workbook.saveAs($newname)
    }     

    CleanUpExcel

    Write-Host "Completed Running DIT"
}

ExecuteVSTOAdd_DITRefresh ExecuteVSTOAdd_DITRefresh

Function ExecuteVSTOAdd_DITRefresh(){


    try {
        $DITAddin = $global:excel.COMAddins.Item("DITUtility")
        Write-Host "Addin $($DITAddin.ProgID) is connected"

        $autom = $DITAddin.Object
        $CallProc =  $autom.RefreshDIT()
        Write-Host "DIT Refreshed within VSTO"
        $CallProc
    } Catch {
        HandleError($_)
    }

}

This issue is that when RefreshDIT runs Powershell doesn't wait for it to complete. 问题是,当RefreshDIT运行Powershell时,它不会等待它完成。 EDIT :- I had an issue with establishing com automation - NOW - i can see details for $DITAddin and I can see the exposed methods BUT I cannot see the exposed method RefreshDIT - even though i can call it - this one is Async and the others are not Async method. 编辑:-我在建立com自动化时遇到问题-现在-我可以看到$ DITAddin的详细信息,但可以看到公开的方法,但是看不到公开的方法RefreshDIT-即使我可以调用它-这是异步的,其他不是异步方法。 Its also not obvious to me how to call it Async from Powershell so it functions as an Async method. 对我来说,如何从Powershell调用Async也并不明显,因此它可以作为Async方法使用。 Any pointers? 有指针吗?

    $DITAddin | Get-Member


   TypeName: System.__ComObject#{000c033a-0000-0000-c000-000000000046}

Name        MemberType Definition                        
----        ---------- ----------                        
Application Property   IDispatch Application () {get}    
Connect     Property   bool Connect () {get} {set}       
Creator     Property   int Creator () {get}              
Description Property   string Description () {get} {set} 
Guid        Property   string Guid () {get}              
Object      Property   IDispatch Object () {get} {set}   
Parent      Property   IDispatch Parent () {get}         
ProgId      Property   string ProgId () {get}            

$autom | Get-Member


   TypeName: System.__ComObject#{159faa2b-4a8e-3bca-bb69-e2268f06d436}

Name             MemberType Definition                
----             ---------- ----------                
GetState         Method     string GetState ()        
GetStatusDetails Method     string GetStatusDetails ()

If I run 如果我跑步

$CallProc =  $autom.RefreshDIT()


       $CallProc | Get-Member


   TypeName: System.__ComObject

Name                      MemberType Definition                                                     
----                      ---------- ----------                                                     
CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Equals                    Method     bool Equals(System.Object obj)                                 
GetHashCode               Method     int GetHashCode()                                              
GetLifetimeService        Method     System.Object GetLifetimeService()                             
GetType                   Method     type GetType()                                                 
InitializeLifetimeService Method     System.Object InitializeLifetimeService()                      
ToString                  Method     string ToString

() ()

There is no Run() method and if I try and execute it i get 没有Run()方法,如果我尝试执行它,我会得到

    $CallProc.Run()
    Method invocation failed because [System.__ComObject] does not contain a method named 'Run'.
    At line:1 char:1
    + $CallProc.Run()
    + ~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
        + FullyQualifiedErrorId : MethodNotFound


That failed with ERROR ExecuteVSTOAdd_DITRefresh  : 
RunDIT_VSTO.ps1:164 char:9
+         [System.Threading.Tasks.Task]$tskRefreshDIT = $autom.RefreshD ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException

Solved the problem. 解决了问题。 The Async method wasn't displaying in Powershell when performing a Get-Member on the object but the NON Async methods were. 对对象执行Get-Member时,Async方法未在Powershell中显示,但NON Async方法显示了。

I already had a an async function with an Await statement in VB.NET so i wrapped it a function without the Async modifier and called that:- 我已经在VB.NET中有一个带有Await语句的异步函数,所以我将其包装为一个没有Async修饰符的函数,并称之为:

This in the main body of the code:- 在代码主体中:

Public Async Function ExecRefreshInNewThread() As Task(Of Boolean) 公共异步函数Exec​​RefreshInNewThread()作为Task(布尔值)

    Dim msg As String

    Try
        Dim tasks As New List(Of Tasks.Task)()
        tasks.Add(Task.Run(AddressOf RefreshSequenceOfConnectionsH))
        Await Task.WhenAll(tasks)
        log.Info("Executed without error")
        Return True
    Catch e As Exception
        msg = FormatExceptionMsg(e)
        log.Error(msg)
        Return False
    End Try

End Function


Public Function ExecRefreshInNewThread_v2() As Boolean

    Dim boo As Task(Of Boolean) = ExecRefreshInNewThread()
    Return boo.Result
End Function

This in the interface class:- 在接口类中:

Public Function RefreshDITv2() As Boolean Implements IHeadlessExec.RefreshDITv2

        log.Debug("Start")
        Dim pq As New PowerQueryRefresh

        Dim ExecDIT As Boolean = pq.ExecRefreshInNewThread_v2

        Return ExecDIT
        log.Debug("End")

    End Function

Then this worked in Powershell:- 然后这在Powershell中起作用:

Function ExecuteVSTOAdd_DITRefresh(){


    try {
        $DITAddin = $global:excel.COMAddins.Item("DITUtility")
        Write-Host "Addin $($DITAddin.ProgID) is connected"

        $autom = $DITAddin.Object
        $tskRefreshDIT = $autom.RefreshDITv2()
        Write-Host "DIT Refreshed within VSTO $CallProc"
        $tskRefreshDIT
    } Catch {
        HandleError($_)
    }

}

Now it waits before moving on. 现在它等待继续前进。

Try using the Wait method of the task from your async RefreshDIT function: 尝试使用异步RefreshDIT函数中的任务的Wait方法

$tskRefreshDIT = $autom.RefreshDIT()
$bolSuccess = $tskRefreshDIT.Run()
$bolSuccess = $task.Wait(60000)

if ($bolSuccess -eq $true) {
    $CallProc
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM