mullvad / windows-service-rs

Windows services in Rust
Apache License 2.0
521 stars 85 forks source link

System paths not working #106

Closed romancitodev closed 1 year ago

romancitodev commented 1 year ago

I'm making a service that only write a .log in the desktop but the program is not working. I tried using dirs but nothing works. the service crashes with a 1067 error.

If i do this:

    let Ok(mut file) = OpenOptions::new()
    .create(true)
    .read(true)
    .write(true)
        .open(dirs::desktop_dir().unwrap().join("program.log")) else {
        return status.set_service_status(ServiceStatus {
            service_type: ServiceType::INTERACTIVE_PROCESS | ServiceType::USER_OWN_PROCESS,
            current_state: ServiceState::Stopped,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::ServiceSpecific(1),
            checkpoint: 0,
            wait_hint: Duration::default(),
            process_id: None,
        })
    };

    file.write_all(b"now works").unwrap();

the program crashes, but If i do this:

    let Ok(mut file) = OpenOptions::new()
    .create(true)
    .read(true)
    .write(true)
        .open("C:/Users/Shyrux/Desktop/program.log") else {
        return status.set_service_status(ServiceStatus {
            service_type: ServiceType::INTERACTIVE_PROCESS | ServiceType::USER_OWN_PROCESS,
            current_state: ServiceState::Stopped,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::ServiceSpecific(1),
            checkpoint: 0,
            wait_hint: Duration::default(),
            process_id: None,
        })
    };

    file.write_all(b"now works").unwrap();

The program works. But i need to find dinamically the desktop.

faern commented 1 year ago

This does not sound like an issue with windows-service, but rather with dirs, right? As the service is ran by the system user account it might not have any desktop folder. You can't expect it to find your user's desktop folder since the program is not running as your user.

romancitodev commented 1 year ago

So, how can i configure the service to run with my user?

romancitodev commented 1 year ago

I tried using this

use std::ffi::OsString;
use windows_service::{
    service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType},
    service_manager::{ServiceManager, ServiceManagerAccess},
};

const SERVICE_NAME: &str = "SpeedOS";

fn main() -> windows_service::Result<()> {
    let service_manager =
        ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::all())?;

    let service_binary_path = std::env::current_exe()
        .unwrap()
        .with_file_name("service.exe");

    println!("{service_binary_path:?}");

    let service_info = ServiceInfo {
        name: OsString::from(SERVICE_NAME),
        display_name: OsString::from(SERVICE_NAME),
        service_type: ServiceType::USER_OWN_PROCESS | ServiceType::INTERACTIVE_PROCESS,
        start_type: ServiceStartType::OnDemand,
        error_control: ServiceErrorControl::Normal,
        executable_path: service_binary_path,
        launch_arguments: vec![],
        dependencies: vec![],
        account_name: Some(".\\Shyrux".into()), // run as System
        account_password: Some("password".into()),
    };
    let service = service_manager.create_service(&service_info, ServiceAccess::all())?;
    service.set_description("Service to just write a file idk")?;
    println!("{SERVICE_NAME} installed correctly.");
    Ok(())
}

But when I install it, the service is marked as USER_OWN_PROCESS TEMPLATE and WIN32 returns 1077 Error code.

dlon commented 1 year ago

@ChirujanoCodding Your code above should create a template for a per-user service, but you might need to log out and log in again for the actual service to be created for your user:

Per-user services are services that are created when a user signs into Windows or Windows Server and are stopped and deleted when that user signs out.

https://learn.microsoft.com/en-us/windows/application-management/per-user-services-in-windows

This creates an instance of that service for every logged in user.

You probably don't have to specify account_name, account_password, or INTERACTIVE_PROCESS for a per-user service template. If you don't want to create a per-user service, you probably want to use your account instead of creating the template (and not use USER_OWN_PROCESS).

romancitodev commented 1 year ago

@dlon Hi, thanks for the answer but i still don't get it how i did to create a template, i just replaced ServiceType::OWN_PROCESS for ServiceType::USER_OWN_PROCESS.

The reason why i'm using USER_OWN_PROCESS is because i need access to the user desktop to create a file, so i don't even know if i need the account_name & account_password but sounds logic to me.

dlon commented 1 year ago

Tl;dr: Use USER_OWN_PROCESS if you want a separate instance of the service for each user, created at login and destroyed on logout. Use account_name/account_password if you want a single service logged in as that particular user. I've tried each and dirs_next::desktop_dir() returns the correct path in each case.

Here's an example using USER_OWN_PROCESS. I didn't bother to set the service status, but you get the idea:

use std::ffi::OsString;
use windows_service::{
    service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType},
    service_manager::{ServiceManager, ServiceManagerAccess},
};

const SERVICE_NAME: &str = "Test service";

fn main() -> windows_service::Result<()> {
    let mut args = std::env::args();
    args.next();
    let arg = args.next();
    if arg.as_ref().map(|s| s.as_str()) == Some("--run-as-service") {
        if let Some(dir) = dirs_next::desktop_dir() {
            std::fs::write(dir.join("testlog.txt"), "Hello world");
        }
        return Ok(());
    }

    let service_manager =
        ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CREATE_SERVICE)?;

    let service_binary_path = std::env::current_exe().unwrap();

    println!("{service_binary_path:?}");

    let service_info = ServiceInfo {
        name: OsString::from(SERVICE_NAME),
        display_name: OsString::from(SERVICE_NAME),
        service_type: ServiceType::USER_OWN_PROCESS,
        start_type: ServiceStartType::AutoStart,
        error_control: ServiceErrorControl::Normal,
        executable_path: service_binary_path,
        launch_arguments: vec![OsString::from("--run-as-service")],
        dependencies: vec![],
        account_name: None,
        account_password: None,
    };
    let service = service_manager.create_service(&service_info, ServiceAccess::all())?;
    service.set_description("Test service")?;
    println!("{SERVICE_NAME} installed correctly.");
    Ok(())
}

I had to log out and log in again for the service to become visible, and the user-specific service gets assigned a suffix.

I also tried using ServiceType::OWN_PROCESS as the service_type flag and setting the account with account_name and account_password. This worked too. You can also change the account later in the service console (services.msc).

romancitodev commented 1 year ago

@dlon Thanks you! finally i used the USER_OWN_PROCESS and i logged out and logged in and worked!