// // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. #include #include // std::nothrow #include // IInitializeWithStream, IDestinationStreamFactory #include // Property System APIs and interfaces #include // System PROPERTYKEY definitions #include // PROPVARIANT and VARIANT helper APIs #include // DOM interfaces #include // CryptBinaryToString, CryptStringToBinary #include // StringCchPrintf #include "RegisterExtension.h" // CRegisterExtension #include "Dll.h" // MSXML is a dispatch based OM and requires BSTRs as input // we will cheat and cast C strings to BSTRs as MSXML does not // utilize the BSTR features of its inputs. this saves the allocations // that would be necessary otherwise __inline BSTR CastToBSTRForInput(PCWSTR psz) { return const_cast(psz); } template void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } // {1794C9FE-74A9-497f-9C69-B31F03CE7EF9} 100 const PROPERTYKEY PKEY_Microsoft_SampleRecipe_Difficulty = {{0x1794c9fe, 0x74a9, 0x497f, 0x9c, 0x69, 0xb3, 0x1f, 0x3, 0xce, 0x7e, 0xf9}, 100}; // Map of property keys to the locations of their value(s) in the .recipe XML schema struct PROPERTYMAP { const PROPERTYKEY *pkey; // pointer type to enable static declaration PCWSTR pszXPathParent; PCWSTR pszValueNodeName; }; const PROPERTYMAP g_rgPROPERTYMAP[] = { { &PKEY_Title, L"Recipe", L"Title" }, { &PKEY_Comment, L"Recipe", L"Comments" }, { &PKEY_Author, L"Recipe/Background", L"Author" }, { &PKEY_Keywords, L"Recipe/RecipeKeywords", L"Keyword" }, { &PKEY_Microsoft_SampleRecipe_Difficulty, L"Recipe/RecipeInfo", L"Difficulty" }, }; // Helper functions to opaquely serialize and deserialize PROPVARIANT values to and from string form HRESULT SerializePropVariantAsString(REFPROPVARIANT propvar, PWSTR *pszOut); HRESULT DeserializePropVariantFromString(PCWSTR pszIn, PROPVARIANT *ppropvar); // DLL lifetime management functions void DllAddRef(); void DllRelease(); class CRecipePropertyHandler : public IPropertyStore, public IPropertyStoreCapabilities, public IInitializeWithStream { public: CRecipePropertyHandler() : _cRef(1), _pStream(NULL), _grfMode(0), _pDomDoc(NULL), _pCache(NULL) { DllAddRef(); } // IUnknown IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CRecipePropertyHandler, IPropertyStore), QITABENT(CRecipePropertyHandler, IPropertyStoreCapabilities), QITABENT(CRecipePropertyHandler, IInitializeWithStream), { 0 }, }; return QISearch(this, qit, riid, ppv); } IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); } IFACEMETHODIMP_(ULONG) Release() { ULONG cRef = InterlockedDecrement(&_cRef); if (!cRef) { delete this; } return cRef; } // IPropertyStore IFACEMETHODIMP GetCount(DWORD *pcProps); IFACEMETHODIMP GetAt(DWORD iProp, PROPERTYKEY *pkey); IFACEMETHODIMP GetValue(REFPROPERTYKEY key, PROPVARIANT *pPropVar); IFACEMETHODIMP SetValue(REFPROPERTYKEY key, REFPROPVARIANT propVar); IFACEMETHODIMP Commit(); // IPropertyStoreCapabilities IFACEMETHODIMP IsPropertyWritable(REFPROPERTYKEY key); // IInitializeWithStream IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode); private: ~CRecipePropertyHandler() { SafeRelease(&_pStream); SafeRelease(&_pDomDoc); SafeRelease(&_pCache); DllRelease(); } // helpers to load data from the DOM HRESULT _LoadCacheFromDom(); HRESULT _LoadPropertyValues(IXMLDOMNode *pNodeParent, PCWSTR pszNodeValues, PROPVARIANT *ppropvar); HRESULT _LoadProperty(const PROPERTYMAP &map); HRESULT _LoadExtendedProperties(); HRESULT _LoadSearchContent(); // helpers to save data to the DOM HRESULT _SaveCacheToDom(); HRESULT _SavePropertyValues(IXMLDOMNode *pNodeParent, PCWSTR pszNodeValues, REFPROPVARIANT propvar); HRESULT _SaveProperty(REFPROPVARIANT propvar, const PROPERTYMAP &map); HRESULT _SaveExtendedProperty(REFPROPERTYKEY key, REFPROPVARIANT propvar); HRESULT _EnsureChildNodeExists(IXMLDOMNode *pNodeParent, PCWSTR pszName, PCWSTR pszXPath, IXMLDOMNode **ppNodeChild); long _cRef; IStream* _pStream; // data stream passed in to Initialize, and saved to on Commit DWORD _grfMode; // STGM mode passed to Initialize IXMLDOMDocument* _pDomDoc; // DOM object representing the .recipe file IPropertyStoreCache* _pCache; // internal value cache to abstract IPropertyStore operations from the DOM back-end }; // Instantiates a recipe property store object HRESULT CRecipePropertyHandler_CreateInstance(REFIID riid, void **ppv) { *ppv = NULL; CRecipePropertyHandler *pNew = new(std::nothrow) CRecipePropertyHandler; HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { hr = pNew->QueryInterface(riid, ppv); pNew->Release(); } return hr; } // Accessor methods forward directly to internal value cache HRESULT CRecipePropertyHandler::GetCount(DWORD *pcProps) { *pcProps = 0; return _pCache ? _pCache->GetCount(pcProps) : E_UNEXPECTED; } HRESULT CRecipePropertyHandler::GetAt(DWORD iProp, PROPERTYKEY *pkey) { *pkey = PKEY_Null; return _pCache ? _pCache->GetAt(iProp, pkey) : E_UNEXPECTED; } HRESULT CRecipePropertyHandler::GetValue(REFPROPERTYKEY key, PROPVARIANT *pPropVar) { PropVariantInit(pPropVar); return _pCache ? _pCache->GetValue(key, pPropVar) : E_UNEXPECTED; } // SetValue just updates the internal value cache HRESULT CRecipePropertyHandler::SetValue(REFPROPERTYKEY key, REFPROPVARIANT propVar) { HRESULT hr = E_UNEXPECTED; if (_pCache) { // check grfMode to ensure writes are allowed hr = STG_E_ACCESSDENIED; if ((_grfMode & STGM_READWRITE) && (key != PKEY_Search_Contents)) // this property is read-only { hr = _pCache->SetValueAndState(key, &propVar, PSC_DIRTY); } } return hr; } // Commit writes the internal value cache back out to the stream passed to Initialize HRESULT CRecipePropertyHandler::Commit() { HRESULT hr = E_UNEXPECTED; if (_pCache) { // check grfMode to ensure writes are allowed hr = STG_E_ACCESSDENIED; if (_grfMode & STGM_READWRITE) { // save the internal value cache to XML DOM object hr = _SaveCacheToDom(); if (SUCCEEDED(hr)) { // reset the output stream LARGE_INTEGER liZero = {}; hr = _pStream->Seek(liZero, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { // obtain a temporary destination stream for manual safe-save IDestinationStreamFactory *pSafeCommit; hr = _pStream->QueryInterface(&pSafeCommit); if (SUCCEEDED(hr)) { IStream *pStreamCommit; hr = pSafeCommit->GetDestinationStream(&pStreamCommit); if (SUCCEEDED(hr)) { // write the XML out to the temprorary stream and commit it VARIANT varStream = {}; varStream.vt = VT_UNKNOWN; varStream.punkVal = pStreamCommit; hr = _pDomDoc->save(varStream); if (SUCCEEDED(hr)) { hr = pStreamCommit->Commit(STGC_DEFAULT); if (SUCCEEDED(hr)) { // commit the real output stream _pStream->Commit(STGC_DEFAULT); } } pStreamCommit->Release(); } pSafeCommit->Release(); } } } } } return hr; } // Indicates whether the users should be able to edit values for the given property key HRESULT CRecipePropertyHandler::IsPropertyWritable(REFPROPERTYKEY key) { // System.Search.Contents is the only property not supported for writing return (key == PKEY_Search_Contents) ? S_FALSE : S_OK; } // Initialize populates the internal value cache with data from the specified stream HRESULT CRecipePropertyHandler::Initialize(IStream *pStream, DWORD grfMode) { HRESULT hr = E_UNEXPECTED; if (!_pStream) { // instantiate the DOM object hr = CoCreateInstance(__uuidof(DOMDocument60), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pDomDoc)); if (SUCCEEDED(hr)) { // load the DOM object's contents from the stream VARIANT_BOOL vfSuccess = VARIANT_FALSE; VARIANT varStream = {}; varStream.vt = VT_UNKNOWN; varStream.punkVal = pStream; hr = _pDomDoc->load(varStream, &vfSuccess); if (hr == S_OK && vfSuccess == VARIANT_TRUE) { // load the internal value cache from the DOM object hr = _LoadCacheFromDom(); if (SUCCEEDED(hr)) { // save a reference to the stream as well as the grfMode hr = pStream->QueryInterface(IID_PPV_ARGS(&_pStream)); if (SUCCEEDED(hr)) { _grfMode = grfMode; } } } else { hr = E_FAIL; } if (FAILED(hr)) { SafeRelease(&_pDomDoc); } } } return hr; } // Populates the internal value cache from the internal DOM object HRESULT CRecipePropertyHandler::_LoadCacheFromDom() { HRESULT hr = S_OK; if (!_pCache) { // create the internal value cache hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&_pCache)); if (SUCCEEDED(hr)) { // populate native properties directly from the XML for (UINT i = 0; i < ARRAYSIZE(g_rgPROPERTYMAP); ++i) { _LoadProperty(g_rgPROPERTYMAP[i]); } // load extended properties and search content _LoadExtendedProperties(); _LoadSearchContent(); } } return hr; } // Loads specified values from given parent node and creates a PROPVARIANT from them HRESULT CRecipePropertyHandler::_LoadPropertyValues(IXMLDOMNode *pNodeParent, PCWSTR pszValueNodeName, PROPVARIANT *ppropvarValues) { // intialize the outparam PropVariantInit(ppropvarValues); // select the value nodes IXMLDOMNodeList *pValueList = NULL; HRESULT hr = pNodeParent->selectNodes(CastToBSTRForInput(pszValueNodeName), &pValueList); if (hr == S_OK) { // get the count of values long cValues = 0; hr = pValueList->get_length(&cValues); if (SUCCEEDED(hr)) { // create an array to hold the values BSTR *pbstrValues = new(std::nothrow) BSTR[cValues]; hr = pbstrValues ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { // load the text of each value node into the array for (long iValue = 0; iValue < cValues; iValue++) { if (hr == S_OK) { IXMLDOMNode *pValue = NULL; hr = pValueList->get_item(iValue, &pValue); if (hr == S_OK) { hr = pValue->get_text(&pbstrValues[iValue]); pValue->Release(); } } else { // NULL out remaining elements on failure pbstrValues[iValue] = NULL; } } if (hr == S_OK) { // package the list of values up in a PROPVARIANT hr = InitPropVariantFromStringVector(const_cast(pbstrValues), cValues, ppropvarValues); } // clean up array of values for (long iValue = 0; iValue < cValues; iValue++) { SysFreeString(pbstrValues[iValue]); } delete[] pbstrValues; } } } return hr; } // Loads the data for the property specified in the given map into the internal value cache HRESULT CRecipePropertyHandler::_LoadProperty(const PROPERTYMAP &map) { // select the property's parent node and load the property's value(s) IXMLDOMNode *pNodeParent = NULL; HRESULT hr = _pDomDoc->selectSingleNode(CastToBSTRForInput(map.pszXPathParent), &pNodeParent); if (hr == S_OK) { PROPVARIANT propvarValues = {}; hr = _LoadPropertyValues(pNodeParent, map.pszValueNodeName, &propvarValues); if (hr == S_OK) { // coerce the value(s) to the appropriate type for the property key hr = PSCoerceToCanonicalValue(*map.pkey, &propvarValues); if (SUCCEEDED(hr)) { // cache the value(s) loaded hr = _pCache->SetValueAndState(*map.pkey, &propvarValues, PSC_NORMAL); } PropVariantClear(&propvarValues); } pNodeParent->Release(); } return hr; } // Loads data for any external properties (e.g. those not explicitly mapped to the XML schema) into the internal value cache HRESULT CRecipePropertyHandler::_LoadExtendedProperties() { // select the list of extended property nodes IXMLDOMNodeList *pList = NULL; HRESULT hr = _pDomDoc->selectNodes(CastToBSTRForInput(L"Recipe/ExtendedProperties/Property"), &pList); if (hr == S_OK) { long cElems = 0; hr = pList->get_length(&cElems); if (hr == S_OK) { // iterate over the list and cache each value for (long iElem = 0; iElem < cElems; ++iElem) { IXMLDOMNode *pNode = NULL; hr = pList->get_item(iElem, &pNode); if (hr == S_OK) { IXMLDOMElement *pElement = NULL; hr = pNode->QueryInterface(IID_PPV_ARGS(&pElement)); if (SUCCEEDED(hr)) { // get the name of the property and convert it to a PROPERTYKEY VARIANT varPropKey = {}; hr = pElement->getAttribute(CastToBSTRForInput(L"Key"), &varPropKey); if (hr == S_OK) { PROPERTYKEY key; hr = PSPropertyKeyFromString(varPropKey.bstrVal, &key); if (SUCCEEDED(hr)) { // get the encoded value and deserialize it into a PROPVARIANT VARIANT varEncodedValue = {}; hr = pElement->getAttribute(CastToBSTRForInput(L"EncodedValue"), &varEncodedValue); if (hr == S_OK) { PROPVARIANT propvarValue = {}; hr = DeserializePropVariantFromString(varEncodedValue.bstrVal, &propvarValue); if (SUCCEEDED(hr)) { // cache the value loaded hr = _pCache->SetValueAndState(key, &propvarValue, PSC_NORMAL); PropVariantClear(&propvarValue); } VariantClear(&varEncodedValue); } } VariantClear(&varPropKey); } pElement->Release(); } pNode->Release(); } } } pList->Release(); } return hr; } // Populates the System.Search.Contents property in the internal value cache HRESULT CRecipePropertyHandler::_LoadSearchContent() { // XSLT to generate a space-delimited list of Items, Steps, Yield, Difficulty, and Keywords BSTR bstrContentXSLT = SysAllocString(L"\n" L"\n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L" \n" L""); HRESULT hr = bstrContentXSLT ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { // create a DOM object to hold the XSLT IXMLDOMDocument *pContentXSLT = NULL; hr = CoCreateInstance(__uuidof(DOMDocument60), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pContentXSLT)); if (SUCCEEDED(hr)) { // load the XSLT VARIANT_BOOL vfSuccess = VARIANT_FALSE; hr = pContentXSLT->loadXML(bstrContentXSLT, &vfSuccess); if (!vfSuccess) { hr = FAILED(hr) ? hr : E_FAIL; // keep failed hr } if (SUCCEEDED(hr)) { // get the root node of the XSLT IXMLDOMNode* pContentXSLTNode = NULL; hr = pContentXSLT->QueryInterface(IID_PPV_ARGS(&pContentXSLTNode)); if (SUCCEEDED(hr)) { // transform the internal DOM object using the XSLT to generate the content string BSTR bstrContent = NULL; hr = _pDomDoc->transformNode(pContentXSLTNode, &bstrContent); if (SUCCEEDED(hr)) { // initialize a PROPVARIANT from the string, and store it in the internal value cache PROPVARIANT propvarContent = {}; hr = InitPropVariantFromString(bstrContent, &propvarContent); if (SUCCEEDED(hr)) { hr = _pCache->SetValueAndState(PKEY_Search_Contents, &propvarContent, PSC_NORMAL); PropVariantClear(&propvarContent); } SysFreeString(bstrContent); } pContentXSLT->Release(); } } pContentXSLT->Release(); } SysFreeString(bstrContentXSLT); } return hr; } // Saves the values in the internal cache back to the internal DOM object HRESULT CRecipePropertyHandler::_SaveCacheToDom() { // iterate over each property in the internal value cache DWORD cProps; HRESULT hr = _pCache->GetCount(&cProps); for (UINT i = 0; SUCCEEDED(hr) && (i < cProps); ++i) { PROPERTYKEY key; hr = _pCache->GetAt(i, &key); if (SUCCEEDED(hr)) { // check the cache state; only save dirty properties PSC_STATE psc; hr = _pCache->GetState(key, &psc); if (SUCCEEDED(hr) && (psc == PSC_DIRTY)) { // get the cached value PROPVARIANT propvar = {}; hr = _pCache->GetValue(key, &propvar); if (SUCCEEDED(hr)) { // save as a native property if the key is in the property map BOOL fIsNativeProperty = FALSE; for (UINT i = 0; i < ARRAYSIZE(g_rgPROPERTYMAP); ++i) { if (key == *g_rgPROPERTYMAP[i].pkey) { fIsNativeProperty = TRUE; hr = _SaveProperty(propvar, g_rgPROPERTYMAP[i]); break; } } // otherwise, save as an extended proeprty if (!fIsNativeProperty) { hr = _SaveExtendedProperty(key, propvar); } PropVariantClear(&propvar); } } } } return hr; } // Saves the values in the given PROPVARIANT to the specified XML nodes HRESULT CRecipePropertyHandler::_SavePropertyValues(IXMLDOMNode* pNodeParent, PCWSTR pszValueNodeName, REFPROPVARIANT propvarValues) { // iterate through each value in the PROPVARIANT HRESULT hr = S_OK; ULONG cValues = PropVariantGetElementCount(propvarValues); for (ULONG iValue = 0; SUCCEEDED(hr) && (iValue < cValues); iValue++) { PROPVARIANT propvarValue = {}; hr = PropVariantGetElem(propvarValues, iValue, &propvarValue); if (SUCCEEDED(hr)) { // convert to a BSTR BSTR bstrValue = NULL; hr = PropVariantToBSTR(propvarValue, &bstrValue); if (SUCCEEDED(hr)) { // create an element and set its text to the value IXMLDOMElement *pValue = NULL; hr = _pDomDoc->createElement(CastToBSTRForInput(pszValueNodeName), &pValue); if (SUCCEEDED(hr)) { hr = pValue->put_text(bstrValue); if (SUCCEEDED(hr)) { // append the value to its parent node hr = pNodeParent->appendChild(pValue, NULL); } pValue->Release(); } SysFreeString(bstrValue); } PropVariantClear(&propvarValue); } } return hr; } // Saves the given PROPVARIANT value to the XML nodes specified by the specified map HRESULT CRecipePropertyHandler::_SaveProperty(REFPROPVARIANT propvar, const PROPERTYMAP &map) { // obtain the parent node of the value IXMLDOMNode *pNodeParent = NULL; HRESULT hr = _pDomDoc->selectSingleNode(CastToBSTRForInput(map.pszXPathParent), &pNodeParent); if (hr == S_OK) { // remove existing value nodes IXMLDOMNodeList *pNodeListValues = NULL; hr = pNodeParent->selectNodes(CastToBSTRForInput(map.pszValueNodeName), &pNodeListValues); if (hr == S_OK) { IXMLDOMSelection *pSelectionValues = NULL; hr = pNodeListValues->QueryInterface(IID_PPV_ARGS(&pSelectionValues)); if (SUCCEEDED(hr)) { hr = pSelectionValues->removeAll(); pSelectionValues->Release(); } pNodeListValues->Release(); } if (SUCCEEDED(hr)) { // save the new values to the parent node hr = _SavePropertyValues(pNodeParent, map.pszValueNodeName, propvar); } pNodeParent->Release(); } return hr; } // Saves an extended property with the given key and value HRESULT CRecipePropertyHandler::_SaveExtendedProperty(REFPROPERTYKEY key, REFPROPVARIANT propvar) { // convert the key to string form; don't use canonical name, since it may not be registered on other systems WCHAR szKey[MAX_PATH] = {}; HRESULT hr = PSStringFromPropertyKey(key, szKey, ARRAYSIZE(szKey)); if (SUCCEEDED(hr)) { // serialize the value to string form PWSTR pszValue; hr = SerializePropVariantAsString(propvar, &pszValue); if (SUCCEEDED(hr)) { // obtain or create the ExtendedProperties node in the document IXMLDOMElement *pRecipe = NULL; hr = _pDomDoc->get_documentElement(&pRecipe); if (hr == S_OK) { IXMLDOMNode *pExtended = NULL; hr = _EnsureChildNodeExists(pRecipe, L"ExtendedProperties", NULL, &pExtended); if (SUCCEEDED(hr)) { // query for the Property node with the given key, or create a new Property node WCHAR szPropertyPath[MAX_PATH]; hr = StringCchPrintfW(szPropertyPath, ARRAYSIZE(szPropertyPath), L"Property[@Key = '%s']", szKey); if (SUCCEEDED(hr)) { if (propvar.vt != VT_EMPTY) { IXMLDOMNode *pPropNode = NULL; hr = _EnsureChildNodeExists(pExtended, L"Property", szPropertyPath, &pPropNode); if (SUCCEEDED(hr)) { IXMLDOMElement *pPropNodeElem = NULL; hr = pPropNode->QueryInterface(IID_PPV_ARGS(&pPropNodeElem)); if (SUCCEEDED(hr)) { // set the attributes of the node with the name and value of the property VARIANT varKey = {}; hr = InitVariantFromString(szKey, &varKey); if (SUCCEEDED(hr)) { VARIANT varValue = {}; hr = InitVariantFromString(pszValue, &varValue); if (SUCCEEDED(hr)) { hr = pPropNodeElem->setAttribute(CastToBSTRForInput(L"Key"), varKey); if (SUCCEEDED(hr)) { hr = pPropNodeElem->setAttribute(CastToBSTRForInput(L"EncodedValue"), varValue); } VariantClear(&varValue); } VariantClear(&varKey); } pPropNodeElem->Release(); } pPropNode->Release(); } } else { // VT_EMPTY means "clear the value", so remove the corresponding node IXMLDOMNode *pPropNode = NULL; hr = pExtended->selectSingleNode(szPropertyPath, &pPropNode); if (hr == S_OK) { IXMLDOMNode *pRemoved; hr = pExtended->removeChild(pPropNode, &pRemoved); if (SUCCEEDED(hr)) { pRemoved->Release(); } pPropNode->Release(); } } } pExtended->Release(); } pRecipe->Release(); } else { hr = E_UNEXPECTED; } CoTaskMemFree(pszValue); } } return hr; } // Queries for the specified child node, and creates and appends a new one if no such node exists HRESULT CRecipePropertyHandler::_EnsureChildNodeExists(IXMLDOMNode *pNodeParent, PCWSTR pszName, PCWSTR pszXPath, IXMLDOMNode **ppNodeChild) { // query for the child node in case it already exists HRESULT hr = pNodeParent->selectSingleNode(CastToBSTRForInput(pszXPath ? pszXPath : pszName), ppNodeChild); if (hr != S_OK) { // create an element with the specified name and append it to the given parent node IXMLDOMElement *pChildElem = NULL; hr = _pDomDoc->createElement(CastToBSTRForInput(pszName), &pChildElem); if (SUCCEEDED(hr)) { hr = pNodeParent->appendChild(pChildElem, ppNodeChild); pChildElem->Release(); } } return hr; } // Serializes a PROPVARIANT value to string form HRESULT SerializePropVariantAsString(REFPROPVARIANT propvar, PWSTR *ppszOut) { SERIALIZEDPROPERTYVALUE *pBlob; ULONG cbBlob; // serialize PROPVARIANT to binary form HRESULT hr = StgSerializePropVariant(&propvar, &pBlob, &cbBlob); if (SUCCEEDED(hr)) { // determine the required buffer size DWORD cchString; hr = CryptBinaryToStringW((BYTE *)pBlob, cbBlob, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &cchString) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { // allocate a sufficient buffer PWSTR pszOut = (PWSTR)CoTaskMemAlloc(sizeof(WCHAR) * cchString); hr = pszOut ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { // convert the serialized binary blob to a string representation hr = CryptBinaryToStringW((BYTE *)pBlob, cbBlob, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, pszOut, &cchString) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { *ppszOut = pszOut; pszOut = NULL; // ownership transferred to *ppszOut } CoTaskMemFree(pszOut); } } CoTaskMemFree(pBlob); } return hr; } // Deserializes a string value back into PROPVARIANT form HRESULT DeserializePropVariantFromString(PCWSTR pszIn, PROPVARIANT *ppropvar) { HRESULT hr = E_FAIL; DWORD dwFormatUsed, dwSkip, cbBlob; // compute and validate the required buffer size if (CryptStringToBinaryW(pszIn, 0, CRYPT_STRING_BASE64, NULL, &cbBlob, &dwSkip, &dwFormatUsed) && dwSkip == 0 && dwFormatUsed == CRYPT_STRING_BASE64) { // allocate a buffer to hold the serialized binary blob BYTE *pbSerialized = (BYTE *)CoTaskMemAlloc(cbBlob); hr = pbSerialized ? S_OK : E_OUTOFMEMORY; if (SUCCEEDED(hr)) { // convert the string to a serialized binary blob hr = CryptStringToBinaryW(pszIn, 0, CRYPT_STRING_BASE64, pbSerialized, &cbBlob, &dwSkip, &dwFormatUsed) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { // deserialized the blob back into a PROPVARIANT value hr = StgDeserializePropVariant((SERIALIZEDPROPERTYVALUE *)pbSerialized, cbBlob, ppropvar); } CoTaskMemFree(pbSerialized); } } return hr; } const WCHAR c_szRecipeFileExtension[] = L".recipe"; const WCHAR c_szRecipeProgID[] = L"Windows.Recipe"; HRESULT RegisterHandler() { const WCHAR c_szPropertyHandlerDescription[] = L"Recipe (.recipe) Property Handler"; // register the property handler COM object with the system CRegisterExtension re(__uuidof(CRecipePropertyHandler), HKEY_LOCAL_MACHINE); HRESULT hr = re.RegisterInProcServer(c_szPropertyHandlerDescription, L"Both"); if (SUCCEEDED(hr)) { hr = re.RegisterInProcServerAttribute(L"ManualSafeSave", TRUE); if (SUCCEEDED(hr)) { hr = re.RegisterPropertyHandler(c_szRecipeFileExtension); } } // register proplists on the ProgID to facilitate easy unregistration and minimize conflicts with other apps that may handle this file extension const WCHAR c_szRecipeInfoTip[] = L"prop:System.ItemType;System.Author;System.Rating;Microsoft.SampleRecipe.Difficulty"; const WCHAR c_szRecipeFullDetails[] = L"prop:System.PropGroup.Description;System.Title;System.Author;System.Comment;System.Keywords;System.Rating;Microsoft.SampleRecipe.Difficulty;System.PropGroup.FileSystem;System.ItemNameDisplay;System.ItemType;System.ItemFolderPathDisplay;System.Size;System.DateCreated;System.DateModified;System.DateAccessed;System.FileAttributes;System.OfflineAvailability;System.OfflineStatus;System.SharedWith;System.FileOwner;System.ComputerName"; const WCHAR c_szRecipePreviewDetails[] = L"prop:System.DateChanged;System.Author;System.Keywords;Microsoft.SampleRecipe.Difficulty; System.Rating;System.Comment;System.Size;System.ItemFolderPathDisplay;System.DateCreated"; const WCHAR c_szRecipePreviewTitle[] = L"prop:System.Title;System.ItemType"; if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(c_szRecipeProgID, L"InfoTip", c_szRecipeInfoTip); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(c_szRecipeProgID, L"FullDetails", c_szRecipeFullDetails); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(c_szRecipeProgID, L"PreviewDetails", c_szRecipePreviewDetails); if (SUCCEEDED(hr)) { hr = re.RegisterProgIDValue(c_szRecipeProgID, L"PreviewTitle", c_szRecipePreviewTitle); } } } } // associate the ProgID to the file extension if (SUCCEEDED(hr)) { hr = re.RegisterExtensionWithProgID(c_szRecipeFileExtension, c_szRecipeProgID); } return hr; } HRESULT UnregisterHandler() { // Remove the COM object registration. CRegisterExtension re(__uuidof(CRecipePropertyHandler), HKEY_LOCAL_MACHINE); HRESULT hr = re.UnRegisterObject(); if (SUCCEEDED(hr)) { // Unregister the property handler for the file extension. hr = re.UnRegisterPropertyHandler(c_szRecipeFileExtension); if (SUCCEEDED(hr)) { // Remove the whole ProgID since we own all of those settings. // Don't try to remove the file extension association since some other application may have overridden it with their own ProgID in the meantime. // Leaving the association to a non-existing ProgID is handled gracefully by the Shell. // NOTE: If the file extension is unambiguously owned by this application, the association to the ProgID could be safely removed as well, // along with any other association data stored on the file extension itself. hr = re.UnRegisterProgID(c_szRecipeProgID, c_szRecipeFileExtension); } } return hr; }