从 PowerShell 调用 Excel VSTO 插件

Calling Excel VSTO Addin from PowerShell

我为 Excel 构建了一个 VSTO 插件,它刷新了一些 PowerQuery 工作簿连接。为了避免错误阻塞主线程导致 "Cartridge not loaded" 错误,我必须 运行 另一个线程中的主代码。

我正在通过异步方法执行此操作。 我还需要它在命令行中工作,所以我将代码公开为 COM 可见界面并将其公开在 ThisAddIn.vb

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

这是界面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

我通过 COM 从 Powershell 调用它,如下所示 - 关键部分是 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

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($_)
    }

}

这个问题是 RefreshDIT 运行s Powershell 没有等待它完成。编辑:- 我在建立 com 自动化时遇到问题 - 现在 - 我可以看到 $DITAddin 的详细信息,我可以看到公开的方法但我看不到公开的方法 RefreshDIT - 即使我可以调用它 - 这个是异步的,其他不是异步方法。对于我来说,如何从 Powershell 中调用它 Async 也不是很明显,因此它可以用作 Async 方法。有什么指点吗?

    $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 ()

如果我运行

$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

()

没有 运行() 方法,如果我尝试执行它,我会得到

    $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

尝试使用异步 RefreshDIT 函数中任务的 Wait method

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

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

问题解决了。 对对象执行 Get-Member 时,Powershell 中未显示异步方法,但非异步方法显示。

我在 VB.NET 中已经有一个带有 Await 语句的异步函数,所以我将它包装成一个没有 Async 修饰符的函数并调用它:-

代码主体中的这个:-

Public 异步函数 ExecRefreshInNewThread() 作为任务(布尔值)

    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

界面中的这个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

然后这在 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($_)
    }

}

现在它在继续之前等待。