Monday, January 05, 2009
WSH and clipboard access
My solution for scripting the clipboard content in WSH is a regular COM object created with VC++ and ATL.
Download full source code and compiled DLL: WSH_clipboard.zip
To install the COM object run register.bat
I found scripting the clipboard useful enough to add this feature to the next release of Twebst Web Automation Library.
Monday, December 15, 2008
Free Web Macros for Internet Explorer
Twebst Web Automation Library can make things easier.
Get it FREE!
What Twebst can do?
- increase productivity by automating repetitive web tasks
- automate regression testing of web applications
- automate web actions and data-entry
- automatically log in to different web sites
- fill out web-forms automatically
- extract data from web pages (web scraping).
- monitor web pages
Twebst features
- Start new browsers and navigate to a specified URL.
- Connect to existing browsers.
- Search and access HTML elements and frames inside browsers.
- Intuitive names for HTML elements using the text that appears on the screen.
- Advanced search of browsers and HTML elements using regular expressions.
- Perform actions on all HTML controls (button, combo-box, list-box, edit-box etc).
- Simulates user behavior generating hardware or browser events.
- Get access to native interfaces exposed by Internet Explorer so you don't need to learn new things if you already know IE web programming.
- Synchronize web actions and navigation by waiting the page to complete in a specified timeout.
- Available from any programming or script language that supports COM
- Optimized search methods and collections.
Wednesday, December 10, 2008
What's wrong with Internet Explorer Automation?
Though Internet Explorer browser is not part of the Office suite, it supports automation. Here is a short sample:
// Create an IE automation object.
var ie = new ActiveXObject("InternetExplorer.Application");
// Make it visible and navigate to a given URL.
ie.Visible = true;
ie.Navigate("http://www.google.com/");
// Give it some time to load the page and then get the document.
WScript.Sleep(3000);
var doc = ie.Document;
// Fill out search field.
var edit = doc.getElementsByName("q").item(0);
edit.value = "codecentrix";
// ... and press the submit button.
var submit = doc.getElementsByName("btnG").item(0);
submit.click();Here is ie_auto.js file for download.
However there are problems with Internet Explorer automation:
- it may not work at all on Windows Vista unless the script is running at the same integrity level as iexplore.exe process. Simply clicking the js file won't do it. The script will run at medium integrity level and Internet Explorer has low integrity level and as result the script fails. If you run the script at high integrity level the newly started IE instance will have the same high integrity level and the script works (but this is not the best option from a security point of view). Changing the integrity level of the running script (or application) is not always the most desirable or easiest thing to do.
- no support to "connect" to already existing IE documents.
- sub-documents in different domains are not accessible for scripting due to cross scripting security issues (see my older posts: "When IHTMLWindow2::get_document returns E_ACCESSDENIED" and "When IHTMLWindow2.document throws UnauthorizedAccessException").
- difficult search of elements across all sub-documents inside frames/iframes (and sometimes impossible, see the point above).
- difficult and time consuming search of HTML elements on attributes other than id or name (getElementById and getElementsByName are the only methods I know that search elements directly wihtout browsing element collections which might be very slow when performed out of process).
- no tab support (only launch a new tab).
- no direct support for synchronizing input actions (clicks, keys) with the HTML document loading (it could be implemented by registering to IE events like document complete or looping while the browser becomes ready to accept inputs).
- no advanced search criteria like regular expression or searching on multiple attributes.
Get it FREE!
(to be continued)
Tuesday, November 25, 2008
Creating shortcuts to Quick Launch Toolbar with WSH
var shell = WScript.CreateObject("WScript.Shell");
var quickLaunchDir = shell.ExpandEnvironmentStrings("%userprofile%") +
"\\Application Data\\Microsoft\\Internet Explorer\\Quick Launch";
var oShellLink = shell.CreateShortcut(quickLaunchDir + "\\Codecentrix.lnk");
oShellLink.TargetPath = "http://www.codecentrix.com/";
oShellLink.IconLocation = "http://www.codecentrix.com/favicon.ico";
oShellLink.WindowStyle = 1;
oShellLink.Description = "Web Site";
oShellLink.Save();
- QuickLaunch.js sample file
- Script Of The Day application (FREE)
Saturday, August 09, 2008
focus vs fireEvent("onfocus")
While working on Twebst web automation library I encountered this problem: how to simulate setting the focus on HTML edit controls in Internet Explorer? There are two ways to do this.
- Call IHTMLElement2::focus() method on target element that "causes the element to receive the focus and executes the code specified by the onfocus event".
- Rise onfocus event on target element by calling IHTMLElement3::fireEvent() method.
The two approaches are quite similar but there are some interesting differences.
- fireEvent("onfocus") does not actually set the focus on the element, it just executes the code of the onfocus handler event.
- Calling focus method sets the focus on target element and call the onfocus event handler but not immediately. The onfocus event seems to be inserted in a queue and its handler is executed asynchronously after the current handler is finished.
- If focus method is called from inside the onfocus handler nothing happens if the control already has the focus (that prevents an infinite recursion).
Example:
<html>
<script type="text/javascript" language="javascript">
function BtnFocusClick()
{
document.getElementById('editTest').focus();
window.status += "b";
}
function BtnOnFocusClick()
{
document.getElementById('editTest').fireEvent('onfocus');
window.status += "c";
}
function EditOnFocus()
{
window.status += "a";
}
</script>
<body>
<input type="text" onfocus="EditOnFocus()"; id="editTest"/><br/>
<input type="button" value="focus" id="btnFocus" onclick="BtnFocusClick();"/>
<input type="button" value="fire onfocus" id="btnOnFocus" onclick="BtnOnFocusClick();"/>
</body>
</html>
If pressing the button "fire onfocus" button the message in the Internet Explorer status bar is the expected one "ac". If pressing the "focus" button, the message is in reverse order than expected: "ba". That suggests that EditOnFocus handler is called after BtnFocusClick exit.
codeproject
Thursday, June 19, 2008
IHTMLDocument3::getElementsByTagName and IHTMLElementCollection
Usually a collection is retrieved by calling methods of IHTMLDocument2 interface. For some tag-names there specialized methods to retrieve collection (IHTMLDocument2::get_anchors , IHTMLDocument2::get_applets, IHTMLDocument2::get_forms, IHTMLDocument2::get_images, IHTMLDocument2::get_links, IHTMLDocument2::get_scripts).
To get all elements collection there is IHTMLDocument2::get_all.
One way to get a collection of elements having a specified tag-name is:
// CComQIPt<IHTMLDocument2> spDocument is a document object.
CComQIPtr<IHTMLElementCollection> spAllCollection;
HRESULT hRes = spDocument->get_all(&spAllCollection);
_ASSERTE(SUCCEDED(hRes) && (spAllCollection != NULL));
// Get the sub-collection of elements that have the "input" tag name.
CComVariant varTagName(CComBSTR("input"));
CComQIPtr<IDispatch> spDispCollection;
hRes = spAllCollection->tags(varTagName, &spDispCollection);
CComQIPtr<IHTMLElementCollection> spInputCollection = spDispCollection;
_ASSERTE(spInputCollection != NULL);
// Now you can browse spInputCollection using
// IHTMLElementCollection::item and IHTMLElementCollection::get_length methods.
The second method is:
// CComQIPt spDocument is a document object.
// Query for IHTMLDocument3 interface
CComQIPtr<IHTMLDocument3> spDoc3 = spDocument;
_ASSERTE(spDoc3 != NULL);
// Get the collection of elements that have the "input" tag name.
CComQIPtr<IHTMLElementCollection> spInputCollection;
HRESULT hRes = spDoc3->getElementsByTagName(CComBSTR("input"), &spInputCollection);
_ASSERTE(SUCCEDEED(hRes));
// Now you can browse spInputCollection using
// IHTMLElementCollection::item and IHTMLElementCollection::get_length methods.
Monday, May 19, 2008
ATL thunks and Windows DEP story
The ATL implementation of CWindow class uses a technique called thunk. A thunk is a small piece of code that ATL generates in a region of memory allocated on the heap. Older versions of ATL did not set the execution flag on the allocated memory pages and that generates a crash on Windows machines where DEP is enabled.
Usually ATL is used in Internet Explorer extensions. If DEP is enabled then the result is a browser crash. This is a good reason to upgrade to Visual Studio 2005 or later. Find more on how to activate DEP in IE7 here.
Monday, April 21, 2008
ATL + STL = CAdapt
Here's where CAdapt class comes to save us. The list of smart pointers becomes: std::list<CAdapt<CComQIPtr<IHTMLElement> > >
Also you need to use m_T member where needed.
Tuesday, April 15, 2008
Did you know that ? fatal error C1091: compiler limit
fatal error C1091: compiler limit: string exceeds 65535 bytes in length
I was about to complete my programming task when this error struck and I really needed a very long string. On VC++ 2003 compiler the limit is even smaller, 16 Kb.
The conclusion of this story: don't put the whole story of your life inside a C++ string constant!
Monday, March 31, 2008
.Net and COM interop story
COM programmers have to call Release on every interface that has been AddRef'ed. For C# programmers using COM objects that means AddRef is called when:
- a COM object is created.
- a COM object is returned by calling a method or a property.
- a COM object is cast'ed to another COM interface type.
To release a COM object in C# there are two options:
- leave the GC to collect managed wrappers and to call their finalizers that will call Release on native COM object.
- manually call Marshal.ReleaseComObject on every interface used in the code.
Let's see a short example using COM objects exposed by IE. The code bellow changes the color of every link in a HTML document.
This first approach leaves the task of releasing COM objects to garbage collector. Let's manually release COM objects now:
// IHTMLDocument2 doc;
foreach (IHTMLElement elem in doc.all)
{
IHTMLAnchorElement anchor = elem as IHTMLAnchorElement;
if (anchor != null)
{
elem.style.color = "red";
}
}
// IHTMLDocument2 doc;
IHTMLElementCollection allCollection = doc.all;
foreach (IHTMLElement crntElem in allCollection)
{
IHTMLAnchorElement anchor = crntElem as IHTMLAnchorElement;
if (anchor != null)
{
IHTMLStyle style = crntElem.style;
style.color = "red";
Marshal.ReleaseComObject(style);
Marshal.ReleaseComObject(anchor);
}
Marshal.ReleaseComObject(crntElem);
}
Marshal.ReleaseComObject(allCollection);
Some might be tempted to call GC.Collect after a large chunk of code that work with COM objects but this could be even worse because other managed objects could be promoted to next GC generation and their lifespan is therefore longer than necessary.
In theory it is possible to create a lot of large COM objects that will exceed the native heap while the managed heap has a lot of available memory because managed wrappers are smaller in size. GC won't be called in this scenario so the native heap won't be freed.
If your application suffers from this kind of memory allocation problem, maybe using COM objects from managed code is not the best approach for you.
Monday, February 25, 2008
The game of programming. Programming the game.
Source code:
Saturday, February 02, 2008
When IHTMLWindow2.document throws UnauthorizedAccessException
The main problem is the confusion created by System.IServiceProvider .Net interface because it has the same name as the COM interface. Once this issue is passed the code translation is straightforward. Here's the interop code to declare the COM interface IServiceProvider.
// This is the COM IServiceProvider interface, not System.IServiceProvider .Net interface!You find here full source code of the sample assembly.
[ComImport(), ComVisible(true), Guid("6D5140C1-7436-11CE-8034-00AA006009FA"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.I4)][PreserveSig]
int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
}
This technique was successfully implemented and tested in Twebst web automation library.
Wednesday, January 09, 2008
Nunit and STAThread story
First I started by creating an assembly to be used from NUnit GUI. Some tests failed without an obvious reason. After some research I understood that the COM apartment must be STAThread. The threading model must be set before the thread is started but I don't have access to NUnit GUI main thread from my assembly.
One possible solution to this problem is to transform the assembly into an EXE application that uses the NUnit framework like this:
[STAThread]When /nothread command line flag is used the tests are executed by the main thread which already has the right COM apartment properly set.
public static void Main(string[] args)
{
NUnit.ConsoleRunner.Runner.Main(
new string[] { System.AppDomain.CurrentDomain.BaseDirectory + "MyExe.exe", "/nothread" });
}
Thursday, December 20, 2007
Repairing Internet Explorer
3). When you open a new tab in Internet Explorer 7, does the You've opened a new tab message appear every time, and are you unable to turn off this option
4). Open a link in a new tab takes forever to load.
Points of interest:
- Internet Explorer components are registered using regsvr32.exe tool.
- For IE7 mshtml.dll can not be registered as explained above; instead mshtml.tlb is registered using regtlib.exe tool.
- Registering shdocvw.dll using regsvr32.exe is not a good idea on IE7 because it damages [HKEY_CLASSES_ROOT\Typelib\{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}\1.1\0\win32 registry key.
- reg_ieframe.reg file fixes the broken key above.
Sunday, December 09, 2007
Allow local scripted HTML files to run in IE7
"To help protect your security, Internet Explorer has restricted this web page from running scripts or ActiveX controls that could access your computer. Click here for options..."
1). Go to:
Tools > Internet Options > Advanced > Security
2). Check:
Allow active content to run in files on My computer
3). Restart IE
Wednesday, November 14, 2007
How to get a handle to current TabWindowClass tab in IE7
#include <shlguid.h>
HWND GetTabWnd(CComQIPtr<IWebBrowser2> spBrowser)
{
HWND hwndTab = NULL;
CComQIPtr<IServiceProvider> spServiceProvider = spBrowser;
if (spServiceProvider != NULL)
{
CComQIPtr<IOleWindow> spWindow;
if (SUCCEEDED(spServiceProvider->QueryService(
SID_SShellBrowser,
IID_IOleWindow,
(void**)&spWindow)))
{
spWindow->GetWindow(&hwndTab));
}
}
return hwndTab;
}
I think the code is supposed to work on top level IWebBrowser2 objects. You can read more about top browser objects in my previous article.This technique was successfully implemented and tested in Twebst web automation library.
Monday, November 12, 2007
When IWebBrowser2::get_HWND returns E_FAIL
It was hard for me to imagine how this method could fail but I still got an E_FAIL return value. This happened because the IWebBrowser2 object was not the top level browser. A web page containing frames/iframes is represented by a hierarchy of IHTMLWindow objects. Each window has an associated IHTMLDocument2 object exposed by IHTMLWindow2::get_document. An IHTMLWindow can be also converted to a IWebBrowser2 object. Here's my solution to get the main window handle starting from a non-top level browser object (this is a common scenario when adding your custom menu item in the IE context menu).
// IHTMLWindow2 to IWebBrowser2
CComQIPtr<IWebBrowser2> IHTMLWindow2ToIWebBrowser2(CComQIPtr<IHTMLWindow2> spHTMLWindow)
{
ATLASSERT(spHTMLWindow != NULL);
// Query for a service provider.
CComQIPtr<IWebBrowser2> spBrowser;
CComQIPtr<IServiceProvider> spServiceProvider = spHTMLWindow;
if (spServiceProvider != NULL)
{
// Ask the service provider for a IWebBrowser2 object.
spServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&spBrowser);
}
return spBrowser;
}
// IWebBrowser2 to IHTMLWindow2
CComQIPtr<IHTMLWindow2> IWebBrowserToIHTMLWindow(CComQIPtr<IWebBrowser2> spBrowser)
{
ATLASSERT(spBrowser != NULL);
CComQIPtr<IHTMLWindow2> spWindow;
// Get the document of the browser.
CComQIPtr<IDispatch> spDisp;
spBrowser->get_Document(&spDisp);
// Get the window of the document.
CComQIPtr<IHTMLDocument2> spDoc = spDisp;
if (spDoc != NULL)
{
spDoc->get_parentWindow(&spWindow);
}
return spWindow;
}
CComQIPtr<IWebBrowser2> TopBrowser(CComQIPtr<IWebBrowser2> spBrowser)
{
ATLASSERT(spBrowser != NULL);
// Retrieve IHTMLWindow2 from browser.
CComQIPtr<IHTMLWindow2> spHTMLWnd = IWebBrowserToIHTMLWindow(spBrowser);
if (spHTMLWnd != NULL)
{
// Find top window.
CComQIPtr<IHTMLWindow2> spTopWindow;
HRESULT hResult = spHTMLWnd->get_top(&spTopWindow);
if (SUCCEEDED(hResult) && (spTopWindow != NULL))
{
// Convert the browser object to window.
return IHTMLWindow2ToIWebBrowser2(spTopWindow);
}
}
return CComQIPtr<IWebBrowser2>();
}
This technique was successfully implemented and tested in My web automation library.codeproject
Monday, November 05, 2007
How to programmatically find if XP theme is active?
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager
Value:
ThemeActive is "1" if Windows XP, "0" if Windows Classic.
Sunday, October 28, 2007
Open TextRange selection as URL
- select the text URL
- ctrl+C to copy it in the clipboard
- ctrl+T to open a new tab
- paste the URL in the address bar of the newly created tab
- press "Enter" key to start navigation
I found this whole process very annoying and it seems I have to go through this many times a day. Here's my attempt to automate it by creating an extension for "Internet Explorer" similar to Linkification extension (for "Fire Fox" browser). I called my extension: "Make Link".
How it works:
- right click on text URL
- choose "MakeLink: use clicked text as link" menu item (the URL is open in a new tab automatically)
or you can
- fully select the URL text or only a part of it
- right click on selected text
- choose "MakeLink: use clicked text as link" menu item (text link is open in a new tab automatically)
DOWNLOADS:
Points of interests:
- HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt registry key to add a item in the IE context menu
- gain access to window object passed in external.menuArguments
- document.selection property to get the selected TextRange
- TextRange methods: moveStart, moveEnd, moveToPoint, select
- The extension is entirely written in JScript (no BHO, no COM).
Known issues:
- on some web pages with horizontal scroll simply right click a text URL won't work. You need to select a part of the URL and then right click it.
Wednesday, October 17, 2007
How to properly catch RBN_CHEVRONPUSHED notification?
WorkerW <- ReBarWindow32 <- ToolbarWindow32
- I subclass a window that does not belong to me, it was created by IE.
- I don’t know what is the best time to subclass it: on IObjectWithSite.SetSite or on IDockingWindow.ShowDW ? (Those functions are implemented by my toolbar component)
- I don’t know what is the best time to un-subclass it.
- I don’t know when other toolbar might subclass/un-subclass the same window (I actually got a conflict with other toolbar resulting in IE stack overflow crash because of the order of subclassing/unsubclassing).
So the question remains: what is the best way to catch RBN_CHEVRONPUSHED notification when creating an IE toolbar extension?