Skip to content

Commit

Permalink
#75: Added the ability to set the current working directory for comma…
Browse files Browse the repository at this point in the history
…nd components
  • Loading branch information
icnocop committed Jun 30, 2014
1 parent 6430d5f commit faf0a7f
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@ Features
--------

* [#46](https://github.com/dblock/dotnetinstaller/pull/46) - Added support for hiding the application window for ShellExecute execution method for command components - [@icnocop](https://github.com/icnocop).
* [#75](https://github.com/dblock/dotnetinstaller/issues/75) - Added the ability to set the current working directory for command components - [@icnocop](https://github.com/icnocop).

Bugs
----
Expand Down
11 changes: 11 additions & 0 deletions InstallerLib/ComponentCmd.cs
Expand Up @@ -69,6 +69,15 @@ public string uninstall_command_basic
set { m_uninstall_command_basic = value; }
}

private string m_working_directory;
[Description("The working directory for the process. Defaults to the current working directory of the bootstrapper.")]
[Category("Runtime")]
public string working_directory
{
get { return m_working_directory; }
set { m_working_directory = value; }
}

private bool m_hide_window = false;
[Description("Indicates whether to hide the application window from being displayed when run.")]
[Category("Runtime")]
Expand Down Expand Up @@ -131,6 +140,7 @@ protected override void OnXmlWriteTag(XmlWriterEventArgs e)
e.XmlWriter.WriteAttributeString("uninstall_command", m_uninstall_command);
e.XmlWriter.WriteAttributeString("uninstall_command_silent", m_uninstall_command_silent);
e.XmlWriter.WriteAttributeString("uninstall_command_basic", m_uninstall_command_basic);
e.XmlWriter.WriteAttributeString("working_directory", m_working_directory);
e.XmlWriter.WriteAttributeString("returncodes_success", m_returncodes_success);
e.XmlWriter.WriteAttributeString("returncodes_reboot", m_returncodes_reboot);
e.XmlWriter.WriteAttributeString("hide_window", m_hide_window.ToString());
Expand All @@ -147,6 +157,7 @@ protected override void OnXmlReadTag(XmlElementEventArgs e)
ReadAttributeValue(e, "uninstall_command", ref m_uninstall_command);
ReadAttributeValue(e, "uninstall_command_silent", ref m_uninstall_command_silent);
ReadAttributeValue(e, "uninstall_command_basic", ref m_uninstall_command_basic);
ReadAttributeValue(e, "working_directory", ref m_working_directory);
ReadAttributeValue(e, "returncodes_success", ref m_returncodes_success);
ReadAttributeValue(e, "returncodes_reboot", ref m_returncodes_reboot);
ReadAttributeValue(e, "hide_window", ref m_hide_window);
Expand Down
102 changes: 98 additions & 4 deletions UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.cpp
Expand Up @@ -23,7 +23,10 @@ void ShellUtilUnitTests::testExpandEnvironmentVariables()
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"") == L"");
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%%") == L"%%");
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%%%") == L"%%%");
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%" + DVLib::GenerateGUIDStringW() + L"%") == L"");

std::wstring guid = DVLib::GenerateGUIDStringW();
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%" + guid + L"%") == L"%" + guid + L"%");
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%cd%") == L"%cd%");
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%COMPUTERNAME%") == DVLib::GetEnvironmentVariableW(L"COMPUTERNAME"));
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%COMPUTERNAME%%COMPUTERNAME%") == DVLib::GetEnvironmentVariableW(L"COMPUTERNAME") + DVLib::GetEnvironmentVariableW(L"COMPUTERNAME"));
CPPUNIT_ASSERT(DVLib::ExpandEnvironmentVariables(L"%COMPUTERNAME") == L"%COMPUTERNAME");
Expand Down Expand Up @@ -75,7 +78,7 @@ void ShellUtilUnitTests::testExecCmd()
CPPUNIT_ASSERT(123 == DVLib::ExecCmd(L"cmd.exe /C exit /b 123"));

// hide window
CPPUNIT_ASSERT(456 == DVLib::ExecCmd(L"cmd.exe /C exit /b 456", SW_HIDE));
CPPUNIT_ASSERT(456 == DVLib::ExecCmd(L"cmd.exe /C exit /b 456", L"", SW_HIDE));
}

void ShellUtilUnitTests::testShellCmd()
Expand All @@ -96,7 +99,7 @@ void ShellUtilUnitTests::testRunCmdWithHiddenWindow()

// Act
PROCESS_INFORMATION pi = { 0 };
DVLib::RunCmd(L"cmd.exe /C ping -n 6 127.0.0.1 > nul && exit /b 0", & pi, 0, nShow);
DVLib::RunCmd(L"cmd.exe /C ping -n 6 127.0.0.1 > nul && exit /b 0", & pi, 0, L"", nShow);
auto_handle pi_thread(pi.hThread);
auto_handle pi_process(pi.hProcess);

Expand All @@ -114,11 +117,102 @@ void ShellUtilUnitTests::testShellCmdWithHiddenWindow()
HANDLE hProcess;

// Act
DVLib::ShellCmd(L"\"cmd.exe\" /C ping -n 6 127.0.0.1 > nul", NULL, &hProcess, NULL, nShow);
DVLib::ShellCmd(L"\"cmd.exe\" /C ping -n 6 127.0.0.1 > nul", NULL, &hProcess, NULL, L"", nShow);
auto_handle pi_process(hProcess);

// Assert
CPPUNIT_ASSERT(hProcess != NULL);
CPPUNIT_ASSERT(NULL == FindWindow::FindWindowFromProcess(hProcess));
CPPUNIT_ASSERT(WAIT_OBJECT_0 == ::WaitForSingleObject(hProcess, INFINITE));
}

void ShellUtilUnitTests::testRunCmdWithoutWorkingDirectorySpecified()
{
// Arrange
std::wstring working_directory = DVLib::GetCurrentDirectoryW();
PROCESS_INFORMATION pi = { 0 };
std::wstring command = DVLib::FormatMessage(
L"cmd.exe /C if '%%cd%%'=='%s' (exit /b 0) else (echo '%%cd%%'!='%s' && exit /b 1)",
working_directory.c_str(),
working_directory.c_str());

// Act
DVLib::RunCmd(command, &pi, 0);
auto_handle pi_thread(pi.hThread);
auto_handle pi_process(pi.hProcess);

// Assert
CPPUNIT_ASSERT(pi.dwProcessId > 0);
CPPUNIT_ASSERT(WAIT_OBJECT_0 == ::WaitForSingleObject(pi.hProcess, INFINITE));

DWORD exitCode = 0;
CHECK_WIN32_BOOL(::GetExitCodeProcess(pi.hProcess, &exitCode),
L"GetExitCodeProcess");
CPPUNIT_ASSERT(exitCode == 0);
}

void ShellUtilUnitTests::testRunCmdWithWorkingDirectorySpecified()
{
// Arrange
std::wstring working_directory = DVLib::GetTemporaryDirectoryW();

// Act
PROCESS_INFORMATION pi = { 0 };
DVLib::RunCmd(L"cmd.exe /C if '%%cd%%'=='%%temp%%' (exit /b 0) else (echo '%%cd%%'!='%%temp%%' && exit /b 1)", &pi, 0, working_directory);
auto_handle pi_thread(pi.hThread);
auto_handle pi_process(pi.hProcess);

// Assert
CPPUNIT_ASSERT(pi.dwProcessId > 0);
CPPUNIT_ASSERT(WAIT_OBJECT_0 == ::WaitForSingleObject(pi.hProcess, INFINITE));

DWORD exitCode = 0;
CHECK_WIN32_BOOL(::GetExitCodeProcess(pi.hProcess, &exitCode),
L"GetExitCodeProcess");
CPPUNIT_ASSERT(exitCode == 0);
}

void ShellUtilUnitTests::testShellCmdWithoutWorkingDirectorySpecified()
{
// Arrange
std::wstring working_directory = DVLib::GetCurrentDirectoryW();
PROCESS_INFORMATION pi = { 0 };
std::wstring command = DVLib::FormatMessage(
L"cmd.exe /C if '%%cd%%'=='%s' (exit /b 0) else (echo '%%cd%%'!='%s' && exit /b 1)",
working_directory.c_str(),
working_directory.c_str());
HANDLE hProcess;

// Act
DVLib::ShellCmd(command.c_str(), NULL, &hProcess, NULL);
auto_handle pi_process(hProcess);

// Assert
CPPUNIT_ASSERT(hProcess != NULL);
CPPUNIT_ASSERT(WAIT_OBJECT_0 == ::WaitForSingleObject(hProcess, INFINITE));

DWORD exitCode = 0;
CHECK_WIN32_BOOL(::GetExitCodeProcess(hProcess, &exitCode),
L"GetExitCodeProcess");
CPPUNIT_ASSERT(exitCode == 0);
}

void ShellUtilUnitTests::testShellCmdWithWorkingDirectorySpecified()
{
// Arrange
std::wstring working_directory = DVLib::GetTemporaryDirectoryW();
HANDLE hProcess;

// Act
DVLib::ShellCmd(L"\"cmd.exe\" /C if '%%cd%%'=='%%temp%%' (exit /b 0) else (echo '%%cd%%'!='%%temp%%' && exit /b 1)", NULL, &hProcess, NULL, working_directory);
auto_handle pi_process(hProcess);

// Assert
CPPUNIT_ASSERT(hProcess != NULL);
CPPUNIT_ASSERT(WAIT_OBJECT_0 == ::WaitForSingleObject(hProcess, INFINITE));

DWORD exitCode = 0;
CHECK_WIN32_BOOL(::GetExitCodeProcess(hProcess, &exitCode),
L"GetExitCodeProcess");
CPPUNIT_ASSERT(exitCode == 0);
}
Expand Up @@ -15,6 +15,10 @@ namespace DVLib
CPPUNIT_TEST( testShellCmd );
CPPUNIT_TEST( testRunCmdWithHiddenWindow );
CPPUNIT_TEST( testShellCmdWithHiddenWindow );
CPPUNIT_TEST( testRunCmdWithoutWorkingDirectorySpecified );
CPPUNIT_TEST( testRunCmdWithWorkingDirectorySpecified );
CPPUNIT_TEST( testShellCmdWithoutWorkingDirectorySpecified );
CPPUNIT_TEST( testShellCmdWithWorkingDirectorySpecified );
CPPUNIT_TEST_SUITE_END();
public:
void testGetEnvironmentVariable();
Expand All @@ -25,6 +29,10 @@ namespace DVLib
void testRunCmd();
void testRunCmdWithHiddenWindow();
void testShellCmdWithHiddenWindow();
void testRunCmdWithoutWorkingDirectorySpecified();
void testRunCmdWithWorkingDirectorySpecified();
void testShellCmdWithoutWorkingDirectorySpecified();
void testShellCmdWithWorkingDirectorySpecified();
};
}
}
1 change: 1 addition & 0 deletions dni.sln
Expand Up @@ -79,6 +79,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Content", "Content", "{64CA
Documentation\Content\Checks.aml = Documentation\Content\Checks.aml
Documentation\Content\ComponentRules.aml = Documentation\Content\ComponentRules.aml
Documentation\Content\Components.aml = Documentation\Content\Components.aml
Documentation\Content\ComponentsAdministrator.aml = Documentation\Content\ComponentsAdministrator.aml
Documentation\Content\ComponentsReboot.aml = Documentation\Content\ComponentsReboot.aml
Documentation\Content\ConfigurationAttributes.aml = Documentation\Content\ConfigurationAttributes.aml
Documentation\Content\Contributing.aml = Documentation\Content\Contributing.aml
Expand Down
5 changes: 3 additions & 2 deletions dotNetInstallerLib/CmdComponent.cpp
Expand Up @@ -41,7 +41,7 @@ void CmdComponent::Exec()

LOG(L"Executing: " << l_command);

ProcessComponent::ExecCmd(l_command, execution_method, disable_wow64_fs_redirection, hide_window ? SW_HIDE : SW_SHOWNORMAL);
ProcessComponent::ExecCmd(l_command, execution_method, disable_wow64_fs_redirection, working_directory, hide_window ? SW_HIDE : SW_SHOWNORMAL);
};

void CmdComponent::Load(TiXmlElement * node)
Expand All @@ -51,7 +51,8 @@ void CmdComponent::Load(TiXmlElement * node)
command_basic = node->Attribute("command_basic");
uninstall_command = node->Attribute("uninstall_command");
uninstall_command_silent = node->Attribute("uninstall_command_silent");
uninstall_command_basic = node->Attribute("uninstall_command_basic");
uninstall_command_basic = node->Attribute("uninstall_command_basic");
working_directory = node->Attribute("working_directory");
returncodes_success = node->Attribute("returncodes_success");
returncodes_reboot = node->Attribute("returncodes_reboot");
hide_window = XmlAttribute(node->Attribute("hide_window")).GetBoolValue(false);
Expand Down
2 changes: 2 additions & 0 deletions dotNetInstallerLib/CmdComponent.h
Expand Up @@ -12,6 +12,8 @@ class CmdComponent : public ProcessComponent
XmlAttribute uninstall_command;
XmlAttribute uninstall_command_silent;
XmlAttribute uninstall_command_basic;
// working directory
XmlAttribute working_directory;
// hide window
bool hide_window;
// wow64 fs redirection
Expand Down
12 changes: 6 additions & 6 deletions dotNetInstallerLib/ProcessComponent.cpp
Expand Up @@ -44,31 +44,31 @@ void ProcessComponent::Wait(DWORD /* tt */)
L"WaitForSingleObject");
}

void ProcessComponent::ExecCmd(std::wstring command, DVLib::CommandExecutionMethod executionMethod, bool disableWow64FsRedirection, int nShow)
void ProcessComponent::ExecCmd(std::wstring command, DVLib::CommandExecutionMethod executionMethod, bool disableWow64FsRedirection, const std::wstring& working_directory, int nShow)
{
if (disableWow64FsRedirection)
{
auto_any<Wow64NativeFS *, close_delete> wow64_native_fs(new Wow64NativeFS());
ExecCmdCore(command, executionMethod, nShow);
ExecCmdCore(command, executionMethod, working_directory, nShow);
}
else
{
ExecCmdCore(command, executionMethod, nShow);
ExecCmdCore(command, executionMethod, working_directory, nShow);
}
}

void ProcessComponent::ExecCmdCore(std::wstring command, DVLib::CommandExecutionMethod executionMethod, int nShow)
void ProcessComponent::ExecCmdCore(std::wstring command, DVLib::CommandExecutionMethod executionMethod, const std::wstring& working_directory, int nShow)
{
PROCESS_INFORMATION process_info;
switch (executionMethod)
{
case DVLib::CemCreateProcess:
DVLib::RunCmd(command, & process_info, 0, nShow);
DVLib::RunCmd(command, & process_info, 0, working_directory, nShow);
m_process_handle = process_info.hProcess;
::CloseHandle(process_info.hThread);
break;
case DVLib::CemShellExecute:
DVLib::ShellCmd(command, NULL, & m_process_handle, main_window, nShow);
DVLib::ShellCmd(command, NULL, & m_process_handle, main_window, working_directory, nShow);
break;
}
}
7 changes: 3 additions & 4 deletions dotNetInstallerLib/ProcessComponent.h
Expand Up @@ -12,8 +12,7 @@ class ProcessComponent : public Component
void Wait(DWORD tt = 1000);
int GetExitCode() const;
protected:
void ExecCmd(std::wstring command, DVLib::CommandExecutionMethod executionMethod, bool disableWow64FsRedirection, int nShow = SW_SHOWNORMAL);
void ExecCmd(std::wstring command, DVLib::CommandExecutionMethod executionMethod, bool disableWow64FsRedirection, const std::wstring& working_directory = L"", int nShow = SW_SHOWNORMAL);
private:
void ExecCmdCore(std::wstring command, DVLib::CommandExecutionMethod executionMethod, int nShow = SW_SHOWNORMAL);
};

void ExecCmdCore(std::wstring command, DVLib::CommandExecutionMethod executionMethod, const std::wstring& working_directory = L"", int nShow = SW_SHOWNORMAL);
};
40 changes: 33 additions & 7 deletions dotNetInstallerToolsLib/ShellUtil.cpp
Expand Up @@ -63,8 +63,17 @@ std::wstring DVLib::ExpandEnvironmentVariables(const std::wstring& s_in)
{
std::wstring name = s.substr(i + 1, j - i - 1);
std::wstring value = DVLib::GetEnvironmentVariable(name);
s.replace(i, j - i + 1, value);
i += value.length();

// if it's not an environment variable, just ignore it and let the command interpreter handle it
if (value != L"")
{
s.replace(i, j - i + 1, value);
i += value.length();
}
else
{
i = j + 1;
}
}
else
{
Expand All @@ -79,7 +88,7 @@ void DVLib::DetachCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi)
RunCmd(cmd, lpi, DETACHED_PROCESS);
}

void DVLib::RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi, int flags, int nShow)
void DVLib::RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi, int flags, const std::wstring& working_directory, int nShow)
{
// expand command line, using ShellExecuteEx API function with setting the flag
// SEE_MASK_DOENVSUBST does not work because environment variables can also be
Expand All @@ -101,7 +110,15 @@ void DVLib::RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi, int flags
PROCESS_INFORMATION pi = { 0 };

std::wstring cmd_expanded = DVLib::ExpandEnvironmentVariables(cmd);
CHECK_WIN32_BOOL(::CreateProcessW(NULL, & * cmd_expanded.begin(), NULL, NULL, FALSE, flags, NULL, NULL, & si, lpi == NULL ? & pi : lpi),

// set the current directory if it was specified
LPCWSTR lpCurrentDirectory = NULL;
if (working_directory != L"")
{
lpCurrentDirectory = working_directory.c_str();
}

CHECK_WIN32_BOOL(::CreateProcessW(NULL, & * cmd_expanded.begin(), NULL, NULL, FALSE, flags, NULL, lpCurrentDirectory, & si, lpi == NULL ? & pi : lpi),
L"CreateProcessW: " << cmd_expanded);

if (lpi == NULL)
Expand All @@ -111,10 +128,10 @@ void DVLib::RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi, int flags
}
}

DWORD DVLib::ExecCmd(const std::wstring& cmd, int nShow)
DWORD DVLib::ExecCmd(const std::wstring& cmd, const std::wstring& working_directory, int nShow)
{
PROCESS_INFORMATION pi = { 0 };
RunCmd(cmd, & pi, 0, nShow);
RunCmd(cmd, & pi, 0, working_directory, nShow);
auto_handle pi_thread(pi.hThread);
auto_handle pi_process(pi.hProcess);
CHECK_WIN32_BOOL(WAIT_OBJECT_0 == WaitForSingleObject(pi.hProcess, INFINITE),
Expand All @@ -125,7 +142,7 @@ DWORD DVLib::ExecCmd(const std::wstring& cmd, int nShow)
return dwExitCode;
}

void DVLib::ShellCmd(const std::wstring& cmd, int * rc, LPHANDLE lpProcessHandle, HWND hWnd, int nShow)
void DVLib::ShellCmd(const std::wstring& cmd, int * rc, LPHANDLE lpProcessHandle, HWND hWnd, const std::wstring& working_directory, int nShow)
{
std::wstring cmd_expanded = DVLib::ExpandEnvironmentVariables(cmd);
CHECK_BOOL(! cmd_expanded.empty(), L"Missing command");
Expand All @@ -139,6 +156,15 @@ void DVLib::ShellCmd(const std::wstring& cmd, int * rc, LPHANDLE lpProcessHandle
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE;
sei.hwnd = hWnd;

// set the current directory if it was specified
LPCWSTR lpCurrentDirectory = NULL;
if (working_directory != L"")
{
lpCurrentDirectory = working_directory.c_str();
}

sei.lpDirectory = lpCurrentDirectory;

sei.lpFile = cmd_file.c_str();
sei.lpParameters = cmd_args.size() == 2 ? cmd_args[1].c_str() : NULL;
Expand Down
6 changes: 3 additions & 3 deletions dotNetInstallerToolsLib/ShellUtil.h
Expand Up @@ -30,9 +30,9 @@ namespace DVLib
// detach a command, return process information
void DetachCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi = NULL);
// run a command, return process information
void RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi = NULL, int flags = 0, int nShow = SW_SHOWNORMAL);
void RunCmd(const std::wstring& cmd, LPPROCESS_INFORMATION lpi = NULL, int flags = 0, const std::wstring& working_directory = L"", int nShow = SW_SHOWNORMAL);
// execute a process, wait and return exit code
DWORD ExecCmd(const std::wstring& cmd, int nShow = SW_SHOWNORMAL);
DWORD ExecCmd(const std::wstring& cmd, const std::wstring& working_directory = L"", int nShow = SW_SHOWNORMAL);
// shell-execute a process
void ShellCmd(const std::wstring& cmd, int * rc = NULL, LPHANDLE lpProcessHandle = NULL, HWND hWnd = NULL, int nShow = SW_SHOWNORMAL);
void ShellCmd(const std::wstring& cmd, int * rc = NULL, LPHANDLE lpProcessHandle = NULL, HWND hWnd = NULL, const std::wstring& working_directory = L"", int nShow = SW_SHOWNORMAL);
}

0 comments on commit faf0a7f

Please sign in to comment.