Getting Law Manager v2.4 to run on Windows 7
Several of my customers run old versions (circa 2003) of a legal case management application called Law Manager, since acquired by Bridgeway. These customers reported that their old versions of Law Manager “don’t work” on Windows 7 and that the vendor did not support running them on Windows 7. Normally, if the vendor of an incompatible app has a newer version that is compatible with Windows 7, we recommend upgrading to the new version. Sometimes, though, customers will choose for various reasons to try to continue using the version they have at least temporarily, even if it means that they will do so without vendor support. This article describes the troubleshooting and remediation steps I performed to get Law Manager working for one of those customers. The incompatibility turned out to be due to a very subtle change in a single registry value. Two different solutions to the problem are provided.
This case describes troubleshooting and remediation for a specific version of Law Manager. The results may be the same for other legacy versions of Law Manager, but they would each need to be verified separately. Sysinternals SigCheck with the -a command line option reports the following version information for this customer’s copy:
C:\Program Files\Law Manager, Inc\LawManager.Pro\lm2000.exe:
File date: 4:50 PM 10/6/2003
Publisher: Law Manager, Inc.
Version: 2.4 Service Pack 1 (204)
File version: 2.4 Service Pack 1 (204)
Strong Name: Unsigned
Original Name: lm2000.exe
Internal Name: lm2000.exe
Copyright: Copyright ⌐ Law Manager, Inc 1985-2002
Comments: leader in premier practice management software for large corporate legal departments, government agencies and multi-office law firms
After installing Law Manager (with administrative rights), I ran Law Manager from the Start menu as a standard user. It displayed a terse error message and then exited when the dialog was dismissed:
It turns out that this kind of error message is typical of runtime failures from Borland Delphi applications. As far as I can tell, when a Delphi app triggers an otherwise-unhandled exception, the outermost Delphi runtime code captures the exception, displays an error message like the one shown with an error code and a memory address, then exits. In Pascal and Delphi, error code 204 means “invalid pointer operation”. In addition to the Borland-style UI elements in a working version of Law Manager, I confirmed my suspicion that Law Manager is a Delphi application with the Sysinternals Strings utility:
strings -q lm2000.exe | findstr /i delp
Software\Developer Express\Delphi\Design Forms\
Software\Developer Express\Delphi\Design Forms\
| Compilation Flags: Win32, Production, TimerView, Delphi 6.0
To figure out what the app could be doing that would lead to failure, I turned to Sysinternals Process Monitor, a.k.a. Procmon, the best troubleshooting tool in the universe (well, at least for that part of the universe that runs Microsoft Windows).
Here is a troubleshooting pattern I use all the time. I started Procmon, which began capturing details about all file system and registry events as well as many types of process and network events. When the error message appeared, I stopped the Procmon trace and dragged the crosshairs “Include Process From Window” icon from the Procmon toolbar to the error message. This feature applies a filter to the results so that Procmon displays only those events associated with the process that owns the window; in this case lm2000.exe, process ID 3600. The events of interest are then usually near the end of the trace, so I went to the end of the trace and worked back. One thing to note is that displaying an error dialog usually involves a number of registry accesses. If the Procmon trace has clues, they usually show up right before the events involved in the error message display.
It took some digging, but the evidence turned up and can be seen in the following screenshot. The first event shown in the screenshot is a registry value read that appears to be related to COM component invocation. The retrieved value is a REG_EXPAND_SZ – a text string that can contain environment variables that need to be expanded before use: “%SystemRoot%\System32\hhctrl.ocx”. Two events later is an attempt to open a file system folder with the name “C:\Program Files\Law Manager, Inc\LawManager.Pro\%SystemRoot%\System32\”, which of course fails with “PATH NOT FOUND”. Evidently the program failed to expand the environment variable before calling a file system API with the returned data. The program probably also expected the API to succeed because the error and exit followed very quickly.
(Click on the screenshots in this post to see the full resolution versions.)
We can dig deeper into these two events by double-clicking each to open their Properties dialogs, shown below. Looking at the call stack of the registry read in the first screenshot, we see that lm2000.exe (frame 6) did not invoke a COM API but instead read the value directly by calling the RegQueryValueExA API (frame 5 – note that beginning with Windows 7 and Server 2008 R2 the registry APIs now live in kernel32 instead of advapi32). The file system access shown in the second screenshot occurred when lm2000.exe invoked the FindFirstFileA API (frame 14) which is used to search for file names matching a pattern. (Note that this core function is now exported from KernelBase.dll).
Just for extra verification (and turbo-nerdiness) I ran lm2000.exe again, but this time in Windbg from the Debugging Tools for Windows. Here’s an annotated screenshot:
- “bp kernelbase!FindFirstFileA” sets a breakpoint at the entry point to the function we want to see; “g” is the “Go” command that lets the program run.
- The FindFirstFileA breakpoint has been hit; “kv 1” shows the top stack frame including parameters (arguments) passed to the function on the stack.
- The first value passed to FindFirstFileA is the memory address 0x01b01011c and represents the file name or pattern to search for. “da 01b1011c” displays the data found at that address as an ANSI string, which turns out to be “%SystemRoot%\System32\hhctrl.ocx”, which is not a valid file name or pattern. Combined with the Procmon trace, it appears that because FindFirstFileA didn’t recognize the supplied path as an absolute path, it treated it as a relative path and appended it to the current directory.
- “bc 0” clears breakpoint #0, and “g” lets the program continue. It quickly hits a first-chance exception due to an access violation.
- The problem is that the instruction pointer (eip) which identifies the next instruction to execute is pointing to address 0, which is invalid. That will always cause a crash.
Why did this code work on Windows XP but fail on Windows 7? Let’s take a look at the registry value as it existed in Windows XP and compare it to Windows 7. The XP screenshot shows that the value had been a REG_SZ with the literal path to the file rather than a REG_EXPAND_SZ containing “%SystemRoot%” as it is in Windows 7. The change makes sense: literal paths like the one on XP need to be customized at installation time depending on where Windows is installed, while “%SystemRoot%\System32\hhctrl.ocx” is correct for all Windows instances.
Solution #1: Overriding the Registry Value
One way to get Law Manager to work the way it did on Windows XP is to change the registry data back to the way it was on Windows XP. That, however, is not a good idea, and Windows will tell you so with an “access denied” if you try. Take a look at the permissions on that registry key:
That’s right: even Administrators and System are granted Read-Only, just like standard users. Because of Windows Resource Protection, only the TrustedInstaller (a.k.a., “Windows Modules Installer”) service is granted Full Control on this key. Objects with permissions set this way belong to Windows and should not be modified except by Windows itself. (Of course, if you have administrative rights, you could take ownership of the resource, change its permissions and then make any changes to the resource you want. It is impossible to restrict what an administrator can do. Just be aware that just because you can doesn’t mean it’s a good idea or that you will have a supportable and serviceable instance of Windows when you’re done. Consider that “access denied” an unsubtle hint that you shouldn’t continue.)
So my first idea was to take advantage of the fact that HKEY_CLASSES_ROOT is actually a merged view of HKEY_LOCAL_MACHINE\Software\Classes and HKEY_CURRENT_USER\Software\Classes. If a value exists under HKCU\Software\Classes, it takes precedence over a corresponding value in HKLM\Software\Classes. I exported the key from HKCR to a file called “fix-law-manager.reg”, opened it with Notepad, changed the beginning of the key name from “HKEY_CLASSES_ROOT” to “HKEY_CURRENT_USER\Software\Classes” and changed the default value from a REG_EXPAND_SZ specified in hexadecimal bytes to a REG_SZ with a literal path:
Windows Registry Editor Version 5.00
Finally, I imported the edited file into the user’s HKCU with this command line: “reg import fix-law-manager.reg”.
The fix appeared to work: when I started Law Manager again it got past the previous error and displayed a login dialog. (That was as far as I could test it myself without back end systems. The customer tested the application’s full functionality and found no further problems.)
A Procmon trace captured during that test shows that the literal path is picked up from the HKCU side of HKCR, and hhctrl.ocx is then successfully found in the C:\Windows\System32 folder.
I was satisfied that I had found a solution: simply package “fix-law-manager.reg” so that it can be imported into the HKCU of every end user who uses Law Manager one time before they run Law Manager for the first time. It is a pretty simple fix that doesn’t involve breaking permissions on Windows-owned resources. There are some downsides, however:
· The fix-law-manager.reg file needs to be imported by every end user who uses Law Manager prior to first use. If two users share a computer and both use Law Manager, both users need to import the file. Further, because HKCU\Software\Classes doesn’t roam, if an end user logs on to different machines and runs Law Manager, the registry file needs to be imported for that user on each of those computers.
· The overriding of the global value in HKLM\Software\Classes affects all programs that the user runs. While it would seem that a minor change like this is unlikely to cause any problems, you could have said the same about the change from REG_SZ to REG_EXPAND_SZ.
Solution #2: Apply an Application Compatibility Shim
I shared my great app compat sleuthing and my fabulously simple solution with Chris Jackson, The App Compat Guy. I don’t think he was impressed. All he said was, “Are you aware of the HandleRegExpandSzRegistryKeys shim? It’s less risky to target the fix at the particular app, since hhctrl is the HTML Help control, the universe of possible side-effects is smaller than just adding the key and perpetuating the use of a hard-coded Windows path.” Well, no, I wasn’t aware of that shim at all. It’s certainly not one of the most over-documented shims we have. I Bing’d it and found Installation Failure Issues which lists twenty-six fixes (shims) with the symptoms they treat along with one-sentence descriptions. HandleRegExpandSzRegistryKeys is the last one on the page and says it is good for when “You cannot open the Readme.txt file.” However, if I had read and remembered every page of TechNet documentation written since Windows Vista shipped, I would have remembered this valuable description: “Modifies the RegQueryValueEx function of the REG_EXPAND_SZ registry keys so that it automatically expands the environment strings.” That is exactly what we need. Apply this shim to Law Manager so that whenever it reads a REG_EXPAND_SZ from the registry, the shim will expand any environment variables it finds before returning the data to the application.
Note that shims do not modify the executable image. When an application is configured to have shims applied to it, Windows loads one or more additional DLLs into the process’ address space and patches portions of memory to intercept API calls so that the shim DLL can manipulate incoming or outgoing data or change other results.
How do we configure this shim? Here is the step-by-step with lots of screenshots. Install the Application Compatibility Toolkit (ACT) v5.6, then run Compatibility Administrator (32-bit). Select the new custom database (selected by default) and click the “Fix” toolbar icon:
Enter the name and vendor of the program to be shimmed, browse to the file location to identify the executable image, and click Next:
We won’t use any compatibility modes, so click Next without selecting any:
Find and select the HandleRegExpandSzRegistryKeys compatibility fix. No parameters are needed this time. Because the call stack showed that lm2000.exe calls RegQueryValueEx directly, we don’t need to configure other calling modules to be fixed up. (See in the screenshot that the tooltip text says, “Applies to: Windows 95, Windows 98”? Yeah, that’s helpful. And a bug.)
On the Matching Information page, just accept the provided defaults and click Finish.
We now have a fix prepared. Click Save in the Compatibility Administrator toolbar:
Give the new shim database a display name (such as “Law Manager”), and save it to a file location, such as to LawManager.sdb on your desktop.
To install the new shim database on the current computer, right-click the custom database (the label next to the big oil barrel) and choose Install. This is the easiest way to install a shim database on a test system where you have Compatibility Administrator installed. To install it in an unattended fashion – such as in an automated Windows image build as an MDT task sequence, in a computer startup script configured through Group Policy, or incorporated into the application’s installation – use the Sdbinst.exe program that ships in Windows. (For example, “sdbinst.exe -q \\server\sysvol\shims\LawManager.sdb”.)
We now have a fix prepared and installed. As shown in the following screenshot, when Windows loads an executable image called “lm2000.exe” that has a binary file and product version of 18.104.22.168, text product and file version of “2.4 Service Pack 1 (204)”, a company name of “Law Manager, Inc.” and a product name of “LawManager.Pro” in its version resource information, Windows will apply the HandlRegExpandSzRegistryKeys fix to it:
After removing my previous HKCU registry edit, I ran Law Manager with the shim configured while monitoring with Procmon. Again, it ran successfully to the login dialog. Note the difference in the Procmon results. The HKCU\Software\Classes read failed with “Name not found”, followed by a successful read from the corresponding HKCR key. This is a very common pattern in Procmon traces. (In Procmon, HKCR refers to the global HKLM\Software\Classes.) At the low level that Procmon captured the RegQueryValueEx event, it shows that the value was still indeed a REG_EXPAND_SZ with an environment variable:
But opening that event’s Properties, you can see that the lm2000.exe code that had invoked RegQueryValueEx directly has now been redirected through AcLayers.dll, which expands the environment variables in the text to return to lm2000.exe.