What technique does Snoop uses to inspect a WPF application

4.1k views Asked by At

Snoop, the spy utility, uses some powerful technique (probably some sort of reflection) to inspect a running WPF application. Most interesting is the fact, that Snnop is able to readout the entire object structure.

A few days ago I downloaded the Snoop source code and spent some time on studying the internal behavior. Unfortunately, I couldn't find out yet how Snoop is doing these things, so I hope that anybody can help me out.

At work I am currently writing a Coded UI Testing-Framework and it would be fantastic if I had access to the application's object structures because this would allow me to not only assert the UI state.

UPDATE:

This is the code needed:

string filePath = "WpfApp.exe";
AppDomain appDomain = AppDomain.CurrentDomain;
byte[] bytes = System.IO.File.ReadAllBytes(filePath);
Assembly ass = appDomain.Load(bytes);
ass.EntryPoint.Invoke(null, new object[] { });
IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
Window w = System.Windows.Interop.HwndSource.FromHwnd(handle).RootVisual as Window;

This is already a big help for me, but it is also interesting to find out, how Snoop injects itself into another process.

4

There are 4 answers

24
Kess On BEST ANSWER

You can accomplish what Snoop does by using the WPF VisualTreeHelper and/or the LogicalTreeHelper. Once you get a hold of any visual element, you can pretty much traverse its entire visual tree to see all the elements it contains. Visual tree helper here

So in your UI test, grab the main window and traverse its visual tree to find any element you want and then perform any validations or operations you want on that element.

Furthermore, you may be able to use System.Diagnostics.Process.MainWindowHandle to get the windows handle from an existing process and then use the window's handle to create a wpf window. Its been a while so I dont remember the specifics without doing more research. The code below may help:

Window window = (Window)System.Windows.Interop.HwndSource.FromHwnd(process.MainWindowHandle).RootVisual;
2
Dennis Kassel On

UPDATE:

Okay, I found the basic code location, that is used by Snoop to provide the injection ability. To my astonishment that code is written C++/CLI. Probably there is a reason for.

And that is the code (I hope that it is okay to post it here):

//-----------------------------------------------------------------------------
//Spying Process functions follow
//-----------------------------------------------------------------------------
void Injector::Launch(System::IntPtr windowHandle, System::String^ assembly, System::String^ className, System::String^ methodName)
{
    System::String^ assemblyClassAndMethod = assembly + "$" + className + "$" + methodName;
    pin_ptr<const wchar_t> acmLocal = PtrToStringChars(assemblyClassAndMethod);

    HINSTANCE hinstDLL; 

    if (::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&MessageHookProc, &hinstDLL))
    {
        LogMessage("GetModuleHandleEx successful", true);
        DWORD processID = 0;
        DWORD threadID = ::GetWindowThreadProcessId((HWND)windowHandle.ToPointer(), &processID);

        if (processID)
        {
            LogMessage("Got process id", true);
            HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
            if (hProcess)
            {
                LogMessage("Got process handle", true);
                int buffLen = (assemblyClassAndMethod->Length + 1) * sizeof(wchar_t);
                void* acmRemote = ::VirtualAllocEx(hProcess, NULL, buffLen, MEM_COMMIT, PAGE_READWRITE);

                if (acmRemote)
                {
                    LogMessage("VirtualAllocEx successful", true);
                    ::WriteProcessMemory(hProcess, acmRemote, acmLocal, buffLen, NULL);

                    _messageHookHandle = ::SetWindowsHookEx(WH_CALLWNDPROC, &MessageHookProc, hinstDLL, threadID);

                    if (_messageHookHandle)
                    {
                        LogMessage("SetWindowsHookEx successful", true);
                        ::SendMessage((HWND)windowHandle.ToPointer(), WM_GOBABYGO, (WPARAM)acmRemote, 0);
                        ::UnhookWindowsHookEx(_messageHookHandle);
                    }

                    ::VirtualFreeEx(hProcess, acmRemote, 0, MEM_RELEASE);
                }

                ::CloseHandle(hProcess);
            }
        }
        ::FreeLibrary(hinstDLL);
    }
}
0
Christian Findlay On

The answer above doesn't work for me. It seems a bit vague. I expanded on accepted answer a little with this code:

    var allProcesses = Process.GetProcesses();
    var filteredProcess = allProcesses.Where(p => p.ProcessName.Contains(ProcessSearchText)).First();
    var windowHandle = filteredProcess.MainWindowHandle;
    var hwndSource = HwndSource.FromHwnd(windowHandle);

This answer seems more complete and will work for others if the accepted answer works for anyone. However, this the last line of this code returns null for me.

1
Wouter Schut On

Snoop doesn't inspect a WPF from the outside. It injects itself into the application and actually adds the magnify or snoop window to it. Thats also why when you exit snoop the inspection windows actually stay open.

So the 'inspection' code simply inspects the window it wants and it can use all avaible WPF functions to do so. Like the VisualTreeHelper and LogicalTreeHelper as mentioned here earlier.

For a small test framework i build i injected code to add a small proxy object so i can control the application easily (press buttons, change values, execute functions on viewmodels etc).