Skip to main content

windows_erg/service/
manager.rs

1use windows::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_SERVICE_DOES_NOT_EXIST};
2use windows::Win32::System::Services::{
3    CloseServiceHandle, ENUM_SERVICE_STATUS_PROCESSW, EnumServicesStatusExW, OpenSCManagerW,
4    OpenServiceW, SC_ENUM_PROCESS_INFO, SC_HANDLE, SC_MANAGER_CONNECT,
5    SC_MANAGER_ENUMERATE_SERVICE, SERVICE_STATE_ALL, SERVICE_WIN32,
6};
7
8use super::service::{Service, as_pcwstr};
9use super::status::ServiceStatus;
10use super::types::{ServiceAccess, ServiceManagerAccess};
11use crate::Result;
12use crate::error::{
13    AccessDeniedError, Error, InvalidParameterError, NotFoundError, ServiceError,
14    ServiceManagerError, ServiceNotFoundError,
15};
16use crate::utils::to_utf16_nul;
17
18/// Handle to the Windows Service Control Manager.
19pub struct ServiceManager {
20    handle: SC_HANDLE,
21}
22
23impl ServiceManager {
24    /// Open a service manager handle with default connect + enumeration rights.
25    pub fn connect() -> Result<Self> {
26        let rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE;
27        Self::connect_with_access(ServiceManagerAccess::Custom(rights))
28    }
29
30    /// Open a service manager handle with explicit access rights.
31    pub fn connect_with_access(access: ServiceManagerAccess) -> Result<Self> {
32        let handle = unsafe { OpenSCManagerW(None, None, access.to_windows()) }.map_err(|e| {
33            if e.code().0 == ERROR_ACCESS_DENIED.to_hresult().0 {
34                return Error::AccessDenied(AccessDeniedError::new("service manager", "connect"));
35            }
36
37            Error::Service(ServiceError::ManagerError(ServiceManagerError::with_code(
38                "connect",
39                "OpenSCManagerW failed",
40                e.code().0,
41            )))
42        })?;
43
44        Ok(ServiceManager { handle })
45    }
46
47    /// Open a service by name with default access (query/start/stop/pause-continue).
48    pub fn open(&self, name: &str) -> Result<Service> {
49        let access = ServiceAccess::QueryStatus.to_windows()
50            | ServiceAccess::Start.to_windows()
51            | ServiceAccess::Stop.to_windows()
52            | ServiceAccess::PauseContinue.to_windows();
53        self.open_with_access(name, ServiceAccess::Custom(access))
54    }
55
56    /// Open a service by name with explicit access rights.
57    pub fn open_with_access(&self, name: &str, access: ServiceAccess) -> Result<Service> {
58        if name.trim().is_empty() {
59            return Err(Error::InvalidParameter(InvalidParameterError::new(
60                "name",
61                "service name cannot be empty",
62            )));
63        }
64
65        let name_wide = to_utf16_nul(name);
66        let handle =
67            unsafe { OpenServiceW(self.handle, as_pcwstr(&name_wide), access.to_windows()) }
68                .map_err(|e| {
69                    if e.code().0 == ERROR_SERVICE_DOES_NOT_EXIST.to_hresult().0 {
70                        return Error::NotFound(NotFoundError::new("service", name.to_owned()));
71                    }
72                    if e.code().0 == ERROR_ACCESS_DENIED.to_hresult().0 {
73                        return Error::AccessDenied(AccessDeniedError::new(
74                            name.to_owned(),
75                            "open",
76                        ));
77                    }
78
79                    Error::Service(ServiceError::NotFound(ServiceNotFoundError::with_code(
80                        name.to_owned(),
81                        e.code().0,
82                    )))
83                })?;
84
85        Ok(Service::new(handle, name.to_owned()))
86    }
87
88    /// Enumerate all services.
89    pub fn list(&self) -> Result<Vec<ServiceStatus>> {
90        let mut out_services = Vec::with_capacity(256);
91        self.list_with_buffer(&mut out_services)?;
92        Ok(out_services)
93    }
94
95    /// Enumerate all services into a reusable output buffer.
96    pub fn list_with_buffer(&self, out_services: &mut Vec<ServiceStatus>) -> Result<usize> {
97        self.list_with_filter(out_services, |_| true)
98    }
99
100    /// Enumerate services into a reusable output buffer, filtered during enumeration.
101    pub fn list_with_filter<F>(
102        &self,
103        out_services: &mut Vec<ServiceStatus>,
104        filter: F,
105    ) -> Result<usize>
106    where
107        F: Fn(&ServiceStatus) -> bool,
108    {
109        out_services.clear();
110
111        let mut work_buffer = Vec::new();
112        let mut bytes_needed = 0u32;
113        let mut services_returned = 0u32;
114
115        let _ = unsafe {
116            EnumServicesStatusExW(
117                self.handle,
118                SC_ENUM_PROCESS_INFO,
119                SERVICE_WIN32,
120                SERVICE_STATE_ALL,
121                None,
122                &mut bytes_needed,
123                &mut services_returned,
124                None,
125                None,
126            )
127        };
128
129        if bytes_needed == 0 {
130            return Ok(0);
131        }
132
133        work_buffer.resize(bytes_needed as usize, 0);
134
135        unsafe {
136            EnumServicesStatusExW(
137                self.handle,
138                SC_ENUM_PROCESS_INFO,
139                SERVICE_WIN32,
140                SERVICE_STATE_ALL,
141                Some(&mut work_buffer),
142                &mut bytes_needed,
143                &mut services_returned,
144                None,
145                None,
146            )
147        }
148        .map_err(|e| {
149            Error::Service(ServiceError::ManagerError(ServiceManagerError::with_code(
150                "enumerate",
151                "EnumServicesStatusExW failed",
152                e.code().0,
153            )))
154        })?;
155
156        let entries = unsafe {
157            std::slice::from_raw_parts(
158                work_buffer.as_ptr() as *const ENUM_SERVICE_STATUS_PROCESSW,
159                services_returned as usize,
160            )
161        };
162
163        for entry in entries {
164            let status = ServiceStatus::from_enum_status(entry);
165            if filter(&status) {
166                out_services.push(status);
167            }
168        }
169
170        Ok(out_services.len())
171    }
172}
173
174impl Drop for ServiceManager {
175    fn drop(&mut self) {
176        let _ = unsafe { CloseServiceHandle(self.handle) };
177    }
178}