I'm going to be talking about writable properties over the next few days.  I know that some of you are itching to try this out yourselves, so here is an overly simplistic program that will write a single property to a file.  I have omitted a lot of diagnostic code, so this will not work for all files, all strings, or all properties.  I plan to go through those details another day.

int wmain(__in int argc, __in_ecount(argc+1) WCHAR **argv)
{
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        if (argc < 3)
        {
            wprintf(L"Usage: %s <filename> <property> <value>\n", argv[0]);
        }
        else
        {
            PCWSTR pszFile = argv[1];
            PCWSTR pszProperty = argv[2];
            PCWSTR pszValue = argv[3];

            PROPERTYKEY key;
            hr = PSGetPropertyKeyFromName(pszProperty, &key);
            if (SUCCEEDED(hr))
            {
                IShellItem2 *psi;
                hr = _GetShellItemFromRelativePath(pszFile, IID_PPV_ARGS(&psi));
                if (SUCCEEDED(hr))
                {
                    IPropertyStore *pps;
                    hr = psi->GetPropertyStore(GPS_READWRITE, IID_PPV_ARGS(&pps));
                    if (SUCCEEDED(hr))
                    {
                        PROPVARIANT propvar = {0};
                        hr = _GetValueFromString(key, pszValue, &propvar);
                        if (SUCCEEDED(hr))
                        {
                            hr = pps->SetValue(key, propvar);
                            if (SUCCEEDED(hr))
                            {
                                hr = pps->Commit();
                            }
                            PropVariantClear(&propvar);
                        }
                        pps->Release();
                    }
                    psi->Release();
                }
            }
            wprintf(L"Wrote %s:%s to %s; hr = 0x%08x\n", pszProperty, pszValue, pszFile, hr);
        }

        CoUninitialize();
    }
}

This code figures out which property to set, opens the store for writing, figures out the value to set, sets it, and commits the changes.  Here are the helper functions I'm using:

HRESULT _GetValueFromString(__in REFPROPERTYKEY key, __in PCWSTR pszValue, __out PROPVARIANT *ppropvar)
{
    return InitPropVariantFromString(pszValue, ppropvar); 
}

HRESULT _GetShellItemFromRelativePath(
__in
PCWSTR pszRelative,
__in REFIID riid,
__deref_out void **ppv) { WCHAR szFile[MAX_PATH]; HRESULT hr = StringCchCopyW(szFile, ARRAYSIZE(szFile), pszRelative); if (SUCCEEDED(hr)) { PathUnquoteSpacesW(szFile); WCHAR szDir[MAX_PATH]; hr = GetCurrentDirectory(ARRAYSIZE(szDir), szDir) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { WCHAR szPath[MAX_PATH]; hr = PathCombineW(szPath, szDir, szFile) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { hr = SHCreateItemFromParsingName(szPath, NULL, riid, ppv); } } } return hr; }
And here's my output:
C:\> propset.exe scan0010.jpg System.Keywords abc
Wrote System.Keywords:abc to scan0010.jpg; hr = 0x00000000 
-Ben Karas
see also: Reading properties