use std::{cell::RefCell, ffi::OsString, thread, time::Duration};
use windows_service::{
service::{
ServiceAccess, ServiceDependency, ServiceErrorControl, ServiceInfo,
ServiceStartType, ServiceState, ServiceType
},
service_manager::{ServiceManager, ServiceManagerAccess}
};
use crate::rt::winsvc::{
create_service_params, get_service_params_subkey, write_service_subkey
};
use super::Error;
pub fn install(ctx: super::RegSvc) -> Result<(), Error> {
let svcname = &ctx.svcname;
let status = RefCell::new(false);
fltevtlog::register(svcname)?;
let _status = scopeguard::guard(&status, |st| {
if !*st.borrow() && fltevtlog::deregister(svcname).is_err() {
eprintln!("!!> Unable to deregister event source");
}
});
let manager_access =
ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
let service_manager =
ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_binary_path = ::std::env::current_exe()?;
let display_name = ctx
.display_name
.as_ref()
.map_or(svcname, |display_name| display_name);
let launch_args: Vec<OsString> =
ctx.args.iter().map(OsString::from).collect();
let autostart = if ctx.autostart {
ServiceStartType::AutoStart
} else {
ServiceStartType::OnDemand
};
let mut deps = vec![];
for dep in &ctx.deps {
match dep {
super::Depend::Network => {
deps.push(OsString::from("Tcpip"));
}
super::Depend::Custom(lst) => {
for d in lst {
deps.push(OsString::from(d));
}
}
}
}
let dependencies: Vec<ServiceDependency> =
deps.into_iter().map(ServiceDependency::Service).collect();
let account_name = ctx.runas.user.clone().map(OsString::from);
let service_info = ServiceInfo {
name: OsString::from(svcname),
display_name: OsString::from(display_name),
service_type: ServiceType::OWN_PROCESS,
start_type: autostart,
error_control: ServiceErrorControl::Normal,
executable_path: service_binary_path,
launch_arguments: launch_args,
dependencies,
account_name,
account_password: None
};
let service = service_manager
.create_service(&service_info, ServiceAccess::CHANGE_CONFIG)?;
let _status = scopeguard::guard(&status, |st| {
if !(*st.borrow()) {
let service_access = ServiceAccess::DELETE;
let res = service_manager.open_service(svcname, service_access);
if let Ok(service) = res {
if service.delete().is_err() {
eprintln!("!!> Unable to delete service");
}
} else {
eprintln!("!!> Unable to open registered service");
}
}
});
if let Some(ref desc) = ctx.description {
service.set_description(desc)?;
}
if ctx.have_envs() {
let key = write_service_subkey(svcname)?;
let envs: Vec<String> =
ctx.envs.iter().map(|(k, v)| format!("{k}={v}")).collect();
let elst: Vec<&str> = envs.iter().map(String::as_str).collect();
key.set_multi_string("Environment", &elst)?;
}
let mut params = create_service_params(svcname)?;
params.set_string("Installer", "qsu")?;
if let Some(wd) = ctx.workdir {
params.set_string("WorkDir", &wd)?;
}
if let Some(ll) = ctx.log_level {
params.set_string("LogLevel", ll.to_string())?;
}
if let Some(lf) = ctx.log_filter {
params.set_string("LogFilter", &lf)?;
}
if let Some(ll) = ctx.trace_filter {
params.set_string("TraceFilter", &ll)?;
}
if let Some(fname) = ctx.trace_file {
params.set_string("TraceFile", &fname)?;
}
if let Some(cb) = ctx.regconf {
cb(svcname, &mut params)?;
}
*status.borrow_mut() = true;
Ok(())
}
pub fn uninstall(svcname: &str) -> Result<(), Error> {
if let Ok(params) = get_service_params_subkey(svcname) {
if let Ok(val) = params.get_string("Installer") {
if val != "qsu" {
Err(Error::missing(
"Refusing to uninstall service that doesn't appear to be installed \
by qsu"
))?;
}
} else {
Err(Error::missing(
"Service Parameters does not have a Installer key."
))?;
}
} else {
Err(Error::missing("Service does not have a Parameters subkey."))?;
}
let manager_access = ServiceManagerAccess::CONNECT;
let service_manager =
ServiceManager::local_computer(None::<&str>, manager_access)?;
let service_access =
ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE;
let service = service_manager.open_service(svcname, service_access)?;
loop {
let service_status = service.query_status()?;
if service_status.current_state == ServiceState::Stopped {
break;
}
println!("==> Requesting service '{svcname}' to stop ..");
service.stop()?;
thread::sleep(Duration::from_secs(2));
}
service.delete()?;
fltevtlog::deregister(svcname)
.map_err(|e| Error::SubSystem(e.to_string()))?;
Ok(())
}