2025-11-28 00:35:46 +09:00

930 lines
36 KiB
C++

//
// 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 <windows.h>
#include <new> // std::nothrow
#include <shobjidl.h> // IInitializeWithStream, IDestinationStreamFactory
#include <propsys.h> // Property System APIs and interfaces
#include <propkey.h> // System PROPERTYKEY definitions
#include <propvarutil.h> // PROPVARIANT and VARIANT helper APIs
#include <msxml6.h> // DOM interfaces
#include <wincrypt.h> // CryptBinaryToString, CryptStringToBinary
#include <strsafe.h> // 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<BSTR>(psz); }
template <class T> 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<PCWSTR*>(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"<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n"
L"<xsl:output method=\"text\" version=\"1.0\" encoding=\"UTF-8\" indent=\"no\"/>\n"
L" <xsl:template match=\"/\">\n"
L" <xsl:apply-templates select=\"Recipe/Ingredients/Item\"/>\n"
L" <xsl:apply-templates select=\"Recipe/Directions/Step\"/>\n"
L" <xsl:apply-templates select=\"Recipe/RecipeInfo/Yield\"/>\n"
L" <xsl:apply-templates select=\"Recipe/RecipeInfo/Difficulty\"/>\n"
L" <xsl:apply-templates select=\"Recipe/RecipeKeywords/Keyword\"/>\n"
L" </xsl:template>\n"
L" <xsl:template match=\"*\">\n"
L" <xsl:value-of select=\".\"/>\n"
L" <xsl:text> </xsl:text>\n"
L" </xsl:template>\n"
L"</xsl:stylesheet>");
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;
}