How can I use installscript to detect Excel.exe running? - excel

Ive been trying to detect the excel process in my installshield installer.
I have a custom action that runs after appsearch and pops a window if it finds the process and displays a warning to the user.
I have tried using some old examples I found on installsite.org and using the findWindow() call. Neither seems to find excel.exe in the process list.
Here is a snippet of code I was using when trying the findwindow
export prototype MyTestFunction(HWND);
function MyTestFunction(hMSI)
HWND nHwnd;
begin
nHwnd = FindWindow("EXCEL", "");
if (nHwnd != 0) then
MessageBox("found excel", WARNING);
SendMessage(nHwnd, WM_CLOSE, 0, 0);
else
MessageBox("cant find excel", WARNING);
endif;
end;
Note that only the else block ever seems to fire regardless of the application being open or closed.
I have tried several different variants of this mostly just replacing the "excel" with different capitalization, extensions and versions. Nothing seems to detect the window. I used Spy++ and it reported that the window is named after the name of the currently opened notebook which complicates things since I have no way of knowing what a user could have opened.
I am open to suggestions here. The only requirement for this solution is that it has to be able to run as either a custom action or part of an install condition from within Installshield.

You could use a vbscript Custom Action.
You can run this CA at the begining of UISequence or ExecuteSequence (or both) If you want it as a part of the Install condition.
Add the code in a vbscript function and configure "Return Processing" Option for the Custom Action to "Synchonous (Check exit code)" if you want to stop the installation process.
Here is my script:
Public Function StopProcess
Dim objWMIService, objProcess, colProcess
Dim strComputer, executableFileName
Const IDABORT = 3
strComputer = "."
executableFileName = "excel.exe"
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" & executableFileName & "'")
For Each objProcess in colProcess
objProcess.Terminate()
' OR
StopProcess = IDABORT
Exit for
Next
End function

Obviously trying to figure out if a process is running by finding the associated window has its pitfalls.
My suggestion is to detect if the process for Excel.exe is running. It would involve enumerating the processes on the system. Modify your code accordingly. Its easier to do it using C++ but there are numerous examples available which show you how to achieve what i have just stated.
https://community.flexerasoftware.com/archive/index.php?t-162141.html
https://community.flexerasoftware.com/archive/index.php?t-188807.html
Take

We can write a InstallScript code as well to achieve this. Please refer the code below :
function CheckRunningProcessAndTerminate(hMSI)
// To Do: Declare local variables.
OBJECT wmi,objProc;
STRING szProcessName;
begin
// To Do: Write script that will be executed when MyFunction is called.
szProcessName = "Excel.exe";
set wmi = CoGetObject("winmgmts://./root/cimv2", "");
set objProc = wmi.ExecQuery("Select * from Win32_Process where Name = '" + szProcessName + "'");
if (objProc.count > 0) then
MessageBox("Process is running.", INFORMATION);
//kill proces
TerminateProcesses(szProcessName);
//objProc.Terminate(); //I tried this, but it didn't worked.
else
MessageBox("Process is not running.", INFORMATION);
endif;
end;

Related

Extract file names from a File Explorer search into Excel

This has been bugging me for while as I feel I have few pieces of the puzzle but I cant put them all together
So my goal is to be able to search all .pdfs in a given location for a keyword or phrase within the content of the files, not the filename, and then use the results of the search to populate an excel spreadsheet.
Before we start, I know that this easy to do using the Acrobat Pro API, but my company are not going to pay for licences for everyone so that this one macro will work.
The windows file explorer search accepts advanced query syntax and will search inside the contents of files assuming that the correct ifilters are enabled. E.g. if you have a word document called doc1.docx and the text inside the document reads "blahblahblah", and you search for "blah" doc1.docx will appear as the result.
As far as I know, this cannot be acheived using the FileSystemObject, but if someone could confirm either way that would be really useful?
I have a simple code that opens an explorer window and searches for a string within the contents of all files in the given location. Once the search has completed I have an explorer window with all the files required listed. How do I take this list and populate an excel with the filenames of these files?
dim eSearch As String
eSearch = "explorer " & Chr$(34) & "search-ms://query=System.Generic.String:" & [search term here] & "&crumb=location:" & [Directory Here] & Chr$(34)
Call Shell (eSearch)
Assuming the location is indexed you can access the catalog directly with ADO (add a reference to Microsoft ActiveX Data Objects 2.x):
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim sql As String
cn.Open "Provider=Search.CollatorDSO;Extended Properties='Application=Windows'"
sql = "SELECT System.ItemNameDisplay, System.ItemPathDisplay FROM SystemIndex WHERE SCOPE='file:C:\look\here' AND System.Kind <> 'folder' AND CONTAINS(System.FileName, '""*.PDF""') AND CONTAINS ('""find this text""')"
rs.Open sql, cn, adOpenForwardOnly, adLockReadOnly
If Not rs.EOF Then
Do While Not rs.EOF
Debug.Print "File: "; rs.Collect(0)
Debug.Print "Path: "; rs.Collect(1)
rs.MoveNext
Loop
End If
Try using the next function, please:
Function GetFilteredFiles(foldPath As String) As Collection
'If using a reference to `Microsoft Internet Controls (ShDocVW.dll)_____________________
'uncomment the next 2 lines and comment the following three (without any reference part)
'Dim ExpWin As SHDocVw.ShellWindows, CurrWin As SHDocVw.InternetExplorer
'Set ExpWin = New SHDocVw.ShellWindows
'_______________________________________________________________________________________
'Without any reference:_____________________________________
Dim ExpWin As Object, CurrWin As Object, objshell As Object
Set objshell = CreateObject("Shell.Application")
Set ExpWin = objshell.Windows
'___________________________________________________________
Dim Result As New Collection, oFolderItems As Object, i As Long
Dim CurrSelFile As String
For Each CurrWin In ExpWin
If Not CurrWin.Document Is Nothing Then
If Not CurrWin.Document.FocusedItem Is Nothing Then
If left(CurrWin.Document.FocusedItem.Path, _
InStrRev(CurrWin.Document.FocusedItem.Path, "\")) = foldPath Then
Set oFolderItems = CurrWin.Document.folder.Items
For i = 0 To oFolderItems.count
On Error Resume Next
If Err.Number <> 0 Then
Err.Clear: On Error GoTo 0
Else
Result.Add oFolderItems.item(CLng(i)).Name
On Error GoTo 0
End If
Next
End If
End If
End If
Next CurrWin
Set GetFilteredFiles = Result
End Function
Like it is, the function works without any reference...
The above function must be called after you executed the search query in your existing code. It can be called in the next (testing) way:
Sub testGetFilteredFiles()
Dim C As Collection, El As Variant
Set C = GetFilteredFiles("C:\Teste VBA Excel\")'use here the folder path you used for searching
For Each El In C
Debug.Print El
Next
End Sub
The above solution iterates between all IExplorer windows and return what is visible there (after filtering) for the folder you initially used to search.
You can manually test it, searching for something in a specific folder and then call the function with that specific folder path as argument ("\" backslash at the end...).
I've forgotten everything I ever knew about VBA, but recently stumbled across an easy way to execute Explorer searches using the Shell.Application COM object. My code is PowerShell, but the COM objects & methods are what's critical. Surely someone here can translate.
This has what I think are several advantages:
The query text is identical to what you wouold type in the Search Bar in Explorer, e.g.'Ext:pdf Content:compressor'
It's easily launched from code and results are easily extracted with code, but SearchResults window is available for visual inspection/review.
With looping & pauses, you can execute a series of searches in the same window.
I think this ability has been sitting there forever, but the MS documentation of the Document object & FilterView method make no mention of how they apply to File Explorer.
I hope others find this useful.
$FolderToSearch = 'c:\Path\To\Folder'
$SearchBoxText = 'ext:pdf Content:compressor'
$Shell = New-Object -ComObject shell.application
### Get handles of currenlty open Explorer Windows
$CurrentWindows = ( $Shell.Windows() | Where FullName -match 'explorer.exe$' ).HWND
$WinCount = $Shell.Windows().Count
$Shell.Open( $FolderToSearch )
Do { Sleep -m 50 } Until ( $Shell.Windows().Count -gt $WinCount )
$WindowToSerch = ( $Shell.Windows() | Where FullName -match 'explorer.exe$' ) | Where { $_.HWND -notIn $CurrentWindows }
$WindowToSearch.Document.FilterView( $SearchBoxText )
Do { Sleep -m 50 } Until ( $WindowToSearch.ReadyState -eq 4 )
### Fully-qualified name:
$FoundFiles = ( $WindowToSearch.Document.Folder.Items() ).Path
### or just the filename:
$FoundFiles = ( $WindowToSearch.Document.Folder.Items() ).Name
### $FoundFIles is an array of strings containing the names.
### The Excel portion I leave to you! :D

Determine if Window is Open Using UIAutomation

I am writing an excel plugin that outputs an edge list and a graphml sheet that are later used by the program yED to make a bitmap of the graph for the overall output of the plugin. I am using a shell command to open the appropriate file in yED, and UIAutomation to send the commands to yED.
When there is a window of yED already open, the code executes just fine. The window is found by the polling, then set active and the commands sent over. Where it goes wrong is when the shell command causes a new window of yED to be launched. There is a splash screen for yED as it is loading in that takes a few seconds to get through, and shares the same Name and Class as the window that I am looking for. The HWND is different between the two.
My code will error out whenever there is a new window of yED launching. The error reads:
Run-time error '-2147467259 (80004005)': Automation error Unspecified
error
Reference code:
Function FindyEdByClass() As IUIAutomationElement
Dim oUIAutomation As New CUIAutomation
Dim oUIADesktop As IUIAutomationElement
Dim allChilds As IUIAutomationElementArray
Dim oUIAyED As IUIAutomationElement
Dim i As Integer
Dim Timer As Date
Set oUIADesktop = oUIAutomation.GetRootElement
Set oUIAyED = oUIADesktop
Timer = Now
RestartLoop:
Set allChilds = oUIADesktop.FindAll(TreeScope_Children, oUIAutomation.CreateTrueCondition)
Debug.Print "StartLoop" & vbCrLf;
For i = 0 To allChilds.Length - 1
'EDIT: the following line is the one that errors out.
If allChilds.GetElement(i).CurrentName = "Graph.graphml - yEd" And allChilds.GetElement(i).CurrentClassName = "SunAwtFrame" Then
Debug.Print "Found Child - yED" & vbCrLf;
Set oUIAyED = allChilds.GetElement(i)
End If
Next
If Now() > (Timer + TimeValue("00:00:10")) Then GoTo NoyED
If oUIAyED.CurrentName = "Desktop" Then GoTo RestartLoop
EndOFLoop:
Debug.Print oUIAyED.CurrentName & " " & oUIAyED.CurrentClassName & vbCrLf;
Set FindyEdByClass = oUIAyED
Exit Function
NoyED:
MsgBox "No yED Window Found"
End
End Function
The line it errors out on the If statement. Through use of Debug.Print and FindWindowEx I have discovered that the object it errors out on is not the yED window I am looking for, but the yED splash screen that precedes it. I assume that the error is caused by the splash screen disappearing after its load time is up.
How do I go about finding the window I am looking for in this case? I need to be able to find the window without seeing the splash screen that will cause the error, and I need to be able to differentiate between the two by something other than class or name.
Note: I want to compare the windows by HWND but I don't know how to find it without the class/name, and it is never the same between two runs.

Determine Process ID with VBA

Situation - I have a macro where I need to send keystrokes to two Firefox windows in order. Unfortunately both windows have the same title. To handle this I have activated the window, sent my keystrokes, then used F6 to load the URL of the second window and then send the keystrokes then use F6 to return it to the original page.
The issue is that loading the webpages is unreliable. Page load speeds vary so much that using a wait command is not consistent or reliable to ensure the keystroke makes it to the second window.
Question -
I've read a scattering of posts that mentioned that app activate will work with Process ID's. Since each window would have its own PID that would be an ideal way to handle 2 windows with the same title. I am unable to find information specifically how to determine the PID of each window with a given name.
You can use something like the following. You'll have to tinker about with the different info available in the Win32_Process class to figure out which window is which. It's also important to keep in mind that one window could mean many processes.
Public Sub getPID()
Dim objServices As Object, objProcessSet As Object, Process As Object
Set objServices = GetObject("winmgmts:\\.\root\CIMV2")
Set objProcessSet = objServices.ExecQuery("SELECT ProcessID, name FROM Win32_Process WHERE name = ""firefox.exe""", , 48)
'you may find more than one processid depending on your search/program
For Each Process In objProcessSet
Debug.Print Process.ProcessID, Process.Name
Next
Set objProcessSet = Nothing
End Sub
Since you'll probably want to explore the options with WMI a bit, you may want to add a Tools>>References to the Microsoft WMI library so you don't have to deal with Dim bla as Object. Then you can add breakpoints and see what's going on in the Locals pane.
After adding the reference:
Public Sub getDetailsByAppName()
Dim objProcessSet As WbemScripting.SWbemObjectSet
Dim objProcess As WbemScripting.SWbemObject
Dim objServices As WbemScripting.SWbemServices
Dim objLocator As WbemScripting.SWbemLocator
'set up wmi for local computer querying
Set objLocator = New WbemScripting.SWbemLocator
Set objServices = objLocator.ConnectServer(".") 'local
'Get all the gory details for a name of a running application
Set objProcessSet = objServices.ExecQuery("SELECT * FROM Win32_Process WHERE name = ""firefox.exe""", , 48)
RecordCount = 1
'Loop through each process returned
For Each objProcess In objProcessSet
'Loop through each property/field
For Each Field In objProcess.Properties_
Debug.Print RecordCount, Field.Name, Field.Value
Next
RecordCount = RecordCount + 1
Next
Set objProcessSet = Nothing
Set objServices = Nothing
Set objLocator = Nothing
End Sub
That will print out every property of every process found for the name 'firefox.exe'.

After a WMI search in VBScript, can I create my search filter BEFORE my "For Each" statement?

I've created an alternative search utility to the Windows search utility with VBScript using a WQL search, but, as it turns out, it's quite slow. I would like to speed it up and I think I can do it, but I would need to place my search filter AFTER my WQL search and BEFORE my For Each statement. Is this even possible?
I've already tested by filtering in the WQL search, but it's about 40% faster if I filter after the WQL search. I've also tested with and without iFlags, but they tend to slow the search quite a bit, even though MS seems to believe otherwise.
Since the user can search by filename, creation date, last modified date and/or file size, if the filter is after the For Each statement then the script has to create the search filter each time it enumerates a file. I'd like to create the filter once in the hope of shaving some time off the search.
This will probably make better sense when you take a look at the snippet of code I've posted. Note that the sub subCreateSearchString will have calls to other search options and functions (ie: convert from UTC to local time, format file sizes, etc.)
Dim strSearchName, strComputer, objSWbemServices, objFile, colFiles
Dim strFileName, strReturnedFileName, strQueryDriveAndPath
strSearchName = "test" 'Text being searched for - change as needed
strQueryDriveAndPath = "PATH = '\\Drop_RW\\' AND DRIVE = 'D:'" 'Path and drive in which to search - change as needed
strComputer = "."
Set objSWbemServices = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colFiles = objSWbemServices.ExecQuery("Select * from CIM_DataFile WHERE " & "" & strQueryDriveAndPath & "")
'* I'd like to place the call to "subCreateSearchString" here
On Error Resume Next
For Each objFile in colFiles
strReturnedFileName = objFile.Name
subCreateSearchString ' Search filter - it works when placed here
If strSearchForString Then
MsgBox "File matches:" & vbCrLf & strReturnedFileName
Else
MsgBox "File DOES NOT match" & vbCrLf & strReturnedFileName
End If
Next
Sub subCreateSearchString
'* Set Filename Variable for search:
strFileName = InStr(LCase(strReturnedFileName), LCase(strSearchName))
strSearchForString = strFileName
End Sub
Since you depend on the names of the files you're iterating over in the For Each loop: no, not possible.
I'd strongly recommend making some adjustments, though.
Use a Function rather than a Sub if you want to return something from a subroutine.
Avoid using global variables. They have a nasty tendency of introducing undesired side effects and also make debugging your code a pain in the rear. Pass values into your subroutines via parameters, and return values as actual return values.
The returned value is an integer (or Null), but you use it like a boolean and named your variables (and sub) as if it were a string. Don't do that. Name your functions/procedures after what they're doing, and name your variables after what they contain. And if you want to use a boolean value make your function actually return a boolean value.
Avoid Hungarian Notation. It's pointless code-bloat the way most people use it. Even more if your naming doesn't even match the actual type.
Do not use global On Error Resume Next. Ever. It simply makes your code fail silently without telling you anything about what actually went wrong. Keep error handling as local as possible. Enable it only for single commands or short code blocks, and only if there is no other way to avoid/handle the error.
Function IsInFilename(searchName, fileName)
IsInFilename = InStr(LCase(fileName), LCase(searchName)) > 0
End Function
For Each objFile in colFiles
If IsInFilename(strSearchName, objFile.Name) Then
MsgBox "..."
Else
MsgBox "..."
End If
Next

Execute shell is not opening up file provided as string

I am trying to solve an error in code written by someone else. The shell function running in an MS Excel VBA environment is as follows:
Public Function ExecCmd(cmdline$)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ret&
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
ret& = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.hProcess, INFINITE)
Call GetExitCodeProcess(proc.hProcess, ret&)
Call CloseHandle(proc.hThread)
Call CloseHandle(proc.hProcess)
ExecCmd = ret&
End Function
The cmdline$ input is: "excel.exe W:\L\BDTP\Products\Mandate FS\OfferToolUpdate.xlsm"
When I run the code it opens another excel instance and attempts to open a file under "W:\L\BDTP\Products\Mandate.xlsx"... after two more error messages it tells me that it can also not find "FS\OfferToolUpdate.xlsm"
Clearly this error is produced some how due to the space in the naming convention of the Folder the file resides in.
How can I open the file without changing the folder name?
I believe you used example as shown at: https://support.microsoft.com/en-us/kb/129796 ...
Just update execution line to wrap out the file:
ExecCmd "excel.exe ""U:\ADMINISTRATION\Expenses\Some File.xlsx"""
I have found a viable workaround for the time being, though I'm sure there are more professional ways of going about it. I have changed the input command line to be in extra quotes:
"""" & "excel.exe W:\L\BDTP\Products\Mandate FS\OfferToolUpdate.xlsm" & """"

Resources