Writing properties #3 - Which properties are writable?

While we don't have a table of properties and filetypes that are writable, there is a programmatic method to determine if a given property can be written to a given property handler. Here's how it works:

First, properties can be designated innate. This means they are supposed to be read-only system-wide regardless of filetype. To check this, call IPropertyDescription::GetTypeFlags and look for PDTF_ISINNATE. Examples are System.Size and System.Image.Dimensions. These are things that the user should not be able to set.

Next, a property handler can reject the call to IShellItem2::GetPropertyStore(GPS_READWRITE). The GPS flag is passed to the property handler via IInitializeWithStream::Initialize. If a property handler is read-only, it will return STG_E_ACCESSDENIED. If the store is writable, it returns S_OK.

Finally, the property handler can indicate if a particular property is writable or not. Clients of the property system absolutely must call IPropertyStoreCapabilities::IsPropertyWritable if the handler supports this interface. This method returns S_OK for writable properties and S_FALSE if the property is readonly.

That's it! Here I've put this into code:

 BOOL _IsPropertyValueWritable(__in IPropertyStore *pps, __in REFPROPERTYKEY key, __in REFPROPVARIANT propvar)
{
    BOOL fWritable = TRUE;
    // 1. The caller already got the property store, so we know it likes GPS_READWRITE
    // 2. Check if the property itself is innate
    //    Ignore errors just in case we want to write a property the system doesn't recognize
    IPropertyDescription *ppropdesc;
    if (SUCCEEDED(PSGetPropertyDescription(key, IID_PPV_ARGS(&ppropdesc))))
    {
        PROPDESC_TYPE_FLAGS flags;
        if (SUCCEEDED(ppropdesc->GetTypeFlags(PDTF_ISINNATE, &flags)))
        {
            fWritable = !(flags & PDTF_ISINNATE); // 2006/11/2 Edit: Add a ! that was missing here
        }
    }

    // 3. Check if the property store likes the property
    //    Ignore errors since this interface is optional
    if (fWritable)
    {
        IPropertyStoreCapabilities *ppsc;
        if (SUCCEEDED(pps->QueryInterface(IID_PPV_ARGS(&ppsc))))
        {
            fWritable = (S_OK == ppsc->IsPropertyWritable(key));
            ppsc->Release();
        }
    }

    return fWritable;
}

If you're following along at home, feel free make propset call this function and print an appropriate diagnostic.

-Ben Karas