Overriding IE settings when embedding a WebBrowser control using IE9 - windows

I have an application (written in C++ with MFC, but I don't think that that's particularly relevant) that embeds the Internet Explorer ActiveX WebBrowser control for the purpose of showing some HTML pages. One requirement has always been to use the application's font name and size settings as the default settings for the HTML, rather than Internet Exporer's defaults.
To achieve this, the application implements the IDocHostUIHandler2 COM interface, which it passes to the WebBrowser control. This causes the control to call the application's implementation of GetOptionKeyPath, which lets the application set the registry location that the WebBrowser control gets its settings from. Armed with Sysinternals' tools to see which keys IE uses to find the font name and size, this has been sufficient to do what I need.
However, the appeareance of Internet Explorer 9 has come as a nasty surprise: on all machines that I've tested with that have IE9 installed, the WebBrowser control uses its own settings, ignoring the registry location from the application. Testing with a debugger shows that the WebBrowser control never calls the provided GetOptionKeyPath.
A bit more experimentation shows that the IE9 WebBrowser control is calling the similar (but not identical) GetOverrideKeyPath method: this allegedly provides a way to override IE settings, while falling back to IE's actual settings if nothing is found in the relevant part of the registry. Unfortunately this has two problems: 1) It's not quite what I want, and 2) IE9 doesn't always check under the GetOverrideKeyPath registry location before going to the IE default registry settings.
Looking at the GetOptionKeyPath MSDN page there are a few complaints along similar lines, but no solutions. Has anyone found a clean way to persuade the WebBrowser control to revert to the pre-IE9 behaviour of actually calling GetOptionKeyPath as documented?

I've come up with a hack to solve this problem, but I should warn you: it's not pretty. Stop reading now if you've easily offended ...
Since there seems to be no way of making IE9 use the IDocHostUIHandler::GetOptionKeyPath() method, I used SysInternals' tools to see which IE9 DLLs accessed the relevant parts of the registry to load the IE9 settings. This revealed the only culprits as "mshtml.dll" and "iertutil.dll", both of which call RegOpenKeyExW().
The plan was then to load these DLLs before initializing the WebBrowser control, and patch them so that calls are redirected to my code, where I can lie about what registry key I've opened, using dbghelp.dll. So, to start, before initializing the WebBrowser control:
if (theApp.GetIEVersion() >= 9.0)
{
HMODULE advadi = ::LoadLibrary("advapi32.dll");
HMODULE mshtml = ::LoadLibrary("mshtml.dll");
HookApiFunction(mshtml,advadi,"advapi32.dll","RegOpenKeyExW",(PROC)HookRegOpenKeyExW);
HMODULE iertutil = ::LoadLibrary("iertutil.dll");
HookApiFunction(iertutil,advadi,"advapi32.dll","RegOpenKeyExW",(PROC)HookRegOpenKeyExW);
}
And now, the code that does the evil work of scanning the DLLs import address tables, and patching the requested function (error handling omitted to keep the code size down):
void HookApiFunction(HMODULE callingDll, HMODULE calledDll, const char* calledDllName, const char* functionName, PROC newFunction)
{
// Get the pointer to the 'real' function
PROC realFunction = ::GetProcAddress(calledDll,functionName);
// Get the import section of the DLL, using dbghelp.dll's ImageDirectoryEntryToData()
ULONG sz;
PIMAGE_IMPORT_DESCRIPTOR import = (PIMAGE_IMPORT_DESCRIPTOR)
ImageDirectoryEntryToData(callingDll,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&sz);
// Find the import section matching the named DLL
while (import->Name)
{
PSTR dllName = (PSTR)((PBYTE)callingDll + import->Name);
{
if (stricmp(dllName,calledDllName) == 0)
break;
}
import++;
}
if (import->Name == NULL)
return;
// Scan the IAT for this DLL
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((PBYTE)callingDll + import->FirstThunk);
while (thunk->u1.Function)
{
PROC* function = (PROC*)&(thunk->u1.Function);
if (*function == realFunction)
{
// Make the function pointer writable and hook the function
MEMORY_BASIC_INFORMATION mbi;
::VirtualQuery(function,&mbi,sizeof mbi);
if (::VirtualProtect(mbi.BaseAddress,mbi.RegionSize,PAGE_READWRITE,&mbi.Protect))
{
*function = newFunction;
DWORD protect;
::VirtualProtect(mbi.BaseAddress,mbi.RegionSize,mbi.Protect,&protect);
return;
}
}
thunk++;
}
Finally, the function that I have patched the DLLs to call in my code, in place of RegOpenKeyExW():
LONG WINAPI HookRegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult)
{
static const wchar_t* ieKey = L"Software\\Microsoft\\Internet Explorer";
// Never redirect any of the FeatureControl settings
if (wcsstr(lpSubKey,L"FeatureControl") != NULL)
return ::RegOpenKeyExW(hKey,lpSubKey,ulOptions,samDesired,phkResult);
if (wcsnicmp(lpSubKey,ieKey,wcslen(ieKey)) == 0)
{
// Redirect the IE settings to our registry key
CStringW newSubKey(m_registryPath);
newSubKey.Append(lpSubKey+wcslen(ieKey));
return ::RegOpenKeyExW(hKey,newSubKey,ulOptions,samDesired,phkResult);
}
else
return ::RegOpenKeyExW(hKey,lpSubKey,ulOptions,samDesired,phkResult);
}
Amazingly enough, this horrible hack actually works. But please, Microsoft, if you're listening, please fix this properly in IE10.

Related

How to force Explorer use modern file operation dialog with Shell Namespace Extension

In my understanding currently there are two ways to copy virtual files from a Shell Namespace Extension with the Explorer so that Copy GUI will be shown to the user:
Via IDataObject interface:
Reading a file is done via IDataObject::GetData that should support CFSTR_FILEDESCRIPTORW, CFSTR_FILECONTENTS and CFSTR_SHELLIDLIST clipboard formats at very minimum. Requesting CFSTR_FILECONTENTS from IDataObject::GetData should create an IStream that is used to access the data. UI is enabled via setting FD_PROGRESSUI flag when CFSTR_FILEDESCRIPTORW is requested.
Via ITransferSource interface:
Reading a file is done via ITransferSource::OpenItem requesting for IShellItemResources. Then IShellItemResources should report {4F74D1CF-680C-4EA3-8020-4BDA6792DA3C} resource as supported (GUID indicating that there is an IStream for the item). Finally an IStream is requested via parent ShellFolder::BindToObject for accessing the data. UI is handled by the Explorer itself, it is always shown.
My problem is: these two mechanisms are working just fine separately (as you can see from the screenshots). But once I enable both IDataObject from IShellFolder::GetUIObjectOf and ITransferSource from IShellFolder::CreateViewObject - the approach via IDataObject is always used leading to the old copy GUI (as on the first screenshot). I see from the trace logs that ITransferSource is requested several times, but no actions are performed, it just gets Released and destroyed right away.
So how may I force Explorer to show fancy copy GUI when copying from my Shell Namespace Extension?
A minimal reproducible example may be found here: https://github.com/BilyakA/SO_73938149
While working on Minimal Reproducible example I somehow managed to make it work as expected with both IDataObject and ITranfserSource interfaces enabled. It happened after:
unregistred x64 build SNE example (regsvr32 /u)
registred x32 build SNE example (it was not working in x64 explorer, root was not opening)
unregistred x32
registred x64 again.
Somehow new copy UI was shown to me when copying the files.
But I was not able to reproduce this result constantly after I have unregistred x64 SNE, restarted explorer and registered SNE x64 again.
What I have tried:
Registred both x32 and x64 SNE - still old GUI
Removed Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Cached value with my NSE GUID and restarted Explorer afterwards. Still old GUI.
I suspect there is some kind of cache (other than Registry) that keeps track if NSE supports ITransferSource. And since I have developed and tested without ITransferSource at the beginning - it is cached that my NSE does not support it even that I have added it later. And somehow registering 32bit reset that cache value.
The current code is doing something like this when asked for an IDataObject:
HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
....
if (riid == IID_IDataObject)
{
CDataObject* dataObject = new (std::nothrow) CDataObject(this, cidl, apidl);
return ::SHCreateDataObject(m_pidl, cidl, apidl, dataObject, riid, ppv);
}
...
}
SHCreateDataObject already creates already a full-blown IDataObject with everything needed from the (optional) input PIDL(s), including all Shell formats for items in the Shell namespace (CFSTR_SHELLIDLIST, etc.).
Passing an inner object 1) will probably conflict with what the Shell does and 2) requires a good implementation (I've not checked it).
So you can just replace it like this:
HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT rgfReserved, void **ppv)
{
....
if (riid == IID_IDataObject)
{
return ::SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);
}
...
}
In fact, the IDataObject provided by the Shell is complete (supports SetData method, etc.) so you should never need to implement it yourself, it's more complex than it seems. You can reuse it as a general purpose IDataObject (you can pass nulls and 0 for the 4 first arguments).
PS: the Shell also provides the "reverse" method: SHCreateShellItemArrayFromDataObject that gets a list of Shell items from an IDataObject (if the data object contains the expected clipboard formats).
For those who need to use inner IDataObject within SHCreateDataObject for additional format:
Inner IDataObject should support CFSTR_SHELLIDLIST clipboard format in GetData() as well as report this format in EnumFormatEtc(). Supporting SetData() with CFSTR_SHELLIDLIST format is really useful here since DefDataObject sets inner data object with already fully constructed CIDA and you may just store a copy of it.
Once inner data object will report CFSTR_SHELLIDLIST in EnumFormatEtc() and return valid CIDA in GetData() - modern Copy UI will be used since it relies only on PIDL of the items.

How can I filter a breakpoint on an exported function of a module, based on the name of other modules that call that function?

I am debugging a process in Visual Studio 2017 that loads many modules, I have set a function breakpoint on the CreateFile of Kernel32.dll.
This breakpoint hits many times, but I want to break the execution if a specific module makes call to the CreateFile function.
Passing unwanted breaks with F5 is not possible (there are too many of them).
There is no any code, all are just assemblies.
I want to see when "bcript.dll" (one of items in modules window) calls "CreateFile" from "Kernel32.dll".
As I didn't find any solution for my question, I decided to use other tools than Visual Studio, then I found some useful tools that I want to share:
1- In my case I used windows hooks:
void CInstallerDlg::OnBnClickedInstall()
{
if (m_hHook == INVALID_HANDLE_VALUE)
{
CString strTarget;
GetDlgItem(IDC_TARGET)->GetWindowText(strTarget);
HMODULE hDll = LoadLibrary(L"Dll.dll");
if (hDll)
{
HOOKPROC hProc = (HOOKPROC)GetProcAddress(hDll, (char*)1);
if (hProc)
{
FARPROC SetTarge = GetProcAddress(hDll, (char*)2);
if (SetTarge)
{
((void (CALLBACK *) (CString))SetTarge)(strTarget);
m_hHook = SetWindowsHookEx(WH_CALLWNDPROC, hProc, hDll, 0);
}
}
}
}
}
2- sysinternals have lots of analyzing tools.
3- winapioverride32 allows monitoring and overriding api calls.
4- snowman is a PE decompiler.
5- LordPE: download link allows to view and edit binary data in PE sections, along with general details about PE.

DirectX Windows 8.1 fstream won't load file

I'm building a Windows 8.1 DirectX app and trying to load in an external to store level data.
The relevant code currently is (this method is called in the AssetHandler constructor):
void AssetHandler::LoadLevelsData()
{
unsigned int i = 0;
std::string lineData;
this->currentFile.open("Assets/reg.txt");
//Below statement here purely to check if the blasted thing is opening
if (this->currentFile.is_open())
{
i++;
}
while (std::getline(this->currentFile, lineData))
{
levels[i] = lineData;
i++;
}
currentFile.close();
}
The problem that i'm having is that the file does not appear to be opening. I have tried:
Using a full path
Opening the file in the initialisation list
A breakpoint shows that it is jumping over the if and while
I found some information saying that DirectX has constraints on working with external files but it did not specify exactly what these were.
The Item Type was set to 'Does not participate in build'. Setting this value to 'Text' solved the problem.

Obtaining IAccessible2 interface for Firefox / Thunderbird window - QueryService always returns E_INVALIDARG

I am trying out accessing the information in (Windows only) Firefox / Thunderbird using the IAccessible2 API: http://www.linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2
However I'm failing at almost the first step. I can obtain the IAccessible interface for either application, and then the IServiceProvider interface. But when I call QueryService to obtain the IAccessible2 interface, as described here: http://accessibility.linuxfoundation.org/a11yspecs/ia2/docs/html/_generalinfo.html#_dicoveringInterfaces it always returns E_INVALIDARG.
AccProbe successfully returns IA2 information for both applications. The MS documentation for QueryService doesn't list E_INVALIDARG as a possible return value. However browsing for the mozilla source it appears that it returns this if the first parameter (service ID) is unexpected, and otherwise calls through to QueryInterface (which would return E_NOINTERFACE for a bad interface ID). So... that implies the first parameter to QueryService is wrong; but I've tried pretty much every value I think mozilla expects with no difference.
I started off in c# and then tried again in c++ in case I was losing something in InterOp. Same result in both languages. Here's my current c++ test code:
HWND hw = GetForegroundWindow();
IAccessible *pIA;
HRESULT hr = AccessibleObjectFromWindow(hw, OBJID_WINDOW, IID_IAccessible, (void**)&pIA);
if (!SUCCEEDED(hr))
return -1;
// SNIP - calls pIA->get_accName to check correct window is being accessed. This works OK.
const IID IID_IAccessible2 = {0xE89F726E, 0xC4F4, 0x4c19, 0xbb, 0x19, 0xb6, 0x47, 0xd7, 0xfa, 0x84, 0x78};
::IServiceProvider *pService = NULL;
hr = pIA->QueryInterface(IID_IServiceProvider, (void **)&pService);
if(SUCCEEDED(hr))
{
IAccessible2 *pIA2 = NULL;
hr = pService->QueryService(IID_IAccessible2, IID_IAccessible2, (void**)&pIA2);
if (SUCCEEDED(hr) && pIA2)
{
// Always fails with E_INVALIDARG
pIA2->Release();
}
pService->Release();
}
This is all on Win7 - both 32-bit and 64-bit used. Firefox 3.6.24 and Thunderbird 8.0. Visual Studio 2005
Am I doing something wrong?
HRESULT hr = AccessibleObjectFromWindow(hw, OBJID_WINDOW, IID_IAccessible, (void**)&pIA);
if (!SUCCEEDED(hr))
return -1;
I think the problem is here - replace OBJID_WINDOW with OBJID_CLIENT, and it seems to work. (I don't have the typelib registered, but I can QS for IID_IAccessible and the IUnknown interface, and it seems to work. Also, be sure to CoInitialize() also.)
IAccessible has an interesting hierarchy: every HWND has both a 'window' part and a 'client' part. This is partially due to how Win32 works internally; a Win32 HWND can have items like titlebar, menu, scrollbars, and so on, that all share the same HWND - along with the content area aka client area where the actual control content lives. To allow these items to have their own representation, the designers of MSAA picked a two-level structure, where OBJID_WINDOW represents the entire window, and it has children that then represent things like scrollbars, menubar, titlebar, and so on - if they are present.
The part of a window that implements the accessiblity, however, is generally the client part, so you usually need to ask for OBJID_CLIENT to get the 'real' IAccessible.

DSOFramer closing Excel doc in another window. If unsaved data in file, dsoframer fails to open with "Attempt to access invalid address"

I'm using Microsoft's DSOFramer control to allow me to embed an Excel file in my dialog so the user can choose his sheet, then select his range of cells; it's used with an import button on my dialog.
The problem is that when I call the DSOFramer's OPEN function, if I have Excel open in another window, it closes the Excel document (but leaves Excel running). If the document it tries to close has unsaved data, I get a dialog boxclosing Excel doc in another window. If unsaved data in file, dsoframer fails to open with a messagebox: Attempt to access invalid address.
I built the source, and stepped through, and its making a call in its CDsoDocObject::CreateFromFile function, calling BindToObject on an object of class IMoniker. The HR is 0x8001010a The message filter indicated that the application is busy. On that failure, it tries to InstantiateDocObjectServer by classid of CLSID Microsoft Excel Worksheet... this fails with an HRESULT of 0x80040154 Class not registered. The InstantiateDocObjectServer just calls CoCreateInstance on the classid, first with CLSCTX_LOCAL_SERVER, then (if that fails) with CLSCTX_INPROC_SERVER.
I know DSOFramer is a popular sample project for embedding Office apps in various dialog and forms. I'm hoping someone else has had this problem and might have some insight on how I can solve this. I really don't want it to close any other open Excel documents, and I really don't want it to error-out if it can't close the document due to unsaved data.
Update 1: I've tried changing the classid that's passed in to Excel.Application (I know that class will resolve), but that didn't work. In CDsoDocObject, it tries to open key HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}\DocObject, but fails. I've visually confirmed that the key is not present in my registry; The key is present for the guide, but there's no DocObject subkey. It then produces an error message box: The associated COM server does not support ActiveX document embedding. I get similar (different key, of course) results when I try to use the Excel.Workbook programid.
Update 2: I tried starting a 2nd instance of Excel, hoping that my automation would bind to it (being the most recently invoked) instead of the problem Excel instance, but it didn't seem to do that. Results were the same. My problem seems to have boiled down to this: I'm calling the BindToObject on an object of class IMoniker, and receiving 0x8001010A (RPC_E_SERVERCALL_RETRYLATER) The message filter indicated that the application is busy. I've tried playing with the flags passed to the BindToObject (via the SetBindOptions), but nothing seems to make any difference.
Update 3: It first tries to bind using an IMoniker class. If that fails, it calls CoCreateInstance for the clsid as a fallback method. This may work for other MS Office objects, but when it's Excel, the class is for the Worksheet. I modified the sample to CoCreateInstance _Application, then got the workbooks, then called the Workbooks::Open for the target file, which returns a Worksheet object. I then returned that pointer and merged back with the original sample code path. All working now.
#Jinjin
You can use the #import directive to import your Excel's OLB file. this should generate (and automatically include an Excel .tlh file which contains the structures for _Application (and the rest you need)). Ideally, you should find an OLB file that matches the earliest Excel version that you wish to support. The one on your local system is probably in c:\Program Files\Microsoft Office\Office12 (presuming you have Office 2007 installed). It may be named Excel.olb, or XL5EN32.OLB (different, obviously if you haven't installed the US English verion of Excel.
So, copy the .olb file to your project source directory, then at the top of the source file, add a line for #import "XL5EN32.olb".
Yes, opens older versions. Best way to guarantee that this will be the case is to find an OLB file (mentioned in item 1 above) that is from an installation of Excel that is the earliest version you wish to support. I use an Excel9.olb from Office 2000. Works fine with my testing of Excel versions all the way to the latest from Office 2007.
Yes, you should use dsoframer normally after making these changes.
I'm afraid I probably can't do that due to restrictions of my employer. However, if you take the "stock" dsoframer project, make the changes described in part 1 of this post, and the changes I described in my earlier post, you have pretty much recreated exactly what I have.
#Jinjin: did you put the import statement (#import "XL5EN32.olb") in the cpp file where you are using the Excel::_Application? If not, do that... can't just add it to the project. If you have already done that, try also adding this statement to the cpp file where you are using those mappings #import "Debug\XL5EN32.tlh". The tlh file is a header that is generated by running the #import; you should find it in your Debug directory (presuming you're performing a Debug build).
Renaming _Application to Application (and the others) is not the right way to go. The _Application structure is the one that has the mappings. That is why you are not finding the app->get_Workbooks.
What file are you looking in that you are finding Application but not _Application?
Assuming you are using the DSOFRAMER project, you need to add this code to dsofdocobj.cpp in the CreateFromFile function, at around line 348:
CLSID clsidExcelWS;
hr = CLSIDFromProgID(OLESTR("Excel.Sheet"),clsidExcelWS);
if (FAILED(hr)) return hr;
if (clsid == clsidExcelWS)
{
hr = InstantiateAndLoadExcel(pwszFile, &pole);
if (FAILED(hr)) return hr;
}
else
{
<the IMoniker::BindToObject call and it's failure handling from the "stock" sample goes here>
}
Then, define the following new member function in CDsoDocObject:
////////////////////////////////////////////////////////////////////////
// CDsoDocObject::InstantiateAndLoadExcel (protected)
//
// Create an instance of Excel and load the target file into its worksheet
//
STDMETHODIMP CDsoDocObject::InstantiateAndLoadExcel(LPWSTR pwszFile, IOleObject **ppole)
{
IUnknown *punkApp=NULL;
Excel::_Application *app=NULL;
Excel::Workbooks *wbList=NULL;
Excel::_Workbook *wb;
CLSID clsidExcel;
HRESULT hr = CLSIDFromProgID(OLESTR("Excel.Application"), &clsidExcel);
if (FAILED(hr))
return hr;
hr = CoCreateInstance(clsidExcel, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&punkApp);
if (SUCCEEDED(hr))
{
hr = punkApp->QueryInterface(__uuidof(Excel::_Application),(LPVOID *)&app);
if (SUCCEEDED(hr))
{
hr = app->get_Workbooks(&wbList);
VARIANT vNoParam;
VariantInit(&vNoParam);
V_VT(&vNoParam) = VT_ERROR;
V_ERROR(&vNoParam) = DISP_E_PARAMNOTFOUND;
VARIANT vReadOnly;
VariantInit(&vReadOnly);
V_VT(&vReadOnly) = VT_BOOL;
V_BOOL(&vReadOnly) = VARIANT_TRUE;
BSTR bstrFilename = SysAllocString(pwszFile);
hr = wbList->Open(bstrFilename, vNoParam,vNoParam,vNoParam,vNoParam,vReadOnly,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,0,&wb);
if (SUCCEEDED(hr))
hr = wb->QueryInterface(IID_IOleObject, (void**)ppole);
VariantClear(&vReadOnly);
VariantClear(&vNoParam);
SysFreeString(bstrFilename);
}
}
if (wb != NULL) wb->Release();
if (wbList != NULL) wbList->Release();
if (app != NULL) app->Release();
if (punkApp != NULL) punkApp->Release();
return hr;
}

Resources