Skip to content

Commit

Permalink
Modified ActionExecute::Execute() to delegate to ExecuteVerb() or Exe…
Browse files Browse the repository at this point in the history
…cuteProcess(). Created unit tests.
  • Loading branch information
end2endzone committed Nov 3, 2020
1 parent 23e3df9 commit bff3894
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 14 deletions.
11 changes: 10 additions & 1 deletion include/shellanything/ActionExecute.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,21 @@ namespace shellanything
void SetVerb(const std::string& iVerb);

private:
/// <summary>
/// Execute an application with ShellExecuteEx method.
/// This execute method supports verbs.
/// </summary>
/// <param name="iContext">The current context of execution.</param>
/// <returns>Returns true if the execution is successful. Returns false otherwise.</returns>
virtual bool ExecuteVerb(const Context & iContext) const;

/// <summary>
/// Execute an application with RapidAssist method.
/// This execute method does not supports verbs.
/// </summary>
/// <param name="iContext">The current context of execution.</param>
/// <returns>Returns true if the execution is successful. Returns false otherwise.</returns>
virtual bool StartProcess(const Context & iContext) const;
virtual bool ExecuteProcess(const Context & iContext) const;

private:
std::string mPath;
Expand Down
56 changes: 43 additions & 13 deletions src/ActionExecute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ namespace shellanything
}

bool ActionExecute::Execute(const Context& iContext) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string verb = pmgr.Expand(mVerb);

//If a verb was specified, delegate to VerbExecute(). Otherwise, use ProcessExecute().
if (verb.empty())
return ExecuteProcess(iContext);
else
return ExecuteVerb(iContext);
}

bool ActionExecute::ExecuteVerb(const Context& iContext) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
Expand All @@ -71,35 +83,44 @@ namespace shellanything
info.nShow = SW_SHOWDEFAULT;
info.lpFile = pathW.c_str();

//Print execute values in the logs
LOG(INFO) << "Exec: '" << path << "'.";

if (!verb.empty())
{
info.lpVerb = verbW.c_str(); // Verb
LOG(INFO) << "Verb: '" << verb << "'.";
}

if (!arguments.empty())
{
info.lpParameters = argumentsW.c_str(); // Arguments
LOG(INFO) << "Arguments: '" << arguments << "'.";
}

if (!basedir.empty())
{
info.lpDirectory = basedirW.c_str(); // Default directory
LOG(INFO) << "Basedir: '" << basedir << "'.";
}

BOOL success = ShellExecuteExW(&info);
return (success == TRUE);
//Execute and get the pid
bool success = (ShellExecuteExW(&info) == TRUE);
if (!success)
return false;
DWORD pId = GetProcessId(info.hProcess);

success = (pId != ra::process::INVALID_PROCESS_ID);
if (success)
{
LOG(INFO) << "Process created. PID=" << pId;
}

return success;
}

bool ActionExecute::StartProcess(const Context & iContext) const
bool ActionExecute::ExecuteProcess(const Context & iContext) const
{
PropertyManager & pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
std::string basedir = pmgr.Expand(mBaseDir);
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
std::string basedir = pmgr.Expand(mBaseDir);
std::string arguments = pmgr.Expand(mArguments);

bool basedir_missing = basedir.empty();
Expand Down Expand Up @@ -144,20 +165,29 @@ namespace shellanything
LOG(WARNING) << "attribute 'basedir' not specified.";
}

//debug
//Print execute values in the logs
LOG(INFO) << "Exec: '" << path << "'.";
if (!arguments.empty())
{
LOG(INFO) << "Arguments: '" << arguments << "'.";
}
if (!basedir.empty())
{
LOG(INFO) << "Basedir: '" << basedir << "'.";
}

//Execute and get the pid
uint32_t pId = ra::process::INVALID_PROCESS_ID;
if (arguments_missing)
{
LOG(INFO) << "Running '" << path << "' from directory '" << basedir << "'.";
pId = ra::process::StartProcessUtf8(path, basedir);
}
else
{
LOG(INFO) << "Running '" << path << "' from directory '" << basedir << "' with arguments '" << arguments << "'.";
pId = ra::process::StartProcessUtf8(path, basedir, arguments);
}

bool success = pId != ra::process::INVALID_PROCESS_ID;
bool success = (pId != ra::process::INVALID_PROCESS_ID);
if (success)
{
LOG(INFO) << "Process created. PID=" << pId;
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ add_executable(shellanything_unittest
${CONFIGURATION_TEST_FILES}
${SHELLANYTHING_PRIVATE_FILES}
main.cpp
TestActionExecute.cpp
TestActionExecute.h
TestActionFile.cpp
TestActionFile.h
TestBitmapCache.cpp
Expand Down
228 changes: 228 additions & 0 deletions test/TestActionExecute.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/**********************************************************************************
* MIT License
*
* Copyright (c) 2018 Antoine Beauchamp
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*********************************************************************************/

#include "TestActionExecute.h"
#include "shellanything/Context.h"
#include "shellanything/ActionExecute.h"
#include "PropertyManager.h"
#include "rapidassist/testing.h"
#include "rapidassist/filesystem_utf8.h"
#include "rapidassist/user.h"
#include "rapidassist/timing.h"
#include "rapidassist/environment.h"

#include <Windows.h>

namespace shellanything { namespace test
{

//--------------------------------------------------------------------------------------------------
void TestActionExecute::SetUp()
{
}
//--------------------------------------------------------------------------------------------------
void TestActionExecute::TearDown()
{
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionExecute, testBasic)
{
PropertyManager & pmgr = PropertyManager::GetInstance();

//Create a valid context
Context c;
Context::ElementList elements;
elements.push_back("C:\\Windows\\System32\\calc.exe");
c.SetElements(elements);

c.RegisterProperties();

//execute the action
ActionExecute ae;
ae.SetPath("C:\\Windows\\System32\\calc.exe");
ae.SetBaseDir("");
ae.SetArguments("");

bool executed = ae.Execute(c);
ASSERT_TRUE( executed );

//cleanup
ra::timing::Millisleep(500);
system("cmd.exe /c taskkill /IM calc.exe >NUL 2>NUL");
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionExecute, testBaseDir)
{
PropertyManager & pmgr = PropertyManager::GetInstance();

//Create a valid context
Context c;
Context::ElementList elements;
elements.push_back("C:\\Windows\\System32\\calc.exe");
c.SetElements(elements);

c.RegisterProperties();

std::string home_dir = ra::user::GetHomeDirectory();

//execute the action
ActionExecute ae;
ae.SetPath("calc.exe");
ae.SetBaseDir("C:\\Windows\\System32");
ae.SetArguments("");

bool executed = ae.Execute(c);
ASSERT_TRUE( executed );

//cleanup
ra::timing::Millisleep(500);
system("cmd.exe /c taskkill /IM calc.exe >NUL 2>NUL");
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionExecute, testArguments)
{
PropertyManager & pmgr = PropertyManager::GetInstance();

//Create a valid context
Context c;
Context::ElementList elements;
elements.push_back("C:\\Windows\\System32\\calc.exe");
c.SetElements(elements);

c.RegisterProperties();

std::string home_dir = ra::user::GetHomeDirectory();
std::string temp_dir = ra::filesystem::GetTemporaryDirectory();
std::string destination_path = temp_dir + "\\my_calc.exe";
std::string arguments = "/c copy C:\\Windows\\System32\\calc.exe " + destination_path + ">NUL 2>NUL";

//execute the action
ActionExecute ae;
ae.SetPath("cmd.exe");
ae.SetBaseDir(temp_dir);
ae.SetArguments(arguments);

bool executed = ae.Execute(c);
ASSERT_TRUE( executed );

//Wait for the copy to complete, with a timeout
static const double timeout_time = 5000; //ms
bool file_copied = false;
double timer_start = ra::timing::GetMillisecondsTimer();
double time_elapsed = ra::timing::GetMillisecondsTimer() - timer_start;
while(!file_copied && time_elapsed <= timeout_time)
{
file_copied = ra::filesystem::FileExists(destination_path.c_str());
ra::timing::Millisleep(500); //allow process to complete
time_elapsed = ra::timing::GetMillisecondsTimer() - timer_start; //evaluate elapsed time again
}

//Validate arguments
ASSERT_TRUE(file_copied);

//cleanup
ra::filesystem::DeleteFileUtf8(destination_path.c_str());
}
//--------------------------------------------------------------------------------------------------
TEST_F(TestActionExecute, testVerb)
{
//Skip this test if run on AppVeyor as it requires administrative (elevated) privileges.
if (ra::testing::IsAppVeyor() ||
ra::testing::IsJenkins() ||
ra::testing::IsTravis())
{
printf("Skipping tests as it requires administrative (elevated) privileges.\n");
return;
}

PropertyManager & pmgr = PropertyManager::GetInstance();

//Create a valid context
Context c;
Context::ElementList elements;
elements.push_back("C:\\Windows\\System32\\calc.exe");
c.SetElements(elements);

c.RegisterProperties();

std::string temp_dir = ra::filesystem::GetTemporaryDirectory();
std::string batch_file_filename = ra::testing::GetTestQualifiedName() + ".bat";
std::string result_file_filename = ra::testing::GetTestQualifiedName() + ".txt";
std::string batch_file_path = temp_dir + "\\" + batch_file_filename;
std::string result_file_path = temp_dir + "\\" + result_file_filename;
std::string arguments = "/c " + batch_file_filename + " >" + result_file_filename;

//This is a batch file that prints ADMIN if it is run in elevated mode or prints FAIL otherwise.
//Inspired from https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
const std::string content =
"@echo off\n"
"net session >nul 2>&1\n"
"if %errorLevel% == 0 (\n"
" echo ADMIN\n"
") else (\n"
" echo FAIL\n"
")\n";
ASSERT_TRUE( ra::filesystem::WriteTextFile(batch_file_path, content) );

//execute the action
ActionExecute ae;
ae.SetPath("cmd.exe");
ae.SetBaseDir(temp_dir);
ae.SetArguments(arguments);
ae.SetVerb("runas");

bool executed = ae.Execute(c);
ASSERT_TRUE( executed );

//Wait for the operation to complete, with a timeout
static const double timeout_time = 5000; //ms
bool result_file_found = false;
double timer_start = ra::timing::GetMillisecondsTimer();
double time_elapsed = ra::timing::GetMillisecondsTimer() - timer_start;
while(!result_file_found && time_elapsed <= timeout_time)
{
result_file_found = ra::filesystem::FileExists(result_file_path.c_str());
ra::timing::Millisleep(500); //allow process to complete
time_elapsed = ra::timing::GetMillisecondsTimer() - timer_start; //evaluate elapsed time again
}

//Validate arguments
ASSERT_TRUE(result_file_found);

//Read the result file
std::string result;
ASSERT_TRUE( ra::filesystem::ReadTextFile(result_file_path, result) );
ra::strings::Replace(result, ra::environment::GetLineSeparator(), "");
ra::strings::Replace(result, "\n", "");
static const std::string EXPECTED_RESULT = "ADMIN";
ASSERT_EQ(EXPECTED_RESULT, result);

//cleanup
ra::filesystem::DeleteFileUtf8(batch_file_path.c_str());
ra::filesystem::DeleteFileUtf8(result_file_path.c_str());
}
//--------------------------------------------------------------------------------------------------

} //namespace test
} //namespace shellanything
Loading

0 comments on commit bff3894

Please sign in to comment.