Append to registry without expanding variables - c++

I'll just start off by saying that I'm by no means an expert in C++, so any pointers/tips are greatly appreciated.
I'm having some difficulties reading and writing from registry, while keeping variables, i.e. not expanding them.
I'm trying to append my executable path to the PATH environment variable (permanently), but I'm running into all sorts of problems.
I have a long PATH variable that makes it impossible to edit without using a program or regedit, so I opted to create an "OldPath" variable with my current PATH variable, and change my PATH variable to %OldPath%. This has worked great, but now when I try to write to it with C++, %OldPath% gets expanded into the old path variable and as a result, the variable gets truncated.
I tried first with normal strings, but I ended up with what looked like Chinese symbols in my PATH variable, so I changed it to wstring. Now I get normal strings, but the string gets truncated at 1172 characters.
My desired end result is that PATH is set to %OldPath;<current_path>
get_path_env()
inline std::wstring get_path_env()
{
wchar_t* buf = nullptr;
size_t sz = 0;
if (_wdupenv_s(&buf, &sz, L"PATH") == 0 && buf != nullptr)
{
std::wstring path_env = buf;
free(buf);
return path_env;
}
return L"";
}
set_permanent_environment_variable()
inline bool set_permanent_environment_variable()
{
const std::wstring path_env = get_path_env();
if (path_env == L"")
{
return false;
}
std::wstringstream wss;
wss << path_env;
if (path_env.back() != ';')
{
wss << L';';
}
wss << std::filesystem::current_path().wstring() << L'\0';
const std::wstring temp_data = wss.str();
HKEY h_key;
const auto key_path = TEXT(R"(System\CurrentControlSet\Control\Session Manager\Environment)");
if (const auto l_open_status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_path, 0, KEY_ALL_ACCESS, &h_key); l_open_status == ERROR_SUCCESS)
{
const auto data = temp_data.c_str();
const DWORD data_size = static_cast<DWORD>(lstrlenW(data) + 1);
// ReSharper disable once CppCStyleCast
const auto l_set_status = RegSetValueExW(h_key, L"PATH", 0, REG_EXPAND_SZ, (LPBYTE)data, data_size);
RegCloseKey(h_key);
if (l_set_status == ERROR_SUCCESS)
{
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>("Environment"), SMTO_BLOCK, 100, nullptr);
return true;
}
}
return false;
}
In other words, I want to find the equivalent of the following in C#:
var assemblyPath = Directory.GetParent(Assembly.GetEntryAssembly()!.Location).FullName;
var pathVariable = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("PATH", $"{pathVariable};{assemblyPath}", EnvironmentVariableTarget.Machine);
EDIT: I actually haven't tested if that code expands the value or not, but I want to do as the C# code states and if possible, not expand the variables in the path variable.

You are trying to change the PATH setting in the registry. So one would expect that you would get the current PATH setting from the registry, change it, and set the new PATH setting in the registry.
But you are not getting the PATH setting from the registry. You are getting the PATH variable from the environment instead. Why is that? The environment is controlled by the setting in the registry, but it's not that setting. In particular, you noticed that the environment variables set in the registry get expanded before they actually go into the environment.
It's like changing the wallpaper by taking a screenshot of the desktop, changing the screenshot, then setting it as the wallpaper, then asking how to remove the icons from the wallpaper.
The solution is to simply get the current unexpanded PATH setting from the registry instead of the expanded one from the environment.

Related

Printing different documents silently in C++

I have folder of different documents like: pdf, txt, rtf, images.
My case is to send all documents to the printer (print it). Used framework is MFC and WinAPI. Current implementation has dialog box for choose documents and another dialog for choose printer.
Then question appears, how to print it all? Do I need to convert every documents to PDF, then merge it and print one pdf document? I will appreciate any advice in that field.
void CMultipleDocsPrintTestDlg::OnBnClickedButton1()
{
TCHAR strFilter[] = { _T("Rule Profile (*.pdf)||") };
// Create buffer for file names.
const DWORD numberOfFileNames = 100;
const DWORD fileNameMaxLength = MAX_PATH + 1;
const DWORD bufferSize = (numberOfFileNames * fileNameMaxLength) + 1;
CFileDialog fileDlg(TRUE, _T("pdf"), NULL, OFN_ALLOWMULTISELECT, strFilter);
TCHAR* filenamesBuffer = new TCHAR[bufferSize];
// Initialize beginning and end of buffer.
filenamesBuffer[0] = NULL;
filenamesBuffer[bufferSize - 1] = NULL;
// Attach buffer to OPENFILENAME member.
fileDlg.GetOFN().lpstrFile = filenamesBuffer;
fileDlg.GetOFN().nMaxFile = bufferSize;
// Create array for file names.
CString fileNameArray[numberOfFileNames];
if (fileDlg.DoModal() == IDOK)
{
// Retrieve file name(s).
POSITION fileNamesPosition = fileDlg.GetStartPosition();
int iCtr = 0;
while (fileNamesPosition != NULL)
{
fileNameArray[iCtr++] = fileDlg.GetNextPathName(fileNamesPosition);
}
}
// Release file names buffer.
delete[] filenamesBuffer;
CPrintDialog dlg(FALSE);
dlg.m_pd.Flags |= PD_PRINTSETUP;
CString printerName;
if (dlg.DoModal() == IDOK)
{
printerName = dlg.GetDeviceName();
}
// What next ???
}
You could make use of ShellExecute to do this. The parameter lpOperation can be set to print. To quote:
Prints the file specified by lpFile. If lpFile is not a document file, the function fails.
As mentioned in a similar discussion here on StackOverflow (ShellExecute, "Print") you should keep in mind:
You need to make sure that the machine's associations are configured to handle the print verb.
You referred to pdf, txt, rtf, images which should all be supported I would think by this mechanism.
ShellExecute(NULL, "print", fileNameArray[0], nullptr, nullptr, SW_SHOWNORMAL);
The last parameter might have to be changed (SW_SHOWNORMAL). This code would be put in a loop so you could call it for each file. And note that the above code snippet has not been tested.

Get string of temp file and add custom folder

I'm new to C++ and I have been thinking how to get string of the user's temp folder on Windows and append to it a custom folder name.
e.g "\Users\user\AppData\Local\Temp\NameOfCustomFolder"
I've tried this:
std::string szOutput{};
TCHAR path_buf[MAX_PATH];
DWORD ret_val = GetTempPath(MAX_PATH, path_buf);
if (ret_val > MAX_PATH || (ret_val == 0))
{
std::cout << "GetTempPath failed";
}
else
{
szOutput = path_buf, "NameOfCustomFolder\\file.exe"
}
return szOutput;
This does not perform the string concatenation you want
szOutput = path_buf, "NameOfCustomFolder\\file.exe";
Rather do
szOutput = std::string{path_buf} + "NameOfCustomFolder\\file.exe";
If you can use C++17, I would suggest std::filesystem::path::append
It will take care of the path separators, and will make it easy to go cross-platform.

Loading of textfile resource in Visual Studio loads more than it should

As seen in various other posts, I have added a few text files as resources in a Visual Studio 2013 project in the following way: After right-clicking on the project and selecting Add -> Resources I added the following lines to the generated file resource.h:
#define MY_TEXTFILE 256
#define MY_CONFIG_FILE_RELEASE 4313
#define MY_CONFIG_FILE_DEV1 4314
#define MY_CONFIG_FILE_DEV2 4315
Then, I added the following lines to the .rc file:
MY_CONFIG_FILE_RELEASE MY_TEXTFILE "configFiles/releaseConfig.properties"
MY_CONFIG_FILE_DEV1 MY_TEXTFILE "configFiles/devConfig.properties"
MY_CONFIG_FILE_DEV2 MY_TEXTFILE "configFiles/dev2Config.properties"
The content of those files is just simply one line, e.g. for the devConfig.properties it would be
# DEV1 CONFIG
To test the loading mechanism, I use the following directly within main
int main(int argc, char *argv[]) {
const char* data = NULL;
loadTextFileResource(MY_CONFIG_FILE_DEV1, data);
return 0;
}
where the loadTextFileResource is the following:
bool loadTextFileResource(int inName, const char*& outData) {
HMODULE _handle;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&someFunction, &_handle);
if (_handle == NULL) return false;
HRSRC _rc = FindResource(_handle, MAKEINTRESOURCE(inName), MAKEINTRESOURCE(MY_TEXTFILE));
if (_rc == NULL) return false;
HGLOBAL _rcData = LoadResource(_handle, _rc);
if (_rcData == NULL) return false;
LPVOID _rcDataLocked = LockResource(_rcData);
if (_rcDataLocked == NULL) return false;
DWORD _size = SizeofResource(_handle, _rc);
if (_size == 0) return false;
outData = static_cast<const char*>(_rcDataLocked);
std::cout << "Loaded: " << outData << std::endl;
return true;
}
The output of this little program is:
Loaded: # DEV1 CONFIG
P# DEV2 CONFIG
PADPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADD
INGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADD
INGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADD
INGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADD
INGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING
Note that the two lines in the beginning are the content of not only MY_CONFIG_FILE_DEV1 but also MY_CONFIG_FILE_DEV2 even though I only requested the former. The same happens when I request to load MY_CONFIG_FILE_RELEASE (i.e. all three files are loaded into outData). So it seems that somehow all "subsequent" resources are loaded together with the one I'm requesting. What is going on here exactly and why is my loadTextFileResource function not doing what I'm expecting, namely load only the content of the resource I'm requesting?
Also: What is the "P" doing in front of "#DEV2 CONFIG" (which is the content of the respective file)? Should I somehow clean the data that was loaded?
Note that this loading mechanism should also work if the project is compiled as a DLL.
You assume that the text file data is zero-terminated by assigning the pointer to the beginning of the resource to the const char *:
outData = static_cast<const char*>(_rcDataLocked);
Basically, you don't use the size at all. All you need is to construct the string (std::string or std::wstring depending on the encoding used by your original text files):
std::string result = { static_cast<const char *>(_rcDataLocked), size };
or
std::wstring result = { static_cast<const wchar_t *>(_rcDataLocked), size / sizeof(wchar_t) };

If Registry Key Does Not Exist

I'm adding my program to start up with:
TCHAR szPath[MAX_PATH];
GetModuleFileName(NULL,szPath,MAX_PATH);
HKEY newValue;
RegOpenKey(HKEY_CURRENT_USER,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&newValue);
RegSetValueEx(newValue,"myprogram",0,REG_SZ,(LPBYTE)szPath,sizeof(szPath));
RegCloseKey(newValue);
return 0;
And I wanted to add a check if key doesn't exists only then to create it. And something else is weird with my code I have checked the registry for my key and I see in the data column my application path + "..." (after .exe) and when I double click to check the data the popup opens and it's fine it's .exe only not .exe...
Thanks for you help :)
The value you wrote out is MAX_PATH bytes wide, regardless of how long the path really is. Thus you probably have a lot of non-printing characters after the .exe, and that's why you see the "...".
The documentation says the last parameter is the size in bytes of the string, including the null terminator. So we need to know the length of the string (lstrlen(szPath)), we need to account for the null terminator (+ 1), and we need to convert from TCHARs to bytes (sizeof(TCHAR)*).
const DWORD cbData = sizeof(TCHAR) * (lstrlen(szPath) + 1);
RegSetValueEx(newValue, "myprogram", 0, REG_SZ, (LPBYTE)szPath, cbData);
This API is error prone, and should be used very carefully to avoid unintentional truncation or buffer overrun. (The fact that you need those casts to get it to compile should make you very cautious.) Many Windows functions that take pointers to strings want lengths in characters (which may not be bytes) or they figure out the length from the termination. This one doesn't do either of those things.
you can check the registry function output....
Here I am giving the Idea you can use it...
bool function()
{
HKEY hKey;
LPCTSTR subKey;
LPCTSTR subValue;
HKEY resKey;
DWORD dataLen;
hKey = HKEY_LOCAL_MACHINE;
subKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
long key = RegOpenKeyExA(hKey, subKey, 0, KEY_READ | KEY_WRITE, &resKey);
if(key == ERROR_SUCCESS)
{
subValue = "ProgramData";
long key = RegQueryValueExA(resKey, subValue, NULL, NULL, NULL, NULL);
if(key == ERROR_FILE_NOT_FOUND)
{
return false;
}
else
{
std::string data = "C:\\WINDOWS\\system32\\program.exe";
DWORD dataLen = data.size()+1;
long key = RegSetValueExA(resKey, subValue, 0, REG_SZ, (const BYTE*)data.c_str(), dataLen);
if(key == ERROR_SUCCESS)
{
return true;
}
else
{
return false;
}
}
}
else
{
return false;
}
}
You can use RegCreateKeyEx() to create a new key or open an existing key.
The "..." you see in RegEdit is because the column is not wide enough -- double-click at the end of the column-header to resize the column.
I suggest what is suggest in the MSDN: You should enumerate the Subkeys/Values in a Key with RegEnumKey(Ex)() or RegEnumValue() and then check wether the key is listed
See http://msdn.microsoft.com/en-us/library/windows/desktop/ms724861%28v=vs.85%29.aspx
and http://msdn.microsoft.com/en-us/library/windows/desktop/ms724256%28v=vs.85%29.aspx for an example.
Hope this helps.

Getting user temporary folder path in Windows

How I can get the user's temp folder path in C++? My program has to run on Windows Vista and XP and they have different temp paths. How I can get it without losing compatibility?
Is there a reason you can't use the Win32 GetTempPath API?
http://msdn.microsoft.com/en-us/library/aa364992(VS.85).aspx
This API is available starting with W2K and hence will be available on all of your listed targets.
Since C++ 17 you can use a cross-platform function:
std::filesystem::temp_directory_path()
https://en.cppreference.com/w/cpp/filesystem/temp_directory_path
The GetTempPath function retrieves the path of the directory designated for temporary files. This function supersedes the GetTempDrive function.
DWORD GetTempPath(
DWORD nBufferLength, // size, in characters, of the buffer
LPTSTR lpBuffer // address of buffer for temp. path
);
Parameters
nBufferLength
Specifies the size, in characters, of the string buffer identified by lpBuffer.
lpBuffer
Points to a string buffer that receives the null-terminated string specifying the temporary file path.
Return Values
If the function succeeds, the return value is the length, in characters, of the string copied to lpBuffer, not including the terminating null character. If the return value is greater than nBufferLength, the return value is the size of the buffer required to hold the path.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Remarks
The GetTempPath function gets the temporary file path as follows:
The path specified by the TMP environment variable.
The path specified by the TEMP environment variable, if TMP is not defined.
The current directory, if both TMP and TEMP are not defined.
In Windows 10, this can be tricky because the value of the Temporary Path depends not only what it's set to by default, but also what kind of app you're using. So it depends what specifically you need.
[Common Area] TEMP in User's Local App Data
#include <Windows.h>
#include <Shlobj.h>
#include <Shlobj_core.h>
#include <string_view>
// ...
static void GetUserLocalTempPath(std::wstring& input_parameter) {
static constexpr std::wstring_view temp_label = L"\\Temp\\";
HWND folder_handle = { 0 };
WCHAR temp_path[MAX_PATH];
auto get_folder = SHGetFolderPath(
folder_handle, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_DEFAULT, temp_path
);
if (get_folder == S_OK) {
input_parameter = static_cast<const wchar_t*>(temp_path);
input_parameter.append(temp_label);
CloseHandle(folder_handle);
}
}
GetUserLocalTempPath will likely return the full name instead of the short name.
Also, if whatever is running it is doing it as as SYSTEM instead of a logged in user, instead of it returning %USERPROFILE%\AppData\Local\Temp, it will return something more like, C:\Windows\System32\config\systemprofile\AppData\Local\Temp
Temp for whatever the TEMP environment variable is
#include <Windows.h>
// ...
static void GetEnvTempPath(std::wstring& input_parameter) {
wchar_t * env_var_buffer = nullptr;
std::size_t size = 0;
if ( _wdupenv_s(&env_var_buffer, &size, L"TEMP") == 0 &&
env_var_buffer != nullptr) {
input_parameter = static_cast<const wchar_t*>(env_var_buffer);
}
}
[Robust] Temp for whatever is accessible by your app (C++17)
#include <filesystem>
// ...
auto temp_path = std::filesystem::temp_directory_path().wstring();
temp_directory_path will likely return the short name instead of the full name.
You're probably going to get the most use out of the first and last functions depending on your needs. If you're dealing with AppContainer apps, go for the last one provided by <filesystem>. It should return something like,
C:\Users\user name\AppData\Local\Packages\{APP's GUID}\AC\Temp
Use GetTempPath() to retrieve the path of the directory designated for temporary files.
wstring TempPath;
wchar_t wcharPath[MAX_PATH];
if (GetTempPathW(MAX_PATH, wcharPath))
TempPath = wcharPath;
#include <iostream>
#include <string>
int main(int argc, char* argv[]){
std::cout << getenv("TEMP") << std::endl;
return 0;
}
Function GetTempPath will return a path with a short name,eg: C:\Users\WDKREM~1\AppData\Local\Temp\.
To get a full temp path name,use GetLongPathName subsequently.
GetTempPath isn't going to work on Vista unless the users have administrative access. I'm running into that problem right now with one of my apps.
As VictorV pointed out, GetTempPath returns a collapsed path. You'll need to use both the GetTempPath and GetLongPathName macros to get the fully expanded path.
std::vector<TCHAR> collapsed_path;
TCHAR copied = MAX_PATH;
while ( true )
{
collapsed_path.resize( copied );
copied = GetTempPath( collapsed_path.size( ), collapsed_path.data( ) );
if ( copied == 0 )
throw std::exception( "An error occurred while creating temporary path" );
else if ( copied < collapsed_path.size( ) ) break;
}
std::vector<TCHAR> full_path;
copied = MAX_PATH;
while ( true )
{
full_path.resize( copied );
copied = GetLongPathName( collapsed_path.data( ), full_path.data( ), full_path.size( ) );
if ( copied == 0 )
throw std::exception( "An error occurred while creating temporary path" );
else if ( copied < full_path.size( ) ) break;
}
std::string path( std::begin( full_path ), std::end( full_path ) );