Search code examples
c#winapisendmessagewin32gui

Get Treeview(SysTreeView32) items text using win32 api


I am writing an application to automate some repetitive tasks at my job. One of the task I wish to do, is to be able to automate the process of creating a recovery drive from "RecoveryDrive.exe" in windows 10. All the process is done, but at one step, a human need to select the drive in a SysTreeView32 control.

I have tried to find how to get the text of the current selected treeNodeItem.

I have the handle of the control, but when I try to read it, using a code sample found online, the recoveryDrive application crash.

I'm suspecting this have to do with 64bits/32bits mismatch with the api methods I'm using and maybe ASCI and Unicode encoding mismatch ... I also think I need to use LocalAlloc inside of the target app Handle or memory

here is the pasteBin of the code in the present state.

It also have the 3 page I have based my code from. The app crash in the GetTreeItemText function when I'm using the sendMessage.

I have found some example on how to do this in C++, but I don't really understand it.

 public static string GetTreeItemText(IntPtr treeViewHwnd, IntPtr hItem)
            {
                int ret;
                TVITEM tvi = new TVITEM();
                IntPtr pszText = LocalAlloc(0x40, MY_MAXLVITEMTEXT);

                tvi.mask = TVIF_TEXT;
                tvi.hItem = hItem;
                tvi.cchTextMax = MY_MAXLVITEMTEXT;
                tvi.pszText = pszText;

                ret = SendMessageTVI(treeViewHwnd, TVM_GETITEM, 0, ref tvi);
                string buffer = Marshal.PtrToStringUni((IntPtr)tvi.pszText,
                MY_MAXLVITEMTEXT);

                //char[] arr = buffer.ToCharArray(); //<== use this array to look at the bytes in debug mode

                LocalFree(pszText);
                return buffer;
            }

Solution

  • The LPARAM of the TVM_GETITEM message is a pointer to a TVITEM structure. The thing is, that structure MUST be allocated in the same process that owns the TreeView control. So, when sending TVM_GETITEM across process boundaries, you must use VirtualAllocEx() to allocate the TVITEM and its pszText buffer in the address space of the target process, and then use WriteProcessMemory()/ReadProcessMemory() to write/read that structure's data.

    Try something like this (you can find declarations for the Win32 API functions used at PInvoke.net):

    public static string GetTreeItemText(IntPtr treeViewHwnd, IntPtr hItem)
    {
        string itemText;
    
        uint pid;
        GetWindowThreadProcessId(treeViewHwnd, out pid);
    
        IntPtr process = OpenProcess(ProcessAccessFlags.VirtualMemoryOperation | ProcessAccessFlags.VirtualMemoryRead | ProcessAccessFlags.VirtualMemoryWrite | ProcessAccessFlags.QueryInformation, false, pid);
        if (process == IntPtr.Zero)
            throw new Exception("Could not open handle to owning process of TreeView", new Win32Exception());
    
        try
        {
            uint tviSize = Marshal.SizeOf(typeof(TVITEM));
    
            uint textSize = MY_MAXLVITEMTEXT;
            bool isUnicode = IsWindowUnicode(treeViewHwnd);
            if (isUnicode)
                textSize *= 2;
    
            IntPtr tviPtr = VirtualAllocEx(process, IntPtr.Zero, tviSize + textSize, AllocationType.Commit, MemoryProtection.ReadWrite);
            if (tviPtr == IntPtr.Zero)
                throw new Exception("Could not allocate memory in owning process of TreeView", new Win32Exception());
    
            try
            {
                IntPtr textPtr = IntPtr.Add(tviPtr, tviSize);
    
                TVITEM tvi = new TVITEM();
                tvi.mask = TVIF_TEXT;
                tvi.hItem = hItem;
                tvi.cchTextMax = MY_MAXLVITEMTEXT;
                tvi.pszText = textPtr;
    
                IntPtr ptr = Marshal.AllocHGlobal(tviSize);
                try
                {
                    Marshal.StructureToPtr(tvi, ptr, false);
                    if (!WriteProcessMemory(process, tviPtr, ptr, tviSize, IntPtr.Zero))
                        throw new Exception("Could not write to memory in owning process of TreeView", new Win32Exception());
                }
                finally
                {
                    Marshal.FreeHGlobal(ptr);
                }
    
                if (SendMessage(treeViewHwnd, isUnicode ? TVM_GETITEMW : TVM_GETITEMA, 0, tviPtr) != 1)
                    throw new Exception("Could not get item data from TreeView");
    
                ptr = Marshal.AllocHGlobal(textSize);
                try
                {
                    int bytesRead;
                    if (!ReadProcessMemory(process, textPtr, ptr, textSize, out bytesRead))
                        throw new Exception("Could not read from memory in owning process of TreeView", new Win32Exception());
    
                    if (isUnicode)
                        itemText = Marshal.PtrToStringUni(ptr, bytesRead / 2);
                    else
                        itemText = Marshal.PtrToStringAnsi(ptr, bytesRead);
                }
                finally
                {
                    Marshal.FreeHGlobal(ptr);
                }
            }
            finally
            {
                VirtualFreeEx(process, tviPtr, 0, FreeType.Release);
            }
        }
        finally
        {
            CloseHandle(process);
        }
    
        //char[] arr = itemText.ToCharArray(); //<== use this array to look at the bytes in debug mode
    
        return itemText;
    }