Why would GetPath always return E_FAIL when querying FOLDERID_ControlPanelFolder? Other FOLDERIDs actually do work:
HRESULT hr = S_OK;
*path = '\0';
LPWSTR pwcPath = NULL;
CoInitialize(NULL);
IKnownFolderManager *pFolderManager = NULL;
if ((hr = CoCreateInstance(__uuidof(KnownFolderManager), NULL, CLSCTX_INPROC_SERVER, __uuidof(IKnownFolderManager), (LPVOID *)&pFolderManager)) == S_OK)
{
IKnownFolder *pControlPanelFolder = NULL;
if ((hr = pFolderManager->GetFolder(FOLDERID_ControlPanelFolder, &pControlPanelFolder)) == S_OK)
{
hr = pControlPanelFolder->GetPath(0, &pwcPath);
if (hr == S_OK && pwcPath)
{
int nSize = wcslen(pwcPath);
WideCharToMultiByte(CP_ACP, 0, pwcPath, nSize, path, nSize+2, NULL, NULL);
path[nSize] = '\0';
CoTaskMemFree(pwcPath);
}
pControlPanelFolder->Release();
pControlPanelFolder = NULL;
}
pFolderManager->Release();
pFolderManager = NULL;
}
CoUninitialize();
(Yes, I stumbled upon this question but I don't have need for all that enumeration stuff.)
The Control Panel has no directory path because it does not exist on the disc. You can get its PIDL, and even a Desktop Absolute Parsing "display name" (via GetShellItem and GetDisplayName), but not a directory path.
Reason why I needed the path was that I wanted to open the controp panel with a ShellExecute "open". I now execute the control panel program directly, with the benefit of being able to select the desired applet right away (in this case "Sound"). I hope it's not too pretentious that I post this as answer:
char controlpanelpath[2000];
UINT controlpanelpathbuffersize = sizeof(controlpanelpath);
int actualcontrolpanelpathsize;
if (actualcontrolpanelpathsize = GetSystemDirectory(controlpanelpath, controlpanelpathbuffersize))
{
char *parameters = "\\control.exe mmsys.cpl,,0";
if (actualcontrolpanelpathsize + strlen(parameters) < controlpanelpathbuffersize)
{
strcat(controlpanelpath, parameters);
WinExec(controlpanelpath, SW_NORMAL);
}
}
Related
Consider the following code:
bool ListFolderContent(const std::wstring& fileName)
{
PIDLIST_ABSOLUTE pidl = nullptr;
if (FAILED(::SHILCreateFromPath(fileName.c_str(), &pidl, nullptr)))
return false;
IShellFolder* pShellfolder = nullptr;
LPCITEMIDLIST pidlRelative = nullptr;
HRESULT hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&pShellfolder, &pidlRelative);
if (FAILED(hr))
{
::CoTaskMemFree(pidl);
return false;
}
IEnumIDList* pEnumIDList = nullptr;
hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnumIDList);
if (FAILED(hr))
{
pShellfolder->Release();
::CoTaskMemFree(pidl);
return false;
}
while (1)
{
LPITEMIDLIST pChild = nullptr;
hr = pEnumIDList->Next(1, &pChild, nullptr);
if (FAILED(hr))
{
pShellfolder->Release();
::CoTaskMemFree(pidl);
return false;
}
if (hr == S_FALSE)
break;
wchar_t buffer[MAX_PATH + 1];
if (::SHGetPathFromIDListW(pChild, buffer))
{
::OutputDebugString(buffer);
::OutputDebugString(L"\r\n");
}
}
pShellfolder->Release();
::CoTaskMemFree(pidl);
return true;
}
This code works well and logs the content of the folder owning the given file I pass through the fileName parameter.
However I have an issues with this code: Whatever I pass as file name, the logged path is always C:\Users\Admin\Desktop, and NOT the path to the parent folder I'm iterating, as expected. On the other hand the file names are correct.
Can someone explain me what I'm doing wrong?
You are passing just the last component ("filename") here: SHGetPathFromIDListW(pChild, buffer). Since the desktop is the root you are basically asking to get the filesystem path of [Desktop] [Filename] from the namespace.
There are two solutions:
pShellfolder->GetDisplayNameOf(pChild, SHGDN_FORPARSING, ...)+StrRetToBuf. This is fast since you already have an instance of the folder interface.
Call SHGetPathFromIDList with an absolute (full) pidl. On the pidl from SHILCreateFromPath, call ILCloneFull, ILRemoveLastID and ILCombine(clone, pChild).
HRESULT Example()
{
WCHAR buf[MAX_PATH];
GetWindowsDirectory(buf, MAX_PATH);
PathAppend(buf, L"Explorer.exe");
PIDLIST_ABSOLUTE pidl, pidlFullItem;
HRESULT hr = SHILCreateFromPath(buf, &pidl, nullptr); // %windir%\Explorer.exe
if (FAILED(hr)) return hr;
LPITEMIDLIST pLeaf;
IShellFolder*pShellfolder; // %windir%
hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&pShellfolder, nullptr);
if (SUCCEEDED(hr))
{
IEnumIDList*pEnum;
// Method 1:
hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnum);
if (SUCCEEDED(hr))
{
for (; S_OK == (hr = pEnum->Next(1, &pLeaf, nullptr));)
{
STRRET sr;
hr = pShellfolder->GetDisplayNameOf(pLeaf, SHGDN_FORPARSING, &sr);
if (SUCCEEDED(hr))
{
hr = StrRetToBuf(&sr, pLeaf, buf, MAX_PATH);
if (SUCCEEDED(hr)) wprintf(L"M1: Item: %s\n", buf);
}
}
pEnum->Release();
}
// Method 2:
ILRemoveLastID(pidl); // %windir%\Explorer.exe => %windir%
hr = pShellfolder->EnumObjects(nullptr, SHCONTF_NONFOLDERS, &pEnum);
if (SUCCEEDED(hr))
{
for (; S_OK == (hr = pEnum->Next(1, &pLeaf, nullptr));)
{
pidlFullItem = ILCombine(pidl, pLeaf); // %windir% + Filename
if (pidlFullItem)
{
hr = SHGetPathFromIDListW(pidlFullItem, buf);
if (SUCCEEDED(hr)) wprintf(L"M2: Item: %s\n", buf);
ILFree(pidlFullItem);
}
}
pEnum->Release();
}
pShellfolder->Release();
}
ILFree(pidl);
return hr;
}
I have searched a lot and have got some findings on how to get extended FA, but they are in C# using the language's own built-in APIs. I am trying to find the author name for a file in Windows, but my requirement is in Go/Python/C/Batch (order of priority).
In python, the third party packages (exifread and hachoir_metadata) are not working (not giving any result for a sample doc/xlsx file. Maybe the package I am installing via pip-install is erroneous).
Is there any other way or any user-level MSDN API available?
Please let me know if any experience on this. Thanks.
In C, C++ or other language, you get file properties with IPropertyStore interface
For example, for a .jpg file (test on Windows 10, VS 2015) =>
I get for Author :
System.Author(Auteurs) = Test Auteur
PIDLIST_ABSOLUTE pidl = ILCreateFromPath(L"E:\\icon_rose.jpg");
if (pidl != NULL)
{
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreFromIDList(pidl, GPS_DEFAULT, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr))
{
DWORD dwCount;
hr = pps->GetCount(&dwCount);
PROPERTYKEY propKey;
for (DWORD i = 0; i < dwCount; ++i)
{
hr = pps->GetAt(i, &propKey);
if (SUCCEEDED(hr))
{
PWSTR pszCanonicalName = NULL;
hr = PSGetNameFromPropertyKey(propKey, &pszCanonicalName);
PWSTR pszDescriptionName = NULL;
IPropertyDescription *ppd;
hr = PSGetPropertyDescription(propKey, IID_PPV_ARGS(&ppd));
if (SUCCEEDED(hr))
{
hr = ppd->GetDisplayName(&pszDescriptionName);
ppd->Release();
}
PROPVARIANT propvarValue = { 0 };
HRESULT hr = pps->GetValue(propKey, &propvarValue);
if (SUCCEEDED(hr))
{
PWSTR pszDisplayValue = NULL;
hr = PSFormatForDisplayAlloc(propKey, propvarValue, PDFF_DEFAULT, &pszDisplayValue);
if (SUCCEEDED(hr))
{
WCHAR wsBuffer[255];
wsprintf(wsBuffer, L"%s(%s) = %s\n", pszCanonicalName, (pszDescriptionName==NULL?L"Unknown":pszDescriptionName), pszDisplayValue);
OutputDebugString(wsBuffer);
CoTaskMemFree(pszDisplayValue);
}
PropVariantClear(&propvarValue);
}
if (pszCanonicalName != NULL)
CoTaskMemFree(pszCanonicalName);
if (pszDescriptionName != NULL)
CoTaskMemFree(pszDescriptionName);;
}
}
pps->Release();
}
ILFree(pidl);
}
I have a namespace extension in development and I'm stuck adding a folder programmatically from within the extension. What happens visually is a tool bar button is clicked, a file path is chosen and that file is the datasource that the virtual folders and files are created from.
What I want to happen since the load file button on the tool bar is within the namespace extensions root, is for the new virtual folder to appear automatically. In order for it to show up, I need to click the tree view or go up out of the root and back in. Then the folder is present.
I've researched this problem and normally when this occurs people are using SHChangeNotify. I tried that like the example below and using various combonations such as providing the path or pidl off the namespace extension root, the example below including the new folder that should be there, using the pidl of that path (with proper flag in SHChangeNotify) and still no dice. I also tried the SHCNE_xxx where xxx is a notify all flag.
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, retPidl, 0);
Right clicking the view pane and choosing refresh does not invoke the folder to appear.
In the code that gets the path of the folder, I called BindToObject of my folder then EnumObjects and Next, the breakpoints weren't hit. When I click the tree view or go up a folder and back down, all the breakpoints are hit. SHChangeNotify does not invoke the breakpoints.
The view is created using SHCreateShellFolderView in CreateViewObject like so:
if (IID_IShellView == riid) {
SFV_CREATE csfv = {0};
csfv.cbSize = sizeof(SFV_CREATE);
hr = QueryInterface(IID_PPV_ARGS(&csfv.pshf));
if (SUCCEEDED(hr)) {
hr = CShellFolderView_CreateInstance(IID_PPV_ARGS(&csfv.psfvcb));
if (SUCCEEDED(hr)) {
hr = SHCreateShellFolderView(&csfv, (IShellView**)ppv);
csfv.psfvcb->Release();
m_hWnd = hwndOwner;
}
csfv.pshf->Release();
}
}
in the ShellVolderView class, I set a bp on the notification flags and they never hit. I read that returning S_OK is needed for the SFVM_UPDATEOBJECT so I added that.
IFACEMETHODIMP CShellFolderView::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) {
HRESULT hr = E_NOTIMPL;
switch(uMsg) {
case SFVM_GETSORTDEFAULTS:
wParam = (WPARAM)(int*)-1;
lParam = (LPARAM)(int*)Col::Size;
hr = S_OK;
break;
case SFVM_GETNOTIFY:
*((LONG*)lParam) = SHCNE_ALLEVENTS;
hr = S_OK;
break;
}
return hr;
}
*Edit: Adding suspect function. When the *ppidl is NULL, E_INVALIDARG is returned.
STDMETHODIMP CShellFolder::ParseDisplayName(HWND hwnd, IBindCtx *pbc, LPWSTR pszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes) {
HRESULT hr = E_INVALIDARG;
if (pszDisplayName && *ppidl) {
WCHAR szNameComponent[MAX_SIZE] = {};
PWSTR pszNext = PathFindNextComponent(pszDisplayName);
if (pszNext && *pszNext) {
// unsure of max length, can't use StringCchLength for this
size_t len = lstrlen(pszDisplayName);
size_t nextLen = 0;
StringCchLength(pszNext, MAX_SIZE, &nextLen);
hr = StringCchCopyN(szNameComponent, MAX_SIZE, pszDisplayName, len - nextLen);
}
else {
hr = StringCchCopy(szNameComponent, MAX_SIZE, pszDisplayName);
}
if (SUCCEEDED(hr)) {
PathRemoveBackslash(szNameComponent);
PIDLIST_RELATIVE pidlCurrent = NULL;
LPPIDLDATA child = m_pPidlMgr->GetSubItemFromPidl((LPCITEMIDLIST)ppidl);
hr = m_pPidlMgr->Create(&pidlCurrent, child);
if (SUCCEEDED(hr)) {
if (pszNext && *pszNext) {
IShellFolder *psf;
hr = BindToObject(pidlCurrent, pbc, IID_PPV_ARGS(&psf));
if (SUCCEEDED(hr)) {
PIDLIST_RELATIVE pidlNext = NULL;
hr = psf->ParseDisplayName(hwnd, pbc, pszNext, pchEaten, &pidlNext, pdwAttributes);
if (SUCCEEDED(hr)) {
*ppidl = ILCombine(pidlCurrent, pidlNext);
ILFree(pidlNext);
}
psf->Release();
}
ILFree(pidlCurrent);
}
else {
*ppidl = pidlCurrent;
}
}
}
}
return hr;
}
What steps should I be taking to get the folder to show up programmatically?
Try the following:
SFVM_GETNOTIFY:
begin
PDWORD(ALParam)^ := SHCNE_ALLEVENTS;
Result := S_OK;
end;
I am using SHAutoComplete with the SHACF_FILESYSTEM option. The problem is, files relative to the current working directory are not autocompleted. There are no suggestions for relative paths -- for example, the working directory contains settings.txt, but I can type "settings" into the edit and nothing appears.
Is there a relatively easy solution? Or do I have to override the autocomplete behaviour with some own lookup?
Thank you in advance!
Please take a look at the documentation at:
http://msdn.microsoft.com/en-us/library/bb776884
You need to explicit specify the "Current directory" as an option. This must be done throu IACList2::SetOptions http://msdn.microsoft.com/en-us/library/windows/desktop/bb776376
==> You must use the COM interface, to set the options you want... Here is an example:
HRESULT EnableAutoComplete(HWND hWndEdit, LPWSTR szCurrentWorkingDirectory = NULL, AUTOCOMPLETELISTOPTIONS acloOptions = ACLO_NONE, AUTOCOMPLETEOPTIONS acoOptions = ACO_AUTOSUGGEST, REFCLSID clsid = CLSID_ACListISF)
{
IAutoComplete *pac;
HRESULT hr = CoCreateInstance(CLSID_AutoComplete,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pac));
if (FAILED(hr))
{
return hr;
}
IUnknown *punkSource;
hr = CoCreateInstance(clsid,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&punkSource));
if (FAILED(hr))
{
pac->Release();
return hr;
}
if ( (acloOptions != ACLO_NONE) || (szCurrentWorkingDirectory != NULL) )
{
IACList2 *pal2;
hr = punkSource->QueryInterface(IID_PPV_ARGS(&pal2));
if (SUCCEEDED(hr))
{
if (acloOptions != ACLO_NONE)
{
hr = pal2->SetOptions(acloOptions);
}
if (szCurrentWorkingDirectory != NULL)
{
ICurrentWorkingDirectory *pcwd;
hr = pal2->QueryInterface(IID_PPV_ARGS(&pcwd));
if (SUCCEEDED(hr))
{
hr = pcwd->SetDirectory(szCurrentWorkingDirectory);
pcwd->Release();
}
}
pal2->Release();
}
}
hr = pac->Init(hWndEdit, punkSource, NULL, NULL);
if (acoOptions != ACO_NONE)
{
IAutoComplete2 *pac2;
hr = pac->QueryInterface(IID_PPV_ARGS(&pac2));
if (SUCCEEDED(hr))
{
hr = pac2->SetOptions(acoOptions);
pac2->Release();
}
}
punkSource->Release();
pac->Release();
}
you can call it via:
wchar_t szCurDir[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, szCurDir);
EnableAutoComplete(m_txtBox1.m_hWnd, szCurDir, ACLO_CURRENTDIR);
I am using Outlook automation to find items in the Calendar. For that I use IsSearchSynchronous() method to see if I have to wait for the AdvancedSearchComplete event. BTW, is it ever synchronous???
Anyway, if I have Outlook running, I have no problems with this call. But if it doesn't run - the call fails with
HRESULT: 0x80020009 Exception occurred
The EXCEPINFO contains:
Source: "Microsoft Outlook"
Description: "The operation failed."
scode: 0x80004005
Any suggestions?
Here is my test case:
#include <atlstr.h>
int _tmain()
{
IDispatch* pApp;
HRESULT hr;
CoInitialize(NULL);
CLSID clsid;
hr = CLSIDFromProgID(L"Outlook.Application", &clsid);
if(SUCCEEDED(hr))
{
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pApp);
if(SUCCEEDED(hr))
{
// Get DISPID for "IsSearchSynchronous"
OLECHAR Name[] = {L"IsSearchSynchronous"};
LPOLESTR lp = Name;
DISPID dispID;
hr = pApp->GetIDsOfNames(IID_NULL, &lp, 1, LOCALE_USER_DEFAULT, &dispID);
if(SUCCEEDED(hr))
{
// The path name of the folders that the search will search through.
VARIANT path;
path.vt = VT_BSTR;
path.bstrVal = SysAllocString(L"'Calendar'");
// Build DISPPARAMS
DISPPARAMS dp = { NULL, NULL, 0, 0 };
dp.cArgs = 1;
dp.rgvarg = &path;
// get IsSearchSynchronous
VARIANT result;
VariantInit(&result);
EXCEPINFO ei = {0};
hr = pApp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dp, &result, &ei, NULL);
VARIANT_BOOL vbIsSearchSynchronous = result.boolVal;
}
}
}
CoUninitialize();
return 0;
}
In case anybody else is interested, this is resolved with a help from Microsoft.
The “scope” parameter to a IsSearchSynchronous() call should be a complete path to the calendar folder, like:
"\\Mailbox - <user_name>\\Calendar"