C/C++ 开发SCM服务管理组件

SCM(Service Control Manager)服务管理器是 Windows 操作系统中的一个关键组件,负责管理系统服务的启动、停止和配置。服务是一种在后台运行的应用程序,可以在系统启动时自动启动,也可以由用户或其他应用程序手动启动。本篇文章中,我们将通过使用 Windows 的服务管理器(SCM)提供的API接口,实现一个简单的服务管理组件的编写。

服务管理器的主要功能包括:

  1. 服务启动和停止: SCM 管理系统服务的启动和停止。在系统启动时,SCM 会根据每个服务的配置启动相应的服务。用户也可以通过服务管理器手动启动或停止服务。
  2. 服务配置: SCM 管理服务的配置信息,包括服务的启动类型(如自动、手动、禁用)、服务的依赖关系、服务的用户身份等。
  3. 服务状态监控: SCM 监控运行中服务的状态。服务可以处于运行、暂停、停止等状态。SCM 提供 API 函数,允许应用程序查询和控制服务的状态。
  4. 事件日志: SCM 记录服务启动、停止等事件到系统的事件日志中,这有助于故障排查和系统管理。
  5. 服务通知: SCM 允许应用程序注册服务状态变化的通知,以便及时响应服务状态的改变。
  6. 服务安全性: SCM 确保服务以适当的权限和身份运行,以保障系统的安全性。

开发者可以通过使用 Windows API 提供的相关函数(例如 OpenSCManagerCreateServiceStartService 等)与 SCM 进行交互,管理系统中的服务。这些 API 函数允许开发者创建、配置、启动、停止和查询服务,以及监控服务的状态变化。

枚举SCM系统服务

Windows 的服务控制管理器(SCM)允许开发者通过 EnumServicesStatus 函数来枚举系统中正在运行的服务。这个功能非常有用,可以用于监控系统中的服务状态、获取服务的详细信息等。在这篇文章中,我们将学习如何使用 EnumServicesStatus 函数来实现对 SCM 系统服务的枚举,并获取相关信息。

OpenSCManager 用于打开服务控制管理器数据库,并返回一个指向服务控制管理器的句柄。通过这个句柄,你可以进行对服务的查询、创建、启动、停止等操作。

以下是 OpenSCManager 函数的原型:

SC_HANDLE OpenSCManager(
  LPCTSTR lpMachineName,
  LPCTSTR lpDatabaseName,
  DWORD   dwDesiredAccess
);
  • lpMachineName: 指定远程计算机的名称。如果为 NULL,表示本地计算机。

  • lpDatabaseName: 指定要打开的服务控制管理器数据库的名称。通常为 SERVICES_ACTIVE_DATABASE

  • dwDesiredAccess
    

    : 指定所请求的访问权限。可以是以下之一或它们的组合:

    • SC_MANAGER_CONNECT: 允许连接服务控制管理器。
    • SC_MANAGER_CREATE_SERVICE: 允许创建服务。
    • SC_MANAGER_ENUMERATE_SERVICE: 允许枚举服务。
    • SC_MANAGER_LOCK: 允许锁定服务数据库。
    • SC_MANAGER_QUERY_LOCK_STATUS: 允许查询服务数据库的锁定状态。
    • SC_MANAGER_MODIFY_BOOT_CONFIG: 允许修改系统启动配置。
    • SC_MANAGER_ALL_ACCESS: 允许执行上述所有操作。

函数返回一个指向服务控制管理器的句柄 (SC_HANDLE)。如果操作失败,返回 NULL,可以通过调用 GetLastError 函数获取错误代码。

EnumServicesStatus 用于枚举指定服务控制管理器数据库中的服务。通过这个函数,你可以获取正在运行的服务的信息,如服务的名称、显示名称、状态等。

以下是 EnumServicesStatus 函数的原型:

BOOL EnumServicesStatus(
  SC_HANDLE hSCManager,
  DWORD     dwServiceType,
  DWORD     dwServiceState,
  LPENUM_SERVICE_STATUS lpServices,
  DWORD     cbBufSize,
  LPDWORD   pcbBytesNeeded,
  LPDWORD   lpServicesReturned,
  LPDWORD   lpResumeHandle
);
  • hSCManager: 指定服务控制管理器的句柄,通过 OpenSCManager 函数获取。
  • dwServiceType: 指定服务的类型,如 SERVICE_WIN32
  • dwServiceState: 指定服务的状态,如 SERVICE_STATE_ALL
  • lpServices: 指向 ENUM_SERVICE_STATUS 结构体数组的指针,用于接收服务的信息。
  • cbBufSize: 指定 lpServices 缓冲区的大小,以字节为单位。
  • pcbBytesNeeded: 接收所需的缓冲区大小,以字节为单位。
  • lpServicesReturned: 接收实际返回的服务数。
  • lpResumeHandle: 用于标识服务的遍历位置。

函数返回 BOOL 类型,如果调用成功,返回 TRUE,否则返回 FALSE。如果函数返回 FALSE,可以通过调用 GetLastError 函数获取错误代码。

上述EnumServicesStatus中的第二个参数dwServiceType非常重要,在 Windows 操作系统中,服务的启动类型和服务类型是通过服务的标志(Service Flags)来指定的。这些标志是用于定义服务的性质和启动方式的。以下是其中几个标志的含义:

  1. 0x0 (SERVICE_KERNEL_DRIVER): 设备驱动程序。这种服务类型表示一个内核模式的设备驱动程序。
  2. 0x2 (SERVICE_FILE_SYSTEM_DRIVER): 内核模式文件系统驱动程序。这种服务类型表示一个内核模式的文件系统驱动程序。
  3. 0x8 (SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER): 文件系统识别器驱动程序。这种服务类型表示一个同时具有文件系统驱动程序和文件系统识别器驱动程序功能的服务。
  4. 0x10 (SERVICE_WIN32_OWN_PROCESS): 独占一个进程的服务。这种服务类型表示服务运行在自己的进程中。
  5. 0x20 (SERVICE_WIN32_SHARE_PROCESS): 与其他服务共享一个进程的服务。这种服务类型表示服务可以与其他服务运行在同一个进程中。

需要注意的是,上述标志可以通过按位 OR 运算来组合使用,以表示服务的多个特性。例如,SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS 表示一个交互式服务,即运行在自己的进程中并与桌面交互。

除了上述标志之外,还有一些其他的标志,如:

  • SERVICE_INTERACTIVE_PROCESS (0x100): 交互式服务。表示服务可以与桌面进行交互,通常用于服务需要显示用户界面的情况。
  • SERVICE_AUTO_START (0x2): 自动启动。表示服务会在系统启动时自动启动。
  • SERVICE_DEMAND_START (0x3): 手动启动。表示服务需要由用户手动启动。
  • SERVICE_DISABLED (0x4): 禁用。表示服务被禁用,不会自动启动。

这些标志允许开发者灵活地定义服务的启动方式和性质。在使用服务相关的 API 函数时,这些标志会在函数参数中进行指定。例如,在使用 CreateService 函数时,可以通过设置 dwServiceTypedwStartType 参数来指定服务的类型和启动方式。

如下代码则实现了对系统内特定服务的枚举功能,通过向Enum_Services函数中传入不同的参数来实现枚举不同的服务类型;

#include <stdio.h>
#include <Windows.h>

void Enum_Services(DWORD dwServiceType)
{
	DWORD ServiceCount = 0, dwSize = 0;
	LPENUM_SERVICE_STATUS lpInfo;

	SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	BOOL bRet = EnumServicesStatus(hSCM, dwServiceType, SERVICE_STATE_ALL, NULL, 0, &dwSize, &ServiceCount, NULL);
	if (!bRet && GetLastError() == ERROR_MORE_DATA)
	{
		// 分配缓冲区,保存服务列表
		lpInfo = (LPENUM_SERVICE_STATUS)(new BYTE[dwSize]);
		bRet = EnumServicesStatus(hSCM, dwServiceType, SERVICE_STATE_ALL, (LPENUM_SERVICE_STATUS)lpInfo,
			dwSize, &dwSize, &ServiceCount, NULL);
		
		if (NULL == hSCM)
		{
			return;
		}

		// 逐个遍历获取服务信息
		for (int x = 0; x < ServiceCount; x++)
		{
			printf("名称:%-30s 名称: %-30s 状态: ", lpInfo[x].lpServiceName, lpInfo[x].lpDisplayName);
			switch (lpInfo[x].ServiceStatus.dwCurrentState)
			{
			case SERVICE_PAUSED:  printf("暂停 \n"); break;
			case SERVICE_STOPPED: printf("停止 \n"); break;
			case SERVICE_RUNNING: printf("运行 (*) \n"); break;
			default: printf("其他 \n");
			}
		}
		delete lpInfo;
	}
	CloseServiceHandle(hSCM);
}

int main(int argc, char *argv[])
{
	// 0x0 => 设备驱动程序
	// 0x2=> 内核模式文件系统驱动程序
	// 0x8 => 文件系统识别器驱动程序
	// 0x10 => 独占一个进程的服务
	// 0x20 => 与其他服务共享一个进程的服务

	Enum_Services(0x10);
	system("pause");
	return 0;
}

我们传入0x10则代表枚举当前系统中的独占一个进程的服务,代码需要使用管理员权限运行,输出效果图如下所示;

编写SCM系统服务

Windows 服务程序的主体框架需要包括关键的两个函数,其中ServiceMain标志着服务程序的入口,而ServiceCtrlHandle则是服务程序的控制处理流程,最后的TellSCM函数则用于通知SCM服务的当前状态,当然了TellSCM可以单独出来也可以写在ServiceCtrlHandle都可以,任何一个正常的服务程序都必须包含这两个关键位置,并且需要将该函数导出,首先展示核心API函数的定义信息。

SERVICE_TABLE_ENTRY 用于定义服务表的结构体。服务表是一个包含服务入口函数和服务名的数组,它告诉 SCM (服务控制管理器)哪个服务程序入口函数与哪个服务相关联。

以下是 SERVICE_TABLE_ENTRY 结构体的定义:

typedef struct _SERVICE_TABLE_ENTRY {
    LPSTR lpServiceName;          // 服务名
    LPSERVICE_MAIN_FUNCTION lpServiceProc;  // 服务入口函数
} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;
  • lpServiceName: 指向服务名的指针。服务名是服务在 SCM 中的标识符,可以通过该名字启动、停止、控制服务等。
  • lpServiceProc: 指向服务入口函数的指针。该函数是服务的主要执行点,当 SCM 启动服务时会调用该函数。

在主程序中,你通过创建 SERVICE_TABLE_ENTRY 数组来定义服务表,然后将其传递给 StartServiceCtrlDispatcher 函数。代码中,服务表包含一个 SERVICE_TABLE_ENTRY 结构体:

SERVICE_TABLE_ENTRY stDispatchTable[] = {
    { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
    { NULL, NULL }
};
  • g_szServiceName: 是你的服务的名字,这里定义了为 “ServiceTest.exe”。
  • (LPSERVICE_MAIN_FUNCTION)ServiceMain: 是指向服务入口函数 ServiceMain 的指针。当 SCM 启动服务时,将调用这个函数。

这个服务表告诉 SCM 与哪个服务相关联,通过哪个函数来启动和管理服务。 StartServiceCtrlDispatcher 函数接受这个服务表作为参数,并负责将控制传递给适当的服务。

StartServiceCtrlDispatcher 用于启动服务控制分发器。这个函数通常在服务程序的 main 函数中调用,它接受一个包含服务表的数组作为参数,并将控制传递给适当的服务。

以下是 StartServiceCtrlDispatcher 函数的原型:

BOOL StartServiceCtrlDispatcher(
  const SERVICE_TABLE_ENTRY *lpServiceTable
);
  • lpServiceTable: 指向 SERVICE_TABLE_ENTRY 结构体数组的指针,该数组定义了服务表。服务表中的每个元素指定了服务的名称和服务入口函数。

该函数返回 BOOL 类型。如果调用成功,返回 TRUE,否则返回 FALSE。如果返回 FALSE,可以通过调用 GetLastError 函数获取错误代码。

RegisterServiceCtrlHandler 用于注册一个服务控制处理程序,该处理程序将接收来自 SCM(服务控制管理器)的控制请求。每个服务都需要注册一个服务控制处理程序,以便在服务状态发生变化时接收通知。

以下是 RegisterServiceCtrlHandler 函数的原型:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(
  LPCTSTR                  lpServiceName,
  LPHANDLER_FUNCTION_EX    lpHandlerProc
);
  • lpServiceName: 指定要注册的服务的名称。这应该是服务在 SCM 中注册的唯一标识符。
  • lpHandlerProc: 指定服务控制处理程序的地址。这是一个指向处理函数的指针,该函数将在接收到控制请求时被调用。

函数返回一个 SERVICE_STATUS_HANDLE 类型的句柄。这个句柄用于标识服务控制管理器中的服务控制处理程序。

SetServiceStatus 用于通知 SCM(服务控制管理器)关于服务的当前状态。这个函数通常在服务的主循环中调用,以便及时向 SCM 报告服务的状态变化。

以下是 SetServiceStatus 函数的原型:

BOOL SetServiceStatus(
  SERVICE_STATUS_HANDLE hServiceStatus,
  LPSERVICE_STATUS      lpServiceStatus
);
  • hServiceStatus: 指定服务控制管理器中的服务的句柄,即由 RegisterServiceCtrlHandler 返回的句柄。
  • lpServiceStatus: 指向 SERVICE_STATUS 结构体的指针,该结构体描述了服务的当前状态。

SERVICE_STATUS 结构体定义如下:

typedef struct _SERVICE_STATUS {
  DWORD dwServiceType;
  DWORD dwCurrentState;
  DWORD dwControlsAccepted;
  DWORD dwWin32ExitCode;
  DWORD dwServiceSpecificExitCode;
  DWORD dwCheckPoint;
  DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
  • dwServiceType: 服务的类型,例如 SERVICE_WIN32_OWN_PROCESS
  • dwCurrentState: 服务的当前状态,例如 SERVICE_RUNNING
  • dwControlsAccepted: 服务接受的控制码,例如 SERVICE_ACCEPT_STOP 表示服务接受停止控制。
  • dwWin32ExitCode: 服务的 Win32 退出码。
  • dwServiceSpecificExitCode: 服务的特定退出码。
  • dwCheckPoint: 在操作进行中时,用于指示操作的进度。
  • dwWaitHint: SCM 期望服务完成操作所需的等待时间。

有了上述接口的说明,并通过遵循微软的对服务编写的定义即可实现一个系统服务,这里的DoTask()是一个自定义函数,该服务在启动后会率先执行此处,此处可用于定义特定的功能,例如开机自启动某个进程,或者是远程创建套接字等,当然了服务程序也可以是exe如下可以使用控制台方式创建。

#include <Windows.h>

// 服务入口函数以及处理回调函数
void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv);
void __stdcall ServiceCtrlHandle(DWORD dwOperateCode);
BOOL TellSCM(DWORD dwState, DWORD dwExitCode, DWORD dwProgress);
// 自定义函数
void DoTask();

// 全局变量
char g_szServiceName[MAX_PATH] = "ServiceTest.exe";    // 自身服务名称 
SERVICE_STATUS_HANDLE g_ServiceStatusHandle = { 0 };

int main(int argc, char * argv[])
{
  // 注册服务入口函数
  SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } };
  ::StartServiceCtrlDispatcher(stDispatchTable);

  return 0;
}

void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv)
{
  g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);

  TellSCM(SERVICE_START_PENDING, 0, 1);
  TellSCM(SERVICE_RUNNING, 0, 0);

  while (TRUE)
  {
    Sleep(5000);
    DoTask();
  }
}

void __stdcall ServiceCtrlHandle(DWORD dwOperateCode)
{
  switch (dwOperateCode)
  {
  case SERVICE_CONTROL_PAUSE:
  {
    // 暂停
    TellSCM(SERVICE_PAUSE_PENDING, 0, 1);
    TellSCM(SERVICE_PAUSED, 0, 0);
    break;
  }
  case SERVICE_CONTROL_CONTINUE:
  {
    // 继续
    TellSCM(SERVICE_CONTINUE_PENDING, 0, 1);
    TellSCM(SERVICE_RUNNING, 0, 0);
    break;
  }
  case SERVICE_CONTROL_STOP:
  {
    // 停止
    TellSCM(SERVICE_STOP_PENDING, 0, 1);
    TellSCM(SERVICE_STOPPED, 0, 0);
    break;
  }
  case SERVICE_CONTROL_INTERROGATE:
  {
    // 询问
    break;
  }
  default:
    break;
  }
}

BOOL TellSCM(DWORD dwState, DWORD dwExitCode, DWORD dwProgress)
{
  SERVICE_STATUS serviceStatus = { 0 };
  BOOL bRet = FALSE;

  ::RtlZeroMemory(&serviceStatus, sizeof(serviceStatus));
  serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  serviceStatus.dwCurrentState = dwState;
  serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
  serviceStatus.dwWin32ExitCode = dwExitCode;
  serviceStatus.dwWaitHint = 3000;

  bRet = ::SetServiceStatus(g_ServiceStatusHandle, &serviceStatus);
  return bRet;
}

void DoTask()
{
  // 自己程序实现部分代码放在这里
}

设置SCM开机运行

独立的SCM程序无法直接双击运行,该服务程序只能通过服务管理器运行,通过使用CreateService将服务管理器程序设置为开机自动运行,并使用StartService将服务启动。

CreateService 函数用于创建一个新的服务。这个函数通常在安装服务时使用。在服务安装过程中,需要指定服务的名称、显示名称、服务类型、启动类型、二进制路径等信息。

以下是 CreateService 函数的原型:

SC_HANDLE CreateService(
  SC_HANDLE hSCManager,
  LPCTSTR   lpServiceName,
  LPCTSTR   lpDisplayName,
  DWORD     dwDesiredAccess,
  DWORD     dwServiceType,
  DWORD     dwStartType,
  DWORD     dwErrorControl,
  LPCTSTR   lpBinaryPathName,
  LPCTSTR   lpLoadOrderGroup,
  LPDWORD   lpdwTagId,
  LPCTSTR   lpDependencies,
  LPCTSTR   lpServiceStartName,
  LPCTSTR   lpPassword
);
  • hSCManager: 服务控制管理器的句柄,可以通过 OpenSCManager 函数获取。
  • lpServiceName: 要创建的服务的名称。这是服务在 SCM 中的唯一标识符。
  • lpDisplayName: 服务的显示名称,这是在服务列表中显示的名称。
  • dwDesiredAccess: 对服务的访问权限,例如 SERVICE_ALL_ACCESS
  • dwServiceType: 服务的类型,例如 SERVICE_WIN32_OWN_PROCESS
  • dwStartType: 服务的启动类型,例如 SERVICE_AUTO_START
  • dwErrorControl: 当服务无法启动时的错误处理控制。
  • lpBinaryPathName: 服务程序的可执行文件的路径。
  • lpLoadOrderGroup: 指定服务应属于的加载顺序组。
  • lpdwTagId: 指向接收服务标识符的指针。
  • lpDependencies: 指定服务依赖的服务名称。
  • lpServiceStartName: 服务启动时使用的用户名。
  • lpPassword: 服务启动时使用的密码。

函数返回一个 SC_HANDLE 类型的句柄,该句柄标识了新创建的服务。如果函数调用失败,返回 NULL。可以通过调用 GetLastError 函数获取错误代码。

StartService 函数用于启动一个已注册的服务。这个函数通常在服务程序中的启动代码或者通过服务管理工具中手动启动服务时使用。

以下是 StartService 函数的原型:

BOOL StartService(
  SC_HANDLE hService,
  DWORD     dwNumServiceArgs,
  LPCTSTR   *lpServiceArgVectors
);
  • hService: 要启动的服务的句柄,可以通过 OpenService 函数获取。
  • dwNumServiceArgs: 指定传递给服务的命令行参数数量。
  • lpServiceArgVectors: 指向包含服务命令行参数的字符串数组。

函数返回一个 BOOL 类型的值,如果调用成功返回 TRUE,否则返回 FALSE。可以通过调用 GetLastError 函数获取错误代码。

ControlService 函数用于向已注册的服务发送控制码,以便执行特定的操作。这个函数通常在服务程序中的控制逻辑或者通过服务管理工具中手动控制服务时使用。

以下是 ControlService 函数的原型:

BOOL ControlService(
  SC_HANDLE        hService,
  DWORD            dwControl,
  LPSERVICE_STATUS lpServiceStatus
);
  • hService: 要控制的服务的句柄,可以通过 OpenService 函数获取。
  • dwControl: 指定服务的控制码,可以是以下之一:
    • SERVICE_CONTROL_CONTINUE: 继续服务。
    • SERVICE_CONTROL_PAUSE: 暂停服务。
    • SERVICE_CONTROL_STOP: 停止服务。
    • 等等,还有其他服务控制码。
  • lpServiceStatus: 指向 SERVICE_STATUS 结构体的指针,用于接收服务的当前状态信息。

函数返回一个 BOOL 类型的值,如果调用成功返回 TRUE,否则返回 FALSE。可以通过调用 GetLastError 函数获取错误代码。

如下代码实现了服务管理的两个关键功能:AutoRunService 函数用于注册并启动服务,使其在系统启动时自动运行;SetServiceStatus 函数用于设置服务的状态,包括停止服务、启动服务和删除服务。

这样的功能对于管理系统服务的状态和自启动行为具有重要意义。然而,需要注意确保在执行这些操作时具有足够的权限,并在实际应用中加强错误处理以确保操作的可靠性。

#include <stdio.h>
#include <Windows.h>
#include <Shlwapi.h>

#pragma comment(lib, "Shlwapi.lib")

// 注册服务自启动
void AutoRunService(char* szFilePath, char* szDescribe)
{
  char szName[MAX_PATH] = { 0 };
  SC_HANDLE scHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  lstrcpy(szName, szFilePath);
  PathStripPath(szName);

  SC_HANDLE scHandleOpen = OpenService(scHandle, szName, SERVICE_ALL_ACCESS);
  if (scHandleOpen == NULL)
  {
    // SERVICE_AUTO_START => 随系统自动启动 | SERVICE_DEMAND_START => 手动启动
    SC_HANDLE scNewHandle = CreateService(scHandle, szName, szDescribe,
      SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
      SERVICE_ERROR_IGNORE, szFilePath, NULL, NULL, NULL, NULL, NULL);
    StartService(scNewHandle, 0, NULL);
    CloseServiceHandle(scNewHandle);
    printf("[*] 创建服务完成 \n");
  }
  CloseServiceHandle(scHandleOpen);
  CloseServiceHandle(scHandle);
}

// 设置服务状态
BOOL SetServiceStatus(char* szName, int Status)
{
  SERVICE_STATUS ss;
  SC_HANDLE scHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  SC_HANDLE scHandleOpen = OpenService(scHandle, szName, SERVICE_ALL_ACCESS);
  BOOL bRet = TRUE;
  if (scHandleOpen != NULL)
  {
    switch (Status)
    {
    case 1: if (!ControlService(scHandleOpen, SERVICE_CONTROL_STOP, &ss)) { bRet = FALSE; }; break;
    case 2: if (!StartService(scHandleOpen, 0, NULL)) { bRet = FALSE; }; break;
    case 3: if (!DeleteService(scHandleOpen)) { bRet = FALSE; }break;
    default:break;
    }
  }
  CloseServiceHandle(scHandleOpen);
  CloseServiceHandle(scHandle);
  return bRet;
}

int main(int argc, char* argv[])
{
  // 注册为自启动服务将d:/myservice.exe 注册为自启动服务 后面是描述信息
  AutoRunService((char *)"d:/myservice.exe", (char *)"Microsoft Windows Security Services");

  // 根据服务名称管理服务 1=>停止服务 2=>启动服务 3=>删除服务
  BOOL ret = SetServiceStatus((char *)"myservice.exe", 2);
  printf("状态: %d \n", ret);

  system("pause");
  return 0;
}

运行上述代码将自动把d:/myservice.exe添加至服务自启动列表,并可以通过枚举的方式找到该服务的具体信息,如下图所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/182775.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

win10戴尔电脑安装操作系统遇到的问题MBR分区表只能安装GPT磁盘

首先按F2启动boot管理界面 调整启动盘的启动顺序&#xff0c;这里启动U盘为第一顺序。 第一步 选择安装程序的磁盘 第二步 转换磁盘为GPT磁盘 一般出现 磁盘0和1&#xff0c;说明存在两个盘 &#xff0c;这里两个盘不是说的是C盘和D盘的问题&#xff0c;而是在物理上实际存在…

2024年度投资策略:AI大模型和半导体国产化加速

今天分享的是AI系列深度研究报告&#xff1a;《2024年度投资策略&#xff1a;AI大模型和半导体国产化加速》。 &#xff08;报告出品方&#xff1a;东方证券&#xff09; 报告共计&#xff1a;48页 前言: 行情回顾与未来展望 电子板块涨幅转正&#xff0c;信心逐渐回归。截至…

Ps:参考线

参考线 Guides用于帮助精确地定位图像或元素&#xff0c;显示为浮动在图像上的非打印线&#xff0c;可以移动或移除&#xff0c;还可以临时锁定。 Ps 中的参考线可分为三大类&#xff1a;画布参考线、画板参考线和智能参考线。 可在“首选项/参考线、网格和切片”中设置参考线的…

【Java线程通信】一窥究竟

今天我们要聊一聊Java线程通信。如果你是一个Java开发者&#xff0c;那么你肯定知道线程是Java中的一个重要概念。线程是程序执行的最小单位&#xff0c;它可以独立运行&#xff0c;也可以与其他线程共享资源。那么&#xff0c;线程之间如何进行通信呢&#xff1f;这就是我们今…

【uniapp】部分图标点击事件无反应

比如&#xff1a;点击这个图标在h5都正常&#xff0c;在小程序上无反应 css&#xff1a;也设置z-index&#xff0c;padding 页面上也试过click.native.stop.prevent"changePassword()" 时而可以时而不行&#xff0c; 最后发现是手机里输入键盘的原因&#xff0c;输…

文心一言 VS 讯飞星火 VS chatgpt (141)-- 算法导论12.1 1题

一、用go语言&#xff0c;对于关键字集合(1&#xff0c;4&#xff0c;5&#xff0c;10&#xff0c;16&#xff0c;17&#xff0c;21)&#xff0c;分别画出高度为 2、3、4、5和6的二又搜索树。 文心一言&#xff1a; 以下是用Go语言为给定的关键字集合(1, 4, 5, 10, 16, 17, 2…

马养殖场建设VR模拟实训教学平台具有灵活性和复用性

为保障养殖场生物安全&#xff0c;避免疫病传播&#xff0c;学生出入养殖场受时间和地域的限制&#xff0c; 生产实习多以参观为主&#xff0c;通过畜牧企业技术人员的讲解&#xff0c;学生被动了解生产过程。为了解决畜牧养殖实训难的问题&#xff0c;借助VR技术开展畜牧养殖虚…

CUDA与GPU编程

文章目录 CUDA与GPU编程1. 并行处理与GPU体系架构1.1 并行处理简介1.1.1 串行处理与并行处理的区别1.1.2 并行处理的概念1.1.3 常见的并行处理 1.2 GPU并行处理1.2.1 GPU与CPU并行处理的异同1.2.2 CPU的优化方式1.2.3 GPU的特点 1.3 环境搭建 CUDA与GPU编程 1. 并行处理与GPU体…

关于easy-es的聚合问题

es实体类&#xff1a; public class ChemicalES {IndexId(type IdType.CUSTOMIZE)private Long id;HighLightIndexField(fieldType FieldType.TEXT, analyzer "ik_max_word")private String name;IndexField(fieldType FieldType.KEYWORD)private List<Stri…

某60区块链安全之未初始化的存储指针实战一学习记录

区块链安全 文章目录 区块链安全未初始化的存储指针实战一实验目的实验环境实验工具实验原理实验过程 未初始化的存储指针实战一 实验目的 学会使用python3的web3模块 学会分析以太坊智能合约未初始化的存储指针漏洞 找到合约漏洞进行分析并形成利用 实验环境 Ubuntu18.04操…

Vue3 封装组件库并发布到npm仓库

一、创建 Vue3 TS Vite 项目 输入项目名称&#xff0c;并依次选择需要安装的依赖项 npm create vuelatest 项目目录结构截图如下&#xff1a; 二、编写组件代码、配置项和本地打包测试组件 在项目根目录新建 package 文件夹用于存放组件 &#xff08;以customVideo为例&a…

HTTPS攻击怎么防御?

HTTPS 简介 超文本传输安全协议&#xff08; HTTPS &#xff09;是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信&#xff0c;但利用 SSL/TLS 来加密数据包。 HTTPS 开发的主要目的&#xff0c;是提供对网站服务器的身份认证&#xff0c;保护交换数据的…

【开源】基于Vue.js的数据可视化的智慧河南大屏

项目编号&#xff1a; S 059 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S059&#xff0c;文末获取源码。} 项目编号&#xff1a;S059&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 …

基于遗传优化的多属性判决5G-Wifi网络切换算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 .......................................................................... %接收功率、网…

浅谈 Guava 中的 ImmutableMap.of 方法的坑

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐&…

练习七-在Verilog中使用任务task

在Verilog中使用任务task 1&#xff0c;任务目的2&#xff0c;RTL代码&#xff0c;交换3&#xff0c;测试代码4&#xff0c;波形显示 1&#xff0c;任务目的 &#xff08;1&#xff09;掌握任务在verilog模块设计中的应用&#xff1b; &#xff08;2&#xff09;学会在电平敏感…

新一代网络监控技术——Telemetry

一、Telemetry的背景 传统的网络设备监控方式有SNMP、CLI、Syslog、NetStream、sFlow&#xff0c;其中SNMP为主流的监控数据方式。而随着网络系统规模的扩大&#xff0c;网络设备数量的增多&#xff0c;网络结构的复杂&#xff0c;相应监控要求也不断提升&#xff0c;如今这些…

CUDA学习笔记9——CUDA 共享内存 / Shared Memory

由于共享内存拥有仅次于寄存器的读写速度&#xff0c;比全局内存快得多。因此&#xff0c;能够用共享内存访问替换全局内存访问的场景都可以考虑做对应的优化。 不利用共享内存的矩阵乘法 不利用共享内存的矩阵乘法的直接实现。每个线程读取A的一行和B的一列&#xff0c;并计…

CVE-2022-0543(Redis 沙盒逃逸漏洞)

简介 CVE-2022-0543是一个与Redis相关的安全漏洞。在Redis中&#xff0c;用户连接后可以通过eval命令执行Lua脚本&#xff0c;但在沙箱环境中脚本无法执行命令或读取文件。然而&#xff0c;攻击者可以利用Lua沙箱中遗留的变量package的loadlib函数来加载动态链接库liblua5.1.s…

jenkins 参数构建

应用保存 [rootjenkins-node1 .ssh]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved i…