Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature issue177 #179

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dcdb37b
Implemented support for `console` attribute for `<exec>` elements.
end2endzone Sep 22, 2024
45b1704
Created dummy IProcessLauncherService interface in preparation for #177.
end2endzone Sep 22, 2024
8117efe
Moved OS specific code from ActionOpen to WindowsProcessLauncherServi…
end2endzone Sep 22, 2024
dd3656d
Added more error messages in WindowsProcessLauncherService.cpp.
end2endzone Sep 22, 2024
86151f4
Added method `StartProcess()` to interface `IProcessLauncherService`.
end2endzone Sep 23, 2024
fe0f6a7
Split code from `WindowsProcessLauncherService::StartProcess()` into …
end2endzone Sep 24, 2024
2374454
Implemented `console=false` attribute handling for ActionExecute.cpp.…
end2endzone Sep 25, 2024
ac0a788
Updated documentation for #177
end2endzone Sep 25, 2024
d9ebea7
* Fixed issue #177: Execute a console program without showing a window.
end2endzone Sep 25, 2024
0bbeb19
* Fixed issue #178: Exec action should expose the created process id.
end2endzone Sep 25, 2024
319f594
Modified tests that starts processes to use the new `console` attribu…
end2endzone Sep 26, 2024
6375f36
Add code to trace execusion of test `TestPlugins.testProcess()` which…
end2endzone Sep 28, 2024
4a7afe8
Created new ConsoleLoggerService that can be used for debugging on CI…
end2endzone Sep 28, 2024
25ca469
Temporary modified TestPlugins.cpp for high debugging on AppVeyor.
end2endzone Sep 28, 2024
77d23ba
Fixed a small bug in ConsoleLoggerService
end2endzone Sep 28, 2024
497a46c
Fixed typo.
end2endzone Sep 28, 2024
444e17a
Temporary added verbose function logging for sa_plugin_process.cpp pl…
end2endzone Sep 28, 2024
10800f6
Modified arguments.debugger to show the current directory.
end2endzone Sep 28, 2024
6dd793b
Now using mspaint.exe as default executable to start/stop while runni…
end2endzone Sep 28, 2024
c752c0f
Replaced `calc.exe` example by `mspaint.exe` as per comment in #178
end2endzone Sep 28, 2024
218d81c
Revert "Temporary added verbose function logging for sa_plugin_proces…
end2endzone Sep 28, 2024
3872670
Revert "Temporary modified TestPlugins.cpp for high debugging on AppV…
end2endzone Sep 28, 2024
f49dda1
Revert "Add code to trace execusion of test `TestPlugins.testProcess(…
end2endzone Sep 28, 2024
b690f7f
Removed references to `KillCalculatorProcess()` and `StartCalculatorP…
end2endzone Sep 29, 2024
23c7e36
Renamed `KillShellAnythingArgumentsDebuggerProcess()` to `KillArgumen…
end2endzone Sep 29, 2024
d0d1bfa
Fixed unit test `TestTools.testArgumentsDebugger()` in release config…
end2endzone Sep 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added method StartProcess() to interface IProcessLauncherService.
Moved code specific to Windows api to WindowsProcessLauncherService.
Modified ActionExecute::Execute() to delegate to WindowsProcessLauncherService.
  • Loading branch information
end2endzone committed Sep 23, 2024
commit 86151f48cb1dc79a3fcb2381624711a8cd39e118
129 changes: 27 additions & 102 deletions src/core/ActionExecute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
#include "PropertyManager.h"
#include "ObjectFactory.h"
#include "LoggerHelper.h"

#include <windows.h>
#include "SaUtils.h"

#include "tinyxml2.h"
using namespace tinyxml2;
Expand Down Expand Up @@ -147,31 +146,6 @@ namespace shellanything
}

bool ActionExecute::Execute(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string verb = pmgr.Expand(mVerb);
std::string timeout = pmgr.Expand(mTimeout);

// Validate that timeout is valid
if (!timeout.empty())
{
uint32_t tmp = 0;
bool parsed = ra::strings::Parse(timeout, tmp);
if (!parsed)
{
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout << "'.";
return false;
}
}

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

bool ActionExecute::ExecuteVerb(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
Expand All @@ -181,76 +155,25 @@ namespace shellanything
std::string wait = pmgr.Expand(mWait);
std::string timeout_str = pmgr.Expand(mTimeout);

std::wstring pathW = ra::unicode::Utf8ToUnicode(path);
std::wstring argumentsW = ra::unicode::Utf8ToUnicode(arguments);
std::wstring basedirW = ra::unicode::Utf8ToUnicode(basedir);
std::wstring verbW = ra::unicode::Utf8ToUnicode(verb);

SHELLEXECUTEINFOW info = { 0 };

info.cbSize = sizeof(SHELLEXECUTEINFOW);

info.fMask |= SEE_MASK_NOCLOSEPROCESS;
info.fMask |= SEE_MASK_NOASYNC;
info.fMask |= SEE_MASK_FLAG_DDEWAIT;

info.hwnd = HWND_DESKTOP;
info.nShow = SW_SHOWDEFAULT;
info.lpFile = pathW.c_str();

//Print execute values in the logs
SA_LOG(INFO) << "Path: " << path;
if (!verb.empty())
IProcessLauncherService* process_launcher_service = App::GetInstance().GetProcessLauncherService();
if (process_launcher_service == NULL)
{
info.lpVerb = verbW.c_str(); // Verb
SA_LOG(INFO) << "Verb: " << verb;
}
if (!arguments.empty())
{
info.lpParameters = argumentsW.c_str(); // Arguments
SA_LOG(INFO) << "Arguments: " << arguments;
}
if (!basedir.empty())
{
info.lpDirectory = basedirW.c_str(); // Default directory
SA_LOG(INFO) << "Basedir: " << basedir;
}

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

// Check valid process
success = (pId != ra::process::INVALID_PROCESS_ID);
if (!success)
{
SA_LOG(WARNING) << "Failed to create process.";
SA_LOG(ERROR) << "No Process Launcher service configured for creating process.";
return false;
}
SA_LOG(INFO) << "Process created. PID=" << pId;

// Check for wait exit code
bool wait_success = WaitForExit(pId);
if (!wait_success)
// Validate that timeout is valid
if (!timeout_str.empty())
{
SA_LOG(WARNING) << "Timed out! The process with PID=" << pId << " has failed to exit before the specified timeout.";
return false;
uint32_t tmp = 0;
bool parsed = ra::strings::Parse(timeout_str, tmp);
if (!parsed)
{
SA_LOG(ERROR) << "Failed parsing time out value: '" << timeout_str << "'.";
return false;
}
}

return wait_success;
}

bool ActionExecute::ExecuteProcess(const SelectionContext& context) const
{
PropertyManager& pmgr = PropertyManager::GetInstance();
std::string path = pmgr.Expand(mPath);
std::string basedir = pmgr.Expand(mBaseDir);
std::string arguments = pmgr.Expand(mArguments);
std::string wait = pmgr.Expand(mWait);
std::string timeout_str = pmgr.Expand(mTimeout);

bool basedir_missing = basedir.empty();
bool arguments_missing = arguments.empty();

Expand Down Expand Up @@ -295,6 +218,10 @@ namespace shellanything

//Print execute values in the logs
SA_LOG(INFO) << "Path: " << path;
if (!verb.empty())
{
SA_LOG(INFO) << "Verb: " << verb;
}
if (!arguments.empty())
{
SA_LOG(INFO) << "Arguments: " << arguments;
Expand All @@ -304,25 +231,23 @@ namespace shellanything
SA_LOG(INFO) << "Basedir: " << basedir;
}

//Execute and get the pid
uint32_t pId = ra::process::INVALID_PROCESS_ID;
if (arguments_missing)
{
pId = ra::process::StartProcessUtf8(path, basedir);
}
else
{
pId = ra::process::StartProcessUtf8(path, basedir, arguments);
}
// Prepare options for process launcher service
PropertyStore options;
if (!verb.empty())
options.SetProperty("verb", verb);

// Call the process launcher service
IProcessLauncherService::ProcessLaunchResult result = { 0 };
bool success = process_launcher_service->StartProcess(path, basedir, arguments, options, &result);

// Check valid process
bool success = (pId != ra::process::INVALID_PROCESS_ID);
if (!success)
{
SA_LOG(WARNING) << "Failed to create process.";
return false;
}
SA_LOG(INFO) << "Process created. PID=" << pId;
uint32_t pId = result.pId;
SA_LOG(INFO) << "Process created. PID=" << pId << " (" << ToHexString(pId) << ")";

// Check for wait exit code
bool wait_success = WaitForExit(pId);
Expand Down
15 changes: 0 additions & 15 deletions src/core/ActionExecute.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,6 @@ namespace shellanything
void SetTimeout(const std::string& value);

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

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

/// <summary>
/// Wait for the process to exit, if required.
Expand Down
12 changes: 12 additions & 0 deletions src/core/IProcessLauncherService.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "shellanything/export.h"
#include "shellanything/config.h"
#include "PropertyStore.h"
#include "Enums.h"

#include <string>
Expand Down Expand Up @@ -59,6 +60,17 @@ namespace shellanything
uint32_t pId; // PROCESS ID
};

/// <summary>
/// Start the given process.
/// </summary>
/// <param name="path">The path to the process to start.</param>
/// <param name="basedir">The base directory for the process to start in.</param>
/// <param name="arguments">The arguments for the process.</param>
/// <param name="args">A PropertyStore which contains optional settings for the start process.</param>
/// <param name="result">The optional result of the process launch.</param>
/// <returns>Returns true if the process was started. Returns false otherwise.</returns>
virtual bool StartProcess(const std::string& path, const std::string& basedir, const std::string& arguments, PropertyStore& options, ProcessLaunchResult* result = NULL) const = 0;

/// <summary>
/// Open a document with the default system application.
/// </summary>
Expand Down
105 changes: 102 additions & 3 deletions src/windows/WindowsProcessLauncherService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace shellanything
{
}

std::string GetErrorMessageUtf8(DWORD dwMessageId)
std::string WindowsProcessLauncherService::GetErrorMessageUtf8(uint32_t dwMessageId) const
{
LPWSTR lpMessageBuffer = NULL;

Expand All @@ -67,6 +67,105 @@ namespace shellanything
return message;
}

bool WindowsProcessLauncherService::StartProcess(const std::string& path, const std::string& basedir, const std::string& arguments, PropertyStore& options, ProcessLaunchResult* result) const
{
std::wstring pathW = ra::unicode::Utf8ToUnicode(path);
std::wstring basedirW = ra::unicode::Utf8ToUnicode(basedir);
std::wstring argumentsW = ra::unicode::Utf8ToUnicode(arguments);

std::wstring verbW = ra::unicode::Utf8ToUnicode(options.GetProperty("verb"));

bool success = false;
HANDLE hProcess = NULL;
DWORD pId = 0;

const char* launch_api_function = "";

// If a verb was specified
if (!verbW.empty())
{
// We must use ShellExecuteEx api
launch_api_function = "ShellExecuteExW";

SHELLEXECUTEINFOW info = { 0 };

info.cbSize = sizeof(SHELLEXECUTEINFOW);

info.fMask |= SEE_MASK_NOCLOSEPROCESS;
info.fMask |= SEE_MASK_NOASYNC;
info.fMask |= SEE_MASK_FLAG_DDEWAIT;

info.hwnd = HWND_DESKTOP;
info.nShow = SW_SHOWDEFAULT;
info.lpFile = pathW.c_str();

info.lpVerb = verbW.c_str(); // Verb
info.lpParameters = argumentsW.c_str(); // Arguments
info.lpDirectory = basedirW.c_str(); // Default directory

success = (ShellExecuteExW(&info) == TRUE);
if (success)
hProcess = info.hProcess;
}
else
{
// We use CreateProcessEx api
launch_api_function = "CreateProcessW";

//build the full command line
std::string command;

command += path;
if (path.find(" ") != std::string::npos)
{
command.insert(0, 1, '\"');
command += "\"";
}

if (!arguments.empty())
{
command += " ";
command += arguments;
}

const std::wstring commandW = ra::unicode::Utf8ToUnicode(command);

//launch a new process with the command line
PROCESS_INFORMATION pi = { 0 };
STARTUPINFOW si = { 0 };
si.cb = sizeof(STARTUPINFOW);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOWDEFAULT; //SW_SHOW, SW_SHOWNORMAL
static const DWORD creation_flags = 0; //EXTENDED_STARTUPINFO_PRESENT
success = (CreateProcessW(NULL, (wchar_t*)commandW.c_str(), NULL, NULL, FALSE, creation_flags, NULL, basedirW.c_str(), &si, &pi) != 0);
if (success)
{
hProcess = pi.hProcess;

//Wait for the application to initialize properly
WaitForInputIdle(hProcess, INFINITE);
}
}

// inform the caller of the result on success
if (success && result)
{
pId = GetProcessId(hProcess);
result->pId = pId;
}

// Log a windows specific error in case of failure.
if (!success)
{
DWORD dwLastError = ::GetLastError();
std::string sErrorMessage = GetErrorMessageUtf8((uint32_t)dwLastError);

SA_LOG(ERROR) << "Failed to call " << launch_api_function << "() for value '" << path << "', Error " << ToHexString(dwLastError) << ".Description: " << sErrorMessage << ".";
}

return success;
}

bool WindowsProcessLauncherService::OpenDocument(const std::string& path, ProcessLaunchResult* result) const
{
bool success = OpenPath(path, result);
Expand Down Expand Up @@ -102,11 +201,11 @@ namespace shellanything
result->pId = dwPid;
}

// Log a windows specific error in failure.
// Log a windows specific error in case of failure.
if (!success)
{
DWORD dwLastError = ::GetLastError();
std::string sErrorMessage = GetErrorMessageUtf8(dwLastError);
std::string sErrorMessage = GetErrorMessageUtf8((uint32_t)dwLastError);

SA_LOG(ERROR) << "Failed to call ShellExecuteExW() for value '" << path << "', Error " << ToHexString(dwLastError) << ". Description: " << sErrorMessage << ".";
}
Expand Down
13 changes: 13 additions & 0 deletions src/windows/WindowsProcessLauncherService.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ namespace shellanything
WindowsProcessLauncherService& operator=(const WindowsProcessLauncherService&);
public:

/// <summary>
/// Start the given process.
/// </summary>
/// <param name="path">The path to the process to start.</param>
/// <param name="basedir">The base directory for the process to start in.</param>
/// <param name="arguments">The arguments for the process.</param>
/// <param name="args">A PropertyStore which contains optional settings for the start process.</param>
/// <param name="result">The optional result of the process launch.</param>
/// <returns>Returns true if the process was started. Returns false otherwise.</returns>
virtual bool StartProcess(const std::string& path, const std::string& basedir, const std::string& arguments, PropertyStore& options, ProcessLaunchResult* result = NULL) const;

/// <summary>
/// Open a document with the default system application.
/// </summary>
Expand Down Expand Up @@ -76,6 +87,8 @@ namespace shellanything
/// <returns>Returns true if the given url was opened with the system's default application. Returns false otherwise.</returns>
virtual bool OpenUrl(const std::string& path, ProcessLaunchResult* result = NULL) const;

private:
std::string GetErrorMessageUtf8(uint32_t dwMessageId) const;
};

} //namespace shellanything
Expand Down