通过CreateProcess()运行批处理文件的多线程服务

时间:2010-12-08 05:11:25

标签: c++ multithreading visual-c++ windows-services batch-file

最后看到更新

我正在尝试运行以下批处理文件(名为boot_time.bat,与.exe位于同一目录中):

@echo off
cd %1
For /F %%I in ('Cscript boot_time.vbs //Nologo') Do Set var=%%I
set DATETIME=%var:~0,4%/%var:~4,2%/%var:~6,2% %var:~8,2%:%var:~10,2%:%var:~12,2%.%var:~15,3%
echo %DATETIME%

批处理文件采用一个参数,它是当前的工作目录。它的目的是通过boot_time.vbs脚本检索系统引导时间并将其格式化为通用日期格式。为了完整起见,这里是vbs文件的内容:

set objWMI = GetObject("winmgmts:\\.\root\cimv2")
set colOS = objWMI.InstancesOf("Win32_OperatingSystem")
for each objOS in colOS
    Wscript.Echo objOS.LastBootUpTime
NEXT

虽然我很欣赏有关替代(读取:更简单)方法的评论来检索系统启动时间,但请放心,我已经探索了所有途径(我遇到过)并且没有一个符合系统要求。

现在,问题的关键。当我尝试通过C ++运行.bat文件时,CreateProcess()返回1,但是批处理文件没有运行(我已经通过用一个简单的'start calc'替换boot_time.bat的内容来验证这一点,它仍然拒绝运行)。违规代码:

//run batch file
stringstream commandStream;
commandStream << "/C "                           //close window on termination
         << "\"" << batchFile.c_str() << "\" "   //batch file path inside ""
         << "\"" << processPath.c_str() << "\" " //working directory as argument inside ""
         << ">"                                  //redirect
         << "\"" << outFile.c_str() << "\"";     //output file inside ""

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );

BOOL ret = CreateProcess("cmd.exe", const_cast<char*>(commandStream.str().c_str()), NULL, NULL, TRUE, NULL, NULL, processPath.c_str(), &si, &pi);

WaitForSingleObject( pi.hProcess, INFINITE );

CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );

路径和命令行参数都很好(我可以调试打印它们并粘贴到cmd.exe中,它们按预期工作)。单步执行代码,CreateProcess()返回1,一切都正常运行,但批处理文件永远不会运行。

在搜索互联网的几个小时内,我遇到了从服务运行批处理文件时遇到的问题,但是我:

a)再也找不到所说的耳语了 b)会认为CreateProcess(“cmd.exe”,...)没有运行批处理文件,而是运行exe,剩下的就是命令提示符。

那么,知道发生了什么事吗?

哦,我正在运行VC ++ 6

更新1:
如果我勾选“允许服务与桌面交互”,并更改CreateProcess()以不隐藏控制台窗口(通过CREATE_NEW_CONSOLE),我会弹出命令提示符的瞬间。因此,CreateProcess()正在创建cmd.exe进程,但cmd.exe拒绝运行批处理文件。

更新2:
在加布的建议之后,我重新考虑了我的过程,并在某种程度上对其进行了简化,试图追查罪魁祸首。我删除了批处理文件,并将其合并到VBS文件中,现在是:

set objWMI = GetObject("winmgmts:\\.\root\cimv2")
set colOS = objWMI.InstancesOf("Win32_OperatingSystem")
Dim bootTime
for each objOS in colOS
   bootTime = objOS.LastBootUpTime

bootTime = mid(bootTime,1,4) & "/" & Mid(bootTime,5,2) & "/" & Mid(bootTime,7,2) & " " & Mid(bootTime,9,2) & ":" & Mid(bootTime,11,2) & ":" & Mid(bootTime,13,2) & "." & Mid(bootTime,16,3)
Wscript.Echo bootTime
NEXT

c ++代码基本没有变化,只是修改了命令行来调用WScript.exe而不是cmd.exe。我正在获取所有文件的完全限定路径,这会导致以下字符串:

"C:\WINDOWS\System32\Wscript.exe" //Nologo "c:\<repository_dir>\boot_time.vbs" >"C:\WINDOWS\TEMP\rts5FD.tmp"

(repository_dir故意遗漏名字)。另外,我只是使用Wscript进行测试,因为它会弹出一个消息框。正确使用Cscript.exe,输出重定向到临时文件。

CreateProcess不会导致弹出消息框,直接从命令行运行。

3 个答案:

答案 0 :(得分:0)

请仔细阅读MSDN's description of CreateProcess

中的这一行
  

因为argv [0]是模块名称,所以C程序员通常会重复模块名称作为命令行中的第一个标记。

这实现了您的代码应该是:

commandStream << "cmd.exe /C "

另见http://blogs.msdn.com/b/oldnewthing/archive/2006/05/15/597984.aspx

但是,我必须补充一点,我会拍摄一些我发现使用C ++运行批处理文件来执行VBScript以处理其输出的人。由于批处理文件只执行字符串操作,为什么不能在VBS或C ++中进行字符串操作?

或者,您可以避免批处理文件,只需使用以下代码直接调用cscript

set objWMI = GetObject("winmgmts:\\.\root\cimv2") 
set colOS = objWMI.InstancesOf("Win32_OperatingSystem") 
for each objOS in colOS 
    t = objOS.LastBootUpTime 
next
wscript.echo left(t, 4) & "/" & mid(t, 5, 2) & "/" & mid(t, 7, 2) & " " & _
             mid(t, 9, 2) & ":" & mid(t, 11, 2) & ":" & mid(t, 13, 2) & "." & _
             mid(t, 16, 3)

另一种选择是查询System\System Up Time性能计数器并从当前时间中减去其值。

答案 1 :(得分:0)

我设法找出C ++代码来完成所有这些。

与大多数C ++ COM代码一样,它变得非常冗长。但是你走了:

#define _WIN32_DCOM
#include <iostream>
#include <exception>
#include <string>

#include <windows.h>
#include <wbemidl.h>
# pragma comment(lib, "wbemuuid.lib")

void com_init() {
  HRESULT hr;
  hr = CoInitializeEx(0, COINIT_MULTITHREADED); 
  if (FAILED(hr)) {
    throw std::runtime_error("COM Error");
  }
  hr =  CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
  if (FAILED(hr)) {
    CoUninitialize();
    throw std::runtime_error("COM Error");
  }
}

std::wstring wmi_last_boot_time() {
  IWbemLocator *pLoc = NULL;
  IWbemServices *pSvc = NULL;
  IEnumWbemClassObject *pEnum = NULL;
  IWbemClassObject *pEach = NULL;

  std::wstring lastBootUpTime;

  try {
    HRESULT hr;
    // Get WMI object
    hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &pLoc);
    if (FAILED(hr)) {
      throw std::runtime_error("WMI: Unable to create WbemLocator");
    }
    hr = pLoc->ConnectServer(L"root\\cimv2", NULL, NULL, 0, NULL, 0, 0, &pSvc);
    if (FAILED(hr))  {
      throw std::runtime_error("WMI: Unable to connect");
    }

    // Exec query
    hr = pSvc->ExecQuery(L"WQL", L"SELECT LastBootUpTime FROM Win32_OperatingSystem", 0, 0, &pEnum);
    if (FAILED(hr))  {
      throw std::runtime_error("WMI: Query failed");
    }

    // Fetch result
    VARIANT value;
    ULONG uCount;
    hr = pEnum->Next(WBEM_INFINITE, 1, &pEach, &uCount);
    if (FAILED(hr) || uCount == 0)  {
      throw std::runtime_error("WMI: Can't fetch result");
    }

    hr = pEach->Get(L"LastBootUpTime", 0, &value, 0, 0);
    if (FAILED(hr))  {
      throw std::runtime_error("WMI: Can't get LastBootUpTime");
    }

    if (value.vt != VT_BSTR) {
      throw std::runtime_error("Expected string");
    }

    lastBootUpTime = value.bstrVal;

  } catch (std::runtime_error &) {
    if (pLoc)  pLoc->Release();
    if (pSvc)  pSvc->Release();
    if (pEnum) pEnum->Release();
    if (pEach) pEach->Release();
    throw;
  }

  pLoc->Release();
  pSvc->Release();
  pEnum->Release();
  pEach->Release();

  return lastBootUpTime;
}


int main() {
  try {
    // Initialize COM. Do this only once
    com_init();

    // Get last boot time
    std::wstring str = wmi_last_boot_time();

    // Reformat time
    std::wcout << str.substr(0, 4) << "/" << str.substr(4, 2) << "/" << str.substr(6, 2) << " "
               << str.substr(8, 2) << ":" << str.substr(10, 2) << ":" << str.substr(12, 2) << "."
               << str.substr(15, 3) << std::endl;


    // Uninitialize COM
    CoUninitialize();

  } catch (std::exception &e) {
    std::cerr << e.what() << std::endl;
    return 1;
  }
  return 0;
}

我认为你可以在vc6上使用它。 wbemidl.h包含您可能使用midl生成的COM接口定义,或者甚至可能使用最近的SDK中的标头。

不幸的是我甚至没有安装vc6。

答案 2 :(得分:0)

感谢您的投入,但我已成功解决了这个问题。似乎问题源于我正在调用CreateProcess()的服务作为LocalSystem运行的事实。 CreateProcess()在同一个用户下创建新进程,我需要它以登录用户身份运行。以下是获取当前用户的代码,并在CreateProcessAsUser()中使用其标记:

//retrieve the user token via an open process
HANDLE hToken = NULL;                
HANDLE hProcess = NULL;
DWORD dwProcessId = NULL;

//user 'explorer.exe' as the process to search for
PROCESSENTRY32 pe32;
ZeroMemory(&pe32,sizeof(pe32));

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);

pe32.dwSize = sizeof(PROCESSENTRY32); 

if(Process32First(hSnapshot,&pe32))
{
    do
    {
        if(!strcmp(pe32.szExeFile,"explorer.exe"))
        {
            dwProcessId = pe32.th32ProcessID;
            break;
        }

    }while(Process32Next(hSnapshot,&pe32));
}

if( dwProcessId ) 
{
    hProcess = OpenProcess(PROCESS_ALL_ACCESS,TRUE, dwProcessId );
    if( hProcess) 
    {
        OpenProcessToken(hProcess,  TOKEN_EXECUTE | TOKEN_READ | TOKEN_QUERY | 
                                    TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY_SOURCE |
                                    TOKEN_WRITE | TOKEN_DUPLICATE, 
                                    &hToken);
        CloseHandle( hProcess );
    }
    else
    {
        error = _T("Could not open process 'explorer.exe'");
        return false;
    }
}
else
{
    error = _T("Could not retrieve process id for 'explorer.exe'");
    return false;
}

if (hToken != NULL)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );
    if (CreateProcessAsUser(hToken, "cmd.exe", const_cast<char*>(command.c_str()), NULL, NULL, TRUE, 
                                            CREATE_NO_WINDOW, NULL, NULL, &si, &pi) == 0)
    {
        tstringstream err;
        err << "CreateProcessAsUser() failed with error: " << GetLastError();
        error = err.str();
        CloseHandle(hToken);
        return false;
    }

    WaitForSingleObject( pi.hProcess, INFINITE );

    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    CloseHandle( hToken );
}
else
{
    error = _T("Token retrieved from 'explorer.exe' is NULL");
    return false;
}

return true;

在我的情况下,command.c_str()指向以下字符串:

/C ""C:\WINDOWS\System32\Cscript.exe" //Nologo "c:\<repository_dir>\boot_time.vbs" >"C:\WINDOWS\TEMP\rts452.tmp""

感谢您的帮助!