ultimatepp/bazaar/SysExec/ShellLib.cpp
micio f4fc322c93 Bazaar/SysExec : now ShellExecute should pass environment correctly
git-svn-id: svn://ultimatepp.org/upp/trunk@3078 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2011-01-23 23:34:13 +00:00

203 lines
5.5 KiB
C++

#include "ShellLib.h"
#include "ArgEnv.h"
#ifdef PLATFORM_WIN32
#include <Shellapi.h>
NAMESPACE_UPP
////////////////////////////////////////////////////////////////////////////////////
// as I found that ShellExecuteEx don't take environment from current process
// but gets it from registry, the only way I found to pass it to spawned process
// is to modify the current user's registry keys directly; so, here a function that
// reads current environment and replaces it with a given one in registry for
// current user (KEY : HKCU/Environment
// it takes the new envoronment, replaces registry one's and returns it
struct RegData : Moveable<RegData>
{
DWORD type;
String data;
RegData(DWORD t, const String &d)
{
type = t;
data = d;
}
};
typedef VectorMap<String, RegData> RegMap;
// builds a regmap from an anvironment
// (all values are set as REG_SZ strings)
RegMap BuildEnvMap(VectorMap<String, String> const &env)
{
RegMap res;
for(int i = 0; i < env.GetCount(); i++)
res.Add(env.GetKey(i), RegData((DWORD)REG_SZ, String(~env[i], env[i].GetCount()+1)));
return res;
}
bool ReplaceRegEnv(RegMap &prevEnv, RegMap const &newEnv)
{
prevEnv.Clear();
// opens the environment key for reading
HKEY hKey;
if(RegOpenKeyEx(HKEY_CURRENT_USER, "Environment", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
// gets buffer sizes for names and values... sigh
DWORD nValues, maxValueNameLen, maxValueLen;
if(RegQueryInfoKey(
hKey,
NULL, // LPTSTR lpClass,
NULL, // LPDWORD lpcClass,
NULL, // LPDWORD lpReserved,
NULL, // LPDWORD lpcSubKeys,
NULL, // LPDWORD lpcMaxSubKeyLen,
NULL, // LPDWORD lpcMaxClassLen,
&nValues, // LPDWORD lpcValues,
&maxValueNameLen, // LPDWORD lpcMaxValueNameLen,
&maxValueLen, // LPDWORD lpcMaxValueLen,
NULL, // LPDWORD lpcbSecurityDescriptor,
NULL // PFILETIME lpftLastWriteTime
) != ERROR_SUCCESS)
{
LOG("ERROR OPENING REGISTRY");
return false;
}
LOG("Num values is " << nValues);
// read all user environment variables
Buffer<char>name(maxValueNameLen + 1);
Buffer<BYTE>value(maxValueLen + 1);
DWORD nameLen, valueLen, valueType;
for(DWORD i = 0; i < nValues; i++)
{
nameLen = maxValueNameLen + 1;
valueLen = maxValueLen + 1;
int r;
if( (r = RegEnumValue(
hKey,
i, // DWORD dwIndex,
name, // LPTSTR lpValueName,
&nameLen, // LPDWORD lpcchValueName,
NULL, // LPDWORD lpReserved,
&valueType, // LPDWORD lpType,
value, // LPBYTE lpData,
&valueLen // LPDWORD lpcbData
)) != ERROR_SUCCESS)
{
LOG("ERROR ENUMERATING VALUES, IDX is " << i << " error code is " << r);
RegCloseKey(hKey);
return false;
}
name[nameLen] = 0;
value[valueLen] = 0;
prevEnv.Add(~name, RegData(valueType, String(~value, valueLen)));
}
// close environment and reopen it for write/delete
RegCloseKey(hKey);
if(RegOpenKeyEx(HKEY_CURRENT_USER, "Environment", 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
return false;
// wipe all previous values from environment
for(DWORD i = 0; i < nValues; i++)
{
if(RegDeleteValue(hKey, prevEnv.GetKey(i)) != ERROR_SUCCESS)
{
LOG("ERROR DELETING VALUE " << prevEnv.GetKey(i));
RegCloseKey(hKey);
return false;
}
}
// now add new values inside registry
for(int i = 0; i < newEnv.GetCount(); i++)
{
if(RegSetValueEx(
hKey,
newEnv.GetKey(i), // LPCTSTR lpValueName,
0, // DWORD Reserved,
newEnv[i].type, // DWORD dwType,
newEnv[i].data, // const BYTE *lpData,
newEnv[i].data.GetCount() // DWORD cbData
) != ERROR_SUCCESS
)
{
LOG("ERROR ADDING VALUE " << newEnv.GetKey(i));
RegCloseKey(hKey);
return false;
}
}
RegCloseKey(hKey);
return true;
}
////////////////////////////////////////////////////////////////////////////////////
// executes a command via shell "runas" as admin user;
// if wait is true, will wait for command end, otherwise executes it in background
bool ShellExec(String const &args, VectorMap<String, String> const &env, bool wait)
{
// we use ShellExecuteEx to start an Admin process
// as ShellExecuteEx doesn't take an environment argument, but gets user's default one
// we must replace in inside registry before calling
RegMap prevEnv;
if(!ReplaceRegEnv(prevEnv, BuildEnvMap(env)))
{
LOG("ERROR SETTING ENVIRONMENT FOR SHELLEXECUTEEX");
return false;
}
// now we can use ShellExecute to raise process level
SHELLEXECUTEINFO info =
{
sizeof(SHELLEXECUTEINFO), // cbsize
SEE_MASK_NOASYNC, // fMask
0, // hwnd
"runas", // lpVerb
args, // lpFile
0, // lpParameters
0, // lpDirectory
SW_SHOW, // nShow
0, // hHinstApp -- result handle or error code
/* REST AS DEFAULT -- NOT NEEDED
LPVOID lpIDList;
LPCTSTR lpClass;
HKEY hkeyClass;
DWORD dwHotKey;
union { HANDLE hIcon; HANDLE hMonitor; } DUMMYUNIONNAME;
HANDLE hProcess;
*/
};
// if we shall wait for process termination, we shall get spawned process
// hanlde too...
if(wait)
info.fMask |= SEE_MASK_NOCLOSEPROCESS;
bool res = ShellExecuteEx(&info);
// restore the environment
RegMap dummyEnv;
if(!ReplaceRegEnv(dummyEnv, prevEnv))
{
LOG("ERROR RESETTING ENVIRONMENT");
return false;
}
if(wait && res)
WaitForSingleObject(info.hProcess, INFINITE);
return res;
}
END_UPP_NAMESPACE
#endif