共通(Common)
type GenericError = Box<dyn std::error::Error + Send + Sync>;
type Result<T> = std::result::Result<T, GenericError>;
Cargo.toml
[target.'cfg(windows)'.dependencies]
windows = { version = "0", features = [
"Win32_Security_Authorization",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Environment",
"Win32_System_IO",
"Win32_System_Pipes",
"Win32_System_Registry",
"Win32_System_RemoteDesktop",
"Win32_System_Services",
"Win32_System_Shutdown",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging"
] }
Rust の文字列と C++/wchar_t, Win32/WSTR の相互変換(Interconversion between Rust strings and C++/wchar_t, Win32/WSTR)
wstr.rs
#![allow(dead_code)]
use std::{char::REPLACEMENT_CHARACTER, marker::PhantomData};
use windows::core::{PCWSTR, PWSTR};
pub struct CWSTR<'a> {
pub s: PCWSTR,
phantom: PhantomData<&'a ()>,
}
pub struct WSTR<'a> {
pub s: PWSTR,
phantom: PhantomData<&'a ()>,
}
pub trait WStrExt<'a> {
fn to_cw(&'a self) -> CWSTR<'a>;
fn to_w(&'a mut self) -> WSTR<'a>;
}
impl<'a> WStrExt<'a> for Vec<u16> {
fn to_cw(&'a self) -> CWSTR<'a> {
CWSTR {
s: PCWSTR::from_raw(self.as_ptr()),
phantom: PhantomData,
}
}
fn to_w(&'a mut self) -> WSTR<'a> {
WSTR {
s: PWSTR::from_raw(self.as_mut_ptr()),
phantom: PhantomData,
}
}
}
impl<'a> From<&'a Vec<u16>> for CWSTR<'a> {
fn from(v: &'a Vec<u16>) -> Self {
v.to_cw()
}
}
impl<'a> From<&'a mut Vec<u16>> for WSTR<'a> {
fn from(v: &'a mut Vec<u16>) -> Self {
v.to_w()
}
}
pub fn encode_utf16(source: &str) -> Vec<u16> {
source.encode_utf16().chain(Some(0)).collect()
}
pub fn decode_utf16(source: &[u16]) -> String {
std::char::decode_utf16(source.iter().cloned())
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
.collect()
}
URL を開く(Open URL)
pub fn browse_url(hwnd: HWND, url: &str, process: Option<&mut HANDLE>) -> Result<()> {
let param_u16 = wstr::encode_utf16(&format!("url.dll,FileProtocolHandler {}", url));
let mut si = SHELLEXECUTEINFOW {
cbSize: mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS,
hwnd,
lpFile: w!("rundll32.exe"),
lpParameters: param_u16.to_cw().s,
nShow: SW_SHOWNORMAL.0,
..Default::default()
};
unsafe { ShellExecuteExW(&mut si)?; }
match process {
None => {
unsafe { let _ = CloseHandle(si.hProcess); }
},
Some(process) => {
*process = si.hProcess;
},
}
Ok(())
}
実行ファイルのパスを取得(Get the path to the executable file)
pub fn query_full_process_path(process_id: u32) -> Result<String> {
unsafe {
let mut process_id = 0u32;
let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id)?;
defer! {
let _ = CloseHandle(process);
}
let mut len = 4096u32;
let mut path = [0u16; 4096];
QueryFullProcessImageNameW(process, PROCESS_NAME_WIN32, PWSTR::from_raw(path.as_mut_ptr()), &mut len)?;
return Ok(wstr::decode_utf16(&path[0..len as usize]));
}
Err(Box::new(Error::other("Error message here")))
}
プロセスID, 名前列挙(Process ID, Name Enumeration)
pub fn enum_processes() -> Result<Vec<(u32, String)>> {
let mut processes = vec![0; 1024];
let mut num_processes: u32 = 0;
if let Err(e) = unsafe { EnumProcesses(processes.as_mut_ptr(), (mem::size_of::<u32>() * processes.len()) as u32, &mut num_processes) } {
eprintln!("{e}");
return Err(Box::new(Error::other("Error message here")));
}
num_processes /= mem::size_of::<u32>() as u32;
let mut result = Vec::with_capacity(num_processes as usize);
for i in 0..num_processes {
let pid = processes[i as usize];
match unsafe { OpenProcess(PROCESS_QUERY_INFORMATION, false, pid) } {
Ok(handle) => {
let mut path = [0; 1024];
unsafe { GetModuleFileNameExW(handle, HINSTANCE::default(), path.as_mut_slice()); }
let path_str = match path.iter().position(|&x| x == 0) {
Some(pos) => OsString::from_wide(&path[..pos]),
None => OsString::from_wide(&path),
};
if let Ok(path) = path_str.into_string() {
result.push((pid, path));
}
},
Err(e) => {
eprintln!("{e}");
},
}
}
Ok(result)
}
カレントディレクトリ(Current directory, Working directory)
pub fn get_wd() -> Result<String> {
let mut current_exe = env::current_exe()?;
if current_exe.pop() {
Ok(current_exe.display().to_string())
} else {
Err(Box::new(Error::other("Error message here")))
}
}
ShellExecute
pub fn shell_execute(file: &str, param: Option<&str>, show_cmd: SHOW_WINDOW_CMD) -> HINSTANCE {
let file_u16 = wstr::encode_utf16(file);
match param {
Some(param) => {
let param_u16 = wstr::encode_utf16(param);
unsafe { ShellExecuteW(Some(HWND::default()), None, file_u16.to_cw().s, param_u16.to_cw().s, None, show_cmd) }
},
None => {
unsafe { ShellExecuteW(Some(HWND::default()), None, file_u16.to_cw().s, None, None, show_cmd) }
},
}
}
CreateProcess
pub fn create_process(app: &str, param: Option<&str>, cd: Option<&str>, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
let mut arg_u16 = match param {
Some(param) => {
wstr::encode_utf16(&(app.to_string() + " " + param))
},
None => {
wstr::encode_utf16(app)
},
};
let cd_u16 = match cd {
Some(cd) => {
wstr::encode_utf16(cd)
},
None => {
Vec::new()
},
};
let si = STARTUPINFOW {
cb: mem::size_of::<STARTUPINFOW>() as u32,
..Default::default()
};
match pi {
Some(pi) => {
unsafe {
match cd {
Some(_) => {
CreateProcessW(None, Some(arg_u16.to_w().s), None, None, false, PROCESS_CREATION_FLAGS(0), None, cd_u16.to_cw().s, &si, pi)?;
},
None => {
CreateProcessW(None, Some(arg_u16.to_w().s), None, None, false, PROCESS_CREATION_FLAGS(0), None, None, &si, pi)?;
},
}
}
},
None => {
let mut pi = PROCESS_INFORMATION::default();
unsafe {
match cd {
Some(_) => {
CreateProcessW(None, Some(arg_u16.to_w().s), None, None, false, PROCESS_CREATION_FLAGS(0), None, cd_u16.to_cw().s, &si, &mut pi)?;
},
None => {
CreateProcessW(None, Some(arg_u16.to_w().s), None, None, false, PROCESS_CREATION_FLAGS(0), None, None, &si, &mut pi)?;
},
}
let _ = CloseHandle(pi.hThread);
let _ = CloseHandle(pi.hProcess);
}
},
}
Ok(())
}
pub fn create_process_as_user(app: &str, param: Option<&str>, token: HANDLE, creation_flags: PROCESS_CREATION_FLAGS, environment: *const c_void, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
let mut desktop_u16 = wstr::encode_utf16(r#"winsta0\default"#);
let si = STARTUPINFOW {
cb: mem::size_of::<STARTUPINFOW>() as u32,
lpDesktop: desktop_u16.to_w().s,
..Default::default()
};
let arg = match param {
Some(param) => {
app.to_string() + " " + param
},
None => {
app.to_string()
},
};
let mut arg_u16 = wstr::encode_utf16(&arg);
match pi {
None => {
let mut pi = PROCESS_INFORMATION::default();
unsafe {
CreateProcessAsUserW(Some(token), None, Some(arg_u16.to_w().s), None, None, false, creation_flags, Some(environment), None, &si, &mut pi)?;
let _ = CloseHandle(pi.hThread);
let _ = CloseHandle(pi.hProcess);
}
},
Some(pi) => {
unsafe { CreateProcessAsUserW(Some(token), None, Some(arg_u16.to_w().s), None, None, false, creation_flags, Some(environment), None, &si, pi)?; }
},
}
Ok(())
}
pub fn create_process_as_active_user(app: &str, param: Option<&str>, pi: Option<&mut PROCESS_INFORMATION>) -> Result<()> {
let session_id = get_active_session_id()?;
let mut user_token = HANDLE::default();
unsafe { WTSQueryUserToken(session_id, &mut user_token)? };
defer! {
unsafe { let _ = CloseHandle(user_token); }
}
unsafe {
let mut size = mem::size_of::<HANDLE>() as u32;
let mut linked_token = HANDLE::default();
GetTokenInformation(user_token, TokenLinkedToken, Some(&mut linked_token as *mut _ as *mut _), size, &mut size)?;
defer! {
let _ = CloseHandle(linked_token);
}
let mut duplicated_token = HANDLE::default();
DuplicateTokenEx(linked_token, TOKEN_ACCESS_MASK(MAXIMUM_ALLOWED), None, SecurityImpersonation, TokenPrimary, &mut duplicated_token)?;
defer! {
let _ = CloseHandle(duplicated_token);
}
let mut environment = ptr::null_mut();
let mut creation_flags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS;
match CreateEnvironmentBlock(&mut environment, Some(duplicated_token), true) {
Ok(_) => {
creation_flags |= CREATE_UNICODE_ENVIRONMENT;
},
Err(_) => {
environment = ptr::null_mut();
},
}
defer! {
let _ = DestroyEnvironmentBlock(environment);
}
create_process_as_user(app, param, duplicated_token, creation_flags, environment, pi)?;
};
Ok(())
}
pub fn get_active_session_id() -> Result<u32> {
let mut si: *mut WTS_SESSION_INFOW = ptr::null_mut();
let mut count = 0u32;
unsafe { WTSEnumerateSessionsW(Some(WTS_CURRENT_SERVER_HANDLE), 0, 1, &mut si, &mut count)?; }
defer! {
unsafe { WTSFreeMemory(si as *mut _); }
}
let mut iter = si;
unsafe {
for _ in 0..count {
if (*iter).State == WTSActive {
return Ok((*iter).SessionId);
}
iter = iter.offset(1);
}
}
Err(Box::new(Error::new(ErrorKind::NotFound, "Error message here")))
}
ホットキー(Hot Key)
#[derive(FromPrimitive)]
enum IdHotKey {
ABC,
}
// 登録削除
RegisterHotKey(Some(hwnd), IdHotKey::ABC as i32, MOD_CONTROL | MOD_SHIFT, VK_SPACE.0 as u32);
RegisterHotKey(Some(hwnd), IdHotKey::ABC as i32, MOD_ALT | MOD_CONTROL, 'A' as u32);
UnregisterHotKey(Some(hwnd), IdHotKey::ABC as i32);
// イベント処理
WM_HOTKEY => {
match FromPrimitive::from_usize(w_param.0) {
Some(IdHotKey::ABC) => { ... }
...
}
// enum に FromPrimitive を使わない場合
// match w_param.0 {
// x if x == IdHotKey::ABC as usize => { ... }
// ...
// }
}
ウィンドウ作成/メッセージループ(Window creation/Message loop)
pub fn create_window() -> Result<HWND> {
let class_name = w!("class name here");
let winc = WNDCLASSW {
lpfnWndProc: Some(wnd_proc),
lpszClassName: class_name,
..Default::default()
};
if unsafe { RegisterClassW(&winc) } == 0 {
return Err(Box::new(Error::other("Error message here")))
}
match unsafe { CreateWindowExW(WINDOW_EX_STYLE(0), class_name, w!("window name here"), WS_OVERLAPPEDWINDOW | WS_MINIMIZE, 0, 0, 0, 0, None, None, None, None) } {
Ok(hwnd) => {
Ok(hwnd)
},
Err(e) => {
Err(Box::new(e))
},
}
}
unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
match msg {
WM_CREATE => {
},
WM_CLOSE => {
let _ = DestroyWindow(hwnd);
},
WM_DESTROY => {
PostQuitMessage(0);
}
_ => {
return DefWindowProcW(hwnd, msg, w_param, l_param);
}
}
LRESULT(0)
}
fn main() -> Result<()> {
unsafe {
let hwnd = create_window()?;
let _ = ShowWindow(hwnd, SW_SHOWNORMAL);
let _ = UpdateWindow(hwnd);
let mut msg = MSG::default();
while GetMessageW(&mut msg, None, 0, 0).as_bool() {
let _ = TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
Ok(())
}
フォント(Font)
let log_font = LOGFONTW {
lfHeight: 20,
lfWidth: 0,
lfEscapement: 0,
lfOrientation: 0,
lfWeight: FW_NORMAL.0 as i32,
lfItalic: 0,
lfUnderline: 0,
lfStrikeOut: 0,
lfCharSet: DEFAULT_CHARSET,
lfOutPrecision: OUT_TT_PRECIS,
lfClipPrecision: CLIP_DEFAULT_PRECIS,
lfQuality: ANTIALIASED_QUALITY,
lfPitchAndFamily: DEFAULT_PITCH.0 | FF_DONTCARE.0,
lfFaceName: {
let mut face_name_u16 = wstr::encode_utf16("Meiryo");
face_name_u16.resize(32, 0);
face_name_u16.try_into().unwrap()
},
};
let font = CreateFontIndirectW(&log_font);
名前付きパイプ(Named Pipe)
pub fn create_security_named_pipe(name: &str) -> Result<HANDLE> {
let mut desc = SECURITY_DESCRIPTOR::default();
unsafe {
InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR(&mut desc as *mut _ as *mut _), SECURITY_DESCRIPTOR_REVISION)?;
let mut accesses: [EXPLICIT_ACCESS_W; 2] = [EXPLICIT_ACCESS_W::default(); 2];
// 設定例
BuildExplicitAccessWithNameW(&mut accesses[0], w!("Everyone"), FILE_ALL_ACCESS.0, GRANT_ACCESS, ACE_FLAGS(0));
BuildExplicitAccessWithNameW(&mut accesses[1], w!("ANONYMOUS LOGON"), FILE_ALL_ACCESS.0, GRANT_ACCESS, ACE_FLAGS(0));
let mut dacl: *mut ACL = ptr::null_mut();
SetEntriesInAclW(Some(&accesses), None, &mut dacl).ok()?;
defer! {
LocalFree(Some(HLOCAL(dacl as _)));
}
SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR(&mut desc as *mut _ as *mut _), true, Some(dacl), false)?;
let mut attr = SECURITY_ATTRIBUTES::default();
attr.lpSecurityDescriptor = &mut desc as *mut _ as *mut _;
attr.bInheritHandle = BOOL(0);
let name_u16 = wstr::encode_utf16(name);
let pipe = CreateNamedPipeW(name_u16.to_cw().s, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_READMODE_BYTE, 1, 1024, 1024, 1000, Some(&attr));
if pipe == INVALID_HANDLE_VALUE {
return Err(Box::new(Error::other("Error message here")));
}
Ok(pipe)
}
}
特権(Privilege)
pub fn adjust_token_privilege(token: HANDLE, privilege: PCWSTR) -> Result<()> {
let mut tp = TOKEN_PRIVILEGES::default();
tp.PrivilegeCount = 1;
unsafe {
LookupPrivilegeValueW(None, privilege, &mut tp.Privileges[0].Luid)?;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(token, false, Some(&tp), 0, None, None)?;
}
Ok(())
}
pub fn adjust_process_privilege(process: HANDLE, privilege: PCWSTR) -> Result<()> {
let mut token = HANDLE::default();
unsafe {
OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &mut token)?;
defer! {
let _ = CloseHandle(token);
}
adjust_token_privilege(token, privilege)
}
}
fn main() -> Result<()> {
adjust_process_privilege(unsafe { GetCurrentProcess() }, SE_DEBUG_NAME) // 使用例
}
ExitWindows
pub fn exit_windows(flags: EXIT_WINDOWS_FLAGS) -> Result<()> {
if flags.0 & EWX_LOGOFF.0 == 0 {
adjust_process_privilege(unsafe { GetCurrentProcess() }, SE_SHUTDOWN_NAME)?;
}
unsafe { ExitWindowsEx(flags, SHTDN_REASON_NONE)?; }
Ok(())
}
サービス(Service)
開始(Start)
let mut service_name_u16 = wstr::encode_utf16("Service name");
let services: &[SERVICE_TABLE_ENTRYW] = &[
SERVICE_TABLE_ENTRYW {
lpServiceName: service_name_u16.to_w().s,
lpServiceProc: Some(service_main), // service_main は下記参照
},
SERVICE_TABLE_ENTRYW {
..Default::default()
},
];
unsafe { StartServiceCtrlDispatcherW(services.as_ptr())?; }
登録/削除(Register/Unregister)
pub fn register_service() -> Result<()> {
let manager = unsafe { OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE | SC_MANAGER_LOCK)? };
if manager.is_invalid() {
return Err(Box::new(Error::other("Error message here")));
}
defer! {
unsafe { let _ = CloseServiceHandle(manager); }
}
let current_exe = env::current_exe()?;
let current_exe_path = current_exe.display().to_string();
let current_exe_path_u16 = wstr::encode_utf16(¤t_exe_path);
let service = unsafe { CreateServiceW(manager, w!("Service name"), w!("Display name"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, current_exe_path_u16.to_cw().s, None, None, None, None, None)? };
if service.is_invalid() {
return Err(Box::new(Error::other("Error message here")));
}
defer! {
unsafe { let _ = CloseServiceHandle(service); }
}
let mut description_u16 = wstr::encode_utf16("Service description");
let desc = SERVICE_DESCRIPTIONW {
lpDescription: description_u16.to_w().s,
};
unsafe {
{
let lock = LockServiceDatabase(manager);
defer! {
let _ = UnlockServiceDatabase(lock);
}
ChangeServiceConfig2W(service, SERVICE_CONFIG_DESCRIPTION, Some(&desc as *const _ as *mut _))?;
}
std::thread::sleep(std::time::Duration::from_millis(500));
StartServiceW(service, None)?;
}
Ok(())
}
pub fn unregister_service() -> Result<()> {
let manager = unsafe { OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE | SC_MANAGER_LOCK)? };
if manager.is_invalid() {
return Err(Box::new(Error::other("Error message here")));
}
defer! {
unsafe { let _ = CloseServiceHandle(manager); }
}
let service = unsafe { OpenServiceW(manager, w!("Service name"), SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE.0)? };
if service.is_invalid() {
return Err(Box::new(Error::other("Error message here")));
}
defer! {
unsafe { let _ = CloseServiceHandle(service); }
}
let mut ss = SERVICE_STATUS::default();
unsafe {
QueryServiceStatus(service, &mut ss)?;
if ss.dwCurrentState == SERVICE_RUNNING {
ControlService(service, SERVICE_CONTROL_STOP, &mut ss)?;
}
DeleteService(service)?;
}
Ok(())
}
ハンドラ(Service handler)
#[derive(Debug, PartialEq, Default)]
struct ServiceData {
stop_event: HANDLE,
service_status: SERVICE_STATUS,
service_handle: SERVICE_STATUS_HANDLE,
}
unsafe impl Send for ServiceData {}
static SERVICE_DATA: LazyLock<Mutex<ServiceData>> = LazyLock::new(|| {
Mutex::new(ServiceData::default())
});
fn handler_ex_stub(control: u32, _event_type: u32, _event_data: *mut core::ffi::c_void, _context: *mut core::ffi::c_void) -> Result<u32> {
match control {
SERVICE_CONTROL_STOP => {
let mut sd = SERVICE_DATA.lock().unwrap();
sd.service_status.dwCurrentState = SERVICE_STOP_PENDING;
sd.service_status.dwCheckPoint = 0;
sd.service_status.dwWaitHint = 1000;
unsafe {
SetServiceStatus(sd.service_handle, &sd.service_status)?;
SetEvent(sd.stop_event)?;
}
},
SERVICE_CONTROL_INTERROGATE => {
let sd = SERVICE_DATA.lock().unwrap();
unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
},
_ => {
},
}
Ok(NO_ERROR.0)
}
unsafe extern "system" fn handler_ex(control: u32, event_type: u32, event_data: *mut core::ffi::c_void, context: *mut core::ffi::c_void) -> u32 {
match handler_ex_stub(control, event_type, event_data, context) {
Ok(n) => {
n
},
Err(e) => {
// Error handling here
NO_ERROR.0
},
}
}
fn service_main_stub(_num_services_args: u32, _service_arg_vectors: *mut PWSTR) -> Result<()> {
{
let mut sd = SERVICE_DATA.lock().unwrap();
sd.stop_event = unsafe { CreateEventW(None, false, false, None)? };
sd.service_handle = unsafe { RegisterServiceCtrlHandlerExW(w!("Service name"), Some(handler_ex), None)? };
sd.service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
sd.service_status.dwCurrentState = SERVICE_RUNNING;
sd.service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
sd.service_status.dwWin32ExitCode = NO_ERROR.0;
sd.service_status.dwServiceSpecificExitCode = 0;
sd.service_status.dwCheckPoint = 0;
sd.service_status.dwWaitHint = 0;
unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
}
defer! {
unsafe { let _ = CloseHandle(SERVICE_DATA.lock().unwrap().stop_event); }
}
{
let stop_event = SERVICE_DATA.lock().unwrap().stop_event;
unsafe { WaitForSingleObject(stop_event, INFINITE); }
let mut sd = SERVICE_DATA.lock().unwrap();
sd.service_status.dwCurrentState = SERVICE_STOPPED;
sd.service_status.dwCheckPoint = 0;
sd.service_status.dwWaitHint = 0;
unsafe { SetServiceStatus(sd.service_handle, &sd.service_status)?; }
}
return Ok(());
}
pub unsafe extern "system" fn service_main(num_services_args: u32, service_arg_vectors: *mut PWSTR) {
if let Err(e) = service_main_stub(num_services_args, service_arg_vectors) {
// Error handling here
}
}