サービス関連記事
C++でサービスを作る
基本は下記のドキュメント、サンプルを元に、個人的に不要そうな部分を間引いて、自分に合う形で(VisualStudio2019を使って)C++のサービスを作る。
今回書いたコード自体は、下記のMSのサンプルコードを、自分のVS2019の環境でうまくビルドが通るようにちょっと修正したもの。
C++でのサービスに関するMSページ
https://docs.microsoft.com/en-us/windows/win32/services/services
C++でのサービスのサンプルプログラム
https://docs.microsoft.com/en-us/windows/win32/services/svc-cpp
手順
VS2019を開き、新しいプロジェクトの作成で、C++の「からのプロジェクト」を選択してPJ作成する。
(今回は、「serviceJikken1」という名前にした)
ソースファイルに、cppファイルを一個追加する。(今回は「serviceJikken1.cpp」とした)
MSのサンプルプログラム(こちらのコード)をそのままコピーし、作ったcppに貼り付ける。
21/04/02時点では、そのままビルドすることができなかったので、一部修正する。修正したものは下記。
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
//#include "sample.h"
#pragma comment(lib, "advapi32.lib")
#define SVCNAME TEXT("SvcName")
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
VOID SvcInstall(void);
VOID WINAPI SvcCtrlHandler(DWORD);
VOID WINAPI SvcMain(DWORD, LPTSTR*);
VOID ReportSvcStatus(DWORD, DWORD, DWORD);
VOID SvcInit(DWORD, LPTSTR*);
VOID SvcReportEvent(LPTSTR);
//
// Purpose:
// Entry point for the process
//
// Parameters:
// None
//
// Return value:
// None, defaults to 0 (zero)
//
int __cdecl _tmain(int argc, TCHAR* argv[])
{
// If command-line parameter is "install", install the service.
// Otherwise, the service is probably being started by the SCM.
if (lstrcmpi(argv[1], TEXT("install")) == 0)
{
SvcInstall();
return 0;
}
// TO_DO: Add any additional services for the process to this table.
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ (LPTSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION)SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher(DispatchTable))
//→こいつの中で、RegisterServiceCtrlHandler()で渡したループのハンドラ
// (今回だとSvcCtrlHandler())がぐるぐる回ってるイメージ。
// サービスが終了になったら、そのぐるぐるから抜けてここから下に行く。
{
SvcReportEvent((LPTSTR)(TEXT("StartServiceCtrlDispatcher")));
}
return 0;
}
//
// Purpose:
// Installs a service in the SCM database
//
// Parameters:
// None
//
// Return value:
// None
//
VOID SvcInstall()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
TCHAR szPath[MAX_PATH];
if (!GetModuleFileName(0, szPath, MAX_PATH))
{
printf("Cannot install service (%d)\n", GetLastError());
return;
}
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
SVCNAME, // name of service
SVCNAME, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
printf("CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else printf("Service installed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Entry point for the service
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None.
//
VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv)
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler(
SVCNAME,
SvcCtrlHandler);
if (!gSvcStatusHandle)
{
SvcReportEvent((LPTSTR)(TEXT("RegisterServiceCtrlHandler")));
return;
}
// These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
// Perform service-specific initialization and work.
SvcInit(dwArgc, lpszArgv);
}
//
// Purpose:
// The service code
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None
//
VOID SvcInit(DWORD dwArgc, LPTSTR* lpszArgv)
{
// TO_DO: Declare and set any required variables.
// Be sure to periodically call ReportSvcStatus() with
// SERVICE_START_PENDING. If initialization fails, call
// ReportSvcStatus with SERVICE_STOPPED.
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if (ghSvcStopEvent == NULL)
{
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
// Report running status when initialization is complete.
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
// TO_DO: Perform work until service stops.
while (1)
{
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
}
//
// Purpose:
// Sets the current service status and reports it to the SCM.
//
// Parameters:
// dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation,
// in milliseconds
//
// Return value:
// None
//
VOID ReportSvcStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
gSvcStatus.dwControlsAccepted = 0;
else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED))
gSvcStatus.dwCheckPoint = 0;
else gSvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
//
// Purpose:
// Called by SCM whenever a control code is sent to the service
// using the ControlService function.
//
// Parameters:
// dwCtrl - control code
//
// Return value:
// None
//
VOID WINAPI SvcCtrlHandler(DWORD dwCtrl)
{
// Handle the requested control code.
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
//
// Purpose:
// Logs messages to the event log
//
// Parameters:
// szFunction - name of function that failed
//
// Return value:
// None
//
// Remarks:
// The service must have an entry in the Application event log.
//
VOID SvcReportEvent(LPTSTR szFunction)
{
HANDLE hEventSource;
LPCTSTR lpszStrings[2];
TCHAR Buffer[80];
hEventSource = RegisterEventSource(NULL, SVCNAME);
if (NULL != hEventSource)
{
StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());
lpszStrings[0] = SVCNAME;
lpszStrings[1] = Buffer;
ReportEvent(hEventSource, // event log handle
EVENTLOG_ERROR_TYPE, // event type
0, // event category
//SVC_ERROR, // event identifier
0,
NULL, // no security identifier
2, // size of lpszStrings array
0, // no binary data
lpszStrings, // array of strings
NULL); // no binary data
DeregisterEventSource(hEventSource);
}
}
ビルドする。
出力フォルダに「serviceJikken1.exe」ができる。
開発者用コンソールを管理者権限で開き、出力フォルダにcdする。
下記コマンドをたたく。
(今回のサンプルのexeは「install」という引数を与えて実行すると、サービスのインストールをするように作られている様子。たぶん、これがイヤだったら、そんな機能はなくして、別途コマンドでインストール等できるはずだが、いったんサンプル通りにやる)(たぶん「sc」コマンドで、サービスのインストールとかができるはず?)
serviceJikken1.exe install
これで「SvcName」という名前のインストールされる。
※その名前は、serviceJiken1.cppの一番上で定義されているもの。自由に変えてOK。
svcconfig delete SvcName
とうつと、サービスがアンインストール(削除)される。
「SvcContol」と「SvcConfig」という名前のプロジェクトを、上と同じ「空のプロジェクト」で作成する。作ったプロジェクトに、それぞれ「SvcContol.cpp」と「SvcConfig.cpp」という名前のcppファイルを追加し、MSのサンプルページからコードをコピーする。
これらもそのままビルドすることができなかったので、一部修正する。修正したものは下記。
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <aclapi.h>
#include <stdio.h>
#pragma comment(lib, "advapi32.lib")
TCHAR szCommand[10];
TCHAR szSvcName[80];
SC_HANDLE schSCManager;
SC_HANDLE schService;
VOID __stdcall DisplayUsage(void);
VOID __stdcall DoStartSvc(void);
VOID __stdcall DoUpdateSvcDacl(void);
VOID __stdcall DoStopSvc(void);
BOOL __stdcall StopDependentServices(void);
//
// Purpose:
// Entry point function. Executes specified command from user.
//
// Parameters:
// Command-line syntax is: svccontrol [command] [service_name]
//
// Return value:
// None
//
void _tmain(int argc, TCHAR* argv[])
{
printf("\n");
if (argc != 3)
{
printf("ERROR: Incorrect number of arguments\n\n");
DisplayUsage();
return;
}
StringCchCopy(szCommand, 10, argv[1]);
StringCchCopy(szSvcName, 80, argv[2]);
if (lstrcmpi(szCommand, TEXT("start")) == 0)
DoStartSvc();
else if (lstrcmpi(szCommand, TEXT("dacl")) == 0)
DoUpdateSvcDacl();
else if (lstrcmpi(szCommand, TEXT("stop")) == 0)
DoStopSvc();
else
{
_tprintf(TEXT("Unknown command (%s)\n\n"), szCommand);
DisplayUsage();
}
}
VOID __stdcall DisplayUsage()
{
printf("Description:\n");
printf("\tCommand-line tool that controls a service.\n\n");
printf("Usage:\n");
printf("\tsvccontrol [command] [service_name]\n\n");
printf("\t[command]\n");
printf("\t start\n");
printf("\t dacl\n");
printf("\t stop\n");
}
//
// Purpose:
// Starts the service if possible.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoStartSvc()
{
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwOldCheckPoint;
DWORD dwStartTickCount;
DWORD dwWaitTime;
DWORD dwBytesNeeded;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // servicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_ALL_ACCESS); // full access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Check the status in case the service is not stopped.
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // information level
(LPBYTE)&ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded)) // size needed if buffer is too small
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
// Check if the service is already running. It would be possible
// to stop the service here, but for simplicity this example just returns.
if (ssStatus.dwCurrentState != SERVICE_STOPPED && ssStatus.dwCurrentState != SERVICE_STOP_PENDING)
{
printf("Cannot start the service because it is already running\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
// Save the tick count and initial checkpoint.
dwStartTickCount = GetTickCount();
dwOldCheckPoint = ssStatus.dwCheckPoint;
// Wait for the service to stop before attempting to start it.
while (ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
{
// Do not wait longer than the wait hint. A good interval is
// one-tenth of the wait hint but not less than 1 second
// and not more than 10 seconds.
dwWaitTime = ssStatus.dwWaitHint / 10;
if (dwWaitTime < 1000)
dwWaitTime = 1000;
else if (dwWaitTime > 10000)
dwWaitTime = 10000;
Sleep(dwWaitTime);
// Check the status until the service is no longer stop pending.
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // information level
(LPBYTE)&ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded)) // size needed if buffer is too small
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
if (ssStatus.dwCheckPoint > dwOldCheckPoint)
{
// Continue to wait and check.
dwStartTickCount = GetTickCount();
dwOldCheckPoint = ssStatus.dwCheckPoint;
}
else
{
if (GetTickCount() - dwStartTickCount > ssStatus.dwWaitHint)
{
printf("Timeout waiting for service to stop\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
}
}
// Attempt to start the service.
if (!StartService(
schService, // handle to service
0, // number of arguments
NULL)) // no arguments
{
printf("StartService failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
else printf("Service start pending...\n");
// Check the status until the service is no longer start pending.
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // info level
(LPBYTE)&ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded)) // if buffer too small
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return;
}
// Save the tick count and initial checkpoint.
dwStartTickCount = GetTickCount();
dwOldCheckPoint = ssStatus.dwCheckPoint;
while (ssStatus.dwCurrentState == SERVICE_START_PENDING)
{
// Do not wait longer than the wait hint. A good interval is
// one-tenth the wait hint, but no less than 1 second and no
// more than 10 seconds.
dwWaitTime = ssStatus.dwWaitHint / 10;
if (dwWaitTime < 1000)
dwWaitTime = 1000;
else if (dwWaitTime > 10000)
dwWaitTime = 10000;
Sleep(dwWaitTime);
// Check the status again.
if (!QueryServiceStatusEx(
schService, // handle to service
SC_STATUS_PROCESS_INFO, // info level
(LPBYTE)&ssStatus, // address of structure
sizeof(SERVICE_STATUS_PROCESS), // size of structure
&dwBytesNeeded)) // if buffer too small
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
break;
}
if (ssStatus.dwCheckPoint > dwOldCheckPoint)
{
// Continue to wait and check.
dwStartTickCount = GetTickCount();
dwOldCheckPoint = ssStatus.dwCheckPoint;
}
else
{
if (GetTickCount() - dwStartTickCount > ssStatus.dwWaitHint)
{
// No progress made within the wait hint.
break;
}
}
}
// Determine whether the service is running.
if (ssStatus.dwCurrentState == SERVICE_RUNNING)
{
printf("Service started successfully.\n");
}
else
{
printf("Service not started. \n");
printf(" Current State: %d\n", ssStatus.dwCurrentState);
printf(" Exit Code: %d\n", ssStatus.dwWin32ExitCode);
printf(" Check Point: %d\n", ssStatus.dwCheckPoint);
printf(" Wait Hint: %d\n", ssStatus.dwWaitHint);
}
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Updates the service DACL to grant start, stop, delete, and read
// control access to the Guest account.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoUpdateSvcDacl()
{
EXPLICIT_ACCESS ea;
SECURITY_DESCRIPTOR sd;
PSECURITY_DESCRIPTOR psd = NULL;
PACL pacl = NULL;
PACL pNewAcl = NULL;
BOOL bDaclPresent = FALSE;
BOOL bDaclDefaulted = FALSE;
DWORD dwError = 0;
DWORD dwSize = 0;
DWORD dwBytesNeeded = 0;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service
schService = OpenService(
schSCManager, // SCManager database
szSvcName, // name of service
READ_CONTROL | WRITE_DAC); // access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Get the current security descriptor.
if (!QueryServiceObjectSecurity(schService,
DACL_SECURITY_INFORMATION,
&psd, // using NULL does not work on all versions
0,
&dwBytesNeeded))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
dwSize = dwBytesNeeded;
psd = (PSECURITY_DESCRIPTOR)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, dwSize);
if (psd == NULL)
{
// Note: HeapAlloc does not support GetLastError.
printf("HeapAlloc failed\n");
goto dacl_cleanup;
}
if (!QueryServiceObjectSecurity(schService,
DACL_SECURITY_INFORMATION, psd, dwSize, &dwBytesNeeded))
{
printf("QueryServiceObjectSecurity failed (%d)\n", GetLastError());
goto dacl_cleanup;
}
}
else
{
printf("QueryServiceObjectSecurity failed (%d)\n", GetLastError());
goto dacl_cleanup;
}
}
// Get the DACL.
if (!GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl,
&bDaclDefaulted))
{
printf("GetSecurityDescriptorDacl failed(%d)\n", GetLastError());
goto dacl_cleanup;
}
// Build the ACE.
BuildExplicitAccessWithName(&ea, (LPTSTR)(TEXT("GUEST")),
SERVICE_START | SERVICE_STOP | READ_CONTROL | DELETE,
SET_ACCESS, NO_INHERITANCE);
dwError = SetEntriesInAcl(1, &ea, pacl, &pNewAcl);
if (dwError != ERROR_SUCCESS)
{
printf("SetEntriesInAcl failed(%d)\n", dwError);
goto dacl_cleanup;
}
// Initialize a new security descriptor.
if (!InitializeSecurityDescriptor(&sd,
SECURITY_DESCRIPTOR_REVISION))
{
printf("InitializeSecurityDescriptor failed(%d)\n", GetLastError());
goto dacl_cleanup;
}
// Set the new DACL in the security descriptor.
if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE))
{
printf("SetSecurityDescriptorDacl failed(%d)\n", GetLastError());
goto dacl_cleanup;
}
// Set the new DACL for the service object.
if (!SetServiceObjectSecurity(schService,
DACL_SECURITY_INFORMATION, &sd))
{
printf("SetServiceObjectSecurity failed(%d)\n", GetLastError());
goto dacl_cleanup;
}
else printf("Service DACL updated successfully\n");
dacl_cleanup:
CloseServiceHandle(schSCManager);
CloseServiceHandle(schService);
if (NULL != pNewAcl)
LocalFree((HLOCAL)pNewAcl);
if (NULL != psd)
HeapFree(GetProcessHeap(), 0, (LPVOID)psd);
}
//
// Purpose:
// Stops the service.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoStopSvc()
{
SERVICE_STATUS_PROCESS ssp;
DWORD dwStartTime = GetTickCount();
DWORD dwBytesNeeded;
DWORD dwTimeout = 30000; // 30-second time-out
DWORD dwWaitTime;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_STOP |
SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS);
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Make sure the service is not already stopped.
if (!QueryServiceStatusEx(
schService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded))
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
goto stop_cleanup;
}
if (ssp.dwCurrentState == SERVICE_STOPPED)
{
printf("Service is already stopped.\n");
goto stop_cleanup;
}
// If a stop is pending, wait for it.
while (ssp.dwCurrentState == SERVICE_STOP_PENDING)
{
printf("Service stop pending...\n");
// Do not wait longer than the wait hint. A good interval is
// one-tenth of the wait hint but not less than 1 second
// and not more than 10 seconds.
dwWaitTime = ssp.dwWaitHint / 10;
if (dwWaitTime < 1000)
dwWaitTime = 1000;
else if (dwWaitTime > 10000)
dwWaitTime = 10000;
Sleep(dwWaitTime);
if (!QueryServiceStatusEx(
schService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded))
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
goto stop_cleanup;
}
if (ssp.dwCurrentState == SERVICE_STOPPED)
{
printf("Service stopped successfully.\n");
goto stop_cleanup;
}
if (GetTickCount() - dwStartTime > dwTimeout)
{
printf("Service stop timed out.\n");
goto stop_cleanup;
}
}
// If the service is running, dependencies must be stopped first.
StopDependentServices();
// Send a stop code to the service.
if (!ControlService(
schService,
SERVICE_CONTROL_STOP,
(LPSERVICE_STATUS)&ssp))
{
printf("ControlService failed (%d)\n", GetLastError());
goto stop_cleanup;
}
// Wait for the service to stop.
while (ssp.dwCurrentState != SERVICE_STOPPED)
{
Sleep(ssp.dwWaitHint);
if (!QueryServiceStatusEx(
schService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded))
{
printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
goto stop_cleanup;
}
if (ssp.dwCurrentState == SERVICE_STOPPED)
break;
if (GetTickCount() - dwStartTime > dwTimeout)
{
printf("Wait timed out\n");
goto stop_cleanup;
}
}
printf("Service stopped successfully\n");
stop_cleanup:
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
BOOL __stdcall StopDependentServices()
{
DWORD i;
DWORD dwBytesNeeded;
DWORD dwCount;
LPENUM_SERVICE_STATUS lpDependencies = NULL;
ENUM_SERVICE_STATUS ess;
SC_HANDLE hDepService;
SERVICE_STATUS_PROCESS ssp;
DWORD dwStartTime = GetTickCount();
DWORD dwTimeout = 30000; // 30-second time-out
// Pass a zero-length buffer to get the required buffer size.
if (EnumDependentServices(schService, SERVICE_ACTIVE,
lpDependencies, 0, &dwBytesNeeded, &dwCount))
{
// If the Enum call succeeds, then there are no dependent
// services, so do nothing.
return TRUE;
}
else
{
if (GetLastError() != ERROR_MORE_DATA)
return FALSE; // Unexpected error
// Allocate a buffer for the dependencies.
lpDependencies = (LPENUM_SERVICE_STATUS)HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytesNeeded);
if (!lpDependencies)
return FALSE;
__try {
// Enumerate the dependencies.
if (!EnumDependentServices(schService, SERVICE_ACTIVE,
lpDependencies, dwBytesNeeded, &dwBytesNeeded,
&dwCount))
return FALSE;
for (i = 0; i < dwCount; i++)
{
ess = *(lpDependencies + i);
// Open the service.
hDepService = OpenService(schSCManager,
ess.lpServiceName,
SERVICE_STOP | SERVICE_QUERY_STATUS);
if (!hDepService)
return FALSE;
__try {
// Send a stop code.
if (!ControlService(hDepService,
SERVICE_CONTROL_STOP,
(LPSERVICE_STATUS)&ssp))
return FALSE;
// Wait for the service to stop.
while (ssp.dwCurrentState != SERVICE_STOPPED)
{
Sleep(ssp.dwWaitHint);
if (!QueryServiceStatusEx(
hDepService,
SC_STATUS_PROCESS_INFO,
(LPBYTE)&ssp,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded))
return FALSE;
if (ssp.dwCurrentState == SERVICE_STOPPED)
break;
if (GetTickCount() - dwStartTime > dwTimeout)
return FALSE;
}
}
__finally
{
// Always release the service handle.
CloseServiceHandle(hDepService);
}
}
}
__finally
{
// Always free the enumeration buffer.
HeapFree(GetProcessHeap(), 0, lpDependencies);
}
}
return TRUE;
}
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <stdio.h>
#pragma comment(lib, "advapi32.lib")
TCHAR szCommand[10];
TCHAR szSvcName[80];
VOID __stdcall DisplayUsage(void);
VOID __stdcall DoQuerySvc(void);
VOID __stdcall DoUpdateSvcDesc(void);
VOID __stdcall DoDisableSvc(void);
VOID __stdcall DoEnableSvc(void);
VOID __stdcall DoDeleteSvc(void);
//
// Purpose:
// Entry point function. Executes specified command from user.
//
// Parameters:
// Command-line syntax is: svcconfig [command] [service_path]
//
// Return value:
// None, defaults to 0 (zero)
//
int __cdecl _tmain(int argc, TCHAR* argv[])
{
printf("\n");
if (argc != 3)
{
printf("ERROR:\tIncorrect number of arguments\n\n");
DisplayUsage();
return 0;
}
StringCchCopy(szCommand, 10, argv[1]);
StringCchCopy(szSvcName, 80, argv[2]);
if (lstrcmpi(szCommand, TEXT("query")) == 0)
DoQuerySvc();
else if (lstrcmpi(szCommand, TEXT("describe")) == 0)
DoUpdateSvcDesc();
else if (lstrcmpi(szCommand, TEXT("disable")) == 0)
DoDisableSvc();
else if (lstrcmpi(szCommand, TEXT("enable")) == 0)
DoEnableSvc();
else if (lstrcmpi(szCommand, TEXT("delete")) == 0)
DoDeleteSvc();
else
{
_tprintf(TEXT("Unknown command (%s)\n\n"), szCommand);
DisplayUsage();
}
return 0;
}
VOID __stdcall DisplayUsage()
{
printf("Description:\n");
printf("\tCommand-line tool that configures a service.\n\n");
printf("Usage:\n");
printf("\tsvcconfig [command] [service_name]\n\n");
printf("\t[command]\n");
printf("\t query\n");
printf("\t describe\n");
printf("\t disable\n");
printf("\t enable\n");
printf("\t delete\n");
}
//
// Purpose:
// Retrieves and displays the current service configuration.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoQuerySvc()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
LPQUERY_SERVICE_CONFIG lpsc = NULL;
LPSERVICE_DESCRIPTION lpsd = NULL;
DWORD dwBytesNeeded, cbBufSize, dwError;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_QUERY_CONFIG); // need query config access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Get the configuration information.
if (!QueryServiceConfig(
schService,
NULL,
0,
&dwBytesNeeded))
{
dwError = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == dwError)
{
cbBufSize = dwBytesNeeded;
lpsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, cbBufSize);
}
else
{
printf("QueryServiceConfig failed (%d)", dwError);
goto cleanup;
}
}
if (!QueryServiceConfig(
schService,
lpsc,
cbBufSize,
&dwBytesNeeded))
{
printf("QueryServiceConfig failed (%d)", GetLastError());
goto cleanup;
}
if (!QueryServiceConfig2(
schService,
SERVICE_CONFIG_DESCRIPTION,
NULL,
0,
&dwBytesNeeded))
{
dwError = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == dwError)
{
cbBufSize = dwBytesNeeded;
lpsd = (LPSERVICE_DESCRIPTION)LocalAlloc(LMEM_FIXED, cbBufSize);
}
else
{
printf("QueryServiceConfig2 failed (%d)", dwError);
goto cleanup;
}
}
if (!QueryServiceConfig2(
schService,
SERVICE_CONFIG_DESCRIPTION,
(LPBYTE)lpsd,
cbBufSize,
&dwBytesNeeded))
{
printf("QueryServiceConfig2 failed (%d)", GetLastError());
goto cleanup;
}
// Print the configuration information.
_tprintf(TEXT("%s configuration: \n"), szSvcName);
_tprintf(TEXT(" Type: 0x%x\n"), lpsc->dwServiceType);
_tprintf(TEXT(" Start Type: 0x%x\n"), lpsc->dwStartType);
_tprintf(TEXT(" Error Control: 0x%x\n"), lpsc->dwErrorControl);
_tprintf(TEXT(" Binary path: %s\n"), lpsc->lpBinaryPathName);
_tprintf(TEXT(" Account: %s\n"), lpsc->lpServiceStartName);
if (lpsd->lpDescription != NULL && lstrcmp(lpsd->lpDescription, TEXT("")) != 0)
_tprintf(TEXT(" Description: %s\n"), lpsd->lpDescription);
if (lpsc->lpLoadOrderGroup != NULL && lstrcmp(lpsc->lpLoadOrderGroup, TEXT("")) != 0)
_tprintf(TEXT(" Load order group: %s\n"), lpsc->lpLoadOrderGroup);
if (lpsc->dwTagId != 0)
_tprintf(TEXT(" Tag ID: %d\n"), lpsc->dwTagId);
if (lpsc->lpDependencies != NULL && lstrcmp(lpsc->lpDependencies, TEXT("")) != 0)
_tprintf(TEXT(" Dependencies: %s\n"), lpsc->lpDependencies);
LocalFree(lpsc);
LocalFree(lpsd);
cleanup:
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Disables the service.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoDisableSvc()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Change the service start type.
if (!ChangeServiceConfig(
schService, // handle of service
SERVICE_NO_CHANGE, // service type: no change
SERVICE_DISABLED, // service start type
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL)) // display name: no change
{
printf("ChangeServiceConfig failed (%d)\n", GetLastError());
}
else printf("Service disabled successfully.\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Enables the service.
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoEnableSvc()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Change the service start type.
if (!ChangeServiceConfig(
schService, // handle of service
SERVICE_NO_CHANGE, // service type: no change
SERVICE_DEMAND_START, // service start type
SERVICE_NO_CHANGE, // error control: no change
NULL, // binary path: no change
NULL, // load order group: no change
NULL, // tag ID: no change
NULL, // dependencies: no change
NULL, // account name: no change
NULL, // password: no change
NULL)) // display name: no change
{
printf("ChangeServiceConfig failed (%d)\n", GetLastError());
}
else printf("Service enabled successfully.\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Updates the service description to "This is a test description".
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoUpdateSvcDesc()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
SERVICE_DESCRIPTION sd;
LPTSTR szDesc = (LPTSTR)(TEXT("This is a test description"));
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
SERVICE_CHANGE_CONFIG); // need change config access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Change the service description.
sd.lpDescription = szDesc;
if (!ChangeServiceConfig2(
schService, // handle to service
SERVICE_CONFIG_DESCRIPTION, // change: description
&sd)) // new description
{
printf("ChangeServiceConfig2 failed\n");
}
else printf("Service description updated successfully.\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Deletes a service from the SCM database
//
// Parameters:
// None
//
// Return value:
// None
//
VOID __stdcall DoDeleteSvc()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
SERVICE_STATUS ssStatus;
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Get a handle to the service.
schService = OpenService(
schSCManager, // SCM database
szSvcName, // name of service
DELETE); // need delete access
if (schService == NULL)
{
printf("OpenService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
// Delete the service.
if (!DeleteService(schService))
{
printf("DeleteService failed (%d)\n", GetLastError());
}
else printf("Service deleted successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
両方をビルドする。こんな感じになるはず。(出力物を一か所に集めるようにしている)
ここで、また開発者用コンソールを開いてこのフォルダにcdする。
svccontrol start SvcName
とコマンドを打つと、サービスが動き出す。
svccontrol stop SvcName
とうつとサービスが止まる。
svcconfig describe SvcName
とコマンドを打つと、サービスの説明が"This is a test description"になる。
(そうなるように、サンプルのsvcconfig.exeが作られている)
あと、サンプルのページにあるように、
svcconfig disable SvcName
で無効に、
svcconfig enable SvcName
で有効にできる。
大雑把な処理の流れ
インサイドWindows 第6版 P348あたりより。
SERVICE_TABLE_ENTRY構造体に、サービス名とサービスコントロールが来たときに実行する関数(SvcMain()とする)をセットする。
StartServiceCtrlDispatcher()に、作ったSERVICE_TABLE_ENTRY構造体をセットして実行
→これで、SCM(ServiceControlManager)から、SvcMain()関数がサービスのメイン関数として呼ばれる。
SCMが、、、
・このサービスのためのスレッドを起動
→ここでうごくのがSvcMain()。
・このサービスとの名前付きパイプを作成、サービス開始こまんどをサービスに対して送信
SvcMain()の中で、RegisterServiceCtrlHandler()をまずすぐに呼び出す。
その引数で、サービス名とサービスコントロールのハンドラ(SvcCtrlHandler()とする)を指定する。
→これで、SCM(ServiceControlManager)から、SvcCtrlHandler()が各種ServiceControlを受け取れるようになる。
で、そのハンドラは、アプリ起動時のメインスレッド、もっというとStartServiceCtrlDispatcher()
の中で実施される。(たぶん)
このあと
とりあえず上記で動くものはできたので、
- 下記ページで、scコマンドでサービスインストールする方法を調べる。
https://windows.command-ref.com/cmd-sc.html - サービスのコードの中身を、MSのページ見て学習する
- 起動中に、コードからもサービスのステータスを「Pending」とかにできるのか?調べたい。
参考
dwEventTypeに何がはいるか
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nc-winsvc-lphandler_function_ex
RegisterServiceCtrlHandlerExの使い方
http://eternalwindows.jp/windevelop/service/service03.html
WM_POWERBROADCAST message
https://docs.microsoft.com/en-us/windows/win32/power/wm-powerbroadcast
POWERBROADCAST_SETTING structure (winuser.h)
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-powerbroadcast_setting
Power Setting GUIDs.
https://docs.microsoft.com/en-us/windows/win32/power/power-setting-guids
SCコマンド
https://windows.command-ref.com/cmd-sc.html
ステータス周り
http://eternalwindows.jp/windevelop/service/service04.html