Results 1 to 8 of 8

Thread: Need help getting full file names

  1. #1

    Thread Starter
    Fanatic Member Peter Porter's Avatar
    Join Date
    Jul 2013
    Posts
    532

    Need help getting full file names

    I need complete file names, but the code below only gets up to 24 characters. I'm not sure what's the problem. Just know it's not the buffer. I have to get this code to play nice since it's part of a larger program.

    VB.NET 2010
    Configuration Manager: x64

    Code:
    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Public Class Form1
    
        Public Const LVM_FIRST As UInteger = &H1000
        Public Const LVM_GETITEMCOUNT As UInteger = LVM_FIRST + 4
        Public Const LVM_GETITEM = (LVM_FIRST + 5)
        Public Const LVM_GETITEMW As UInteger = LVM_FIRST + 75
        Public Const LVM_GETITEMPOSITION As UInteger = LVM_FIRST + 16
        Public Const LVM_GETITEMTEXT = (LVM_FIRST + 45)
        Public Const PROCESS_VM_OPERATION As UInteger = &H8
        Public Const PROCESS_VM_READ As UInteger = &H10
        Public Const PROCESS_VM_WRITE As UInteger = &H20
        Public Const MEM_COMMIT As UInteger = &H1000
        Public Const MEM_RELEASE As UInteger = &H8000
        Public Const MEM_RESERVE As UInteger = &H2000
        Public Const PAGE_READWRITE As UInteger = 4
        Public Const LVIF_TEXT As Integer = &H1
    
        <DllImport("kernel32.dll")> _
        Public Shared Function VirtualAllocEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As UInteger, ByVal flAllocationType As UInteger, ByVal flProtect As UInteger) As IntPtr
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function VirtualFreeEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As UInteger, ByVal dwFreeType As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function WriteProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, ByVal nSize As Integer, ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function ReadProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, ByVal nSize As Integer, ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger, ByVal bInheritHandle As Boolean, ByVal dwProcessId As UInteger) As IntPtr
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function FindWindow(ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.dll")> _
        Public Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, ByRef dwProcessId As UInteger) As UInteger
        End Function
    
    
        Public Structure LVITEM
            Public mask As Integer
            Public iItem As Integer
            Public iSubItem As Integer
            Public state As Integer
            Public stateMask As Integer
            Public placeholder1 As Integer
            Public pszText As Integer
            Public placeholder2 As Integer
            Public cchTextMax As Long
            Public iImage As Integer
        End Structure
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Me.Size = New System.Drawing.Size(542, 622)
    
            Dim listview As New ListView
            With listview
                .Size = New System.Drawing.Size(500, 560)
                .Location = New System.Drawing.Point(13, 13)
                .View = View.Details
                .Font = New Font("Microsoft Sans Serif", 11)
                .Columns.Add("#", 20)
                .Columns.Add("Name", 150)
            End With
            Me.Controls.Add(listview)
    
            'Get the handle of the desktop listview
            Dim vHandle As IntPtr = FindWindow("Progman", "Program Manager")
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", Nothing)
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView")
    
            'Get total count of the icons on the desktop
            Dim vItemCount As Integer = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0)
    
            Dim itemIndex As Integer
    
            Dim vProcessId As UInteger
            GetWindowThreadProcessId(vHandle, vProcessId)
            Dim vProcess As IntPtr = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, vProcessId)
            Dim vPointer As IntPtr = VirtualAllocEx(vProcess, IntPtr.Zero, 255, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
    
            Try
                For j As Integer = 0 To vItemCount - 1
    
                    'Declare and populate the LVITEM structure.
                    Dim vBuffer As Byte() = New Byte(255) {}
                    Dim vItem As New LVITEM()
                    vItem.iSubItem = 0
                    vItem.cchTextMax = vBuffer.length
                    vItem.pszText = vPointer
                    vItem.mask = LVIF_TEXT
                    vItem.iItem = itemIndex
    
                    Dim ptrvItem As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vItem))
                    Marshal.StructureToPtr(vItem, ptrvItem, False)
    
                    'Write the vItem structure into the desktops process memory
                    Dim vNumberOfBytesRead As UInteger = 0
                    WriteProcessMemory(vProcess, vPointer, ptrvItem, Marshal.SizeOf(vItem), vNumberOfBytesRead)
    
                    'Get the item at itemIndex
                    SendMessage(vHandle, LVM_GETITEMTEXT, j, vPointer)
    
                    'Read item text from desktop process memory
                    ReadProcessMemory(vProcess, vPointer, Marshal.UnsafeAddrOfPinnedArrayElement(vBuffer, 0), 255, vNumberOfBytesRead)
    
                    Dim itemText As String = Encoding.UTF8.GetString(vBuffer, 0, CInt(vNumberOfBytesRead))
    
                    Dim lvi As New ListViewItem
                    lvi.Text = CStr(j + 1)
                    lvi.SubItems.Add(itemText)
    
                    listview.Items.Add(lvi)
                    listview.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent)
                Next
            Finally
                VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE)
                CloseHandle(vProcess)
            End Try
        End Sub
    
    End Class
    Last edited by Peter Porter; Nov 7th, 2017 at 03:38 PM.

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Need help getting full file names

    I want to say most of the same things I said here,, and have most of the same questions.

    Have you looked at the bytes you are reading and determined if the buffer even looks like a String? You are looking into an undocumented memory construct, that means you could have bad information about where the information you want is located. You're using the encoding UTF7, which is a very strange choice and not likely the one Microsoft is using for their data.

    Besides, it's not logical to expect LVM_GETITEMTEXT to return a full item path. That will get you the Text for the item, but that will be just a file name, potentially with the extension not present. Let's look at the documentation for funsies.

    https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx

    First off, this is dangerous as heck:
    Code:
    vPointer.ToInt32()
    This is why you're having a lot of trouble when switching platforms. vPointer is an IntPtr. It'll be 32-bit on an x86 platform and 64-bit on an x64 platform. But you're manually converting it down to a 32-bit pointer. This makes your code only compatible with x86 in a way that isn't clear.

    So one hypothesis: if your Windows is 64-bit, then I'd expect the Shell data structures to be 64-bit and I'd also expect they intend to interact with 64-bit calls. But you're an x86 process trying to read those values: it could be this creates some issues with memory alignment and other things. The best way is to look at the bytes you get and see if they seem abnormal.

    But let's keep going and see if there's something else odd going on.

    lParam is supposed to be a pointer to an LVITEM. I'm again concerned about x86 vs. x64. You've declared 'pszText' as an Integer. But 'pszText' is a pointer to a null-terminated string in API. That means if your system is x64, it might be a 64-bit value, even if your specific program is 32-bit. Remember you're talking to Explorer.exe, not your program, so it could be different. If that's true, then the in-memory alignment of your LVITEM will be out of whack compared to what the other program is using, and it could cause subtle problems.

    If it were me, I'd have declared 'pszText' as String, or maybe a pre-initialized StringBuilder. 'IntPtr' also comes to mind, but the trick here is you're an x86 process so IntPtr won't magically change to 64-bit. In general, PInvoke is pretty good at interpreting String as meaning "a pointer to a null-terminated String" but when it screws up, StringBuilder gives it a stronger hint.

    Anyway, it's sort of strange you do that, then call ReadProcessMemory() to try and get what you think is the text.

    I'd sort of expect the code to look something like this:
    Code:
    Dim item As New LVITEM()
    ' set up the structure for the call
    
    Dim pointerToItem As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(item))
    Marshal.StructureToPtr(item, pointerToItem, False)
    
    SendMessage(handle, LVM_GETITEMTEXT, j, pointerToItem)
    
    Marshal.PtrToStructure(pointerToItem, item)
    
    Dim text As String = item.pszText
    The use of ReadProcessMemory() and WriteProcessMemory() seems like it's probably what's breaking here. Nothing about LVM_GETITEMTEXT says you have to create the LVITEM structure in the same process memory. Here's what I think happens when you call WriteProcessMemory:

    The pointer you get back from Marshal.AllocHGlobal is in terms of YOUR address space, or possibly the GLOBAL address space. But WriteProcessMemory() very specifically expects lpBaseAddress to be in terms of the target process's address space. So it's very, very likely you write your LVITEM to essentially a random address, and it's interesting that you haven't corrupted anything yet.

    The ReadProcessMemory() is equally confusing. What I think it's doing is "read up to 255 bytes starting at the location where I wrote that LVITEM". That LVITEM is probably only about 40-50 bytes, so you'll get a lot of random data for fun at the end. Then you try to interpret those bytes as a String. That's... going to behave randomly if I'm right. Here's a good way to tell: are the first bytes &H00 and &H01? That's LVIF_TEXT, the 'mask' variable at the start of the LVITEM structure. If that's true, the rest of the story is "you aren't reading actual text, you're reading the bytes of the LVITEM and the String 'ends' the first place 0x00 0x00 happens."

    I'm guessing at a lot of this, but this is what seems most likely to me. I don't think you're properly accessing the API, and I'm not sure it's going to be easy/possible from an x86 application that wants to speak to the x64 Windows Shell.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  3. #3

    Thread Starter
    Fanatic Member Peter Porter's Avatar
    Join Date
    Jul 2013
    Posts
    532

    Re: Need help getting full file names

    I believe my coding and re-coding broke things to the point that it would only run in x86 mode. Strangely I can get the code to run in x64 today. I've updated it above.

    Quote Originally Posted by Sitten Spynne View Post
    ...it's not logical to expect LVM_GETITEMTEXT to return a full item path. That will get you the Text for the item, but that will be just a file name, potentially with the extension not present.
    I know LVM_GETITEMTEXT only returns file names. That's what I want, but even under 64-bit, long file names are still cut off after 24 characters.

    Originally the code above was for getting the xy coordinates of desktop icons with ReadProcessMemory() and WriteProcessMemory(). I thought I could bend it for full file names to go along with this info.

    Below I tried your code, but it didn't work. No errors. Nothing in Output, but Marshal is highlighted in grey. The structure might be causing the problem.

    Oops! Corrected a few typos. Should work now under x64 now.

    Code:
    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Public Class Form1
    
        Public Const LVM_FIRST As UInteger = &H1000
        Public Const LVM_GETITEMCOUNT As UInteger = LVM_FIRST + 4
        Public Const LVM_GETITEM = (LVM_FIRST + 5)
        Public Const LVM_GETITEMW As UInteger = LVM_FIRST + 75
        Public Const LVM_GETITEMPOSITION As UInteger = LVM_FIRST + 16
        Public Const LVM_GETITEMTEXT = (LVM_FIRST + 45)
        Public Const PROCESS_VM_OPERATION As UInteger = &H8
        Public Const PROCESS_VM_READ As UInteger = &H10
        Public Const PROCESS_VM_WRITE As UInteger = &H20
        Public Const MEM_COMMIT As UInteger = &H1000
        Public Const MEM_RELEASE As UInteger = &H8000
        Public Const MEM_RESERVE As UInteger = &H2000
        Public Const PAGE_READWRITE As UInteger = 4
        Public Const LVIF_TEXT As Integer = &H1
    
        <DllImport("kernel32.dll")> _
        Public Shared Function VirtualAllocEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As UInteger, ByVal flAllocationType As UInteger, ByVal flProtect As UInteger) As IntPtr
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function VirtualFreeEx(ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As UInteger, ByVal dwFreeType As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function WriteProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, ByVal nSize As Integer, ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function ReadProcessMemory(ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer As IntPtr, ByVal nSize As Integer, ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")> _
        Public Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger, ByVal bInheritHandle As Boolean, ByVal dwProcessId As UInteger) As IntPtr
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function FindWindow(ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.DLL")> _
        Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.dll")> _
        Public Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, ByRef dwProcessId As UInteger) As UInteger
        End Function
    
    
        Public Structure LVITEM
            Public mask As Integer
            Public iItem As Integer
            Public iSubItem As Integer
            Public state As Integer
            Public stateMask As Integer
            Public placeholder1 As Integer
            Public pszText As string
            Public placeholder2 As Integer
            Public cchTextMax As Long
            Public iImage As Integer
        End Structure
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Me.Size = New System.Drawing.Size(542, 622)
    
            Dim listview As New ListView
            With listview
                .Size = New System.Drawing.Size(500, 560)
                .Location = New System.Drawing.Point(13, 13)
                .View = View.Details
                .Font = New Font("Microsoft Sans Serif", 11)
                .Columns.Add("#", 20)
                .Columns.Add("Name", 150)
            End With
            Me.Controls.Add(listview)
    
            'Get the handle of the desktop listview
            Dim vHandle As IntPtr = FindWindow("Progman", "Program Manager")
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", Nothing)
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView")
    
            'Get total count of the icons on the desktop
            Dim vItemCount As Integer = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0)
    
            Dim vProcessId As UInteger
            GetWindowThreadProcessId(vHandle, vProcessId)
            Dim vProcess As IntPtr = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, vProcessId)
            Dim vPointer As IntPtr = VirtualAllocEx(vProcess, IntPtr.Zero, 255, MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
    
            Try
                For j As Integer = 0 To vItemCount - 1
    
                    'Declare and populate the LVITEM structure.
                    Dim vItem As New LVITEM()
                    vItem.mask = LVIF_TEXT
                    vItem.cchTextMax = 255
                    vItem.iSubItem = 0
                    vItem.pszText = vPointer
    
                    Dim pointerToItem As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vItem))
                    Marshal.StructureToPtr(vItem, pointerToItem, False)
    
                    SendMessage(Handle, LVM_GETITEMTEXT, j, pointerToItem)
    
                    Marshal.PtrToStructure(pointerToItem, vItem)
    
                    Dim itemText As String = vItem.pszText
    
                    Dim lvi As New ListViewItem
                    lvi.Text = CStr(j + 1)
                    lvi.SubItems.Add(itemText)
    
                    listview.Items.Add(lvi)
                    listview.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent)
                Next
            Finally
                VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE)
                CloseHandle(vProcess)
            End Try
        End Sub
    
    End Class
    Last edited by Peter Porter; Nov 7th, 2017 at 05:05 PM.

  4. #4
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Need help getting full file names

    With regards to your original code (Post#1 before you edited it!!...)


    Your LVITEM structure has the line
    Code:
    vItem.pszText = vPointer
    When you send the LVM_GETITEMTEXT message, the above line tells Windows to write the Item's text to the memory pointed to by vPointer.

    However, you also store the LVITEM structure in the memory pointed to by vPointer. That means that the Item's text will overwrite the LVITEM structure.

    Unfortunately, it looks like SendMessage updates the structure when it is called; specifically, it looks like it updates the LVITEM's pszText field. That field is at byte offset 24 from the start of the memory pointed to by vPointer, and that's why your strings are being truncated to 24 characters.

    You need to specify a different area of memory for the string to be written to (in Explorer's memory space), so it's not overwriting the LVITEM structure.

  5. #5

    Thread Starter
    Fanatic Member Peter Porter's Avatar
    Join Date
    Jul 2013
    Posts
    532

    Re: Need help getting full file names

    Thanks, Inferrd.

    Not sure how to code this. I'm burnt out!

  6. #6
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: Need help getting full file names

    (Oh, good, someone who actually knows what they're doing posted! So I can erase my, "I don't know what I'm doing and haven't had a chance to experiment" post!)
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  7. #7
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Need help getting full file names

    Quote Originally Posted by Sitten Spynne View Post
    (Oh, good, someone who actually knows what they're doing posted! So I can erase my, "I don't know what I'm doing and haven't had a chance to experiment" post!)
    I wouldn't claim to know what I'm doing. It's more than 10 years since I programmed in anger against the Win32 API, and that was in VB6. I just read Peter's code and noticed the use of the same memory pointer for what should be 2 separate objects.

    Quote Originally Posted by Peter Porter View Post
    Thanks, Inferrd.

    Not sure how to code this. I'm burnt out!
    Peter,
    So now we've established I'm making this up as I go along ;-).......

    You could use VirtualAllocEx to reserve 2 areas of memory in the remote process. Write your LVITEM into one area, and use the second area as the buffer for the Item's text. You'd have to pass the pointer to the second area to the LVITEM's pszText field, then read the contents of that area back into your local process memory (using ReadProcessMemory) before decoding to a String.

    Alternatively, you could just reserve a larger area of memory in the remote process, write the LVITEM structure to the start of the area and use the remainder as the buffer that receives the Item's text.


    The former approach is probably the more sensible way, but no one's ever accused me of having any sense so I just tried it using the single area approach.


    I ask for 1024 bytes of memory to be reserved in the remote process, place the LVITEM structure at the start of it, and use the upper 512 bytes for the buffer to write the Item's text into. That upper portion of reserved memory is copied back to your local process so that you can retrieve the text.

    Notice that I changed the definition of the LVITEM structure based on the information here. It looks like the "proper" definition changes based on the version of IE and Windows you are using, and I've completely ignored those portions. Shouldn't affect what you are doing here, but you would probably want to leave a gap between the end of the structure and the Text buffer in case Windows tries to fill in the missing fields (unlikely, but not impossible).

    I've also overloaded the SendMessage function so that the lParam argument can be an integer or an IntPtr (both are used in your code).

    When it comes to Text Encodings, never use UTF-7 unless you really have to. It's a rather specialized encoding which has security issues.
    UTF-8 is also not the Encoding you want. Windows APIs return text using what Microsoft term Unicode or ANSI. You are using the ANSI versions of the APIs so you need System.Text.Encoding.Default.

    That said, you really should change your code to use the Unicode versions of the APIs. If your file names contain any foreign characters, ANSI won't be able to decode them correctly.


    Anyways, here you go. I'm too tired to explain it all, but if you understand the code you posted earlier then you should be able to follow this. If you have any questions then please ask.

    Compiled to .NET FW 4.0, x64 on Windows 7 64bit, tested working:
    Code:
    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Public Class Form1
    
    
        Public Const LVM_FIRST As UInteger = &H1000
        Public Const LVM_GETITEMCOUNT As UInteger = LVM_FIRST + 4
        Public Const LVM_GETITEM = (LVM_FIRST + 5)
        Public Const LVM_GETITEMW As UInteger = LVM_FIRST + 75
        Public Const LVM_GETITEMPOSITION As UInteger = LVM_FIRST + 16
        Public Const LVM_GETITEMTEXT = (LVM_FIRST + 45)
        Public Const PROCESS_VM_OPERATION As UInteger = &H8
        Public Const PROCESS_VM_READ As UInteger = &H10
        Public Const PROCESS_VM_WRITE As UInteger = &H20
        Public Const MEM_COMMIT As UInteger = &H1000
        Public Const MEM_RELEASE As UInteger = &H8000
        Public Const MEM_RESERVE As UInteger = &H2000
        Public Const PAGE_READWRITE As UInteger = 4
        Public Const LVIF_TEXT As Integer = &H1
    
        <DllImport("kernel32.dll")>
        Public Shared Function VirtualAllocEx(ByVal hProcess As IntPtr,
                                              ByVal lpAddress As IntPtr,
                                              ByVal dwSize As UInteger,
                                              ByVal flAllocationType As UInteger,
                                              ByVal flProtect As UInteger) As IntPtr
        End Function
    
        <DllImport("kernel32.dll")>
        Public Shared Function VirtualFreeEx(ByVal hProcess As IntPtr,
                                             ByVal lpAddress As IntPtr,
                                             ByVal dwSize As UInteger,
                                             ByVal dwFreeType As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")>
        Public Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
        End Function
    
        <DllImport("kernel32.dll")>
        Public Shared Function WriteProcessMemory(ByVal hProcess As IntPtr,
                                                  ByVal lpBaseAddress As IntPtr,
                                                  ByVal lpBuffer As IntPtr,
                                                  ByVal nSize As Integer,
                                                  ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")>
        Public Shared Function ReadProcessMemory(ByVal hProcess As IntPtr,
                                                 ByVal lpBaseAddress As IntPtr,
                                                 ByVal lpBuffer As IntPtr,
                                                 ByVal nSize As Integer,
                                                 ByRef vNumberOfBytesRead As UInteger) As Boolean
        End Function
    
        <DllImport("kernel32.dll")>
        Public Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger,
                                           ByVal bInheritHandle As Boolean,
                                           ByVal dwProcessId As UInteger) As IntPtr
        End Function
    
        <DllImport("user32.DLL")>
        Public Shared Function SendMessage(ByVal hWnd As IntPtr,
                                           ByVal Msg As UInteger,
                                           ByVal wParam As Integer,
                                           ByVal lParam As Integer) As Integer
        End Function
    
        <DllImport("user32.DLL")>
        Public Shared Function SendMessage(ByVal hWnd As IntPtr,
                                           ByVal Msg As UInteger,
                                           ByVal wParam As Integer,
                                           ByVal lParam As IntPtr) As Integer
        End Function
    
    
        <DllImport("user32.DLL")>
        Public Shared Function FindWindow(ByVal lpszClass As String,
                                          ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.DLL")>
        Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr,
                                            ByVal hwndChildAfter As IntPtr,
                                            ByVal lpszClass As String,
                                            ByVal lpszWindow As String) As IntPtr
        End Function
    
        <DllImport("user32.dll")>
        Public Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr,
                                                        ByRef dwProcessId As UInteger) As UInteger
        End Function
    
    
        Public Structure LVITEM
            Public mask As UInteger
            Public iItem As Integer
            Public iSubItem As Integer
            Public state As UInteger
            Public stateMask As UInteger
            Public pszText As IntPtr
            Public cchTextMax As Integer
            Public iImage As Integer
        End Structure
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Me.Size = New System.Drawing.Size(542, 622)
    
            Dim listview As New ListView
            With listview
                .Size = New System.Drawing.Size(500, 560)
                .Location = New System.Drawing.Point(13, 13)
                .View = View.Details
                .Font = New Font("Microsoft Sans Serif", 11)
                .Columns.Add("#", 20)
                .Columns.Add("Name", 150)
            End With
            Me.Controls.Add(listview)
            listview.BringToFront()
    
            'Get the handle of the desktop listview
            Dim vHandle As IntPtr = FindWindow("Progman", "Program Manager")
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", Nothing)
            vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView")
    
            'Get total count of the icons on the desktop
            Dim vItemCount As Integer = SendMessage(vHandle, LVM_GETITEMCOUNT, 0, 0)
    
            Dim itemIndex As Integer
    
            Dim remoteBufferSize As Integer = 1024
            Dim textBufferOffset As Integer = 512
            Dim textBufferSize As Integer = remoteBufferSize - textBufferOffset ' 512 Bytes
    
    
            Dim vProcessId As UInteger
            GetWindowThreadProcessId(vHandle, vProcessId)
            Dim vProcess As IntPtr = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, vProcessId)
    
            Dim vPointer As IntPtr = VirtualAllocEx(vProcess, IntPtr.Zero, CUInt(remoteBufferSize), MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE) ''''''''''''''''''
            Dim vTextPointer As IntPtr = IntPtr.Add(vPointer, textBufferOffset)
    
            Dim localTextBuffer As Byte() = New Byte(textBufferSize - 1) {}
            Try
                For j As Integer = 0 To vItemCount - 1
    
                    'Declare and populate the LVITEM structure.
                    Dim vItem As New LVITEM()
                    vItem.iSubItem = 0
                    vItem.cchTextMax = textBufferSize ' 512 bytes 
                    vItem.pszText = vTextPointer
                    vItem.mask = LVIF_TEXT
                    vItem.iItem = itemIndex
    
                    Dim ptrvItem As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(vItem))
                    Marshal.StructureToPtr(vItem, ptrvItem, False)
    
                    'Write the vItem structure into the desktops process memory
                    Dim vNumberOfBytesWritten As UInteger = 0
                    WriteProcessMemory(vProcess, vPointer, ptrvItem, Marshal.SizeOf(vItem), vNumberOfBytesWritten)
    
                    'Get the item at itemIndex
                    'If charCount returns 0 then the buffer is too small
                    Dim charCount As Integer = SendMessage(vHandle, LVM_GETITEMTEXT, j, vPointer)
    
                    'Read item text from desktop process memory
                    Dim vNumberOfBytesRead As UInteger = 0
                    ReadProcessMemory(vProcess, vTextPointer, Marshal.UnsafeAddrOfPinnedArrayElement(localTextBuffer, 0), localTextBuffer.Length, vNumberOfBytesRead)
    
                    Dim itemText As String = Encoding.Default.GetString(localTextBuffer, 0, charCount)
    
                    Dim lvi As New ListViewItem
                    lvi.Text = CStr(j + 1)
                    lvi.SubItems.Add(itemText)
    
                    listview.Items.Add(lvi)
                    listview.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent)
                Next
            Finally
                VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE)
                CloseHandle(vProcess)
            End Try
        End Sub
    End Class
    Last edited by Inferrd; Nov 7th, 2017 at 06:50 PM.

  8. #8
    Frenzied Member
    Join Date
    Jul 2011
    Location
    UK
    Posts
    1,335

    Re: Need help getting full file names

    There's an example of using UI Automation to enumerate the Icon names in this SO post.

    I've not studied UI Automation, but I suspect its use would solve a lot of your problems in terms of x86 vs x64 and also text encodings.

    Here's my take on what the code might look like when using UI Automation (with thanks to the original author):
    VB.NET Code:
    1. Imports System.Runtime.InteropServices
    2. Imports System.Windows.Automation
    3.  
    4. Public Class Form1
    5.  
    6.     <DllImport("user32.DLL")>
    7.     Public Shared Function FindWindow(ByVal lpszClass As String,
    8.                                       ByVal lpszWindow As String) As IntPtr
    9.     End Function
    10.  
    11.     <DllImport("user32.DLL")>
    12.     Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr,
    13.                                         ByVal hwndChildAfter As IntPtr,
    14.                                         ByVal lpszClass As String,
    15.                                         ByVal lpszWindow As String) As IntPtr
    16.     End Function
    17.  
    18.  
    19.  
    20.     Private listview As New ListView
    21.  
    22.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    23.         Me.Size = New System.Drawing.Size(542, 622)
    24.  
    25.  
    26.         With listview
    27.             .Size = New System.Drawing.Size(500, 560)
    28.             .Location = New System.Drawing.Point(13, 13)
    29.             .View = View.Details
    30.             .Font = New Font("Microsoft Sans Serif", 11)
    31.             .Columns.Add("#", 20)
    32.             .Columns.Add("Name", 150)
    33.         End With
    34.         Me.Controls.Add(listview)
    35.         listview.BringToFront()
    36.  
    37.  
    38.         Try
    39.             'Get the handle of the desktop listview
    40.             Dim vHandle As IntPtr = FindWindow("Progman", "Program Manager")
    41.             vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SHELLDLL_DefView", Nothing)
    42.             vHandle = FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", "FolderView")
    43.  
    44.             'read the icon names using UI Automation and add them to a Listview
    45.             Dim j As Integer = 0
    46.             For Each iconName As String In GetDesktopIconNames(vHandle)
    47.                 j += 1
    48.                 listview.Items.Add(New ListViewItem({j.ToString, iconName}))
    49.             Next
    50.             listview.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent)
    51.  
    52.         Catch ex As Exception
    53.             MsgBox(ex.Message)
    54.         End Try
    55.  
    56.     End Sub
    57.  
    58.     Private Function GetDesktopIconNames(lvHandle As IntPtr) As List(Of String)
    59.         Dim names As New List(Of String)
    60.  
    61.         ' Get the AutomationElement that represents the window handle...
    62.         Dim el As AutomationElement = AutomationElement.FromHandle(lvHandle)
    63.  
    64.         ' Walk the automation element tree using content view, so we only see
    65.         ' list items, not scrollbars and headers. (Use ControlViewWalker if you
    66.         ' want to traverse those also.)
    67.         Dim walker As TreeWalker = TreeWalker.ContentViewWalker
    68.         Dim child As AutomationElement = walker.GetFirstChild(el)
    69.         Do While child IsNot Nothing
    70.             names.Add(child.Current.Name)
    71.             child = walker.GetNextSibling(child)
    72.         Loop
    73.  
    74.         Return names
    75.     End Function
    76.  
    77.  
    78. End Class


    I think the way that you are retrieving the Desktop Listview Handle is going to bite you in the backside at some point. SS has already pointed out that it fails on his Windows 10 machine. That said, I don't have an alternative approach at the moment. I feel it is worth researching, though.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width