[英]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) 公共异步函数ExecRefreshInNewThread()作为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.