bool ConsoleHandler::StartShellProcess ( const wstring& strCustomShell, const wstring& strInitialDir, const wstring& strUser, const wstring& strPassword, const wstring& strInitialCmd, const wstring& strConsoleTitle, DWORD dwStartupRows, DWORD dwStartupColumns, bool bDebugFlag ) { wstring strUsername(strUser); wstring strDomain; // shared_ptr<void> userProfileKey; // shared_ptr<void> userEnvironment; // shared_ptr<void> userToken; if (strUsername.length() > 0) { size_t pos = strUsername.find(L'\\'); if (pos != wstring::npos) { strDomain = strUsername.substr(0, pos); strUsername = strUsername.substr(pos+1); } /* // logon user HANDLE hUserToken = NULL; ::LogonUser( strUsername.c_str(), strDomain.length() > 0 ? strDomain.c_str() : NULL, strPassword.c_str(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hUserToken); userToken.reset(hUserToken, ::CloseHandle); */ /* ::ImpersonateLoggedOnUser(userToken.get()); wchar_t szComspec[MAX_PATH]; ::GetEnvironmentVariable(L"COMSPEC", szComspec, MAX_PATH); */ /* // load user's profile // seems to be necessary on WinXP for environment strings' expainsion to work properly PROFILEINFO userProfile; ::ZeroMemory(&userProfile, sizeof(PROFILEINFO)); userProfile.dwSize = sizeof(PROFILEINFO); userProfile.lpUserName = const_cast<wchar_t*>(strUser.c_str()); ::LoadUserProfile(userToken.get(), &userProfile); userProfileKey.reset(userProfile.hProfile, bind<BOOL>(::UnloadUserProfile, userToken.get(), _1)); */ /* // load user's environment void* pEnvironment = NULL; ::CreateEnvironmentBlock(&pEnvironment, userToken.get(), FALSE); userEnvironment.reset(pEnvironment, ::DestroyEnvironmentBlock); ::RevertToSelf(); */ } wstring strShellCmdLine(strCustomShell); if (strShellCmdLine.length() == 0) { wchar_t szComspec[MAX_PATH]; ::ZeroMemory(szComspec, MAX_PATH*sizeof(wchar_t)); if (strUsername.length() > 0) { /* // resolve comspec when running as another user wchar_t* pszComspec = reinterpret_cast<wchar_t*>(userEnvironment.get()); while ((pszComspec[0] != L'\x00') && (_wcsnicmp(pszComspec, L"comspec", 7) != 0)) pszComspec += wcslen(pszComspec)+1; if (pszComspec[0] != L'\x00') { strShellCmdLine = (pszComspec + 8); } */ if (strShellCmdLine.length() == 0) strShellCmdLine = L"cmd.exe"; } else { if (::GetEnvironmentVariable(L"COMSPEC", szComspec, MAX_PATH) > 0) { strShellCmdLine = szComspec; } if (strShellCmdLine.length() == 0) strShellCmdLine = L"cmd.exe"; } } if (strInitialCmd.length() > 0) { strShellCmdLine += L" "; strShellCmdLine += strInitialCmd; } wstring strStartupTitle(strConsoleTitle); if (strStartupTitle.length() == 0) { strStartupTitle = L"Console2 command window"; // strStartupTitle = str(wformat(L"Console2 command window 0x%08X") % this); } // wstring strStartupDir((strUsername.length() > 0) ? Helpers::ExpandEnvironmentStringsForUser(userToken, strInitialDir) : Helpers::ExpandEnvironmentStrings(strInitialDir)); wstring strStartupDir((strUsername.length() > 0) ? strInitialDir : Helpers::ExpandEnvironmentStrings(strInitialDir)); if (strStartupDir.length() > 0) { if ((*(strStartupDir.end() - 1) == L'\"') && (*strStartupDir.begin() != L'\"')) { // startup dir name ends with ", but doesn't start with ", the user passed // something like "C:\" as the parameter, it got parsed to C:", remove the trailing " // // This is a common mistake, thus the check... strStartupDir = strStartupDir.substr(0, strStartupDir.length()-1); } // startup dir doesn't end with \, add it if (*(strStartupDir.end() - 1) != L'\\') strStartupDir += L'\\'; // check if startup directory exists DWORD dwDirAttributes = ::GetFileAttributes(strStartupDir.c_str()); if ((dwDirAttributes == INVALID_FILE_ATTRIBUTES) || (dwDirAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { // no directory, use Console.exe directory strStartupDir = Helpers::GetModulePath(NULL); } } // setup the startup info struct STARTUPINFO si; ::ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.lpTitle = const_cast<wchar_t*>(strStartupTitle.c_str()); if (g_settingsHandler->GetConsoleSettings().bStartHidden) { // Starting Windows console window hidden causes problems with // some GUI apps started from Console that use SW_SHOWDEFAULT to // initially show their main window (i.e. the window inherits our // SW_HIDE flag and remains invisible :-) si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; } else { // To avoid invisible GUI windows, default settings will create // a Windows console window far offscreen and hide the window // after it has been created. // // This approach can flash console window's taskbar button and // taskbar button can sometimes remain visible, but nothing is perfect :) si.dwFlags = STARTF_USEPOSITION; si.dwX = 0x7FFF; si.dwY = 0x7FFF; } PROCESS_INFORMATION pi; // we must use CREATE_UNICODE_ENVIRONMENT here, since s_environmentBlock contains Unicode strings DWORD dwStartupFlags = CREATE_NEW_CONSOLE|CREATE_SUSPENDED|CREATE_UNICODE_ENVIRONMENT; // TODO: not supported yet //if (bDebugFlag) dwStartupFlags |= DEBUG_PROCESS; if (strUsername.length() > 0) { if (!::CreateProcessWithLogonW( strUsername.c_str(), strDomain.length() > 0 ? strDomain.c_str() : NULL, strPassword.c_str(), LOGON_WITH_PROFILE, NULL, // const_cast<wchar_t*>(Helpers::ExpandEnvironmentStringsForUser(userToken, strShellCmdLine).c_str()), const_cast<wchar_t*>(strShellCmdLine.c_str()), dwStartupFlags, // userEnvironment.get(), NULL, (strStartupDir.length() > 0) ? const_cast<wchar_t*>(strStartupDir.c_str()) : NULL, &si, &pi)) { throw ConsoleException(str(wformat(Helpers::LoadStringW(IDS_ERR_CANT_START_SHELL_AS_USER)) % strShellCmdLine % strUser)); } } else { if (!::CreateProcess( NULL, const_cast<wchar_t*>(Helpers::ExpandEnvironmentStrings(strShellCmdLine).c_str()), NULL, NULL, FALSE, dwStartupFlags, s_environmentBlock.get(), (strStartupDir.length() > 0) ? const_cast<wchar_t*>(strStartupDir.c_str()) : NULL, &si, &pi)) { throw ConsoleException(str(wformat(Helpers::LoadString(IDS_ERR_CANT_START_SHELL)) % strShellCmdLine)); } } // create shared memory objects CreateSharedObjects(pi.dwProcessId, strUser); CreateWatchdog(); // write startup params m_consoleParams->dwConsoleMainThreadId = pi.dwThreadId; m_consoleParams->dwParentProcessId = ::GetCurrentProcessId(); m_consoleParams->dwNotificationTimeout = g_settingsHandler->GetConsoleSettings().dwChangeRefreshInterval; m_consoleParams->dwRefreshInterval = g_settingsHandler->GetConsoleSettings().dwRefreshInterval; m_consoleParams->dwRows = dwStartupRows; m_consoleParams->dwColumns = dwStartupColumns; m_consoleParams->dwBufferRows = g_settingsHandler->GetConsoleSettings().dwBufferRows; m_consoleParams->dwBufferColumns = g_settingsHandler->GetConsoleSettings().dwBufferColumns; m_hConsoleProcess = shared_ptr<void>(pi.hProcess, ::CloseHandle); // inject our hook DLL into console process if (!InjectHookDLL(pi)) return false; // resume the console process ::ResumeThread(pi.hThread); ::CloseHandle(pi.hThread); // wait for hook DLL to set console handle if (::WaitForSingleObject(m_consoleParams.GetReqEvent(), 10000) == WAIT_TIMEOUT) return false; ::ShowWindow(m_consoleParams->hwndConsoleWindow, SW_HIDE); return true; }
bool ConsoleHandler::StartShellProcess(const wstring& strCustomShell, const wstring& strInitialDir, const wstring& strInitialCmd, const wstring& strConsoleTitle, DWORD dwStartupRows, DWORD dwStartupColumns, bool bDebugFlag) { wstring strShellCmdLine(strCustomShell); if (strShellCmdLine.length() == 0) { wchar_t szComspec[MAX_PATH]; ::ZeroMemory(szComspec, MAX_PATH*sizeof(wchar_t)); if (::GetEnvironmentVariable(L"COMSPEC", szComspec, MAX_PATH) > 0) { strShellCmdLine = szComspec; } else { strShellCmdLine = L"cmd.exe"; } } if (strInitialCmd.length() > 0) { strShellCmdLine += L" "; strShellCmdLine += strInitialCmd; } wstring strStartupTitle(strConsoleTitle); if (strStartupTitle.length() == 0) { strStartupTitle = L"Console2 command window"; // strStartupTitle = str(wformat(L"Console2 command window 0x%08X") % this); } wstring strStartupDir(strInitialDir); if (strStartupDir.length()) { // check if startup directory exists DWORD dwDirAttributes = ::GetFileAttributes(strStartupDir.c_str()); if ((dwDirAttributes == INVALID_FILE_ATTRIBUTES) || (dwDirAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { // no directory, use Console.exe directory strStartupDir = Helpers::GetModulePath(NULL); } } // setup the startup info struct STARTUPINFO si; ::ZeroMemory(&si, sizeof(STARTUPINFO)); si.dwFlags = STARTF_USESHOWWINDOW; si.cb = sizeof(STARTUPINFO); si.wShowWindow = SW_HIDE; si.lpTitle = const_cast<wchar_t*>(strStartupTitle.c_str()); PROCESS_INFORMATION pi; DWORD dwStartupFlags = CREATE_NEW_CONSOLE|CREATE_SUSPENDED; // TODO: not supported yet //if (bDebugFlag) dwStartupFlags |= DEBUG_PROCESS; if (!::CreateProcess( NULL, const_cast<wchar_t*>(Helpers::ExpandEnvironmentStrings(strShellCmdLine).c_str()), NULL, NULL, FALSE, dwStartupFlags, NULL, (strStartupDir.length() > 0) ? const_cast<wchar_t*>(Helpers::ExpandEnvironmentStrings(strStartupDir).c_str()) : NULL, &si, &pi)) { return false; } // create shared memory objects CreateSharedObjects(pi.dwProcessId); // write startup params m_consoleParams->dwConsoleMainThreadId = pi.dwThreadId; m_consoleParams->dwParentProcessId = ::GetCurrentProcessId(); m_consoleParams->dwNotificationTimeout = g_settingsHandler->GetConsoleSettings().dwChangeRefreshInterval; m_consoleParams->dwRefreshInterval = g_settingsHandler->GetConsoleSettings().dwRefreshInterval; m_consoleParams->dwRows = dwStartupRows; m_consoleParams->dwColumns = dwStartupColumns; m_consoleParams->dwBufferRows = g_settingsHandler->GetConsoleSettings().dwBufferRows; m_consoleParams->dwBufferColumns = g_settingsHandler->GetConsoleSettings().dwBufferColumns; m_hConsoleProcess = shared_ptr<void>(pi.hProcess, ::CloseHandle); // inject our hook DLL into console process if (!InjectHookDLL()) return false; // resume the console process ::ResumeThread(pi.hThread); ::CloseHandle(pi.hThread); // wait for hook DLL to set console handle if (::WaitForSingleObject(m_consoleParams.GetReqEvent(), 10000) == WAIT_TIMEOUT) return false; return true; }
void ConsoleHandler::CreateShellProcess ( const wstring& strShell, const wstring& strInitialDir, const UserCredentials& userCredentials, const wstring& strInitialCmd, const wstring& strConsoleTitle, PROCESS_INFORMATION& pi ) { std::unique_ptr<void, DestroyEnvironmentBlockHelper> userEnvironment; std::unique_ptr<void, CloseHandleHelper> userToken; if (userCredentials.strUsername.length() > 0) { if (!userCredentials.netOnly) { // logon user HANDLE hUserToken = NULL; if( !::LogonUser( userCredentials.strUsername.c_str(), userCredentials.strDomain.length() > 0 ? userCredentials.strDomain.c_str() : NULL, userCredentials.password.c_str(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hUserToken) || !::ImpersonateLoggedOnUser(hUserToken) ) { Win32Exception err(::GetLastError()); throw ConsoleException(boost::str(boost::wformat(Helpers::LoadStringW(IDS_ERR_CANT_START_SHELL_AS_USER)) % L"?" % userCredentials.user % err.what())); } userToken.reset(hUserToken); /* // load user's profile // seems to be necessary on WinXP for environment strings' expainsion to work properly PROFILEINFO userProfile; ::ZeroMemory(&userProfile, sizeof(PROFILEINFO)); userProfile.dwSize = sizeof(PROFILEINFO); userProfile.lpUserName = const_cast<wchar_t*>(userCredentials.strUsername.c_str()); ::LoadUserProfile(userToken.get(), &userProfile); userProfileKey.reset(userProfile.hProfile, bind<BOOL>(::UnloadUserProfile, userToken.get(), _1)); */ // load user's environment void* pEnvironment = nullptr; if( !::CreateEnvironmentBlock(&pEnvironment, userToken.get(), FALSE) ) { Win32Exception err(::GetLastError()); ::RevertToSelf(); throw ConsoleException(boost::str(boost::wformat(Helpers::LoadStringW(IDS_ERR_CANT_START_SHELL_AS_USER)) % L"?" % userCredentials.user % err.what())); } userEnvironment.reset(pEnvironment); } } wstring strShellCmdLine(strShell); if (strShellCmdLine.length() == 0) { wchar_t szComspec[MAX_PATH]; ::ZeroMemory(szComspec, MAX_PATH*sizeof(wchar_t)); if (userEnvironment.get()) { // resolve comspec when running as another user wchar_t* pszComspec = reinterpret_cast<wchar_t*>(userEnvironment.get()); while ((pszComspec[0] != L'\x00') && (_wcsnicmp(pszComspec, L"comspec", 7) != 0)) pszComspec += wcslen(pszComspec)+1; if (pszComspec[0] != L'\x00') { strShellCmdLine = (pszComspec + 8); } if (strShellCmdLine.length() == 0) strShellCmdLine = L"cmd.exe"; } else { if (::GetEnvironmentVariable(L"COMSPEC", szComspec, MAX_PATH) > 0) { strShellCmdLine = szComspec; } if (strShellCmdLine.length() == 0) strShellCmdLine = L"cmd.exe"; } } if (strInitialCmd.length() > 0) { strShellCmdLine += L" "; strShellCmdLine += strInitialCmd; } wstring strStartupTitle(strConsoleTitle); if (strStartupTitle.length() == 0) { strStartupTitle = L"Console2 command window"; // strStartupTitle = boost::str(boost::wformat(L"Console2 command window 0x%08X") % this); } wstring strStartupDir( userToken.get() ? Helpers::ExpandEnvironmentStringsForUser(userToken.get(), strInitialDir) : Helpers::ExpandEnvironmentStrings(strInitialDir)); if (strStartupDir.length() > 0) { if ((*(strStartupDir.end() - 1) == L'\"') && (*strStartupDir.begin() != L'\"')) { // startup dir name ends with ", but doesn't start with ", the user passed // something like "C:\" as the parameter, it got parsed to C:", remove the trailing " // // This is a common mistake, thus the check... strStartupDir = strStartupDir.substr(0, strStartupDir.length()-1); } // startup dir doesn't end with \, add it if (*(strStartupDir.end() - 1) != L'\\') strStartupDir += L'\\'; // check if startup directory exists DWORD dwDirAttributes = ::GetFileAttributes(strStartupDir.c_str()); if ((dwDirAttributes == INVALID_FILE_ATTRIBUTES) || (dwDirAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { // no directory, use Console.exe directory strStartupDir = Helpers::GetModulePath(NULL); } } wstring strCmdLine( userToken.get() ? Helpers::ExpandEnvironmentStringsForUser(userToken.get(), strShellCmdLine) : Helpers::ExpandEnvironmentStrings(strShellCmdLine)); if( userToken.get() ) ::RevertToSelf(); // setup the startup info struct STARTUPINFO si; ::ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.lpTitle = const_cast<wchar_t*>(strStartupTitle.c_str()); if (g_settingsHandler->GetConsoleSettings().bStartHidden) { // Starting Windows console window hidden causes problems with // some GUI apps started from Console that use SW_SHOWDEFAULT to // initially show their main window (i.e. the window inherits our // SW_HIDE flag and remains invisible :-) si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; } else { // To avoid invisible GUI windows, default settings will create // a Windows console window far offscreen and hide the window // after it has been created. // // This approach can flash console window's taskbar button and // taskbar button can sometimes remain visible, but nothing is perfect :) si.dwFlags = STARTF_USEPOSITION; si.dwX = 0x7FFF; si.dwY = 0x7FFF; } // we must use CREATE_UNICODE_ENVIRONMENT here, since s_environmentBlock contains Unicode strings DWORD dwStartupFlags = CREATE_NEW_CONSOLE|CREATE_SUSPENDED|CREATE_UNICODE_ENVIRONMENT; // TODO: not supported yet //if (bDebugFlag) dwStartupFlags |= DEBUG_PROCESS; if (userCredentials.strUsername.length() > 0) { if( !::CreateProcessWithLogonW( userCredentials.strUsername.c_str(), userCredentials.strDomain.length() > 0 ? userCredentials.strDomain.c_str() : NULL, userCredentials.password.c_str(), userCredentials.netOnly? LOGON_NETCREDENTIALS_ONLY : LOGON_WITH_PROFILE, NULL, const_cast<wchar_t*>(strCmdLine.c_str()), dwStartupFlags, s_environmentBlock.get(), (strStartupDir.length() > 0) ? const_cast<wchar_t*>(strStartupDir.c_str()) : NULL, &si, &pi)) { Win32Exception err(::GetLastError()); throw ConsoleException(boost::str(boost::wformat(Helpers::LoadStringW(IDS_ERR_CANT_START_SHELL_AS_USER)) % strShellCmdLine % userCredentials.user % err.what())); } } else { if (!::CreateProcess( NULL, const_cast<wchar_t*>(strCmdLine.c_str()), NULL, NULL, FALSE, dwStartupFlags, s_environmentBlock.get(), (strStartupDir.length() > 0) ? const_cast<wchar_t*>(strStartupDir.c_str()) : NULL, &si, &pi)) { Win32Exception err(::GetLastError()); throw ConsoleException(boost::str(boost::wformat(Helpers::LoadString(IDS_ERR_CANT_START_SHELL)) % strShellCmdLine % err.what())); } } }