cfun 0.2.11

Tidy up common functions
Documentation
#[cfg(all(feature = "winapi", target_os = "windows"))]
use windows::Win32::{
    Foundation::CloseHandle,
    System::Diagnostics::ToolHelp::{
        CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W,
        TH32CS_SNAPPROCESS,
    },
};

/// get process id by process name on windows
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn get_process_id_by_name(name: &str) -> Option<u32> {
    unsafe {
        let h_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        let Ok(h_snapshot) = h_snapshot else {
            return None;
        };
        let mut pe = PROCESSENTRY32W {
            dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
            ..Default::default()
        };
        let ret = Process32FirstW(h_snapshot, &mut pe);
        loop {
            if ret.is_err() {
                break;
            }
            let end_pos = pe.szExeFile.iter().position(|p| *p == 0);
            let Some(end_pos) = end_pos else {
                break;
            };
            let process_name = String::from_utf16_lossy(&pe.szExeFile[0..end_pos]);
            if process_name == name {
                let _ = CloseHandle(h_snapshot);
                return Some(pe.th32ProcessID);
            }
            let ret = Process32NextW(h_snapshot, &mut pe);
            if ret.is_err() {
                break;
            }
        }
        let _ = CloseHandle(h_snapshot);
    }
    None
}

/// service status
#[cfg(all(feature = "winapi", target_os = "windows"))]
#[derive(Debug)]
pub enum ServStatus {
    Uninstalled,
    Running,
    Paused,
    Stoped,
    StartPending,
    StopPending,
    PausePending,
    ContinuePending,
    Unknow,
}

/// query system service status
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn sc_query(service_name: &str) -> Result<ServStatus, windows::core::Error> {
    use windows::{
        core::PWSTR,
        Win32::System::Services::{
            CloseServiceHandle, OpenSCManagerW, OpenServiceW, QueryServiceStatus,
            SC_MANAGER_CONNECT, SERVICE_CONTINUE_PENDING, SERVICE_PAUSED, SERVICE_PAUSE_PENDING,
            SERVICE_QUERY_STATUS, SERVICE_RUNNING, SERVICE_START_PENDING, SERVICE_STATUS,
            SERVICE_STOPPED, SERVICE_STOP_PENDING,
        },
    };

    unsafe {
        let h_sc = OpenSCManagerW(None, None, SC_MANAGER_CONNECT)?;
        let mut service_name: Vec<u16> = service_name.encode_utf16().chain([0]).collect();
        let service_name = PWSTR::from_raw(service_name.as_mut_ptr());

        let h_service = OpenServiceW(h_sc, service_name, SERVICE_QUERY_STATUS);
        let Ok(h_service) = h_service else {
            let err = h_service.unwrap_err();
            if err.code().0 == 0x80070424u32 as i32 {
                return Ok(ServStatus::Uninstalled);
            }
            return Err(err);
        };
        let mut s_status = SERVICE_STATUS::default();
        QueryServiceStatus(h_service, &mut s_status)?;

        let _ = CloseServiceHandle(h_service);
        let _ = CloseServiceHandle(h_sc);

        let status = match s_status.dwCurrentState {
            SERVICE_RUNNING => ServStatus::Running,
            SERVICE_PAUSED => ServStatus::Paused,
            SERVICE_STOPPED => ServStatus::Stoped,
            SERVICE_CONTINUE_PENDING => ServStatus::ContinuePending,
            SERVICE_START_PENDING => ServStatus::StartPending,
            SERVICE_PAUSE_PENDING => ServStatus::PausePending,
            SERVICE_STOP_PENDING => ServStatus::StopPending,
            _ => ServStatus::Unknow,
        };
        Ok(status)
    }
}

/// run the program with administrator privileges
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn runas(
    exe_path: &str,
    args: Option<Vec<String>>,
    wait: bool,
) -> Result<(), windows::core::Error> {
    use windows::{
        core::{w, PCWSTR},
        Win32::{
            System::{
                Com::{
                    CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED,
                    COINIT_DISABLE_OLE1DDE,
                },
                Threading::{WaitForSingleObject, INFINITE},
            },
            UI::{
                Shell::{
                    ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SEE_MASK_UNICODE, SHELLEXECUTEINFOW,
                },
                WindowsAndMessaging::SW_SHOWNORMAL,
            },
        },
    };

    unsafe {
        // 初始化 COM
        let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

        // 设置 SHELLEXECUTEINFOW 结构体
        let mut sei: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW::default();
        sei.cbSize = std::mem::size_of::<SHELLEXECUTEINFOW>() as u32;
        sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE;
        sei.lpVerb = w!("runas");
        // 明确调用 cmd.exe 而不是直接调用 .cmd 文件
        sei.lpFile = PCWSTR::from_raw(w!("cmd.exe").as_ptr());
        // 如果想让窗口执行完毕后保持打开状态,可以用 /k;如果希望运行完毕后关闭,则用 /c
        let shell_switch = "/C"; // 或者使用 "/c" 根据需求选择

        // // 对脚本路径加引号(只做一次包装即可)
        let script_path_quoted = format!("\"{}\"", exe_path);
        // 对各参数也做一次包装,防止因空格被拆分
        let args_str = if let Some(args) = args {
            args.into_iter()
                .map(|arg| format!("\"{}\"", arg))
                .collect::<Vec<_>>()
                .join(" ")
        } else {
            String::new()
        };

        // 构造完整的参数字符串,注意各部分只引号包装一次
        // 例如:/k "D:\Desktop\test.cmd" "C:\Program Files\7-Zip"
        let params_str = format!("{} \"{} {}\"", shell_switch, script_path_quoted, args_str);

        // 将参数转换为 UTF-16 并确保其生命周期足够长(这里简单用 leak,生产代码中建议用更安全的方式管理内存)
        let mut params_w: Vec<u16> = params_str.encode_utf16().collect();
        params_w.push(0);
        sei.lpParameters = PCWSTR::from_raw(params_w.leak().as_mut_ptr());
        sei.nShow = SW_SHOWNORMAL.0;

        ShellExecuteExW(&mut sei)?;
        if wait {
            // 如果需要等待进程结束
            WaitForSingleObject(sei.hProcess, INFINITE);
        }
        CoUninitialize();
        Ok(())
    }
}

#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn current_is_admin() -> Result<bool, windows::core::Error> {
    use windows::Win32::{
        Foundation::HANDLE,
        Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY},
        System::Threading::{GetCurrentProcess, OpenProcessToken},
    };
    unsafe {
        let mut token_handle = HANDLE::default();
        OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token_handle)?;
        let mut token_info = TOKEN_ELEVATION::default();

        let mut return_len = 0;
        GetTokenInformation(
            token_handle,
            TokenElevation,
            Some(&mut token_info as *mut _ as *mut std::ffi::c_void),
            std::mem::size_of::<TOKEN_ELEVATION>() as u32,
            &mut return_len,
        )?;

        CloseHandle(token_handle)?;

        return Ok(token_info.TokenIsElevated == 1);
    }
}
/// allow cmd show color and so on...
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn enable_virtual_terminal() -> Result<(), windows::core::Error> {
    use windows::Win32::System::Console::{
        GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE,
        ENABLE_VIRTUAL_TERMINAL_PROCESSING, STD_OUTPUT_HANDLE,
    };
    unsafe {
        let handle = GetStdHandle(STD_OUTPUT_HANDLE).unwrap();
        let mut mode = CONSOLE_MODE::default();
        let _ = GetConsoleMode(handle, &mut mode)?;
        let _ = SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?;
        Ok(())
    }
}

/// install msi package
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn msi_install(path: &str, args: Option<&str>) -> Result<(), windows::core::Error> {
    use windows::{
        core::PCWSTR, Win32::System::ApplicationInstallationAndServicing::MsiInstallProductW,
    };

    let msi_path_wide: Vec<u16> = path.encode_utf16().chain([0]).collect();
    let msi_args = if args.is_some() {
        let args = args.unwrap();
        let args: Vec<u16> = args.encode_utf16().chain([0]).collect();
        args
    } else {
        Vec::new()
    };

    let msi_args = if msi_args.is_empty() {
        PCWSTR::null()
    } else {
        PCWSTR::from_raw(msi_args.as_ptr())
    };

    let result = unsafe {
        MsiInstallProductW(
            PCWSTR(msi_path_wide.as_ptr()),
            msi_args, // 空命令行参数
        )
    };
    if result != 0 {
        return Err(windows::core::Error::from_win32());
    }
    Ok(())
}

/// get the installed msi package version
#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn msi_get_version(name: &str) -> Result<String, windows::core::Error> {
    use windows::{
        core::{HRESULT, PWSTR},
        Win32::System::ApplicationInstallationAndServicing::{
            MsiEnumProductsW, MsiGetProductInfoW, INSTALLPROPERTY_PRODUCTNAME,
            INSTALLPROPERTY_VERSIONSTRING,
        },
    };

    unsafe {
        let mut prod_code: Vec<u16> = Vec::new();
        prod_code.resize(0xff, 0);
        let prod_code = PWSTR::from_raw(prod_code.as_mut_ptr());
        let mut idx = 0;
        let mut ret = MsiEnumProductsW(idx, prod_code);
        loop {
            if ret != 0 {
                break;
            }
            idx += 1;
            let mut buf: Vec<u16> = Vec::new();
            buf.resize(0xff, 0);
            let prod_name = PWSTR::from_raw(buf.as_mut_ptr());
            let mut len = 0xff;
            let name_ret = MsiGetProductInfoW(
                prod_code,
                INSTALLPROPERTY_PRODUCTNAME,
                Some(prod_name),
                Some(&mut len),
            );
            if name_ret != 0 {
                continue;
            }
            let prod_name = prod_name.to_string().unwrap_or_default();
            if prod_name == name {
                let mut buf: Vec<u16> = Vec::new();
                buf.resize(0xff, 0);
                let buf = PWSTR::from_raw(buf.as_mut_ptr());
                let mut len = 0xff;
                let ret = MsiGetProductInfoW(
                    prod_code,
                    INSTALLPROPERTY_VERSIONSTRING,
                    Some(buf),
                    Some(&mut len),
                );
                if ret != 0 {
                    return Err(windows::core::Error::from_win32());
                }
                let version = buf.to_string().unwrap_or_default();
                return Ok(version);
            }
            ret = MsiEnumProductsW(idx, prod_code);
        }
        Err(windows::core::Error::from_hresult(HRESULT::from_win32(1)))
    }
}

#[cfg(all(feature = "winapi", target_os = "windows"))]
pub fn get_system_proxy() -> Option<String> {
    use std::ffi::c_void;

    use windows::Win32::{
        Foundation::{GlobalFree, HGLOBAL},
        Networking::WinInet::{
            InternetQueryOptionW, INTERNET_OPTION_PER_CONNECTION_OPTION,
            INTERNET_PER_CONN_AUTOCONFIG_URL, INTERNET_PER_CONN_AUTODISCOVERY_FLAGS,
            INTERNET_PER_CONN_FLAGS, INTERNET_PER_CONN_OPTIONW, INTERNET_PER_CONN_OPTION_LISTW,
            INTERNET_PER_CONN_PROXY_BYPASS, INTERNET_PER_CONN_PROXY_SERVER, PROXY_TYPE_PROXY,
        },
    };

    let mut list = INTERNET_PER_CONN_OPTION_LISTW::default();
    list.dwSize = size_of::<INTERNET_PER_CONN_OPTION_LISTW>() as u32;

    let mut options = [INTERNET_PER_CONN_OPTIONW::default(); 5];
    options[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
    options[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
    options[2].dwOption = INTERNET_PER_CONN_FLAGS;
    options[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
    options[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;

    list.dwOptionCount = 5;
    list.dwOptionError = 0;
    list.pOptions = &mut options as *mut INTERNET_PER_CONN_OPTIONW;
    let mut n_size = size_of::<INTERNET_PER_CONN_OPTION_LISTW>() as u32;
    let ret = unsafe {
        InternetQueryOptionW(
            None,
            INTERNET_OPTION_PER_CONNECTION_OPTION,
            Some(&mut list as *mut INTERNET_PER_CONN_OPTION_LISTW as *mut c_void),
            &mut n_size,
        )
    };
    if ret.is_err() {
        println!("{:?}", ret.unwrap_err());
        return None;
    }
    let enable = unsafe { options[2].Value.dwValue } & PROXY_TYPE_PROXY == PROXY_TYPE_PROXY;
    let proxy_url = unsafe { options[4].Value.pszValue.to_string().unwrap() };
    unsafe {
        if !options[0].Value.pszValue.is_null() {
            let mut mem = HGLOBAL::default();
            mem.0 = options[0].Value.pszValue.as_ptr() as *mut c_void;
            let _ = GlobalFree(Some(mem));
        }
        if !options[3].Value.pszValue.is_null() {
            let mut mem = HGLOBAL::default();
            mem.0 = options[3].Value.pszValue.as_ptr() as *mut c_void;
            let _ = GlobalFree(Some(mem));
        }
        if !options[4].Value.pszValue.is_null() {
            let mut mem = HGLOBAL::default();
            mem.0 = options[4].Value.pszValue.as_ptr() as *mut c_void;
            let _ = GlobalFree(Some(mem));
        }
    }
    if enable {
        Some(proxy_url)
    } else {
        None
    }
}

#[test]
#[cfg(all(feature = "winapi", target_os = "windows"))]
fn test_get_process_id_by_name() {
    let ret = get_process_id_by_name("Notepad.exe");
    println!("{:?}", ret);
}

#[test]
#[cfg(all(feature = "winapi", target_os = "windows"))]
fn test_sc_query() {
    let ret = sc_query("slmd");
    println!("{:?}", ret);
}