Closing image viewing software

John Sh

Active member
Local time
Today, 12:28
Joined
Feb 8, 2021
Messages
601
I am using the following code to open a picture viewer via vba

Code:
oShell.NameSpace(0).ParseName(sPath).InvokeVerb "Open"

Then this code to close the viewer

Code:
Dim Exe As Object
    For Each Exe In GetObject("winmgmts:").ExecQuery("Select Name from Win32_Process Where Name = '" & sViewer & "'")
        Exe.Terminate
    Next
This all works but the viewer being used must be named specifically via "sViewer" which is included in the "sPath" in the opening code.,

How does one determine the default viewer programmatically so the code will run on multiple work stations.
Google is a bit coy with it's answers.
 
Use the PID returned by the Shell() command instead of the exe name.

(Replying from my phone - will post an example when I get back to a computer)
 
Use the PID returned by the Shell() command instead of the exe name.

(Replying from my phone - will post an example when I get back to a computer)
Thanks David.
My description maybe misleading.
Firstly, I am displaying 70 mb NEF files that Access cannot handle, so have to use external software.
I need to be able to display the images in the default viewer on various computers, so have to be able to determine the default for each machine.
I use Honeyview as the default but others will use various other viewers and the shell needs to be told what the viewer in use is.
Ideally I need a method of reading the default and passing that to the shell programmatically.
John
 
Thanks David.
My description maybe misleading.
Firstly, I am displaying 70 mb NEF files that Access cannot handle, so have to use external software.
I need to be able to display the images in the default viewer on various computers, so have to be able to determine the default for each machine.
I use Honeyview as the default but others will use various other viewers and the shell needs to be told what the viewer in use is.
Ideally I need a method of reading the default and passing that to the shell programmatically.
John
In the interest of covering more bases, I wonder if you could not specify a particular image viewer for your users. Perhaps you could even install one as part of your deployment process.

Otherwise, it seems like a cat-and-mouse search for potential viewer software on each user's computer.

Maybe the number of possible viewers is small enough that you can create a search list based on that list, and inspect each user's hard drive for them. But even then, I wonder if the default selected by any given user would be easily determined if there is more than one potential candidate available.

In short, like a lot of deployment decisions, it might serve your needs, and the needs of the majority of your users, to take control of the choice and simplify the process by limiting the choices to one you can manage.

Is that feasible?
 
We have diferent viewers for 3D cad drawings installed on different machines, based on the licence of application.
This is how we close the viewers.
  1. Find the name of default program associated application to the extension, in registry
  2. close the application.
This finds the default application associated with any extension:

SQL:
Function GetDefaultViewerExe(ext As String) As String
    Dim wsh As Object
    Dim progID As String
    Dim cmdKey As String
    Dim openCmd As String
    Dim shellApp As Object

    Set wsh = CreateObject("WScript.Shell")

    On Error GoTo ErrHandler

    progID = wsh.RegRead("HKEY_CLASSES_ROOT\" & ext & "\")
    If progID = "" Then Exit Function
    cmdKey = "HKEY_CLASSES_ROOT\" & progID & "\shell\open\command\"
    openCmd = wsh.RegRead(cmdKey)

    ' Extract the EXE name from the command'
    openCmd = Trim(openCmd)
    If Left(openCmd, 1) = Chr(34) Then
        openCmd = Mid(openCmd, 2)
        openCmd = Left(openCmd, InStr(openCmd, Chr(34)) - 1)
    Else
        openCmd = Left(openCmd, InStr(openCmd, " ") - 1)
    End If

    GetDefaultViewerExe = Mid(openCmd, InStrRev(openCmd, "\") + 1)
    Exit Function

ErrHandler:
    GetDefaultViewerExe = ""
End Function

Now that we have the application name, we can close it.
SQL:
Sub CloseViewer()
 
    Dim sViewer As String
    Dim Exe As Object
 
    sViewer = GetDefaultViewerExe(".slddrw")

    For Each Exe In GetObject("winmgmts:").ExecQuery("Select Name from Win32_Process Where Name = '" & sViewer & "'")
        Exe.Terminate
    Next

End Sub

Change the .slddrw to the extension of your files.
Becaues we have to do it frequently, we read the viewer name at login and set a tempvar. This way there's no need to repeat reading registry.
 
Last edited:
  1. Find the name of default program associated application to the extension, in registry
  2. close the application.
Good approach in theory.
In practice there might be a problem....

The default picture viewer, as defined in the Windows Registry, will often be:
%SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Windows Photo Viewer\PhotoViewer.dll", ImageView_Fullscreen %1
However, the actual running process with be "Photos.exe".

I would rather use ShellExecuteEx to invoke the verb ("open") and then use process id of the created process to close the application later. - That's probably the same as @cheekybuddha suggested.
 
Good approach in theory.
In practice there might be a problem....

The default picture viewer, as defined in the Windows Registry, will often be:
%SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Windows Photo Viewer\PhotoViewer.dll", ImageView_Fullscreen %1
However, the actual running process with be "Photos.exe".

I would rather use ShellExecuteEx to invoke the verb ("open") and then use process id of the created process to close the application later. - That's probably the same as @cheekybuddha suggested.
Since we use it for CAD files, we hadn't any problem so far. But in some cases as you explain, it may cause problems.
How about This method :
  1. Read the list of running processes
  2. Open your file
  3. Find the new process and keep it in a public variable or a tempvar
  4. Terminate the process when necessary
SQL:
Function GetProcessList() As Collection
    Dim col As New Collection
    Dim proc As Object
    For Each proc In GetObject("winmgmts:").ExecQuery("Select * from Win32_Process")
        col.Add proc.Name
    Next
    Set GetProcessList = col
End Function

Function FindNewProcess(before As Collection, after As Collection) As String
    Dim item As Variant, found As Boolean
    For Each item In after
        found = False
        Dim b As Variant
        For Each b In before
            If item = b Then
                found = True
                Exit For
            End If
        Next
        If Not found Then
            FindNewProcess = item
            Exit Function
        End If
    Next
End Function

Usage:
SQL:
Sub test()
    Dim beforeList As Collection, afterList As Collection
    Dim oShell As Object
    Dim newProc As String

    Set oShell = CreateObject("Shell.Application")
    Set beforeList = GetProcessList()
    oShell.NameSpace(0).ParseName("D:\1.jpg").InvokeVerb "Open"
    Application.Wait Now + TimeValue("0:00:01")
 
    Set afterList = GetProcessList()
    newProc = FindNewProcess(beforeList, afterList)
 
    MsgBox "Likely viewer: " & newProc
    TerminateProcessByName newProc
End Sub


I'll wait to see how @cheekybuddha solves the puzzle.
 
Last edited:
Here's how I terminate a process

SQL:
Sub TerminateProcessByName(procName As String)
    Dim objWMIService As Object
    Dim colProcessList As Object
    Dim objProcess As Object
   
    Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
    Set colProcessList = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = '" & procName & "'")
   
    For Each objProcess In colProcessList
        objProcess.Terminate
    Next
End Sub
 
Last edited:
Now that I think about the process method (second method), I can see three problems.
  1. This method fails if while the file is opening, another application, manually or from another script is launched.
  2. If you have an unsaved file, the first method, (exe.terminate) asks if you want to save the file, but using process.terminate doesn't ask to save the file.
  3. Our IT guy says working with running processes is less reliable on systems with a lot of background activity.
So it's safer to use the first method, if possibel.
 
Some of the computers are on a uni network so should be using the same viewer and I can control that to a certain degree.
It's the couple of private machines that are of concern.
I will try the suggestions put forward and see how that works, otherwise I might have to limit this particular function to machines controlled by the uni.
Thanks.
John
 

Users who are viewing this thread

Back
Top Bottom