从 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($_)
}
}
现在它在继续之前等待。
我为 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($_)
}
}
现在它在继续之前等待。