I am trying to extract the file description of executables. The file description is the one seen when you right click on a file, choose Properties and it is in the General tab.
I have tried using the algorithm found here: https://docs.microsoft.com/en-us/windows/desktop/api/winver/nf-winver-verqueryvaluea but for some files the file description returned is empty although I can see it in the Properties window. For example, if I create an executable the file description returned will be empty, but in the Properties window its file description is the same as its name. How can I extract the file description for every executable that has one in the General tab and not get empty strings?
I am using the functions in the following order:
GetFileVersionInfoSize
GetFileVersionInfo
VerQueryValue
StringCchPrintfW
VerQueryValue
StringCchCopyNW
Sometimes it fails at VerQueryValue and sometimes it fails at GetFileVersionInfo. I have also noticed that it fails for Microsoft.Photos.exe
If you want to mimic the shells behaviour, use the shell API, specifically its property system.
Most of the data shown in the properties dialog can be queried using a set of predefined constants, which are defined in "Propkey.h". In this case we want the System.FileDescription property. To query it, we need its PKEY, which is PKEY_FileDescription
.
One of the easiest ways to query a property is the IShellItem2::GetString()
method. The out parameter ppsz
returns a pointer to a string, which must be freed using CoTaskMemFree()
. This is not mentioned by the reference, but is the usual way to free memory allocated for you by the shell.
To obtain an IShellItem2 interface from a filesystem path, we can use SHCreateItemFromParsingName()
.
In the following example, I've wrapped up the reusable code in the function GetShellPropStringFromPath()
.
Example C++ console application:
#include <Windows.h>
#include <ShlObj.h> // Shell API
#include <Propkey.h> // PKEY_* constants
#include <atlbase.h> // CComPtr, CComHeapPtr
#include <iostream>
#include <io.h>
#include <fcntl.h>
#include <string>
#include <system_error>
// Wrapper for SHCreateItemFromParsingName(), IShellItem2::GetString()
// Throws std::system_error in case of any error.
std::wstring GetShellPropStringFromPath( LPCWSTR pPath, PROPERTYKEY const& key )
{
// Use CComPtr to automatically release the IShellItem2 interface when the function returns
// or an exception is thrown.
CComPtr<IShellItem2> pItem;
HRESULT hr = SHCreateItemFromParsingName( pPath, nullptr, IID_PPV_ARGS( &pItem ) );
if( FAILED( hr ) )
throw std::system_error( hr, std::system_category(), "SHCreateItemFromParsingName() failed" );
// Use CComHeapPtr to automatically release the string allocated by the shell when the function returns
// or an exception is thrown (calls CoTaskMemFree).
CComHeapPtr<WCHAR> pValue;
hr = pItem->GetString( key, &pValue );
if( FAILED( hr ) )
throw std::system_error( hr, std::system_category(), "IShellItem2::GetString() failed" );
// Copy to wstring for convenience
return std::wstring( pValue );
}
int main()
{
CoInitialize( nullptr ); // TODO: error handling
_setmode( _fileno( stdout ), _O_U16TEXT ); // for proper UTF-16 console output
try
{
// Show some properties of Microsoft.Photos.exe (adjust path if necessary)
LPCWSTR path = LR"(C:\Program Files\WindowsApps\Microsoft.Windows.Photos_2018.18061.17410.0_x64__8wekyb3d8bbwe\Microsoft.Photos.exe)";
std::wcout << L"PKEY_FileDescription: "
<< GetShellPropStringFromPath( path, PKEY_FileDescription ) << std::endl;
std::wcout << L"PKEY_Software_ProductName: "
<< GetShellPropStringFromPath( path, PKEY_Software_ProductName ) << std::endl;
}
catch( std::system_error const& e )
{
std::wcout << L"ERROR: " << e.what() << L"\nError code: " << e.code() << std::endl;
}
CoUninitialize();
}
Output:
PKEY_FileDescription: Microsoft.Photos.exe
PKEY_Software_ProductName: Microsoft Photos