Многопоточная служба, выполняющая пакетный файл через CreateProcess () - PullRequest
0 голосов
/ 08 декабря 2010

СМ. ОБНОВЛЕНИЕ В КОНЦЕ

Я пытаюсь запустить следующий пакетный файл (с именем 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

Хотя я ценю комментарии об альтернативных (читай: более простых) способах получения времени загрузки системы, будьте уверены, что я изучил все возможности (с которыми я сталкивался), и ни один из них не соответствует требованиям для системы.

Теперь суть вопроса. Когда я пытаюсь запустить файл .bat через C ++, CreateProcess () возвращает 1, но пакетный файл не запускается (я подтвердил это, заменив содержимое boot_time.bat на простой 'start calc', который по-прежнему отказывается запускаться ). Оскорбительный код:

//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, и все работает как обычно, однако пакетный файл никогда не запускается.

За долгие часы поисков в сети я натолкнулся на шепот проблем, когда дело доходит до запуска командного файла из службы, но я:

а) больше не может найти указанный шепот,
б) подумал бы, что CreateProcess ("cmd.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

Код на С ++ в основном не изменился, просто изменил командную строку для вызова 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 голосов
/ 08 декабря 2010

Мне удалось выяснить код 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 содержит определения интерфейсов COM, которые вы, вероятно, можете сгенерировать с помощью midl или, возможно, даже использовать заголовки из недавнего SDK.

К сожалению, у меня даже не установлен vc6.

0 голосов
/ 09 декабря 2010

Спасибо за ввод, однако мне удалось решить проблему.Кажется, проблема связана с тем, что служба, из которой я вызываю 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""

Спасибо за помощь!

0 голосов
/ 08 декабря 2010

Прочтите внимательно эту строку из MSDN-описания CreateProcess :

Поскольку argv [0] - это имя модуля, программисты на Си обычно повторяют имя модуля в качестве первого токена в командной строке.

Это означает, что ваш код должен быть:

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 и вычесть его значение из текущего времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...