Using sc.exe to communicate with Windows Services in Visual C++

SC.exe is commonly used to retrieve and set control information about installed Windows services. Common tasks are to configure, start and stop a service, as well as retrieve the status of a specific service. These tasks can be achieved by executing sc.exe inside a command prompt as a batch (.bat) files that can call combinations of sc.exe commands to automate startup or shutdown of services.

View the list of Windows services by selecting Start > Run… > services.msc:

services

Consider the “Themes” Windows process for example.

themes

To obtain the status of this service is obtained via the sc.exe command:

queryThemes

This post shows how to programmatically use calls to sc.exe in Visual C++ by way of calls made to the CreateProcess() Windows API. The CreatePipe() API is used for interprocess communication in that any data that gets written to the pipe by one process can be read by another process via calls to the ReadFile function.

Full code listing:

#include <iostream>
#include <direct.h>
#include <afx.h>

const int MAX_READ_BUFFER = 4096;
 
int main( int argc, char *argv[] )
{
	// Obtain location of system directory eg C:\Windows\System32
	TCHAR systemDirPath[MAX_PATH] = _T("");
    GetSystemDirectory( systemDirPath, sizeof(systemDirPath)/sizeof(_TCHAR) );

	// Set location of sc.exe
	CString strExePath;
	strExePath.Format( "%s%s", systemDirPath, "\\sc.exe" );

    // Create security attributes needed to create the pipe
    HANDLE hChildStdoutRd;
    HANDLE hChildStdoutWr;
    SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) } ;
    saAttr.bInheritHandle  = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
     
    // Create pipe to get results from child process stdou\t, return if unsuccessful
    if ( !CreatePipe( &hChildStdoutRd, &hChildStdoutWr, &saAttr, 0 ) )
    {
        return 1;
    }
    
	// Default if no command line arguments supplied
    CString l_strParameters = argc < 2 ? " QUERY \"Themes\"" : argv[ 1 ]; 
 
    PROCESS_INFORMATION pif  = { 0 };
    STARTUPINFO si = { sizeof(STARTUPINFO) };
 
    ZeroMemory( &si,sizeof( si ) );  
    si.cb = sizeof( si ); 
    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdOutput  = hChildStdoutWr;
    si.hStdError   = hChildStdoutWr;
    si.wShowWindow = SW_HIDE;
 
    BOOL bRet = CreateProcess(
                        strExePath.GetBuffer(0),       // Path to sc executable
                        l_strParameters.GetBuffer(0),  // Command parameters string
                        NULL,                          // Process handle not inherited
                        NULL,                          // Thread handle not inherited
                        TRUE,                          // Inheritance of handles
                        CREATE_NO_WINDOW,              // Suppress console window
                        NULL,                          // Same environment block as this prog
                        NULL,                          // Current directory
                        &si,                           // Pointer to STARTUPINFO
                        &pif );                        // Pointer to PROCESS_INFORMATION
 
    if( bRet == FALSE )
    {      
        return false;
    }
 
    // Wait until child processes exit, but not forever.  Terminate if still running.
    WaitForSingleObject( pif.hProcess, 4000 );
    TerminateProcess( pif.hProcess, 0 );
 
    // Close the write end of the pipe before reading from the read end of the pipe.
    if ( !CloseHandle( hChildStdoutWr ) )
    {
        return false;
    }
 
    // Read output chunks from the child process.
    CString l_strResult;
    for ( ;; )
    {
        DWORD l_dwRead;
        CHAR l_chBuffer[ MAX_READ_BUFFER ];
                 
        bool l_fIsDone = !ReadFile( 
								hChildStdoutRd, 
								l_chBuffer, 
								MAX_READ_BUFFER, 
								&l_dwRead, NULL) || l_dwRead == 0;
         
        if( l_fIsDone ) break;
                 
        l_strResult += CString( l_chBuffer, l_dwRead) ;
    }
 
    CloseHandle( hChildStdoutRd );
    CloseHandle( pif.hProcess ); 
    CloseHandle( pif.hThread );   

    std::cout << l_strResult << std::endl;
 
    std::getchar();
    return 0;
}

This obtains the same result as we would get via the command line, but this time the result is being written to the console, via std::cout:

queryThemes2

Please note I compiled this as a simple Visual Studio empty project. Certain project property settings can sometimes cause the program to compile incorrectly like this:

c:\program files\microsoft visual studio 10.0\vc\atlmfc\include\afx.h(24): fatal error C1189: #error :  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or do not use /MD[d]

If this happens just change the property setting C++ > Code Generation > Runtime Library so that it is non-DLL based eg:

services1

And it should then compile fine…

Download the Visual Studio 2010 project from here:

http://www.technical-recipes.com/Downloads/ServiceStarter.zip

Leave a Reply