//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997 - 2000.
//
//  File:       cimbmgr.cxx
//
//  Contents:   Content Index Meta Base Manager
//
//  Classes:    CMetaDataMgr
//
//  History:    07-Feb-1997   dlee    Created
//              24-Apr-1997   dlee    Converted to new Unicode interface
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <cimbmgr.hxx>

#define MYDEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
    EXTERN_C const GUID name \
    = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }

MYDEFINE_GUID(CLSID_MSAdminBase_W, 0xa9e69610, 0xb80d, 0x11d0, 0xb9, 0xb9, 0x0, 0xa0, 0xc9, 0x22, 0xe7, 0x50);
MYDEFINE_GUID(IID_IMSAdminBase_W, 0x70b51430, 0xb6ca, 0x11d0, 0xb9, 0xb9, 0x0, 0xa0, 0xc9, 0x22, 0xe7, 0x50);
MYDEFINE_GUID(CLSID_MSAdminBaseExe_W, 0xa9e69611, 0xb80d, 0x11d0, 0xb9, 0xb9, 0x0, 0xa0, 0xc9, 0x22, 0xe7, 0x50);
MYDEFINE_GUID(IID_IMSAdminBaseSink_W, 0xa9e69612, 0xb80d, 0x11d0, 0xb9, 0xb9, 0x0, 0xa0, 0xc9, 0x22, 0xe7, 0x50);

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::CMetaDataMgr, public
//
//  Synopsis:   Creates an object for talking to the IIS metabase.
//
//  Arguments:  [fTopLevel]  - TRUE for the top level, FALSE for a
//                             vserver instance
//              [dwInstance] - instance # of the server
//              [eType]      - type of vroot provider -- W3, NNTP, or IMAP
//              [pwcMachine] - the machine to open, L"." for local machine
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

CMetaDataMgr::CMetaDataMgr(
    BOOL            fTopLevel,
    CiVRootTypeEnum eType,
    DWORD           dwInstance,
    WCHAR const *   pwcMachine ) :
        _fNotifyEnabled( FALSE ),
        _fTopLevel( fTopLevel )
{
    // -1 is a valid instance number, so this is a bogus assert, but it'll
    // never be hit unless something else is broken or someone hacked the
    // registry.

    #if CIDBG == 1

        if ( fTopLevel )
            Win4Assert( 0xffffffff == dwInstance );
        else
            Win4Assert( 0xffffffff != dwInstance );

    #endif // CIDBG == 1

    if ( fTopLevel )
        swprintf( _awcInstance, L"/lm/%ws", GetVRootService( eType ) );
    else
        swprintf( _awcInstance,
                  L"/lm/%ws/%d",
                  GetVRootService( eType ),
                  dwInstance );

    IMSAdminBase * pcAdmCom;

    if ( !_wcsicmp( pwcMachine, L"." ) )
    {
        SCODE sc = CoCreateInstance( GETAdminBaseCLSID(TRUE),
                                     NULL,
                                     CLSCTX_ALL,
                                     IID_IMSAdminBase,
                                     (void **) &pcAdmCom );
        if ( FAILED(sc) )
        {
            ciDebugOut(( DEB_WARN, "CMetaDataMgr can't CoCreateInstance: %x\n", sc ));
            THROW( CException(sc) );
        }
    }
    else
    {
        COSERVERINFO info;
        RtlZeroMemory( &info, sizeof info );
        info.pwszName = (WCHAR *) pwcMachine;

        XInterface<IClassFactory> xFactory;
        SCODE sc = CoGetClassObject( GETAdminBaseCLSID(TRUE),
                                     CLSCTX_SERVER,
                                     &info,
                                     IID_IClassFactory,
                                     xFactory.GetQIPointer() );

        if ( FAILED(sc) )
        {
            ciDebugOut(( DEB_WARN, "CMetaDataMgr can't CoGetClassObject: %x\n", sc ));
            THROW( CException(sc) );
        }

        sc = xFactory->CreateInstance( 0, IID_IMSAdminBase, (void**) &pcAdmCom );

        if ( FAILED(sc) )
        {
            ciDebugOut(( DEB_WARN, "CMetaDataMgr can't CreateInstance: %x\n", sc ));
            THROW( CException(sc) );
        }
    }

    _xAdminBase.Set( pcAdmCom );
} //CMetaDataMgr

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::~CMetaDataMgr, public
//
//  Synopsis:   Destroys an object for talking to the IIS metabase
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

CMetaDataMgr::~CMetaDataMgr()
{
    if ( !_xAdminBase.IsNull() )
    {
        // just in case we are still connected

        DisableVPathNotify();
    }
} //~CMetaDataMgr

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::IsIISAdminUp, public/static
//
//  Synopsis:   Returns TRUE if iisadmin svc is up
//
//  Arguments:  [fIISAdminInstalled] - returns TRUE if it's installed,
//                                     FALSE otherwise
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

BOOL CMetaDataMgr::IsIISAdminUp(
    BOOL & fIISAdminInstalled )
{
    fIISAdminInstalled = TRUE;
    BOOL fIsUp = TRUE;

    TRY
    {
        // The constructor will throw if the iisadmin svc is unavailable.

        CMetaDataMgr( TRUE, W3VRoot );
    }
    CATCH( CException, e )
    {
        fIsUp = FALSE;

        if ( REGDB_E_CLASSNOTREG == e.GetErrorCode() )
            fIISAdminInstalled = FALSE;
    }
    END_CATCH

    return fIsUp;
} //IsIISAdminUp

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::EnumVPaths, public
//
//  Synopsis:   Enumerates vpaths by calling the callback
//
//  Arguments:  [callBack] - called for each vpath
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::EnumVPaths(
    CMetaDataCallBack & callBack )
{
    Win4Assert( !_fTopLevel );

    CMetaDataHandle mdRoot( _xAdminBase, _awcInstance );

    CVRootStack vrootStack;

    Enum( vrootStack, callBack, mdRoot, L"" );
} //EnumVPaths

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::AddVRoot, public
//
//  Synopsis:   Adds a VRoot to the metabase
//
//  Arguments:  [pwcVRoot]  - name of the vroot, e.g.: /here
//              [pwcPRoot]  - physical path of the vroot, e.g.: x:\here
//              [dwAccess]  - access rights to the vroot
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::AddVRoot(
    WCHAR const * pwcVRoot,
    WCHAR const * pwcPRoot,
    DWORD         dwAccess )
{
    Win4Assert( !_fTopLevel );

    // blow it away if it currently exists

    RemoveVRoot( pwcVRoot );

    {
        unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" );
        if ( cwc >= METADATA_MAX_NAME_LEN )
            THROW( CException( E_INVALIDARG ) );

        WCHAR awc[ METADATA_MAX_NAME_LEN ];
        wcscpy( awc, _awcInstance );
        wcscat( awc, L"/Root" );
        CMetaDataHandle mdRoot( _xAdminBase, awc, TRUE );

        _xAdminBase->AddKey( mdRoot.Get(), pwcVRoot );
    }

    WCHAR awcVRootPath[ METADATA_MAX_NAME_LEN ];

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVRoot );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    wcscpy( awcVRootPath, _awcInstance );
    wcscat( awcVRootPath, L"/Root" );
    wcscat( awcVRootPath, pwcVRoot );

    CMetaDataHandle mdVRoot( _xAdminBase, awcVRootPath, TRUE );

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_VR_PATH;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.dwMDUserType = IIS_MD_UT_FILE;
        mdr.dwMDDataType = STRING_METADATA;
        mdr.pbMDData = (BYTE *) pwcPRoot;
        mdr.dwMDDataLen = sizeof WCHAR * ( 1 + wcslen( pwcPRoot ) );

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->SetData( mdVRoot.Get(),
                                         L"",
                                         &mdr );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "SetData PRoot failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
    {
        // must set a null username to enforce metadata consistency

        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_VR_USERNAME;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.dwMDUserType = IIS_MD_UT_SERVER;
        mdr.dwMDDataType = STRING_METADATA;
        mdr.pbMDData = (BYTE *) L"";
        mdr.dwMDDataLen = sizeof WCHAR;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->SetData( mdVRoot.Get(),
                                         L"",
                                         &mdr );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "SetData user failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_ACCESS_PERM;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.dwMDUserType = IIS_MD_UT_FILE;
        mdr.dwMDDataType = DWORD_METADATA;
        mdr.pbMDData = (BYTE *) &dwAccess;
        mdr.dwMDDataLen = sizeof dwAccess;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->SetData( mdVRoot.Get(),
                                         L"",
                                         &mdr );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "SetData accessperm failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
} //AddVRoot

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::Flush, public
//
//  Synopsis:   Flushes the metabase, since it's not robust.
//
//  History:    4-Dec-1998   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::Flush()
{
    SCODE sc = _xAdminBase->SaveData();

    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "CMetaDataMgr::Flush failed: %#x\n", sc ));
        THROW( CException( sc ) );
    }
} //Flush

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::RemoveVRoot, public
//
//  Synopsis:   Removes a VRoot from the metabase
//
//  Arguments:  [pwcVRoot]   - name of the vroot. e.g.: /scripts
//
//  Notes:      Doesn't throw on failure to remove the root.
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

SCODE CMetaDataMgr::RemoveVRoot(
    WCHAR const * pwcVRoot )
{
    Win4Assert( !_fTopLevel );

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcRoot[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcRoot, _awcInstance );
    wcscat( awcRoot, L"/Root" );

    CMetaDataHandle mdRoot( _xAdminBase, awcRoot, TRUE );

    // don't throw on error deleting vroot -- the root may not exist

    return _xAdminBase->DeleteKey( mdRoot.Get(), pwcVRoot );
} //RemoveVRoot

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::WriteRootValue, private
//
//  Synopsis:   Retrieves data for the key and identifier
//
//  Arguments:  [mdRoot] - Metabase key where data reside
//              [mdr]    - Scratch pad for the record.  On output, can be
//                         used to write the data, since all the
//                         fields are initialized properly.
//              [xData]  - Where data is written
//              [dwIdentifier] - The metabase id
//
//  History:    24-Feb-1998   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::ReadRootValue(
    CMetaDataHandle &  mdRoot,
    METADATA_RECORD &  mdr,
    XGrowable<WCHAR> & xData,
    DWORD              dwIdentifier )
{
    RtlZeroMemory( &mdr, sizeof mdr );
    mdr.dwMDIdentifier = dwIdentifier;

    // script maps, etc. can be enormous due to bugs in ISV apps

    do
    {
        mdr.pbMDData = (BYTE *) xData.Get();
        mdr.dwMDDataLen = xData.SizeOf();

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                         L"",
                                         &mdr,
                                         &cbRequired );

        if ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == sc )
        {
            xData.SetSizeInBytes( cbRequired );
            continue;
        }
        else if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData root value failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }

        break;
    } while ( TRUE );
} //ReadRootValue

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::WriteRootValue, private
//
//  Synopsis:   Writes data to the key
//
//  Arguments:  [mdRoot]  - Metabase key where data resides
//              [mdr]     - Metadata record suitable for writing data
//              [pwcData] - Multi-sz string with data
//              [cwcData] - Total length including all terminating nulls
//
//  History:    24-Feb-1998   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::WriteRootValue(
    CMetaDataHandle & mdRoot,
    METADATA_RECORD & mdr,
    WCHAR const *     pwcData,
    unsigned          cwcData )
{
    // note: the other fields in mdr were initialized properly by a call
    //       to ReadRootValue.

    mdr.dwMDDataLen = sizeof WCHAR * cwcData;
    mdr.pbMDData = (BYTE *) pwcData;

    SCODE sc = _xAdminBase->SetData( mdRoot.Get(),
                                     L"",
                                     &mdr );

    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "SetData root value failed: 0x%x\n", sc ));
        THROW( CException( sc ) );
    }
} //WriteRootValue

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::AddScriptMap, public
//
//  Synopsis:   Adds a script map to the metabase
//
//  Arguments:  [pwcMap]  - script map of the form:
//                          L".idq,d:\\winnt\\system32\\idq.dll,0"
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::AddScriptMap(
    WCHAR const * pwcMap )
{
    // remove the existing script map if it exists.  have to get the
    // extension first.

    if ( wcslen( pwcMap ) > MAX_PATH )
        THROW( CException( HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ) ) );

    WCHAR awcExt[ MAX_PATH ];
    wcscpy( awcExt, pwcMap );
    WCHAR *pwc = wcschr( awcExt, L',' );
    if ( !pwc )
        THROW( CException( HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ) ) );
    *pwc = 0;

    RemoveScriptMap( awcExt );

    CMetaDataHandle mdRoot( _xAdminBase, L"/lm/w3svc", TRUE );
    XGrowable<WCHAR> xMaps;
    METADATA_RECORD mdr;

    ReadRootValue( mdRoot, mdr, xMaps, MD_SCRIPT_MAPS );

    // add the new script map to the existing ones

    XGrowable<WCHAR> xTemp;
    xTemp.SetSize( xMaps.Count() + wcslen( pwcMap ) + 1 );
    WCHAR *pwcTemp = xTemp.Get();
    wcscpy( pwcTemp, pwcMap );
    pwcTemp += ( 1 + wcslen( pwcTemp ) );

    WCHAR * pwcOldMaps = xMaps.Get();
    while ( 0 != *pwcOldMaps )
    {
        wcscpy( pwcTemp, pwcOldMaps );
        int x = 1 + wcslen( pwcOldMaps );
        pwcTemp += x;
        pwcOldMaps += x;
    }

    *pwcTemp++ = 0;

    // write the new set of script maps

    WriteRootValue( mdRoot, mdr, xTemp.Get(), (UINT)( pwcTemp - xTemp.Get() ) );
} //AddScriptMap

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::RemoveScriptMap, public
//
//  Synopsis:   Removes a script map from the metabase
//
//  Arguments:  [pwcExt]     - extension of map to remove:  L".idq"
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::RemoveScriptMap(
    WCHAR const * pwcExt )
{
    // retrieve the existing script maps

    CMetaDataHandle mdRoot( _xAdminBase, L"/lm/w3svc", TRUE );
    XGrowable<WCHAR> xMaps;
    METADATA_RECORD mdr;

    ReadRootValue( mdRoot, mdr, xMaps, MD_SCRIPT_MAPS );

    // awcMaps is a multi-sz string

    XGrowable<WCHAR> xNew( xMaps.Count() );
    WCHAR *pwcNew = xNew.Get();
    pwcNew[0] = 0;
    int cwcExt = wcslen( pwcExt );
    BOOL fFound = FALSE;

    // re-add all mappings other than pwcExt

    WCHAR const *pwcCur = xMaps.Get();
    while ( 0 != *pwcCur )
    {
        if ( _wcsnicmp( pwcCur, pwcExt, cwcExt ) ||
             L',' != pwcCur[cwcExt] )
        {
            wcscpy( pwcNew, pwcCur );
            pwcNew += ( 1 + wcslen( pwcNew ) );
        }
        else
        {
            fFound = TRUE;
        }

        int cwc = wcslen( pwcCur );
        pwcCur += ( cwc + 1 );
    }

    // If the script map wasn't found, don't do the write

    if ( !fFound )
        return;

    *pwcNew++ = 0;

    // got the string, now pound it in the metabase

    WriteRootValue( mdRoot, mdr, xNew.Get(), (UINT)(pwcNew - xNew.Get()) );
} //RemoveScriptMap

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::ExtensionHasScriptMap, public
//
//  Synopsis:   Finds a script map in the metabase
//
//  Arguments:  [pwcExt]     - extension of map to lookup:  L".idq"
//
//  Returns:    TRUE if the extension has a script map association
//              FALSE otherwise
//
//  History:    10-Jul-1997   dlee    Created
//
//--------------------------------------------------------------------------

BOOL CMetaDataMgr::ExtensionHasScriptMap(
    WCHAR const * pwcExt )
{
    CMetaDataHandle mdRoot( _xAdminBase, L"/lm/w3svc", FALSE );
    XGrowable<WCHAR> xMaps;
    METADATA_RECORD mdr;

    ReadRootValue( mdRoot, mdr, xMaps, MD_SCRIPT_MAPS );

    // xMaps is a multi-sz string, look for pwcExt

    int cwcExt = wcslen( pwcExt );
    WCHAR *pwcCur = xMaps.Get();

    while ( 0 != *pwcCur )
    {
        if ( !_wcsnicmp( pwcCur, pwcExt, cwcExt ) &&
             L',' == pwcCur[cwcExt] )
            return TRUE;

        int cwc = wcslen( pwcCur );
        pwcCur += ( cwc + 1 );
    }

    return FALSE;
} //ExtensionHasScriptMap

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::ExtensionHasTargetScriptMap, public
//
//  Synopsis:   Sees if a scriptmap is pointing at a given dll
//
//  Arguments:  [pwcExt]     - extension of map to lookup:  L".idq"
//              [pwcDll]     - DLL to check, e.g. L"idq.dll"
//
//  Returns:    TRUE if the extension has a script map association
//              FALSE otherwise
//
//  History:    10-Jul-1997   dlee    Created
//
//  Note:       scriptmaps look like ".idq,c:\\windows\\system32\\idq.dll,3,GET,HEAD,POST"
//                  
//--------------------------------------------------------------------------

BOOL CMetaDataMgr::ExtensionHasTargetScriptMap(
    WCHAR const * pwcExt,
    WCHAR const * pwcDll )
{
    CMetaDataHandle mdRoot( _xAdminBase, L"/lm/w3svc", FALSE );
    XGrowable<WCHAR> xMaps;
    METADATA_RECORD mdr;

    ReadRootValue( mdRoot, mdr, xMaps, MD_SCRIPT_MAPS );

    // xMaps is a multi-sz string, look for pwcExt

    int cwcExt = wcslen( pwcExt );
    WCHAR *pwcCur = xMaps.Get();
    int cwcDll = wcslen( pwcDll );

    while ( 0 != *pwcCur )
    {
        if ( !_wcsnicmp( pwcCur, pwcExt, cwcExt ) &&
             L',' == pwcCur[cwcExt] )
        {
            // Skip to the end of the full path and check the dll name

            WCHAR const * pwcSlash = wcsrchr( pwcCur, L'\\' );

            if ( 0 != pwcSlash )
            {
                if ( !_wcsnicmp( pwcSlash + 1, pwcDll, cwcDll ) )
                    return TRUE;
            }
        }

        int cwc = wcslen( pwcCur );
        pwcCur += ( cwc + 1 );
    }

    return FALSE;
} //ExtensionHasTargetScriptMap

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::AddInProcessApp, public
//
//  Synopsis:   Adds an app to the list of in-process apps
//
//  Arguments:  [pwcApp] - App in the form "d:\\winnt\\system32\\idq.dll"
//
//  History:    09-Dec-1998   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::AddInProcessIsapiApp( WCHAR const * pwcApp )
{
    //
    // Read the existing multi-sz value
    //

    CMetaDataHandle mdRoot( _xAdminBase, L"/lm/w3svc", TRUE );
    XGrowable<WCHAR> xApps;
    METADATA_RECORD mdr;
    ReadRootValue( mdRoot, mdr, xApps, MD_IN_PROCESS_ISAPI_APPS );
    WCHAR *pwcCur = xApps.Get();

    //
    // Look for an existing entry so a duplicate isn't added
    //

    while ( 0 != *pwcCur )
    {
        //
        // If it's already there, leave it alone
        //

        if ( !_wcsicmp( pwcCur, pwcApp ) )
            return;

        int cwc = wcslen( pwcCur );
        pwcCur += ( cwc + 1 );
    }

    unsigned cwcOld = 1 + (unsigned) ( pwcCur - xApps.Get() );

    //
    // It wasn't found, so add it
    //

    unsigned cwc = wcslen( pwcApp ) + 1;
    XGrowable<WCHAR> xNew( cwcOld + cwc );

    RtlCopyMemory( xNew.Get(), pwcApp, cwc * sizeof WCHAR );
    RtlCopyMemory( xNew.Get() + cwc, xApps.Get(), cwcOld * sizeof WCHAR );

    WriteRootValue( mdRoot, mdr, xNew.Get(), cwcOld + cwc );
} //AddInProcessIsapiApp

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::EnableVPathNotify, public
//
//  Synopsis:   Enables notification of vpaths
//
//  Arguments:  [pCallBack] - called on a change to any vpaths
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::EnableVPathNotify(
    CMetaDataVPathChangeCallBack *pCallBack )
{
    Win4Assert( !_fTopLevel );
    Win4Assert( !_fNotifyEnabled );

    if ( _fNotifyEnabled )
        THROW( CException( HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ) ) );

    ciDebugOut(( DEB_WARN,
                 "enabling vpath notifications on '%ws'\n",
                 _awcInstance ));

    Win4Assert( _xSink.IsNull() );

    //
    // NOTE: This new will be reported as a leak if iisamin dies or fails
    //       to call Release() on the sink the right number of times.
    //

    XInterface<CMetaDataComSink> xSink( new CMetaDataComSink() );

    xSink->SetCallBack( pCallBack );
    xSink->SetInstance( _awcInstance );

    XInterface< IConnectionPointContainer > xCPC;
    SCODE sc = _xAdminBase->QueryInterface( IID_IConnectionPointContainer,
                                            xCPC.GetQIPointer() );

    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "could not get cpc: 0x%x\n", sc ));
        THROW( CException( sc ) );
    }

    XInterface< IConnectionPoint> xCP;
    sc = xCPC->FindConnectionPoint( IID_IMSAdminBaseSink, xCP.GetPPointer() );

    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "could not get cp: 0x%x\n", sc ));
        THROW( CException( sc ) );
    }

    //
    // Tell COM to impersonate at IMPERSONATE level instead of the default
    // IDENTITY level.  This is to work-around a change made in IIS in
    // Windows 2000 SP1.
    //

    sc = CoSetProxyBlanket( xCP.GetPointer(),
                            RPC_C_AUTHN_WINNT,      // use NT default security
                            RPC_C_AUTHZ_NONE,       // use NT default authentication
                            NULL,                   // must be null if default
                            RPC_C_AUTHN_LEVEL_CALL, // call
                            RPC_C_IMP_LEVEL_IMPERSONATE,
                            NULL,                   // use process token
                            EOAC_STATIC_CLOAKING );    
    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "could not set proxy blanket: %#x\n", sc ));
        THROW( CException( sc ) );
    }

    sc = xCP->Advise( xSink.GetPointer(), &_dwCookie);

    if ( FAILED( sc ) )
    {
        ciDebugOut(( DEB_WARN, "could not advise cp: 0x%x\n", sc ));
        THROW( CException( sc ) );
    }

    _xCP.Set( xCP.Acquire() );
    _xSink.Set( xSink.Acquire() );

    _fNotifyEnabled = TRUE;
} //EnableVPathNotify

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::DisableVPathNotify, public
//
//  Synopsis:   Disables notification on VPaths
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::DisableVPathNotify()
{
    if ( _fNotifyEnabled )
    {
        SCODE sc = _xCP->Unadvise( _dwCookie );
        ciDebugOut(( DEB_ITRACE, "result of unadvise: 0x%x\n", sc ));
        _fNotifyEnabled = FALSE;

        //
        // Note: The sink may still have a refcount after the free if
        //       iisadmin has a bug or their process died.  We can't just
        //       delete it here because CoUninitialize() will realize
        //       iisadmin messed up and try to help by calling Release()
        //       for iisadmin.
        //

        _xSink.Free();
        _xCP.Free();
    }
} //DisableVPathNotify

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::ReportVPath, private
//
//  Synopsis:   Helper method for reporting a vpath via callback
//
//  Arguments:  [vrootStack]  - stack of vroots for depth-first search
//              [mdRoot]      - handle to the root of the enumeration
//              [callBack]    - callback to call for vpath
//              [pwcRelative] - relative path in metabase
//
//  Returns:    TRUE if the vpath is a vroot and was pushed on vrootStack
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

BOOL CMetaDataMgr::ReportVPath(
    CVRootStack &       vrootStack,
    CMetaDataCallBack & callBack,
    CMetaDataHandle &   mdRoot,
    WCHAR const *       pwcRelative )
{
    Win4Assert( !_fTopLevel );

    // read the path, username, access permissions, and password

    WCHAR awcPPath[ METADATA_MAX_NAME_LEN ];
    awcPPath[0] = 0;
    WCHAR awcUser[ METADATA_MAX_NAME_LEN ];
    awcUser[0] = 0;
    DWORD dwAccess = 0;
    WCHAR awcPassword[ METADATA_MAX_NAME_LEN ];
    awcPassword[0] = 0;
    BOOL fIsIndexed = TRUE;
    BOOL fVRoot = TRUE;

    // Get the metabase access permission mask

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_ACCESS_PERM;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.pbMDData = (BYTE *) &dwAccess;
        mdr.dwMDDataLen = sizeof dwAccess;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                         pwcRelative,
                                         &mdr,
                                         &cbRequired );

        if ( ( FAILED( sc ) ) &&
             ( (MD_ERROR_DATA_NOT_FOUND) != sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData awccessperm failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }

    // Get the physical path if one exists

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_VR_PATH;
        mdr.pbMDData = (BYTE *) awcPPath;
        mdr.dwMDDataLen = sizeof awcPPath;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                         pwcRelative,
                                         &mdr,
                                         &cbRequired );

        if ( ( FAILED( sc ) ) &&
             ( (MD_ERROR_DATA_NOT_FOUND) != sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData PRoot failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }

    //
    // Only look for a username/access/password if there is a physical path,
    // since it can only be a virtual root if it has a physical path.
    //

    if ( 0 != awcPPath[0] )
    {
        // Trim any trailing backslash from the physical path

        unsigned cwcPRoot = wcslen( awcPPath );
        Win4Assert( 0 != cwcPRoot );

        if ( L'\\' == awcPPath[ cwcPRoot - 1 ] )
            awcPPath[ cwcPRoot - 1 ] = 0;


        {
            METADATA_RECORD mdr;
            RtlZeroMemory( &mdr, sizeof mdr );
            mdr.dwMDIdentifier = MD_VR_USERNAME;
            mdr.pbMDData = (BYTE *) awcUser;
            mdr.dwMDDataLen = sizeof awcUser;

            DWORD cbRequired = 0;
            SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                             pwcRelative,
                                             &mdr,
                                             &cbRequired );

            if ( ( FAILED( sc ) ) &&
                 ( (MD_ERROR_DATA_NOT_FOUND) != sc ) )
            {
                ciDebugOut(( DEB_WARN, "GetData user failed: 0x%x\n", sc ));
                THROW( CException( sc ) );
            }
        }
        {
            METADATA_RECORD mdr;
            RtlZeroMemory( &mdr, sizeof mdr );
            mdr.dwMDIdentifier = MD_VR_PASSWORD;
            mdr.pbMDData = (BYTE *) awcPassword;
            mdr.dwMDDataLen = sizeof awcPassword;

            DWORD cbRequired = 0;
            SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                             pwcRelative,
                                             &mdr,
                                             &cbRequired );

            if ( ( FAILED( sc ) ) &&
                 ( (MD_ERROR_DATA_NOT_FOUND) != sc ) )
            {
                ciDebugOut(( DEB_WARN, "GetData password failed: 0x%x\n", sc ));
                THROW( CException( sc ) );
            }
        }
    }

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_IS_CONTENT_INDEXED;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.pbMDData = (BYTE *) &fIsIndexed;
        mdr.dwMDDataLen = sizeof fIsIndexed;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                         pwcRelative,
                                         &mdr,
                                         &cbRequired );

        if ( MD_ERROR_DATA_NOT_FOUND == sc )
        {
            fIsIndexed = FALSE;
        }
        else if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData isindexed failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
    WCHAR awcVPath[METADATA_MAX_NAME_LEN];
    wcscpy( awcVPath, pwcRelative );
    WCHAR *pwcVPath = awcVPath;

    // Important: The metabase names root "/Root" the rest of the world
    //            names root "/".

    if ( !_wcsicmp( awcVPath, L"/Root" ) )
        awcVPath[1] = 0;
    else if ( !_wcsnicmp( awcVPath, L"/Root", 5 ) )
    {
        Win4Assert( L'/' == awcVPath[5] );
        pwcVPath = awcVPath + 5;
    }

    if ( 0 == awcPPath[0] )
    {
        // if there isn't a physical path, it isn't a virtual root.

        fVRoot = FALSE;

        if ( vrootStack.IsEmpty() )
        {
            awcPPath[0] = 0;
        }
        else
        {
            // generate a physical path based on the virtual path and
            // the most recent vroot parent on the stack

            ciDebugOut(( DEB_ITRACE,
                         "making vpath from vroot '%ws' proot '%ws' vpath '%ws'\n",
                         vrootStack.PeekTopVRoot(),
                         vrootStack.PeekTopPRoot(),
                         pwcVPath  ));

            wcscpy( awcPPath, vrootStack.PeekTopPRoot() );

            unsigned cwcVRoot = wcslen( vrootStack.PeekTopVRoot() );
            unsigned cwcPRoot = wcslen( vrootStack.PeekTopPRoot() );

            // The metabase can contain trailing backslashes in physical paths

            if ( L'\\' == awcPPath[ cwcPRoot - 1 ] )
                cwcPRoot--;

            wcscpy( awcPPath + cwcPRoot,
                    pwcVPath + ( ( 1 == cwcVRoot ) ? 0 : cwcVRoot ) );

            for ( WCHAR *pwc = awcPPath + cwcPRoot;
                  0 != *pwc;
                  pwc++ )
            {
                if ( L'/' == *pwc )
                    *pwc = L'\\';
            }

            ciDebugOut(( DEB_ITRACE, "resulting ppath: '%ws'\n", awcPPath ));
        }
    }

    // now we can finally call the callback

    if ( 0 != awcPPath[0] )
        callBack.CallBack( pwcVPath,
                           awcPPath,
                           fIsIndexed,
                           dwAccess,
                           awcUser,
                           awcPassword,
                           fVRoot );

    if ( fVRoot )
        vrootStack.Push( pwcVPath, awcPPath, dwAccess );

    return fVRoot;
} //ReportVPath

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::EnumVServers, public
//
//  Synopsis:   Enumerates virtual servers by calling the callback
//
//  Arguments:  [callBack] - called for each vroot
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::EnumVServers( CMetaDataVirtualServerCallBack & callBack )
{
    Win4Assert( _fTopLevel );

    CMetaDataHandle mdRoot( _xAdminBase, _awcInstance );

    Enum( callBack, mdRoot, L"" );
} //EnumVRoots

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::ReportVirtualServer, private
//
//  Synopsis:   Helper method for reporting a virtual server via callback
//
//  Arguments:  [callBack]    - callback to call for virtual server
//              [pwcRelative] - relative path in metabase
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::ReportVirtualServer(
    CMetaDataVirtualServerCallBack & callBack,
    CMetaDataHandle &                mdRoot,
    WCHAR const *                    pwcRelative )
{
    Win4Assert( _fTopLevel );

    // read the comment

    WCHAR awcComment[ METADATA_MAX_NAME_LEN ];
    awcComment[0] = 0;

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_SERVER_COMMENT;
        mdr.pbMDData = (BYTE *) awcComment;
        mdr.dwMDDataLen = sizeof awcComment;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdRoot.Get(),
                                         pwcRelative,
                                         &mdr,
                                         &cbRequired );

        if ( ( FAILED( sc ) ) &&
             ( (MD_ERROR_DATA_NOT_FOUND) != sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData virtual server failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }

    //
    // Convert ID to integer
    //

    DWORD iInstance = wcstoul( pwcRelative, 0, 10 );

    // now we can finally call the callback

    callBack.CallBack( iInstance, awcComment );
} //ReportVirtualServer

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::Enum, private
//
//  Synopsis:   Helper method for enumerating vpaths
//
//  Arguments:  [vrootStack]  - stack of vroots for depth-first search
//              [callBack]    - callback to call when a vpath is found
//              [mdRoot]      - root of the enumeration
//              [pwcRelative] - relative location in enumeration
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::Enum(
    CVRootStack &       vrootStack,
    CMetaDataCallBack & callBack,
    CMetaDataHandle &   mdRoot,
    WCHAR const *       pwcRelative )
{
    // enumerate looking for vpaths

    int c = wcslen( pwcRelative );
    WCHAR awcNewRelPath[ METADATA_MAX_NAME_LEN ];
    RtlCopyMemory( awcNewRelPath, pwcRelative, (c + 1) * sizeof WCHAR );

    if ( 0 == c ||
         L'/' != pwcRelative[c-1] )
    {
        wcscpy( awcNewRelPath + c, L"/" );
        c++;
    }

    for ( int i = 0; ; i++ )
    {
        WCHAR NameBuf[METADATA_MAX_NAME_LEN];
        SCODE sc =_xAdminBase->EnumKeys( mdRoot.Get(),
                                         pwcRelative,
                                         NameBuf,
                                         i );

        if ( RETURNCODETOHRESULT(ERROR_NO_MORE_ITEMS) == sc )
            break;

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "EnumKeys error 0x%x\n", sc ));
            THROW( CException( sc ) );
        }

        Win4Assert( 0 != NameBuf[0] );

        Win4Assert( ( c + wcslen( NameBuf ) ) < METADATA_MAX_NAME_LEN );

        wcscpy( awcNewRelPath + c, NameBuf );

        BOOL fVRoot = ReportVPath( vrootStack,
                                   callBack,
                                   mdRoot,
                                   awcNewRelPath );

        Enum( vrootStack, callBack, mdRoot, awcNewRelPath );

        if ( fVRoot )
            vrootStack.Pop();
    }
} //Enum

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::Enum, private
//
//  Synopsis:   Helper method for enumerating virtual servers
//
//  Arguments:  [callBack]    - callback to call when a vserver is found
//              [mdRoot]      - root of the enumeration
//              [pwcRelative] - relative location in enumeration
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::Enum(
    CMetaDataVirtualServerCallBack & callBack,
    CMetaDataHandle &                mdRoot,
    WCHAR const *                    pwcRelative )
{
    // enumerate looking for virtual servers

    for ( int i = 0; ; i++ )
    {
        WCHAR NameBuf[METADATA_MAX_NAME_LEN];
        SCODE sc =_xAdminBase->EnumKeys( mdRoot.Get(),
                                         pwcRelative,
                                         NameBuf,
                                         i );

        if ( RETURNCODETOHRESULT(ERROR_NO_MORE_ITEMS) == sc )
            break;

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "EnumKeys error 0x%x\n", sc ));
            THROW( CException( sc ) );
        }

        ciDebugOut(( DEB_WARN, "Key: %ws\n", NameBuf ));

        //
        // Ignore things that don't look like virtual servers.
        //

        if ( !isdigit( NameBuf[0] ) )
            continue;

        //
        // Assume we just got a virtual server.
        //

        ReportVirtualServer( callBack, mdRoot, NameBuf );
    }
} //Enum

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVRoot, public
//
//  Synopsis:   Returns the physical root corresponding to a virtual root
//
//  Arguments:  [pwcVRoot]  - VRoot to lookup
//              [pwcPRoot]  - where pwcVRoot's physical root is returned
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::GetVRoot(
    WCHAR const * pwcVRoot,
    WCHAR *       pwcPRoot )
{
    Win4Assert( !_fTopLevel );

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVRoot );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcVRootPath[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcVRootPath, _awcInstance );
    wcscat( awcVRootPath, L"/Root" );
    wcscat( awcVRootPath, pwcVRoot );

    CMetaDataHandle mdVRoot( _xAdminBase, awcVRootPath, FALSE );

    *pwcPRoot = 0;

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_VR_PATH;
        mdr.pbMDData = (BYTE *) pwcPRoot;
        mdr.dwMDDataLen = METADATA_MAX_NAME_LEN * sizeof WCHAR;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdVRoot.Get(),
                                         L"",
                                         &mdr,
                                         &cbRequired );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData PRoot failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
} //GetVRoot

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVRootPW, public
//
//  Synopsis:   Returns the physical root corresponding to a virtual root
//
//  Arguments:  [pwcVRoot]  - VRoot to lookup
//              [pwcPRoot]  - where pwcVRoot's physical root is returned
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::GetVRootPW(
    WCHAR const * pwcVRoot,
    WCHAR *       pwcPW )
{
    Win4Assert( !_fTopLevel );

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVRoot );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcVRootPath[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcVRootPath, _awcInstance );
    wcscat( awcVRootPath, L"/Root" );
    wcscat( awcVRootPath, pwcVRoot );

    CMetaDataHandle mdVRoot( _xAdminBase, awcVRootPath, FALSE );

    *pwcPW = 0;

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_VR_PASSWORD;
        mdr.pbMDData = (BYTE *) pwcPW;
        mdr.dwMDDataLen = METADATA_MAX_NAME_LEN * sizeof WCHAR;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdVRoot.Get(),
                                         L"",
                                         &mdr,
                                         &cbRequired );

        //char ac[ 1000 ];
        //sprintf( ac, "result: 0x%x, cbResult: 0x%x, string: '%ws', char 1: 0x%x\n",
        //         sc, mdr.dwMDDataLen, pwcPW, (ULONG) *pwcPW );
        //DbgPrint( ac );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData PRoot failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
} //GetVRootPW

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::SetIsIndexed, public
//
//  Synopsis:   Sets the indexed state of a virtual path
//
//  Arguments:  [pwcPath]    - Virtual path (optionally a vroot) to set
//              [fIsIndexed] - if TRUE, path is indexed, if FALSE it isn't
//
//  History:    19-Mar-1997   dlee    Created
//
//--------------------------------------------------------------------------

void CMetaDataMgr::SetIsIndexed(
    WCHAR const * pwcVPath,
    BOOL          fIsIndexed )
{
    // Add the key if it doesn't exist yet

    {
        unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" );

        if ( cwc >= METADATA_MAX_NAME_LEN )
            THROW( CException( E_INVALIDARG ) );

        WCHAR awc[ METADATA_MAX_NAME_LEN ];
        wcscpy( awc, _awcInstance );
        wcscat( awc, L"/Root" );

        CMetaDataHandle mdRoot( _xAdminBase, awc, TRUE );

        _xAdminBase->AddKey( mdRoot.Get(), pwcVPath );
    }

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVPath );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcCompleteVPath[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcCompleteVPath, _awcInstance );
    wcscat( awcCompleteVPath, L"/Root" );
    wcscat( awcCompleteVPath, pwcVPath );

    CMetaDataHandle mdVRoot( _xAdminBase, awcCompleteVPath, TRUE );

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = MD_IS_CONTENT_INDEXED;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.dwMDUserType = IIS_MD_UT_FILE;
        mdr.dwMDDataType = DWORD_METADATA;
        mdr.pbMDData = (BYTE *) &fIsIndexed;
        mdr.dwMDDataLen = sizeof fIsIndexed;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->SetData( mdVRoot.Get(),
                                         L"",
                                         &mdr );

        if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "SetData isindexed failed: 0x%x\n", sc ));
            THROW( CException( sc ) );
        }
    }
} //SetIsIndexed

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::IsIndexed, public
//
//  Synopsis:   Checks the path to return the state of the IsIndexed flag.
//              The default for no value is TRUE.  No checking is made to
//              if a parent directory is indexed or not.
//
//  Arguments:  [pwcVPath]  - Virtual path (optionally a vroot) to check
//
//  History:    19-Mar-1997   dlee    Created
//
//--------------------------------------------------------------------------

BOOL CMetaDataMgr::IsIndexed(
    WCHAR const * pwcVPath )
{
    BOOL fIsIndexed = TRUE;

    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVPath );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcCompletePath[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcCompletePath, _awcInstance );
    wcscat( awcCompletePath, L"/Root" );
    wcscat( awcCompletePath, pwcVPath );

    TRY
    {
        CMetaDataHandle mdVRoot( _xAdminBase, awcCompletePath, FALSE );

        {
            METADATA_RECORD mdr;
            RtlZeroMemory( &mdr, sizeof mdr );
            mdr.dwMDIdentifier = MD_IS_CONTENT_INDEXED;
            mdr.dwMDAttributes = METADATA_INHERIT;
            mdr.pbMDData = (BYTE *) &fIsIndexed;
            mdr.dwMDDataLen = sizeof fIsIndexed;

            DWORD cbRequired = 0;
            SCODE sc = _xAdminBase->GetData( mdVRoot.Get(),
                                             L"",
                                             &mdr,
                                             &cbRequired );

            if ( MD_ERROR_DATA_NOT_FOUND == sc )
            {
                fIsIndexed = FALSE;
            }
            else if ( FAILED( sc ) )
            {
                ciDebugOut(( DEB_WARN, "GetData isindexed failed: 0x%x\n", sc ));
                THROW( CException( sc ) );
            }
        }
    }
    CATCH( CException, e )
    {
        // ignore -- assume IsIndexed
    }
    END_CATCH;

    return fIsIndexed;
} //IsIndexed

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVPathFlags, public
//
//  Synopsis:   Returns flag settings on a virtual path
//
//  Arguments:  [pwcVPath]  - Virtual path (optionally a vroot) to check
//              [mdID]      - MD_x constant for the data
//              [ulDefault] - Default if no value is in the metabase
//
//  History:    18-Aug-1997   dlee    Created
//
//--------------------------------------------------------------------------

ULONG CMetaDataMgr::GetVPathFlags(
    WCHAR const * pwcVPath,
    ULONG         mdID,
    ULONG         ulDefault )
{
    unsigned cwc = wcslen( _awcInstance ) + wcslen( L"/Root" ) + wcslen( pwcVPath );

    if ( cwc >= METADATA_MAX_NAME_LEN )
        THROW( CException( E_INVALIDARG ) );

    WCHAR awcCompletePath[ METADATA_MAX_NAME_LEN ];
    wcscpy( awcCompletePath, _awcInstance );
    wcscat( awcCompletePath, L"/Root" );
    wcscat( awcCompletePath, pwcVPath );

    DWORD dwFlags = ulDefault;

    // Keep removing path components on the right of the path until
    // either out of path or the metabase recognizes the path.

    METADATA_HANDLE h;

    do
    {
        SCODE sc = _xAdminBase->OpenKey( METADATA_MASTER_ROOT_HANDLE,
                                         awcCompletePath,
                                         METADATA_PERMISSION_READ,
                                         cmsCIMetabaseTimeout,
                                         &h );

        if ( S_OK == sc )
        {
            break;
        }
        else if ( HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) == sc )
        {
            WCHAR * pwc = wcsrchr( awcCompletePath, L'/' );
            if ( 0 == pwc )
                THROW( CException( sc ) );
            *pwc = 0;
        }
        else
            THROW( CException( sc ) );
    } while ( TRUE );

    CMetaDataHandle mdVRoot( _xAdminBase, h );

    {
        METADATA_RECORD mdr;
        RtlZeroMemory( &mdr, sizeof mdr );
        mdr.dwMDIdentifier = mdID;
        mdr.dwMDAttributes = METADATA_INHERIT;
        mdr.pbMDData = (BYTE *) &dwFlags;
        mdr.dwMDDataLen = sizeof dwFlags;

        DWORD cbRequired = 0;
        SCODE sc = _xAdminBase->GetData( mdVRoot.Get(),
                                         L"",
                                         &mdr,
                                         &cbRequired );

        if ( MD_ERROR_DATA_NOT_FOUND == sc )
        {
            // no value specified for this flag; use default

            dwFlags = ulDefault;
        }
        else if ( FAILED( sc ) )
        {
            ciDebugOut(( DEB_WARN, "GetData mdid %d failed: 0x%x on '%ws'\n",
                         mdID, sc, awcCompletePath ));
            THROW( CException( sc ) );
        }
    }

    return dwFlags;
} //GetVPathFlags

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVPathAccess, public
//
//  Synopsis:   Returns access permission settings on a virtual path
//
//  Arguments:  [pwcVPath]  - Virtual path (optionally a vroot) to check
//
//  History:    18-Aug-1997   dlee    Created
//
//--------------------------------------------------------------------------

ULONG CMetaDataMgr::GetVPathAccess(
    WCHAR const * pwcVPath )
{
    // note: the default of 0 is from IIS' metabase guru

    return GetVPathFlags( pwcVPath, MD_ACCESS_PERM, 0 );
} //GetVPathAccess

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVPathSSLAccess, public
//
//  Synopsis:   Returns SSL access permission settings on a virtual path
//
//  Arguments:  [pwcVPath]  - Virtual path (optionally a vroot) to check
//
//  History:    18-Aug-1997   dlee    Created
//
//--------------------------------------------------------------------------

ULONG CMetaDataMgr::GetVPathSSLAccess(
    WCHAR const * pwcVPath )
{
    // note: the default of 0 is from IIS' metabase guru

    return GetVPathFlags( pwcVPath, MD_SSL_ACCESS_PERM, 0 );
} //GetVPathSSLAccess

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataMgr::GetVPathAuthorization, public
//
//  Synopsis:   Returns authorization on a virtual path
//
//  Arguments:  [pwcVPath]  - Virtual path (optionally a vroot) to check
//
//  History:    18-Aug-1997   dlee    Created
//
//--------------------------------------------------------------------------

ULONG CMetaDataMgr::GetVPathAuthorization(
    WCHAR const * pwcVPath )
{
    // note: the default of MD_AUTH_ANONYMOUS is from IIS' metabase guru

    return GetVPathFlags( pwcVPath, MD_AUTHORIZATION, MD_AUTH_ANONYMOUS );
} //GetVPathAuthorization

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataComSink::SinkNotify, public
//
//  Synopsis:   Called for any metadata change
//
//  Arguments:  [cChanges]      - # of changes in pcoChangeList
//              [pcoChangeList] - list of changes
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CMetaDataComSink::SinkNotify(
    DWORD            cChanges,
    MD_CHANGE_OBJECT pcoChangeList[] )
{
    // This is called by an RPC worker thread -- assimilate it

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        int cwcInstance = wcslen( _awcInstance );
        BOOL fInterestingChange = FALSE;

        ciDebugOut(( DEB_WARN, "iis sinknotify '%ws', cchanges: %d\n",
                     _awcInstance, cChanges ));

        for ( DWORD i = 0; !fInterestingChange && i < cChanges; i++ )
        {
            MD_CHANGE_OBJECT & co = pcoChangeList[i];

            // we only care about notifications to our instance

            if ( _wcsnicmp( co.pszMDPath, _awcInstance, cwcInstance ) != 0 ||
                 ( L'/' != co.pszMDPath[cwcInstance] &&
                   L'\0' != co.pszMDPath[cwcInstance] ) )
                continue;

            // Ignore adds of vroots -- we'll get a set_data for its
            // parameters and trigger on that.

            if ( ( MD_CHANGE_TYPE_DELETE_OBJECT & co.dwMDChangeType ) ||
                 ( MD_CHANGE_TYPE_RENAME_OBJECT & co.dwMDChangeType ) )
            {
                // guess that the deletion was a vroot

                fInterestingChange = TRUE;
            }
            else if ( ( MD_CHANGE_TYPE_SET_DATA & co.dwMDChangeType ) ||
                      ( MD_CHANGE_TYPE_DELETE_DATA & co.dwMDChangeType ) )
            {
                for ( DWORD x = 0; x < co.dwMDNumDataIDs; x++ )
                {
                    DWORD id = co.pdwMDDataIDs[x];

                    if ( MD_VR_PATH            == id ||
                         MD_VR_USERNAME        == id ||
                         MD_VR_PASSWORD        == id ||
                         MD_ACCESS_PERM        == id ||
                         MD_IS_CONTENT_INDEXED == id )
                    {
                        fInterestingChange = TRUE;
                        break;
                    }
                }
            }
        }

        if ( fInterestingChange && ( 0 != _pCallBack ) )
            _pCallBack->CallBack( FALSE );
    }
    CATCH (CException, e)
    {
        ciDebugOut(( DEB_WARN,
                     "SinkNotify caught 0x%x\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
    UNTRANSLATE_EXCEPTIONS;

    return S_OK;
} //SinkNotify

//+-------------------------------------------------------------------------
//
//  Member:     CMetaDataComSink::ShutdownNotify, public
//
//  Synopsis:   Called when iisadmin is going down cleanly
//
//  History:    07-Feb-1997   dlee    Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CMetaDataComSink::ShutdownNotify()
{
    // This is called by an RPC worker thread -- assimilate it

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        ciDebugOut(( DEB_WARN, "iis shutdownnotify '%ws'\n", _awcInstance ));

        // in case we get more random notifications, ignore them

        CMetaDataVPathChangeCallBack * pCallBack = _pCallBack;
        _pCallBack = 0;

        pCallBack->CallBack( TRUE );
    }
    CATCH (CException, e)
    {
        ciDebugOut(( DEB_WARN,
                     "ShutdownNotify caught 0x%x\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
    UNTRANSLATE_EXCEPTIONS;

    return S_OK;
} //ShutdownNotify