GetPrivateProfileString inconsistent behaviour when compiled with different Visual Studio versions - winapi

We have a c++ program compiled long ago that uses GetPrivateProfileString. The lpFileName arg is just the name of an ini file, so the expected behaviour according to the documentation is that it should look for the file in the Windows directory:
lpFileName [in]
The name of the initialization file. If this parameter
does not contain a full path to the file, the system searches for the
file in the Windows directory.
Recently the program was recompiled (with no code changes) and redeployed, which resulted in the program not being able to read the ini file anymore.
Comparisons between the two programs with Process Monitor showed that the old program was attempting to look for the file in %HOMEPATH%\Windows rather than C:\Windows.
As a test I compiled the following code with Visual Studio 2012 and ran it on a 2003 server, passing in some random file name on the command line:
void wmain(int argc, wchar_t* argv[])
{
wchar_t *appName = L"xxx";
wchar_t *keyName = L"yyy";
wchar_t *defValue = L"default value";
wchar_t buffer[1024];
DWORD bufferSize = sizeof(buffer);
wchar_t *fileName = argv[1];
DWORD result = GetPrivateProfileString(
appName,
keyName,
defValue,
buffer,
bufferSize,
fileName
);
wprintf(L"result: %d\n", result);
}
Using Process Monitor to monitor for that file name, I could see it looked for the file in C:\Windows - as expected. However when the exact same code is compiled in VS 2003 and run on 2003 server, it looks for the file in %HOMEPATH%\Windows.
Back to the original, non-recompiled program. I noticed that on a Windows 2008 server, if I simply double click the exe, it looks for the file in %HOMEPATH%\Windows. However if I right-click the exe and Run As Administrator, it looks for the file in C:\Windows! But on Windows Server 2003 it always looks in %HOMEPATH%\Windows.
This isn't consistent with the documentation!

The behavior is almost certainly related to Terminal Server. When you have Terminal Server (later renamed to Remote Desktop Services in Server 2003 and later SKUs) installed, any attempts to access the system (Windows) directory get redirected to a user-local Windows directory (%HOMEPATH%\Windows).
This is documented under the "remarks" section of the documentation for GetWindowsDirectory(), which is presumably what GetPrivateProfileString() is using under the hood when you don't specify a full path and it falls back to its 16-bit Windows behavior of looking in the system directory.
If you are linking your binary with the IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE flag (set in the Project Property Pages under Linker → System → Terminal Server → /TSAWARE:YES), then it assumes you are aware of this behavior and understand that Terminal Servers are different than normal client machines. Thus, it gives you the actual Windows directory, instead of the private Windows directory.
Otherwise, if you're linking with /TSAWARE:NO, then you get the backwards-compatibility behavior that redirects requests for the system Windows directory to the private user-local Windows directory.
Presumably the default setting of this flag changed some time between VS 2003 and VS 2012. I don't know when exactly, but modern versions of Visual Studio do assume you are Terminal Server-aware.
Raymond Chen blogged about this once: When will GetSystemWindowsDirectory return something different from GetWindowsDirectory?
Anyway, this is all just academic curiosity. Your program is buggy, and it was long before you discovered this curiosity. An application should never write data into the Windows directory, whether the system Windows directory or a pseudo-user-local Windows directory. There are two ways of getting around this:
Stop calling GetPrivateProfileString() altogether and, as David Heffernan suggested, use a third-party INI-file parser. Google will turn up several results. simpleini looks like a good choice.
If you aren't ready to make the change yet, you should at a minimum be specifying a full path to your INI file. That will avoid the 16-bit Windows behavior of dumping files in the system directory, but you're still at the mercy of the other crustiness of this ancient API.

Related

ZeroMQ Windows Installer NSIS Error

I want to install ZeroMQ for Ratchet/PHP and I downloaded the installer from http://zeromq.org/distro:microsoft-windows. But I keep getting "NSIS error" whenever I try to install it.
It immediately shows after I run the installer. Different versions, x64 or x86 ones, none of them works. This problem only shows up with ZeroMQ installers.
Does anyone have any idea why does this happen?
P.S. I use Windows 8.1. (Up to date)
This question does not belong here on Stackoverflow but since you posted it here anyway I will give you the technical answer: NSIS needs to open a file handle to itself so it can read the compressed data, it does this by calling GetModuleFileName to get the path and CreateFile to open the file. If this step fails it displays the _LANG_CANTOPENSELF message ("Error launching installer", the text in your screenshot).
A) GetModuleFileName can return a "incorrect" path when filesystem redirection is involved, this is most commonly seen when psexec is used to execute the program from the Windows directory on a remote 64-bit computer and this is probably not the case here?
B) The call to CreateFile can fail, this is most often caused by Anti-Virus software holding a lock/denying access to the file. Try to disable/uninstall any 3rd-party Anti-Virus software...

How to make a exe that is removed from the servers open files list when running (started from a share)

I need to make a exe that will be started from a Windows server share. As soon as the application is running it should disappear from the servers open files list.
For example I have this simple Delphi source as a test - it compiles to a small 28k exe file that simply waits for user input when invoked. While the application is running it appears on the servers open files list. I already tried PEFlags setting IMAGE_FILE_NET_RUN_FROM_SWAP and IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP:
program RunFromShare;
Uses
Windows;
{$APPTYPE CONSOLE}
{$SetPEFlags IMAGE_FILE_NET_RUN_FROM_SWAP} // no exe file open on network share?
{$SetPEFlags IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}
begin
WriteLn('Waiting for [Enter] key');
ReadLn;
end.
It seems like IMAGE_FILE_NET_RUN_FROM_SWAP (or IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) tells Windows that it should load the whole EXE into memory (backed by the swap file). This doesn't mean it is copied and then run from the local disk, it just prevents page faults from happening later which cause access to the share (possibly after dismount; see such a case here). Which in turn means, the file on the network share is still open as long as the share is connected and the file running.
MSDN says this about IMAGE_FILE_NET_RUN_FROM_SWAP:
If Image is on Net, copy and run from the swap file.
I would interpret copy as copy to memory, not as copy to disk.
So if nobody does the job for you just do it yourself: copy your file and run it :)

Opening %SystemRoot%\system32\calc.exe from a 32-bit process redirects to another file. Which, why and how?

I'm currently writing some test code in C++ that messes around with PE files to understand its file format structure. My project is set to compile to 64 bit. In my code I open %SystemRoot%\system32\calc.exe and read the IMAGE_DOS_HEADER and IMAGE_NT_HEADERS structures. At the same time I have the same calc.exe opened in Notepad++ with the hex editor plugin. I compared the values my code reads with Notepad++ and noticed they were different. I copied calc.exe from System32 to C:\Temp\calc.exe, and now the values match.
Notepad++ seems to be a 32 bit application (haven't checked the PE file, but since it's installed to Program Files (x86) by default, it seems to be a safe assumption to make).
Is this WinSxS at work? Or what else is causing this? And which file is actually fed to 32-bit applications opening %SystemRoot%\system32\calc.exe?
Just curious. Thanks in advance for any light shed on this.
Yes, this is the WOW redirector. You'll see that there is a calc.exe in C:\Windows\SysWOW64 as well. That's the file that is opened when you use the %SystemRoot%\System32\calc.exe path.
This can be temporarily disabled to access the 64-bit version of the file with Wow64DisableWow64FsRedirection
More details can be found at File System Redirector
If I remember well, when a 32bit apps tries to open system32 directory, it's automatically redirected to syswow64 dir.
Disabling WowFs redirection is unnecessary and sometimes is not even an option (for instance, when you are attempting to get Notepad++ to open files in the system32 directory). You can use the virtual directory %windir%\Sysnative instead of %windir%\System32 (you will not see it in explorer, but you can type it in the address bar)
WOW64 is implemented in three DLLs: wow64.dll, wow64cpu.dll, and wow64win.dll (and 32-bit NTDLL). Redirection (among other things) is implemented in wow64.dll, CPU emulation / helper routines in wow64cpu.dll, and wow64win.dll contains thunks to win32k.sys (the kernel mode driver responsible for the windows GUI).

VB6 Application fails 8.3 path conversion on a single PC

I have a VB6 desktop application that is deployed on well over 1200 desktops. The devices throughout are a mix of Windows XP SP2 and SP3 systems. All but one of these PCs (XP SP2) is able to successfully decipher the DOS 8.3 path (ie C:\PROGRA~1\DATFOL~1\Config\) that is used in an .ini file related to this application. This particular PC errors out with a message: "Run-time error '76': Path not found".
The string is obtained from the .ini file using the
GetPrivateProfileString function. (The string is not hard-coded into the application - it is obtained from an ini file).
Since there is only one machine having the problem, I'm looking towards some configuration value on that device as being the root cause. I looked at the NtfsDisable8dot3NameCreation setting in the registry to see if this might cause the issue, but I have been unable to reproduce the problem on any other machine when changing this setting.
Anybody have any thoughts or perhaps another direction I could take?
Don't use hard-coded paths or short filenames. The Program Files folder might not be on the C: drive, might not be named Program Files, and even if it is, might not have a short filename of PROGRA~1 (and the same for DATAFOL~1). Write the install path to an INI file or the registry during installation and read+use that in your program.
If someone was gimping around and made a temp/backup/testing \DataFolder_Temp, deleted the original then renamed, the short path would be DATAFOL~2.
Delete the directory and recreate it.
check the PC. The PROGRA~1 or DATFOL~1 might actually be ~2 instead. Put the 8.3 name used in your code into explorer and see what IT tells you.

Why is "This program cannot be run in DOS mode" text present in .dll files?

Recently I opened a .dll file produced by Visual Studio 9 while compiling a native C++ DLL project and was surprised to see the "This program cannot be run in DOS mode" text near the beginning.
Why have this text in .dll files?
A dll is very much like an executable with a different extension. The text you saw is part of the 'standard' executable header on windows. It is (was) used to gracefully abort the attempt to run a windows executable from DOS.
The Portable Executable format specification states the following:
The MS-DOS stub is a valid application that runs under MS-DOS. It is
placed at the front of the EXE image. The linker places a default stub
here, which prints out the message “This program cannot be run in DOS
mode.” when the image is run in MS-DOS. The user can specify a
different stub by using the /STUB linker option.
At location 0x3c, the stub has the file offset to the PE signature.
This information enables Windows to properly execute the image file,
even though it has an MS-DOS stub. This file offset is placed at
location 0x3c during linking.
Win32 programs run from DOS mode (ie, single user, no graphics) print that text. DLLs probably print that message too if you try to use them without Windows running.

Resources