From faf0a7f02e9ecf07531bc6f8c72cbe83e958528a Mon Sep 17 00:00:00 2001 From: Rami Abughazaleh Date: Sun, 29 Jun 2014 22:37:09 -0700 Subject: [PATCH] #75: Added the ability to set the current working directory for command components --- CHANGELOG.md | 1 + InstallerLib/ComponentCmd.cs | 11 ++ .../ShellUtilUnitTests.cpp | 102 +++++++++++++++++- .../ShellUtilUnitTests.h | 8 ++ dni.sln | 1 + dotNetInstallerLib/CmdComponent.cpp | 5 +- dotNetInstallerLib/CmdComponent.h | 2 + dotNetInstallerLib/ProcessComponent.cpp | 12 +-- dotNetInstallerLib/ProcessComponent.h | 7 +- dotNetInstallerToolsLib/ShellUtil.cpp | 40 +++++-- dotNetInstallerToolsLib/ShellUtil.h | 6 +- 11 files changed, 169 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b9f296..434ca70e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ---- diff --git a/InstallerLib/ComponentCmd.cs b/InstallerLib/ComponentCmd.cs index 21a53142..b022038c 100644 --- a/InstallerLib/ComponentCmd.cs +++ b/InstallerLib/ComponentCmd.cs @@ -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")] @@ -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()); @@ -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); diff --git a/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.cpp b/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.cpp index 9b8376bc..05cd2a06 100644 --- a/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.cpp +++ b/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.cpp @@ -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"); @@ -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() @@ -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); @@ -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); } \ No newline at end of file diff --git a/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.h b/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.h index 59da16c8..d5dee04f 100644 --- a/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.h +++ b/UnitTests/dotNetInstallerToolsLibUnitTests/ShellUtilUnitTests.h @@ -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(); @@ -25,6 +29,10 @@ namespace DVLib void testRunCmd(); void testRunCmdWithHiddenWindow(); void testShellCmdWithHiddenWindow(); + void testRunCmdWithoutWorkingDirectorySpecified(); + void testRunCmdWithWorkingDirectorySpecified(); + void testShellCmdWithoutWorkingDirectorySpecified(); + void testShellCmdWithWorkingDirectorySpecified(); }; } } diff --git a/dni.sln b/dni.sln index a17e8e57..fdc67dc4 100644 --- a/dni.sln +++ b/dni.sln @@ -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 diff --git a/dotNetInstallerLib/CmdComponent.cpp b/dotNetInstallerLib/CmdComponent.cpp index 440cbcd9..1457190d 100644 --- a/dotNetInstallerLib/CmdComponent.cpp +++ b/dotNetInstallerLib/CmdComponent.cpp @@ -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) @@ -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); diff --git a/dotNetInstallerLib/CmdComponent.h b/dotNetInstallerLib/CmdComponent.h index 7d30c69c..523c755c 100644 --- a/dotNetInstallerLib/CmdComponent.h +++ b/dotNetInstallerLib/CmdComponent.h @@ -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 diff --git a/dotNetInstallerLib/ProcessComponent.cpp b/dotNetInstallerLib/ProcessComponent.cpp index fa7e551a..b140d96c 100644 --- a/dotNetInstallerLib/ProcessComponent.cpp +++ b/dotNetInstallerLib/ProcessComponent.cpp @@ -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 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; } } \ No newline at end of file diff --git a/dotNetInstallerLib/ProcessComponent.h b/dotNetInstallerLib/ProcessComponent.h index 5fb03e08..9a724760 100644 --- a/dotNetInstallerLib/ProcessComponent.h +++ b/dotNetInstallerLib/ProcessComponent.h @@ -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); +}; \ No newline at end of file diff --git a/dotNetInstallerToolsLib/ShellUtil.cpp b/dotNetInstallerToolsLib/ShellUtil.cpp index 7dc6d150..61473831 100644 --- a/dotNetInstallerToolsLib/ShellUtil.cpp +++ b/dotNetInstallerToolsLib/ShellUtil.cpp @@ -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 { @@ -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 @@ -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) @@ -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), @@ -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"); @@ -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; diff --git a/dotNetInstallerToolsLib/ShellUtil.h b/dotNetInstallerToolsLib/ShellUtil.h index dd805409..32db1e81 100644 --- a/dotNetInstallerToolsLib/ShellUtil.h +++ b/dotNetInstallerToolsLib/ShellUtil.h @@ -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); }