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().
Related
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>
Background: I want to add my application to the windows context menu, I have done this by adding a new key to the windows registry:
HKEY_CLASSES_ROOT\*\shell\myapp
HKEY_CLASSES_ROOT\*\shell\myapp\command
And assigning the default value of the "command" key to the location of my exe, plus an extra argument:
value = "c:\users\john\myapp\myappexe.exe" "%1" arg1
It works, i can 'right click' any file and run my application with that file. The problem comes when I try to select multiple files, it opens as many windows of my app as files selected, I want to be able to handle all the inputs with one instance of my program.
I'm aware that this may be solved by creating shell extensions as posted here, here, here or here. Creating a full functional shell extension is out of the scope of my (small) project, and I haven't found tutorials which I can understand.
Problem: I'm looking a way around it, and I found that a program called from the "send to" folder in windows is able to handle multiple files, for example, if i put the execulable of this code (c++) in the C:\Users\john\AppData\Roaming\Microsoft\Windows\SendTo folder,
#include <iostream>
using namespace std;
int main(int argc, char* argv[]){
for(int i=0;i<=argc;i++){
cout << argv[i] << endl;
}
return 0;
}
...select a bunch of flies, and drag them into the executable, I'll get in one window the path of all the selected files (send to tutorial). How does this work? Can I use this behavior and apply it to my app?
One approach is to design your application so that any newly launched instance checks for a pre-existing instance (you could use a mutex to do this) and then forwards the command-line parameters to that one, encapsulated in a message of some kind. The original instance can then take the appropriate action.
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?
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);
I would like a process to always run at the user level. Either when it is launched by the installer (custom, not msi), which runs as at the administrator level, or when a user logs on. Looking around, I'm not sure this is possible.
The easiest way is to have 2 processes. One is normal user and it launches elevated/admin process. Then admin process can use IPC to ask normal user process to do things.
If you have no normal user process, then Raymond Chen documents:
Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the runas verb to ShellExecute or ShellExecuteEx.
Going the other way is trickier. For one thing, it's really hard to munge your token to remove the elevation nature properly. And for another thing, even if you could do it, it's not the right thing to do, because the unelevated user may be different from the elevated user.
The solution here is to go back to Explorer and ask Explorer to launch the program for you. Since Explorer is running as the original unelevated user, the program (in this case, the Web browser) will run as Bob. This is also important in the case that the handler for the file you want to open runs as an in-process extension rather than as a separate process, for in that case, the attempt to unelevate would be pointless since no new process was created in the first place. (And if the handler for the file tries to communicate with an existing unelevated copy of itself, things may fail because of UIPI.)
Okay, I know that Little Programs are not supposed to have motivation, but I couldn't help myself. Enough jabber. Let's write code. (Remember that Little Programs do little or no error checking, because that's the way they roll.)
#define STRICT
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
void FindDesktopFolderView(REFIID riid, void **ppv)
{
CComPtr<IShellWindows> spShellWindows;
spShellWindows.CoCreateInstance(CLSID_ShellWindows);
CComVariant vtLoc(CSIDL_DESKTOP);
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty,
SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
CComPtr<IShellBrowser> spBrowser;
CComQIPtr<IServiceProvider>(spdisp)->
QueryService(SID_STopLevelBrowser,
IID_PPV_ARGS(&spBrowser));
CComPtr<IShellView> spView;
spBrowser->QueryActiveShellView(&spView);
spView->QueryInterface(riid, ppv);
}
void GetDesktopAutomationObject(REFIID riid, void **ppv)
{
CComPtr<IShellView> spsv;
FindDesktopFolderView(IID_PPV_ARGS(&spsv));
CComPtr<IDispatch> spdispView;
spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
spdispView->QueryInterface(riid, ppv);
}
The GetDesktopAutomationObject function locates the desktop folder view then asks for the dispatch object for the view. We then return that dispatch object in the form requested by the caller. This dispatch object is a ShellFolderView, and the C++ interface for that is IShellFolderViewDual, so most callres are going to ask for that interface, but if you are a masochist, you can skip the dual interface and talk directly to IDispatch.
void ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters = nullptr,
PCWSTR pszDirectory = nullptr,
PCWSTR pszOperation = nullptr,
int nShowCmd = SW_SHOWNORMAL)
{
CComPtr<IShellFolderViewDual> spFolderView;
GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
CComPtr<IDispatch> spdispShell;
spFolderView->get_Application(&spdispShell);
CComQIPtr<IShellDispatch2>(spdispShell)
->ShellExecute(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(pszDirectory ? pszDirectory : L""),
CComVariant(pszOperation ? pszOperation : L""),
CComVariant(nShowCmd));
}
The ShellExecuteFromExplorer function starts by getting the desktop folder automation object. We use the desktop not because it's particularly meaningful but because we know that it's always going to be there.
As with the desktop folder view, the ShellFolderView object is not interesting to us for itself. It's interesting to us because the object resides in the process that is hosting the desktop view (which is the main Explorer process). From the ShellFolderView, we ask for the Application property so that we can get to the main Shell.Application object, which has the IShellDispatch interface (and its extensions IShellDispatch2 through IShellDispatch6) as its C++ interfaces. And it is the IShellDispatch2::ShellExecute method that is what we really want.
And we call IShellDispatch2::ShellExecute with the appropriate parameters. Note that the parameters to IShellDispatch2::ShellExecute are in a different order from the parameters to ShellExecute!
Okay, let's put this inside a little program.
int __cdecl wmain(int argc, wchar_t **argv)
{
if (argc < 2) return 0;
CCoInitialize init;
ShellExecuteFromExplorer(
argv[1],
argc >= 3 ? argv[2] : L"",
argc >= 4 ? argv[3] : L"",
argc >= 5 ? argv[4] : L"",
argc >= 6 ? _wtoi(argv[5]) : SW_SHOWNORMAL);
return 0;
}
The program takes a mandatory command line argument which is the thing to execute, be it a program or a document or a URL. Optional parameters are the parameters to the thing being executed, the current directory to use, the operation to perform, and how the window should be opened.
Open an elevated command prompt, and then run this program in various ways.
scratch http://www.msn.com/
Open an unelevated Web page in the user's default Web browser.
scratch cmd.exe "" C:\Users "" 3
Open an unelevated command prompt at C:\Users, maximized.
scratch C:\Path\To\Image.bmp "" "" edit
Edit a bitmap in an unelevated image editor.
Everybody is always looking for going around the other way. Anyhoo, this code project should help.
Assuming you know which user you want to run as, and have their password, the CreateProcessWithLogonW Function will do that.
There are many hacky ways to do this (Use the taskscheduler, inject into explorer.exe etc)
The only way to get the correct user (The one that started your program before UAC elevation (This might not be the same user as the shell/"login"/"session owner")) is to have the installer run two instances of itself, one "outer" instance that is not elevated, it mostly just starts the other instance by starting itself with ShellExecute[Ex] with the runas verb. When it comes time to start the medium/low level process, the elevated instance by some form of IPC tells the outer instance to start the new process.
It is a pain in the neck to implement, I would recommend just not having a run checkbox at the end of your installer.
similar to what Bill said, you can also use CreateProcessAsUser() API to do that.
First use LogonUser() and get an access token for the user that the process needs to run as . Here if the user belongs to administrators group (then you will get a split token if you pass the LOGON_FLAG as LOGON32_LOGON_INTERACTIVE). So if you need the elevated administrator token pass the flag as LOGON32_LOGON_BATCH.
With the token obtained above, you can call CreateProcessAsUser() passing the commandline and parameters.