通过代码实现驱动注册和服务管理

简介:通过代码实现驱动及服务的查询、创建、启动、停止以及删除等。

前言

之前我们介绍过如何把驱动文件能够实际运行起来,是通过服务的方式注册到系统里,但是我们当时用的是一个大佬的工具,虽然这个工具非常好用(具体可以看这个链接https://daliu.net/posts/20241222/#%E6%BC%94%E7%A4%BA%E5%B9%B6%E6%B5%8B%E8%AF%95%E7%A8%8B%E5%BA%8F),但我们还是想通过自己的方式能实现驱动的管理,那么,我们今天就来了解下这一块的内容。

服务管理器

微软用来管理所有已安装的服务和驱动的程序就是服务管理器,具体可以参考这个官方链接https://learn.microsoft.com/zh-cn/windows/win32/services/about-services,这个既是一个工具,也有对应的接口,可以方便我们实现对服务和驱动程序的管理与配置。

命令行工具

在通过代码管理这一块之前,我们首先了解一下命令行工具,sc.exe,在windows的命令行,直接就可以使用,而且使用起来非常的方便简单,因为无非就是几种操作,查询服务的名称,开启服务,关闭服务,删除服务,创建服务。

2

为了方便查询,在此把命令放到下面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
query-----------查询服务的状态,或枚举服务类型的状态。
queryex---------查询服务的扩展状态,或枚举服务类型的状态。
start-----------启动服务。
pause-----------向服务发送 PAUSE 控制请求。
interrogate-----向服务发送 INTERROGATE 控制请求。
continue--------向服务发送 CONTINUE 控制请求。
stop------------向服务发送 STOP 请求。
config----------更改服务的配置(永久)description-----更改服务的描述。
failure---------更改失败时服务执行的操作。
failureflag-----更改服务的失败操作标志。
sidtype---------更改服务的服务 SID 类型。
privs-----------更改服务的所需特权。
managedaccount--更改服务以将服务帐户密码标记为由 LSA 管理。
qc--------------查询服务的配置信息。
qdescription----查询服务的描述。
qfailure--------查询失败时服务执行的操作。
qfailureflag----查询服务的失败操作标志。
qsidtype--------查询服务的服务 SID 类型。
qprivs----------查询服务的所需特权。
qtriggerinfo----查询服务的触发器参数。
qpreferrednode--查询服务的首选 NUMA 节点。
qmanagedaccount-查询服务是否将帐户与 LSA 管理的密码结合使用。
qprotection-----查询服务的进程保护级别。
quserservice----查询用户服务模板的本地实例。
delete ----------(从注册表中)删除服务。
create----------创建服务(并将其添加到注册表中)control---------向服务发送控制。
sdshow----------显示服务的安全描述符。
sdset-----------设置服务的安全描述符。
showsid---------显示与任意名称对应的服务 SID 字符串。
triggerinfo-----配置服务的触发器参数。
preferrednode---设置服务的首选 NUMA 节点。
GetDisplayName--获取服务的 DisplayName。
GetKeyName------获取服务的 ServiceKeyName。
EnumDepend------枚举服务依赖关系。

我们在命令行输入一个sc create 看看创建服务应该有什么要求?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
描述:
        在注册表和服务数据库中创建服务项。
用法:
        sc <server> create [service name] [binPath= ] <option1> <option2>...

选项:
注意: 选项名称包括等号。等号和值之间需要一个空格。
 type= <own|share|interact|kernel|filesys|rec|userown|usershare>
       (默认 = own)
 start= <boot|system|auto|demand|disabled|delayed-auto>
       (默认 = demand)
 error= <normal|severe|critical|ignore>
       (默认 = normal)
 binPath= <.exe 文件的 BinaryPathName>
 group= <LoadOrderGroup>
 tag= <yes|no>
 depend= <依存关系(以 / (斜杠)分隔)>
 obj= <AccountName|ObjectName>
       (默认= LocalSystem)
 DisplayName= <显示名称>
 password= <密码>

我们按照上文的指示在命令行输入如下内容,注意,驱动的地址对应的就是自己的驱动文件哈,这都理解起来不难。(这里有坑,后面说)

1
sc create MyDriver1blog binPath="C:\Users\Anonymous\Desktop\MyDriver1blog.sys" DisplayName="MyDriver1blog"

然后看到命令行如下效果,我们在用sc queryex MyDriver1blog查询下驱动的状态,可以看到已经创建成功,处于未启动的状态。(注意:一定是管理员方式开启终端哈)

3

同接着在用服务管理器的窗口直接看下,win+R,然后输入 services.msc,回车之后,打开服务管理器,我们可以看到我们的MyDriver1blog的服务已经存在了。

4

然后我们用sc命令开启服务,sc start 服务名,但是会报错:[SC] 由于发生错误 129, StartService 失败。因为我们开启的服务是驱动,不是应用文件,所以,我们在最开始创建的时候,有一个type参数,默认是own,这就是用户所有,意思就是应用文件,所以我们要修改为kernel。

既然如此,我们先删除这个服务,sc delete 服务名,删除成功,我们再重新创建一次服务,并把type参数加上去,重新创建并开启服务,展示如下效果:

5

并且我们在windbg中也看到了如下效果,对应要打印的内容也是打印了出来

6

然后用命令 sc stop 驱动名 和sc delete 驱动名就可以把这个服务关闭并删除了,不多赘述了哈。

7

代码实现

通过windows的默认工具已经能够非常好的实现这个功能,那么通过代码是否能够实现你呢,我们在微软的官方文档里可以看到几个主要的函数,地址如下:https://learn.microsoft.com/zh-cn/windows/win32/services/service-configuration-programs,虽然有对应的c语言的接口,但是我们依然计划用go语言来写,go里面封装了对应的应用,执行效率也不低。

首先列一下在c语言对应的api函数分别是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//打开服务控制管理器
SC_HANDLE OpenSCManagerA(
  [in, optional] LPCSTR lpMachineName,
  [in, optional] LPCSTR lpDatabaseName,
  [in]           DWORD  dwDesiredAccess
);

//打开服务,获取句柄
SC_HANDLE OpenServiceA(
  [in] SC_HANDLE hSCManager,
  [in] LPCSTR    lpServiceName,
  [in] DWORD     dwDesiredAccess
);

//创建服务
SC_HANDLE CreateServiceA(
  [in]            SC_HANDLE hSCManager,
  [in]            LPCSTR    lpServiceName,
  [in, optional]  LPCSTR    lpDisplayName,
  [in]            DWORD     dwDesiredAccess,
  [in]            DWORD     dwServiceType,
  [in]            DWORD     dwStartType,
  [in]            DWORD     dwErrorControl,
  [in, optional]  LPCSTR    lpBinaryPathName,
  [in, optional]  LPCSTR    lpLoadOrderGroup,
  [out, optional] LPDWORD   lpdwTagId,
  [in, optional]  LPCSTR    lpDependencies,
  [in, optional]  LPCSTR    lpServiceStartName,
  [in, optional]  LPCSTR    lpPassword
);

// 删除服务
BOOL DeleteService(
  [in] SC_HANDLE hService
);

// 控制服务,比如,停止服务
BOOL ControlService(
  [in]  SC_HANDLE        hService,
  [in]  DWORD            dwControl,
  [out] LPSERVICE_STATUS lpServiceStatus
);
枚举所有服务

在进行单一服务增删改查前,我们先尝试枚举所有的服务,并打印出服务的一些信息,比如服务的名字等。枚举服务用的api函数是EnumServicesStatusExA

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
BOOL EnumServicesStatusExA(
  [in]                SC_HANDLE    hSCManager,
  [in]                SC_ENUM_TYPE InfoLevel,
  [in]                DWORD        dwServiceType,
  [in]                DWORD        dwServiceState,
  [out, optional]     LPBYTE       lpServices,
  [in]                DWORD        cbBufSize,
  [out]               LPDWORD      pcbBytesNeeded,
  [out]               LPDWORD      lpServicesReturned,
  [in, out, optional] LPDWORD      lpResumeHandle,
  [in, optional]      LPCSTR       pszGroupName
);

使用前提是需要打开服务管理器,打开服务用go的方式去写如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import (
    "golang.org/x/sys/windows"
)
// 需要引入这个windows包,里面含有很多操作

scmHandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS)
if err != nil {
    fmt.Printf("Failed to open SCM: %v\n", err)
    return
}

为了方便我们可以把对应的句柄都放在一个SCM的结构体里,也就是自己写一个对象来存储这些东西

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ENUM_SERVICE_STATUS_PROCESS structure
type ENUM_SERVICE_STATUS_PROCESS struct {
    ServiceName   *byte
    DisplayName   *byte
    ServiceStatus windows.SERVICE_STATUS_PROCESS
}

// SCM represents a Service Control Manager struct
type SCM struct {
    Handle        windows.Handle
    services      []ENUM_SERVICE_STATUS_PROCESS
    serviceHandle windows.Handle
    serviceName   string
}

//其中ENUM_SERVICE_STATUS_PROCESS这个结构体就是枚举返回的描述服务内容的结构体

然后给这个结构体定义对应的方法,枚举所有的服务,windows.EnumServicesStatusEx这个函数是主要调用的函数,在c里面也有同名函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
func (scm *SCM) EnumServicesStatusEx(serviceType, serviceState uint32) ([]ENUM_SERVICE_STATUS_PROCESS, error) {
    var (
        bytesNeeded, servicesReturned uint32
        resumeHandle                  uint32 = 0
        buffer                        []byte
        tempservices *byte
    )

    gN, error := syscall.UTF16PtrFromString("")
    if error != nil {
        return nil, error
    }

// 第一次可以把这个缓冲区大小为0
//bytesNeeded这个参数会返回还需要多少大小
//这样就可以知道总共需要多大来填充缓冲区

    err := windows.EnumServicesStatusEx(
        scm.Handle,//sc管理器句柄
        windows.SC_ENUM_PROCESS_INFO,//使用这个参数就可以检索所有服务名称和状态
// 服务类型
//windows.SERVICE_KERNEL_DRIVER 驱动的服务
//windows.SERVICE_WIN32 win32应用的服务
        serviceType,

// 服务状态
//windows.SERVICE_STATE_ALL 所有状态
        serviceState,
// 存储返回信息的缓冲区
        tempservices,
        0,
        &bytesNeeded,
// 返回服务的数量
        &servicesReturned,
        &resumeHandle,
        gN,
    )

    if err != nil && err != syscall.ERROR_MORE_DATA {
        fmt.Printf("failed to get buffer size: %v\n", err)
        return nil, err
    }
    fmt.Println("bytesNeeded:", bytesNeeded)

    // Allocate buffer based on bytes needed
    buffer = make([]byte, bytesNeeded)

    err = windows.EnumServicesStatusEx(
        scm.Handle,
        windows.SC_ENUM_PROCESS_INFO,
        serviceType,
        serviceState,
        &buffer[0],
        uint32(len(buffer)),
        &bytesNeeded,
        &servicesReturned,
        &resumeHandle,
        gN,
    )
    if err != nil {
        fmt.Printf("failed to get services: %v\n", err)
        return nil, err
    }

    // Parse the buffer into ENUM_SERVICE_STATUS_PROCESS structures
    services := make([]ENUM_SERVICE_STATUS_PROCESS, servicesReturned)
    for i := uint32(0); i < servicesReturned; i++ {
        offset := i * uint32(unsafe.Sizeof(services[0]))
        services[i] = *(*ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buffer[offset]))
    }

    scm.services = services
    return services, nil
}

通过上述方法获取之后再打印所有的服务,注意返回的内容都是双字节,所以需要转换,通过ai给了个方法,代码如下:

1
2
3
4
5
6
7
8
9
// 打印服务信息
for _, service := range services {
    serviceName := syscall.UTF16ToString((*[1 << 30]uint16)(unsafe.Pointer(service.ServiceName))[:])
    displayName := syscall.UTF16ToString((*[1 << 30]uint16)(unsafe.Pointer(service.DisplayName))[:])
    fmt.Printf("Service Name: %s, Display Name: %s, ProcessId: %d\n",
        serviceName,
        displayName,
        service.ServiceStatus.ProcessId)
}

效果如下:

8

创建、开启、停止、删除服务

创建服务相对就不复杂了,只不过需要几个固定的参数,跟我们在命令行大差不多,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// CreateService 创建服务
func (scm *SCM) CreateService(serviceName, displayName string, desiredAccess uint32, pathName string) error {

    // 将字符串转换为 UTF-16 编码的指针
    sN, error := syscall.UTF16PtrFromString(serviceName)
    if error != nil {
        return fmt.Errorf("failed to convert service name: %v", error)
    }
    dN, error := syscall.UTF16PtrFromString(displayName)
    if error != nil {
        return fmt.Errorf("failed to convert display name: %v", error)
    }
    pN, error := syscall.UTF16PtrFromString(pathName)
    if error != nil {
        return fmt.Errorf("failed to convert path name: %v", error)
    }

    // 创建服务
    serviceHandle, err := windows.CreateService(scm.Handle,
        sN,
        dN,
        desiredAccess,
        windows.SERVICE_KERNEL_DRIVER,
        windows.SERVICE_DEMAND_START,
        windows.SERVICE_ERROR_NORMAL,
        pN,
        nil, nil, nil, nil, nil)
    if err != nil {
        return fmt.Errorf("failed to create service: %v", err)
    }

    scm.serviceHandle = serviceHandle
    scm.serviceName = serviceName

    return nil
}

主要的是需要将 将字符串转换为 UTF-16 编码的指针,因为入参都是双字节的字符串,其次三个用到windows包的常量,跟命令行输入的内容差不多,剩下就是0了,其中要注意一点就是desiredAccess这个参数,是服务权限,总结来说涉及两种一个是针对服务管理器,一个是针对服务的,可以参考这个链接https://learn.microsoft.com/zh-cn/windows/win32/services/service-security-and-access-rights

调用部分的代码如下,我们用的是desiredAccess值是用windows包的常量,windows.SERVICE_ALL_ACCESS

1
2
3
4
5
err = scm.CreateService("MyDriver1blog", "mydriver", windows.SERVICE_ALL_ACCESS, "C:\\Users\\Anonymous\\Desktop\\MyDriver1blog.sys")
if err != nil {
    fmt.Printf("Failed to create service: %v\n", err)
    return
}

开启服务的代码就更加简单了,因为创建服务了想要开启,需要获取打开服务获取句柄,然后开启服务,跟打开文件一样,想要读取文件,就需要打开文件,然后用文件句柄进行操作,针对服务也是一样的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// StartCurrentService 开启当前创建的服务
func (scm *SCM) StartCurrentService() error {
    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    sN, error := syscall.UTF16PtrFromString(scm.serviceName)
    if error != nil {
        return fmt.Errorf("failed to convert service name: %v", error)
    }

    // 开启服务
    // 先打开获取句柄
    scm.serviceHandle, error = windows.OpenService(scm.Handle,
        sN, windows.SERVICE_ALL_ACCESS)
    if error != nil {
        return fmt.Errorf("failed to open service: %v", error)
    }
    // 通过句柄打开服务
    error = windows.StartService(scm.serviceHandle, 0, nil)
    if error != nil {
        return fmt.Errorf("failed to start service: %v", error)
    }
    return nil
}

关闭和删除也是类似的方式,代码如下,但是要注意,停止服务并不是stop,而是发送一个控制指令,让服务停止,也就是这个api函数,windows.ControlService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// StopCurrentService 停止当前创建的服务
func (scm *SCM) StopCurrentService() error {

    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    if scm.serviceHandle == 0 {
        return fmt.Errorf("service handle is empty")
    }

    // 停止服务
    var serviceStatus windows.SERVICE_STATUS
    err := windows.ControlService(scm.serviceHandle,
        windows.SERVICE_CONTROL_STOP, &serviceStatus)
    if err != nil {
        return fmt.Errorf("failed to stop service: %v", err)
    }
    return nil
}

// DeleteCurrentService 删除当前创建的服务
func (scm *SCM) DeleteCurrentService() error {

    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    if scm.serviceHandle == 0 {
        return fmt.Errorf("service handle is empty")
    }

    // 删除服务
    err := windows.DeleteService(scm.serviceHandle)
    if err != nil {
        return fmt.Errorf("failed to delete service: %v", err)
    }
    return nil
}

然后去执行对应函数,效果如下:

9

10

11

完整的代码放下来:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
package main

import (
    "bytes"
    "fmt"
    "io"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
    "golang.org/x/text/encoding/simplifiedchinese"
    "golang.org/x/text/transform"
)

var (
    advapi32                 = windows.NewLazySystemDLL("advapi32.dll")
    procEnumServicesStatusEx = advapi32.NewProc("EnumServicesStatusExA")
)

// ENUM_SERVICE_STATUS_PROCESS structure
type ENUM_SERVICE_STATUS_PROCESS struct {
    ServiceName   *byte
    DisplayName   *byte
    ServiceStatus windows.SERVICE_STATUS_PROCESS
}

// SCM represents a Service Control Manager struct
type SCM struct {
    Handle        windows.Handle
    services      []ENUM_SERVICE_STATUS_PROCESS
    serviceHandle windows.Handle
    serviceName   string
}

func main() {

    scm := NewSCM()
    // 打开服务控制管理器
    fmt.Println("打开服务控制管理器---------------------")
    scmHandle, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_ALL_ACCESS)
    if err != nil {
        fmt.Printf("Failed to open SCM: %v\n", err)
        return
    }
    scm.Handle = scmHandle
    fmt.Println("打开服务控制管理器----------成功--------")
    defer windows.CloseServiceHandle(scm.Handle)

    fmt.Println("创建服务---------------------")
    err = scm.CreateService("MyDriver1blog", "mydriver", windows.SERVICE_ALL_ACCESS, "C:\\Users\\Anonymous\\Desktop\\MyDriver1blog.sys")
    if err != nil {
        fmt.Printf("Failed to create service: %v\n", err)
        return
    }
    fmt.Println("创建服务---------成功--------")

    fmt.Println("开启服务---------------------")

    err = scm.StartCurrentService()
    if err != nil {
        fmt.Printf("Failed to start service: %v\n", err)
        return
    }

    fmt.Println("开启服务---------成功--------")
    var input string
    fmt.Scan(&input)

    err = scm.StopCurrentService()
    if err != nil {
        fmt.Printf("Failed to stop service: %v\n", err)
        return
    }

    fmt.Println("删除服务---------------------")
    err = scm.DeleteCurrentService()
    if err != nil {
        fmt.Printf("Failed to delete service: %v\n", err)
        return
    }
    fmt.Println("删除服务---------成功--------")

    fmt.Println("枚举内核服务-----------------")

    enumServicesStatusEx(scm)
}

// enumServicesStatusEx 枚举服务状态
func enumServicesStatusEx(scm *SCM) {
    // 枚举所有服务
    fmt.Println("枚举服务---------------------")
    services, err := scm.EnumServicesStatusEx(windows.SERVICE_KERNEL_DRIVER, windows.SERVICE_STATE_ALL)
    if err != nil {
        fmt.Printf("Failed to enumerate services: %v\n", err)
        return
    }

    // 打印服务信息
    for _, service := range services {
        serviceName := syscall.UTF16ToString((*[1 << 30]uint16)(unsafe.Pointer(service.ServiceName))[:])
        displayName := syscall.UTF16ToString((*[1 << 30]uint16)(unsafe.Pointer(service.DisplayName))[:])
        fmt.Printf("Service Name: %s, Display Name: %s, ProcessId: %d\n",
            serviceName,
            displayName,
            service.ServiceStatus.ProcessId)
    }

}

// NewSCM 初始化SCM结构体
func NewSCM() *SCM {
    return &SCM{}
}

// EnumServicesStatusEx enumerates services of a specified type and state.
func (scm *SCM) EnumServicesStatusEx(serviceType, serviceState uint32) ([]ENUM_SERVICE_STATUS_PROCESS, error) {
    var (
        bytesNeeded, servicesReturned uint32
        resumeHandle                  uint32 = 0
        buffer                        []byte
    )

    gN, error := syscall.UTF16PtrFromString("")
    if error != nil {
        return nil, error
    }
    var tempservices *byte
    err := windows.EnumServicesStatusEx(
        scm.Handle,
        windows.SC_ENUM_PROCESS_INFO,
        serviceType,
        serviceState,
        tempservices,
        0,
        &bytesNeeded,
        &servicesReturned,
        &resumeHandle,
        gN,
    )

    if err != nil && err != syscall.ERROR_MORE_DATA {
        fmt.Printf("failed to get buffer size: %v\n", err)
        return nil, err
    }
    fmt.Println("bytesNeeded:", bytesNeeded)

    // Allocate buffer based on bytes needed
    buffer = make([]byte, bytesNeeded)

    err = windows.EnumServicesStatusEx(
        scm.Handle,
        windows.SC_ENUM_PROCESS_INFO,
        serviceType,
        serviceState,
        &buffer[0],
        uint32(len(buffer)),
        &bytesNeeded,
        &servicesReturned,
        &resumeHandle,
        gN, // 修改此处
    )
    if err != nil {
        fmt.Printf("failed to get services: %v\n", err)
        return nil, err
    }

    // Parse the buffer into ENUM_SERVICE_STATUS_PROCESS structures
    services := make([]ENUM_SERVICE_STATUS_PROCESS, servicesReturned)
    for i := uint32(0); i < servicesReturned; i++ {
        offset := i * uint32(unsafe.Sizeof(services[0]))
        services[i] = *(*ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buffer[offset]))
    }

    scm.services = services
    return services, nil
}

// decodeGBK converts a GBK-encoded string to UTF-8
func decodeGBK(s string) string {
    reader := transform.NewReader(
        bytes.NewReader([]byte(s)), // 使用 bytes.NewReader 将 []byte 转换为 io.Reader
        simplifiedchinese.GBK.NewDecoder(),
    )
    d, err := io.ReadAll(reader)
    if err != nil {
        return s
    }
    return string(d)
}

// CreateService 创建服务
func (scm *SCM) CreateService(serviceName, displayName string, desiredAccess uint32, pathName string) error {

    // 将字符串转换为 UTF-16 编码的指针
    sN, error := syscall.UTF16PtrFromString(serviceName)
    if error != nil {
        return fmt.Errorf("failed to convert service name: %v", error)
    }
    dN, error := syscall.UTF16PtrFromString(displayName)
    if error != nil {
        return fmt.Errorf("failed to convert display name: %v", error)
    }
    pN, error := syscall.UTF16PtrFromString(pathName)
    if error != nil {
        return fmt.Errorf("failed to convert path name: %v", error)
    }

    // 创建服务
    serviceHandle, err := windows.CreateService(scm.Handle,
        sN,
        dN,
        desiredAccess,
        windows.SERVICE_KERNEL_DRIVER,
        windows.SERVICE_DEMAND_START,
        windows.SERVICE_ERROR_NORMAL,
        pN,
        nil, nil, nil, nil, nil)
    if err != nil {
        return fmt.Errorf("failed to create service: %v", err)
    }

    scm.serviceHandle = serviceHandle
    scm.serviceName = serviceName

    return nil
}

// StartCurrentService 开启当前创建的服务
func (scm *SCM) StartCurrentService() error {
    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    sN, error := syscall.UTF16PtrFromString(scm.serviceName)
    if error != nil {
        return fmt.Errorf("failed to convert service name: %v", error)
    }

    // 开启服务
    scm.serviceHandle, error = windows.OpenService(scm.Handle,
        sN, windows.SERVICE_ALL_ACCESS)
    if error != nil {
        return fmt.Errorf("failed to open service: %v", error)
    }
    error = windows.StartService(scm.serviceHandle, 0, nil)
    if error != nil {
        return fmt.Errorf("failed to start service: %v", error)
    }
    return nil
}

// StopCurrentService 停止当前创建的服务
func (scm *SCM) StopCurrentService() error {

    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    if scm.serviceHandle == 0 {
        return fmt.Errorf("service handle is empty")
    }

    // 停止服务
    var serviceStatus windows.SERVICE_STATUS
    err := windows.ControlService(scm.serviceHandle,
        windows.SERVICE_CONTROL_STOP, &serviceStatus)
    if err != nil {
        return fmt.Errorf("failed to stop service: %v", err)
    }
    return nil
}

// DeleteCurrentService 删除当前创建的服务
func (scm *SCM) DeleteCurrentService() error {

    if scm.serviceName == "" {
        return fmt.Errorf("service name is empty")
    }

    if scm.serviceHandle == 0 {
        return fmt.Errorf("service handle is empty")
    }

    // 删除服务
    err := windows.DeleteService(scm.serviceHandle)
    if err != nil {
        return fmt.Errorf("failed to delete service: %v", err)
    }
    return nil
}

今天就写到这里,后面再集中到wails的小工具上,展示起来效果就更好了。

updatedupdated2025-02-092025-02-09