GUI-Test automation: Finding WinForms buttons via pinvoke with c# - c#

I'm building a small GUI-Test automation tool in C# for a application.
One of the functions in the test tool is to close dialogs pops up from the tested application.
The trouble that I have is to find the button to click on without giving the full class name. I have used the FindWindowEx method to get the dialog box and the button that I want to click on. I know the Caption of the button, but the trouble is that I also need to specify the class name for the button. The class name is not always the same, but it looks something like this: "WindowsForms10.BUTTON.app.0.3ce0bb8". For instance is the part in the end "3ce0bb8" different if you start the application locally or via click-once.
So, my question is: How can I find the button with just specifying the first part (that is always the same) of the class like this ""WindowsForms10.BUTTON.app." Or could I solve this in some other way?
The dll import looks like this:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string pszWindow);
My code looks something like this when trying to click the button:
private void SendDialogButtonClick(IntPtr windowHandle, ApplicationStartType applicationStartType)
{
if (applicationStartType == ApplicationStartType.Localy)
buttonClassName = "WindowsForms10.BUTTON.app.0.3ce0bb8";
else if (applicationStartType == ApplicationStartType.ClickOnce)
buttonClassName = "WindowsForms10.BUTTON.app.0.3d893c";
// Find the "&No"-button
IntPtr buttonAndNoHandle = FindWindowEx(windowHandle, IntPtr.Zero, buttonClassName, "&No");
// Send the button click event to the appropriate button found on the dialog
if (buttonAndNoHandle.ToInt64() != 0)
{
SendMessage(new HandleRef(null, buttonAndNoHandle), WM_CLICK, IntPtr.Zero, IntPtr.Zero);
}
}

Yes, that's difficult, the class names are auto-generated. You can't use FindWindowEx(), you have to iterate the controls with EnumChildWindows() and GetClassName().
You could adapt the source code for the Managed Spy tool to make all this a lot easier and cleaner.

Related

How To Programmatically Execute "Clear File Explorer History"

I'm currently creating a WPF application, and like to add as a small side feature the ability to clear the windows file explorer history.
If one were to manually do this operation, it is possible via the file menu within a file explorer window,
as shown here.
My goal is pretty much to programmatically execute the same action as this button does, but I've been unable to find what executable or user32.dll method is behind this operation (if it exists), and been also unsuccessful on finding the full logic behind it (namely, finding what folder and files it targets), to replicate it.
Can you help me?
As the comment by dxiv suggested, you can achieve this via the following:
enum ShellAddToRecentDocsFlags
{
Pidl = 0x001,
Path = 0x002,
PathW = 0x003
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern void SHAddToRecentDocs(ShellAddToRecentDocsFlags flag, string path);
// How To Clear Everything
SHAddToRecentDocs(ShellAddToRecentDocsFlags.Pidl, null);

SHChangeNotify in Windows 10 not updating Quick Access items

It seems like calling SHChangeNotify in Windows does not call an update to the items in the QuickAccess pane (or any custom-namespace folders found on the left side of Explorer). Seems like expanding the tree on the left side to the folder works alright, as well as anything in the main view on the right side.
We are calling the SHChangeNotify from a c# WPF app, though the SHChangeNotify seems to call into our DLL hook in explorer just fine for anything in the right view. This will eventually call into a named-pipe that will hook back into our c# code to call an update to the file or folder's icon.
This is what we are calling from c#:
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
private static extern void SHChangeNotify(
int wEventId,
uint uFlags,
IntPtr dwItem1,
IntPtr dwItem2);
var ptr = Marshal.StringToHGlobalUni(fullPath);
SHChangeNotify((int)SHCNE.SHCNE_UPDATEITEM, (int)(SHCNF.SHCNF_PATHW | SHCNF.SHCNF_FLUSHNOWAIT), ptr, IntPtr.Zero);
Marshal.FreeHGlobal(ptr);
We can assume that all consts and enums are defined correctly.
This is what the icons look like:
Note that the gray icon is the default icon. The green icon in the main window was triggered by calling the function above with the path C:\Users\Test User\Pocket Test. I would think that this should trigger a refresh for both folders.
I've also tried replacing SHCNF_FLUSHNOWAIT with SHCNF_FLUSH. I'm at a loss on how to procede here. Any any ideas on how to force-update the folders on that left pane in Explorer?
The Quick Access virtual folder path, as a string, is shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}, (this guid name is CLSID_HomeFolder).
So, you can force a refresh of all items under this virtual folder with a call to:
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATHW, L"shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}", NULL);
If you want to refresh only a specific set of children, just get the PIDL or the path of these items and call SHChangeNotify(SHCNE_UPDATEITEM, ...) on each.

How to copy content of an opened notepad instance?

I need to get the text found in an notepad process instance that isn't saved on the hard-drive. For example the notepad app is opened and contains a string "This is a notepad Window" but still isn't saved, I wanna get the string inside the notepad without saving it to a file.
I must complete this task, in powershell. But if it can't be done in powershell C# is the next best option. The closest thing to my case I found in an answer on stack overflow is the following code in C#, but I couldn't really figure out how to make use of it
I've tried dumping the memory data of the notepad app, but it wasn't useful.
I've tried creating an com object for the notepad app but it wasn't doable.
Code I talked about:
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
private void btnCopyToNotepad_Click(object sender, EventArgs e)
{
StartNotepad();
Process[] notepads = null;
while (notepads == null || notepads.Length == 0)
{
notepads = Process.GetProcessesByName("notepad");
Thread.Sleep(500);
}
if (notepads.Length == 0) return;
if (notepads[0] != null)
{
Clipboard.SetText(textBox1.Text);
SendMessage(FindWindowEx(notepads[0].MainWindowHandle, new IntPtr(0), "Edit", null), 0x000C, 0, textBox1.Text);
}
}
private static void StartNotepad()
{
Process.Start("notepad.exe");
}
I expect to be able to copy the text from a running notepad instance without saving it on the hard-drive.
As for...
I expect to be able to copy the text from a running notepad instance
without saving it on the hard-drive.
...why are you trying to grab something that you had to put there to start with?
Or... are you saying, some other process started and wrote something to notepad?
If so, then capture why not just content from the other process before it goes to notepad?
Anyway, you can just do something like this directly and avoid all the C# stuff...
Old school...
$wshell = New-Object -ComObject wscript.shell
$wshell.AppActivate((Get-Process -Name notepad).MainWindowTitle)
$wshell.SendKeys("^{A}^{C}")
Or via .Net
[void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
[Microsoft.VisualBasic.Interaction]::AppActivate((Get-Process -Name notepad).MainWindowTitle)
[System.Windows.Forms.SendKeys]::SendWait("^{A}^{C}")
Update for OP
As for …
Any Chance we can get the text from the memory ? Without using the
Send Keys ?
Not without using some other 3rd UI Automation tool, or writing your own or memory dumping (but the last one is a serialization thing, soooo..., it would be just as easy to save the notepad session in a tempo location and call it up later).
There 3rdP tools around for PowerShell UI automation. Be aware some are no longer updated, though they still work.
Still, you'd have to get these on every system you need to hit.
Here is a list:
WASP
UiAutomation
selenium
FlaUI
BitCollectors.UIAutomationLib
AutoIt Scripting Language
https://www.autoitconsulting.com/site/scripting/autoit-cmdlets-for-windows-powershell
Otherwise, you end up digging at this.
Inspect
UI Automation Overview
As per the author, with FlaUI, you would do something like this.
Add-Type -Path "\Documents\WindowsPowerShell\Modules\FlaUI\src\FlaUI.UIA3\bin\Debug\FlaUI.Core.dll"
Add-Type -Path "\Documents\WindowsPowerShell\Modules\FlaUI\src\FlaUI.UIA3\bin\Debug\FlaUI.UIA3.dll"
$app = [FlaUI.Core.Application]::AttachOrLaunch('notepad')
$uia = New-Object FlaUI.UIA3.UIA3Automation
$mw = $app.GetMainWindow($uia)
$Document = $mw.FindFirstChild($uia.ConditionFactory.ByControlType([FlaUI.Core.Definitions.ControlType]::Document))
$mw.Title
$Document.Patterns.Value.Pattern.Value.Value
Sure, SendKeys can be finicky (but easy to use), and the list are more polished, but use similar approaches.

RegisterWindowMessage Possible values

Maybe I am just missing some silly link on the MSDN, but I cannot seem to find the list of possible values RegisterWindowMessage() can take
The only one I can find is "WM_HTML_GETOBJECT". I found this on pinvoke.net.
This however, I believe this crashes my application because what I am trying to get is an IHTMLDialog and not a IHTMLDocument
I have looked at
Message Reference
Message Constants
SendMessage
OCM_BASE
WM_USER
A google search for RegisterWIndowMessage list of possible values
Another google search for send message types
Maybe I am searching for the wrong things, but I sure can't find it.
My application fails here:
Dialog = (IHTMLDialog)ObjectFromLresult(lRes, typeof(IHTMLDialog).GUID, IntPtr.Zero);
However I believe the issue happened further up the pipeline up here :
uint iMsg = RegisterWindowMessage("WM_HTML_GETOBJECT");
Because this is not an HTML document, but actually a dialog.
If it helps I am getting the hwnd to the dialog this way :
IntPtr hwnd = FindWindow("Internet Explorer_TridentDlgFrame", "Google -- Webpage Dialog");
Here is the full snippet of what I am trying to do if it helps :
UIntPtr lRes;
IHTMLDialog Dialog;
IntPtr hwnd = FindWindow("Internet Explorer_TridentDlgFrame", "Google -- Webpage Dialog");
uint iMsg = RegisterWindowMessage("WM_HTML_GETOBJECT");
if (SendMessageTimeout(hwnd, iMsg, IntPtr.Zero, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 1000, out lRes) == IntPtr.Zero)
{
MessageBox.Show("operation failed");
}
else
{
Dialog = (IHTMLDialog)ObjectFromLresult(lRes, typeof(IHTMLDialog).GUID, IntPtr.Zero);
}
RegisterWindowMessage takes a string argument. You can pass any string value. If you are registering messages for your application, make sure to use unique string values (e.g. string representations of GUIDs).
Other than that, there is no complete list of string values you can pass, because any application can choose to register its own set of messages. You will have to consult the documentation that comes with those applications to find out, which messages it supports (if any).

code to open windows explorer (or focus if exists) with file selected

My goal is to write a C# code that will open a Windows Explorer window, with a particular file selected. If such window is already open, I want to bring it to front. I have tried two options.
First, I start by explicitly calling explorer.exe:
arg = "/select, " + pathToFile;
Process.Start("explorer.exe", arg);
This opens and selects a window fine, but the problem is that it will always open a new window, even if one exists. So I tried this:
Process.Start(pathToDir);
This either opens a new window or focuses an old one, but gives me no option to select a file.
What can I do? I looked at explorer's arguments and I don't see anything I can use. A last-resort option I can come up with is to get the list of already open windows and use some WINAPI-level code to handle it, but that seems like an overkill.
I don't know if it's possible using process start, but the following code opens the Windows explorer on the containing folder only if needed (if the folder is already open, or selected on another file, it's reused) and selects the desired file.
It's using p/invoke interop code on the SHOpenFolderAndSelectItems function:
public static void OpenFolderAndSelectFile(string filePath)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
IntPtr pidl = ILCreateFromPathW(filePath);
SHOpenFolderAndSelectItems(pidl, 0, IntPtr.Zero, 0);
ILFree(pidl);
}
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll")]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
[DllImport("shell32.dll")]
private static extern void ILFree(IntPtr pidl);

Categories

Resources