SHGetFileInfo on the public desktop - winapi

I'm having an issue with using SHGetFileInfo on the public desktop and files in the public desktop. I'll focus on the actual desktop folder (CSIDL_COMMON_DESKTOPDIRECTORY or usually "C:\Users\Public\Desktop"). Also - I've started seeing this behavior only recently, but I can't pinpoint the exact change which made it faulty. The actual call to ShGetFileInfo has not changed (as far as I can tell).
So I have this (I've omitted intermediate error checks to shorten - the function calls return with success):
SHFILEINFOW info;
uint32_t return_value = 0;
uint32_t flags = SHGFI_TYPENAME|SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX;
uint32_t attributes = FILE_ATTRIBUTE_NORMAL;
wchar_t *path = L"C:\\Users\\Public\\Desktop";
return_value = SHGetFileInfoW(path, attributes, &info, sizeof(SHFILEINFOW), flags);
printf("[%ls] %u ", path, return_value);
This returns 0 as the return value. If I populate path using:
SHGetFolderPathW(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, path)
I get the same result. But if I use the pidl, as in:
LPITEMIDLIST pidl = NULL;
SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &pidl);
return_value = SHGetFileInfoW((LPCWSTR) pidl, attributes, &info, sizeof(SHFILEINFOW), flags | SHGFI_PIDL);
Then I get something which I expect, a handle to the system small icon list.
I can't tell what I'm doing wrong - and it only happens on this specific folder. I actually need icons for the items inside the directory, so using the pidl doesn't seem to be an option right now. Any ideas on what is the expected way to retrieve icons from the common desktop?
--
I should mention this behavior happens on Windows Vista - using the path populated by SHGetFolderPathW on XP works fine

I had the same problem. It can be fixed by calling the function CoInitialize from OLE32.DLL when the program starts.
CoInitialize(0);
return_value = SHGetFileInfoW(path, attributes, &info, sizeof(SHFILEINFOW), flags);

Related

Detect Alt-Tab/Task Switching/ForegroundStaging Window accurately

Given a valid hwnd, how can we verify if it is indeed the alt-tab window?
One of my previous methods was to get the class of the window that the hwnd belongs to, then compare it to these values: MultitaskingViewFrame, ForegroundStaging, TaskSwitcherWnd and TaskSwitcherOverlayWnd.
However, I've come to realise that class names are not unique across the system, and indeed one can RegisterClassEx a class with the same name as the above names, which means my method above would give false positives.
I found a working solution to the problem: additionally, filter by process path using GetWindowModuleFileNameW.
rough pseudocode:
if window.class() in [..] && GetWindowModuleFileNameW(window.hwnd) == "C:\Windows\explorer.exe" {
ignore()
}
Processes cannot fake their path, so this works.
I recommend using the SetWinEventHook function to get the active display information correctly.
HWINEVENTHOOK SetWinEventHook(
DWORD eventMin,
DWORD eventMax,
HMODULE hmodWinEventProc,
WINEVENTPROC pfnWinEventProc,
DWORD idProcess,
DWORD idThread,
DWORD dwFlags
);
DWORD eventMin,
DWORD eventMax,
It is sufficient to give the constants EVENT_SYSTEM_FOREGROUND as arguments.
If I press alt-tab, the window switches fast but the callback is fired
for the the Task Switcher window and some other invisible window with
class ForegroundStaging.
For invisible windows, you can use IsWindowVisible to filter.
For the the Task Switcher window, you can compare their title names to filter.
Some code:
void __stdcall Wineventproc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime)
{
if (event == EVENT_SYSTEM_FOREGROUND)
{
if (IsWindowVisible(hwnd))
{
TCHAR title[512] = {};
int index = GetWindowText(hwnd, title, 512);
if (_tcsncmp(title, TEXT("Task Switching"), 14))
{
title[index] = L'\n';
title[index + 1] = L'\0';
OutputDebugString(title);
}
}
}
}
but I also need to account for rogue processes that try to masquerade
as ForegroundStaging etc.
This is a complicated question. No antivirus or protection technology is perfect. It takes time to identify and block malicious sites and applications, or trust newly released programs and certificates.  With almost 2 billion websites on the internet and software continuously updated and released, it's impossible to have information about every single site and program.
In other words, it is not easy to efficiently identify the true identity of each unknown application.
Refer: Unknown – Unrecognized software

Creating a shortcut on Win10 fails accessdenied

I have implemented the creation of a shortcut as defined in this article on Shell Links by MSDN
MSDN Shell Links
Here is what I implemented
CreateLink - Uses the Shell's IShellLink and IPersistFile interfaces
to create and store a shortcut to the specified object.
Returns the result of calling the member functions of the interfaces.
Parameters:
lpszPathObj - Address of a buffer that contains the path of the object,
including the file name.
lpszPathLink - Address of a buffer that contains the path where the
Shell link is to be stored, including the file name.
lpszDesc - Address of a buffer that contains a description of the
Shell link, stored in the Comment field of the link
properties.
HRESULT CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc)
{
HRESULT hres;
IShellLink* psl;
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
// has already been called.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Set the path to the shortcut target and add the description.
psl->SetPath(lpszPathObj);
psl->SetDescription(lpszDesc);
// Query IShellLink for the IPersistFile interface, used for saving the
// shortcut in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres))
{
WCHAR wsz[MAX_PATH];
// Ensure that the string is Unicode.
MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH);
// Add code here to check return value from MultiByteWideChar
// for success.
// Save the link by calling IPersistFile::Save.
hres = ppf->Save(wsz, TRUE);
ppf->Release();
}
psl->Release();
}
return hres;
I call it as:
r = CreateLink("c:\\TT\\TT.exe", "C:\\Users\\Public\\Desktop\\TT.lnk", "TT Program");
This works great on a WIN7 machine but fails on a WIN10
This is the code line that is failing:
hres = ppf->Save(wsz, TRUE);
hres == AccessDenied;
I have found other system calls that work on a Win7 but fails on Win10 and it usually comes down to some undocumented change.
Has Win10 tightened privileges on the DeskTop?
Any suggestions on addressing this on a Win10 machine?
Thanks.

behavior different when run outside of visual studio

I was surprised about the behavior of the following code:
if(RegQueryValueEx(....)!=ERROR_SUCCESS){
...
}
when it was run from visual studio it didn't enter this if block, because the key did exist. when ran outside of the visual studio environment it evaluated true and hence entered the block, even though the queried key existed. After some testing i found out that when i first save it to a variable it runs always fine. With the following code:
HKEY hSoftwareKey,hAppKey;
DWORD dwLength;
int iStatus=1;
char szBuffer[MAX_PATH];
if(iStatus&&RegOpenKeyA(HKEY_CURRENT_USER,"Software",&hSoftwareKey)!=ERROR_SUCCESS)
iStatus = 0;
if(iStatus&&RegCreateKeyA(hSoftwareKey,"Amine",&hAppKey)!=ERROR_SUCCESS){
iStatus = 0;
}
ZeroMemory(szBuffer,MAX_PATH);
LONG lRet;
lRet = RegQueryValueExA(hAppKey,"One",0,0,reinterpret_cast<LPBYTE>(szBuffer),&dwLength);
Does this bevavior have anything to do with the __stdcall/WINAPI calling convention? If so could somebody please explain why
You need to initialize dwLength to MAX_PATH before calling RegQueryValueEx(), otherwise it's value is undefined.
from MSDN RegQueryValueEx:
lpcbData is a pointer to a variable that specifies the size of the buffer pointed to by the lpData parameter, in bytes. When the function returns, this variable contains the size of the data copied to lpData

Force GetKeyNameText to english

The Win32 function GetKeyNameText will provide the name of keyboard keys in the current input locale.
From MSDN:
The key name is translated according to the layout of the currently
installed keyboard, thus the function may give different results for
different input locales.
Is it possible to force the input locale for a short amount of time? Or is there another alternative to GetKeyNameText that will always return the name in English?
Update: This answer does not work. It actually modifies the keyboard settings of the user. This appear to be a behavior change between Windows versions.
CString csLangId;
csLangId.Format( L"%08X", MAKELANGID( LANG_INVARIANT, SUBLANG_NEUTRAL ) );
HKL hLocale = LoadKeyboardLayout( (LPCTSTR)csLangId, KLF_ACTIVATE );
HKL hPrevious = ActivateKeyboardLayout( hLocale, KLF_SETFORPROCESS );
// Call GetKeyNameText
ActivateKeyboardLayout( hPrevious, KLF_SETFORPROCESS );
UnloadKeyboardLayout( hLocale );
WARNING: GetKeyNameText is broken (it returns wrong A-Z key names for non-english keyboard layouts since it uses MapVirtualKey with MAPVK_VK_TO_CHAR that is broken), keyboard layout dlls pKeyNames and pKeyNamesExt text is bugged and outdated. I cannot recommend dealing with this stuff at all. :)
If you're really-really want to get this info - then you can load and parse it manually from keyboard layout dll file (kbdus.dll, kbdger.dll etc).
There is a bunch of undocumented stuff involved:
In order to get proper keyboard layout dll file name first you need to convert HKL to KLID string. You can do this via such code:
// Returns KLID string of size KL_NAMELENGTH
// Same as GetKeyboardLayoutName but for any HKL
// https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values
BOOL GetKLIDFromHKL(HKL hkl, _Out_writes_(KL_NAMELENGTH) LPWSTR pwszKLID)
{
bool succeded = false;
if ((HIWORD(hkl) & 0xf000) == 0xf000) // deviceId contains layoutId
{
WORD layoutId = HIWORD(hkl) & 0x0fff;
HKEY key;
CHECK_EQ(::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key), ERROR_SUCCESS);
DWORD index = 0;
while (::RegEnumKeyW(key, index, pwszKLID, KL_NAMELENGTH) == ERROR_SUCCESS)
{
WCHAR layoutIdBuffer[MAX_PATH] = {};
DWORD layoutIdBufferSize = sizeof(layoutIdBuffer);
if (::RegGetValueW(key, pwszKLID, L"Layout Id", RRF_RT_REG_SZ, nullptr, layoutIdBuffer, &layoutIdBufferSize) == ERROR_SUCCESS)
{
if (layoutId == std::stoul(layoutIdBuffer, nullptr, 16))
{
succeded = true;
DBGPRINT("Found KLID 0x%ls by layoutId=0x%04x", pwszKLID, layoutId);
break;
}
}
++index;
}
CHECK_EQ(::RegCloseKey(key), ERROR_SUCCESS);
}
else
{
WORD langId = LOWORD(hkl);
// deviceId overrides langId if set
if (HIWORD(hkl) != 0)
langId = HIWORD(hkl);
std::swprintf(pwszKLID, KL_NAMELENGTH, L"%08X", langId);
succeded = true;
DBGPRINT("Found KLID 0x%ls by langId=0x%04x", pwszKLID, langId);
}
return succeded;
}
Then with KLID string you need to go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\%KLID% registry path and read Layout File string from it.
Load this dll file from SHGetKnownFolderPath(FOLDERID_System, ...) (usually C:\Windows\System32) with LoadLibrary() call.
Next you need to do GetProcAddress(KbdDllHandle, "KbdLayerDescriptor") - you're receive pointer that can be casted to PKBDTABLES.
There is kbd.h header in Windows SDK that have KBDTABLES struct definition (there is some stuff involved to use proper KBD_LONG_POINTER size for x32 code running on x64 Windows. See my link to Gtk source at the end).
You have to look at pKeyNames and pKeyNamesExt in it to get scan code -> key name mapping.
Long story short: The GTK toolkit have the code that doing all this(see here and here). Actually they are building scan code -> printed chars tables from Windows keyboard layout dlls.

How to tell if I'm leaking IMalloc memory?

I'd like to just know if there is a well-established standard way to ensure that one's process doesn't leak COM based resources (such as IMalloc'd objects)?
Take the following code as an example:
HRESULT STDMETHODCALLTYPE CShellBrowserDialog::OnStateChange(__RPC__in_opt IShellView *ppshv, ULONG uChange)
{
TRACE("ICommDlgBrowser::OnStateChange\n");
if (uChange == CDBOSC_SELCHANGE)
{
CComPtr<IDataObject> data;
if (ppshv->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (void**)&data) == S_OK )
{
UINT cfFormat = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fmtetc = { cfFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed;
if (data->GetData(&fmtetc, &stgmed) == S_OK)
{
TCHAR path[MAX_PATH];
// check if this single selection (or multiple)
CIDA * cida = (CIDA*)stgmed.hGlobal;
if (cida->cidl == 1)
{
const ITEMIDLIST * pidlDirectory = (const ITEMIDLIST *)(((LPBYTE)cida) + cida->aoffset[0]);
const ITEMIDLIST * pidlFile = (const ITEMIDLIST *)(((LPBYTE)cida) + cida->aoffset[1]);
ITEMIDLIST * pidl = Pidl_Concatenate(pidlDirectory, pidlFile);
// we now have the absolute pidl of the currently selected filesystem object
if (!SHGetPathFromIDList(pidl, path))
strcpy_s(path, _T("<this object has no path>"));
// free our absolute pidl
Pidl_Free(pidl);
}
else if (cida->cidl > 1)
strcpy_s(path, _T("{multiple selection}"));
else
strcpy_s(path, _T("-"));
// trace the current selection
TRACE(_T(" Current Selection = %s\n"), path);
// release the data
ReleaseStgMedium(&stgmed);
}
}
}
return E_NOTIMPL;
}
So in the above code, I have at least three allocations that occur in code that I call, with only one of them being properly cleaned up automatically. The first is the acquisition of IDataObject pointer, which increments that object's reference count. CComPtr<> takes care of that issue for me.
But then there is IDataObject::GetData, which allocates an HGLOBAL for me. And a utility function Pidl_Concatenate which creates a PIDL for me (code left out, but you can imagine it does the obvious thing, via IMalloc::Alloc()). I have another utility Pidl_Free which releases that memory for me, but must be manually called [which makes the code in question full of exception safety issues (its utterly unsafe as its currently written -- I hate MS's coding mechanics - just asking for memory to fail to be released properly].
I will enhance this block of code to have a PIDL class of some sort, and probably a CIDA class as well, to ensure that they're properly deallocated even in the face of exceptions. But I would still like to know if there is a good utility or idiom for writing COM applications in C++ that can ensure that all IMallocs and AddRef/Dispose are called for that application's lifetime!
Implementing the IMallocSpy interface (see CoRegisterMallocSpy Function) may help get you some of the way.
Note that this is for debugging only, and be careful. There are cautionary tales on the web...
You can not free the global handle returned by IDataObject::GetData, otherwise other programs can not paste from the clipboard after the data is cleaned up.
Any pidl you get from shell needs to be freed using IMalloc::Free or ILFree (same effect once OLE32.DLL is loaded into the process). Exceptions are pointers to the middle of item list which can not be freed independently. If you are worried about exceptions, guard your code with try/catch/finally and put the free code in the finally block.

Resources