quickcfg 0.6.3

Do basic configuration of a system, declaratively and quickly.
Documentation
use std::ffi::{OsStr, OsString};
use std::io;
use std::os::windows::ffi::OsStrExt as _;
use std::ptr;
use winapi::shared::minwindef::DWORD;
use winapi::shared::minwindef::TRUE;
use winapi::shared::winerror::WAIT_TIMEOUT;
use winapi::um::processthreadsapi::GetExitCodeProcess;
use winapi::um::shellapi;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::{INFINITE, WAIT_ABANDONED, WAIT_OBJECT_0};
use winapi::um::winuser;

fn wide_string(s: impl AsRef<OsStr>) -> Vec<u16> {
    s.as_ref().encode_wide().chain(Some(0)).collect::<Vec<_>>()
}

pub fn runas(command: crate::Command) -> io::Result<i32> {
    let operation: Vec<u16> = OsStr::new("runas\0").encode_wide().collect();

    let file = wide_string(&command.name);
    let params = wide_string(encode_params(&command.args)?);

    let mut exit_code = 0;

    unsafe {
        let mut info = shellapi::SHELLEXECUTEINFOW::default();

        info.cbSize = std::mem::size_of::<shellapi::SHELLEXECUTEINFOW>() as DWORD;
        info.fMask = shellapi::SEE_MASK_NOCLOSEPROCESS;
        info.hwnd = ptr::null_mut();
        info.lpVerb = operation.as_ptr();
        info.lpFile = file.as_ptr();
        info.lpParameters = params.as_ptr();
        info.lpDirectory = ptr::null();
        info.nShow = winuser::SW_SHOW;
        info.hInstApp = ptr::null_mut();

        let result = shellapi::ShellExecuteExW(&mut info);

        if result != TRUE {
            return Err(io::Error::last_os_error());
        }

        match WaitForSingleObject(info.hProcess, INFINITE) {
            WAIT_OBJECT_0 => (),
            WAIT_ABANDONED => return Err(io::Error::new(io::ErrorKind::Other, "wait abandoned")),
            WAIT_TIMEOUT => return Err(io::Error::new(io::ErrorKind::Other, "wait timed out")),
            _ => return Err(io::Error::last_os_error()),
        }

        let result = GetExitCodeProcess(info.hProcess, &mut exit_code);

        if result != TRUE {
            return Err(io::Error::last_os_error());
        }

        Ok(exit_code as i32)
    }
}

fn encode_params<A>(args: A) -> io::Result<OsString>
where
    A: IntoIterator,
    A::Item: AsRef<OsStr>,
{
    let mut params = String::new();

    for arg in args {
        let arg = arg.as_ref();

        let arg = match arg.to_str() {
            Some(arg) => arg,
            None => {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    "argument is not valid UTF-8",
                ))
            }
        };

        params.push(' ');

        if arg.is_empty() {
            params.push_str("\"\"");
        } else if arg.find(&[' ', '\t', '"'][..]).is_none() {
            params.push_str(arg);
        } else {
            params.push('"');

            for c in arg.chars() {
                match c {
                    '\\' => params.push_str("\\\\"),
                    '"' => params.push_str("\\\""),
                    c => params.push(c),
                }
            }

            params.push('"');
        }
    }

    Ok(OsString::from(params))
}