Updating the PATH environment variable in Windows using C++ - c++

I am trying to launch a new process from my current process. I am using CreateProcess() to launch it. The issue is that I need to have certain directories in my PATH to successfully do so. Here is my current implementation but it doesn't work. What am I doing wrong?
// Environment variables
char *env = new char[2048];
char *ptr = env;
char temp[MAX_PATH] = "PATH=";
strcpy(ptr, strcat(temp, plugin_path));
ptr += strlen(ptr) + 1;
char temp2[MAX_PATH] = "PATH=";
strcpy(ptr, strcat(temp, lib_path));
ptr += strlen(ptr) + 1;
*ptr = '\0';
// Execute
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// error checking required
if(!CreateProcess(
NULL, // application name
command_path, // app.exe
NULL,
NULL,
TRUE,
0,
env, // environment
NULL,
&si,
&pi)) {
std::cout << GetLastError();
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
std::cout << "Process Started!";
Please let me know if anything else is required.
EDIT: Somebody mentioned below that I need to be a little more specific. It doesn't work in the sense that the environment variables don't get passed. It fails because the library path is not in PATH. The createProcess does actually launch it though.
EDIT2: Here's the updated code. Same problem. Further, CreateProcess throws error 1087 which doesn't seem to exist in the docs.
// Environment variables
char env[2048];
char *ptr = env;
char *path_path = getenv("PATH");
// copy original path
memcpy(ptr, path_path, strlen(path_path));
ptr += strlen(ptr) + 1;
memcpy(ptr, ";", 1);
ptr++;
// copy plugin path
memcpy(ptr, plugin_path, strlen(plugin_path));
ptr += strlen(plugin_path) + 1;
memcpy(ptr, ";", 1);
ptr++;
// copy libpath
memcpy(ptr, lib_path, strlen(lib_path));
ptr += strlen(lib_path) + 1;
memcpy(ptr, ";", 1);
ptr++;
// double null terminated
memcpy(ptr, "\0\0", 2);
std::cout << "ENV : " << env << std::endl;
// error checking required
if(!CreateProcess(
NULL, // application name
command_path, // app.exe
NULL,
NULL,
TRUE,
0,
env, // environment
NULL,
&si,
&pi)) {
std::cout << GetLastError();
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
std::cout << "Process Started!";

The PATH variable is a single variable. Different directories are listed in that variable, separated by semi-colons. But you've attempted to define the variable twice. That is the mistake.
The code should be something like this (assuming that you want to extend the existing path):
char *env = new char[2048]; // fingers crossed this is enough
strcpy(env, "PATH=");
strcat(env, getenv("PATH"));
strcat(env, ";");
strcat(env, plugin_path);
strcat(env, ";");
strcat(env, lib_path);
env[strlen(env)+1] = '\0';
Although this code (as is yours in the question) is simply begging for a buffer overrun.
It would be so much easier if you used C++ facilities to build your strings. For instance:
std::stringstream ss;
ss << "PATH=" << getenv("PATH");
ss << ";" << plugin_path;
ss << ";" << lib_path;
ss << '\0';
std::string env = ss.str();
Then pass env.c_str() to CreateProcess.
Not only does this make the code easier to read and verify, you know that you won't overrun any buffers.
I also note that you are passing an environment that has only one variable defined in it, namely PATH. It might be better if you started from the environment of the calling process, added the extra directories to PATH, and then passed that as the environment for the new process.

#include <iostream>
#include <windows.h>
#include <cstring>
#include "tchar.h"
void SetUserVariablePath(){
HKEY hkey;
long regOpenResult;
const char key_name[] = "Environment";
const char path[]="D:/custom_command"; //new_value path need to update
regOpenResult = RegOpenKeyEx(HKEY_CURRENT_USER,key_name, 0, KEY_ALL_ACCESS, &hkey);
LPCSTR stuff = "VVS_LOGGING_PATH"; //Variable Name
RegSetValueEx(hkey,stuff,0,REG_SZ,(BYTE*) path, strlen(path));
RegCloseKey(hkey);
}
void GetUserVariablePath(){
static const char path[] = "VVS_LOGGING_PATH" ; //Variable Name
static BYTE buffer1[1000000] ;
DWORD buffsz1 = sizeof(buffer1) ;
{
//HKEY_CURRENT_USER\Environment
const char key_name[] = "Environment";
HKEY key ;
if( RegOpenKeyExA( HKEY_CURRENT_USER, key_name, 0, KEY_QUERY_VALUE, std::addressof(key) ) == 0 &&
RegQueryValueExA( key, path, nullptr, nullptr, buffer1, std::addressof(buffsz1) ) == 0 )
{
std::cout << "The updated value of the user variable is : " << reinterpret_cast<const char*>(buffer1) << '\n' ;
}
}
}
int main()
{
SetUserVariablePath();
GetUserVariablePath();
return 0;
}

Related

How to add a path with spaces as an arguments to CreateProcess batch file?

I am trying to pass an argument with a space in i to a batch file I run via CreateProcess(). How do I specify that the entire object is an argument?
std::wstring args = TEXT("/C \"C:\\setup.bat\" C:\\TEST TEST");
In the example above, my batch file reads the first argument as C:\TEST.
And, this does not work (batch file exits immediately and does not run):
std::wstring args = TEXT("/C \"C:\\setup.bat\" \"C:\\TEST TEST\"");
Here is the entire code:
#include <iostream>
#define WINDOWS_LEAN_AND_MEAN
#include <Windows.h>
#include <strsafe.h>
#include <string>
#include <UserEnv.h>
#include <vector>
#define BUFSIZE 4096
#pragma comment(lib, "userenv.lib")
std::wstring GetEnvString()
{
wchar_t* env = GetEnvironmentStrings();
if (!env)
{
abort();
}
const wchar_t* var = env;
size_t total_len = 0;
size_t len;
while ((len = wcslen(var)) > 0)
{
total_len += len + 1;
var += len + 1;
}
std::wstring result(env, total_len);
FreeEnvironmentStrings(env);
return result;
}
int main()
{
LPVOID env;
if (!CreateEnvironmentBlock(&env, NULL, FALSE))
{
std::cout << "FAILURE" << std::endl;
system("PAUSE");
abort();
}
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
std::wstring program = TEXT("C:\\Windows\\System32\\cmd.exe");
std::wstring args = TEXT("/C");
args.append(TEXT(" \"C:\\setup.bat\""));
args.append(TEXT(" C:\TEST TEST"));
std::vector<wchar_t> buf(args.begin(), args.end());
buf.push_back(0);
if (!CreateProcess(program.c_str(), buf.data(), NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, env, NULL, &si, &pi))
{
std::cout << "FAILURE" << std::endl;
system("pause");
abort();
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
DestroyEnvironmentBlock(env);
if (!CreateEnvironmentBlock(&env, NULL, FALSE))
{
std::cout << "FAILURE" << std::endl;
abort();
}
return 0;
}
the problem is that the /C parameter needs its entire parameter enclosed by quotes (if it contain spaces).
so, instead of cmd /c "c:\setup.bat" "c:\test test", you need cmd /c ""c:\setup.bat" "c:\test test"" (or at least cmd /c "c:\setup.bat "c:\test test"")
Figured it out:
std::wstring args = TEXT("/C");
args.append(TEXT(" \"\"C:\\setup.bat\""));
args.append(TEXT(" \"C:\\TEST TEST\""));
I figure this ends up doing something like: cmd.exe "C:\setup.bat "C:\Test Test""
How about this:
std::wstring args = TEXT("/C \"C:\\setup.bat\" \"C:\\TEST\ TEST\"");

Passing arguments to executable not working in C++

This is the source code for "sleeper.exe" I have:
int main(int argc, char** argv) {
cout<<argv[1];
return 0;
}
When I call from command line like this:
C:\sleeper 5
I see
5
in command line so this works fine..
Now I am trying to call this exe from some other exe like this:
std::cout << "ret is:" << ret;
std::cout << "\n";
CreateProcess("sleeper.exe", // No module name (use command line)
ret, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure
)
Here ret is 5 as well and I am sure because I see it in the commandline fine:
ret is: 5
There is a file called config.mpap in the same directory and I read the value from here like this:
std::ifstream myReadFile;
myReadFile.open("config.mpap");
char output[400];
if (myReadFile.is_open()) {
while (!myReadFile.eof()) {
myReadFile >> output;
}
}
myReadFile.close();
char y = output[37];
int numberOfSleeps = y - '0'; // So now numberOfSleeps is 5
And then I convert numberOfSleeps to ret like this:
char* ret = NULL;
int numChars = 0;
bool isNegative = false;
// Count how much space we will need for the string
int temp = sleepTime;
do {
numChars++;
temp /= 10;
} while (temp);
ret = new char[ numChars + 1 ];
ret[numChars] = 0;
if (isNegative) ret[0] = '-';
int i = numChars - 1;
do {
ret[i--] = sleepTime % 10 + '0';
sleepTime /= 10;
} while (sleepTime);
Can please someone help me why ret is not passed to sleeper.exe from createprocess.exe?
EDIT:
It works like this:
if (!CreateProcess(NULL, // No module name (use command line)
"sleeper 5", // Command line
However this does not even compile:
std::string sleeper("sleeper ");
sleeper += ret;
if (!CreateProcess(NULL, // No module name (use command line)
sleeper, // Command line
The command line (second parameter of CreateProcess) takes the full command line, including the executable name. If the first argument is not NULL, it is used as the executable to run, but the command line still has to include an executable name. In the past even prepending a single space (giving an empty executable name) worked for me.

Converting LPBYTE into String

I'm querying data from the registry and it's being outputted as LPBYTE, and this is where i'm stuck. I need to convert the LPBYTE into a type of data that I can manipulate such as a String.
This is my code so far
HKEY hk;
string poolID;
DWORD dwSize = 0;
DWORD dwDataType = 0;
DWORD dwValue;
LPBYTE lpValue = NULL;
CA2W registryLocation("Software\\Example");
// Check registry if exists, otherwise create.
LONG openReg = RegOpenKeyEx(HKEY_CURRENT_USER, registryLocation, 0, KEY_QUERY_VALUE, &hk);
if (openReg==ERROR_SUCCESS) { } else { cout << "Error (Could not open/create Registry Location)\n"; }
// Get buffer size
LONG getRegBuf = RegQueryValueExA(hk, "", 0, &dwDataType, lpValue, &dwSize);
if (getRegBuf==ERROR_SUCCESS) { cout << "Got reg key buf size\n"; } else { cout << "Error (registry key does not exist)/n"; intro(); }
lpValue = (LPBYTE)malloc(dwSize);
// Open reg value
LONG getReg = RegQueryValueExA(hk, "", 0, &dwDataType, (LPBYTE)&dwValue, &dwSize);
if (getReg==ERROR_SUCCESS) { cout << "Successful\n"; } else { cout << "Error\n"; }
cout << dwValue;
Any help or code examples will be much appreciated.
You need to declare lpValue to be char*.
char* lpValue;
Then allocate it with a call to new.
lpValue = new char[dwSize+1];
Allocate an extra element in case the registry data is mal-formed and is missing a null-terminator. That is something that can happen. Then set the last element to \0:
lpValue[dwSize] = '\0';
Then get the value:
LONG getReg = RegQueryValueExA(..., (LPBYTE)&dwValue, ...);
Deallocate using delete[]:
delete[] lpValue;

Memory leak when read from file

I'm trying to read data from XML file and store every element ("< some data/>") in vector container vector<TCHAR*> , why the Task Manager shows the memory usage much greater than vector size(~80mb instead of ~59mb) :
#define _UNICODE
#include<tchar.h>
#include<iostream>
#include<windows.h>
#include<vector>
using namespace std;
HANDLE hFile;
HANDLE hThread;
vector<TCHAR*> tokens;
DWORD tokensSize;
DWORD WINAPI Thread(LPVOID lpVoid);
void main()
{
tokensSize = 0;
hFile = CreateFile("db.xml",GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile == INVALID_HANDLE_VALUE) {
cout<<"CreateFile Error # "<<GetLastError()<<endl;
}
DWORD fileSize = GetFileSize(hFile,NULL);
cout<<"fileSize = "<<fileSize<<" bytes = "<<fileSize/1024/1024<<" mb"<<endl;
TCHAR* buffer = new TCHAR[fileSize / sizeof(TCHAR) + 1];
ZeroMemory(buffer,fileSize);
DWORD bytesRead;
if(!ReadFile(hFile,buffer,fileSize,&bytesRead,NULL)){
cout<<"ReadFile Error # "<<GetLastError()<<endl;
}
CloseHandle(hFile);
hThread = CreateThread(NULL,0,Thread,(LPVOID)buffer,0,NULL);
WaitForSingleObject(hThread,INFINITE);
for(int i=0;i<tokens.size();i++)
tokensSize+=(_tcslen(tokens[i])+1)*sizeof(TCHAR);
cout<<"vector size = "<<tokensSize<<" bytes = "<<tokensSize/1024/1024<<" mb"<<endl;
cin.get();
}
DWORD WINAPI Thread(LPVOID lpVoid)
{
wstring entireDB = (TCHAR*)lpVoid;
delete[]lpVoid;
wstring currentElement;
wstring::size_type lastPos = 0;
wstring::size_type next;
next = entireDB.find(_T(">"),lastPos);
TCHAR* szStr;
do
{
currentElement = entireDB.substr(lastPos,next+1-lastPos);
szStr = new TCHAR[currentElement.length()+1];
_tcscpy(szStr,currentElement.c_str());
tokens.push_back(szStr);
lastPos = next+1;
next = entireDB.find(_T(">"),lastPos);
}
while(next != wstring::npos);
entireDB.clear();
return 0;
}
OUTPUT:~
fileSize = 57mb
vectorSize = 58mb
but the TaskManager shows ~ 81mb.
What am I doing wrong?
THNX!
First, as Aesthete as pointed out, you never clear the token vector once you're finished with it. This should be done, or change the token vector to utilize self-cleaning content like std::string or std::wstring.
Which brings me to the side-by-side below. Please review this against your existing code. There are a number of changes you'll want to compare. The one you will likely not see until you cmopile+run is the memory footprint difference, which may surprise you.
Major Changes
Global tokens is now a vector of std::wstring rather than raw wchar_t pointers
Uses MultiByteToWideChar to translate the input file.
Allocates a std::wstring dynamically as the thread parameter. This removes one full copy of the file image. The thread is responsible for deleteing the wstring once finished parsing the content.
Uses _beginthreadex() for starting the thread. The fundamental reason for this is because of the C/C++ runtime usage. In the past the runtime sets up various thread-local-storage that must be properly cleaned, and are so when using _beginthreadex(). It is almost identical to CreateThread(), but honestly I look forward to the day when MS has their stuff together and gives us std::thread officially like the rest of the civilized world.
Minor/Meaningless Changes
Global variables are brought to local scope where appropriate. this means the only real global now is the tokens vector.
The thread procedure now pushes substrings straight to the tokens vector.
uses argv[1] for the filename (easy to debug that way, no other special reason). can be changed back to your hard-coded filename as needed.
I hope this gives you some ideas on cleaning this up, and more importantly, how yoy can do almost the entire task you're given without having to go new and delete nuts.
Notes: this does NOT check the input file for a byte-order-mark. I'm taking it on faith that your claim it is UTF8 is straight-up and doesn't have a BOM at the file beginning. If your input file does have a BOM, you need to adjust the code that reads the file in to account for this.
#include <windows.h>
#include <tchar.h>
#include <process.h>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// global map of tokens
vector<wstring> tokens;
// format required by _beginthreadex()
unsigned int _stdcall ThreadProc(void *p);
int main(int argc, char *argv[])
{
HANDLE hThread = NULL;
std::string xml;
std::wstring* pwstr = NULL;
// check early exit
if (argc != 2)
{
cout << "Usage: " << argv[0] << " filename" << endl;
return EXIT_FAILURE;
}
// use runtime library for reading the file content. the WIN32 CreateFile
// API is required for some things, but not for general file ops.
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize > 0)
{
// allocate a string large enough for the whole file.
std::string xml(dwFileSize, 0);
DWORD bytesRead = 0;
if (ReadFile(hFile, &xml.at(0), dwFileSize, &bytesRead, NULL) && (bytesRead == dwFileSize))
{
// invoke MB2WC to determine wide-char requirements
int ires = MultiByteToWideChar(CP_UTF8, 0, xml.c_str(), -1, NULL, 0);
if (ires > 0)
{
// allocate a wstring for our thread parameter.
pwstr = new wstring(ires, 0);
MultiByteToWideChar(CP_UTF8, 0, xml.c_str(), -1, &pwstr->at(0), ires);
// launch thread. it own the wstring we're sending, including cleanup.
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, pwstr, 0, NULL);
}
}
}
// release the file handle
CloseHandle(hFile);
}
// wait for potential thread
if (hThread != NULL)
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
// report space taken by tokens
size_t tokensSize = 0;
for (vector<wstring>::const_iterator it = tokens.begin(); it != tokens.end(); ++it)
tokensSize += it->size()+1;
cout << "tokens count = " << tokens.size() << endl
<< "tokens size = "<< tokensSize <<" bytes" << endl;
cin.get();
}
// our thread parameter is a dynamic-allocated wstring.
unsigned int _stdcall ThreadProc(void *p)
{
// early exit on null insertion
if (p == NULL)
return EXIT_FAILURE;
// use string passed to us.
wstring* pEntireDB = static_cast<wstring*>(p);
wstring::size_type last = 0;
wstring::size_type next = pEntireDB->find(L'>',last);
while(next != wstring::npos)
{
tokens.push_back(pEntireDB->substr(last, next-last+1));
last = next+1;
next = pEntireDB->find(L'>', last);
}
// delete the wstring (no longer needed)
delete pEntireDB;
return EXIT_SUCCESS;
}
You allocate memory here, in the do-while loop:
szStr = new TCHAR[currentElement.length()+1];
And you never release it with the delete operator

I can't use RegOpenKeyEx

I am having problems with reading the registry.
This function finds the number of entries in a registry path. It works perfectly, I have tested it:
void findNumberEntries(registryTest &INSTALLKEY) {
char buffer[50];
char size = sizeof(buffer);
int index = 0;
if(RegOpenKeyEx(INSTALLKEY.hKey,(LPTSTR)(INSTALLKEY.regpath.c_str()),0,KEY_ALL_ACCESS,&INSTALLKEY.hKey) == ERROR_SUCCESS) {
DWORD readEntry;
do {
readEntry = RegEnumValue(INSTALLKEY.hKey,index,(LPTSTR)buffer,(LPDWORD)&size,NULL,NULL,NULL,NULL);
index++;
}
while(readEntry != ERROR_NO_MORE_ITEMS);
}
INSTALLKEY.number = index;
RegCloseKey(INSTALLKEY.hKey);
}
now, the main function:
std::string regpath32 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\";
struct registryTest {
HKEY hKey;
std::string regpath;
int number;
};
registryTest INSTALLKEY = {HKEY_LOCAL_MACHINE, regpath32};
findNumberEntries(INSTALLKEY);
printf("%d\n",INSTALLKEY.number);
system("PAUSE");
//until here everything works as it should
HKEY hKey = INSTALLKEY.hKey;
std::string regpath = INSTALLKEY.regpath;
char buffer[50];
char size = sizeof(buffer);
std::string bufferString;
DWORD regOpen = RegOpenKeyEx(INSTALLKEY.hKey,(LPTSTR)INSTALLKEY.regpath.c_str(),0,KEY_READ,&INSTALLKEY.hKey);
if(regOpen == ERROR_SUCCESS) //this is the part that fails.
{
printf("Registry Key was successfully opened\n");
}
else
{
printf("Unable to open registry key\n");
LPVOID message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), NULL,(LPTSTR) &message, 0, NULL );
MessageBox(NULL,(LPCTSTR)message,"ERROR",MB_OK|MB_ICONINFORMATION);
}
...rest of the code
I always get "Unable to open registry" and the error message I get is "There are no more files". What is the problem??
your problem is that when you first open the registry key ,you assign it to hkey-member of your struct. So the second time this hkey doesn't contain the original basekey anymore.
change :
DWORD regOpen =
RegOpenKeyEx(INSTALLKEY.hKey,(LPTSTR)INSTALLKEY.regpath.c_str(),0,KEY_READ,&INSTALLKEY.hKey);
into
DWORD regOpen = RegOpenKeyEx(
HKEY_LOCAL_MACHINE
,(LPTSTR)INSTALLKEY.regpath.c_str(),0,KEY_READ,&INSTALLKEY.hKey);
or change this:
void findNumberEntries( registryTest &INSTALLKEY)
{
char buffer[50];
char size = sizeof(buffer);
int index = 0;
HKEY hkOpen = 0; // can't use INVALID_HANDLE_VALUE for HKEY's;
if (RegOpenKeyEx( INSTALLKEY.hKey ,(LPTSTR)(INSTALLKEY.regpath.c_str())
,0,&hkOpen ) == ERROR_SUCCESS)
{
// You should use RegQueryInfoKey for below code !
DWORD readEntry;
do {
readEntry = RegEnumValue( hkOpen ,index,(LPTSTR)buffer
,(LPDWORD size,NULL,NULL,NULL,NULL);
index++;
}
while(readEntry != ERROR_NO_MORE_ITEMS); }
INSTALLKEY.number = index;
RegCloseKey( hkOpen );
}
You may need to specify KEY_ALL_ACCESS in the second call as well, rather than just in the first. And on Win7 64-bit you may be running into the registry redirect craziness (http://msdn.microsoft.com/en-us/library/aa384232%28VS.85%29.aspx).
EDIT: ah, you might just be getting an ERROR_CANTWRITE back (error code number 5). You might be able to ignore that and see if it still works.
It's very likely that on Windows 7 64-bit that you are being redirected via Registry Virtualization. You can determine what keys are being redirected by calling RegQueryReflectionKey.
If you modify your code to output the actual integer value that is returned rather than a generic "Unable to open key", then it would be helpful. For example,
long n = RegOpenKeyEx(HKEY_LOCAL_MACHINE,TEXT("\\SOFTWARE"),
0,KEY_QUERY_VALUE, &hk );
if ( n == ERROR_SUCCESS ) {
cout << "OK" << endl;
}
else {
cout << "Failed with value " << n << endl;
}