0

SEE UPDATE AM ENDEMultithreaded-Dienst ausgeführt werden Batch-Datei über Createprocess()

Ich versuche, die folgende Batchdatei (mit dem Namen boot_time.bat, im selben Verzeichnis wie die EXE) auszuführen:

@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% 

Die Stapeldatei benötigt ein Argument, das aktuelle Arbeitsverzeichnis. Der Zweck besteht darin, die Startzeit des Systems über das Skript boot_time.vbs abzurufen und in ein gemeinsames Datumsformat zu formatieren. Für completenesses willen, sind hier die Inhalte der vbs-Datei:

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

Während ich Kommentare über alternative schätzen (sprich: einfachere) Methoden, um die Systemstartzeit abgerufen werden, seien Sie versichert, ich alle Möglichkeiten erkundet haben (die ich habe rüberkommen) und keines entspricht den Anforderungen für das System.

Nun, der Kern des Problems. Wenn ich versuche, die .bat-Datei über C++ auszuführen, gibt CreateProcess() 1 zurück, aber die Stapeldatei wird nicht ausgeführt (ich habe dies verifiziert, indem ich den Inhalt von boot_time.bat durch einen einfachen 'start calc' ersetzt habe, der immer noch nicht ausgeführt werden kann)). Die problematischen Code:

//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); 

Die Pfade und Befehlszeilenargumente sind alle in Ordnung (ich kann sie debuggen drucken und in cmd.exe einfügen, wo sie wie erwartet). Beim Durchlaufen des Codes gibt CreateProcess() 1 zurück und alles läuft wie normal, jedoch wird die Stapeldatei nie ausgeführt.

In meinen vielen Stunden die interwebs Suche ich auf Einflüsterungen von Fragen gekommen sind, wenn es darum geht, eine Batch-Datei von einem Dienst zu laufen, aber ich:

a) nicht mehr die Einflüsterungen finden,
b) würde denken, dass CreateProcess ("cmd.exe", ...) keine Batch-Datei, sondern eine Exe ausführt und den Rest bis zur Eingabeaufforderung belässt.

Also, irgendeine Idee, was ist los?

Oh, ich bin VC läuft ++ 6

UPDATE 1:
Wenn ich tick 'mit Desktop erlauben zu interagieren', und ändern Sie Createprocess(), um nicht das Konsolenfenster zu verstecken (via CREATE_NEW_CONSOLE) , Bekomme ich einen Bruchteil einer Sekunde, in der die Eingabeaufforderung erscheint. CreateProcess() erstellt also einen cmd.exe-Prozess, aber cmd.exe weigert sich, die Batchdatei auszuführen.

UPDATE 2:
Nach Gabe Vorschlag überlegte ich meinen Prozess und vereinfachte es etwas in einem Versuch, den Schuldigen aufzuspüren. Ich habe die Batch-Datei entfernt und consolodated es in die VBS-Datei, die nun:

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 

Die C++ Code ist weitgehend unverändert, nur die Befehlszeile geändert WScript.exe ruft statt cmd.exe. Ich erhalte die vollständig qualifizierten Pfade für alle Dateien, die auf die folgende Zeichenfolge führt:

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

(repository_dir vorsätzliches Namen wegzulassen). Außerdem verwende ich nur Wscript, um zu testen, wie es ein Meldungsfeld öffnet. Die korrekte Verwendung erfolgt mit Cscript.exe, wobei die Ausgabe in die temporäre Datei umgeleitet wird.

CreateProcess bewirkt nicht, dass das Meldungsfenster angezeigt wird, wenn es direkt über die Befehlszeile ausgeführt wird.

+0

Zunächst funktioniert es, wenn Sie Ihr Programm von der Befehlszeile statt als Dienst ausführen? Zweitens sollten Sie 'commandStream' drucken und sicherstellen, dass das Ausführen aus dem Arbeitsverzeichnis Ihres Programms funktioniert. – Gabe

+0

Ein offensichtliches Problem ist, dass das VBS-Skript nicht ausgeführt wird, wenn es sich auf einem anderen Laufwerksbuchstaben des Arbeitsverzeichnisses des Dienstes befindet, in dem Sie 'cd/d% 1' verwenden. – Gabe

+0

Ich werde das/d hinzufügen, danke (aber das Skript befindet sich im selben Ordner, also ist das zur Zeit kein Problem). Ja, ich habe versucht, die Ausgabe des Befehls stream aus dem Arbeitsverzeichnis (und jedem anderen Verzeichnis auszuführen, da sich die CD% 1 in das Arbeitsverzeichnis ändert) und es hat gut funktioniert. –

Antwort

0

Danke für die Eingabe, aber ich habe es geschafft, das Problem zu beheben. Es scheint, dass das Problem von der Tatsache herrührt, dass der Dienst, von dem ich CreateProcess() anrufe, als LocalSystem läuft. CreateProcess() erstellt den neuen Prozess unter demselben Benutzer, wo ich ihn als angemeldeten Benutzer ausführen musste. Hier ist der Code den aktuellen Benutzer zu bekommen, und nutzen ihre Token in 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; 

Wo, in meinem Fall, command.c_str() auf die folgende Zeichenfolge:

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

Danke für die Hilfe!

0

genau diese Linie von MSDN's description of CreateProcess lesen:

Da argv [0] wird der Modulname, C-Programmierer der Modulnamen als erste Token in der Befehlszeile im Allgemeinen wiederholen.

Diese imples, dass Ihr Code sein sollte:

commandStream << "cmd.exe /C " 

Siehe http://blogs.msdn.com/b/oldnewthing/archive/2006/05/15/597984.aspx auch.

Allerdings muss ich hinzufügen, dass ich jemand, den ich gefunden habe, mit C++ schießen würde, um eine Batch-Datei auszuführen, um ein VBScript auszuführen, um seine Ausgabe zu verarbeiten. Da die Batch-Datei nichts anderes als String-Manipulation macht, warum können Sie diese String-Manipulation nicht in VBS oder C++ durchführen?

Alternativ können Sie die Batchdatei vermeiden und rufen Sie einfach cscript direkt mit diesem Code:

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) 

Eine weitere Option ist die System\System Up Time Leistungsindikator und subtrahieren seinen Wert aus der aktuellen Zeit abzufragen.

+0

Erstens, danke für den Vorschlag, leider weigert es sich immer noch zu laufen. Es folgen Versuche, meine Methoden zu verteidigen. –

+0

Ich könnte die WMI-Befehle direkt aus dem Code aufrufen, die Header-Dateien stammen jedoch aus einer späteren Version des von uns verwendeten Windows-SDK. Ich könnte den Befehl wmic verwenden, aber dieser Code wird benötigt, um auf W2k-Rechnern zu laufen, wo wmic nicht existiert. Daher ist die einzige Möglichkeit, die benötigten Daten zu erhalten, ein VBScript. Der Grund, warum ich die Zeichenkette in der Batch-Datei manipuliere, ist, dass mein VBS-Wissen minimal ist und eine Anforderung ist, dass wir irgendwann die Art und Weise ändern können, wie die Batch-Datei die Daten abruft, ohne den C++ - Parsing-Code ändern zu müssen . –

+0

Sie benötigen die Headerdateien des SDK nicht. Sie können einfach die Deklarationen aus dem richtigen SDK nehmen und sie in Ihre eigene '.cpp' oder' .h' Datei einfügen. – Gabe

0

Ich habe es geschafft, den C++ Code herauszufinden, um all dies zu tun.

Wie die meisten C++ COM-Code wird es ziemlich ausführlich. Aber hier gehts:

#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; 
} 

Ich denke, dass Sie dies auf vc6 arbeiten können. wbemidl.h enthält COM-Schnittstellen-Definitionen, die Sie wahrscheinlich mit midl generieren könnten oder vielleicht sogar die Header eines aktuellen SDK verwenden.

Leider habe ich nicht einmal vc6 installiert.

Verwandte Themen