SHGetFolderPath Deprecated: What is alternative to retrieve path for Windows folders? - c++

The SHGetFolderPath() function is deprecated beginning with Windows Vista: http://msdn.microsoft.com/en-us/library/bb762181%28v=VS.85%29.aspx
What is the alternative way to retrieve the path to the Application Folder in Windows?
SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szPath)
Aside from that, why do I get those errors when using this function:
Error 1 error C2065: 'CSIDL_COMMON_APPDATA' : undeclared identifier
Error 2 error C3861: 'SHGetFolderPath': identifier not found

The alternative is described in the documentation to which you link. Namely it is SHGetKnownFolderPath.
However, SHGetKnownFolderPath is only available on Vista or later. So if you use load time linking, and run a program that calls SHGetKnownFolderPath on XP, then that program will fail to start. This is clearly a problem if you wish to support XP.
Now, you could switch to run time linking of SHGetKnownFolderPath. Carry out a version check before you call it, and if the function is not available, then fall back to SHGetFolderPath.
Personally, I would not let this deprecation worry you excessively. Microsoft are renowned for maintaining backwards compatibility. Don't expect SHGetFolderPath to disappear any time soon. You will find that SHGetFolderPath exists in Windows 8 and I would expect it still to be present in whatever Windows is current 10 years from now. My advice is to stick to load time linking, and only switch to SHGetKnownFolderPath when you give up supporting XP.
Your other question, that you ask in an edit, is how to call SHGetFolderPath. You need to respect the requirements which are laid out at the bottom of the MSDN documentation topic which you linked to in your question. Specifically, include Shlobj.h and pass Shlobj.lib to the linker.

It is linked right at the top, SHGetKnownFolderPath.
CSIDL_COMMON_APPDATA is replaced by FOLDERID_ProgramData in the new API.

I faced the same set of errors when I added few new header files to my already working solution.
I was already calling SHGetFolderPath and had also included #include <ShlObj.h> but it was in a different header file. The solution was compiling without any errors before I added new library header files to it.
I tried replacing SHGetFolderPath() with SHGetKnownFolderPath() but this just redirected the identifier not found error to SHGetKnownFolderPath.
On adding #include <ShlObj.h> to the header file of the class calling SHGetFolderPath, the
errors ceased and the solution compiled successfully again.
As mentioned in this page, calling SHGetFolderPath on Windows Vista or a higher OS, will internally call SHGetKnownFolderPath.

I have tested using the SHGetFolderPath() with Visual Studio 2015 Enterprise on a Windows 10 PC and it compiled and worked just fine to find the current user's home folder. In the Windows Dev Center page on SHGetFolderPath() SHGetFolderPath function there is the following note:
Note As of Windows Vista, this function is merely a wrapper for
SHGetKnownFolderPath. The CSIDL value is translated to its associated
KNOWNFOLDERID and then SHGetKnownFolderPath is called. New
applications should use the known folder system rather than the older
CSIDL system, which is supported only for backward compatibility.
As David Heffman pointed out in his answer, Microsoft has a history of keeping backwards compatibility for years especially when they can take the older function and just redirect it to the new function with the appropriate arguments. The CSIDL values seem to have a corresponding KNOWNFOLDERID value. See this table of the CSIDL constants with brief annotations and the corresponding KNOWNFOLDERID value.
An example of the use of the function follows. This use retrieves the current user's user folder (e.g. "C:\Users\myuser\Documents" under Windows 7) and then adds a folder name to the end of the path using the PathAppend() function.
TCHAR achDevice[MAX_PATH];
HRESULT hr;
// include file ShlObj.h contains list of CSIDL defines however only a subset
// are supported with Windows 7 and later.
// for the 3rd argument, hToken, can be a specified Access Token or SSID for
// a user other than the current user. Using NULL gives us the current user.
if (SUCCEEDED(hr = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, achDevice))) {
// append a folder name to the user's Documents directory.
// the Path Handling functions are pretty handy.
PathAppend(achDevice, L"xxx");
}
One possible failure is one or more invalid arguments (hr == E_INVALIDARG). A returned value of S_OK indicates the call succeeded.
There are a few CSIDL constants that can be used to modify the results of the function such as CSIDL_FLAG_CREATE by using the bitwise OR operator. I am not sure how well those operators will work with Windows 7 and later.
There are limits on the supported CSIDL constants with Windows 7 and later. It also looks like there may be possible issues to overcome in complex, remote mounted, redirected, and/or shared folders in an Active Directory or similar environment.
See also KNOWNFOLDERID which includes a table that indicates some of the limitations of CSIDL and SHGetFolderPath(). Some examples from the table of CSIDL constants that may be useful.
CSIDL_LOCAL_APPDATA - %USERPROFILE%\AppData\Local
CSIDL_MYDOCUMENTS - %USERPROFILE%\Document
CSIDL_PERSONAL - %USERPROFILE%\Documents
CSIDL_FONTS - %windir%\Fonts
CSIDL_MYMUSIC - %USERPROFILE%\Music
CSIDL_MYPICTURES - %USERPROFILE%\Pictures
CSIDL_COMMON_APPDATA - %ALLUSERSPROFILE% (%ProgramData%, %SystemDrive%\ProgramData)
CSIDL_COMMON_DOCUMENTS - %PUBLIC%\Documents
By the way, the Shell Path Handling Functions are a nice library of methods for manipulating file paths.
See also Where to put common writable application files?

From microsoft, the altenate is "SHGetKnownFolderPath"
https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpatha
From my point of view, these functions are for c, c++ and similar languages.
From powershell, I just read the registry:
PS> cd hkcu:\Software\Microsoft\Windows\CurrentVersion\Explorer\
PS> dir
Here peek at "Shell Folders" and "User Shell Folders".
btw: These are for getting the values. I'd say that's fairly safe. For setting the values, better not use the registry direct, as it will ruin your day. Using the explorer -> properties of these 'directories' to move them, will also move the contents. Unfortunately, I donnot know a hook to use that in powershell.

Related

Run own code elevated at will from non-elevated plugin DLL

I am making a suite of 64-bit plugin DLLs for a Windows host application using Visual Studio/C++, and from the current version onward, the setup.exe that they come in creates a single shared user-writable folder under ProgramData in which I cache all sorts of (non-user specific) data files. Older versions didn't have that folder yet.
However, the distribution of my plugin binaries is often out of my hands too. They are repackaged by a 3rd party bundle which can only do dumb file copies of the DLLs (so no real setup.exe functionality I need like creating folder + set permissions). And since my binary DLLs are all 100% self-contained, users also historically have a hand of just copying the DLLs around to other machines as they see fit, but that ofc also lacks the new folder setup phase.
I am looking into a workaround to have my DLLs create the folder at runtime if it is missing. I know I can't elevate the host process in-place whenever I want, but I thought of the following ways:
Have an extra "FixSetup" entry point in my DLL, and when the need arises, start an elevated RunDLL32.exe and let it use this entry point in my DLL.However, I see all sorts of people all over the place talking about RunDLL being as good as deprecated and advising against using it, but then again that was already since Windows XP and it's still with us. I also hear of RunDLL having it's own runtime context which can change with every Windows release (like switching to high-DPI aware when that came available), and that it thus is a 'hostile' environment to run in (read it on Raymond Chan's blog IIRC). Should I really be afraid of using it, or is my use case so simple it can barely break? (no GUI, just a wrapped CreateDirectory call)
Create a small "FixSetup.exe" which just does the folder creation, package it into my DLL's resources, and extract-to-temp + run-elevated it at runtime.While this would bloat my DLLs (depending on how small I can get the .exe), I feel like it's also a more fragile + convoluted solution than 1. above (with file extraction and all; prob. best to sign the utility exe too to keep HIPS / antivirus from acting funny etc?).
Alter my DLLs so that they're actually .exes in disguise which happen to export the host-expected DLL entry points, so that I can call them directly (elevated).I know there are some major caveats here (like conflicts between the C runtime being included in DLL or non-DLL mode, Visual Studio prob. not approving of these shenanigans, etc.), and honestly I already feel I need a shower just after talking about this one. So while theoretically maybe feasible, it is my last resort.
Does anyone have any advise on my uncertainties above? Or maybe an even better suggestion?
EDIT
I've already managed to get option 1. working, and while it works seamlessly there's one drawback I spotted: the UAC prompt (understandably) asks whether the user wants to run RunDLL32.exe, signed by Microsoft. This might confuse/scare people no end (that is: if they even read these prompts...). I'd rather have the UAC prompt asking about MyPluginSetup.exe signed by MyCompany, so now I'm more inclined to go with option 2. instead.

How to monitor file changes via Win API

I need to monitor changes of particular set of files (or just one file) and let Windows report to my application.
It's likely, that most of the files will be in same directory, but I'd prefer per-file monitoring system.
I found this example http://codewee.com/view.php?idx=20
but the example monitors only special, Desktop folder.
First by calling SHGetSpecialFolderLocation, then using resultant LPITEMIDLIST
in SHChangeNotifyRegister function (via SHChangeNotifyEntry struct)
I was not able to generalize it to arbitrary directory.
MS Docs says that SHGetSpecialFolderLocation will not be supported in future anyway,
SHGetFolderLocation should be used instead.
But again, SHGetFolderLocation is deprecated, not even mentioning it has no string/path
parameter.
Is there any convenient function which takes directory path or complete file name
and produces LPITEMIDLIST, which can be then sticked into SHChangeNotifyRegister?
FindFirstChangeNotification et al.
ReadDirectoryChangesW, ReadDirectoryChangesExW
SHParseDisplayName can be used to convert a file path to PIDL, for use with SHChangeNotifyRegister

GetOpenFileName open at default directory 'Computer' possible?

I'm using GetOpenFileName to open files in C++, is it possible to set the initial dir at "Computer" virtual location with lpstrInitialDir?
Thanks,
Lee.
This is not possible with GetOpenFileName because the location you wish to use is not part of the file system. Rather it is part of the wider shell namespace.
If you look at the documentation for GetOpenFileName you will see that it has been superseded (over 10 years ago in fact) by the Common Item Dialogs. Those dialogs do allow you to specify the initial folder as a shell item.
If you need to support legacy Windows older than Vista, where IFileDialog is not available, try specifying a Shell folder GUID. For example, the My Computer GUID is 20D04FE0-3AEA-1069-A2D8-08002B30309D. You can specify it like this:
ofn.lpstrInitialDir = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
However, it is worth noting that this method is almost doomed to fail on Windows 7 and later, due to behavioral changes
So, you are better off using IFileDialog on Vista and later instead.

Do Windows Shell APIs work with long Unicode paths?

I can't seem to find the answer to this in MSDN. I'm curious, if I have something like this:
LPITEMIDLIST pidl = NULL;
HRESULT hr = SHParseDisplayName(L"\\\\?\\C:\\Users\\Name\\Folder", NULL, &pidl, 0, NULL);
It fails with HRESULT set to E_INVALIDARG. The issue goes away if I supply the path as "C:\\Users\\Name\\Folder", which is limited only to MAX_PATH characters.
Are those Shell APIs not compatible with long Unicode paths?
Typically no, it is not supported. \\?\ is a feature of the lower level file I/O API, not the higher level Shell API. \\?\ does not represent a Shell namespace.
Update: For something like parsing a long file path into a PIDL, you may need to manually divide the path string into its individual pieces and use IShellFolder directly to parse each one into parent/child PIDLs recursively as needed. If nothing else, that will help you identify which subfolder breaks the parsing, then you can report that to the user: "sorry, Windows path length limitation reached, cannot work with files/folder underneath path XXX".
No, the Shell API functions are (in general) not compatible with long Unicode paths.
E.g. in the documentation of the inverse function of SHParseDisplayName, namely SHGetPathFromIDList, you find
pszPath [out]
Type: LPTSTR
The address of a buffer to receive the file system path. This buffer must be at least MAX_PATH characters in size.
And in general the documentation notes this path length restriction for each relevant function, but AFAICS it's not there as a higher level overall general statement.
From a development point of view it's only reasonable to create >MAX_PATH paths, or e.g. paths involving reserved names such as CON, if they will not be handled by an ordinary end-user, because Windows Explorer refuses to handle them.
(I checked just now. Windows 8.1 Explorer refuses silently to delete a folder named con. I think it should, because an ordinary end user will find it difficult to remove it.)
A power user can work around the shell's path length limitation, in order to e.g. delete or rename, by leveraging some bugs in the command interpreter, by using subst drives, by using DOS shortnames, by writing programs that call the API functions, and possibly other techniques (hopefully not by disk editing). But to the average end user such techniques are unknown. So when the average end user gets some undesired >MAX_PATH path, then that user is stuck with it.

Windows Store C++ apps cannot create files using fopen() under Windows 8.1

We have several Windows 8 Store C++ apps that need to maintain configuration and data files.
Files are written in subfolders of Windows::Storage::ApplicationData::Current->LocalFolder. Example:
C:\Users\<username>\AppData\Local\Packages\<packagename>\LocalState\SubFolder1\SubFolder2\data.txt
In Windows 8.1 we have received a few reports from users that say state isn't remembered between app invocations. Upon closer inspection the files are not created (the subfolders are indeed created, but there are no files inside them)
Notes:
Subfolders are created using CreateDirectory(), files are created using fopen()
Files are created/opened using absolute paths
This always worked under Windows 8.0 and the code has not been changed since. In fact, one of our user reports stated that the app saved files fine under Windows 8.0, but stopped saving after the user upgraded to Windows 8.1.
We have not been able to replicate the issue locally using Windows 8.1. We're not sure how common this failure is, but we estimate that most users are unaffected. Affected users do not appear to have any special hardware/software configuration.
If a user is affected, then files are consistently never saved, even after retrying or uninstalling and re-installing the app (i.e., it's not a case of intermittent failure)
It's hard to get error information given (i) the rarity of the issue (ii) the fact that the logs that would reveal this are by definition not saved, and (iii) the apps don't require internet connectivity so there is no alternative communication channel.
Can anyone think of any reason why this might fail under Windows 8.1?
Are there non-ascii characters in path to appdata? CreateDirectory has unicode version, but fopen takes const char* strings as argument.
If I were you, I'd try to abstract away from OS-specific calls using something like boost or Qt. That should work, because Qt uses unicode string for opening files and Boost should have something similar (unsure about this one).
Also on windows compiler _wfopen may be present. It is the same as fopen, but takes wchar_t strings as argument. It should work for you, but you'll need a few ifdefs here and there.
You could also try setting current directory with function that supports unicode and then calling fopen, but I wouldn't call it a "clean" solution.
Anyway, when you run into problem that is related to system calls, then on machine with a problem you can monitor calls using something like process monitor. You could instruct user with a problem to do that and send you a log.
Why don't you use the Windows.Storage classes to work with filesystem? WinRT is recommended way to work with IO not legacy C API. I believe it is more robust approach and you could get more info about the cause from WinRT exception rather than from an unknown failure of old API.