use std::borrow::Cow;
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::os::raw::c_void;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::PathBuf;
use std::ptr;
use std::time::Duration;
use std::{io, mem};
use widestring::{NulError, WideCStr, WideCString, WideString};
use winapi::shared::guiddef::{IsEqualGUID, GUID};
use winapi::shared::minwindef::DWORD;
use winapi::shared::winerror::{ERROR_SERVICE_SPECIFIC_ERROR, NO_ERROR};
use winapi::um::winbase::INFINITE;
use winapi::um::{dbt, winnt, winuser};
use crate::sc_handle::ScHandle;
use crate::shell_escape;
use crate::winsvc_vendored as winsvc;
use crate::{double_nul_terminated, Error};
bitflags::bitflags! {
pub struct ServiceType: DWORD {
const FILE_SYSTEM_DRIVER = winnt::SERVICE_FILE_SYSTEM_DRIVER;
const KERNEL_DRIVER = winnt::SERVICE_KERNEL_DRIVER;
const OWN_PROCESS = winnt::SERVICE_WIN32_OWN_PROCESS;
const SHARE_PROCESS = winnt::SERVICE_WIN32_SHARE_PROCESS;
const USER_OWN_PROCESS = winnt::SERVICE_USER_OWN_PROCESS;
const USER_SHARE_PROCESS = winnt::SERVICE_USER_SHARE_PROCESS;
const INTERACTIVE_PROCESS = winnt::SERVICE_INTERACTIVE_PROCESS;
}
}
bitflags::bitflags! {
pub struct ServiceAccess: u32 {
const QUERY_STATUS = winsvc::SERVICE_QUERY_STATUS;
const START = winsvc::SERVICE_START;
const STOP = winsvc::SERVICE_STOP;
const PAUSE_CONTINUE = winsvc::SERVICE_PAUSE_CONTINUE;
const INTERROGATE = winsvc::SERVICE_INTERROGATE;
const DELETE = winnt::DELETE;
const QUERY_CONFIG = winsvc::SERVICE_QUERY_CONFIG;
const CHANGE_CONFIG = winsvc::SERVICE_CHANGE_CONFIG;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceStartType {
AutoStart = winnt::SERVICE_AUTO_START,
OnDemand = winnt::SERVICE_DEMAND_START,
Disabled = winnt::SERVICE_DISABLED,
}
impl ServiceStartType {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<ServiceStartType, ParseRawError> {
match raw {
x if x == ServiceStartType::AutoStart.to_raw() => Ok(ServiceStartType::AutoStart),
x if x == ServiceStartType::OnDemand.to_raw() => Ok(ServiceStartType::OnDemand),
x if x == ServiceStartType::Disabled.to_raw() => Ok(ServiceStartType::Disabled),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceErrorControl {
Critical = winnt::SERVICE_ERROR_CRITICAL,
Ignore = winnt::SERVICE_ERROR_IGNORE,
Normal = winnt::SERVICE_ERROR_NORMAL,
Severe = winnt::SERVICE_ERROR_SEVERE,
}
impl ServiceErrorControl {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<ServiceErrorControl, ParseRawError> {
match raw {
x if x == ServiceErrorControl::Critical.to_raw() => Ok(ServiceErrorControl::Critical),
x if x == ServiceErrorControl::Ignore.to_raw() => Ok(ServiceErrorControl::Ignore),
x if x == ServiceErrorControl::Normal.to_raw() => Ok(ServiceErrorControl::Normal),
x if x == ServiceErrorControl::Severe.to_raw() => Ok(ServiceErrorControl::Severe),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ServiceDependency {
Service(OsString),
Group(OsString),
}
impl ServiceDependency {
pub fn to_system_identifier(&self) -> OsString {
match *self {
ServiceDependency::Service(ref name) => name.to_owned(),
ServiceDependency::Group(ref name) => {
let mut group_identifier = OsString::new();
group_identifier.push("+");
group_identifier.push(name);
group_identifier
}
}
}
pub fn from_system_identifier<S: AsRef<OsStr>>(identifier: S) -> Self {
let group_prefix: u16 = '+' as u16;
let mut iter = identifier.as_ref().encode_wide().peekable();
if iter.peek() == Some(&group_prefix) {
let chars: Vec<u16> = iter.skip(1).collect();
let group_name = OsString::from_wide(&chars);
ServiceDependency::Group(group_name)
} else {
let chars: Vec<u16> = iter.collect();
let service_name = OsString::from_wide(&chars);
ServiceDependency::Service(service_name)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceActionType {
None = winsvc::SC_ACTION_NONE,
Reboot = winsvc::SC_ACTION_REBOOT,
Restart = winsvc::SC_ACTION_RESTART,
RunCommand = winsvc::SC_ACTION_RUN_COMMAND,
}
impl ServiceActionType {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<ServiceActionType, ParseRawError> {
match raw {
x if x == ServiceActionType::None.to_raw() => Ok(ServiceActionType::None),
x if x == ServiceActionType::Reboot.to_raw() => Ok(ServiceActionType::Reboot),
x if x == ServiceActionType::Restart.to_raw() => Ok(ServiceActionType::Restart),
x if x == ServiceActionType::RunCommand.to_raw() => Ok(ServiceActionType::RunCommand),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceAction {
pub action_type: ServiceActionType,
pub delay: Duration,
}
impl ServiceAction {
pub fn from_raw(raw: winsvc::SC_ACTION) -> crate::Result<ServiceAction> {
Ok(ServiceAction {
action_type: ServiceActionType::from_raw(raw.Type)
.map_err(Error::InvalidServiceActionType)?,
delay: Duration::from_millis(raw.Delay as u64),
})
}
pub fn to_raw(&self) -> winsvc::SC_ACTION {
winsvc::SC_ACTION {
Type: self.action_type.to_raw(),
Delay: DWORD::try_from(self.delay.as_millis()).expect("Too long delay"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceFailureResetPeriod {
Never,
After(Duration),
}
impl ServiceFailureResetPeriod {
pub fn from_raw(raw: DWORD) -> ServiceFailureResetPeriod {
match raw {
INFINITE => ServiceFailureResetPeriod::Never,
_ => ServiceFailureResetPeriod::After(Duration::from_secs(raw as u64)),
}
}
pub fn to_raw(&self) -> DWORD {
match self {
ServiceFailureResetPeriod::Never => INFINITE,
ServiceFailureResetPeriod::After(duration) => {
DWORD::try_from(duration.as_secs()).expect("Too long reset period")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceFailureActions {
pub reset_period: ServiceFailureResetPeriod,
pub reboot_msg: Option<OsString>,
pub command: Option<OsString>,
pub actions: Option<Vec<ServiceAction>>,
}
impl ServiceFailureActions {
pub unsafe fn from_raw(
raw: winsvc::SERVICE_FAILURE_ACTIONSW,
) -> crate::Result<ServiceFailureActions> {
let reboot_msg = ptr::NonNull::new(raw.lpRebootMsg)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
let command = ptr::NonNull::new(raw.lpCommand)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
let reset_period = ServiceFailureResetPeriod::from_raw(raw.dwResetPeriod);
let actions: Option<Vec<ServiceAction>> = if raw.lpsaActions.is_null() {
None
} else {
Some(
(0..raw.cActions)
.map(|i| {
let array_element_ptr: *mut winsvc::SC_ACTION =
raw.lpsaActions.offset(i as isize);
ServiceAction::from_raw(*array_element_ptr)
})
.collect::<crate::Result<Vec<ServiceAction>>>()?,
)
};
Ok(ServiceFailureActions {
reset_period,
reboot_msg,
command,
actions,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceInfo {
pub name: OsString,
pub display_name: OsString,
pub service_type: ServiceType,
pub start_type: ServiceStartType,
pub error_control: ServiceErrorControl,
pub executable_path: PathBuf,
pub launch_arguments: Vec<OsString>,
pub dependencies: Vec<ServiceDependency>,
pub account_name: Option<OsString>,
pub account_password: Option<OsString>,
}
pub(crate) struct RawServiceInfo {
pub name: WideCString,
pub display_name: WideCString,
pub service_type: DWORD,
pub start_type: DWORD,
pub error_control: DWORD,
pub launch_command: WideCString,
pub dependencies: Option<WideString>,
pub account_name: Option<WideCString>,
pub account_password: Option<WideCString>,
}
impl RawServiceInfo {
pub fn new(service_info: &ServiceInfo) -> crate::Result<Self> {
let service_name =
WideCString::from_os_str(&service_info.name).map_err(Error::InvalidServiceName)?;
let display_name = WideCString::from_os_str(&service_info.display_name)
.map_err(Error::InvalidDisplayName)?;
let account_name =
to_wide(service_info.account_name.as_ref()).map_err(Error::InvalidAccountName)?;
let account_password = to_wide(service_info.account_password.as_ref())
.map_err(Error::InvalidAccountPassword)?;
let executable_path =
escape_wide(&service_info.executable_path).map_err(Error::InvalidExecutablePath)?;
let mut launch_command_buffer = WideString::new();
launch_command_buffer.push(executable_path);
for (i, launch_argument) in service_info.launch_arguments.iter().enumerate() {
let wide =
escape_wide(launch_argument).map_err(|e| Error::InvalidLaunchArgument(i, e))?;
launch_command_buffer.push_str(" ");
launch_command_buffer.push(wide);
}
let launch_command = unsafe { WideCString::from_ustr_unchecked(launch_command_buffer) };
let dependency_identifiers: Vec<OsString> = service_info
.dependencies
.iter()
.map(|dependency| dependency.to_system_identifier())
.collect();
let joined_dependencies = double_nul_terminated::from_vec(&dependency_identifiers)
.map_err(Error::InvalidDependency)?;
Ok(Self {
name: service_name,
display_name: display_name,
service_type: service_info.service_type.bits(),
start_type: service_info.start_type.to_raw(),
error_control: service_info.error_control.to_raw(),
launch_command: launch_command,
dependencies: joined_dependencies,
account_name: account_name,
account_password: account_password,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceConfig {
pub service_type: ServiceType,
pub start_type: ServiceStartType,
pub error_control: ServiceErrorControl,
pub executable_path: PathBuf,
pub load_order_group: Option<OsString>,
pub tag_id: u32,
pub dependencies: Vec<ServiceDependency>,
pub account_name: Option<OsString>,
pub display_name: OsString,
}
impl ServiceConfig {
pub unsafe fn from_raw(raw: winsvc::QUERY_SERVICE_CONFIGW) -> crate::Result<ServiceConfig> {
let dependencies = double_nul_terminated::parse_str_ptr(raw.lpDependencies)
.iter()
.map(ServiceDependency::from_system_identifier)
.collect();
let load_order_group = ptr::NonNull::new(raw.lpLoadOrderGroup).and_then(|wrapped_ptr| {
let group = WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string();
if group.is_empty() {
None
} else {
Some(group)
}
});
let account_name = ptr::NonNull::new(raw.lpServiceStartName)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
Ok(ServiceConfig {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
start_type: ServiceStartType::from_raw(raw.dwStartType)
.map_err(Error::InvalidServiceStartType)?,
error_control: ServiceErrorControl::from_raw(raw.dwErrorControl)
.map_err(Error::InvalidServiceErrorControl)?,
executable_path: PathBuf::from(
WideCStr::from_ptr_str(raw.lpBinaryPathName).to_os_string(),
),
load_order_group,
tag_id: raw.dwTagId,
dependencies,
account_name,
display_name: WideCStr::from_ptr_str(raw.lpDisplayName).to_os_string(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum HardwareProfileChangeParam {
ConfigChanged = dbt::DBT_CONFIGCHANGED as u32,
QueryChangeConfig = dbt::DBT_QUERYCHANGECONFIG as u32,
ConfigChangeCanceled = dbt::DBT_CONFIGCHANGECANCELED as u32,
}
impl HardwareProfileChangeParam {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<Self, ParseRawError> {
match raw {
x if x == HardwareProfileChangeParam::ConfigChanged.to_raw() => {
Ok(HardwareProfileChangeParam::ConfigChanged)
}
x if x == HardwareProfileChangeParam::QueryChangeConfig.to_raw() => {
Ok(HardwareProfileChangeParam::QueryChangeConfig)
}
x if x == HardwareProfileChangeParam::ConfigChangeCanceled.to_raw() => {
Ok(HardwareProfileChangeParam::ConfigChangeCanceled)
}
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum PowerSource {
Ac = winnt::PoAc,
Dc = winnt::PoDc,
Hot = winnt::PoHot,
}
impl PowerSource {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<PowerSource, ParseRawError> {
match raw {
x if x == PowerSource::Ac.to_raw() => Ok(PowerSource::Ac),
x if x == PowerSource::Dc.to_raw() => Ok(PowerSource::Dc),
x if x == PowerSource::Hot.to_raw() => Ok(PowerSource::Hot),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum DisplayState {
Off = winnt::PowerMonitorOff,
On = winnt::PowerMonitorOn,
Dimmed = winnt::PowerMonitorDim,
}
impl DisplayState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<DisplayState, ParseRawError> {
match raw {
x if x == DisplayState::Off.to_raw() => Ok(DisplayState::Off),
x if x == DisplayState::On.to_raw() => Ok(DisplayState::On),
x if x == DisplayState::Dimmed.to_raw() => Ok(DisplayState::Dimmed),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum UserStatus {
Present = winnt::PowerUserPresent,
Inactive = winnt::PowerUserInactive,
}
impl UserStatus {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<UserStatus, ParseRawError> {
match raw {
x if x == UserStatus::Present.to_raw() => Ok(UserStatus::Present),
x if x == UserStatus::Inactive.to_raw() => Ok(UserStatus::Inactive),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum MonitorState {
Off = 0,
On = 1,
}
impl MonitorState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<MonitorState, ParseRawError> {
match raw {
x if x == MonitorState::Off.to_raw() => Ok(MonitorState::Off),
x if x == MonitorState::On.to_raw() => Ok(MonitorState::On),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum BatterySaverState {
Off = 0,
On = 1,
}
impl BatterySaverState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<BatterySaverState, ParseRawError> {
match raw {
x if x == BatterySaverState::Off.to_raw() => Ok(BatterySaverState::Off),
x if x == BatterySaverState::On.to_raw() => Ok(BatterySaverState::On),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PowerSchemePersonality {
HighPerformance,
PowerSaver,
Automatic,
}
impl PowerSchemePersonality {
pub fn from_guid(guid: &GUID) -> Result<PowerSchemePersonality, ParseRawError> {
match guid {
x if IsEqualGUID(x, &winnt::GUID_MIN_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::HighPerformance)
}
x if IsEqualGUID(x, &winnt::GUID_MAX_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::PowerSaver)
}
x if IsEqualGUID(x, &winnt::GUID_TYPICAL_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::Automatic)
}
x => Err(ParseRawError::InvalidGuid(string_from_guid(x))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum AwayModeState {
Exiting = 0,
Entering = 1,
}
impl AwayModeState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<AwayModeState, ParseRawError> {
match raw {
x if x == AwayModeState::Exiting.to_raw() => Ok(AwayModeState::Exiting),
x if x == AwayModeState::Entering.to_raw() => Ok(AwayModeState::Entering),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PowerBroadcastSetting {
AcdcPowerSource(PowerSource),
BatteryPercentageRemaining(u32),
ConsoleDisplayState(DisplayState),
GlobalUserPresence(UserStatus),
IdleBackgroundTask,
MonitorPowerOn(MonitorState),
PowerSavingStatus(BatterySaverState),
PowerSchemePersonality(PowerSchemePersonality),
SystemAwayMode(AwayModeState),
}
impl PowerBroadcastSetting {
pub unsafe fn from_raw(raw: *mut c_void) -> Result<PowerBroadcastSetting, ParseRawError> {
let setting = &*(raw as *const winuser::POWERBROADCAST_SETTING);
let data = &setting.Data as *const u8;
match &setting.PowerSetting {
x if IsEqualGUID(x, &winnt::GUID_ACDC_POWER_SOURCE) => {
let power_source = *(data as *const u32);
Ok(PowerBroadcastSetting::AcdcPowerSource(
PowerSource::from_raw(power_source)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_BATTERY_PERCENTAGE_REMAINING) => {
let percentage = *(data as *const u32);
Ok(PowerBroadcastSetting::BatteryPercentageRemaining(
percentage,
))
}
x if IsEqualGUID(x, &winnt::GUID_CONSOLE_DISPLAY_STATE) => {
let display_state = *(data as *const u32);
Ok(PowerBroadcastSetting::ConsoleDisplayState(
DisplayState::from_raw(display_state)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_GLOBAL_USER_PRESENCE) => {
let user_status = *(data as *const u32);
Ok(PowerBroadcastSetting::GlobalUserPresence(
UserStatus::from_raw(user_status)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_IDLE_BACKGROUND_TASK) => {
Ok(PowerBroadcastSetting::IdleBackgroundTask)
}
x if IsEqualGUID(x, &winnt::GUID_MONITOR_POWER_ON) => {
let monitor_state = *(data as *const u32);
Ok(PowerBroadcastSetting::MonitorPowerOn(
MonitorState::from_raw(monitor_state)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_POWER_SAVING_STATUS) => {
let battery_saver_state = *(data as *const u32);
Ok(PowerBroadcastSetting::PowerSavingStatus(
BatterySaverState::from_raw(battery_saver_state)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_POWERSCHEME_PERSONALITY) => {
let guid = *(data as *const GUID);
Ok(PowerBroadcastSetting::PowerSchemePersonality(
PowerSchemePersonality::from_guid(&guid)?,
))
}
x if IsEqualGUID(x, &winnt::GUID_SYSTEM_AWAYMODE) => {
let away_mode_state = *(data as *const u32);
Ok(PowerBroadcastSetting::SystemAwayMode(
AwayModeState::from_raw(away_mode_state)?,
))
}
x => Err(ParseRawError::InvalidGuid(string_from_guid(x))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PowerEventParam {
PowerStatusChange,
ResumeAutomatic,
ResumeSuspend,
Suspend,
PowerSettingChange(PowerBroadcastSetting),
BatteryLow,
OemEvent,
QuerySuspend,
QuerySuspendFailed,
ResumeCritical,
}
impl PowerEventParam {
pub unsafe fn from_event(
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
match event_type as usize {
winuser::PBT_APMPOWERSTATUSCHANGE => Ok(PowerEventParam::PowerStatusChange),
winuser::PBT_APMRESUMEAUTOMATIC => Ok(PowerEventParam::ResumeAutomatic),
winuser::PBT_APMRESUMESUSPEND => Ok(PowerEventParam::ResumeSuspend),
winuser::PBT_APMSUSPEND => Ok(PowerEventParam::Suspend),
winuser::PBT_POWERSETTINGCHANGE => Ok(PowerEventParam::PowerSettingChange(
PowerBroadcastSetting::from_raw(event_data)?,
)),
winuser::PBT_APMBATTERYLOW => Ok(PowerEventParam::BatteryLow),
winuser::PBT_APMOEMEVENT => Ok(PowerEventParam::OemEvent),
winuser::PBT_APMQUERYSUSPEND => Ok(PowerEventParam::QuerySuspend),
winuser::PBT_APMQUERYSUSPENDFAILED => Ok(PowerEventParam::QuerySuspendFailed),
winuser::PBT_APMRESUMECRITICAL => Ok(PowerEventParam::ResumeCritical),
_ => Err(ParseRawError::InvalidInteger(event_type)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum SessionChangeReason {
ConsoleConnect = winuser::WTS_CONSOLE_CONNECT as u32,
ConsoleDisconnect = winuser::WTS_CONSOLE_DISCONNECT as u32,
RemoteConnect = winuser::WTS_REMOTE_CONNECT as u32,
RemoteDisconnect = winuser::WTS_REMOTE_DISCONNECT as u32,
SessionLogon = winuser::WTS_SESSION_LOGON as u32,
SessionLogoff = winuser::WTS_SESSION_LOGOFF as u32,
SessionLock = winuser::WTS_SESSION_LOCK as u32,
SessionUnlock = winuser::WTS_SESSION_UNLOCK as u32,
SessionRemoteControl = winuser::WTS_SESSION_REMOTE_CONTROL as u32,
SessionCreate = winuser::WTS_SESSION_CREATE as u32,
SessionTerminate = winuser::WTS_SESSION_TERMINATE as u32,
}
impl SessionChangeReason {
pub fn from_raw(raw: u32) -> Result<SessionChangeReason, ParseRawError> {
match raw {
x if x == SessionChangeReason::ConsoleConnect.to_raw() => {
Ok(SessionChangeReason::ConsoleConnect)
}
x if x == SessionChangeReason::ConsoleDisconnect.to_raw() => {
Ok(SessionChangeReason::ConsoleDisconnect)
}
x if x == SessionChangeReason::RemoteConnect.to_raw() => {
Ok(SessionChangeReason::RemoteConnect)
}
x if x == SessionChangeReason::RemoteDisconnect.to_raw() => {
Ok(SessionChangeReason::RemoteDisconnect)
}
x if x == SessionChangeReason::SessionLogon.to_raw() => {
Ok(SessionChangeReason::SessionLogon)
}
x if x == SessionChangeReason::SessionLogoff.to_raw() => {
Ok(SessionChangeReason::SessionLogoff)
}
x if x == SessionChangeReason::SessionLock.to_raw() => {
Ok(SessionChangeReason::SessionLock)
}
x if x == SessionChangeReason::SessionUnlock.to_raw() => {
Ok(SessionChangeReason::SessionUnlock)
}
x if x == SessionChangeReason::SessionRemoteControl.to_raw() => {
Ok(SessionChangeReason::SessionRemoteControl)
}
x if x == SessionChangeReason::SessionCreate.to_raw() => {
Ok(SessionChangeReason::SessionCreate)
}
x if x == SessionChangeReason::SessionTerminate.to_raw() => {
Ok(SessionChangeReason::SessionTerminate)
}
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
pub fn to_raw(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionNotification {
pub size: u32,
pub session_id: u32,
}
impl SessionNotification {
pub fn from_raw(raw: winuser::WTSSESSION_NOTIFICATION) -> Self {
SessionNotification {
size: raw.cbSize,
session_id: raw.dwSessionId,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionChangeParam {
pub reason: SessionChangeReason,
pub notification: SessionNotification,
}
impl SessionChangeParam {
pub unsafe fn from_event(
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
let notification = *(event_data as *const winuser::WTSSESSION_NOTIFICATION);
Ok(SessionChangeParam {
reason: SessionChangeReason::from_raw(event_type)?,
notification: SessionNotification::from_raw(notification),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceControl {
Continue,
Interrogate,
NetBindAdd,
NetBindDisable,
NetBindEnable,
NetBindRemove,
ParamChange,
Pause,
Preshutdown,
Shutdown,
Stop,
HardwareProfileChange(HardwareProfileChangeParam),
PowerEvent(PowerEventParam),
SessionChange(SessionChangeParam),
TimeChange,
TriggerEvent,
}
impl ServiceControl {
pub unsafe fn from_raw(
raw: u32,
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
match raw {
winsvc::SERVICE_CONTROL_CONTINUE => Ok(ServiceControl::Continue),
winsvc::SERVICE_CONTROL_INTERROGATE => Ok(ServiceControl::Interrogate),
winsvc::SERVICE_CONTROL_NETBINDADD => Ok(ServiceControl::NetBindAdd),
winsvc::SERVICE_CONTROL_NETBINDDISABLE => Ok(ServiceControl::NetBindDisable),
winsvc::SERVICE_CONTROL_NETBINDENABLE => Ok(ServiceControl::NetBindEnable),
winsvc::SERVICE_CONTROL_NETBINDREMOVE => Ok(ServiceControl::NetBindRemove),
winsvc::SERVICE_CONTROL_PARAMCHANGE => Ok(ServiceControl::ParamChange),
winsvc::SERVICE_CONTROL_PAUSE => Ok(ServiceControl::Pause),
winsvc::SERVICE_CONTROL_PRESHUTDOWN => Ok(ServiceControl::Preshutdown),
winsvc::SERVICE_CONTROL_SHUTDOWN => Ok(ServiceControl::Shutdown),
winsvc::SERVICE_CONTROL_STOP => Ok(ServiceControl::Stop),
winsvc::SERVICE_CONTROL_HARDWAREPROFILECHANGE => {
HardwareProfileChangeParam::from_raw(event_type)
.map(ServiceControl::HardwareProfileChange)
}
winsvc::SERVICE_CONTROL_POWEREVENT => {
PowerEventParam::from_event(event_type, event_data).map(ServiceControl::PowerEvent)
}
winsvc::SERVICE_CONTROL_SESSIONCHANGE => {
SessionChangeParam::from_event(event_type, event_data)
.map(ServiceControl::SessionChange)
}
winsvc::SERVICE_CONTROL_TIMECHANGE => Ok(ServiceControl::TimeChange),
winsvc::SERVICE_CONTROL_TRIGGEREVENT => Ok(ServiceControl::TriggerEvent),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
pub fn raw_service_control_type(&self) -> u32 {
match self {
ServiceControl::Continue => winsvc::SERVICE_CONTROL_CONTINUE,
ServiceControl::Interrogate => winsvc::SERVICE_CONTROL_INTERROGATE,
ServiceControl::NetBindAdd => winsvc::SERVICE_CONTROL_NETBINDADD,
ServiceControl::NetBindDisable => winsvc::SERVICE_CONTROL_NETBINDDISABLE,
ServiceControl::NetBindEnable => winsvc::SERVICE_CONTROL_NETBINDENABLE,
ServiceControl::NetBindRemove => winsvc::SERVICE_CONTROL_NETBINDREMOVE,
ServiceControl::ParamChange => winsvc::SERVICE_CONTROL_PARAMCHANGE,
ServiceControl::Pause => winsvc::SERVICE_CONTROL_PAUSE,
ServiceControl::Preshutdown => winsvc::SERVICE_CONTROL_PRESHUTDOWN,
ServiceControl::Shutdown => winsvc::SERVICE_CONTROL_SHUTDOWN,
ServiceControl::Stop => winsvc::SERVICE_CONTROL_STOP,
ServiceControl::HardwareProfileChange(_) => {
winsvc::SERVICE_CONTROL_HARDWAREPROFILECHANGE
}
ServiceControl::PowerEvent(_) => winsvc::SERVICE_CONTROL_POWEREVENT,
ServiceControl::SessionChange(_) => winsvc::SERVICE_CONTROL_SESSIONCHANGE,
ServiceControl::TimeChange => winsvc::SERVICE_CONTROL_TIMECHANGE,
ServiceControl::TriggerEvent => winsvc::SERVICE_CONTROL_TRIGGEREVENT,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceState {
Stopped = winsvc::SERVICE_STOPPED,
StartPending = winsvc::SERVICE_START_PENDING,
StopPending = winsvc::SERVICE_STOP_PENDING,
Running = winsvc::SERVICE_RUNNING,
ContinuePending = winsvc::SERVICE_CONTINUE_PENDING,
PausePending = winsvc::SERVICE_PAUSE_PENDING,
Paused = winsvc::SERVICE_PAUSED,
}
impl ServiceState {
fn from_raw(raw: u32) -> Result<Self, ParseRawError> {
match raw {
x if x == ServiceState::Stopped.to_raw() => Ok(ServiceState::Stopped),
x if x == ServiceState::StartPending.to_raw() => Ok(ServiceState::StartPending),
x if x == ServiceState::StopPending.to_raw() => Ok(ServiceState::StopPending),
x if x == ServiceState::Running.to_raw() => Ok(ServiceState::Running),
x if x == ServiceState::ContinuePending.to_raw() => Ok(ServiceState::ContinuePending),
x if x == ServiceState::PausePending.to_raw() => Ok(ServiceState::PausePending),
x if x == ServiceState::Paused.to_raw() => Ok(ServiceState::Paused),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
fn to_raw(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceExitCode {
Win32(u32),
ServiceSpecific(u32),
}
impl ServiceExitCode {
pub const NO_ERROR: Self = ServiceExitCode::Win32(NO_ERROR);
fn copy_to(&self, raw_service_status: &mut winsvc::SERVICE_STATUS) {
match *self {
ServiceExitCode::Win32(win32_error_code) => {
raw_service_status.dwWin32ExitCode = win32_error_code;
raw_service_status.dwServiceSpecificExitCode = 0;
}
ServiceExitCode::ServiceSpecific(service_error_code) => {
raw_service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
raw_service_status.dwServiceSpecificExitCode = service_error_code;
}
}
}
}
impl Default for ServiceExitCode {
fn default() -> Self {
Self::NO_ERROR
}
}
impl<'a> From<&'a winsvc::SERVICE_STATUS> for ServiceExitCode {
fn from(service_status: &'a winsvc::SERVICE_STATUS) -> Self {
if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR {
ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode)
} else {
ServiceExitCode::Win32(service_status.dwWin32ExitCode)
}
}
}
impl<'a> From<&'a winsvc::SERVICE_STATUS_PROCESS> for ServiceExitCode {
fn from(service_status: &'a winsvc::SERVICE_STATUS_PROCESS) -> Self {
if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR {
ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode)
} else {
ServiceExitCode::Win32(service_status.dwWin32ExitCode)
}
}
}
bitflags::bitflags! {
pub struct ServiceControlAccept: u32 {
const NETBIND_CHANGE = winsvc::SERVICE_ACCEPT_NETBINDCHANGE;
const PARAM_CHANGE = winsvc::SERVICE_ACCEPT_PARAMCHANGE;
const PAUSE_CONTINUE = winsvc::SERVICE_ACCEPT_PAUSE_CONTINUE;
const PRESHUTDOWN = winsvc::SERVICE_ACCEPT_PRESHUTDOWN;
const SHUTDOWN = winsvc::SERVICE_ACCEPT_SHUTDOWN;
const STOP = winsvc::SERVICE_ACCEPT_STOP;
const HARDWARE_PROFILE_CHANGE = winsvc::SERVICE_ACCEPT_HARDWAREPROFILECHANGE;
const POWER_EVENT = winsvc::SERVICE_ACCEPT_POWEREVENT;
const SESSION_CHANGE = winsvc::SERVICE_ACCEPT_SESSIONCHANGE;
const TIME_CHANGE = winsvc::SERVICE_ACCEPT_TIMECHANGE;
const TRIGGER_EVENT = winsvc::SERVICE_ACCEPT_TRIGGEREVENT;
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceStatus {
pub service_type: ServiceType,
pub current_state: ServiceState,
pub controls_accepted: ServiceControlAccept,
pub exit_code: ServiceExitCode,
pub checkpoint: u32,
pub wait_hint: Duration,
pub process_id: Option<u32>,
}
impl ServiceStatus {
pub(crate) fn to_raw(&self) -> winsvc::SERVICE_STATUS {
let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() };
raw_status.dwServiceType = self.service_type.bits();
raw_status.dwCurrentState = self.current_state.to_raw();
raw_status.dwControlsAccepted = self.controls_accepted.bits();
self.exit_code.copy_to(&mut raw_status);
raw_status.dwCheckPoint = self.checkpoint;
raw_status.dwWaitHint =
DWORD::try_from(self.wait_hint.as_millis()).expect("Too long wait_hint");
raw_status
}
fn from_raw(raw: winsvc::SERVICE_STATUS) -> Result<Self, ParseRawError> {
Ok(ServiceStatus {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
current_state: ServiceState::from_raw(raw.dwCurrentState)?,
controls_accepted: ServiceControlAccept::from_bits_truncate(raw.dwControlsAccepted),
exit_code: ServiceExitCode::from(&raw),
checkpoint: raw.dwCheckPoint,
wait_hint: Duration::from_millis(raw.dwWaitHint as u64),
process_id: None,
})
}
fn from_raw_ex(raw: winsvc::SERVICE_STATUS_PROCESS) -> Result<Self, ParseRawError> {
let current_state = ServiceState::from_raw(raw.dwCurrentState)?;
let process_id = match current_state {
ServiceState::Running => Some(raw.dwProcessId),
_ => None,
};
Ok(ServiceStatus {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
current_state,
controls_accepted: ServiceControlAccept::from_bits_truncate(raw.dwControlsAccepted),
exit_code: ServiceExitCode::from(&raw),
checkpoint: raw.dwCheckPoint,
wait_hint: Duration::from_millis(raw.dwWaitHint as u64),
process_id,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceSidType {
None = 0,
Restricted = 3,
Unrestricted = 1,
}
pub struct Service {
service_handle: ScHandle,
}
impl Service {
pub(crate) fn new(service_handle: ScHandle) -> Self {
Service { service_handle }
}
pub fn start(&self, service_arguments: &[impl AsRef<OsStr>]) -> crate::Result<()> {
let wide_service_arguments = service_arguments
.iter()
.map(|s| WideCString::from_os_str(s).map_err(Error::InvalidStartArgument))
.collect::<crate::Result<Vec<WideCString>>>()?;
let mut raw_service_arguments: Vec<*const u16> =
wide_service_arguments.iter().map(|s| s.as_ptr()).collect();
let success = unsafe {
winsvc::StartServiceW(
self.service_handle.raw_handle(),
raw_service_arguments.len() as u32,
raw_service_arguments.as_mut_ptr(),
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn stop(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Stop)
}
pub fn pause(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Pause)
}
pub fn resume(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Continue)
}
pub fn query_status(&self) -> crate::Result<ServiceStatus> {
let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS_PROCESS>() };
let mut bytes_needed: DWORD = 0;
let success = unsafe {
winsvc::QueryServiceStatusEx(
self.service_handle.raw_handle(),
winsvc::SC_STATUS_PROCESS_INFO,
&mut raw_status as *mut _ as _,
std::mem::size_of::<winsvc::SERVICE_STATUS_PROCESS>() as u32,
&mut bytes_needed,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
ServiceStatus::from_raw_ex(raw_status).map_err(Error::InvalidServiceState)
}
}
pub fn delete(self) -> crate::Result<()> {
let success = unsafe { winsvc::DeleteService(self.service_handle.raw_handle()) };
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn query_config(&self) -> crate::Result<ServiceConfig> {
let mut data = [0u8; MAX_QUERY_BUFFER_SIZE];
let mut bytes_written: u32 = 0;
let success = unsafe {
winsvc::QueryServiceConfigW(
self.service_handle.raw_handle(),
data.as_mut_ptr() as _,
data.len() as u32,
&mut bytes_written,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
unsafe {
let raw_config = data.as_ptr() as *const winsvc::QUERY_SERVICE_CONFIGW;
ServiceConfig::from_raw(*raw_config)
}
}
}
pub fn change_config(&self, service_info: &ServiceInfo) -> crate::Result<()> {
let raw_info = RawServiceInfo::new(service_info)?;
let success = unsafe {
winsvc::ChangeServiceConfigW(
self.service_handle.raw_handle(),
raw_info.service_type,
raw_info.start_type,
raw_info.error_control,
raw_info.launch_command.as_ptr(),
ptr::null(),
ptr::null_mut(),
raw_info
.dependencies
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info
.account_name
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info
.account_password
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info.display_name.as_ptr(),
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn set_failure_actions_on_non_crash_failures(&self, enabled: bool) -> crate::Result<()> {
let mut raw_failure_actions_flag =
unsafe { mem::zeroed::<winsvc::SERVICE_FAILURE_ACTIONS_FLAG>() };
raw_failure_actions_flag.fFailureActionsOnNonCrashFailures = if enabled { 1 } else { 0 };
unsafe {
self.change_config2(
winsvc::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
&mut raw_failure_actions_flag,
)
.map_err(Error::Winapi)
}
}
pub fn get_failure_actions_on_non_crash_failures(&self) -> crate::Result<bool> {
let mut data = [0u8; MAX_QUERY_BUFFER_SIZE];
let raw_failure_actions_flag: winsvc::SERVICE_FAILURE_ACTIONS_FLAG = unsafe {
self.query_config2(winsvc::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &mut data)
.map_err(Error::Winapi)?
};
let result = if raw_failure_actions_flag.fFailureActionsOnNonCrashFailures == 0 {
false
} else {
true
};
Ok(result)
}
pub fn set_config_service_sid_info(
&self,
mut service_sid_type: ServiceSidType,
) -> crate::Result<()> {
unsafe {
self.change_config2(
winsvc::SERVICE_CONFIG_SERVICE_SID_INFO,
&mut service_sid_type,
)
.map_err(Error::Winapi)
}
}
pub fn get_failure_actions(&self) -> crate::Result<ServiceFailureActions> {
unsafe {
let mut data = [0u8; MAX_QUERY_BUFFER_SIZE];
let raw_failure_actions: winsvc::SERVICE_FAILURE_ACTIONSW = self
.query_config2(winsvc::SERVICE_CONFIG_FAILURE_ACTIONS, &mut data)
.map_err(Error::Winapi)?;
ServiceFailureActions::from_raw(raw_failure_actions)
}
}
pub fn update_failure_actions(&self, update: ServiceFailureActions) -> crate::Result<()> {
let mut raw_failure_actions = unsafe { mem::zeroed::<winsvc::SERVICE_FAILURE_ACTIONSW>() };
let mut reboot_msg = to_wide_slice(update.reboot_msg)
.map_err(Error::InvalidServiceActionFailuresRebootMessage)?;
let mut command =
to_wide_slice(update.command).map_err(Error::InvalidServiceActionFailuresCommand)?;
let mut sc_actions: Option<Vec<winsvc::SC_ACTION>> = update
.actions
.map(|actions| actions.iter().map(ServiceAction::to_raw).collect());
raw_failure_actions.dwResetPeriod = update.reset_period.to_raw();
raw_failure_actions.lpRebootMsg = reboot_msg
.as_mut()
.map_or(ptr::null_mut(), |s| s.as_mut_ptr());
raw_failure_actions.lpCommand =
command.as_mut().map_or(ptr::null_mut(), |s| s.as_mut_ptr());
raw_failure_actions.cActions = sc_actions.as_ref().map_or(0, |v| v.len()) as u32;
raw_failure_actions.lpsaActions = sc_actions
.as_mut()
.map_or(ptr::null_mut(), |actions| actions.as_mut_ptr());
unsafe {
self.change_config2(
winsvc::SERVICE_CONFIG_FAILURE_ACTIONS,
&mut raw_failure_actions,
)
.map_err(Error::Winapi)
}
}
fn send_control_command(&self, command: ServiceControl) -> crate::Result<ServiceStatus> {
let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() };
let success = unsafe {
winsvc::ControlService(
self.service_handle.raw_handle(),
command.raw_service_control_type(),
&mut raw_status,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
ServiceStatus::from_raw(raw_status).map_err(Error::InvalidServiceState)
}
}
unsafe fn query_config2<T: Copy>(
&self,
kind: DWORD,
data: &mut [u8; MAX_QUERY_BUFFER_SIZE],
) -> io::Result<T> {
let mut bytes_written: u32 = 0;
let success = winsvc::QueryServiceConfig2W(
self.service_handle.raw_handle(),
kind,
data.as_mut_ptr() as _,
data.len() as u32,
&mut bytes_written,
);
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(*(data.as_ptr() as *const _))
}
}
unsafe fn change_config2<T>(&self, kind: DWORD, data: &mut T) -> io::Result<()> {
let success = winsvc::ChangeServiceConfig2W(
self.service_handle.raw_handle(),
kind,
data as *mut _ as *mut _,
);
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
const MAX_QUERY_BUFFER_SIZE: usize = 8 * 1024;
fn to_wide_slice(
s: Option<impl AsRef<OsStr>>,
) -> ::std::result::Result<Option<Vec<u16>>, NulError<u16>> {
if let Some(s) = s {
Ok(Some(
WideCString::from_os_str(s).map(|s| s.as_slice_with_nul().to_vec())?,
))
} else {
Ok(None)
}
}
#[derive(err_derive::Error, Debug)]
pub enum ParseRawError {
#[error(display = "Invalid integer value for the target type: {}", _0)]
InvalidInteger(u32),
#[error(display = "Invalid GUID value for the target type: {}", _0)]
InvalidGuid(String),
}
fn string_from_guid(guid: &GUID) -> String {
format!(
"{:8X}-{:4X}-{:4X}-{:2X}{:2X}-{:2X}{:2X}{:2X}{:2X}{:2X}{:2X}",
guid.Data1,
guid.Data2,
guid.Data3,
guid.Data4[0],
guid.Data4[1],
guid.Data4[2],
guid.Data4[3],
guid.Data4[4],
guid.Data4[5],
guid.Data4[6],
guid.Data4[7]
)
}
pub(crate) fn to_wide(
s: Option<impl AsRef<OsStr>>,
) -> ::std::result::Result<Option<WideCString>, NulError<u16>> {
if let Some(s) = s {
Ok(Some(WideCString::from_os_str(s)?))
} else {
Ok(None)
}
}
fn escape_wide(s: impl AsRef<OsStr>) -> ::std::result::Result<WideString, NulError<u16>> {
let escaped = shell_escape::escape(Cow::Borrowed(s.as_ref()));
let wide = WideCString::from_os_str(&escaped)?;
Ok(wide.to_ustring())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_group_identifier() {
let dependency = ServiceDependency::from_system_identifier("+network");
assert_eq!(
dependency,
ServiceDependency::Group(OsString::from("network"))
);
}
#[test]
fn test_service_name_identifier() {
let dependency = ServiceDependency::from_system_identifier("netlogon");
assert_eq!(
dependency,
ServiceDependency::Service(OsString::from("netlogon"))
);
}
}