Unable to view .CHM file content under Windows 7

There is much written elsewhere about problems viewing .chm files but I experienced an issue that doesn’t seem to be documented elsewhere so I thought I should share it.

I have a collection of  .chm files I had copied to a folder called #Reference on my D: drive (the # was there to make it appear at the top of the list), but when I tried to view the content I got a message “This page can’t be displayed Make sure the web address //ieframe.dll/dnserrordiagoff.htm# is correct.

I tried the other solutions for .chm files that are well reported, but none of them worked and strangely I found that if I copied the .chm file elsewhere I could view it without problem! After much digging I came across a reference to to this very old knowledge base article KB319247 which gave me the clue I needed. In my instance the # was in the folder name rather than the file name as mentioned in the KB, but it still applies. Once I removed the # the problem went away.

Note: I also found that using a % causes problems.

Installing .NET Assemblies

This seems to be a very poorly documented area so here are my gleanings on the subject:

Loading files into the Global Assembly Cache

The Global Assembly Cache or GAC is a machine-wide .NET assemblies cache for Microsoft’s CLR platform. The approach of having a specially controlled central repository addresses the shared library concept and helps to avoid pitfalls of other solutions that lead to drawbacks like DLL Hell. It is located at C:\Windows\Assembly.

To manually add a file to the Global Assembly cache the following command can be used:

gacutil.exe /i xyz.dll

NB the Adminstudio Helpfile comments: “Do not call the Global Assembly Cache tool (Gacutill.exe) from a custom action. This tool was not designed to be used during installations.”

Assemblies placed in the GAC Must have a strong name (digital signature). Errors will be listed during build if this is not the case and any attempt to install the resulting .msi will result in a rollback.

In Installshield an assembly is added to the cache via the following process:

Open your Project in Editor & do the following:

1. Go to Files & Folders under Application Data and select the [GlobalAssemblyCache] folder from the Pre-defined folder list
2. Add your .Net Assembly under this folder
3. Behind the scenes, the appropriate component will be created with the attributes set
4. When you build your project, Editor will extract Assembly information and populate the MsiAssembly and MsiAssemblyName tables. Amongst other things, this allows a database like transactional process for committing assemblies to the GAC, and hence proper rollback. It also allows proper file costing of .NET assemblies to occur.

Example Installshield .NET Assembly Component

Note the .NET Scan at build value is set to “Dependancies and Properties”. This should be changed to “Properties Only“. Any Dependancies, such as merge modules should have already been identified and catered for. This is the setting that populates the MsiAssembly andf MsiAssemblyName tables. NB The default can also be changed to “Properties Only” in Tools, Options, on the .NET tab.

Assemblies that are not placed in the GAC are known as Private assemblies.

Note: Dragging & dropping files into the GAC also appears to work.

Registering Assemblies

.NET assemblies do not typically use the registry as the the design goal is that they should be installable by the good old DOS method of just copying the files (presumably to compete with Java which does not use the registry). If the aplication needs to interoperate with COM however, the regasm command can be used used to register an assembly and this is very similar to using regsvr32 for ordinary .DLLs:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe  “C:\program files\Hummingbird\RBSCustom\Shared\Rbs.Gbm.Dms.EventSink.dll” /codebase

To unregister add -u parameter:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe  “C:\program files\Hummingbird\RBSCustom\Shared\Rbs.Gbm.Dms.EventSink.dll” /codebase -u

The following Regasm example also involves a .tlb file. If the .dll has been correctly written the .tlb should get

generated automatically by setting .NET COM Interop to Yes as above. Often however this doesn’t work and the .tlb has to be added manually. Note for package reworks where the .dll has been updated, the .tlb should match the .dll and not be from the earlier version of the .dll – the above regasm command can be used to generate the .tlb if you are unsure and you can then compare it with the .tlb present in the package. You may also find a version number in the .tlb if you open it in Notepad, that should match the .dll version.

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm” HdAnswerSource.dll /tlb:HdAnswerSource.tlb /codebase

In Installshield, Regasm functionality is replaced with setting the .NET COM Interop property for the component to Yes (shown as No in the screenshot above as No is more common) this causes the relevant COM registry entries to be generated at build time and placed in the .msi. A .reg file is also created in the source folder containing the same COM entries. If you have performed a capture, you will probably already have the associated registry entires in your Registry bucket component and the .reg file can be useful for identifying these. NB: the .reg file may contain some Codebase keys that point at hardcoded file locations in your source, but using .NET COM Interop results in the correct path being recorded in the .msi registry entries generated at build time. It does this by changing the path to a variable based on the destination of the component. One slight anomaly resulting from this is that the variable uses backslashes, whereas native Codebase entries use forward slashes. I have not as yet found this to be an issue.


Codebase

The /codebase parameter in the example regasm command above, adds entries to the registry under the relevant Clsid, pointing to the source location of the assembly. This is for information purposes only and in .msi terms, is only valid for private assemblies. Shared assemblies are loaded directly into the GAC by the .msi and therefore /codebase does not apply. The .reg file mentioned above (generated as a result of Setting .NET COM Interop for the component to yes) will not contain a codebase entry if you have specified that it is being installed to [GlobalAssemblyCache]. If it is a private assembly however (placed under your Installdir rather than in the GAC), the Codebase entry will be generated.

Note: using gacutil to load an assembly to the GAC also results in a Codebase entry, as there is a source location for the file at the time when gacutil is run.

[HKEY_CLASSES_ROOT\CLSID\{E454C990-27C0-4495-9E17-7944C6AEC10F}\InprocServer32]
@=”mscoree.dll”
“ThreadingModel”=”Both”
“Class”=”Hummingbird.DM.Extensions.Forms.LocationSave.CreateProfileFormImpl”
“Assembly”=”Hummingbird.DM.Extensions.Forms.LocationSave, Version=5.2.1.0, Culture=neutral, PublicKeyToken=null”
“RuntimeVersion”=”v2.0.50727”
“CodeBase”=”file:///C:/Program Files/Hummingbird/DM Extensions/Hummingbird.DM.Extensions.Forms.LocationSave.dll”

[HKEY_CLASSES_ROOT\CLSID\{E454C990-27C0-4495-9E17-7944C6AEC10F}\InprocServer32\5.2.1.0]
“Class”=”Hummingbird.DM.Extensions.Forms.LocationSave.CreateProfileFormImpl”
“Assembly”=”Hummingbird.DM.Extensions.Forms.LocationSave, Version=5.2.1.0, Culture=neutral, PublicKeyToken=null”
“RuntimeVersion”=”v2.0.50727”
“CodeBase”=”file:///C:/Program Files/Hummingbird/DM Extensions/Hummingbird.DM.Extensions.Forms.LocationSave.dll”

Since writing this I have found some assemblies loaded into the GAC that also had Codebase entries pointing at the GAC.

Identifying Assemblies

Where COM Interop is being used an Assembly can be identified by an Assembly value Under the InprocServer32 entry in the registry. Assemblies can also often be identified by looking at the Version tab under properties for the file and the Language is shown as Language Neutral.  Another approach is to open the file in notepad, sroll to the end and look for the word A s s e m b l y. In Dependancy Walker the .dll will show a dependency on MSCOREE.DLL.

Guidelines from the Windows Installer SDK

  • A Windows Installer component should contain no more than one assembly.
  • All of the files in the assembly should be in a single component.
  • Each component that contains an assembly should have an entry in the MsiAssembly table.
  • The strong assembly cache name of each assembly should be authored into the MsiAssemblyName table.
  • Use the Registry table instead of the Class table when you register COM Interop for an assembly.
  • Assemblies that have the same strong name are the same assembly. When the same assembly is installed by different applications, the components that contain the assembly should use the same value for the ComponentId in their Component tables.
  • The KeyPath value in the Component table for a component that contains the assembly should not be Null.

Error Knowledgebase:

When you build the .msi, you may get the following error shown in the tasks tab in the “Output Window” pane at the bottom of the editor screen :

ISDEV : error -6210: An error occurred building COM .NET Interop information for Component rbs.gbm.dms.eventsink.dll

This may well be because it was not possible to resolve a dependancy. Switch to the build tab and you will find the output of the regasm command:

e.g. RegAsm : error RA0000 : Could not load file or assembly ‘Hummingbird.DM.Extensions.Interop.VBFORMS, Version=5.2.1.0, Culture=neutral, PublicKeyToken=fcfae4436272f66a’ or one of its dependencies. The system cannot find the file specified.

In this instance the dependancy was Hummingbird.DM.Extensions.Interop.VBFORMS.dll. If this is being installed by your package, place a copy of the file in the same folder as the assembly you are registering. If you get another error -6120 go through the same process and see if this time a different file is required.

When all the dependancies are found the error will go away and the relevant COM registry will be added to the .msi.

http://support.installshield.com/kb/view.asp?articleid=Q107276

Notes

Sometimes when working interactively Windows may prevent you from deleting files from the GAC. Try the following:
Back up [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Assemblies\Global] to a .reg file, then delete this key. Delete the assembly(s) from the GAC, reinstate the reg file (edit first if necessary).

After you have installed the .NET Framework, the .NET Framework Optimisation Service will run to precompile .NET assemblies in the background. Typically it will be done with the high priority assemblies in 5-10 minutes, and wait until your PC is idle to process the rest. This may corrupt your capture, so you might want to disable the service temporarily (I haven’t tried this), or leave the PC for long enough till it is finished

Sources:

http://www.codeproject.com/KB/dotnet/demystifygac.aspx

Troubleshooting .NET Assembly Installation Issues

http://blogs.msdn.com/davidnotario/archive/2005/04/27/412838.aspx

NGENhttp://msdn.microsoft.com/en-gb/magazine/cc163808.aspx