Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1733
  • Last Modified:

VBA or VBS enumerate all open Acrobat Reader documents

I want to (in some type of code) find all the pdf files that are open in Acrobat Reader (I only have Reader)
Since I don't have Acrobat Pro, I can't get any API calls to work (e.g. GetObject(, "AcroExch.App"))
The solution to the related link (searching running tasks) doesn't work since it only finds the first one, and that only if the pdf was double clicked (since it parses the shell command that started the process).

I need the entire path of the files. I had the idea to try to capture it from the open windows, but don't know how well that would work since only the file name shows and it's formatted differently in XP and 7.

Any ideas?
0
TommySzalapski
Asked:
TommySzalapski
  • 15
  • 11
  • 8
2 Solutions
 
SiddharthRoutCommented:
I can give you the code to display the names of all the PDF Files that are open using APIs.

I know how to get the path from the running EXE but not from File names. I am working on that at the moment.

Sid
0
 
SiddharthRoutCommented:
There is one way I can think of...

If I close all the pdf one by one then I can get you the path of all the pdf's. I can then reopen them at the end. Would that help?

Sid
0
 
TommySzalapskiAuthor Commented:
Closing is fine. Don't bother reopening, I can do that. The version for Word and Excel closes them too (since that's the best way to iterate through Excel instances). Closing them doesn't change the command line for the process though.

Interested to see what you can up with.
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
TommySzalapskiAuthor Commented:
came* up with
0
 
SiddharthRoutCommented:
Tommy

If you paste this code in a module then it will give you the name of all the pdf files that are opened. I am still working on how to get the path.

Sid

Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Public Declare Function GetWindow Lib "user32" _
(ByVal hwnd As Long, ByVal wCmd As Long) As Long

Public Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long

Public Declare Function EnumChildWindows Lib "user32" _
(ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Public Declare Function GetWindowTextLength Lib "user32" _
Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long

Public Declare Function EnumWindows Lib "user32.dll" _
(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Public Declare Function IsIconic Lib "user32" (ByVal hwnd As Long) As Long

Public Declare Function GetDesktopWindow Lib "user32" () As Long

Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

Public Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long

Public Declare Function GetWindowLW Lib "user32" Alias "GetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long) As Long

#Const WinVar = True
#If WinVar Then
    Function FindWindowLike(hWndArray() As Variant, ByVal hWndStart As Variant, _
    WindowText As String, Classname As String, ID) As Integer
    Dim hwnd, r
    Static level
    Static iFound
#ElseIf Win32 Then
    Function FindWindowLike(hWndArray() As Long, ByVal hWndStart As Long, _
    WindowText As String, Classname As String, ID) As Long

    Dim hwnd As Long, r As Long
    
    '~~> Hold the level of recursion:
    Static level As Long
    '~~> Hold the number of matching windows:
    Static iFound As Long
#ElseIf Win16 Then
    Function FindWindowLike(hWndArray() As Integer, ByVal hWndStart As Integer, _
    WindowText As String, Classname As String, ID) As Integer
    Dim hwnd As Integer, r As Integer
    '~~> Hold the level of recursion:
    Static level As Integer
    '~~> Hold the number of matching windows:
    Static iFound As Integer
#End If

Dim sWindowText As String, sClassname As String, sID
'~~> Initialize if necessary:
If level = 0 Then
    iFound = 0
    ReDim hWndArray(0 To 0)
    If hWndStart = 0 Then hWndStart = GetDesktopWindow()
End If

'~~> Increase recursion counter:
level = level + 1

'~~> Get first child window:
hwnd = GetWindow(hWndStart, GW_CHILD)
Do Until hwnd = 0
    DoEvents
    '~~> Search children by recursion:
    r = FindWindowLike(hWndArray(), hwnd, WindowText, Classname, ID)
    '~~> Get the window text and class name:
    sWindowText = Space(255)
    r = GetWindowText(hwnd, sWindowText, 255)
    sWindowText = Left(sWindowText, r)
    sClassname = Space(255)
    r = GetClassName(hwnd, sClassname, 255)
    sClassname = Left(sClassname, r)
    '~~> If window is a child get the ID:
    If GetParent(hwnd) <> 0 Then
        r = GetWindowLW(hwnd, GWL_ID)
        sID = CLng("&H" & Hex(r))
    Else
        sID = Null
    End If
    '~~>  Check that window matches the search parameters:
    If sWindowText Like WindowText And sClassname Like Classname Then
        If IsNull(ID) Then
            '~~>  If find a match, increment counter and
            '~~>  add handle to array:
            iFound = iFound + 1
            ReDim Preserve hWndArray(0 To iFound)
            hWndArray(iFound) = hwnd
        ElseIf Not IsNull(sID) Then
            If CLng(sID) = CLng(ID) Then
                '~~> If find a match increment counter and
                '~~>  add handle to array:
                iFound = iFound + 1
                ReDim Preserve hWndArray(0 To iFound)
                hWndArray(iFound) = hwnd
            End If
        End If
    End If
    '~~> Get next child window:
    hwnd = GetWindow(hwnd, GW_HWNDNEXT)
Loop
'~~> Decrement recursion counter:
level = level - 1
'~~> Return the number of windows found:
FindWindowLike = iFound
End Function

Public Function EnumChildProc(ByVal hwnd As Long, ByVal lParam As Long) As Long
    Dim Length As Long
    Dim sName As String
    Length = GetWindowTextLength(hwnd) + 1
    If Length > 1 Then
        sName = Space(Length)
        GetWindowText hwnd, sName, Length
        If InStr(1, sName, ".pdf", vbTextCompare) Then Debug.Print Left(sName, Length - 1)
    End If
    EnumChildProc = 1
End Function

Public Sub Sample()
    Static hWnds() As Variant
    r = FindWindowLike(hWnds(), 0, "*.pdf", "*", Null)
    EnumChildWindows hWnds(r), AddressOf EnumChildProc, ByVal 0&
End Sub

Open in new window

0
 
SiddharthRoutCommented:
Sorry you need to run the last Sub Sample. Also press Ctl G to see immediate window where the output will be generated.

Sid
0
 
TommySzalapskiAuthor Commented:
Did you ever find a way to get the path?
0
 
SiddharthRoutCommented:
No Tommy. Even when I close the 1st pdf file, the Acro Exe still refers to the first file!!! I could get the name of all the pdf's though...

Still trying to figure out other way...

Sid
0
 
TommySzalapskiAuthor Commented:
That's because you are looking at the command that started Acrobat.
If you start Acrobat by itself and then open pdfs, the command shell string won't give you any file at all.
0
 
SiddharthRoutCommented:
Exactly but the API that I know can get the path of the Exe and that is not that we are looking at. So I am still thinking of an alternative to get the paths from the window name.

Sid
0
 
SiddharthRoutCommented:
OPTION 1

Ok I have been experimenting and I got stuck. But I am close...

Here is an example that I am trying to incorporate for just one pdf file. If this works then I will be able to get the path for all the PDF in the loop.

Under the file menu of Adobe is the submenu "properties" I am able to click that. I am just stuck as to how to retrieve the value of "Location". See snapshot.

Sid

Code Used

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long

Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Const WM_GETTEXT = &HD
Const WM_GETTEXTLENGTH = &HE

Private Sub Command_Click()
    Dim parenthwnd As Long
    
    '~~> Testing with 1.pdf
    parenthwnd = FindWindow(vbNullString, "1.pdf - Adobe Reader")
    
    '~~> Bringing the window to the foreground
    SetForegroundWindow parenthwnd
    
    '~~> Stimulating Alt F + R
    SendKeys "%F", True
    SendKeys "R", True
    
    '~~> Getting the handle of the "Document Properties"
    parenthwnd = FindWindow(vbNullString, "Document Properties")
    
    '~~> The code works till here
End Sub

Open in new window

Untitled.jpg
0
 
SiddharthRoutCommented:
OPTION 2 (unreliable method as it depends whether the user has enabled Recent List or not)

Getting the handle of the menu and then the handle of Recent List. Each pdf file will be in location 1 of it's recent list (See Picture above)

Sid
0
 
nffvrxqgrcfqvvcCommented:
You mean something like this?
 egl1044-411890.flv
0
 
TommySzalapskiAuthor Commented:
I don't really feel like waiting for an 11MB video to download so I can watch it full screen but if it's getting the path and filename of all pdfs open in Acrobat Reader, then yes. I'm not really looking for a video though, I'm looking for code that I can run. Can you post the code you used?
0
 
TommySzalapskiAuthor Commented:
Yes. That looks like what I want. Can you post the code, please?
0
 
SiddharthRoutCommented:
Even I am curious to see that code.

Sid
0
 
TommySzalapskiAuthor Commented:
egl1044?
0
 
SiddharthRoutCommented:
How do I contact Southmod?

Sid
0
 
SiddharthRoutCommented:
Much appreciated modus_in_rebus :)

Sid
0
 
nffvrxqgrcfqvvcCommented:
Did you get a solution for this yet?
0
 
TommySzalapskiAuthor Commented:
No. I've found programs that find all the open file handles and list them (Process Hacker etc), but none of them have an API that let me get the list programmatically. This may be my best option so far.
I've also been pointed to some Windows API calls beginning with NtQuerySystemInformation but I haven't had the time to try to get them working in VBScript yet.
0
 
nffvrxqgrcfqvvcCommented:
Do you want me to create library that you can use from script?
0
 
TommySzalapskiAuthor Commented:
Sure.
0
 
nffvrxqgrcfqvvcCommented:
Here play with this version until I finish the library. Let me know if you find bugs... If you need more explanation of how to use the library let me know example script version in code section.





'	AdobePDFX32.DLL
'	Version 1.0 Beta
'	egl1044
'        acroPDF.VBS

Dim AcrobatCore			'// AdobePDFX32
Dim AcroProcess			'// IAcrobatProcess
Dim AcroProcessCol		'// IAcrobatProcessCollection
Dim AcroPDFFile			'// IAcrobatPDFFile
Dim AcroPDFFileCol		'// IAcrobatPDFFileCollection
Dim AcroProcessRef		'// IAcrobatProcess (user referenced)


Sub SetObjectInitialStates
	Set AcrobatCore		= Nothing
	Set AcroProcess		= Nothing
	Set AcroProcessCol	= Nothing
	Set AcroPDFFile		= Nothing
	Set AcroPDFFileCol	= Nothing
	Set AcroProcessRef 	= Nothing
End Sub

'// 	Set all object variables to Nothing
Call SetObjectInitialStates


'//	Create Core object
Set AcrobatCore = CreateObject("AdobePDFX32.Core")

'//	Get a collection of Acrobat processes
Set AcroProcessCol = AcrobatCore.GetAcrobatProcesses

'//	Enumerate the IAcrobatProcessCollection collection
'//	There can be atleast two available, the IAcrobatProcess interface
'//		that you should reference should have a valid MainWindowHandle
'//		property not equal to zero.

Msgbox "Acrobat Reader Process Count: " & AcroProcessCol.Count
For Each AcroProcess In AcroProcessCol

	'// Reference the interface on condition.

	If AcroProcess.MainWindowHandle <> 0 Then

		Set AcroProcessRef = AcroProcess
		
		Exit For
	End If

Next


'// Display the IAcrobatProcess Interface referenced above.
If Not AcroProcessRef Is Nothing Then

	Msgbox ( AcroProcessRef.Name & vbCrlf & AcroProcessRef.ProcessId & vbCrlf & AcroProcessRef.MainWindowHandle)

End If

'// Obtain the list of open PDF files 
If Not AcroProcessRef Is Nothing Then

	'// Get a collection of IAcrobatPDFFile
	Set AcroPDFFileCol = AcrobatCore.GetAcrobatPDFFiles(AcroProcessRef)
	

	'// Enumerate the IAcrobatPDFFileCollection collection
	Msgbox "Acrobat Reader Open PDF Count: " & AcroPDFFileCol.Count
	For Each AcroPDFFile In AcroPDFFileCol
		
		'// Display the FullName of each opened PDF document.

		Msgbox ( AcroPDFFile.FullName )
	Next
	
End If

Open in new window

AdobePDFX32.dll
0
 
nffvrxqgrcfqvvcCommented:
Pleased to announce Acroof 1.0.0.0 Release Enjoy Tom

changes_acroof
Acroof Revision History

Acroof 1.0.0.0: 4/5/2011      - Stable: XP,Vista,Windows 7
- New: Added new detection scheme
- New: Added Win32 file path support for Vista or later operating systems.
- New: Added Win32 file path support for Window XP operating system.
- New: Added Win32 file path support for network paths.
- New: Added MUP and LanmanRedirector support in Windows XP operating system.
- New: Added runtime functionality verification
- Fix: Removed various class/interfaces no longer required with the new detection scheme.
- Some minor changes and improvements
Dim oCore       '//	Acroof.Core
  Dim oFile       '//	IPDFFile
  Dim oFileCol    '//	IPDFFileCollection
  
  Set oCore = CreateObject("Acroof.Core")

  If oCore.IsCoreSupported Then
    Set oFileCol = oCore.GetOpenPDFFiles
      If oFileCol.Count <> 0 Then
        For Each oFile In oFileCol
          MsgBox oFile.FullName
        Next
      Else
        MsgBox "No files opened"
      End If
  End If

Open in new window

Acroof-1000.zip
0
 
TommySzalapskiAuthor Commented:
I really appreciate all the time you put into this, but I think you missed the part where I said I only have Acrobat Reader and so I can't call the Acrobat API at all.

I set a reference to your dll and it lets me declare
Dim oCore As Acroof.Core
So the library is running and connected but when it gets to the line
Set oCore = CreateObject("Acroof.Core")
It says it cannot create the object (which is what I expected to see)

If I set references to every Acro* thing in there it still won't let this line work (which I tried from your other post just to prove the point). It gives the same error.
Set AcrobatCore = CreateObject("AdobePDFX32.Core")

I'm sorry you spent so much time on this, but I still can't call any Acrobat ATIs.
0
 
nffvrxqgrcfqvvcCommented:
It doesn't use the acrobat type library only internal windows API... You have to run RegSvr32 on acroof.dll before you can use it
0
 
TommySzalapskiAuthor Commented:
I see. Yes that works nicely. Thanks a lot. The only problem is that it requires Admin privaleges to run, but since that wasn't specified in the original question, I won't count it against you. Post the code that you are using in the current dll, please, and then I'll close this out.
0
 
TommySzalapskiAuthor Commented:
Admin privaleges to set up I mean, not run. Once set up, I know it will run for anybody.
0
 
nffvrxqgrcfqvvcCommented:
<< The only problem is that it requires Admin privaleges to run, but since that wasn't specified in the original question, I won't count it against you. >>

1) Yes and No. You can use per user registration without admin rights and still use the library
2) Count it against me? If you mean points they don't really concern me, I come here to help others
3) The source code not going to happen because it's not a couple lines of code
0
 
TommySzalapskiAuthor Commented:
You can use per user registration without admin rights and still use the library
I had to elevate to admin to register the dll at all.

The source code not going to happen because it's not a couple lines of code
Then why did you bother writing it? This site is supposed to be for experts to freely exchange knowledge. If I wanted a third party app, then you could have read that I had one already. Your library is very inflexible. What if I want it to do something slightly different later? The original question specifically asked for a code solution.
0
 
nffvrxqgrcfqvvcCommented:
< Do you want me to create library that you can use from script?  >
TommySzalapski: Sure.


You can delete this question if it's not up to your standards
0
 
TommySzalapskiAuthor Commented:
I am a very active member on this site. I would prefer if you ask me if I did something on purpose before assuming I messed something up. I needed a code solution (as a library or not, but code nonetheless) and Sid got the closest to providing one. I accepted all the most useful posts and spent some time choosing them. Now I have to do it all again.
0
 
TommySzalapskiAuthor Commented:
I see. His post wasn't asking for a response, but I understand the notion of the system flagging it. In the future, I will make sure mine is the last post when I close.
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

  • 15
  • 11
  • 8
Tackle projects and never again get stuck behind a technical roadblock.
Join Now