Windows registry key AppliesTo does not work with Users directory - c++

What I am trying to do is to enable a customized context menu item to appear when users right-click folders in a specific directory.
So in HKEY_CLASSES_ROOT\Directory\shell I created my key (say with the name: MyProgram), and I created the subkey command that has the path to my program to run (say, "C:\Users\myuser\myApp\MyProgram.exe").
Until now it is ok and works correctly. But when I add the entry AppliesTo under HKEY_CLASSES_ROOT\Directory\shell\MyProgram and set it to C:\Users it does not work and the context menu item does not appear anymore!
Important Note: My windows language is German and the display name of Users folder in my windows explorer is Benutzer. Whenever I set AppliesTo to C:\Benutzer instead it works correctly, despite the fact that the command works with Users path properly! Alsowhen I echo %USERPROFILE% in the cmd it is printed in English as C:\Users\myuser and not Benutzer.
Is there a way to programmatically get the display path of Users or any folder in the system?
Please note: I cannot simply write Benutzer instead of Users in the path because the path can be dynamic. I am doing it programmatically and want my code to have consistent behavior in different machines with different languages. I am using C++ winreg API to set the registry values (for example: RegOpenKeyEx() and RegSetValueEx()).
Below is an export of the working version of the key:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\shell\MyProgram]
"AppliesTo"="C:\\Benutzer\\myuser"
[HKEY_CLASSES_ROOT\Directory\shell\MyProgram\command]
#="\"C:\\Users\\myuser\\path\\to\\MyProgram.exe\" \"%1\""

I found the solution. The localized name of folders can be obtained using the function SHGetLocalizedName, however it requires some additional work to get the string representation of the localized name. The following code snippet shows an example of how to do it.
PWSTR name = new WCHAR[100];;
PCWSTR folder = TEXT("C:\\Users");
UINT len=100;
int id=0;
HRESULT hr = SHGetLocalizedName(folder, name, len, &id);
if (SUCCEEDED(hr)) {
wprintf(L"%ls\n", name);
ExpandEnvironmentStrings(name, name, len);
HMODULE shell_handle = LoadLibraryEx(name, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
if (shell_handle) {
if (LoadString(shell_handle, id, name, len) != 0) {
wprintf(L"%ls\n", name);
}
FreeLibrary(shell_handle);
}
}
The result of the previous code will be Benutzer in case of German locale.
You will need to include the following headers as well
#include <wchar.h>
#include <Shellapi.h>

Related

How to get "INSTALLED" property in Custom Action DLL (MSI/Wix)?

In my custom DLL I need to check if product is being installed or uninstalled, and hence need to get value of "INSTALLED" property (just like in WiX script). Here is what I am doing in C++ DLL:
WCHAR propValue[MAX_PATH];
DWORD propValLen = MAX_PATH;
// MSIHANDLE msiHandle;
MsiGetProperty(msiHandle, L"INSTALLED", propValue, &propValLen);
propValue[propValLen] = 0;
But the outcome is always an empty string (for both installation and uninstallation)! How check if product is being installed or uninstalled?
Property name is case-sensitive, it is "Installed": https://msdn.microsoft.com/en-us/library/windows/desktop/aa369297(v=vs.85).aspx

How to get user home directory on Windows?

I'm developing a cross-platform library that is meant to load configuration files from a user's home directory. The idea is to automatically provide configuration parameters without editing code.
This library can be used in desktop apps or in daemons/services. In (I assume) most Unix environments I can use getpwuid() to get the home directory of the user. In Windows SO told me I could use SHGetKnownFolderPath but its documentation says its for desktop apps only. Is there a way to get this path, on Windows, for the user running a service?
For a console application the simplest method is to either retrieve the USERPROFILE environment variable or concatenate the values of the HOMEDRIVE and HOMEPATH environment variables.
Use the getenv() function in the standard library: https://msdn.microsoft.com/en-us/library/tehxacec.aspx
Example program:
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char** argv) {
printf("USERPROFILE = %s\n", getenv("USERPROFILE"));
printf("HOMEDRIVE = %s\n", getenv("HOMEDRIVE"));
printf("HOMEPATH = %s\n", getenv("HOMEPATH"));
return 0;
}
Output:
USERPROFILE = C:\Users\myuser
HOMEDRIVE = C:
HOMEPATH = \Users\myuser
What about this:
#include <shlobj.h>
WCHAR profilePath[MAX_PATH];
HRESULT result = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, profilePath);
if (SUCCEEDED(result)) {
// Do whatever you want with it
// For example:
// QString::fromWCharArray(profilePath)
}
I haven't tested it, though.
Note that what you receive is a wchar array (necessary to handle paths with special characters).
I think it's also possible to query special folders of other users than the current one by using the hToken parameter.
Also refer to the documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762181(v=vs.85).aspx
I used this code in another case where I wanted to obtain the start menu location. See this answer: C++: How do I create a Shortcut in the Start Menu on Windows
You could resolve %HOMEPATH% using ExpandEnvironmentStrings(...)
So do you want to get user home directory in service state?
- If you want that in service state, you have to use GetUserToken() to get user token then duplicate them for CreateprocessAsUser()
- Else I think it's better using SHGetSpecialPath(), SHGetTempPath().

How to tell if a folder is a subfolder of a special Windows folder?

If I have a CSIDL (or its newer alternative KNOWNFOLDERID) for a special folder (for the sake of this example, let's assume My Documents folder) and a DOS folder path, is there any way to tell that the path refers to a subfolder within the special folder?
EDIT 1: I implemented the following method after #RemyLebeau's suggestion, but it always sets my nIsParent to 0, or not a parent. What am I missing there?
int nCSIDL = CSIDL_PERSONAL;
LPCTSTR pDosPath = L"C:\\Users\\UserName\\Documents\\Subfolder1\\File.txt";
int nIsParent = -1; //-1=error, 0=no, 1=yes
LPITEMIDLIST pidlDocuments = NULL;
if(SUCCEEDED(SHGetFolderLocation(NULL, nCSIDL, NULL, 0, &pidlDocuments)))
{
LPITEMIDLIST pidl = ILCreateFromPath(pDosPath);
if(pidl)
{
nIsParent = ILIsParent(pidlDocuments, pidl, FALSE) ? 1 : 0;
ILFree(pidl);
}
ILFree(pidlDocuments);
}
EDIT 2: As for his 2nd suggestion to use SHGetPathFromIDList and then PathRelativePathTo on both DOS paths, it won't work for the following: My Documents on my computer is redirected to "\\SRVR-A\Home\UserName\Documents", which is also the "R:\Documents" folder with drive R: mapped to that Home share. PathRelativePathTo fails on those paths.
EDIT 3: If I had a folder Test folder in My Documents I could do this using my mapped drive R::
subst S: "R:\Documents\Test folder"
Which will technically make folder "S:\Test folder" a parent of My Documents as well, which is "\\SRVR-A\Home\UserName\Documents\Test folder".
That is why I was looking for a Shell-only, or a single API solution.
Everything in the Shell is represented by the ITEMIDLIST structure, even file system paths. Retrieve the ITEMIDLIST of the special folder using SHGetFolderLocation() or SHGetKnownFolderIDList(), then retrieve the ITEMIDLIST of the DOS path using SHParseDisplayName() or ILCreateFromPath(), then use ILIsParent() to check if the special folder's ITEMIDLIST is a parent of the DOS path's ITEMIDLIST.
Alternatively, retrieve the special folder's path using SHGetFolderPath() or SHGetKnownFolderPath(), then use PathRelativePathTo to check if the DOS path can be represented as a relative subfolder of the special folder's path without using any ".." components.
Create a function that gets a full path, name of the special folder, and just call
strstr on the full path with the name of the special folder and if it does not return NULL then it is a subfolder.
As for an API for it, I'm not aware of something like that but it could be possible.

C++ How do we make our application start on computer startup (and of course, after a user signed in)?

How do we make our application start on computer startup (and of course, after the user had signed in)?
And no, I am not making a virus.
Does registry editing sound like a good idea?
My OS is Windows 8.
However, I will try to make my application available for all possible Window OS.
The correct way to do this is simply to add a shortcut to your application's executable to the user's Startup folder. You do not need to (and should not) modify the registry.
Advanced users know how to do this manually already, but it may also be an option you want to provide as part of your installer and/or a configuration dialog in your application.
To do this from C++ code, you will need to do two things:
Retrieve the location of the current user's Startup folder.
This is accomplished by calling the SHGetKnownFolderPath function and specifying the KNOWNFOLDERID of the folder you're interested in. In this case, that would be FOLDERID_Startup.
Sample code:
std::wstring GetStartupFolderPath()
{
PWSTR pszPath;
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_Startup,
0, // no special options required
NULL, // no access token required
&pszPath);
if (SUCCEEDED(hr))
{
// The function succeeded, so copy the returned path to a
// C++ string, free the memory allocated by the function,
// and return the path string.
std::wstring path(pszPath);
CoTaskMemFree(static_cast<LPVOID>(pszPath));
return path;
}
else
{
// The function failed, so handle the error.
// ...
// You might want to throw an exception, or just return an
// empty string here.
throw std::runtime_error("The SHGetKnownFolderPath function failed");
}
}
Note, however, that while SHGetKnownFolderPath is the recommended function to use, it is supported only by Windows Vista and later. If you need to support older versions of the operating system, you'll need to call it dynamically on newer versions where it is available, and otherwise fall back to calling the SHGetFolderPath function. This one takes a different type of identifier, a CSIDL value. The one you want is CSIDL_STARTUP.
Create a shortcut to your application's executable.
This is accomplished using a different set of Shell API functions. I won't bother writing up sample code here because it's all quite well explained on MSDN already: Shell Links
Now you just connect the dots: when you create the shortcut to your application's executable, specify the user's Startup folder as its destination path.
The only other thing to be aware of is that there are actually multiple Startup folders. Each user has one, which is the one we retrieved above using FOLDERID_Startup. About 99% of the time, that's the one you want. Putting a shortcut to your app there will cause it to be launched automatically when that user logs on.
However, there is also a global Startup folder that is shared by all users. This one is identified by FOLDERID_CommonStartup (or CSIDL_COMMON_STARTUP) and requires administrative privileges to add items to. That makes sense, of course, because whatever you put in there is going to launch automatically when any user logs on to the computer. Only administrators can affect global behavior like this. And chances are, your app doesn't need this anyway.
Start menu
Simplest solution is to place .lnk of .bat file into the Start Menu\On startup folder. This is easiest and not too sneaky against the user.
Registry:
Another solution is creating the key in the registry keys:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run] //All users
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce] //All users once
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run] //Currend user
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce] //Current user once
This is not that transparent - so a bit more agressive against the user.
On windows you can put a shortcut to your application in the Startup folder, or you can implement it as a service.
And that "I am not making a virus" does make you sound guilty... Maybe it is a keylogger? ;)
There are a lot of ways, but they all depend on your OS. For windows take a look at the "Task Schedualer" under "Administrative tools" in the control panel.
Maybe something like this? Note, this code snippet is not written by me.
#include <windows.h>
void Reg() {
::HKEY Handle_Key = 0;
::DWORD Dispoition = 0;
::RegOpenKeyEx( HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0,
KEY_ALL_ACCESS,
&Handle_Key );
const unsigned char Path[ MAX_PATH ] = "C:\\Windows\\YourProgramName.exe";
::RegSetValueEx( Handle_Key, "My Directory", 0, 1, Path, sizeof( unsigned char ) );
};
What do you guys think?

How to properly use %USERPROFILE% inside code?

Is my code correct? It seems can compile but does not work properly..
CString testing = _T(" --url=") + cstring + _T(" --out=%USERPROFILE%\\snapshot.png");
I want to point it to user's folder..but still cannot work.
The answer is that you don't use environment variables at all. Rather, you use the shell functions specifically designed to retrieve the path of special folders.
On Windows Vista and later, that function is SHGetKnownFolderPath. It takes KNOWNFOLDERID values to identify the folder whose path you wish to retrieve. In your case, that would be FOLDERID_Profile.
If you need to target earlier versions of Windows (such as XP), you will need to use the SHGetSpecialFolderPath function, instead. It takes a CSIDL value identifying the folder whose path you wish to retrieve. Again, in your case, that would be CSIDL_PROFILE.
Of course, you should never store data directly in the user's profile folder. So hopefully the bit of code that you've shown is for demonstration purposes only. Applications should only create files in the specific locations under the user profile folder, designed for application data storage.
These locations are CSIDL_APPDATA or CSIDL_LOCAL_APPDATA. If you are creating data that the user should be able to modify and should treat as his/her own, then it would be appropriate to store that data in the user's documents folder (CSIDL_MYDOCUMENTS).
More usage information is available in my answer here.
Sample code:
TCHAR szFolderPath[MAX_PATH];
if (!SHGetSpecialFolderPath(NULL, szFolderPath, CSIDL_APPDATA, FALSE))
{
// Uh-oh! An error occurred; handle it.
}
Or, using MFC's CString class:
CString buffer;
BOOL bRet = SHGetSpecialFolderPath(NULL, buffer.GetBuffer(MAX_PATH), CSIDL_APPDATA, FALSE);
buffer.ReleaseBuffer();
if (!bRet)
{
// Uh-oh! An error occurred; handle it.
}
As Cody suggested, it's better to use the SHGetSpecialFolderPath function. However, you could use the GetEnvironmentVariable function to get that and other variables set in the system.
TCHAR szBuf[MAX_PATH] = {0};
::GetEnvironmentVariable(_T( "USERPROFILE" ), szBuf, MAX_PATH);