ADSI and WMI

I’m improving the installer for a web application. It’s a windows installer. It needs to register a MIME type with the server. This is important to do, since Windows 2003 server will not serve unknown Mime types. (Neither will Windows 2000 after the Lockdown tool has been used).

WMI is the preferred mechanism for diddling IIS settings, but it turns out you can’t rely on WMI being installed. So we need to user ADSI, the underlying technology.

ADSI is a maze of little automation objects, all poorly documented. There’s plenty of documentation. It’s just that most of it is pointless drivel. Undocumentation as some have called it.

The ADSI incantations necessary have been recorded by wizened elders.

Unfortunately these incantations need to be converted to C++ in order to work inside an installer. There is a heavy bias against using script in installers. Partly because script may not be installed, or if it is installed, it’s a lower version than we require. So a hardened C++ version is needed.

In case anyone needs to know, here is how to tweak ADSI to add a mime-type to IIS from C++. It’s not pretty – being a frankenstein monster of cut-and-paste snippets from various places.

char buf[BUFLEN];
strcpy(buf, "");

_bstr_t rdfType = ".rdf";

CoInitialize(NULL);
_bstr_t bsMimePath = "IIS://localhost/MimeMap";

IADs* pContainer = NULL;
HRESULT hr = ADsGetObject( bsMimePath, IID_IADs, (void**) &pContainer);
if( SUCCEEDED(hr) && pContainer != NULL )
{
_variant_t varMimeMap;
_bstr_t nameMimeMap = "MimeMap";
hr = pContainer->GetEx( nameMimeMap, &varMimeMap);
if( SUCCEEDED(hr) && ((varMimeMap.vt & VT_ARRAY) == VT_ARRAY) )
{
bool bFoundRdfType = false;

// MIME map is an array that we can redim and add an item to the end
SAFEARRAY* pArrMimeMap = varMimeMap.parray;
LONG lIdx, lHigh;
::SafeArrayGetLBound(pArrMimeMap, 1, &lIdx);
::SafeArrayGetUBound(pArrMimeMap, 1, &lHigh);
for(LONG i = lIdx; i < = lHigh; i++)
{
_variant_t elem;
hr = ::SafeArrayGetElement(pArrMimeMap, &i, (void*) &elem);
if( SUCCEEDED(hr) && ((elem.vt & VT_DISPATCH) == VT_DISPATCH) )
{

IDispatchPtr pMimeType = elem.pdispVal;
if(pMimeType != NULL)
{
DISPID idExtension = 0;
_bstr_t propName = "Extension";
BSTR pName = (BSTR)propName;
BSTR* ppName = & pName;
hr = pMimeType->GetIDsOfNames( IID_NULL, ppName, 1, LOCALE_USER_DEFAULT, &idExtension );
if( SUCCEEDED(hr) )
{
DISPPARAMS dispparamsNoArgs;
memset( &dispparamsNoArgs, 0, sizeof(dispparamsNoArgs));
_variant_t varResult;
hr = pMimeType->Invoke( idExtension, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispparamsNoArgs, &varResult, NULL, NULL);
if( SUCCEEDED(hr) )
{
_bstr_t tmp = varResult.bstrVal;
if( tmp == rdfType )
bFoundRdfType = true;
}
}

}
}

}
// add one element
if( ! bFoundRdfType )
{
_bstr_t fileType = ".rdf";
_bstr_t mimeType = "application/octet-stream";
_variant_t varFileType = fileType;
_variant_t varMimeType = mimeType;

CLSID CLSID_MimeType;
hr = CLSIDFromProgID( nameMimeMap, &CLSID_MimeType );
if( SUCCEEDED(hr) )
{
IDispatchPtr pMimeType;
hr = ::CoCreateInstance( CLSID_MimeType, NULL, CLSCTX_ALL, IID_IDispatch, (void**) &pMimeType );

DISPID idExtension = -1;
DISPID idMimeType = -1;
DISPID propPut = DISPID_PROPERTYPUT;

_bstr_t propName = "Extension";
BSTR pName = (BSTR)propName;
BSTR* ppName = & pName;
if( SUCCEEDED(hr) )
hr = pMimeType->GetIDsOfNames( IID_NULL, ppName, 1, LOCALE_USER_DEFAULT, &idExtension );
if( SUCCEEDED(hr) )
{
DISPPARAMS dispParam;
dispParam.cArgs = 1;
dispParam.cNamedArgs = 1;
dispParam.rgvarg = & varFileType;
dispParam.rgdispidNamedArgs = &propPut;
hr = pMimeType->Invoke( idExtension, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &dispParam, NULL, NULL, NULL);
}
if( SUCCEEDED(hr) )
{
propName = "MimeType";
pName = (BSTR)propName;
ppName = & pName;
hr = pMimeType->GetIDsOfNames( IID_NULL, ppName, 1, LOCALE_USER_DEFAULT, &idMimeType );
}
if( SUCCEEDED(hr) )
{
DISPPARAMS dispParam;
dispParam.cArgs = 1;
dispParam.cNamedArgs = 1;
dispParam.rgvarg = & varMimeType;
dispParam.rgdispidNamedArgs = &propPut;
hr = pMimeType->Invoke( idMimeType, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &dispParam, NULL, NULL, NULL);
}
if( SUCCEEDED(hr) )
{
SAFEARRAYBOUND bounds;
bounds.lLbound = lIdx;
bounds.cElements = lHigh - lIdx + 1 + 1;
hr = ::SafeArrayRedim(pArrMimeMap, &bounds);
if( SUCCEEDED(hr) )
hr = ::SafeArrayGetUBound(pArrMimeMap, 1, &lHigh);
_variant_t varMimeObj = (IDispatch*) pMimeType;
if( SUCCEEDED(hr) )
hr = ::SafeArrayPutElement(pArrMimeMap, &lHigh, &varMimeObj);
if( SUCCEEDED(hr) )
hr = pContainer->PutEx( ADS_PROPERTY_UPDATE, nameMimeMap, varMimeMap );
if( SUCCEEDED(hr) )
hr = pContainer->SetInfo();
}
}

if( SUCCEEDED(hr) )
strcpy(buf, "Added Mime type for .RDF files to IIS");
else
strcpy(buf, "Failed to add MIME type to IIS");
}
else
{
strcpy(buf, "MIME type already registered in IIS");
}


}
pContainer->Release();

}

CoUninitialize();

2 thoughts on “ADSI and WMI

  1. You are a life saver. ADSI is very poorly documented especially when it comes to the IIS provider. I am writing an installer, and the version of InstallShield we use does not support adding MIME types. This has saved me a ton time.

    Thanks.

  2. You are a life saver. ADSI is very poorly documented especially the IIS provider. This will help out a lot with our installer.

Leave a Reply