I have been trying to edit the metadata of some audio files using Windows' IPropertyStore
methods, and came across this strange problem. After setting an empty PROPVARIANT
value to a property store's key via IPropertyStore::SetValue()
, following attempts to set that key's value fail with a return value of 0x80030005
, which Visual Studio informs me is Access Denied.
.
This is the smallest example with which I could produce this behaviour:
#include <atlbase.h>
#include <filesystem>
#include <PropIdl.h>
#include <Propsys.h>
#include <propkey.h>
#include <propvarutil.h>
#include <ShObjIdl.h>
#include <Windows.h>
namespace fs = std::experimental::filesystem;
int main() {
HRESULT hr;
if (FAILED(hr = CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
// Handle error...
}
fs::path const test_path(fs::current_path() / "test.mp3");
CComPtr<IPropertyStore> property_store;
if (FAILED(hr = SHGetPropertyStoreFromParsingName(test_path.wstring().c_str(), NULL, GPS_READWRITE, IID_PPV_ARGS(&property_store)))) {
// Handle error...
}
// Set an empty value to the key
{
PROPVARIANT property{};
PropVariantInit(&property);
if (FAILED(hr = property_store->SetValue(PKEY_Title, property))) {
// Handle error...
}
if (FAILED(hr = PropVariantClear(&property))) {
// Handle error...
}
}
// Write a new value to the same key
{
PROPVARIANT property{};
if (FAILED(hr = InitPropVariantFromString(L"test file", &property))) {
// Handle error...
}
if (FAILED(hr = property_store->SetValue(PKEY_Title, property))) {
// Always fails here with hr == 0x80030005 "Access Denied."
}
if (FAILED(hr = PropVariantClear(&property))) {
// Handle error...
}
}
if (FAILED(hr = property_store->Commit())) {
// Handle error...
}
CoUninitialize();
}
This only seems to occur when setting an empty value first; any other value results in the program running as expected, with both changes written successfully.
As far as I can tell from the documentation, it is fine to write an empty value to a key - I can successfully write the empty value by itself and the changes are reflected when I view the file's properties in Explorer.
Furthermore, I don't understand how the error could possibly be "access denied" - the user the program is running under (my standard account) definitely has permissions to change the key (I can go into Properties in Explorer and manually change the key's value just fine), and how could it possibly be access denied for only one of the two key accesses?
So why can't I write an empty value to a key and then overwrite it later?
For those wondering why I need to clear a key and then immediately write a new value to it - I don't actually. That sequence of events just happens to occur in a larger program where all the properties are cleared and then later on certain keys are re-populated.
Update:
I've since experimented more with how the IPropertyStore
object handles file access.
When the IPropertyStore
object is initialised with a file (in this case through SHGetPropertyStoreFromParsingName()
), it seems to open the file exclusively - as if through calling CreateFile()
with 0
as the sharing mode. Once opened, none of my various attempts to open the file again via CreateFile()
succeeded; all failed with ERROR_SHARING_VIOLATION
. I believe this rules out another process (or even my program's process) from "stealing" the file access rights after the first property change (even though, as I discussed in the comments, property changes are not written until IPropertyStore::Commit()
is called).
Even once the IPropertyStore::Commit()
method is called, which writes all pending property changes to the file, the file remains open, exclusively. Reopening the file at this point is still not possible as far as I observed. This is peculiar, since the documentation states "[b]efore it returns, Commit releases the file stream or path with which the handler was initialized". What I found was until the IPropertyStore
object was released (IUnknown::Release()
), the associated file remained opened.
Committing and releasing the IPropertyStore
object after clearing the key, then recreating it and writing the new value, seems to work perfectly. The key is successfully cleared, and then successfully rewritten. However this still leaves the original question open: why can't I do the clear and rewrite in one open/write/commit cycle of the property store?
When you set the property to VT_EMPTY, you don't "set" its value, you don't "write an empty value", you don't "clear" it, you remove it. Note that the running property store's count is decremented (GetCount
), and you can't use GetAt
either anymore.
Official documentation for IPropertyStore::SetValue method is very clear:
Removing a property values from a property store is not supported and could lead to unexpected results.
User contributions licensed under CC BY-SA 3.0