Cannot expand shortcuts using SHBrowseForFolder function in Windows 8.1 - c++

I have 2 cpp GUI applications P1 and P2. P1 can be opened by two ways.
Through directly double clicking or using command line
Using P2's GUI, implemented using the CreateProcess function (https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx), using default flags.
P1 has a file selection box, which is implemented using SHBrowseForFolder (https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115(v=vs.85).aspx), using default flags.
In windows 8.1 the file selection box shows several shortcuts under 'This PC' dropdown (Desktop, Pictures, Downloads etc).
If I use method 1 to open P1 these shortcuts expand fine.
But if I use method 2, while expanding these shortcuts it shows the following error in a dialogue box.
C:\Windows\system32\config\systemprofile\Desktop refers to a location that is unavailable. It could be on a hard drive on this computer or on a network. Check to make sure that the disk is properly inserted, or that you are connected to the Internet or your network, then try again. If it still cannot be located, the information might have been moved to a different location
Please help guys. Stuck in this for last two days.
EDIT: Just found out the following.
Process P2 is opened by a system level process in the start up. If I close P2 and reopens it as a user, error is gone ,the file selection works fine.

Found out the issue. I was creating processes using CreateProcess function with default flags. One of the argument is environmental block, for which I passed NULL. So the new process will be created with parent processes environmental variable. So in the startup P2 is created by a system level process with environmental variables of system. Ans P2 will create P1 with same environmental variables. So when expanding shortcuts P1 will look for the USERPROFILE variable in the environmental block, which will be systemprofile.
When I open P2 as a user environmental variables are set correctly and shortcuts expands properly.
May this be helpful for someone in future.

Related

Set current user environment variable from c++ code visible to other process like cmds

I need to set a local environment variable for current user and it shoukd be visible to other processes like a new command prompt. I need it for windows. I have tried options like putenv and editing the registry from C++ code but the new cmd prompt see the old values. Primarily i need to edit PATH variable along with few custom env variables. Will appreciate if i can get a working sample code.
Please note that the environment variable need to persist past program execution.
My requirement is for windows. I even tried running setx from C++ code and it works fine but for PATH variable it trims it down to 1024 character and i lose the update. Is there a workaround to this?
IF my wording looks confusing about the requirement. I need exactly same behavior as if i am using setx.
Thanks in advance.
If you start Cmd.exe from your process you can control its environment. The environment variables are inherited from the parent process. They can also be overridden when you call CreateProcess.
If you change the users/system environment configuration in the registry(HKCU\Environment/HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment) and log off/reboot then the first process will use these new defaults.
If you update the registry you can tell other applications to refresh their environments without logging off by broadcasting a message:
BroadcastSystemMessage(0, 0, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment"));
In reality it is only Explorer.exe that reacts to this message but that is enough to affect new applications started from the taskbar/start menu.
The setx command is actually an executable that sets values in the registry. If you are looking to simulate the behavior where you can set an environment variable that will last longer than the current process you will need to write it to the HKCU\Environment key. The HKCU is for the the current user and can be written to without elevated permissions.
Use RegEdit.exe or reg.exe query HKCU\Environment to view the current settings. From C/C++ you can use the Registry functions. If you can, I recommend using the ATL CRegKey class as it follows RAII and ensures handles are properly cleaned up.

Set child process name in Windows?

I have process, that run multiple times child processes (each without GUI), and it needs to set to all child processes different "names" and "description" for Task Manager. Is it possible using Win API? I can't find solution for Windows family.
I look to WriteProcessMemory, but it looks like form of cheating, even if there is possible to change name.
Solution with copying .exe file, run it, and after process finished - deleting it - is even more cheating.
There may be exist solution using start process from memory (so, I load exe file to memory, then start it from there), but it also looks bad and I'm not sure I will be able to change name.
I hope there have to be solution to set process name to my own child process, isn't it?
You can't change the "Image Name" that appears in Task Manager. As you found, Windows obtains that from the name of the file that is actually executing and the only way to have something different appear is to actually run a file with a different name (such as by copying your executable).
If you are targeting newer versions of Windows, Task Manager can display a "Command Line" column (View -> Select Columns). You can make your processes distinguishable by including a particular flag in the command line, or by setting the first token of the lpCommandLine argument to something unique - you just have to also set the lpApplicationName argument to the actual executable you want to run so the loader can find it.
For example:
BOOL ret1 = CreateProcess(
"c:\\program\\worker.exe",
"worker1.exe infile outfile",
...);
BOOL ret2 = CreateProcess(
"c:\\program\\worker.exe",
"worker2.exe infile outfile",
...);
These will have identical Image Name values in Task Manager, but they will be distinguishable by the Command Line values.

CreateFile to open an MS-DOS device created in another terminal session

I have a (sort of a filter) driver that should communicate with user-mode components. It creates a device object by calling IoCreateDevice, and then it creates a so-called MS-DOS symbolic link for it by IoCreateSymbolicLink, to make it possible to access it from user-mode code (by CreateFile). This is a standard technique more-or-less. The driver creates a symbolic link of the form \DosDevices\mydevicename, whereas the user-mode code opens a file named \\.\mydevicename.
Now, the problems start when the driver creates a device in the context of a terminal server session. The created symbolic link actually belongs to the local session directory, whereas my user-mode service runs under system account in "zero session", and it "sees" symbolic links that belong to global directory.
It's mentioned in the documentation that usually there's no problem, because drivers mostly create device objects in the context of DriverEntry or AddDevice functions, which are guaranteed to run under system account. But my case is different. And I don't want to change this, what I really need is to be able to create/destroy device objects in the context of arbitrary thread, belonging to any session.
According to the documentation there's a way to solve this. The driver may insist to create a symbolic link belonging to the global directory, by naming it this way: \DosDevices\Global\mydevicename. Moreover, if the user-mode code happens to run under some account, it may also insist to look for the link in the global directory, by naming the file this way: \\.\Global\mydevicename. Though this is not required usually, if the symbol doesn't exist in local directory, it's automatically checked in the global directory.
I've tried this trick: it doesn't work for me. I'm using Windows 2008R2, 64-bit. No success so far. I'm consistently able to open devices created in the system account, but unable to open devices created in another sessions (the error code is "file not found"). I've tried all the combinations and variations of specifying \Global in kernel/user mode - so far the result is the same.
This makes me suspect that there's one more level of symbol isolation. Perhaps using \Global creates a symbolic link that is global session-wide, but still not system-wide.
Does this make sense? Is there a way to create a system-wide symbolic link? Or is there a way to open a file whose symbolic link belongs to another session?
EDIT:
Thanks to #Hans Passant. I've tried WinObj utility to actually see which devices and symbolic links the driver actually creates.
Everything seems ok at the first glance. I see all my devices under \Device directory, and all the symbolic links are under \GLOBAL??. Symbolic links point to correct device names.
One thing is weird however. Trying to see the device properties from within WinObj: for devices created in zero session this works ok, but for devices created in other sessions WinObj responds with an error:
Error opening \Device\mydevicename: The system cannot find the file specified.
So, it displays this device object in its list, but OTOH it's "not found" when trying to open it.
Very strange. But this explains my problem. But this is really strange.
Any ideas? Thanks in advance.
There is a \Sessions\N\DosDevices\ path, where N is a session number. I didn't try that but it should work.
Also I noticed that subst and network drive mapping create symbolic links in \Sessions\0\DosDevices\ID\, where ID is a session id. So you can check that path too.

How to read explorer folder address?

I use AVG and it recently detected a virus. It has before ;) but this was the first time I noticed this.
When I went into the folder containing the virus, AVG immediately, automatically, detected the virus without me even clicking on the application. So I though how could it know a virus was there even when I did not even click (single click) on it.
The only possible answer is that it continuously checks the explorer folder location of all windows and scans all the files in the folder. But how does it see what folder is being viewed by me?
Please explain (if possible) with a C program that does what ever AVG did.
Also : I use Windows if that helps.
When you open a folder a bunch of file system operations is executed (you can use tools like FileMon or ProcMon to take a look at this). Your AV software monitors file access.
There are multiple ways to do this monitoring, e.g. Filter Drivers - you can find a great sample at http://www.codeproject.com/Articles/43586/File-System-Filter-Driver-Tutorial
So when you opened the folder, AV software noticed that you opened a directory, consulted its own data, and informed you about the virus.
I say 'consulted its own data', as AV tools usually don't scan files on access - they do it when the files are written to, as it doesn't make sense to scan files which were marked as clean if they haven't changed since the last scan.
Most virus scanners operate on the principle of API hooks/filters. Whenever windows needs to process a command, like opening a folder, clicking a window, executing a file, etc it generates an api call along with some information like the window coordinates clicked, or a string representing a file. Other programs can request a hook into one or more of these functions which basically says 'instead of executing this function, send it to me first, then I might send it back'. This is how many viruses work (preventing you from deleting them, or copying your keystrokes, for example), how many games/apps work (keyboard, joysticks, drag-and-drop), as well as malware detectors and firewalls.
The latter group hooks the commands, checks any incoming ones to see if they're on the level, then either allows them to resume or blocks them. In this example, opening the folder likely triggered a syscall to parse a directory, and the scanner parsed it too (eg 'realtime protection'). To view all of your hookable functions as well as what is using them, google for a free program called 'sanity check' (previously called 'rootkit hook analyzer'). Most of the red entries will be from either windows firewall or avg, so don't worry too much about what you find.

Programmatically adding a directory to Windows PATH environment variable

I'm writing a Win32 DLL with a function that adds a directory to the Windows PATH environment variable (to be used in an installer).
Looking at the environment variables in Regedit or the Control Panel after the DLL has run shows me that my DLL has succeeded in adding the path to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment and HKEY_CURRENT_USER\Environment.
But when I start up a new Command Prompt (after running the DLL), the directory I added does not show up in the output of echo %PATH% and I can not access the executable that lives in that directory by typing its name.
I think my program is not doing a good job of notifying the system that the PATH has changed, or maybe it is notifying them before the change has fully taken effect. I read an article by Microsoft that says to broadcast the WM_SETTINGCHANGE message after changing an environment variable, and I am doing that with this code:
DWORD result2 = 0;
LRESULT result = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM)"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
if (result == 0){ /* ... Display error message to user ... */ }
The order of my calls is: RegCreateKeyEx, RegSetValueEx, RegCloseKey, SendMessageTimeout
If I press "OK" in the Control Panel "Environment Variables" window, the changes made by my DLL to the PATH show up in newly-created command prompts, so there is something that the Control Panel is doing to propagate PATH changes; I want to figure out what it is and do the same thing.
Does anyone know what I should do?
I'm running 64-bit Windows Vista but I want this to work on all Windows XP, Vista and Windows 7 operating systems.
Update: The problem with the code I posted above is that I did not put the L prefix on the "Environment" string. Although it does not say it explicitly anywhere in the Microsoft documentation that I can find, the LPARAM needs to be a pointer to a WCHAR string (2-byte characters) as opposed to a CHAR string, which is what Visual Studio's compiler generates by default when I write a string literal. The solution to my problem was to change "Environment" to L"Environment". (I thought I already tried that before posting this question, but apparently I didn't try it correctly!) But anyone who wants a complete C++ solution for this task should look at Dan Moulding's answer.
It turns out there really isn't anything new under the sun. This has already been done before, at least once. By me. I created a DLL very much like what you describe for exactly the same purpose (for use in modifying the path from an NSIS installer). It gets used by the Visual Leak Detector installer.
The DLL is called editenv.dll. The source is available at github. I just tested the installer and it updated the system PATH environment variable, no problem. Based on what you've written, I don't see anything that stands out as being wrong. I also don't see anything obvious that's missing. But it may be worth a look at the editenv.dll source (you'd be most interested in EnvVar::set() in EnvVar.cpp, and possibly the pathAdd() and pathRemove() C APIs in editenv.cpp).
I have a program which calls the same Win32 API to yours to update the environment, and it works fine.
One thing to be careful of is how you are opening up the command prompt.
If you open up the command prompt by doing this:
Start -> Run -> cmd.exe
then the environment in the prompt shows that the new variable is set.
However, I also have a programmable function key on my keyboard which I have set to run the cmd.exe process. If I open a command prompt via that function key and then type env, it doesn't show the variable as being set.
I'm not sure why it works differently, but it must have something to do with the way the cmd.exe process is launched (although both are running under my user name, not SYSTEM).
How are you opening up the command prompt?