Archive for June, 2007

Type 1 CustomAction Load Failures

When testing a custom action, it is usally practical to have the CA write some information to the installer log, so that you can verify that the CA has been called properly. But what if the installer log does not show up your information but instead the installation aborts and the log only states that the custom action returned 3 (ERROR_INSTALL_FAILURE)?

There may be various reasons for this to happen, but the most common reason is probably that your DLL was unable to load because of unsatisfied dependencies. Note that for the CA to work on all machines, it may only have dependencies to ‘standard DLLs’ — those DLLs that are available on a fresh Windows install.

Odds are that if you created the DLL with Visual C++ 2005 using standard settings (i.e. compiling with /MD[d]), VC has introduced a dependency to msvcr80.dll. As msvcr80.dll is not a ‘standard Windows DLL’, this will result in a DLL loading failure. The solution in this case is mostly as easy as switching to /MT[d].

Advertisements

Where to place RemoveExistingProducts in a major MSI upgrade

When Windows Installer performs a major upgrade, the position of the RemoveExistingProducts action determines when the uninstall of the old product is performed. Despite efficiency, two factors dominate the decision

  • What should happen if either the install or the uninstall fails?
  • Should the new product ‘see’ registry entries/files/etc from the existing product or should the new product install ‘start from scratch’?

The following table summarizes the four possible approaches.

Position Audit scripted? Which product remains if … Unchanged files are reinstalled?
uninstall fails? install fails?
Between InstallValidate and InstallInitializeRemove old, install new No Old Neither Yes
After InstallFinalizeInstall new, remove old No Both Old No
After InstallInitialize, before any script-generating actionsRemove old, install new Yes Old Old Yes
Between InstallExecute (or InstallExecuteAgain) and InstallFinalizeInstall new, remove old Yes Old Old No

Authoring multi-language MSI packages

By default, Windows Installer packages are single-language only — no direct support for multi-language packages is currently provided. Creating a multi-lanuage installation package is thus a little more compex — the following provides a summary of the steps required:

  • Create an english package
    • Compile

      candle -dLANG=1033 foo.wxs

    • Link

      light -out foo.msi -loc strings-en.wxl foo.wixobj

  • Create the german (or whatever language) package
    • Compile

      candle -dLANG=1031 foo.wxs

    • Link

      light -out foo-de.msi -loc strings-de.wxl foo.wixobj

    • Create a transform describing the differences between german and englich package:

      msitran -g foo.msi foo-de.msi de.mst

    • Embed the transform in the english package:msidb -d foo.msi -r de.mst

The boostrapper has to decide which language the installer package should use. To use english, call msiexec without any special parameters. To use german, pass the parameter TRANSFORMS=:de.mst to msiexec, which directs it to apply the embedded transform named de.mst before running the package.

To avoid creating a bootstrapper, you might want to try this. See also: WiX tutorial, Lesson 8.

Determining the apartment of a thread

There are situations in which it would be convenient to list which apartment the threads of a process belong to. In case of managed debugging, the !threads command provided by SOS gives this info:

PreEmptive   GC Alloc               Lock
ID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
0   688 00149528      6020 Enabled  00000000:00000000 00159e68     0 STA
1   f70 00165548      b220 Enabled  00000000:00000000 00159e68     0 MTA (Finalizer)

In case of unmanaged debugging, however, no such command exists (at least to my knowledge). So the first question is how the apartment-information can be retrieved for a given thread.

Knowing that calling CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ) followed by a CoInitializeEx( NULL, COINIT_MULTITHREADED ) yields an error (which implies that code checking which apartment the thread is currently in is executed), I decided to write up a test program and step through the second CoInitializeEx-call.

I whould have expected to find the information stored in some TLS-slot, however, this is not the case. Instead the TEB structure contains a field dedicated to OLE:

typedef struct _TEB
{
	/*...*/
	PVOID           ReservedForOle;
	/*...*/
} TEB, *PTEB;

As a side note — while dedicating a separate field to OLE may have its advantages, it actually vialolates the idea of layering. OLE/COM is layered above NT; NT should not even know about COM/OLEs existance and thus should not reserve a field for COM/OLE. As such, using TLS would have been the cleaner choice. But I digress…

While identifying this field within the TEB is straightforward, it is totally undocumented which structure this field points to. From the disassembly, it is visible that the apartment type is stored in some flag field at 0xC bytes offset. Fortunately, others have written about that before and have found out the flag values of this field. Of course, there is no guarantee that the values and the offset does not change in future releases of windows — all I can currently say is that the implementation works fine on WinXP x86. Given this information, I was able to code up a WinDBG debugging extension that offers me the information I was looking for:

0:008> ~*e !apt
Thread 0x0000057C Apartment: STA
Thread 0x0000053C Apartment: Not a COM thread
Thread 0x0000056C Apartment: Not a COM thread
Thread 0x00000538 Apartment: Unknown (Unrecognized flags)
Thread 0x00000568 Apartment: Not a COM thread
Thread 0x00000524 Apartment: STA
Thread 0x00000558 Apartment: MTA
Thread 0x00000550 Apartment: MTA

Threads for which the ReservedForOle pointer is NULL are reported as ‘Not a COM thread’. There are, however, threads for which the pointer is non-NULL, yet the aforementioned flag field contains the value 0x00000001, which can neither be identified as STA, MTA or TNA. They are thus reported as ‘Unknown’

The follwoing listing shows the code for retrieving the information I used within the debugging extension.

#define OLE_STA_MASK   0x080    // Bugslayer, MSJ 10/99
#define OLE_MTA_MASK   0x140    // Bugslayer, MSJ 10/99
#define OLE_TNA_MASK   0x800    // http://members.tripod.com/IUnknwn

#define JPDBGEXT_E_DEBUGEE_ERROR MAKE_HRESULT( 1, FACILITY_ITF, 0x200 );
#define JPDBGEXT_E_UNKNOWN_APT     MAKE_HRESULT( 1, FACILITY_ITF, 0x201 );

typedef struct _OLE_INFORMATION
{
    CHAR Padding[ 0xC ];
    DWORD Apartment;
} OLE_INFORMATION;

HRESULT JpDbgExtpGetThreadTebBaseAddress(
    __in HANDLE hThread,
    __out DWORD *pdwBaseAddress
    )
{
    THREAD_BASIC_INFORMATION threadInfo;
    DWORD retLen;
    NTSTATUS status;

    _ASSERTE( hThread );
    _ASSERTE( pdwBaseAddress );

    status = NtQueryInformationThread(
        hThread,
        ThreadBasicInformation,
        &threadInfo,
        sizeof( THREAD_BASIC_INFORMATION ),
        &retLen );
    if ( STATUS_SUCCESS != status )
    {
        return HRESULT_FROM_NT( status );
    }

    *pdwBaseAddress = * ( DWORD* ) &threadInfo.TebBaseAddress;
    return S_OK;
}

HRESULT JpDbgExtpGetApartmentType(
    __in HANDLE hThread,
    __out APARTMENT_TYPE *pApt
    )
{
    DWORD dwTebBaseAddress = 0;
    PVOID pOleAddress = 0;
    OLE_INFORMATION oleInfo;
    HRESULT hr = E_UNEXPECTED;
    TEB debugeeTeb;

    _ASSERTE( hThread );
    _ASSERTE( pApt );

    //
    // Get the debugee thread's TEB.
    //
    hr = JpDbgExtpGetThreadTebBaseAddress( hThread, &dwTebBaseAddress );
    if ( FAILED( hr ) )
    {
        return hr;
    }

    if ( ! ReadMemory(
        dwTebBaseAddress,
        &debugeeTeb,
        sizeof( TEB ),
        NULL ) )
    {
        return JPDBGEXT_E_DEBUGEE_ERROR;
    }

    //
    // Reach into the TEB and read OLE information.
    //
    pOleAddress = debugeeTeb.ReservedForOle;

    if ( pOleAddress == NULL )
    {
        //
        // Not a COM thread.
        //
        *pApt = APARTMENT_TYPE_NONE;
        hr = S_OK;
    }
    else
    {
        DWORD dwOleAddress = * ( DWORD* ) &pOleAddress;

        //
        // COM thread, get apartment
        //
        if ( ! ReadMemory(
            dwOleAddress,
            &oleInfo,
            sizeof( OLE_INFORMATION ),
            NULL ) )
        {
            return JPDBGEXT_E_DEBUGEE_ERROR;
        }

        if ( oleInfo.Apartment & OLE_STA_MASK )
        {
            *pApt = APARTMENT_TYPE_STA;
            hr = S_OK;
        }
        else if ( oleInfo.Apartment & OLE_MTA_MASK )
        {
            *pApt = APARTMENT_TYPE_MTA;
            hr = S_OK;
        }
        else if ( oleInfo.Apartment & OLE_TNA_MASK )
        {
            *pApt = APARTMENT_TYPE_TNA;
            hr = S_OK;
        }
        else
        {
            *pApt = APARTMENT_TYPE_UNKNOWN;
            hr = S_OK;
        }
    }

    return hr;
}

}

Categories




About me

Johannes Passing, M.Sc., living in Berlin, Germany.

Besides his consulting work, Johannes mainly focusses on Win32, COM, and NT kernel mode development, along with Java and .Net. He also is the author of cfix, a C/C++ unit testing framework for Win32 and NT kernel mode, Visual Assert, a Visual Studio Unit Testing-AddIn, and NTrace, a dynamic function boundary tracing toolkit for Windows NT/x86 kernel/user mode code.

Contact Johannes: jpassing (at) acm org

Johannes' GPG fingerprint is BBB1 1769 B82D CD07 D90A 57E8 9FE1 D441 F7A0 1BB1.

LinkedIn Profile
Xing Profile
Github Profile