swbemobjectex class ghost/hidden methods (eg. .Terminate) - excel

Dim objList As WbemScripting.SWbemObjectSet
Set objList = GetObject("winmgmts:").ExecQuery("select * from win32_process where name='iexplore.exe'")
This code returns a collection of SWbemObjectEx objects
relating to all the running processes "iexplore.exe" (as seen in task manager).
I read on the web that I can run the method .Terminate of these objects to kill them.
However, neither the Locals window while in breakmode nor the Object Browser for the "SWbemObjectEx" class, nor the official doc at https://learn.microsoft.com/it-ch/windows/win32/wmisdk/swbemobjectex, show this method .Terminate.
And what surprises me is that it works, although not for all the objects...
Why? and how could I see all these hidden(?) methods for this class?

try this code:
Option Explicit
Sub funTaskTerminate()
Dim objTask As Object
Dim objProcesses As Object
Dim objProcess As Object
' Set the object for Task Manager
Set objTask = GetObject("winmgmts:")
' Set the object for all the processes in query
Set objProcesses = objTask.ExecQuery("select * from win32_process where name='iexplore.exe'")
' Loop for all processes in query
For Each objProcess In objProcesses
' In my test, there were 3 processes, but the first Terminate () killed all the others, therefore,
' in the second Terminate () the process no longer existed,
' which caused an error, so the use of "Resume Next".
'------------------------------------------
On Error Resume Next
Call objProcess.Terminate ' Terminate the process
On Error GoTo 0
Next
Set objTask = Nothing
Set objProcesses = Nothing
Set objproces = Nothing
End Sub

The .Terminate method is part of the Win32_Process class. Put a breakpoint onCall objProcess.Terminateand add a watch onobjProcess. That will show you a.Methods_collection, where second item is.Terminate.
Also note the.Properties_collection to get infos on that process (e.g handle).
This sub prints the collections to the immediate window, just call it inside the loop throughobjProcesses, e.g.
...
For Each objProcess In objProcesses
PrintPropertiesAndMethods objProcess
...
Private Sub PrintPropertiesAndMethods(Process As WbemScripting.SWbemObjectEx)
With Process
Debug.Print vbCrLf & "Properties_ collection:"
Dim Prop As WbemScripting.SWbemProperty
For Each Prop In .Properties_
With Prop
Debug.Print .Name & " " & .Value
End With
Next
Debug.Print vbCrLf & "Methods_ collection:"
Dim Method As WbemScripting.SWbemMethod
For Each Method In .Methods_
With Prop
Debug.Print Method.Name
End With
Next
End With
End Sub
You have to be carefull when terminateiexploror.exeas it has a main process (x64 - C:\Program Files\Internet Explorer), that creates a child process (x86 - C:\Program Files (x86)\Internet Explorer) for each tab (check in task-manager). If you terminate the main process, all childs will terminate too. Too get a second main process for testing, use Run as Admin.

Related

Writing from Excel, to Word Activex Text box

I have an Excel application which collects information via a form based interface. This is used to;
Fill values in the workbook
A procedure opens a Word document (template essentially) and names the file according to rules, based
on some of the input data. (Okay to this point)
The idea then, is to transfer collected information (from the Excel app driving this process) to
the same Word document opened and named.
Specifically, I intend to populate a number of uniquely named ActiveX textboxes with document.
*** This is where I fail miserably.
I have enabled the "Microsoft Word 16.0 Object Library" under references in the MSExcel VBA environment.
Given that I know the name/title of the content control (ActiveX textbox is a 'content control' isn't it?). The code below is a simplified example, if it works for the example I should be able to sort out the broader document:
Sub trial()
Dim Word As Word.Application
Dim wdDoc As Word.Document
On error resume next
Set Word = New Word.Application
Set wdDoc = Word.Documents.Open("G:\CAPS Management Tool\Customer.docm")
Word.Application.Visible = True
Dim cc As Object
Set cc = ActiveDocument.SelectContentControlsByTitle(txt_PersonName) 'txt_PersonName is the control name
cc.Range.Text = "SUCCESS" 'Run-time error 438
'Object does not support property or method
Set cc = ActiveDocument.SelectContentControlsByTitle(txt_Address) 'txt_Address is the control name
cc.Range.Text = "SUCCESS" 'Run-time error 438
'Object does not support property or method
End Sub
Anybody able to assist? There are a lot of text boxes in the Word document I wish to plug in to.
Thanks in advance.
OK, so I kept digging (I don't like accepting defeat) and found that my entire premise was wrong! ActiveX controls in Word are considered "InlineShapes" not "ContentControls". But the results I was reading in the internet searches had me confused (didn't claim to be the sharpest tool in the shed).
Once I realised this, some more digging provided the answer (see below).
So, first to list the 3 controls in my document (and their index) with the following Sub
Sub ListActiveXControls()
Dim i As Integer
i = 1
Do Until i > ActiveDocument.InlineShapes.Count
Debug.Print ActiveDocument.InlineShapes(i).OLEFormat.Object.Name & " Control Index = " & i
i = i + 1
Loop
End Sub
Now moving to EXCEL, I used the following:
Sub trial()
Dim Word As Word.Application
Dim wdDoc As Word.Document
Set Word = New Word.Application
Set wdDoc = Word.Documents.Open("G:\CAPS Management Tool\Customer.docm")
Word.Application.Visible = True
debug.print "ActiveDocument Name is : " & ActiveDocument.Name
' Result = Nothing
' Allowing the code to continue without the pause caused the operation to fail
Application.Wait (Now + TimeValue("0:00:10")) ' See text below, would not work without pause
wdDoc.Activate
' Begin set ActiveX control values. In this instance,
' The first line corresponds to 'Textbox1'
ActiveDocument.InlineShapes(1).OLEFormat.Object.Text = "Success"
' The second line corresponds to 'Textbox2'
ActiveDocument.InlineShapes(2).OLEFormat.Object.Text = "Success"
' The third line corresponds to 'ChkBox1'
ActiveDocument.InlineShapes(3).OLEFormat.Object.Value = True
End Sub
For some reason, without the 'Wait' command, the operation fails. Stepping through, if there is no pause, the ActiveDocument seems to be null, no idea why. It occurs with a document with 2 ActiveX controls or 165 ActiveX controls and the wait required seems to
be 10 secs on my PC. Incidentally, setting almost 150 control values was only seconds, once the wait period was completed.
If anyone knows why the 'Wait' is seemingly required, I'd be interested to know!
Here are a few tips to help you work this out.
Don't use On Error Resume Next unless you really need it. And then when you do use it, re-enable error catching with On Error Goto 0 as quickly as possible. Otherwise you'll miss many errors in your code and it will be hard to tell what's happening.
Don't use the name Word as a variable name. It is reserved for the Word.Application and will only confuse the compiler (and you).
The control titles are text strings, so you must enclose them in double-quotes.
I've thrown in a bonus Sub that gives you a quick method to either open a new Word application instance or attach to an existing application instance. You'll find that (especially during debugging) there will be dozens of Word exe's opened and running.
The example code below also breaks the assignment of your "SUCCESS" value to the control into a separate Sub. It's in this short Sub that using On Error Resume Next is appropriate -- and isolated from the rest of the logic -- limiting its scope.
Option Explicit
Sub trial()
Dim wdApp As Word.Application
Dim wdDoc As Word.Document
Set wdApp = AttachToMSWordApplication
Set wdDoc = wdApp.Documents.Open("C:\Temp\Customer.docm")
wdApp.Application.Visible = True
TextToControl wdDoc, "txt_PersonName", "SUCCESS"
TextToControl wdDoc, "txt_Address", "SUCCESS"
End Sub
Private Sub TextToControl(ByRef doc As Word.Document, _
ByVal title As String, _
ByVal value As String)
Dim cc As ContentControl
On Error Resume Next
Set cc = doc.SelectContentControlsByTitle(title).Item(1)
If Not cc Is Nothing Then
cc.Range.Text = value
Else
Debug.Print "ERROR: could not find control titled '" & title & "'"
'--- you could also raise an error here to be handled by the caller
End If
End Sub
Public Function AttachToMSWordApplication() As Word.Application
'--- finds an existing and running instance of MS Word, or starts
' the application if one is not already running
Dim msApp As Word.Application
On Error Resume Next
Set msApp = GetObject(, "Word.Application")
If Err > 0 Then
'--- we have to start one
' an exception will be raised if the application is not installed
Set msApp = CreateObject("Word.Application")
End If
Set AttachToMSWordApplication = msApp
End Function

objProcess.Terminate not Found specific to iexplore.exe

When terminating internet explorer within VBA I'm using the following code sourced (more or less) from stackoverflow:
Sub IE_Sledgehammer()
Dim objWMI As Object, objProcess As Object, objProcesses As Object
Set objWMI = GetObject("winmgmts://.")
Set objProcesses = objWMI.ExecQuery( _
"SELECT * FROM Win32_Process WHERE Name = 'iexplore.exe'")
MsgBox "iexplore.exe processes = " & objProcesses.Count
For Each objProcess In objProcesses
Call objProcess.Terminate
Next
Set objProcesses = Nothing: Set objWMI = Nothing
End sub
The number of processes returned is "2" (which is correct) but in the first iteration of the For loop, when the first iexplore.exe is terminated that also terminates the second process and so in the second loop iteration a 'not found' error occurs. I understand that this is because closing one iexplorer.exe process closes all iexplore.exe tasks.
My question is, how can I reference and terminate only the first 'objprocess' in the objprocesses list OR force the For loop to exit after one iteration?
Do you mean you want to close the first tab or IE window?
I suggest you create an object of Shell and loop through IE windows.
You can close the first IE window and exit the loop or you can match the title of the IE window and close that specific IE window.
You need to add the reference to Microsoft Shell Controls and Automation and Microsoft Internet Controls.
Code:
Sub demo()
Dim oShellWin As Shell
Dim oWin As Object
Dim objIE As InternetExplorer
Set oShellWin = New Shell
For Each objIE In oShellWin.Windows
If TypeName(objIE.Document) = "HTMLDocument" Then
'Debug.Print objIE.Document.Title
If objIE.Document.Title = "Bing" Then 'User can modify the title here...
objIE.Quit
Exit For
End If
End If
Next objIE
End Sub
Output:
Further, you can try to modify the code as per your requirement.

Excel process stuck in Task Manager

In Access I open an Excel file, read from it and close it. The Excel process does not go away from Task Manager.
I found the same problem here but no solution that worked.
If I click the Reset button in the VB Editor, it goes away (or if I change anything in the code, which also causes the project to reset and gets rid of the unwanted Excel process).
I have the following class, called clsTest:
Option Compare Database
Option Explicit
Private xlFolder As String
Private xlFile As String
Private xlSheet As String
Private colShortURL As String
Private oXL As Excel.Application
Private oWB As Excel.Workbook
Private oWS As Excel.Worksheet
Private iLastRow As Long
Private Sub Class_Initialize()
Debug.Print "From class: Going through initialization inside class - constructor"
xlFolder = "E:\COMH\Excel"
xlFile = "Records v8z.xlsm"
xlSheet = "comh"
Set oXL = New Excel.Application
Set oWB = oXL.Workbooks.Open(Filename:=(xlFolder & "\" & xlFile), ReadOnly:=True)
Set oWS = oWB.Sheets(xlSheet)
iLastRow = oWS.Range("A" & Rows.Count).End(xlUp).row
End Sub
Public Property Get ShortURL() As String
ShortURL = "Hello World " & iLastRow
End Property
Private Sub Class_Terminate()
Debug.Print "From class: Going through the clean-up inside class - destructor"
oWB.Close SaveChanges:=False
Set oWS = Nothing
Set oWB = Nothing
oXL.Quit
Set oXL = Nothing
End Sub
I have the following Module to use above class:
Option Compare Database
Option Explicit
Private Sub TestClass()
Dim newExcel As clsTest
Try:
On Error GoTo Catch
Set newExcel = New clsTest
Debug.Print "Class instantiated, all good"
Debug.Print "ShortURL=" & newExcel.ShortURL
GoTo Finally
Catch:
Debug.Print "dealing with the error"
Debug.Print Err.Description & " - " & Err.Number
Finally:
Debug.Print "doing the finally stuff"
Set newExcel = Nothing
End Sub
I get the result I want:
From class: Going through initialization inside class - constructor
Class instantiated, all good
ShortURL=Hello World 2603
doing the finally stuff
From class: Going through the clean-up inside class - destructor
There are no errors but a process for Excel persists in Task Manager Processes tab.
nice troubleshooting!
try changing the problematic line from
iLastRow = oWS.Range("A" & Rows.Count).End(xlUp).row
to this (you may need to go further and reference the range at the Application level, I can't remember this and can't test now)
iLastRow = oWS.Range("A" & oWS.Rows.Count).End(xlUp).row
The likely issue is that you do not have a fully qualified reference, see here
In my scenario, excel and other office apps didnt close completely from TaskManager because i was running Fiddler tool(for tracing the http requests since i mostly work on web applications).
If i close or stop capturing on fiddler tool then it exits from TaskManager properly.

How can I kill task manager processes through VBA code?

I'm trying to kill certain processes through VBA. I have a proprietary object that connects to a market data bus. Through RTD I call this object to pub/sub to the bus. However sometimes the connection drops and I need to kill the process via task manager. Is there a way to kill a process through VBA?
Thanks
Try with this code
Dim oServ As Object
Dim cProc As Variant
Dim oProc As Object
Set oServ = GetObject("winmgmts:")
Set cProc = oServ.ExecQuery("Select * from Win32_Process")
For Each oProc In cProc
'Rename EXCEL.EXE in the line below with the process that you need to Terminate.
'NOTE: It is 'case sensitive
If oProc.Name = "EXCEL.EXE" Then
MsgBox "KILL" ' used to display a message for testing pur
oProc.Terminate()
End If
Next
Take a look at one more example:
Sub Test()
If TaskKill("notepad.exe") = 0 Then MsgBox "Terminated" Else MsgBox "Failed"
End Sub
Function TaskKill(sTaskName)
TaskKill = CreateObject("WScript.Shell").Run("taskkill /f /im " & sTaskName, 0, True)
End Function
You can perform it, this way:
Dim oServ As Object
Dim cProc As Variant
Dim oProc As Object
Set oServ = GetObject("winmgmts:")
Set cProc = oServ.ExecQuery("Select * from Win32_Process")
For Each oProc In cProc
'Rename EXCEL.EXE in the line below with the process that you need to Terminate.
'NOTE: It is 'case sensitive
If oProc.Name = "EXCEL.EXE" Then
MsgBox "KILL" ' used to display a message for testing pur
oProc.Terminate 'kill exe
End If
Next
Sub Kill_Excel()
Dim sKillExcel As String
sKillExcel = "TASKKILL /F /IM Excel.exe"
Shell sKillExcel, vbHide
End Sub

VBA Declaring arguments outside a function

I have vba code which opens a word document based on a template and when finished, runs the code below:
Public Sub Destroy(doc As Word.Document, app As Word.Application)
If Not (doc Is Nothing) Then doc.Close SaveChanges:=wdDoNotSaveChanges
If app.Documents.Count = 0 Then app.Quit wdDoNotSaveChanges
Set app = Nothing
End Sub
(this means that the app only closes if there are no other documents open and doesnt leave a blank application loaded when finished either)
I would like to extend this to excel and possibly other applications in the future; but rather than write a separate function for every different application, I wondered if I could have one function do-it-all.
The problem I'm finding is the declaration of arguments "doc As Word.Document" and "app As Word.Application".... is there a way of declaring what "doc" and "app" are in the calling program, and then getting the type definition of them inside my function to decide what to do depending on what type of application I choose to destroy()?
Edit:
I'm happy with the code, but on running a quick test in the code below, I founf that byref and byval both didnt effect the value of myval:
Private Sub Command12_Click()
Dim myval As Integer
myval = 1
MsgBox "the value of myval is " & myval
doByVal (myval)
MsgBox "the value of myval is " & myval
doByRef (myval)
MsgBox "the value of myval is " & myval
End Sub
Private Sub doByVal(ByVal a As Integer)
a = a + 1
MsgBox "byVal gives " & a
End Sub
Private Sub doByRef(ByRef a As Integer)
a = a + 1
MsgBox "byRef gives " & a
End Sub
Sure. You may declare generic Objects as args of the function, and verify their actual type in function. Basically, your code frame will look like:
Public Sub Destroy(ByVal doc As Object, ByVal app As Object)
If (TypeOf doc Is Word.Document) And (TypeOf app Is Word.Application) Then
' Word related stuff
ElseIf (TypeOf doc Is Excel.Workbook) And (TypeOf app Is Excel.Application) Then
' Excel related stuff
' ...
Else
' Do something about mixed cases, unhandled types etc.
End If
End Sub
And here's passing args to Destroy function, as (a bit exaggerated) example:
Dim my_doc As Excel.Workbooks
Dim my_app As Excel.Application
Set my_app = Excel.Application
Set my_doc = my_app.Workbooks("IWantYouClosed.xlsx")
Call Destroy(my_doc, my_app)
You are trying to use Early binding, and that requires creating a reference to the appropriate library (in Tools, References).
Alternatively, you can use Late binding, declaring doc As Object.
The web has lots of pages on "early binding vs late binding". One sample here.
Using the help given, this is the code I am now using, hope this helps someone.
Public Sub Document(ByVal doc As Object, ByVal app As Object)
Select Case True
Case (TypeOf doc Is Word.Document And TypeOf app Is Word.Application)
If Not (doc Is Nothing) Then doc.Close SaveChanges:=wdDoNotSaveChanges
If app.Documents.Count = 0 Then app.Quit wdDoNotSaveChanges
Set app = Nothing
Case (TypeOf doc Is Workbook And TypeOf app Is Excel.Application)
'code for excel workbook
Case Else
MsgBox "Cannot recognise the document/application, or there may be a mismatch"
End Select
End Sub

Resources