├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ └── release.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── async_query.rs ├── benches └── benchmark.rs ├── bin └── wmiq.rs ├── connection.rs ├── context.rs ├── datetime.rs ├── datetime_time.rs ├── de ├── meta.rs ├── mod.rs ├── variant_de.rs └── wbem_class_de.rs ├── duration.rs ├── lib.rs ├── method.rs ├── notification.rs ├── query.rs ├── query_sink.rs ├── result_enumerator.rs ├── safearray.rs ├── ser ├── mod.rs └── variant_ser.rs ├── tests.rs ├── utils.rs └── variant.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: windows-2019 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Setup Rust toolchain 13 | uses: dtolnay/rust-toolchain@stable 14 | 15 | # Cargo build - Only chrono (default features) 16 | - name: Build - Default 17 | run: cargo build 18 | 19 | # Cargo build - No features 20 | - name: Build - No features 21 | run: cargo build --no-default-features 22 | 23 | # Cargo build - Only time 24 | - name: Build - Only time feature 25 | run: cargo build --no-default-features --features=time 26 | 27 | # Cargo build - All features 28 | - name: Build - All features 29 | run: cargo build --all-features 30 | 31 | test: 32 | name: Run tests 33 | runs-on: windows-2019 34 | steps: 35 | - uses: actions/checkout@v3 36 | 37 | - name: Setup Rust toolchain 38 | uses: dtolnay/rust-toolchain@stable 39 | 40 | # Test on default features 41 | - name: Test - All targets 42 | run: cargo test --all-targets 43 | 44 | # Test time crate 45 | - name: Test - Only tests with the Time crate 46 | run: cargo test --tests --no-default-features --features=time 47 | 48 | # Test documentation with the 'test' feature 49 | - name: Test - Only Documentation 50 | run: cargo test --doc --features=test 51 | 52 | fmt: 53 | name: Rustfmt 54 | runs-on: windows-2019 55 | steps: 56 | - uses: actions/checkout@v3 57 | 58 | - name: Setup Rust toolchain 59 | uses: dtolnay/rust-toolchain@stable 60 | with: 61 | components: rustfmt 62 | 63 | - name: Run format check 64 | run: cargo fmt --check 65 | 66 | clippy: 67 | name: Clippy 68 | runs-on: windows-2019 69 | steps: 70 | - uses: actions/checkout@v3 71 | 72 | - name: Setup Rust toolchain 73 | uses: dtolnay/rust-toolchain@stable 74 | with: 75 | components: clippy 76 | 77 | - name: Run clippy 78 | run: cargo clippy -- -D warnings 79 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | 8 | jobs: 9 | create-release: 10 | name: create-release 11 | runs-on: windows-2019 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Check semver 16 | uses: obi1kenobi/cargo-semver-checks-action@v2 17 | - name: Cleanup 18 | run: rm -r -force semver-checks 19 | 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: publish 26 | args: --token ${{ secrets.CRATES_IO_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | .idea 12 | 13 | # vscode ide 14 | .vscode 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wmi" 3 | version = "0.17.2" 4 | authors = ["Ohad Ravid "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/crate/wmi" 9 | homepage = "https://github.com/ohadravid/wmi-rs" 10 | repository = "https://github.com/ohadravid/wmi-rs" 11 | description = """ 12 | WMI crate for rust. 13 | """ 14 | categories = ["api-bindings", "os::windows-apis"] 15 | keywords = ["wmi", "com", "win32"] 16 | resolver = "2" 17 | 18 | [package.metadata.docs.rs] 19 | default-target = "x86_64-pc-windows-msvc" 20 | 21 | [features] 22 | default = ["chrono"] 23 | # Use { default-features = false, features = ["time"] } to use `time` instead of `chrono`. 24 | 25 | # For use in documentation tests 26 | test = [] 27 | 28 | [target.'cfg(target_os = "windows")'.dependencies] 29 | windows-core = { version = "0.61" } 30 | windows = { version = "0.61", features = [ 31 | "Win32_Foundation", 32 | "Win32_Security", 33 | "Win32_System_Com", 34 | "Win32_System_Ole", 35 | "Win32_System_Rpc", 36 | "Win32_System_Wmi", 37 | "Win32_System_Variant", 38 | ] } 39 | time = { version = "0.3", features = ["formatting", "parsing", "macros", "serde"], optional = true } 40 | chrono = { version = "0.4", features = ["clock", "std", "serde"], optional = true, default-features = false } 41 | serde = { version = "1.0", features = ["derive"] } 42 | futures = { version = "0.3" } 43 | thiserror = "^2" 44 | log = "0.4" 45 | 46 | [dev-dependencies] 47 | async-std = { version = "1", features = ["attributes"] } 48 | tokio = { version = "1", features = ["rt", "macros"] } 49 | serde_json = { version = "1.0" } 50 | criterion = "0.5" 51 | tempdir = "0.3" 52 | 53 | [[bin]] 54 | name = "wmiq" 55 | 56 | [[bench]] 57 | name = "benchmark" 58 | path = "./src/benches/benchmark.rs" 59 | harness = false 60 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wmi 2 | 3 | [![check](https://github.com/ohadravid/wmi-rs/actions/workflows/check.yml/badge.svg?branch=main)](https://github.com/ohadravid/wmi-rs/actions/workflows/check.yml) 4 | [![crates.io](https://img.shields.io/crates/v/wmi.svg)](https://crates.io/crates/wmi) 5 | [![docs.rs](https://docs.rs/wmi/badge.svg)](https://docs.rs/crate/wmi) 6 | 7 | WMI (Windows Management Instrumentation) crate for rust. 8 | 9 | ```toml 10 | # Cargo.toml 11 | [dependencies] 12 | wmi = "*" 13 | ``` 14 | 15 | ## Examples 16 | 17 | Queries can be deserialized into a free-form `HashMap` or a `struct`: 18 | 19 | ```rust 20 | #![allow(non_camel_case_types)] 21 | #![allow(non_snake_case)] 22 | 23 | use serde::Deserialize; 24 | use wmi::{COMLibrary, Variant, WMIConnection, WMIDateTime}; 25 | use std::collections::HashMap; 26 | 27 | fn main() -> Result<(), Box> { 28 | let com_con = COMLibrary::new()?; 29 | let wmi_con = WMIConnection::new(com_con.into())?; 30 | 31 | let results: Vec> = wmi_con.raw_query("SELECT * FROM Win32_OperatingSystem")?; 32 | 33 | for os in results { 34 | println!("{:#?}", os); 35 | } 36 | 37 | #[derive(Deserialize, Debug)] 38 | struct Win32_OperatingSystem { 39 | Caption: String, 40 | Name: String, 41 | CurrentTimeZone: i16, 42 | Debug: bool, 43 | EncryptionLevel: u32, 44 | ForegroundApplicationBoost: u8, 45 | LastBootUpTime: WMIDateTime, 46 | } 47 | 48 | let results: Vec = wmi_con.query()?; 49 | 50 | for os in results { 51 | println!("{:#?}", os); 52 | } 53 | 54 | Ok(()) 55 | } 56 | ``` 57 | 58 | ### `chrono` vs `time` 59 | 60 | If you prefer to use the `time` crate instead of the default `chrono`, include `wmi` as 61 | 62 | ```toml 63 | [dependencies] 64 | wmi-rs = { version = "*", default-features = false, features = ["time"] } 65 | ``` 66 | 67 | and use the `WMIOffsetDateTime` wrapper instead of the `WMIDateTime` wrapper. 68 | 69 | ## Async Queries 70 | 71 | WMI supports async queries, with methods 72 | like [ExecAsyncQuery](https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemservices-execqueryasync). 73 | 74 | ```rust 75 | #![allow(non_camel_case_types)] 76 | #![allow(non_snake_case)] 77 | 78 | use serde::Deserialize; 79 | use wmi::{COMLibrary, Variant, WMIConnection, WMIDateTime}; 80 | use std::collections::HashMap; 81 | use futures::executor::block_on; 82 | 83 | fn main() -> Result<(), Box> { 84 | let com_con = COMLibrary::new()?; 85 | let wmi_con = WMIConnection::new(com_con.into())?; 86 | 87 | block_on(exec_async_query(&wmi_con))?; 88 | 89 | Ok(()) 90 | } 91 | 92 | async fn exec_async_query(wmi_con: &WMIConnection) -> Result<(), Box> { 93 | let results: Vec> = 94 | wmi_con.async_raw_query("SELECT * FROM Win32_OperatingSystem").await?; 95 | 96 | for os in results { 97 | println!("{:#?}", os); 98 | } 99 | 100 | #[derive(Deserialize, Debug)] 101 | struct Win32_OperatingSystem { 102 | Caption: String, 103 | Name: String, 104 | CurrentTimeZone: i16, 105 | Debug: bool, 106 | EncryptionLevel: u32, 107 | ForegroundApplicationBoost: u8, 108 | LastBootUpTime: WMIDateTime, 109 | } 110 | 111 | let results: Vec = wmi_con.async_query().await?; 112 | 113 | for os in results { 114 | println!("{:#?}", os); 115 | } 116 | 117 | Ok(()) 118 | } 119 | ``` 120 | 121 | ## License 122 | 123 | The `wmi` crate is licensed under either of 124 | 125 | ```text 126 | Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0) 127 | MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT) 128 | ``` 129 | 130 | at your option. 131 | -------------------------------------------------------------------------------- /src/async_query.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | connection::WMIConnection, 3 | query::{build_query, FilterValue}, 4 | query_sink::{AsyncQueryResultStream, AsyncQueryResultStreamInner, QuerySink}, 5 | result_enumerator::IWbemClassWrapper, 6 | WMIResult, 7 | }; 8 | use futures::stream::{Stream, StreamExt, TryStreamExt}; 9 | use serde::de; 10 | use std::collections::HashMap; 11 | use windows::core::BSTR; 12 | use windows::Win32::System::Wmi::{IWbemObjectSink, WBEM_FLAG_BIDIRECTIONAL}; 13 | 14 | /// 15 | /// ### Additional async methods 16 | /// 17 | impl WMIConnection { 18 | /// Wrapper for the [ExecQueryAsync](https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemservices-execqueryasync) 19 | /// method. Provides safety checks, and returns results 20 | /// as a Stream instead of the original Sink. 21 | /// 22 | pub fn exec_query_async( 23 | &self, 24 | query: impl AsRef, 25 | ) -> WMIResult>> { 26 | let query_language = BSTR::from("WQL"); 27 | let query = BSTR::from(query.as_ref()); 28 | 29 | let stream = AsyncQueryResultStreamInner::new(); 30 | // The internal RefCount has initial value = 1. 31 | let p_sink = QuerySink { 32 | stream: stream.clone(), 33 | }; 34 | let p_sink_handle: IWbemObjectSink = p_sink.into(); 35 | 36 | unsafe { 37 | // As p_sink's RefCount = 1 before this call, 38 | // p_sink won't be dropped at the end of ExecQueryAsync 39 | self.svc.ExecQueryAsync( 40 | &query_language, 41 | &query, 42 | WBEM_FLAG_BIDIRECTIONAL, 43 | &self.ctx.0, 44 | &p_sink_handle, 45 | )?; 46 | } 47 | 48 | Ok(AsyncQueryResultStream::new( 49 | stream, 50 | self.clone(), 51 | p_sink_handle, 52 | )) 53 | } 54 | 55 | /// Async version of [`raw_query`](WMIConnection#method.raw_query) 56 | /// Execute a free-text query and deserialize the results. 57 | /// Can be used either with a struct (like `query` and `filtered_query`), 58 | /// but also with a generic map. 59 | /// 60 | /// ```edition2018 61 | /// # use wmi::*; 62 | /// # use std::collections::HashMap; 63 | /// # use futures::executor::block_on; 64 | /// # fn main() -> WMIResult<()> { 65 | /// # block_on(exec_async_query())?; 66 | /// # Ok(()) 67 | /// # } 68 | /// # 69 | /// # async fn exec_async_query() -> WMIResult<()> { 70 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 71 | /// use futures::stream::TryStreamExt; 72 | /// let results: Vec> = con.async_raw_query("SELECT Name FROM Win32_OperatingSystem").await?; 73 | /// # Ok(()) 74 | /// # } 75 | /// ``` 76 | pub async fn async_raw_query(&self, query: impl AsRef) -> WMIResult> 77 | where 78 | T: de::DeserializeOwned, 79 | { 80 | self.exec_query_async(query)? 81 | .map(|item| match item { 82 | Ok(wbem_class_obj) => wbem_class_obj.into_desr(), 83 | Err(e) => Err(e), 84 | }) 85 | .try_collect::>() 86 | .await 87 | } 88 | 89 | /// Query all the objects of type T. 90 | /// 91 | /// ```edition2018 92 | /// # use wmi::*; 93 | /// # use std::collections::HashMap; 94 | /// # use futures::executor::block_on; 95 | /// # fn main() -> WMIResult<()> { 96 | /// # block_on(exec_async_query())?; 97 | /// # Ok(()) 98 | /// # } 99 | /// # 100 | /// # async fn exec_async_query() -> WMIResult<()> { 101 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 102 | /// use serde::Deserialize; 103 | /// #[derive(Deserialize, Debug)] 104 | /// struct Win32_Process { 105 | /// Name: String, 106 | /// } 107 | /// 108 | /// let procs: Vec = con.async_query().await?; 109 | /// # Ok(()) 110 | /// # } 111 | /// ``` 112 | pub async fn async_query(&self) -> WMIResult> 113 | where 114 | T: de::DeserializeOwned, 115 | { 116 | let query_text = build_query::(None)?; 117 | 118 | self.async_raw_query(&query_text).await 119 | } 120 | 121 | /// Query all the objects of type T, while filtering according to `filters`. 122 | /// 123 | pub async fn async_filtered_query( 124 | &self, 125 | filters: &HashMap, 126 | ) -> WMIResult> 127 | where 128 | T: de::DeserializeOwned, 129 | { 130 | let query_text = build_query::(Some(filters))?; 131 | 132 | self.async_raw_query(&query_text).await 133 | } 134 | } 135 | 136 | #[allow(non_snake_case)] 137 | #[allow(non_camel_case_types)] 138 | #[cfg(test)] 139 | mod tests { 140 | use crate::{tests::fixtures::*, Variant}; 141 | use futures::stream::{self, StreamExt}; 142 | use serde::Deserialize; 143 | use std::collections::HashMap; 144 | 145 | #[async_std::test] 146 | async fn async_it_works_async() { 147 | let wmi_con = wmi_con(); 148 | 149 | let result = wmi_con 150 | .exec_query_async("SELECT OSArchitecture FROM Win32_OperatingSystem") 151 | .unwrap() 152 | .collect::>() 153 | .await; 154 | 155 | assert_eq!(result.len(), 1); 156 | } 157 | 158 | #[tokio::test] 159 | async fn async_it_works_async_tokio() { 160 | let wmi_con = wmi_con(); 161 | 162 | let result = wmi_con 163 | .exec_query_async("SELECT OSArchitecture FROM Win32_OperatingSystem") 164 | .unwrap() 165 | .collect::>() 166 | .await; 167 | 168 | assert_eq!(result.len(), 1); 169 | } 170 | 171 | #[async_std::test] 172 | async fn async_it_handles_invalid_query() { 173 | let wmi_con = wmi_con(); 174 | 175 | let result = wmi_con 176 | .exec_query_async("invalid query") 177 | .unwrap() 178 | .collect::>() 179 | .await; 180 | 181 | assert_eq!(result.len(), 0); 182 | } 183 | 184 | #[async_std::test] 185 | async fn async_it_provides_raw_query_result() { 186 | let wmi_con = wmi_con(); 187 | 188 | let results: Vec> = wmi_con 189 | .async_raw_query("SELECT * FROM Win32_GroupUser") 190 | .await 191 | .unwrap(); 192 | 193 | for res in results { 194 | match res.get("GroupComponent") { 195 | Some(Variant::String(s)) => assert_ne!(s, ""), 196 | _ => assert!(false), 197 | } 198 | 199 | match res.get("PartComponent") { 200 | Some(Variant::String(s)) => assert_ne!(s, ""), 201 | _ => assert!(false), 202 | } 203 | } 204 | } 205 | 206 | #[tokio::test] 207 | async fn async_it_works_async_tokio_concurrent() { 208 | let wmi_con = wmi_con(); 209 | 210 | // We want to actually consume a bunch of data from WMI. 211 | #[allow(unused)] 212 | #[derive(Deserialize, Debug)] 213 | struct Win32_OperatingSystem { 214 | Name: String, 215 | SerialNumber: String, 216 | OSArchitecture: String, 217 | BootDevice: String, 218 | MUILanguages: Vec, 219 | } 220 | 221 | // Using buffer_unordered(1) will take 2 seconds instead of 0.2 seconds. 222 | let results: Vec = stream::iter(0..150) 223 | .map(|_| async { 224 | let result: Vec = wmi_con.async_query().await.unwrap(); 225 | 226 | result.into_iter().next().unwrap() 227 | }) 228 | .buffer_unordered(50) 229 | .collect() 230 | .await; 231 | 232 | assert_eq!(results.len(), 150); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use criterion::{criterion_group, Criterion}; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::HashMap; 6 | use wmi::{COMLibrary, Variant, WMIConnection}; 7 | 8 | #[derive(Serialize, Deserialize, Debug)] 9 | #[serde(rename = "Win32_Account")] 10 | pub struct Account { 11 | pub __Path: String, 12 | } 13 | 14 | #[derive(Serialize, Deserialize, Debug)] 15 | #[serde(rename = "Win32_UserAccount")] 16 | pub struct UserAccount { 17 | pub __Path: String, 18 | pub AccountType: i64, 19 | pub Caption: String, 20 | pub Description: String, 21 | pub Disabled: bool, 22 | pub Domain: String, 23 | pub FullName: String, 24 | pub LocalAccount: bool, 25 | pub Lockout: bool, 26 | pub Name: String, 27 | pub PasswordChangeable: bool, 28 | pub PasswordExpires: bool, 29 | pub PasswordRequired: bool, 30 | pub SID: String, 31 | pub SIDType: u64, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Debug)] 35 | #[serde(rename = "Win32_Group")] 36 | pub struct Group { 37 | pub __Path: String, 38 | pub Name: String, 39 | } 40 | 41 | #[derive(Serialize, Deserialize, Debug)] 42 | #[serde(rename = "Win32_GroupUser")] 43 | pub struct GroupUser {} 44 | 45 | #[derive(Serialize, Deserialize, Debug)] 46 | #[serde(rename = "Win32_Process")] 47 | pub struct Process { 48 | pub Caption: String, 49 | } 50 | 51 | #[derive(Serialize, Deserialize, Debug)] 52 | #[serde(rename = "CIM_ProcessExecutable")] 53 | pub struct ProcessExecutable { 54 | pub Antecedent: String, 55 | pub Dependent: String, 56 | pub BaseAddress: u64, 57 | } 58 | 59 | #[derive(Serialize, Deserialize, Debug)] 60 | #[serde(rename = "Win32_Service")] 61 | pub struct Service { 62 | pub Name: String, 63 | pub DisplayName: String, 64 | pub PathName: Option, 65 | pub State: String, 66 | pub Started: bool, 67 | } 68 | 69 | fn get_accounts(con: &WMIConnection) { 70 | let _accounts: Vec = con.query().unwrap(); 71 | } 72 | 73 | fn get_user_accounts(con: &WMIConnection) { 74 | let _users: Vec = con.query().unwrap(); 75 | } 76 | 77 | fn get_user_accounts_hash_map(con: &WMIConnection) { 78 | let _users: Vec> = 79 | con.raw_query("SELECT * FROM Win32_UserAccount").unwrap(); 80 | } 81 | 82 | fn get_minimal_procs(con: &WMIConnection) { 83 | let _procs: Vec = con.query().unwrap(); 84 | } 85 | 86 | fn get_procs_hash_map(con: &WMIConnection) { 87 | let _procs: Vec> = 88 | con.raw_query("SELECT * FROM Win32_Process").unwrap(); 89 | } 90 | 91 | pub fn get_users_with_groups(con: &WMIConnection) { 92 | let mut filters = HashMap::new(); 93 | 94 | filters.insert("Name".to_string(), "Administrators".into()); 95 | 96 | let group: Group = con.filtered_query(&filters).unwrap().pop().unwrap(); 97 | let _accounts: Vec = con 98 | .associators::(&group.__Path) 99 | .unwrap(); 100 | } 101 | 102 | pub fn get_modules(con: &WMIConnection) { 103 | let _execs: Vec = con.query().unwrap(); 104 | } 105 | 106 | fn get_services(con: &WMIConnection) { 107 | let _services: Vec = con.query().unwrap(); 108 | } 109 | 110 | fn criterion_benchmark(c: &mut Criterion) { 111 | let com = COMLibrary::new().unwrap(); 112 | 113 | // baseline: 41ms 114 | c.bench_function("get_accounts", |b| { 115 | let wmi_con = WMIConnection::new(com).unwrap(); 116 | b.iter(|| get_accounts(&wmi_con)) 117 | }); 118 | 119 | // baseline: 13ms 120 | c.bench_function("get_user_accounts", |b| { 121 | let wmi_con = WMIConnection::new(com).unwrap(); 122 | b.iter(|| get_user_accounts(&wmi_con)) 123 | }); 124 | 125 | // baseline: 9ms 126 | c.bench_function("get_user_accounts_hash_map", |b| { 127 | let wmi_con = WMIConnection::new(com).unwrap(); 128 | b.iter(|| get_user_accounts_hash_map(&wmi_con)) 129 | }); 130 | 131 | // baseline: 60ms 132 | c.bench_function("get_minimal_procs", |b| { 133 | let wmi_con = WMIConnection::new(com).unwrap(); 134 | b.iter(|| get_minimal_procs(&wmi_con)) 135 | }); 136 | 137 | // baseline: 68ms 138 | c.bench_function("get_procs_hash_map", |b| { 139 | let wmi_con = WMIConnection::new(com).unwrap(); 140 | b.iter(|| get_procs_hash_map(&wmi_con)) 141 | }); 142 | 143 | // baseline: 9s (**seconds**) 144 | // after adding AssocClass: 73ms 145 | c.bench_function("get_users_with_groups", |b| { 146 | let wmi_con = WMIConnection::new(com).unwrap(); 147 | b.iter(|| get_users_with_groups(&wmi_con)) 148 | }); 149 | 150 | // baseline: 625ms. 151 | c.bench_function("get_modules", |b| { 152 | let wmi_con = WMIConnection::new(com).unwrap(); 153 | b.iter(|| get_modules(&wmi_con)) 154 | }); 155 | 156 | // baseline: 300ms. 157 | c.bench_function("get_services", |b| { 158 | let wmi_con = WMIConnection::new(com).unwrap(); 159 | b.iter(|| get_services(&wmi_con)) 160 | }); 161 | } 162 | 163 | criterion_group!(benches, criterion_benchmark); 164 | fn main() { 165 | let mut c = Criterion::default().configure_from_args(); 166 | 167 | // Uncomment when testing changes. 168 | // let mut c = c.sample_size(3); 169 | 170 | criterion_benchmark(&mut c); 171 | 172 | c.final_summary(); 173 | } 174 | -------------------------------------------------------------------------------- /src/bin/wmiq.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, env::args}; 2 | use wmi::{COMLibrary, Variant, WMIConnection, WMIResult}; 3 | 4 | fn main() -> WMIResult<()> { 5 | let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 6 | let args: Vec = args().collect(); 7 | let query = match args.get(1) { 8 | None => { 9 | println!("Expected an argument with a WMI query"); 10 | return Ok(()); 11 | } 12 | Some(query) => query, 13 | }; 14 | 15 | let results: Vec> = match wmi_con.raw_query(query) { 16 | Err(e) => { 17 | println!("Couldn't run query {} because of {:?}", query, e); 18 | return Ok(()); 19 | } 20 | Ok(results) => results, 21 | }; 22 | 23 | for (i, res) in results.iter().enumerate() { 24 | println!("Result {}", i); 25 | println!("{:#?}", res); 26 | } 27 | 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::context::WMIContext; 2 | use crate::utils::WMIResult; 3 | use crate::WMIError; 4 | use log::debug; 5 | use std::marker::PhantomData; 6 | use windows::core::BSTR; 7 | use windows::Win32::Foundation::RPC_E_TOO_LATE; 8 | use windows::Win32::System::Com::{ 9 | CoCreateInstance, CoSetProxyBlanket, CLSCTX_INPROC_SERVER, RPC_C_AUTHN_LEVEL_CALL, 10 | }; 11 | use windows::Win32::System::Com::{ 12 | CoInitializeEx, CoInitializeSecurity, COINIT_MULTITHREADED, EOAC_NONE, 13 | RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, 14 | }; 15 | use windows::Win32::System::Rpc::{RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE}; 16 | use windows::Win32::System::Wmi::{ 17 | IWbemContext, IWbemLocator, IWbemServices, WbemLocator, WBEM_FLAG_CONNECT_USE_MAX_WAIT, 18 | }; 19 | /// A marker to indicate that the current thread was `CoInitialize`d. 20 | /// 21 | /// # Note 22 | /// 23 | /// `COMLibrary` should be treated as a singleton per thread: 24 | /// 25 | /// ```edition2018 26 | /// # use wmi::*; 27 | /// thread_local! { 28 | /// static COM_LIB: COMLibrary = COMLibrary::new().unwrap(); 29 | /// } 30 | /// 31 | /// pub fn wmi_con() -> WMIConnection { 32 | /// let com_lib = COM_LIB.with(|com| *com); 33 | /// WMIConnection::new(com_lib).unwrap() 34 | /// } 35 | /// ``` 36 | #[derive(Clone, Copy, Debug)] 37 | pub struct COMLibrary { 38 | // Force the type to be `!Send`, as each thread must be initialized separately. 39 | _phantom: PhantomData<*mut ()>, 40 | } 41 | 42 | /// Initialize COM. 43 | /// 44 | /// `CoUninitialize` will NOT be called when dropped. 45 | /// See: . 46 | /// 47 | impl COMLibrary { 48 | /// `CoInitialize`s the COM library for use by the calling thread. 49 | /// 50 | pub fn new() -> WMIResult { 51 | let instance = Self::without_security()?; 52 | 53 | match instance.init_security() { 54 | Ok(()) => {} 55 | // Security was already initialized, this is fine 56 | Err(WMIError::HResultError { hres }) if hres == RPC_E_TOO_LATE.0 => {} 57 | Err(err) => return Err(err), 58 | } 59 | 60 | Ok(instance) 61 | } 62 | 63 | /// `CoInitialize`s the COM library for use by the calling thread, but without setting the security context. 64 | /// 65 | pub fn without_security() -> WMIResult { 66 | unsafe { CoInitializeEx(None, COINIT_MULTITHREADED).ok()? } 67 | 68 | let instance = Self { 69 | _phantom: PhantomData, 70 | }; 71 | 72 | Ok(instance) 73 | } 74 | 75 | /// Assumes that COM was already initialized for this thread. 76 | /// 77 | /// # Safety 78 | /// 79 | /// This function is unsafe as it is the caller's responsibility to ensure that COM is initialized 80 | /// and will not be uninitialized while any instance of object is in scope. 81 | /// 82 | /// ```edition2018 83 | /// # fn main() -> wmi::WMIResult<()> { 84 | /// # use wmi::*; 85 | /// # use serde::Deserialize; 86 | /// # let _actual_com = COMLibrary::new()?; 87 | /// let initialized_com = unsafe { COMLibrary::assume_initialized() }; 88 | /// 89 | /// // Later, in the same thread. 90 | /// let wmi_con = WMIConnection::with_namespace_path("ROOT\\CIMV2", initialized_com)?; 91 | /// # Ok(()) 92 | /// # } 93 | /// ``` 94 | pub unsafe fn assume_initialized() -> Self { 95 | Self { 96 | _phantom: PhantomData, 97 | } 98 | } 99 | 100 | fn init_security(&self) -> WMIResult<()> { 101 | unsafe { 102 | CoInitializeSecurity( 103 | None, 104 | -1, // let COM choose. 105 | None, 106 | None, 107 | RPC_C_AUTHN_LEVEL_DEFAULT, 108 | RPC_C_IMP_LEVEL_IMPERSONATE, 109 | None, 110 | EOAC_NONE, 111 | None, 112 | )?; 113 | }; 114 | 115 | Ok(()) 116 | } 117 | } 118 | 119 | /// ```compile_fail 120 | /// let com = COMLibrary::new().unwrap(); 121 | /// _test_com_lib_not_send(com); 122 | /// ``` 123 | fn _test_com_lib_not_send(_s: impl Send) {} 124 | 125 | /// A connection to the local WMI provider. 126 | /// 127 | #[derive(Clone, Debug)] 128 | pub struct WMIConnection { 129 | _com_con: COMLibrary, 130 | pub svc: IWbemServices, 131 | pub(crate) ctx: WMIContext, 132 | } 133 | 134 | impl WMIConnection { 135 | /// Creates a connection with a default `CIMV2` namespace path. 136 | pub fn new(com_lib: COMLibrary) -> WMIResult { 137 | Self::with_namespace_path("ROOT\\CIMV2", com_lib) 138 | } 139 | 140 | /// Creates a connection with the given namespace path. 141 | /// 142 | /// ```edition2018 143 | /// # fn main() -> wmi::WMIResult<()> { 144 | /// # use wmi::*; 145 | /// # use serde::Deserialize; 146 | /// let wmi_con = WMIConnection::with_namespace_path("ROOT\\Microsoft\\Windows\\Storage", COMLibrary::new()?)?; 147 | /// # Ok(()) 148 | /// # } 149 | /// ``` 150 | pub fn with_namespace_path(namespace_path: &str, com_lib: COMLibrary) -> WMIResult { 151 | let loc = create_locator()?; 152 | let ctx = WMIContext::new()?; 153 | let svc = create_services(&loc, namespace_path, None, None, None, &ctx.0)?; 154 | 155 | let this = Self { 156 | _com_con: com_lib, 157 | svc, 158 | ctx, 159 | }; 160 | 161 | this.set_proxy()?; 162 | Ok(this) 163 | } 164 | 165 | fn set_proxy(&self) -> WMIResult<()> { 166 | debug!("Calling CoSetProxyBlanket"); 167 | 168 | unsafe { 169 | CoSetProxyBlanket( 170 | &self.svc, 171 | RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx 172 | RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx 173 | None, 174 | RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx 175 | RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx 176 | None, // client identity 177 | EOAC_NONE, // proxy capabilities 178 | )?; 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | /// Creates a connection to a remote computer with a default `CIMV2` namespace path. 185 | /// https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemlocator-connectserver 186 | /// 187 | /// # Example 188 | /// ```no_run 189 | /// # use wmi::*; 190 | /// # fn main() -> WMIResult<()> { 191 | /// let com_lib = COMLibrary::new()?; 192 | /// let wmi_con = WMIConnection::with_credentials( 193 | /// "ServerName", // Server name or IP address 194 | /// Some("username"), 195 | /// Some("password"), 196 | /// Some("domain"), 197 | /// com_lib 198 | /// )?; 199 | /// # Ok(()) 200 | /// # } 201 | /// ``` 202 | pub fn with_credentials( 203 | server: &str, 204 | username: Option<&str>, 205 | password: Option<&str>, 206 | domain: Option<&str>, 207 | com_lib: COMLibrary, 208 | ) -> WMIResult { 209 | Self::with_credentials_and_namespace( 210 | server, 211 | "ROOT\\CIMV2", 212 | username, 213 | password, 214 | domain, 215 | com_lib, 216 | ) 217 | } 218 | 219 | /// Creates a connection to a remote computer with the given namespace path and credentials. 220 | /// 221 | /// # Example 222 | /// ```no_run 223 | /// # use wmi::*; 224 | /// # fn main() -> WMIResult<()> { 225 | /// let com_lib = COMLibrary::new()?; 226 | /// let wmi_con = WMIConnection::with_credentials_and_namespace( 227 | /// "ServerName", // Server name or IP address 228 | /// "ROOT\\CIMV2", // Namespace path 229 | /// Some("username"), 230 | /// Some("password"), 231 | /// Some("domain"), 232 | /// com_lib 233 | /// )?; 234 | /// # Ok(()) 235 | /// # } 236 | /// ``` 237 | pub fn with_credentials_and_namespace( 238 | server: &str, 239 | namespace_path: &str, 240 | username: Option<&str>, 241 | password: Option<&str>, 242 | domain: Option<&str>, 243 | com_lib: COMLibrary, 244 | ) -> WMIResult { 245 | let loc = create_locator()?; 246 | 247 | // Build the full namespace path for remote connection 248 | let full_namespace = &format!(r"\\{}\{}", server, namespace_path); 249 | 250 | let ctx = WMIContext::new()?; 251 | let svc = create_services(&loc, full_namespace, username, password, domain, &ctx.0)?; 252 | 253 | let this = Self { 254 | _com_con: com_lib, 255 | svc, 256 | ctx, 257 | }; 258 | 259 | this.set_proxy_for_remote()?; 260 | Ok(this) 261 | } 262 | 263 | // Additional authentication for remote WMI connections 264 | fn set_proxy_for_remote(&self) -> WMIResult<()> { 265 | debug!("Calling CoSetProxyBlanket for remote connection"); 266 | 267 | unsafe { 268 | CoSetProxyBlanket( 269 | &self.svc, 270 | RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx 271 | RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx 272 | None, // Server principal name 273 | RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // Stronger authentication level for remote 274 | RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation level 275 | None, // Client identity 276 | EOAC_NONE, // Capability flags 277 | )?; 278 | } 279 | 280 | Ok(()) 281 | } 282 | } 283 | 284 | fn create_locator() -> WMIResult { 285 | debug!("Calling CoCreateInstance for CLSID_WbemLocator"); 286 | 287 | let loc = unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER)? }; 288 | 289 | debug!("Got locator {:?}", loc); 290 | 291 | Ok(loc) 292 | } 293 | 294 | fn create_services( 295 | loc: &IWbemLocator, 296 | namespace_path: &str, 297 | username: Option<&str>, 298 | password: Option<&str>, 299 | authority: Option<&str>, 300 | ctx: &IWbemContext, 301 | ) -> WMIResult { 302 | let namespace_path = BSTR::from(namespace_path); 303 | let user = BSTR::from(username.unwrap_or_default()); 304 | let password = BSTR::from(password.unwrap_or_default()); 305 | let authority = BSTR::from(authority.unwrap_or_default()); 306 | 307 | let svc = unsafe { 308 | loc.ConnectServer( 309 | &namespace_path, 310 | &user, 311 | &password, 312 | &BSTR::new(), 313 | WBEM_FLAG_CONNECT_USE_MAX_WAIT.0, 314 | &authority, 315 | ctx, 316 | )? 317 | }; 318 | 319 | Ok(svc) 320 | } 321 | 322 | #[allow(non_snake_case)] 323 | #[allow(non_camel_case_types)] 324 | #[cfg(test)] 325 | mod tests { 326 | use super::*; 327 | 328 | #[test] 329 | fn it_can_create_multiple_connections() { 330 | { 331 | let com_lib = COMLibrary::new().unwrap(); 332 | let _ = WMIConnection::new(com_lib); 333 | } 334 | { 335 | let com_lib = COMLibrary::new().unwrap(); 336 | let _ = WMIConnection::new(com_lib); 337 | } 338 | } 339 | 340 | #[test] 341 | fn it_can_connect_to_localhost_without_credentials() { 342 | let com_lib = COMLibrary::new().unwrap(); 343 | 344 | // Connect to localhost with empty credentials 345 | let result = WMIConnection::with_credentials("localhost", None, None, None, com_lib); 346 | 347 | // The connection should succeed 348 | assert!( 349 | result.is_ok(), 350 | "Failed to connect to localhost without credentials: {:?}", 351 | result.err() 352 | ); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{WMIConnection, WMIResult}; 2 | use log::debug; 3 | use windows::core::BSTR; 4 | use windows::Win32::System::Variant::VARIANT; 5 | use windows::Win32::System::{ 6 | Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}, 7 | Wmi::{IWbemContext, WbemContext}, 8 | }; 9 | 10 | #[derive(Debug, Clone)] 11 | #[non_exhaustive] 12 | pub enum ContextValueType { 13 | String(String), 14 | I4(i32), 15 | R8(f64), 16 | Bool(bool), 17 | } 18 | 19 | impl From for VARIANT { 20 | fn from(value: ContextValueType) -> Self { 21 | match value { 22 | ContextValueType::Bool(b) => Self::from(b), 23 | ContextValueType::I4(i4) => Self::from(i4), 24 | ContextValueType::R8(r8) => Self::from(r8), 25 | ContextValueType::String(str) => Self::from(BSTR::from(str)), 26 | } 27 | } 28 | } 29 | 30 | /// Provides access to WMI's context, available via [`WMIConnection::ctx`]. 31 | #[derive(Clone, Debug)] 32 | pub struct WMIContext(pub(crate) IWbemContext); 33 | 34 | impl WMIContext { 35 | /// Creates a new instances of [`WMIContext`] 36 | pub(crate) fn new() -> WMIResult { 37 | debug!("Calling CoCreateInstance for CLSID_WbemContext"); 38 | 39 | let ctx = unsafe { CoCreateInstance(&WbemContext, None, CLSCTX_INPROC_SERVER)? }; 40 | 41 | debug!("Got context {:?}", ctx); 42 | 43 | Ok(WMIContext(ctx)) 44 | } 45 | 46 | /// Sets the specified named context value for use in providing additional context information to queries. 47 | /// 48 | /// Note the context values will persist across subsequent queries until [`WMIContext::delete_all`] is called. 49 | pub fn set_value(&mut self, key: &str, value: impl Into) -> WMIResult<()> { 50 | let value = value.into(); 51 | unsafe { self.0.SetValue(&BSTR::from(key), 0, &value.into())? }; 52 | 53 | Ok(()) 54 | } 55 | 56 | /// Clears all named values from the underlying context object. 57 | pub fn delete_all(&mut self) -> WMIResult<()> { 58 | unsafe { self.0.DeleteAll()? }; 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl WMIConnection { 65 | /// Returns a mutable reference to the [`WMIContext`] object 66 | pub fn ctx(&mut self) -> &mut WMIContext { 67 | &mut self.ctx 68 | } 69 | } 70 | 71 | macro_rules! impl_from_type { 72 | ($target_type:ty, $variant:ident) => { 73 | impl From<$target_type> for ContextValueType { 74 | fn from(value: $target_type) -> Self { 75 | Self::$variant(value.into()) 76 | } 77 | } 78 | }; 79 | } 80 | 81 | impl_from_type!(&str, String); 82 | impl_from_type!(i32, I4); 83 | impl_from_type!(f64, R8); 84 | impl_from_type!(bool, Bool); 85 | 86 | #[allow(non_snake_case)] 87 | #[allow(non_camel_case_types)] 88 | #[allow(dead_code)] 89 | #[cfg(test)] 90 | mod tests { 91 | use super::*; 92 | use crate::COMLibrary; 93 | use serde::Deserialize; 94 | 95 | #[test] 96 | fn verify_ctx_values_used() { 97 | let com_con = COMLibrary::new().unwrap(); 98 | let mut wmi_con = 99 | WMIConnection::with_namespace_path("ROOT\\StandardCimv2", com_con).unwrap(); 100 | 101 | #[derive(Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)] 102 | struct MSFT_NetAdapter { 103 | InterfaceName: String, 104 | } 105 | 106 | let mut orig_adapters = wmi_con.query::().unwrap(); 107 | assert!(!orig_adapters.is_empty()); 108 | 109 | // With 'IncludeHidden' set to 'true', expect the response to contain additional adapters 110 | wmi_con.ctx().set_value("IncludeHidden", true).unwrap(); 111 | let all_adapters = wmi_con.query::().unwrap(); 112 | assert!(all_adapters.len() > orig_adapters.len()); 113 | 114 | wmi_con.ctx().delete_all().unwrap(); 115 | let mut adapters = wmi_con.query::().unwrap(); 116 | adapters.sort(); 117 | orig_adapters.sort(); 118 | assert_eq!(adapters, orig_adapters); 119 | } 120 | 121 | #[tokio::test] 122 | async fn async_verify_ctx_values_used() { 123 | let com_con = COMLibrary::new().unwrap(); 124 | let mut wmi_con = 125 | WMIConnection::with_namespace_path("ROOT\\StandardCimv2", com_con).unwrap(); 126 | 127 | #[derive(Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)] 128 | struct MSFT_NetAdapter { 129 | InterfaceName: String, 130 | } 131 | 132 | let mut orig_adapters = wmi_con.async_query::().await.unwrap(); 133 | assert!(!orig_adapters.is_empty()); 134 | 135 | // With 'IncludeHidden' set to 'true', expect the response to contain additional adapters 136 | wmi_con.ctx().set_value("IncludeHidden", true).unwrap(); 137 | let all_adapters = wmi_con.async_query::().await.unwrap(); 138 | assert!(all_adapters.len() > orig_adapters.len()); 139 | 140 | wmi_con.ctx().delete_all().unwrap(); 141 | let mut adapters = wmi_con.async_query::().await.unwrap(); 142 | adapters.sort(); 143 | orig_adapters.sort(); 144 | assert_eq!(adapters, orig_adapters); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/datetime.rs: -------------------------------------------------------------------------------- 1 | use crate::WMIError; 2 | use chrono::prelude::*; 3 | use serde::{de, ser}; 4 | use std::{fmt, str::FromStr}; 5 | 6 | /// A wrapper type around `chrono`'s `DateTime` (if the `chrono` feature is active. ), which supports parsing from WMI-format strings. 7 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 8 | pub struct WMIDateTime(pub DateTime); 9 | 10 | impl FromStr for WMIDateTime { 11 | type Err = WMIError; 12 | 13 | fn from_str(s: &str) -> Result { 14 | if s.len() < 21 { 15 | return Err(WMIError::ConvertDatetimeError(s.into())); 16 | } 17 | 18 | let (datetime_part, tz_part) = s.split_at(21); 19 | let tz_min: i32 = tz_part.parse()?; 20 | let tz = FixedOffset::east_opt(tz_min * 60).unwrap(); 21 | let dt = NaiveDateTime::parse_from_str(datetime_part, "%Y%m%d%H%M%S.%f")? 22 | .and_local_timezone(tz) 23 | .single() 24 | .ok_or(WMIError::ParseDatetimeLocalError)?; 25 | 26 | Ok(Self(dt)) 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | struct DateTimeVisitor; 32 | 33 | impl<'de> de::Visitor<'de> for DateTimeVisitor { 34 | type Value = WMIDateTime; 35 | 36 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 37 | write!(formatter, "a timestamp in WMI format") 38 | } 39 | 40 | fn visit_str(self, value: &str) -> Result 41 | where 42 | E: de::Error, 43 | { 44 | value.parse().map_err(|err| E::custom(format!("{}", err))) 45 | } 46 | } 47 | 48 | impl<'de> de::Deserialize<'de> for WMIDateTime { 49 | fn deserialize(deserializer: D) -> Result 50 | where 51 | D: de::Deserializer<'de>, 52 | { 53 | deserializer.deserialize_str(DateTimeVisitor) 54 | } 55 | } 56 | 57 | impl ser::Serialize for WMIDateTime { 58 | fn serialize(&self, serializer: S) -> Result 59 | where 60 | S: ser::Serializer, 61 | { 62 | let formatted = self.0.to_rfc3339(); 63 | 64 | serializer.serialize_str(&formatted) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::WMIDateTime; 71 | use serde_json; 72 | 73 | #[test] 74 | fn it_works_with_negative_offset() { 75 | let dt: WMIDateTime = "20190113200517.500000-180".parse().unwrap(); 76 | 77 | let formatted = dt.0.to_rfc3339(); 78 | 79 | assert_eq!(formatted, "2019-01-13T20:05:17.000500-03:00"); 80 | } 81 | 82 | #[test] 83 | fn it_works_with_positive_offset() { 84 | let dt: WMIDateTime = "20190113200517.500000+060".parse().unwrap(); 85 | 86 | let formatted = dt.0.to_rfc3339(); 87 | 88 | assert_eq!(formatted, "2019-01-13T20:05:17.000500+01:00"); 89 | } 90 | 91 | #[test] 92 | fn it_fails_with_malformed_str() { 93 | let dt_res: Result = "20190113200517".parse(); 94 | 95 | assert!(dt_res.is_err()); 96 | } 97 | 98 | #[test] 99 | fn it_fails_with_malformed_str_with_no_tz() { 100 | let dt_res: Result = "20190113200517.000500".parse(); 101 | 102 | assert!(dt_res.is_err()); 103 | } 104 | 105 | #[test] 106 | fn it_serializes_to_rfc() { 107 | let dt: WMIDateTime = "20190113200517.500000+060".parse().unwrap(); 108 | 109 | let v = serde_json::to_string(&dt).unwrap(); 110 | assert_eq!(v, "\"2019-01-13T20:05:17.000500+01:00\""); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/datetime_time.rs: -------------------------------------------------------------------------------- 1 | use crate::WMIError; 2 | use serde::{de, ser}; 3 | use std::{fmt, str::FromStr}; 4 | use time::{ 5 | format_description::FormatItem, macros::format_description, parsing::Parsed, PrimitiveDateTime, 6 | UtcOffset, 7 | }; 8 | 9 | /// A wrapper type around `time`'s `OffsetDateTime` (if the 10 | // `time` feature is active), which supports parsing from WMI-format strings. 11 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 12 | pub struct WMIOffsetDateTime(pub time::OffsetDateTime); 13 | 14 | impl FromStr for WMIOffsetDateTime { 15 | type Err = WMIError; 16 | 17 | fn from_str(s: &str) -> Result { 18 | if s.len() < 21 { 19 | return Err(WMIError::ConvertDatetimeError(s.into())); 20 | } 21 | 22 | // We have to ignore the year here, see bottom of https://time-rs.github.io/book/api/format-description.html 23 | // about the large-dates feature (permanent link: 24 | // https://github.com/time-rs/book/blob/0476c5bb35b512ac0cbda5c6cd5f0d0628b0269e/src/api/format-description.md?plain=1#L205) 25 | const TIME_FORMAT: &[FormatItem<'static>] = 26 | format_description!("[month][day][hour][minute][second].[subsecond digits:6]"); 27 | 28 | let minutes_offset = s[21..].parse::()?; 29 | let offset = 30 | UtcOffset::from_whole_seconds(minutes_offset * 60).map_err(time::Error::from)?; 31 | 32 | let mut parser = Parsed::new(); 33 | 34 | let naive_date_time = &s[4..21]; 35 | parser 36 | .parse_items(naive_date_time.as_bytes(), TIME_FORMAT) 37 | .map_err(time::Error::from)?; 38 | // Microsoft thinks it is okay to return a subsecond value in microseconds but not put the zeros before it 39 | // so 1.1 is 1 second and 100 microsecond, ergo 1.000100 ... 40 | parser 41 | .set_subsecond(parser.subsecond().unwrap_or(0) / 1000) 42 | .ok_or_else(|| time::error::Format::InvalidComponent("subsecond")) 43 | .map_err(time::Error::from)?; 44 | 45 | let naive_year = s[..4].parse::()?; 46 | parser 47 | .set_year(naive_year) 48 | .ok_or_else(|| time::error::Format::InvalidComponent("year")) 49 | .map_err(time::Error::from)?; 50 | 51 | let naive_date_time: PrimitiveDateTime = 52 | std::convert::TryInto::try_into(parser).map_err(time::Error::from)?; 53 | let dt = naive_date_time.assume_offset(offset); 54 | Ok(Self(dt)) 55 | } 56 | } 57 | 58 | #[derive(Debug, Clone)] 59 | struct DateTimeVisitor; 60 | 61 | impl<'de> de::Visitor<'de> for DateTimeVisitor { 62 | type Value = WMIOffsetDateTime; 63 | 64 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 65 | write!(formatter, "a timestamp in WMI format") 66 | } 67 | 68 | fn visit_str(self, value: &str) -> Result 69 | where 70 | E: de::Error, 71 | { 72 | value.parse().map_err(|err| E::custom(format!("{}", err))) 73 | } 74 | } 75 | 76 | impl<'de> de::Deserialize<'de> for WMIOffsetDateTime { 77 | fn deserialize(deserializer: D) -> Result 78 | where 79 | D: de::Deserializer<'de>, 80 | { 81 | deserializer.deserialize_str(DateTimeVisitor) 82 | } 83 | } 84 | 85 | const RFC3339_WITH_6_DIGITS: &[FormatItem<'_>] =format_description!( 86 | "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6][offset_hour sign:mandatory]:[offset_minute]" 87 | ); 88 | 89 | impl ser::Serialize for WMIOffsetDateTime { 90 | fn serialize(&self, serializer: S) -> Result 91 | where 92 | S: ser::Serializer, 93 | { 94 | // Unwrap: we passed a well known format, if it fails something has gone very wrong 95 | let formatted = self.0.format(RFC3339_WITH_6_DIGITS).unwrap(); 96 | 97 | serializer.serialize_str(&formatted) 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use super::WMIOffsetDateTime; 104 | use serde_json; 105 | 106 | #[test] 107 | fn it_works_with_negative_offset() { 108 | let dt: WMIOffsetDateTime = "20190113200517.500000-180".parse().unwrap(); 109 | 110 | let formatted = dt.0.format(super::RFC3339_WITH_6_DIGITS).unwrap(); 111 | 112 | assert_eq!(formatted, "2019-01-13T20:05:17.000500-03:00"); 113 | } 114 | 115 | #[test] 116 | fn it_works_with_positive_offset() { 117 | let dt: WMIOffsetDateTime = "20190113200517.500000+060".parse().unwrap(); 118 | 119 | let formatted = dt.0.format(super::RFC3339_WITH_6_DIGITS).unwrap(); 120 | 121 | assert_eq!(formatted, "2019-01-13T20:05:17.000500+01:00"); 122 | } 123 | 124 | #[test] 125 | fn it_fails_with_malformed_str() { 126 | let dt_res: Result = "20190113200517".parse(); 127 | 128 | assert!(dt_res.is_err()); 129 | } 130 | 131 | #[test] 132 | fn it_fails_with_malformed_str_with_no_tz() { 133 | let dt_res: Result = "20190113200517.000500".parse(); 134 | 135 | assert!(dt_res.is_err()); 136 | } 137 | 138 | #[test] 139 | fn it_serializes_to_rfc() { 140 | let dt: WMIOffsetDateTime = "20190113200517.500000+060".parse().unwrap(); 141 | 142 | let v = serde_json::to_string(&dt).unwrap(); 143 | assert_eq!(v, "\"2019-01-13T20:05:17.000500+01:00\""); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/de/meta.rs: -------------------------------------------------------------------------------- 1 | use serde::de::{self, value::Error, Deserialize, Deserializer, Visitor}; 2 | use serde::forward_to_deserialize_any; 3 | 4 | /// Return the fields of a struct. 5 | /// Taken directly from 6 | /// 7 | pub fn struct_name_and_fields<'de, T>( 8 | ) -> Result<(&'static str, Option<&'static [&'static str]>), Error> 9 | where 10 | T: Deserialize<'de>, 11 | { 12 | struct StructNameAndFieldsDeserializer<'a> { 13 | name: &'a mut Option<&'static str>, 14 | fields: &'a mut Option<&'static [&'static str]>, 15 | } 16 | 17 | impl<'de, 'a> Deserializer<'de> for StructNameAndFieldsDeserializer<'a> { 18 | type Error = Error; 19 | 20 | fn deserialize_any(self, _visitor: V) -> Result 21 | where 22 | V: Visitor<'de>, 23 | { 24 | Err(de::Error::custom("I'm just here for the fields")) 25 | } 26 | 27 | fn deserialize_newtype_struct( 28 | self, 29 | name: &'static str, 30 | visitor: V, 31 | ) -> Result 32 | where 33 | V: Visitor<'de>, 34 | { 35 | *self.name = Some(name); 36 | visitor.visit_newtype_struct(self) 37 | } 38 | 39 | fn deserialize_struct( 40 | self, 41 | name: &'static str, 42 | fields: &'static [&'static str], 43 | visitor: V, 44 | ) -> Result 45 | where 46 | V: Visitor<'de>, 47 | { 48 | *self.name = Some(name); 49 | *self.fields = Some(fields); 50 | self.deserialize_any(visitor) 51 | } 52 | 53 | forward_to_deserialize_any! { 54 | bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes 55 | byte_buf option unit seq tuple 56 | tuple_struct map enum identifier ignored_any 57 | } 58 | 59 | fn deserialize_unit_struct( 60 | self, 61 | name: &'static str, 62 | visitor: V, 63 | ) -> Result 64 | where 65 | V: Visitor<'de>, 66 | { 67 | self.deserialize_struct(name, &[], visitor) 68 | } 69 | } 70 | 71 | let mut name = None; 72 | let mut fields = None; 73 | 74 | let _ = T::deserialize(StructNameAndFieldsDeserializer { 75 | name: &mut name, 76 | fields: &mut fields, 77 | }); 78 | 79 | match name { 80 | None => Err(de::Error::custom("Expected a named struct. \ 81 | Hint: You cannot use a HashMap<...> in this context because it requires the struct to have a name")), 82 | Some(name) => { 83 | validate_identifier(name)?; 84 | for field in fields.into_iter().flatten() { 85 | validate_identifier(field)?; 86 | } 87 | 88 | Ok((name, fields)) 89 | } 90 | } 91 | } 92 | 93 | /// Validate a namespace/class/property name. 94 | /// 95 | /// From [DMTF-DSP0004], Appendix F: Unicode Usage: 96 | /// 97 | /// > ...
98 | /// > Therefore, all namespace, class and property names are identifiers composed as follows:
99 | /// > Initial identifier characters must be in set S1, where S1 = {U+005F, U+0041...U+005A, U+0061...U+007A, U+0080...U+FFEF) \[This is alphabetic, plus underscore\]
100 | /// > All following characters must be in set S2 where S2 = S1 union {U+0030...U+0039} \[This is alphabetic, underscore, plus Arabic numerals 0 through 9.\]
101 | /// 102 | /// [DMTF-DSP0004]: https://www.dmtf.org/sites/default/files/standards/documents/DSP0004V2.3_final.pdf 103 | fn validate_identifier(s: &str) -> Result<&str, E> { 104 | fn is_s1(ch: char) -> bool { 105 | match ch { 106 | '\u{005f}' => true, 107 | '\u{0041}'..='\u{005A}' => true, 108 | '\u{0061}'..='\u{007A}' => true, 109 | '\u{0080}'..='\u{FFEF}' => true, 110 | _other => false, 111 | } 112 | } 113 | 114 | fn is_s2(ch: char) -> bool { 115 | match ch { 116 | '\u{0030}'..='\u{0039}' => true, 117 | _other => is_s1(ch), 118 | } 119 | } 120 | 121 | let mut chars = s.chars(); 122 | match chars.next() { 123 | None => Err(de::Error::custom( 124 | "An empty string is not a valid namespace, class, or property name", 125 | )), 126 | Some(ch) if !is_s1(ch) => Err(de::Error::custom( 127 | "An identifier must start with '_' or an alphabetic character", 128 | )), 129 | Some(_) if !chars.all(is_s2) => Err(de::Error::custom( 130 | "An identifier must only consist of '_', alphabetic, or numeric characters", 131 | )), 132 | Some(_) => Ok(s), 133 | } 134 | } 135 | 136 | #[cfg(test)] 137 | #[allow(dead_code)] 138 | mod tests { 139 | use super::*; 140 | use crate::Variant; 141 | use serde::Deserialize; 142 | use std::collections::HashMap; 143 | 144 | #[test] 145 | fn it_works() { 146 | #[derive(Deserialize, Debug)] 147 | struct Win32_OperatingSystem { 148 | Caption: String, 149 | Name: String, 150 | } 151 | 152 | let (name, fields) = struct_name_and_fields::().unwrap(); 153 | 154 | assert_eq!(name, "Win32_OperatingSystem"); 155 | assert_eq!(fields.unwrap(), ["Caption", "Name"]); 156 | } 157 | 158 | #[test] 159 | fn it_works_with_rename() { 160 | #[derive(Deserialize, Debug)] 161 | #[serde(rename = "Win32_OperatingSystem")] 162 | #[serde(rename_all = "PascalCase")] 163 | struct Win32OperatingSystem { 164 | caption: String, 165 | name: String, 166 | } 167 | 168 | let (name, fields) = struct_name_and_fields::().unwrap(); 169 | 170 | assert_eq!(name, "Win32_OperatingSystem"); 171 | assert_eq!(fields.unwrap(), ["Caption", "Name"]); 172 | } 173 | 174 | #[test] 175 | fn it_works_with_flatten() { 176 | #[derive(Deserialize, Debug)] 177 | struct Win32_OperatingSystem_inner { 178 | Caption: String, 179 | Name: String, 180 | 181 | #[serde(flatten)] 182 | extra: HashMap, 183 | } 184 | 185 | #[derive(Deserialize, Debug)] 186 | struct Win32_OperatingSystem(pub Win32_OperatingSystem_inner); 187 | 188 | let (name, fields) = struct_name_and_fields::().unwrap(); 189 | 190 | assert_eq!(name, "Win32_OperatingSystem"); 191 | assert_eq!(fields, None); 192 | } 193 | 194 | #[test] 195 | fn it_fails_for_sqli() { 196 | #[derive(Deserialize, Debug)] 197 | #[serde(rename = "Evil\\Struct\\Name")] 198 | struct EvilStructName {} 199 | 200 | #[derive(Deserialize, Debug)] 201 | struct EvilFieldName { 202 | #[serde(rename = "Evil\"Field\"Name")] 203 | field: String, 204 | } 205 | 206 | struct_name_and_fields::().unwrap_err(); 207 | struct_name_and_fields::().unwrap_err(); 208 | } 209 | 210 | #[test] 211 | fn it_fails_for_non_structs() { 212 | let err = struct_name_and_fields::>().unwrap_err(); 213 | 214 | assert!(format!("{:?}", err).contains("Expected a named struct")); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/de/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod meta; 2 | pub mod variant_de; 3 | pub mod wbem_class_de; 4 | -------------------------------------------------------------------------------- /src/de/variant_de.rs: -------------------------------------------------------------------------------- 1 | use crate::{de::wbem_class_de::Deserializer, variant::Variant, WMIError}; 2 | use serde::{ 3 | de::{self, IntoDeserializer}, 4 | forward_to_deserialize_any, Deserialize, 5 | }; 6 | use std::{fmt, vec::IntoIter}; 7 | 8 | #[derive(Debug)] 9 | struct SeqAccess { 10 | data: IntoIter, 11 | } 12 | 13 | impl<'de> de::SeqAccess<'de> for SeqAccess { 14 | type Error = WMIError; 15 | 16 | fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> 17 | where 18 | T: de::DeserializeSeed<'de>, 19 | { 20 | match self.data.next() { 21 | Some(variant) => seed.deserialize(variant).map(Some), 22 | None => Ok(None), 23 | } 24 | } 25 | } 26 | 27 | impl<'de> serde::Deserializer<'de> for Variant { 28 | type Error = WMIError; 29 | 30 | fn deserialize_any(self, visitor: V) -> Result 31 | where 32 | V: de::Visitor<'de>, 33 | { 34 | match self { 35 | Variant::Null => visitor.visit_none(), 36 | Variant::Empty => visitor.visit_unit(), 37 | Variant::String(s) => visitor.visit_string(s), 38 | Variant::I1(n) => visitor.visit_i8(n), 39 | Variant::I2(n) => visitor.visit_i16(n), 40 | Variant::I4(n) => visitor.visit_i32(n), 41 | Variant::I8(n) => visitor.visit_i64(n), 42 | Variant::R4(f) => visitor.visit_f32(f), 43 | Variant::R8(f) => visitor.visit_f64(f), 44 | Variant::Bool(b) => visitor.visit_bool(b), 45 | Variant::UI1(n) => visitor.visit_u8(n), 46 | Variant::UI2(n) => visitor.visit_u16(n), 47 | Variant::UI4(n) => visitor.visit_u32(n), 48 | Variant::UI8(n) => visitor.visit_u64(n), 49 | Variant::Array(v) => visitor.visit_seq(SeqAccess { 50 | data: v.into_iter(), 51 | }), 52 | _ => Err(WMIError::InvalidDeserializationVariantError(format!( 53 | "{:?}", 54 | self 55 | ))), 56 | } 57 | } 58 | 59 | fn deserialize_option(self, visitor: V) -> Result 60 | where 61 | V: de::Visitor<'de>, 62 | { 63 | match self { 64 | Variant::Null => visitor.visit_none(), 65 | Variant::Empty => visitor.visit_none(), 66 | some => visitor.visit_some(some), 67 | } 68 | } 69 | 70 | fn deserialize_struct( 71 | self, 72 | name: &'static str, 73 | fields: &'static [&'static str], 74 | visitor: V, 75 | ) -> Result 76 | where 77 | V: de::Visitor<'de>, 78 | { 79 | match self { 80 | Variant::Object(o) => { 81 | Deserializer::from_wbem_class_obj(o).deserialize_struct(name, fields, visitor) 82 | } 83 | _ => self.deserialize_any(visitor), 84 | } 85 | } 86 | 87 | fn deserialize_enum( 88 | self, 89 | name: &'static str, 90 | variants: &'static [&'static str], 91 | visitor: V, 92 | ) -> Result 93 | where 94 | V: de::Visitor<'de>, 95 | { 96 | match self { 97 | Variant::Object(o) => { 98 | Deserializer::from_wbem_class_obj(o).deserialize_enum(name, variants, visitor) 99 | } 100 | Variant::String(str) => str 101 | .into_deserializer() 102 | .deserialize_enum(name, variants, visitor), 103 | _ => self.deserialize_any(visitor), 104 | } 105 | } 106 | 107 | forward_to_deserialize_any! { 108 | bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string 109 | bytes byte_buf unit unit_struct newtype_struct seq tuple 110 | tuple_struct map identifier ignored_any 111 | } 112 | } 113 | 114 | impl<'de> Deserialize<'de> for Variant { 115 | #[inline] 116 | fn deserialize(deserializer: D) -> Result 117 | where 118 | D: serde::Deserializer<'de>, 119 | { 120 | struct VariantVisitor; 121 | 122 | impl<'de> de::Visitor<'de> for VariantVisitor { 123 | type Value = Variant; 124 | 125 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 126 | formatter.write_str("any valid variant value") 127 | } 128 | 129 | #[inline] 130 | fn visit_bool(self, value: bool) -> Result { 131 | Ok(Variant::Bool(value)) 132 | } 133 | 134 | #[inline] 135 | fn visit_i8(self, value: i8) -> Result { 136 | Ok(Variant::I1(value)) 137 | } 138 | 139 | #[inline] 140 | fn visit_i16(self, value: i16) -> Result { 141 | Ok(Variant::I2(value)) 142 | } 143 | 144 | #[inline] 145 | fn visit_i32(self, value: i32) -> Result { 146 | Ok(Variant::I4(value)) 147 | } 148 | 149 | #[inline] 150 | fn visit_i64(self, value: i64) -> Result { 151 | Ok(Variant::I8(value)) 152 | } 153 | 154 | #[inline] 155 | fn visit_u8(self, value: u8) -> Result { 156 | Ok(Variant::UI1(value)) 157 | } 158 | 159 | #[inline] 160 | fn visit_u16(self, value: u16) -> Result { 161 | Ok(Variant::UI2(value)) 162 | } 163 | 164 | #[inline] 165 | fn visit_u32(self, value: u32) -> Result { 166 | Ok(Variant::UI4(value)) 167 | } 168 | 169 | #[inline] 170 | fn visit_u64(self, value: u64) -> Result { 171 | Ok(Variant::UI8(value)) 172 | } 173 | 174 | #[inline] 175 | fn visit_f32(self, value: f32) -> Result { 176 | Ok(Variant::R4(value)) 177 | } 178 | 179 | #[inline] 180 | fn visit_f64(self, value: f64) -> Result { 181 | Ok(Variant::R8(value)) 182 | } 183 | 184 | #[inline] 185 | fn visit_str(self, value: &str) -> Result 186 | where 187 | E: de::Error, 188 | { 189 | self.visit_string(String::from(value)) 190 | } 191 | 192 | #[inline] 193 | fn visit_string(self, value: String) -> Result { 194 | Ok(Variant::String(value)) 195 | } 196 | 197 | #[inline] 198 | fn visit_none(self) -> Result { 199 | Ok(Variant::Null) 200 | } 201 | 202 | #[inline] 203 | fn visit_some(self, deserializer: D) -> Result 204 | where 205 | D: serde::Deserializer<'de>, 206 | { 207 | Deserialize::deserialize(deserializer) 208 | } 209 | 210 | #[inline] 211 | fn visit_unit(self) -> Result { 212 | Ok(Variant::Null) 213 | } 214 | 215 | #[inline] 216 | fn visit_seq(self, mut visitor: V) -> Result 217 | where 218 | V: de::SeqAccess<'de>, 219 | { 220 | let mut vec = Vec::new(); 221 | 222 | while let Some(elem) = visitor.next_element()? { 223 | vec.push(elem); 224 | } 225 | 226 | Ok(Variant::Array(vec)) 227 | } 228 | 229 | fn visit_map(self, mut _visitor: V) -> Result 230 | where 231 | V: de::MapAccess<'de>, 232 | { 233 | // TODO: Add support for map type 234 | unimplemented!() 235 | } 236 | } 237 | 238 | deserializer.deserialize_any(VariantVisitor) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/de/wbem_class_de.rs: -------------------------------------------------------------------------------- 1 | use crate::{result_enumerator::IWbemClassWrapper, WMIError, WMIResult}; 2 | use serde::{ 3 | de::{ 4 | self, DeserializeOwned, DeserializeSeed, EnumAccess, IntoDeserializer, MapAccess, 5 | Unexpected, VariantAccess, Visitor, 6 | }, 7 | forward_to_deserialize_any, 8 | }; 9 | use std::iter::Peekable; 10 | 11 | pub struct Deserializer { 12 | pub wbem_class_obj: IWbemClassWrapper, 13 | } 14 | 15 | impl Deserializer { 16 | pub fn from_wbem_class_obj(wbem_class_obj: IWbemClassWrapper) -> Self { 17 | Deserializer { wbem_class_obj } 18 | } 19 | } 20 | 21 | pub fn from_wbem_class_obj(wbem_class_obj: IWbemClassWrapper) -> WMIResult 22 | where 23 | T: DeserializeOwned, 24 | { 25 | let mut deserializer = Deserializer::from_wbem_class_obj(wbem_class_obj); 26 | T::deserialize(&mut deserializer) 27 | } 28 | 29 | struct WMIEnum<'a> { 30 | de: &'a mut Deserializer, 31 | } 32 | 33 | impl<'a> WMIEnum<'a> { 34 | pub fn new(de: &'a mut Deserializer) -> Self { 35 | Self { de } 36 | } 37 | } 38 | 39 | impl<'de, 'a> EnumAccess<'de> for WMIEnum<'a> { 40 | type Error = WMIError; 41 | type Variant = Self; 42 | 43 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> 44 | where 45 | V: DeserializeSeed<'de>, 46 | { 47 | let val = seed.deserialize(&mut *self.de)?; 48 | Ok((val, self)) 49 | } 50 | } 51 | 52 | impl<'de, 'a> VariantAccess<'de> for WMIEnum<'a> { 53 | type Error = WMIError; 54 | 55 | // All other possible enum variants are not supported. 56 | fn unit_variant(self) -> Result<(), Self::Error> { 57 | let unexp = Unexpected::UnitVariant; 58 | Err(de::Error::invalid_type(unexp, &"newtype variant")) 59 | } 60 | 61 | // Newtype variants can be deserialized directly. 62 | fn newtype_variant_seed(self, seed: T) -> Result 63 | where 64 | T: DeserializeSeed<'de>, 65 | { 66 | seed.deserialize(self.de) 67 | } 68 | 69 | fn tuple_variant(self, _len: usize, _visitor: V) -> Result 70 | where 71 | V: Visitor<'de>, 72 | { 73 | let unexp = Unexpected::TupleVariant; 74 | Err(de::Error::invalid_type(unexp, &"newtype variant")) 75 | } 76 | 77 | fn struct_variant( 78 | self, 79 | _fields: &'static [&'static str], 80 | _visitor: V, 81 | ) -> Result 82 | where 83 | V: Visitor<'de>, 84 | { 85 | let unexp = Unexpected::StructVariant; 86 | Err(de::Error::invalid_type(unexp, &"newtype variant")) 87 | } 88 | } 89 | 90 | struct WMIMapAccess<'a, S, I> 91 | where 92 | S: AsRef, 93 | I: Iterator, 94 | { 95 | fields: Peekable, 96 | de: &'a Deserializer, 97 | } 98 | 99 | impl<'a, S, I> WMIMapAccess<'a, S, I> 100 | where 101 | S: AsRef, 102 | I: Iterator, 103 | { 104 | pub fn new(fields: I, de: &'a Deserializer) -> Self { 105 | Self { 106 | fields: fields.peekable(), 107 | de, 108 | } 109 | } 110 | } 111 | 112 | impl<'de, 'a, S, I> MapAccess<'de> for WMIMapAccess<'a, S, I> 113 | where 114 | S: AsRef, 115 | I: Iterator, 116 | { 117 | type Error = WMIError; 118 | 119 | fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> 120 | where 121 | K: DeserializeSeed<'de>, 122 | { 123 | if let Some(field) = self.fields.peek() { 124 | seed.deserialize(field.as_ref().into_deserializer()) 125 | .map(Some) 126 | } else { 127 | Ok(None) 128 | } 129 | } 130 | 131 | fn next_value_seed(&mut self, seed: V) -> Result 132 | where 133 | V: DeserializeSeed<'de>, 134 | { 135 | let current_field = self 136 | .fields 137 | .next() 138 | .ok_or_else(|| WMIError::SerdeError("Expected current field to not be None".into()))?; 139 | 140 | let property_value = self 141 | .de 142 | .wbem_class_obj 143 | .get_property(current_field.as_ref())?; 144 | 145 | seed.deserialize(property_value) 146 | } 147 | } 148 | 149 | impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer { 150 | type Error = WMIError; 151 | 152 | fn deserialize_any(self, _visitor: V) -> Result 153 | where 154 | V: Visitor<'de>, 155 | { 156 | Err(WMIError::SerdeError( 157 | "Only structs and maps can be deserialized from WMI objects".into(), 158 | )) 159 | } 160 | 161 | // Support for deserializing `Wrapper(Win32_OperatingSystem)`. 162 | fn deserialize_newtype_struct( 163 | self, 164 | _name: &'static str, 165 | visitor: V, 166 | ) -> Result 167 | where 168 | V: Visitor<'de>, 169 | { 170 | visitor.visit_newtype_struct(self) 171 | } 172 | 173 | fn deserialize_map(self, visitor: V) -> Result 174 | where 175 | V: Visitor<'de>, 176 | { 177 | let fields = self.wbem_class_obj.list_properties()?; 178 | 179 | visitor.visit_map(WMIMapAccess::new(fields.iter(), self)) 180 | } 181 | 182 | fn deserialize_struct( 183 | self, 184 | _name: &'static str, 185 | fields: &'static [&'static str], 186 | visitor: V, 187 | ) -> Result 188 | where 189 | V: Visitor<'de>, 190 | { 191 | visitor.visit_map(WMIMapAccess::new(fields.iter(), self)) 192 | } 193 | 194 | fn deserialize_enum( 195 | self, 196 | _name: &'static str, 197 | _variants: &'static [&'static str], 198 | visitor: V, 199 | ) -> Result 200 | where 201 | V: Visitor<'de>, 202 | { 203 | visitor.visit_enum(WMIEnum::new(self)) 204 | } 205 | 206 | // When deserializing enums, return the object's class name as the expected enum variant. 207 | fn deserialize_identifier(self, visitor: V) -> Result 208 | where 209 | V: Visitor<'de>, 210 | { 211 | let class_name = self.wbem_class_obj.class()?; 212 | visitor.visit_string(class_name) 213 | } 214 | 215 | fn deserialize_unit(self, visitor: V) -> Result 216 | where 217 | V: Visitor<'de>, 218 | { 219 | visitor.visit_unit() 220 | } 221 | 222 | forward_to_deserialize_any! { 223 | bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes 224 | byte_buf option unit_struct seq tuple 225 | tuple_struct ignored_any 226 | } 227 | } 228 | 229 | #[allow(non_snake_case)] 230 | #[allow(non_camel_case_types)] 231 | #[allow(dead_code)] 232 | #[cfg(test)] 233 | mod tests { 234 | use super::*; 235 | 236 | use crate::duration::WMIDuration; 237 | use crate::variant::Variant; 238 | use crate::FilterValue; 239 | use serde::Deserialize; 240 | use std::collections::HashMap; 241 | use std::time::Duration; 242 | 243 | use crate::tests::fixtures::*; 244 | use std::process; 245 | 246 | #[test] 247 | fn it_works() { 248 | let wmi_con = wmi_con(); 249 | 250 | #[derive(Deserialize, Debug)] 251 | struct Win32_OperatingSystem { 252 | Caption: String, 253 | Name: String, 254 | 255 | CurrentTimeZone: i16, 256 | Debug: bool, 257 | 258 | // This actually returns as an i32 from COM. 259 | EncryptionLevel: u32, 260 | ForegroundApplicationBoost: u8, 261 | 262 | #[cfg(feature = "chrono")] 263 | LastBootUpTime: crate::WMIDateTime, 264 | 265 | #[cfg(all(feature = "time", not(feature = "chrono")))] 266 | LastBootUpTime: crate::WMIOffsetDateTime, 267 | } 268 | 269 | let enumerator = wmi_con 270 | .exec_query("SELECT * FROM Win32_OperatingSystem") 271 | .unwrap(); 272 | 273 | for res in enumerator { 274 | let w = res.unwrap(); 275 | 276 | let w: Win32_OperatingSystem = from_wbem_class_obj(w).unwrap(); 277 | 278 | assert!(w.Caption.contains("Microsoft ")); 279 | assert!(w.Name.contains("Microsoft ") && w.Name.contains("Partition")); 280 | assert_eq!(w.Debug, false); 281 | assert_eq!(w.EncryptionLevel, 256); 282 | assert_eq!(w.ForegroundApplicationBoost, 2); 283 | assert_ne!(w.CurrentTimeZone, i16::MAX); 284 | 285 | #[cfg(any(feature = "time", feature = "chrono"))] 286 | assert!(w.LastBootUpTime.0.to_string().starts_with("20")); 287 | } 288 | } 289 | 290 | #[test] 291 | fn it_desr_into_map() { 292 | let wmi_con = wmi_con(); 293 | 294 | let enumerator = wmi_con 295 | .exec_query("SELECT * FROM Win32_OperatingSystem") 296 | .unwrap(); 297 | 298 | for res in enumerator { 299 | let w = res.unwrap(); 300 | 301 | let w: HashMap = from_wbem_class_obj(w).unwrap(); 302 | 303 | match w.get("Caption").unwrap() { 304 | Variant::String(s) => assert!(s.starts_with("Microsoft Windows")), 305 | _ => assert!(false), 306 | } 307 | 308 | assert_eq!(*w.get("Debug").unwrap(), Variant::Bool(false)); 309 | 310 | let langs = w.get("MUILanguages").unwrap(); 311 | 312 | match langs { 313 | Variant::Array(langs) => { 314 | assert!(matches!(langs[0], Variant::String(_))); 315 | } 316 | _ => assert!(false), 317 | } 318 | } 319 | } 320 | 321 | #[test] 322 | fn it_desr_into_map_with_selected_fields() { 323 | let wmi_con = wmi_con(); 324 | 325 | let enumerator = wmi_con 326 | .exec_query("SELECT Caption FROM Win32_OperatingSystem") 327 | .unwrap(); 328 | 329 | for res in enumerator { 330 | let w = res.unwrap(); 331 | 332 | let w: HashMap = from_wbem_class_obj(w).unwrap(); 333 | 334 | match w.get("Caption").unwrap() { 335 | Variant::String(s) => assert!(s.starts_with("Microsoft Windows")), 336 | _ => assert!(false), 337 | } 338 | 339 | assert_eq!(w.get("Debug"), None); 340 | } 341 | } 342 | 343 | #[test] 344 | fn it_desr_array() { 345 | let wmi_con = wmi_con(); 346 | 347 | #[derive(Deserialize, Debug)] 348 | struct Win32_ComputerSystem { 349 | BootStatus: Vec, 350 | Roles: Vec, 351 | } 352 | 353 | let results: Vec = wmi_con.query().unwrap(); 354 | 355 | for res in results { 356 | assert_eq!(res.BootStatus.len(), 10); 357 | assert!(res.Roles.contains(&"NT".to_owned())); 358 | } 359 | } 360 | 361 | #[test] 362 | fn it_desr_option_string() { 363 | let wmi_con = wmi_con(); 364 | 365 | #[derive(Deserialize, Debug)] 366 | pub struct Win32_Process { 367 | pub Name: String, 368 | pub CommandLine: Option, 369 | pub ProcessID: u32, 370 | } 371 | 372 | let mut filters = HashMap::new(); 373 | filters.insert("ProcessID".into(), 0.into()); 374 | 375 | let system_proc: Win32_Process = wmi_con.filtered_query(&filters).unwrap().pop().unwrap(); 376 | 377 | assert_eq!(system_proc.CommandLine, None); 378 | 379 | let mut filters = HashMap::new(); 380 | filters.insert("ProcessID".into(), i64::from(process::id()).into()); 381 | 382 | let current_proc: Win32_Process = wmi_con.filtered_query(&filters).unwrap().pop().unwrap(); 383 | 384 | assert!(current_proc.CommandLine.is_some()); 385 | } 386 | 387 | #[test] 388 | fn it_fail_to_desr_null_to_string() { 389 | // Values can return as Null / Empty from WMI. 390 | // It is impossible to `desr` such values to `String` (for example). 391 | // See `it_desr_option_string` on how to fix this error. 392 | let wmi_con = wmi_con(); 393 | 394 | #[derive(Deserialize, Debug)] 395 | pub struct Win32_Process { 396 | pub Name: String, 397 | pub CommandLine: String, 398 | pub ProcessID: u32, 399 | } 400 | 401 | let mut filters = HashMap::new(); 402 | filters.insert("ProcessID".into(), 0.into()); 403 | 404 | let res: Result, _> = wmi_con.filtered_query(&filters); 405 | 406 | let err = res.err().unwrap(); 407 | 408 | assert_eq!( 409 | format!("{}", err), 410 | "invalid type: Option value, expected a string" 411 | ) 412 | } 413 | 414 | #[test] 415 | fn it_desr_duration() { 416 | let wmi_con = wmi_con(); 417 | 418 | #[derive(Deserialize, Debug)] 419 | pub struct Win32_NetworkLoginProfile { 420 | pub PasswordAge: Option, 421 | } 422 | 423 | let _profiles: Vec = wmi_con.query().unwrap(); 424 | } 425 | 426 | #[test] 427 | fn it_can_desr_newtype() { 428 | // Values can return as Null / Empty from WMI. 429 | // It is impossible to `desr` such values to `String` (for example). 430 | // See `it_desr_option_string` on how to fix this error. 431 | let wmi_con = wmi_con(); 432 | 433 | #[derive(Deserialize, Debug)] 434 | pub struct Win32_Service { 435 | pub Name: String, 436 | pub PathName: Option, 437 | } 438 | 439 | #[derive(Deserialize, Debug)] 440 | struct Wrapper(Win32_Service); 441 | 442 | let wrapped_service: Wrapper = wmi_con.get().unwrap(); 443 | 444 | assert_ne!(&wrapped_service.0.Name, "") 445 | } 446 | 447 | #[test] 448 | fn it_can_desr_newtype_enum_field() { 449 | let wmi_con = wmi_con(); 450 | 451 | #[derive(Deserialize, Debug)] 452 | struct Win32_Process {} 453 | 454 | #[derive(Deserialize, Debug)] 455 | struct Win32_Thread {} 456 | 457 | #[derive(Deserialize, Debug)] 458 | enum Instance { 459 | #[serde(rename = "Win32_Process")] 460 | Process(Win32_Process), 461 | #[serde(rename = "Win32_Thread")] 462 | Thread(Win32_Thread), 463 | } 464 | 465 | #[derive(Deserialize, Debug)] 466 | struct __InstanceCreationEvent { 467 | TargetInstance: Instance, 468 | } 469 | 470 | let mut filters_process = HashMap::new(); 471 | 472 | filters_process.insert( 473 | "TargetInstance".to_owned(), 474 | FilterValue::is_a::().unwrap(), 475 | ); 476 | 477 | filters_process.insert( 478 | "TargetInstance.Name".to_owned(), 479 | FilterValue::String("ping.exe".to_owned()), 480 | ); 481 | 482 | let mut instances_iter = wmi_con 483 | .filtered_notification::<__InstanceCreationEvent>( 484 | &filters_process, 485 | Some(Duration::from_secs(1)), 486 | ) 487 | .unwrap(); 488 | 489 | std::process::Command::new("ping.exe") 490 | .arg("127.0.0.1") 491 | .status() 492 | .unwrap(); 493 | 494 | let proc = instances_iter.next().unwrap().unwrap(); 495 | 496 | assert!(matches!(proc.TargetInstance, Instance::Process(..))) 497 | } 498 | 499 | #[test] 500 | fn it_can_desr_unit_enum_field_from_string() { 501 | let wmi_con = wmi_con(); 502 | 503 | #[derive(Deserialize, Debug, PartialEq, Eq)] 504 | enum Status { 505 | SomeValue, 506 | OK, 507 | SomeOtherValue, 508 | } 509 | 510 | #[derive(Deserialize, Debug)] 511 | struct Win32_OperatingSystem { 512 | Status: Status, 513 | } 514 | 515 | let os: Win32_OperatingSystem = wmi_con.get().unwrap(); 516 | 517 | assert_eq!(os.Status, Status::OK); 518 | } 519 | 520 | #[test] 521 | fn it_fail_to_desr_unit_enum_field_from_unexpected_string() { 522 | let wmi_con = wmi_con(); 523 | 524 | #[derive(Deserialize, Debug, PartialEq, Eq)] 525 | enum Status { 526 | Potato, 527 | } 528 | 529 | #[derive(Deserialize, Debug)] 530 | struct Win32_OperatingSystem { 531 | Status: Status, 532 | } 533 | 534 | let res: Result = wmi_con.get(); 535 | 536 | let err = res.err().unwrap(); 537 | 538 | assert_eq!( 539 | format!("{}", err), 540 | "unknown variant `OK`, expected `Potato`" 541 | ) 542 | } 543 | } 544 | -------------------------------------------------------------------------------- /src/duration.rs: -------------------------------------------------------------------------------- 1 | use crate::WMIError; 2 | use serde::{de, ser}; 3 | use std::{fmt, str::FromStr, time::Duration}; 4 | 5 | /// A wrapper type around Duration, which supports parsing from WMI-format strings. 6 | /// 7 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 8 | pub struct WMIDuration(pub Duration); 9 | 10 | impl FromStr for WMIDuration { 11 | type Err = WMIError; 12 | 13 | fn from_str(s: &str) -> Result { 14 | if s.len() != 25 { 15 | return Err(WMIError::ConvertDurationError(s.into())); 16 | } 17 | 18 | let (seconds_part, reminder) = s.split_at(14); 19 | let (micros_part, _) = reminder[1..].split_at(6); 20 | 21 | let seconds: u64 = seconds_part.parse()?; 22 | let micros: u64 = micros_part.parse()?; 23 | 24 | let duration = Duration::from_secs(seconds) + Duration::from_micros(micros); 25 | 26 | Ok(Self(duration)) 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | struct DurationVisitor; 32 | 33 | impl<'de> de::Visitor<'de> for DurationVisitor { 34 | type Value = WMIDuration; 35 | 36 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 37 | write!(formatter, "a timestamp in WMI format") 38 | } 39 | 40 | fn visit_str(self, value: &str) -> Result 41 | where 42 | E: de::Error, 43 | { 44 | value.parse().map_err(|err| E::custom(format!("{}", err))) 45 | } 46 | } 47 | 48 | impl<'de> de::Deserialize<'de> for WMIDuration { 49 | fn deserialize(deserializer: D) -> Result 50 | where 51 | D: de::Deserializer<'de>, 52 | { 53 | deserializer.deserialize_u64(DurationVisitor) 54 | } 55 | } 56 | 57 | impl ser::Serialize for WMIDuration { 58 | fn serialize(&self, serializer: S) -> Result 59 | where 60 | S: ser::Serializer, 61 | { 62 | serializer.serialize_u64(self.0.as_micros() as u64) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::WMIDuration; 69 | use serde_json; 70 | 71 | #[test] 72 | fn it_works() { 73 | let duration: WMIDuration = "00000005141436.100001:000".parse().unwrap(); 74 | 75 | assert_eq!(duration.0.as_micros(), 5141436100001); 76 | assert_eq!(duration.0.as_millis(), 5141436100); 77 | assert_eq!(duration.0.as_secs(), 5141436); 78 | } 79 | 80 | #[test] 81 | fn it_serializes_to_rfc() { 82 | let duration: WMIDuration = "00000005141436.100001:000".parse().unwrap(); 83 | 84 | let v = serde_json::to_string(&duration).unwrap(); 85 | assert_eq!(v, "5141436100001"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # WMI-rs 2 | //! 3 | //! [WMI] is a management API for Windows-based operating systems. 4 | //! This crate provides both a high-level Rust API (focused on data retrieval, event queries and method execution), 5 | //! as well as a mid-level API for easy access to native WMI methods. 6 | //! 7 | //! This crate also uses `serde` for transforming between native WMI class objects and plain Rust structs. 8 | //! 9 | //! # Quickstart 10 | //! 11 | //! Before using WMI, a connection must be created. 12 | //! 13 | //! ```rust 14 | //! # fn main() -> wmi::WMIResult<()> { 15 | //! use wmi::{COMLibrary, WMIConnection}; 16 | //! let com_con = COMLibrary::new()?; 17 | //! let wmi_con = WMIConnection::new(com_con)?; 18 | //! # Ok(()) 19 | //! # } 20 | //! ``` 21 | //! 22 | //! There are multiple ways to get data from the OS using this crate. 23 | //! 24 | //! ## Operating on untyped `Variant`s 25 | //! 26 | //! WMI data model is based on COM's [`VARIANT`] Type, which is a struct capable of holding 27 | //! many types of data. 28 | //! 29 | //! This crate provides the analogous [`crate::Variant`] enum. 30 | //! 31 | //! Using this enum, we can execute a simple WMI query and inspect the results. 32 | //! 33 | //! ```edition2018 34 | //! # fn main() -> wmi::WMIResult<()> { 35 | //! use wmi::*; 36 | //! let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 37 | //! use std::collections::HashMap; 38 | //! use wmi::Variant; 39 | //! let results: Vec> = wmi_con.raw_query("SELECT * FROM Win32_OperatingSystem").unwrap(); 40 | //! 41 | //! for os in results { 42 | //! println!("{:#?}", os); 43 | //! } 44 | //! # Ok(()) 45 | //! # } 46 | //! ``` 47 | //! 48 | //! ## Using strongly typed data structures 49 | //! 50 | //! Using `serde`, it is possible to return a struct representing the the data. 51 | //! 52 | //! ```edition2018 53 | //! # fn main() -> wmi::WMIResult<()> { 54 | //! # use wmi::*; 55 | //! # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 56 | //! use serde::Deserialize; 57 | //! # #[cfg(feature = "chrono")] 58 | //! use wmi::WMIDateTime; 59 | //! # #[cfg(all(feature = "time", not(feature = "chrono")))] 60 | //! # use wmi::WMIOffsetDateTime as WMIDateTime; 61 | //! 62 | //! #[derive(Deserialize, Debug)] 63 | //! #[serde(rename = "Win32_OperatingSystem")] 64 | //! #[serde(rename_all = "PascalCase")] 65 | //! struct OperatingSystem { 66 | //! caption: String, 67 | //! debug: bool, 68 | //! last_boot_up_time: WMIDateTime, 69 | //! } 70 | //! 71 | //! let results: Vec = wmi_con.query()?; 72 | //! 73 | //! for os in results { 74 | //! println!("{:#?}", os); 75 | //! } 76 | //! # Ok(()) 77 | //! # } 78 | //! ``` 79 | //! 80 | //! Because the name of the struct given to `serde` matches the [WMI class] name, the SQL query 81 | //! can be inferred. 82 | //! 83 | //! [WMI]: https://docs.microsoft.com/en-us/windows/desktop/wmisdk/about-wmi 84 | //! [Creating a WMI Application Using C++]: https://docs.microsoft.com/en-us/windows/desktop/wmisdk/creating-a-wmi-application-using-c- 85 | //! [`VARIANT`]: https://docs.microsoft.com/en-us/windows/desktop/api/oaidl/ns-oaidl-tagvariant 86 | //! [WMI class]: https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-operatingsystem 87 | //! 88 | //! # High-level API functions 89 | //! 90 | //! Queries and data retrieval - [`WMIConnection::query`], [`WMIConnection::filtered_query`], [`WMIConnection::get`], [`WMIConnection::get_by_path`] and [`WMIConnection::associators`]. 91 | //! 92 | //! Event listening - [`WMIConnection::notification`] and [`WMIConnection::filtered_notification`]. 93 | //! 94 | //! Method calling - [`WMIConnection::exec_class_method`] and [`WMIConnection::exec_instance_method`]. 95 | //! 96 | //! Most of these have `async` versions as well. 97 | //! 98 | //! # Mid-level API functions 99 | //! 100 | //! Queries and data retrieval - [`WMIConnection::get_object`], [`WMIConnection::exec_query`] and [`WMIConnection::exec_query_async`]. 101 | //! 102 | //! Event listening - [`WMIConnection::exec_notification_query`] and [`WMIConnection::exec_notification_query_async`]. 103 | //! 104 | //! Method calling - [`WMIConnection::exec_method`] and [`IWbemClassWrapper`]. 105 | //! 106 | //! These try to keep the names of the underlying WMI machinery. 107 | //! 108 | //! # Subscribing to event notifications 109 | //! 110 | //! Using this crate you can subscribe to events notifications generated upon changes in WMI data and services. 111 | //! 112 | //! When querying for events, it is important to remember there are two types of event notifications. \ 113 | //! The first one is notifications about changes to the standard WMI data models. They are called **intrinsic events**. \ 114 | //! Events like [`__InstanceCreationEvent`] or [`__NamespaceDeletionEvent`] are examples of such events. 115 | //! 116 | //! The second type is notifications about changes made by providers. They are called **extrinsic events**. \ 117 | //! Any WMI class deriving from the [`__ExtrinsicEvent`] class is an extrinsic event. \ 118 | //! An example of such events are [`Win32_ProcessStartTrace`] and [`Win32_VolumeChangeEvent`] classes. 119 | //! 120 | //! For more information about event queries, [see here](https://docs.microsoft.com/en-us/windows/win32/wmisdk/receiving-a-wmi-event#event-queries).\ 121 | //! You can use [WMI Code Creator] to see available events and create queries for them. 122 | //! 123 | //! The [`notification`] method returns an iterator that waits for any incoming events 124 | //! resulting from the provided query. Loops reading from this iterator will not end until they are broken. 125 | //! 126 | //! An example of subscribing to an intrinsic event notification for every new [`Win32_Process`] 127 | //! ```edition2018 128 | //! # use wmi::*; 129 | //! # #[cfg(not(feature = "test"))] 130 | //! # fn main() {} 131 | //! # #[cfg(feature = "test")] 132 | //! # fn main() -> WMIResult<()>{ 133 | //! # use serde::Deserialize; 134 | //! # use std::{collections::HashMap, time::Duration}; 135 | //! # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 136 | //! #[derive(Deserialize, Debug)] 137 | //! #[serde(rename = "__InstanceCreationEvent")] 138 | //! #[serde(rename_all = "PascalCase")] 139 | //! struct NewProcessEvent { 140 | //! target_instance: Process 141 | //! } 142 | //! 143 | //! #[derive(Deserialize, Debug)] 144 | //! #[serde(rename = "Win32_Process")] 145 | //! #[serde(rename_all = "PascalCase")] 146 | //! struct Process { 147 | //! process_id: u32, 148 | //! name: String, 149 | //! executable_path: Option, 150 | //! } 151 | //! 152 | //! let mut filters = HashMap::::new(); 153 | //! 154 | //! filters.insert("TargetInstance".to_owned(), FilterValue::is_a::()?); 155 | //! 156 | //! let iterator = wmi_con.filtered_notification::(&filters, Some(Duration::from_secs(1)))?; 157 | //! # tests::start_test_program(); 158 | //! 159 | //! for result in iterator { 160 | //! let process = result?.target_instance; 161 | //! println!("New process!"); 162 | //! println!("PID: {}", process.process_id); 163 | //! println!("Name: {}", process.name); 164 | //! println!("Executable: {:?}", process.executable_path); 165 | //! # break; 166 | //! } // Loop will end only on error 167 | //! # Ok(()) 168 | //! # } 169 | //! ``` 170 | //! 171 | //! An example of subscribing to an extrinsic event notification [`Win32_ProcessStartTrace`] 172 | //! ```edition2018 173 | //! # use wmi::*; 174 | //! # #[cfg(not(feature = "test"))] 175 | //! # fn main() {} 176 | //! # #[cfg(feature = "test")] 177 | //! # fn main() -> WMIResult<()> { 178 | //! # tests::ignore_access_denied(run()) 179 | //! # } 180 | //! # #[cfg(feature = "test")] 181 | //! # fn run() -> WMIResult<()>{ 182 | //! # use serde::Deserialize; 183 | //! # use std::{collections::HashMap, time::Duration}; 184 | //! # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 185 | //! #[derive(Deserialize, Debug)] 186 | //! #[serde(rename = "Win32_ProcessStartTrace")] 187 | //! #[serde(rename_all = "PascalCase")] 188 | //! struct ProcessStartTrace { 189 | //! process_id: u32, 190 | //! process_name: String, 191 | //! } 192 | //! 193 | //! let iterator = wmi_con.notification::()?; 194 | //! # tests::start_test_program(); 195 | //! 196 | //! for result in iterator { 197 | //! let trace = result?; 198 | //! println!("Process started!"); 199 | //! println!("PID: {}", trace.process_id); 200 | //! println!("Name: {}", trace.process_name); 201 | //! # break; 202 | //! } // Loop will end only on error 203 | //! # Ok(()) 204 | //! # } 205 | //! ``` 206 | //! 207 | //! [`Win32_Process`]: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process 208 | //! [`__InstanceCreationEvent`]: https://docs.microsoft.com/en-us/windows/win32/wmisdk/--instancecreationevent 209 | //! [`__NamespaceDeletionEvent`]: https://docs.microsoft.com/en-us/windows/win32/wmisdk/--namespacedeletionevent 210 | //! [`__ExtrinsicEvent`]: https://docs.microsoft.com/en-us/windows/win32/wmisdk/--extrinsicevent 211 | //! [`Win32_ProcessStartTrace`]: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/krnlprov/win32-processstarttrace 212 | //! [`Win32_VolumeChangeEvent`]: https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-volumechangeevent 213 | //! [WMI Code Creator]: https://www.microsoft.com/en-us/download/details.aspx?id=8572 214 | //! [`notification`]: connection/struct.WMIConnection.html#method.notification 215 | //! 216 | //! # Executing Methods 217 | //! 218 | //! The crate also offers support for executing WMI methods on classes and instances. 219 | //! 220 | //! See [`WMIConnection::exec_class_method`], [`WMIConnection::exec_instance_method`] and [`WMIConnection::exec_method`] 221 | //! for detailed examples. 222 | //! 223 | //! # Internals 224 | //! 225 | //! [`WMIConnection`] is used to create and execute a WMI query, returning 226 | //! [`IWbemClassWrapper`] which is a wrapper for a WMI object pointer. 227 | //! 228 | //! Then, `from_wbem_class_obj` is used to create a Rust struct with the equivalent data. 229 | //! 230 | //! Deserializing data from WMI and into Rust is done via `serde`. 231 | //! More info can be found in `serde`'s documentation about [writing a data format]. 232 | //! The deserializer will either use the field names defined on the output struct, 233 | //! or retrieve all field names from WMI if the output is a `HashMap`. 234 | //! 235 | //! [writing a data format]: https://serde.rs/data-format.html 236 | //! 237 | //! There are two main data structures (other than pointers to object) which convert native data to Rust data structures: 238 | //! [`crate::Variant`] and [`SafeArrayAccessor`](safearray::SafeArrayAccessor). 239 | //! 240 | //! Most native objects has an equivalent wrapper struct which implements `Drop` for that data. 241 | //! 242 | //! # Async Query 243 | //! 244 | //! Async queries use WMI's native async support (but a runtime like `tokio`, `async-std` or `futures::executor::block_on` is still required). 245 | //! 246 | //! ```edition2018 247 | //! # use futures::executor::block_on; 248 | //! # block_on(exec_async_query()).unwrap(); 249 | //! # async fn exec_async_query() -> wmi::WMIResult<()> { 250 | //! use wmi::*; 251 | //! let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 252 | //! use serde::Deserialize; 253 | //! 254 | //! #[derive(Deserialize, Debug)] 255 | //! #[serde(rename = "Win32_OperatingSystem")] 256 | //! #[serde(rename_all = "PascalCase")] 257 | //! struct OperatingSystem { 258 | //! caption: String, 259 | //! debug: bool, 260 | //! } 261 | //! 262 | //! let results: Vec = wmi_con.async_query().await?; 263 | //! 264 | //! for os in results { 265 | //! println!("{:#?}", os); 266 | //! } 267 | //! # Ok(()) 268 | //! # } 269 | //! ``` 270 | //! 271 | #![allow(non_camel_case_types)] 272 | #![allow(non_snake_case)] 273 | #![allow(unused_unsafe)] 274 | #![allow(clippy::arc_with_non_send_sync)] 275 | #![allow(clippy::needless_lifetimes)] 276 | #![cfg(windows)] 277 | 278 | mod connection; 279 | 280 | #[cfg(feature = "chrono")] 281 | mod datetime; 282 | 283 | #[cfg(feature = "time")] 284 | mod datetime_time; 285 | 286 | mod context; 287 | mod de; 288 | mod duration; 289 | mod method; 290 | mod query; 291 | mod result_enumerator; 292 | pub mod safearray; 293 | mod ser; 294 | mod utils; 295 | mod variant; 296 | 297 | mod async_query; 298 | mod query_sink; 299 | 300 | mod notification; 301 | 302 | #[cfg(any(test, feature = "test"))] 303 | pub mod tests; 304 | 305 | pub use connection::{COMLibrary, WMIConnection}; 306 | 307 | #[cfg(feature = "chrono")] 308 | pub use datetime::WMIDateTime; 309 | 310 | #[cfg(feature = "time")] 311 | pub use datetime_time::WMIOffsetDateTime; 312 | 313 | pub use context::{ContextValueType, WMIContext}; 314 | pub use duration::WMIDuration; 315 | pub use query::{build_notification_query, build_query, quote_and_escape_wql_str, FilterValue}; 316 | pub use result_enumerator::IWbemClassWrapper; 317 | pub use utils::{WMIError, WMIResult}; 318 | pub use variant::Variant; 319 | 320 | #[doc = include_str!("../README.md")] 321 | #[cfg(all(doctest, feature = "chrono"))] 322 | pub struct ReadmeDoctests; 323 | -------------------------------------------------------------------------------- /src/method.rs: -------------------------------------------------------------------------------- 1 | use serde::{de, Serialize}; 2 | use windows::core::BSTR; 3 | 4 | use crate::{ 5 | de::meta::struct_name_and_fields, result_enumerator::IWbemClassWrapper, 6 | ser::variant_ser::VariantSerializer, Variant, WMIConnection, WMIError, WMIResult, 7 | }; 8 | 9 | impl WMIConnection { 10 | /// Wrapper for WMI's [ExecMethod](https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemservices-execmethod) function. 11 | /// 12 | /// This function is used internally by [`WMIConnection::exec_class_method`] and [`WMIConnection::exec_instance_method`], 13 | /// which are a higher-level abstraction, dealing with Rust data types instead of raw Variants, that should be preferred to use. 14 | /// 15 | /// In the case of a class ("static") method, `object_path` should be name of the class. 16 | /// 17 | /// Returns `None` if the method has no out parameters and a `void` return type, and an [`IWbemClassWrapper`] containing the output otherwise. 18 | /// A method with a return type other than `void` will always have a generic property named `ReturnValue` in the output class wrapper with the return value of the WMI method call. 19 | /// 20 | /// ```edition2021 21 | /// # use std::collections::HashMap; 22 | /// # use wmi::{COMLibrary, Variant, WMIConnection, WMIResult}; 23 | /// # fn main() -> WMIResult<()> { 24 | /// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 25 | /// let in_params = wmi_con 26 | /// .get_object("Win32_Process")? 27 | /// .get_method("Create")? 28 | /// .unwrap() 29 | /// .spawn_instance()?; 30 | /// in_params.put_property("CommandLine", "explorer.exe".to_string())?; 31 | /// 32 | /// // Because Create has a return value and out parameters, the Option returned will never be None. 33 | /// // Note: The Create call can be unreliable, so consider using another means of starting processes. 34 | /// let out = wmi_con.exec_method("Win32_Process", "Create", Some(&in_params))?.unwrap(); 35 | /// println!("The return code of the Create call is {:?}", out.get_property("ReturnValue")?); 36 | /// # Ok(()) 37 | /// # } 38 | /// ``` 39 | pub fn exec_method( 40 | &self, 41 | object_path: impl AsRef, 42 | method: impl AsRef, 43 | in_params: Option<&IWbemClassWrapper>, 44 | ) -> WMIResult> { 45 | let object_path = BSTR::from(object_path.as_ref()); 46 | 47 | // In the case of a method with no out parameters and a VOID return type, there will be no out-parameters object 48 | let method = BSTR::from(method.as_ref()); 49 | 50 | let mut output = None; 51 | unsafe { 52 | self.svc.ExecMethod( 53 | &object_path, 54 | &method, 55 | Default::default(), 56 | &self.ctx.0, 57 | in_params.as_ref().map(|param| ¶m.inner), 58 | Some(&mut output), 59 | None, 60 | )?; 61 | } 62 | 63 | Ok(output.map(IWbemClassWrapper::new)) 64 | } 65 | 66 | /// Executes a method of a WMI class not tied to any specific instance. Examples include 67 | /// [Create](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process) of `Win32_Process` 68 | /// and [AddPrinterConnection](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/addprinterconnection-method-in-class-win32-printer) of `Win32_Printer`. 69 | /// 70 | /// A method with a return type other than `void` will always try to populate a generic property named `ReturnValue` in the output object with the return value of the WMI method call. 71 | /// If the method call has a `void` return type and no out parameters, the only acceptable type for `Out` is `()`. 72 | /// 73 | /// Arrays, Options, unknowns, and nested objects cannot be passed as input parameters due to limitations in how variants are constructed by `windows-rs`. 74 | /// 75 | /// This function uses [`WMIConnection::exec_method`] internally, with the name of the method class being the instance path, as is expected by WMI. 76 | /// 77 | /// ```edition2021 78 | /// # use serde::{Deserialize, Serialize}; 79 | /// # use wmi::{COMLibrary, Variant, WMIConnection, WMIResult}; 80 | /// #[derive(Serialize)] 81 | /// # #[allow(non_snake_case)] 82 | /// struct CreateInput { 83 | /// CommandLine: String 84 | /// } 85 | /// 86 | /// #[derive(Deserialize)] 87 | /// # #[allow(non_snake_case)] 88 | /// struct CreateOutput { 89 | /// ReturnValue: u32, 90 | /// ProcessId: u32 91 | /// } 92 | /// 93 | /// #[derive(Deserialize)] 94 | /// # #[allow(non_camel_case_types)] 95 | /// struct Win32_Process; 96 | /// 97 | /// # fn main() -> WMIResult<()> { 98 | /// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 99 | /// // Note: The Create call can be unreliable, so consider using another means of starting processes. 100 | /// let input = CreateInput { 101 | /// CommandLine: "explorer.exe".to_string() 102 | /// }; 103 | /// let output: CreateOutput = wmi_con 104 | /// .exec_class_method::("Create", input)?; 105 | /// 106 | /// println!("The return code of the Create call is {}", output.ReturnValue); 107 | /// println!("The ID of the created process is: {}", output.ProcessId); 108 | /// # Ok(()) 109 | /// # } 110 | /// ``` 111 | pub fn exec_class_method( 112 | &self, 113 | method: impl AsRef, 114 | in_params: impl Serialize, 115 | ) -> WMIResult 116 | where 117 | Class: de::DeserializeOwned, 118 | Out: de::DeserializeOwned, 119 | { 120 | let (class, _) = struct_name_and_fields::()?; 121 | self.exec_instance_method::(class, method, in_params) 122 | } 123 | 124 | /// Executes a WMI method on a specific instance of a class. Examples include 125 | /// [GetSupportedSize](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-Volume-getsupportedsizes) of `MSFT_Volume` 126 | /// and [Pause](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/pause-method-in-class-win32-printer) of `Win32_Printer`. 127 | /// 128 | /// `object_path` is the `__Path` variable of the class instance on which the method is being called, which can be obtained from a WMI query. 129 | /// 130 | /// A method with a return type other than `void` will always try to populate a generic property named `ReturnValue` in the output object with the return value of the WMI method call. 131 | /// If the method call has a `void` return type and no out parameters, the only acceptable type for `Out` is `()`. 132 | /// 133 | /// Arrays, Options, unknowns, and nested objects cannot be passed as input parameters due to limitations in how variants are constructed by `windows-rs`. 134 | /// 135 | /// ```edition2021 136 | /// # use serde::{Deserialize, Serialize}; 137 | /// # use wmi::{COMLibrary, FilterValue, Variant, WMIConnection, WMIResult}; 138 | /// #[derive(Deserialize)] 139 | /// # #[allow(non_snake_case)] 140 | /// struct PrinterOutput { 141 | /// ReturnValue: u32 142 | /// } 143 | /// 144 | /// #[derive(Deserialize)] 145 | /// # #[allow(non_camel_case_types, non_snake_case)] 146 | /// struct Win32_Printer { 147 | /// __Path: String 148 | /// } 149 | /// 150 | /// # fn main() -> WMIResult<()> { 151 | /// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?; 152 | /// let printers: Vec = wmi_con.query()?; 153 | /// 154 | /// for printer in printers { 155 | /// let output: PrinterOutput = wmi_con.exec_instance_method::(&printer.__Path, "Pause", ())?; 156 | /// println!("Pausing the printer returned {}", output.ReturnValue); 157 | /// 158 | /// let output: PrinterOutput = wmi_con.exec_instance_method::(&printer.__Path, "Resume", ())?; 159 | /// println!("Resuming the printer returned {}", output.ReturnValue); 160 | /// } 161 | /// # Ok(()) 162 | /// # } 163 | /// ``` 164 | pub fn exec_instance_method( 165 | &self, 166 | object_path: impl AsRef, 167 | method: impl AsRef, 168 | in_params: impl Serialize, 169 | ) -> WMIResult 170 | where 171 | Class: de::DeserializeOwned, 172 | Out: de::DeserializeOwned, 173 | { 174 | let (class, _) = struct_name_and_fields::()?; 175 | let method = method.as_ref(); 176 | 177 | // See https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-getmethod 178 | // GetMethod can only be called on a class definition, so we retrieve that before retrieving a specific object. 179 | let instance = match self.get_object(class)?.get_method(method)? { 180 | None => None, 181 | Some(method_class) => { 182 | let instance = method_class.spawn_instance()?; 183 | 184 | let serializer = VariantSerializer { 185 | wmi: self, 186 | instance: Some(instance), 187 | }; 188 | 189 | match in_params.serialize(serializer) { 190 | Ok(Variant::Object(instance)) => Some(instance), 191 | Ok(other) => { 192 | return Err(WMIError::ConvertVariantError(format!( 193 | "Unexpected serializer output: {:?}", 194 | other 195 | ))) 196 | } 197 | Err(e) => return Err(WMIError::ConvertVariantError(e.to_string())), 198 | } 199 | } 200 | }; 201 | 202 | let output = self.exec_method(object_path, method, instance.as_ref())?; 203 | 204 | match output { 205 | Some(class_wrapper) => Ok(class_wrapper.into_desr()?), 206 | None => Out::deserialize(Variant::Empty), 207 | } 208 | } 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use crate::tests::fixtures::wmi_con; 214 | use crate::Variant; 215 | use serde::{Deserialize, Serialize}; 216 | use std::thread::sleep; 217 | use std::time::Duration; 218 | 219 | #[derive(Deserialize)] 220 | struct Win32_Process { 221 | __Path: String, 222 | HandleCount: u32, 223 | } 224 | 225 | #[derive(Debug, Serialize, Default)] 226 | pub struct Win32_ProcessStartup { 227 | CreateFlags: u32, 228 | } 229 | 230 | #[derive(Serialize)] 231 | struct CreateInput { 232 | CommandLine: String, 233 | ProcessStartupInformation: Win32_ProcessStartup, 234 | } 235 | 236 | #[derive(Deserialize)] 237 | struct CreateOutput { 238 | ReturnValue: u32, 239 | ProcessId: u32, 240 | } 241 | 242 | #[test] 243 | fn it_exec_methods_native() { 244 | let wmi_con = wmi_con(); 245 | 246 | let in_params = wmi_con 247 | .get_object("Win32_Process") 248 | .unwrap() 249 | .get_method("Create") 250 | .unwrap() 251 | .unwrap() 252 | .spawn_instance() 253 | .unwrap(); 254 | 255 | in_params 256 | .put_property("CommandLine", "explorer.exe".to_string()) 257 | .unwrap(); 258 | 259 | let out = wmi_con 260 | .exec_method("Win32_Process", "Create", Some(&in_params)) 261 | .unwrap(); 262 | 263 | let return_value = out.unwrap().get_property("ReturnValue").unwrap(); 264 | 265 | assert!(matches!(return_value, Variant::UI4(0))); 266 | } 267 | 268 | #[test] 269 | fn it_exec_methods() { 270 | let wmi_con = wmi_con(); 271 | const CREATE_SUSPENDED: u32 = 4; 272 | 273 | let in_params = CreateInput { 274 | CommandLine: "explorer.exe".to_string(), 275 | ProcessStartupInformation: Win32_ProcessStartup { 276 | CreateFlags: CREATE_SUSPENDED, 277 | }, 278 | }; 279 | let out: CreateOutput = wmi_con 280 | .exec_class_method::("Create", &in_params) 281 | .unwrap(); 282 | 283 | assert_eq!(out.ReturnValue, 0); 284 | 285 | let query = format!( 286 | "SELECT * FROM Win32_Process WHERE ProcessId = {}", 287 | out.ProcessId 288 | ); 289 | 290 | let process = &wmi_con.raw_query::(&query).unwrap()[0]; 291 | 292 | // Since we started the process as suspended, it will not have any open handles. 293 | assert_eq!(process.HandleCount, 0); 294 | 295 | let _: () = wmi_con 296 | .exec_instance_method::(&process.__Path, "Terminate", ()) 297 | .unwrap(); 298 | 299 | // It can take a moment for the process to terminate, so we retry the query a few times. 300 | for _ in 0..10 { 301 | if wmi_con.raw_query::(&query).unwrap().len() == 0 { 302 | break; 303 | } 304 | sleep(Duration::from_millis(100)); 305 | } 306 | 307 | assert!(wmi_con.raw_query::(&query).unwrap().len() == 0); 308 | } 309 | 310 | #[test] 311 | fn it_exec_with_u8_arrays() { 312 | let wmi_con = wmi_con(); 313 | 314 | #[derive(Deserialize)] 315 | struct StdRegProv; 316 | 317 | #[derive(Deserialize, Serialize)] 318 | struct GetBinaryValue { 319 | sSubKeyName: String, 320 | sValueName: String, 321 | } 322 | 323 | #[derive(Deserialize)] 324 | struct GetBinaryValueOut { 325 | uValue: Vec, 326 | } 327 | 328 | let get_binary_value_params = GetBinaryValue { 329 | sSubKeyName: r#"SYSTEM\CurrentControlSet\Control\Windows"#.to_string(), 330 | sValueName: "FullProcessInformationSID".to_string(), 331 | }; 332 | 333 | let value: GetBinaryValueOut = wmi_con 334 | .exec_class_method::("GetBinaryValue", &get_binary_value_params) 335 | .unwrap(); 336 | 337 | assert!(value.uValue.len() > 0, "Expected to get a non-empty value"); 338 | 339 | #[derive(Deserialize, Serialize)] 340 | struct SetBinaryValue { 341 | sSubKeyName: String, 342 | sValueName: String, 343 | uValue: Vec, 344 | } 345 | 346 | #[derive(Deserialize)] 347 | struct SetBinaryValueOut { 348 | ReturnValue: u32, 349 | } 350 | 351 | let test_value_name = format!("{}.test", get_binary_value_params.sValueName); 352 | let test_value = vec![0, 1, 2, 3]; 353 | 354 | let set_binary_value_params = SetBinaryValue { 355 | sSubKeyName: get_binary_value_params.sSubKeyName, 356 | sValueName: test_value_name, 357 | uValue: test_value, 358 | }; 359 | 360 | let value: SetBinaryValueOut = wmi_con 361 | .exec_class_method::("SetBinaryValue", &set_binary_value_params) 362 | .unwrap(); 363 | 364 | assert_eq!(value.ReturnValue, 0); 365 | 366 | let get_test_binary_value_params = GetBinaryValue { 367 | sSubKeyName: set_binary_value_params.sSubKeyName, 368 | sValueName: set_binary_value_params.sValueName, 369 | }; 370 | 371 | let value: GetBinaryValueOut = wmi_con 372 | .exec_class_method::("GetBinaryValue", &get_test_binary_value_params) 373 | .unwrap(); 374 | 375 | assert_eq!(value.uValue, set_binary_value_params.uValue); 376 | 377 | wmi_con 378 | .exec_class_method::("DeleteValue", &get_test_binary_value_params) 379 | .unwrap(); 380 | } 381 | 382 | #[test] 383 | fn it_exec_with_object_arrays() { 384 | let wmi_con = wmi_con(); 385 | 386 | #[derive(Deserialize)] 387 | struct StdRegProv; 388 | 389 | #[derive(Serialize)] 390 | struct GetSecurityDescriptor { 391 | sSubKeyName: String, 392 | } 393 | 394 | #[derive(Deserialize, Debug)] 395 | struct __Trustee { 396 | Name: String, 397 | } 398 | 399 | #[derive(Deserialize, Debug)] 400 | struct __ACE { 401 | Trustee: __Trustee, 402 | } 403 | 404 | #[derive(Deserialize, Debug)] 405 | struct __SecurityDescriptor { 406 | DACL: Vec<__ACE>, 407 | } 408 | 409 | #[derive(Deserialize, Debug)] 410 | struct GetSecurityDescriptorOut { 411 | Descriptor: Option<__SecurityDescriptor>, 412 | } 413 | 414 | let params = GetSecurityDescriptor { 415 | sSubKeyName: r#"SECURITY"#.to_string(), 416 | }; 417 | 418 | let value: GetSecurityDescriptorOut = wmi_con 419 | .exec_class_method::("GetSecurityDescriptor", ¶ms) 420 | .unwrap(); 421 | 422 | let dacl = value.Descriptor.unwrap().DACL; 423 | 424 | assert!(dacl.len() > 0, "Expected to get a non-empty value"); 425 | assert!(dacl.iter().find(|ac| ac.Trustee.Name == "SYSTEM").is_some()); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/notification.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | build_notification_query, 3 | query_sink::{AsyncQueryResultStream, AsyncQueryResultStreamInner, QuerySink}, 4 | result_enumerator::{IWbemClassWrapper, QueryResultEnumerator}, 5 | FilterValue, WMIConnection, WMIResult, 6 | }; 7 | use futures::{Stream, StreamExt}; 8 | use std::{collections::HashMap, time::Duration}; 9 | use windows::core::BSTR; 10 | use windows::Win32::System::Wmi::{ 11 | IWbemObjectSink, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, 12 | }; 13 | 14 | /// 15 | /// ### Additional notification query methods 16 | /// 17 | impl WMIConnection { 18 | /// Execute the given query to receive events and return an iterator of WMI pointers. 19 | /// It's better to use the other query methods, since this is relatively low level. 20 | /// 21 | pub fn exec_notification_query( 22 | &self, 23 | query: impl AsRef, 24 | ) -> WMIResult> + 'static> { 25 | let query_language = BSTR::from("WQL"); 26 | let query = BSTR::from(query.as_ref()); 27 | 28 | let enumerator = unsafe { 29 | self.svc.ExecNotificationQuery( 30 | &query_language, 31 | &query, 32 | WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 33 | None, 34 | )? 35 | }; 36 | 37 | Ok(QueryResultEnumerator::new(enumerator)) 38 | } 39 | 40 | /// Execute a free-text query and deserialize the incoming events. 41 | /// Returns an iterator of WMIResult\. 42 | /// Can be used either with a struct (like `query` and `filtered_query`), 43 | /// but also with a generic map. 44 | /// 45 | /// ```edition2018 46 | /// # use wmi::*; 47 | /// # #[cfg(not(feature = "test"))] 48 | /// # fn main() {} 49 | /// # #[cfg(feature = "test")] 50 | /// # fn main() -> wmi::WMIResult<()> { 51 | /// # tests::ignore_access_denied(run()) 52 | /// # } 53 | /// # fn run() -> wmi::WMIResult<()> { 54 | /// # use std::collections::HashMap; 55 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 56 | /// let iterator = con.raw_notification::>("SELECT ProcessID, ProcessName FROM Win32_ProcessStartTrace")?; 57 | /// # Ok(()) // This query will fail when not run as admin 58 | /// # } 59 | /// ``` 60 | pub fn raw_notification( 61 | &self, 62 | query: impl AsRef, 63 | ) -> WMIResult> + 'static> 64 | where 65 | T: serde::de::DeserializeOwned, 66 | { 67 | let enumerator = self.exec_notification_query(query)?; 68 | let iter = enumerator.map(|item| match item { 69 | Ok(wbem_class_obj) => wbem_class_obj.into_desr(), 70 | Err(e) => Err(e), 71 | }); 72 | Ok(iter) 73 | } 74 | 75 | /// Subscribe to the T event and return an iterator of WMIResult\. 76 | /// 77 | /// ```edition2018 78 | /// use wmi::*; 79 | /// # #[cfg(not(feature = "test"))] 80 | /// # fn main() {} 81 | /// # #[cfg(feature = "test")] 82 | /// # fn main() -> wmi::WMIResult<()> { 83 | /// # tests::ignore_access_denied(run()) 84 | /// # } 85 | /// # fn run() -> wmi::WMIResult<()> { 86 | /// use serde::Deserialize; 87 | /// 88 | /// let con = WMIConnection::new(COMLibrary::new()?)?; 89 | /// 90 | /// #[derive(Deserialize, Debug)] 91 | /// struct Win32_ProcessStartTrace { 92 | /// ProcessID: u32, 93 | /// ProcessName: String, 94 | /// } 95 | /// 96 | /// let iterator = con.notification::()?; 97 | /// # Ok(()) // This query will fail when not run as admin 98 | /// # } 99 | /// ``` 100 | pub fn notification(&self) -> WMIResult> + 'static> 101 | where 102 | T: serde::de::DeserializeOwned, 103 | { 104 | let query_text = build_notification_query::(None, None)?; 105 | self.raw_notification(query_text) 106 | } 107 | 108 | /// Subscribe to the T event, while filtering according to `filters`. 109 | /// Returns an iterator of WMIResult\. 110 | /// 111 | /// ```edition2018 112 | /// # fn main() -> wmi::WMIResult<()> { 113 | /// # use std::{collections::HashMap, time::Duration}; 114 | /// # use wmi::*; 115 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 116 | /// use serde::Deserialize; 117 | /// #[derive(Deserialize, Debug)] 118 | /// struct __InstanceCreationEvent { 119 | /// TargetInstance: Win32_Process, 120 | /// } 121 | /// 122 | /// #[derive(Deserialize, Debug)] 123 | /// struct Win32_Process { 124 | /// ProcessID: u32, 125 | /// } 126 | /// 127 | /// let mut filters = HashMap::new(); 128 | /// 129 | /// filters.insert("TargetInstance".to_owned(), FilterValue::is_a::()?); 130 | /// 131 | /// let iterator = con.filtered_notification::<__InstanceCreationEvent>(&filters, Some(Duration::from_secs(1)))?; 132 | /// # Ok(()) 133 | /// # } 134 | /// ``` 135 | pub fn filtered_notification( 136 | &self, 137 | filters: &HashMap, 138 | within: Option, 139 | ) -> WMIResult> + 'static> 140 | where 141 | T: serde::de::DeserializeOwned, 142 | { 143 | let query_text = build_notification_query::(Some(filters), within)?; 144 | self.raw_notification(query_text) 145 | } 146 | 147 | /// Wrapper for the [ExecNotificationQueryAsync](https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemservices-execnotificationqueryasync) 148 | /// method. Provides safety checks, and returns results 149 | /// as a stream instead of the original Sink. 150 | /// 151 | pub fn exec_notification_query_async( 152 | &self, 153 | query: impl AsRef, 154 | ) -> WMIResult>> { 155 | let query_language = BSTR::from("WQL"); 156 | let query = BSTR::from(query.as_ref()); 157 | 158 | let stream = AsyncQueryResultStreamInner::new(); 159 | // The internal RefCount has initial value = 1. 160 | let p_sink = QuerySink { 161 | stream: stream.clone(), 162 | }; 163 | let p_sink_handle: IWbemObjectSink = p_sink.into(); 164 | 165 | unsafe { 166 | // As p_sink's RefCount = 1 before this call, 167 | // p_sink won't be dropped at the end of ExecNotificationQueryAsync 168 | self.svc.ExecNotificationQueryAsync( 169 | &query_language, 170 | &query, 171 | Default::default(), 172 | None, 173 | &p_sink_handle, 174 | )? 175 | }; 176 | 177 | Ok(AsyncQueryResultStream::new( 178 | stream, 179 | self.clone(), 180 | p_sink_handle, 181 | )) 182 | } 183 | 184 | /// Async version of [`raw_notification`](WMIConnection#method.raw_notification) 185 | /// Execute a free-text query and deserialize the incoming events. 186 | /// Returns a stream of WMIResult\. 187 | /// Can be used either with a struct (like `query` and `filtered_query`), 188 | /// but also with a generic map. 189 | /// 190 | /// ```edition2018 191 | /// # use wmi::*; 192 | /// # use std::collections::HashMap; 193 | /// # use futures::{executor::block_on, StreamExt}; 194 | /// # #[cfg(not(feature = "test"))] 195 | /// # fn main() {} 196 | /// # #[cfg(feature = "test")] 197 | /// # fn main() -> wmi::WMIResult<()> { 198 | /// # tests::ignore_access_denied(block_on(exec_async_query())) 199 | /// # } 200 | /// # 201 | /// # async fn exec_async_query() -> WMIResult<()> { 202 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 203 | /// let mut stream = con.async_raw_notification::>("SELECT ProcessID, ProcessName FROM Win32_ProcessStartTrace")?; 204 | /// # let event = stream.next().await.unwrap()?; 205 | /// # Ok(()) // This query will fail when not run as admin 206 | /// # } 207 | /// ``` 208 | pub fn async_raw_notification( 209 | &self, 210 | query: impl AsRef, 211 | ) -> WMIResult>> 212 | where 213 | T: serde::de::DeserializeOwned, 214 | { 215 | let stream = self 216 | .exec_notification_query_async(query)? 217 | .map(|item| match item { 218 | Ok(wbem_class_obj) => wbem_class_obj.into_desr(), 219 | Err(e) => Err(e), 220 | }); 221 | Ok(stream) 222 | } 223 | 224 | /// Subscribe to the T event and return a stream of WMIResult\. 225 | /// 226 | /// ```edition2018 227 | /// # use wmi::*; 228 | /// # use std::collections::HashMap; 229 | /// # use futures::executor::block_on; 230 | /// # #[cfg(not(feature = "test"))] 231 | /// # fn main() {} 232 | /// # #[cfg(feature = "test")] 233 | /// # fn main() -> wmi::WMIResult<()> { 234 | /// # tests::ignore_access_denied(block_on(exec_async_query())) 235 | /// # } 236 | /// # 237 | /// # async fn exec_async_query() -> WMIResult<()> { 238 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 239 | /// use futures::StreamExt; 240 | /// use serde::Deserialize; 241 | /// 242 | /// #[derive(Deserialize, Debug)] 243 | /// struct Win32_ProcessStartTrace { 244 | /// ProcessID: u32, 245 | /// ProcessName: String, 246 | /// } 247 | /// 248 | /// let mut stream = con.async_notification::()?; 249 | /// 250 | /// let event = stream.next().await.unwrap()?; 251 | /// # Ok(()) // This query will fail when not run as admin 252 | /// # } 253 | /// ``` 254 | pub fn async_notification(&self) -> WMIResult>> 255 | where 256 | T: serde::de::DeserializeOwned, 257 | { 258 | let query_text = build_notification_query::(None, None)?; 259 | self.async_raw_notification(query_text) 260 | } 261 | 262 | /// Subscribe to the T event, while filtering according to `filters`. 263 | /// Returns a stream of WMIResult\. 264 | /// 265 | /// ```edition2018 266 | /// # use wmi::*; 267 | /// # use futures::{future::FutureExt, select}; 268 | /// # fn main() -> wmi::WMIResult<()> { 269 | /// # async_std::task::block_on(async { 270 | /// # select! { // End in 3 seconds or on event. 271 | /// # () = async_std::task::sleep(std::time::Duration::from_secs(3)).fuse() => Ok(()), 272 | /// # r = exec_async_query().fuse() => r 273 | /// # } 274 | /// # }) 275 | /// # } 276 | /// # 277 | /// # async fn exec_async_query() -> WMIResult<()> { 278 | /// # use std::{collections::HashMap, time::Duration}; 279 | /// # let con = WMIConnection::new(COMLibrary::new()?)?; 280 | /// use futures::StreamExt; 281 | /// use serde::Deserialize; 282 | /// #[derive(Deserialize, Debug)] 283 | /// struct __InstanceCreationEvent { 284 | /// TargetInstance: Win32_Process, 285 | /// } 286 | /// 287 | /// #[derive(Deserialize, Debug)] 288 | /// struct Win32_Process { 289 | /// ProcessID: u32, 290 | /// } 291 | /// 292 | /// let mut filters = HashMap::new(); 293 | /// 294 | /// filters.insert("TargetInstance".to_owned(), FilterValue::is_a::()?); 295 | /// 296 | /// let mut stream = con.async_filtered_notification::<__InstanceCreationEvent>(&filters, Some(Duration::from_secs(1)))?; 297 | /// 298 | /// let event = stream.next().await.unwrap()?; 299 | /// # Ok(()) 300 | /// # } 301 | /// ``` 302 | pub fn async_filtered_notification( 303 | &self, 304 | filters: &HashMap, 305 | within: Option, 306 | ) -> WMIResult>> 307 | where 308 | T: serde::de::DeserializeOwned, 309 | { 310 | let query_text = build_notification_query::(Some(filters), within)?; 311 | self.async_raw_notification(query_text) 312 | } 313 | } 314 | 315 | #[cfg(test)] 316 | mod tests { 317 | use crate::{tests::fixtures::*, FilterValue, WMIError}; 318 | use futures::StreamExt; 319 | use serde::Deserialize; 320 | use std::{collections::HashMap, time::Duration}; 321 | 322 | #[cfg(feature = "chrono")] 323 | use chrono::Datelike; 324 | use windows::Win32::System::Wmi::WBEM_E_UNPARSABLE_QUERY; 325 | 326 | const TEST_QUERY: &str = 327 | "SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime'"; 328 | 329 | pub fn notification_filters() -> HashMap { 330 | let mut map = HashMap::::new(); 331 | map.insert( 332 | "TargetInstance".to_owned(), 333 | FilterValue::is_a::().unwrap(), 334 | ); 335 | map 336 | } 337 | 338 | #[derive(Deserialize, Debug)] 339 | #[serde(rename = "__InstanceModificationEvent")] 340 | #[serde(rename_all = "PascalCase")] 341 | pub struct InstanceModification { 342 | target_instance: LocalTime, 343 | } 344 | 345 | #[derive(Deserialize, Debug)] 346 | #[serde(rename = "Win32_LocalTime")] 347 | #[serde(rename_all = "PascalCase")] 348 | pub struct LocalTime { 349 | year: u32, 350 | } 351 | 352 | #[test] 353 | fn it_works() { 354 | let wmi_con = wmi_con(); 355 | 356 | let mut enumerator = wmi_con.exec_notification_query(TEST_QUERY).unwrap(); 357 | 358 | let res = enumerator.next().unwrap(); 359 | let w = res.unwrap(); 360 | let mut props = w.list_properties().unwrap(); 361 | 362 | props.sort(); 363 | 364 | assert_eq!(props.len(), 4); 365 | assert_eq!(props[..2], ["PreviousInstance", "SECURITY_DESCRIPTOR"]); 366 | assert_eq!(props[props.len() - 2..], ["TIME_CREATED", "TargetInstance"]); 367 | } 368 | 369 | #[test] 370 | fn it_fails_gracefully() { 371 | let wmi_con = wmi_con(); 372 | 373 | let mut enumerator = wmi_con.exec_notification_query("SELECT NoSuchField FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime'").unwrap(); 374 | 375 | let res = enumerator.next().unwrap(); 376 | assert!(res.is_ok()); 377 | 378 | let props = res.unwrap().list_properties().unwrap(); 379 | assert_eq!(props.len(), 0); 380 | } 381 | 382 | #[test] 383 | fn it_fails_gracefully_with_invalid_sql() { 384 | let wmi_con = wmi_con(); 385 | 386 | let result = wmi_con.exec_notification_query("42"); 387 | 388 | match result { 389 | Ok(_) => assert!(false), 390 | Err(wmi_err) => match wmi_err { 391 | WMIError::HResultError { hres } => assert_eq!(hres, WBEM_E_UNPARSABLE_QUERY.0), 392 | _ => assert!(false), 393 | }, 394 | } 395 | } 396 | 397 | #[test] 398 | #[cfg(feature = "chrono")] 399 | fn it_can_run_raw_notification() { 400 | let wmi_con = wmi_con(); 401 | 402 | let mut iterator = wmi_con 403 | .raw_notification::(TEST_QUERY) 404 | .unwrap(); 405 | 406 | let local_time = iterator.next().unwrap(); 407 | assert!(local_time.is_ok()); 408 | 409 | let local_time = local_time.unwrap().target_instance; 410 | assert_eq!(local_time.year as i32, chrono::Local::now().year()); 411 | } 412 | 413 | #[test] 414 | #[cfg(feature = "time")] 415 | fn it_can_run_raw_notification_on_time_crate() { 416 | let wmi_con = wmi_con(); 417 | 418 | let mut iterator = wmi_con 419 | .raw_notification::(TEST_QUERY) 420 | .unwrap(); 421 | 422 | let local_time = iterator.next().unwrap(); 423 | assert!(local_time.is_ok()); 424 | 425 | let local_time = local_time.unwrap().target_instance; 426 | assert_eq!( 427 | local_time.year as i32, 428 | time::OffsetDateTime::now_utc().year() 429 | ); 430 | } 431 | 432 | #[test] 433 | #[cfg(feature = "chrono")] 434 | fn it_can_run_filtered_notification() { 435 | let wmi_con = wmi_con(); 436 | 437 | let mut iterator = wmi_con 438 | .filtered_notification::( 439 | ¬ification_filters(), 440 | Some(Duration::from_secs_f32(0.1)), 441 | ) 442 | .unwrap(); 443 | 444 | let local_time = iterator.next().unwrap(); 445 | assert!(local_time.is_ok()); 446 | 447 | let local_time = local_time.unwrap().target_instance; 448 | assert_eq!(local_time.year as i32, chrono::Local::now().year()); 449 | } 450 | 451 | #[test] 452 | #[cfg(feature = "time")] 453 | fn it_can_run_filtered_notification_on_time_crate() { 454 | let wmi_con = wmi_con(); 455 | 456 | let mut iterator = wmi_con 457 | .filtered_notification::( 458 | ¬ification_filters(), 459 | Some(Duration::from_secs_f32(0.1)), 460 | ) 461 | .unwrap(); 462 | 463 | let local_time = iterator.next().unwrap(); 464 | assert!(local_time.is_ok()); 465 | 466 | let local_time = local_time.unwrap().target_instance; 467 | assert_eq!( 468 | local_time.year as i32, 469 | time::OffsetDateTime::now_utc().year() 470 | ); 471 | } 472 | 473 | #[async_std::test] 474 | async fn async_it_works_async_std() { 475 | let wmi_con = wmi_con(); 476 | 477 | let result = wmi_con 478 | .exec_notification_query_async(TEST_QUERY) 479 | .unwrap() 480 | .next() 481 | .await 482 | .unwrap(); 483 | 484 | assert!(result.is_ok()); 485 | } 486 | 487 | #[tokio::test] 488 | async fn async_it_works_async_tokio() { 489 | let wmi_con = wmi_con(); 490 | 491 | let result = wmi_con 492 | .exec_notification_query_async(TEST_QUERY) 493 | .unwrap() 494 | .next() 495 | .await 496 | .unwrap(); 497 | 498 | assert!(result.is_ok()); 499 | } 500 | 501 | #[async_std::test] 502 | async fn async_it_handles_invalid_query() { 503 | let wmi_con = wmi_con(); 504 | 505 | let result = wmi_con.exec_notification_query_async("Invalid Query"); 506 | 507 | assert!(result.is_err()); 508 | if let WMIError::HResultError { hres } = result.err().unwrap() { 509 | assert_eq!(hres, WBEM_E_UNPARSABLE_QUERY.0); 510 | } else { 511 | assert!(false, "Invalid WMIError type"); 512 | } 513 | } 514 | 515 | #[async_std::test] 516 | #[cfg(feature = "chrono")] 517 | async fn async_it_provides_raw_notification_result() { 518 | let wmi_con = wmi_con(); 519 | 520 | let result = wmi_con 521 | .async_raw_notification::(TEST_QUERY) 522 | .unwrap() 523 | .next() 524 | .await 525 | .unwrap(); 526 | 527 | assert!(result.is_ok()); 528 | assert_eq!( 529 | result.unwrap().target_instance.year as i32, 530 | chrono::Local::now().year() 531 | ) 532 | } 533 | 534 | #[async_std::test] 535 | #[cfg(feature = "time")] 536 | async fn async_it_provides_raw_notification_result_on_time_crate() { 537 | let wmi_con = wmi_con(); 538 | 539 | let result = wmi_con 540 | .async_raw_notification::(TEST_QUERY) 541 | .unwrap() 542 | .next() 543 | .await 544 | .unwrap(); 545 | 546 | assert!(result.is_ok()); 547 | assert_eq!( 548 | result.unwrap().target_instance.year as i32, 549 | time::OffsetDateTime::now_utc().year() 550 | ) 551 | } 552 | 553 | #[async_std::test] 554 | #[cfg(feature = "chrono")] 555 | async fn async_it_provides_filtered_notification_result() { 556 | let wmi_con = wmi_con(); 557 | 558 | let result = wmi_con 559 | .async_filtered_notification::( 560 | ¬ification_filters(), 561 | Some(Duration::from_secs_f32(0.1)), 562 | ) 563 | .unwrap() 564 | .next() 565 | .await 566 | .unwrap(); 567 | 568 | assert!(result.is_ok()); 569 | assert_eq!( 570 | result.unwrap().target_instance.year as i32, 571 | chrono::Local::now().year() 572 | ) 573 | } 574 | 575 | #[async_std::test] 576 | #[cfg(feature = "time")] 577 | async fn async_it_provides_filtered_notification_result_on_time_crate() { 578 | let wmi_con = wmi_con(); 579 | 580 | let result = wmi_con 581 | .async_filtered_notification::( 582 | ¬ification_filters(), 583 | Some(Duration::from_secs_f32(0.1)), 584 | ) 585 | .unwrap() 586 | .next() 587 | .await 588 | .unwrap(); 589 | 590 | assert!(result.is_ok()); 591 | assert_eq!( 592 | result.unwrap().target_instance.year as i32, 593 | time::OffsetDateTime::now_utc().year() 594 | ) 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/query_sink.rs: -------------------------------------------------------------------------------- 1 | use crate::{result_enumerator::IWbemClassWrapper, WMIConnection, WMIError, WMIResult}; 2 | use futures::Stream; 3 | use log::trace; 4 | use std::{ 5 | collections::VecDeque, 6 | sync::{Arc, Mutex}, 7 | task::{Poll, Waker}, 8 | }; 9 | use windows::core::{implement, Ref, Result as WinResult, BSTR, HRESULT}; 10 | use windows::Win32::Foundation::E_POINTER; 11 | use windows::Win32::System::Wmi::{ 12 | IWbemClassObject, IWbemObjectSink, IWbemObjectSink_Impl, WBEM_STATUS_COMPLETE, 13 | }; 14 | 15 | #[derive(Default)] 16 | pub struct AsyncQueryResultStreamImpl { 17 | buf: VecDeque>, 18 | is_done: bool, 19 | waker: Option, 20 | } 21 | 22 | /// We wrap the internal objects to ensure that the waker is correctly called when new data is available or when the query is done. 23 | /// 24 | /// If the waker is still `None`, we know that `poll_next` has not been called yet. 25 | /// We can fill the buffer, and once `poll_next` is called, it'll return `Poll::Ready` and there's no need to wake the stream manually. 26 | /// 27 | /// Once the internal buffer is fully consumed (or empty to begin with) and `poll_next` is called, it'll set the waker and return `Poll::Pending`. 28 | /// Because the waker is set, we can wake the stream. 29 | impl AsyncQueryResultStreamImpl { 30 | pub fn extend(&mut self, iter: impl IntoIterator>) { 31 | self.buf.extend(iter); 32 | 33 | if let Some(waker) = self.waker.as_ref() { 34 | waker.wake_by_ref(); 35 | } 36 | } 37 | 38 | pub fn set_done(&mut self) { 39 | self.is_done = true; 40 | 41 | if let Some(waker) = self.waker.as_ref() { 42 | waker.wake_by_ref(); 43 | } 44 | } 45 | } 46 | 47 | /// A stream of WMI query results. 48 | /// 49 | /// When dropped, the stream is properly cancelled and the resources freed. 50 | pub struct AsyncQueryResultStream { 51 | inner: AsyncQueryResultStreamInner, 52 | connection: WMIConnection, 53 | sink: IWbemObjectSink, 54 | } 55 | 56 | impl AsyncQueryResultStream { 57 | pub fn new( 58 | inner: AsyncQueryResultStreamInner, 59 | connection: WMIConnection, 60 | sink: IWbemObjectSink, 61 | ) -> Self { 62 | Self { 63 | inner, 64 | connection, 65 | sink, 66 | } 67 | } 68 | } 69 | 70 | impl Drop for AsyncQueryResultStream { 71 | fn drop(&mut self) { 72 | let _r = unsafe { self.connection.svc.CancelAsyncCall(&self.sink) }; 73 | } 74 | } 75 | 76 | /// We use a mutex to synchronize the consumer and the calls from the WMI-managed thread. 77 | /// A blocking mutex is used because we want to be runtime agnostic 78 | /// and because according to [`tokio::sync::Mutex`](https://docs.rs/tokio/tokio/tokio/sync/struct.Mutex.html): 79 | /// > The primary use case for the async mutex is to provide shared mutable access to IO resources such as a database connection. If the value behind the mutex is just data, it’s usually appropriate to use a blocking mutex 80 | #[derive(Default, Clone)] 81 | pub struct AsyncQueryResultStreamInner(Arc>); 82 | 83 | impl AsyncQueryResultStreamInner { 84 | pub fn new() -> Self { 85 | Self(Arc::new(Mutex::new(AsyncQueryResultStreamImpl::default()))) 86 | } 87 | 88 | fn extend(&self, iter: impl IntoIterator>) { 89 | let mut lock = self.0.lock().unwrap(); 90 | lock.extend(iter); 91 | } 92 | 93 | fn set_done(&self) { 94 | let mut lock = self.0.lock().unwrap(); 95 | lock.set_done(); 96 | } 97 | } 98 | 99 | impl Stream for AsyncQueryResultStream { 100 | type Item = WMIResult; 101 | 102 | fn poll_next( 103 | self: std::pin::Pin<&mut Self>, 104 | cx: &mut std::task::Context<'_>, 105 | ) -> Poll> { 106 | let waker = cx.waker(); 107 | let mut inner = self.inner.0.lock().unwrap(); 108 | 109 | if !inner 110 | .waker 111 | .as_ref() 112 | .map(|current_waker| waker.will_wake(current_waker)) 113 | .unwrap_or(false) 114 | { 115 | inner.waker.replace(waker.clone()); 116 | } 117 | 118 | let next = inner.buf.pop_back(); 119 | 120 | match next { 121 | Some(item) => { 122 | trace!("poll_next: item found"); 123 | Poll::Ready(Some(item)) 124 | } 125 | None => { 126 | if inner.is_done { 127 | trace!("poll_next: done"); 128 | Poll::Ready(None) 129 | } else { 130 | trace!("poll_next: item not found"); 131 | Poll::Pending 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | #[implement(IWbemObjectSink)] 139 | pub struct QuerySink { 140 | pub stream: AsyncQueryResultStreamInner, 141 | } 142 | 143 | /// Implementation for [IWbemObjectSink](https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-iwbemobjectsink). 144 | /// This [Sink](https://en.wikipedia.org/wiki/Sink_(computing)) 145 | /// receives asynchronously the result of the query, through Indicate calls. 146 | /// When finished,the SetStatus method is called. 147 | /// # 148 | impl IWbemObjectSink_Impl for QuerySink_Impl { 149 | fn Indicate( 150 | &self, 151 | lObjectCount: i32, 152 | apObjArray: *const Option, 153 | ) -> WinResult<()> { 154 | trace!("Indicate call with {} objects", lObjectCount); 155 | // Case of an incorrect or too restrictive query 156 | if lObjectCount <= 0 { 157 | return Ok(()); 158 | } 159 | 160 | let lObjectCount = lObjectCount as usize; 161 | let mut res = Ok(()); 162 | 163 | // Safety: 164 | // 165 | // The safety points are mainly guaranteed by the contract of the Indicate API. 166 | // `apObjArray` is an array pointer to `IWbemClassObject`, whose length is provided by 167 | // lObjectCount. Hence: 168 | // - `apObjArray` is is valid for lObjectCount * reads. `IWbemClassObject` is 169 | // a wrapper on a NonNull pointer. The Option makes it nullable, but it uses the right 170 | // alignment and size. 171 | // - `apObjArray` points to lObjectCount consecutive pointers. 172 | // - the memory behind this pointer is not modified while the slice is alive 173 | let objs = unsafe { std::slice::from_raw_parts(apObjArray, lObjectCount) }; 174 | self.stream.extend(objs.iter().map(|obj| match obj { 175 | Some(p_el) => Ok(IWbemClassWrapper::new(p_el.clone())), 176 | None => { 177 | res = Err(E_POINTER.into()); 178 | Err(WMIError::NullPointerResult) 179 | } 180 | })); 181 | 182 | res 183 | } 184 | 185 | fn SetStatus( 186 | &self, 187 | lFlags: i32, 188 | _hResult: HRESULT, 189 | _strParam: &BSTR, 190 | _pObjParam: Ref, 191 | ) -> WinResult<()> { 192 | // SetStatus is called only once as flag=WBEM_FLAG_BIDIRECTIONAL in ExecQueryAsync 193 | // https://docs.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemobjectsink-setstatus 194 | // If you do not specify WBEM_FLAG_SEND_STATUS when calling your provider or service method, 195 | // you are guaranteed to receive one and only one call to SetStatus 196 | 197 | if lFlags == WBEM_STATUS_COMPLETE.0 { 198 | trace!("End of async result, closing transmitter"); 199 | self.stream.set_done(); 200 | } 201 | Ok(()) 202 | } 203 | } 204 | 205 | #[allow(non_snake_case)] 206 | #[allow(non_camel_case_types)] 207 | #[cfg(test)] 208 | mod tests { 209 | use super::*; 210 | use crate::tests::fixtures::*; 211 | use futures::StreamExt; 212 | use windows::core::{IUnknown, Interface}; 213 | 214 | #[async_std::test] 215 | async fn async_it_should_send_result() { 216 | let con = wmi_con(); 217 | let stream = AsyncQueryResultStreamInner::new(); 218 | let sink = QuerySink { 219 | stream: stream.clone(), 220 | }; 221 | let p_sink: IWbemObjectSink = sink.into(); 222 | let mut stream = AsyncQueryResultStream::new(stream, con.clone(), p_sink.clone()); 223 | 224 | let raw_os = con 225 | .get_object(r#"\\.\root\cimv2:Win32_OperatingSystem=@"#) 226 | .unwrap(); 227 | let raw_os2 = con 228 | .get_object(r#"\\.\root\cimv2:Win32_OperatingSystem=@"#) 229 | .unwrap(); 230 | 231 | // tests on ref count before Indicate call 232 | unsafe { 233 | let test_ptr: IUnknown = raw_os.inner.clone().cast().unwrap(); 234 | let refcount = (test_ptr.vtable().AddRef)(std::mem::transmute_copy(&test_ptr)); 235 | // 1 from p_sink + 1 from test_ptr + 1 from AddRef 236 | assert_eq!(refcount, 3); 237 | let refcount = (test_ptr.vtable().Release)(std::mem::transmute_copy(&test_ptr)); 238 | // 1 from p_sink + 1 from test_ptr 239 | assert_eq!(refcount, 2); 240 | } 241 | 242 | unsafe { 243 | p_sink 244 | .Indicate(&[Some(raw_os.inner.clone()), Some(raw_os2.inner.clone())]) 245 | .unwrap(); 246 | } 247 | // tests on ref count after Indicate call 248 | unsafe { 249 | let test_ptr: IUnknown = raw_os.inner.clone().cast().unwrap(); 250 | let refcount = (test_ptr.vtable().AddRef)(std::mem::transmute_copy(&test_ptr)); 251 | // 1 from p_sink + 1 from test_ptr + 1 from AddRef + 1 from the Indicate call 252 | assert_eq!(refcount, 4); 253 | let refcount = (test_ptr.vtable().Release)(std::mem::transmute_copy(&test_ptr)); 254 | assert_eq!(refcount, 3); 255 | } 256 | 257 | let first = stream.next().await.unwrap().unwrap(); 258 | 259 | assert_eq!(first.class().unwrap().as_str(), "Win32_OperatingSystem"); 260 | 261 | let second = stream.next().await.unwrap().unwrap(); 262 | assert_eq!(second.class().unwrap().as_str(), "Win32_OperatingSystem"); 263 | } 264 | 265 | #[async_std::test] 266 | async fn async_it_should_complete_after_set_status_call() { 267 | let con = wmi_con(); 268 | let stream = AsyncQueryResultStreamInner::new(); 269 | let sink = QuerySink { 270 | stream: stream.clone(), 271 | }; 272 | let p_sink: IWbemObjectSink = sink.into(); 273 | let stream = AsyncQueryResultStream::new(stream, con.clone(), p_sink.clone()); 274 | 275 | unsafe { 276 | p_sink 277 | .SetStatus(WBEM_STATUS_COMPLETE.0, HRESULT(0), &BSTR::new(), None) 278 | .unwrap(); 279 | } 280 | 281 | let results: Vec<_> = stream.collect().await; 282 | 283 | assert!(results.is_empty()); 284 | } 285 | 286 | #[async_std::test] 287 | async fn async_it_should_return_e_pointer_after_indicate_call_with_null_pointer() { 288 | let con = wmi_con(); 289 | let stream = AsyncQueryResultStreamInner::new(); 290 | let sink = QuerySink { 291 | stream: stream.clone(), 292 | }; 293 | let p_sink: IWbemObjectSink = sink.into(); 294 | let mut stream = AsyncQueryResultStream::new(stream, con.clone(), p_sink.clone()); 295 | 296 | let arr = vec![None]; 297 | 298 | let result = unsafe { p_sink.Indicate(&arr) }; 299 | assert_eq!(result.unwrap_err().code(), E_POINTER); 300 | 301 | let item = stream.next().await.unwrap(); 302 | 303 | match item { 304 | Err(WMIError::NullPointerResult) => assert!(true), 305 | _ => assert!(false), 306 | } 307 | } 308 | 309 | #[async_std::test] 310 | async fn async_test_notification() { 311 | let con = wmi_con(); 312 | let inner = AsyncQueryResultStreamInner::new(); 313 | let sink = QuerySink { 314 | stream: inner.clone(), 315 | }; 316 | let p_sink: IWbemObjectSink = sink.into(); 317 | 318 | // Exec a notification to setup the sink properly 319 | let query_language = BSTR::from("WQL"); 320 | let query = BSTR::from( 321 | "SELECT * FROM __InstanceModificationEvent \ 322 | WHERE TargetInstance ISA 'Win32_LocalTime'", 323 | ); 324 | 325 | unsafe { 326 | // As p_sink's RefCount = 1 before this call, 327 | // p_sink won't be dropped at the end of ExecNotificationQueryAsync 328 | con.svc 329 | .ExecNotificationQueryAsync( 330 | &query_language, 331 | &query, 332 | Default::default(), 333 | None, 334 | &p_sink, 335 | ) 336 | .unwrap() 337 | }; 338 | 339 | // lets cheat by keeping the inner stream locally, before dropping the stream object, 340 | // which will cancel the notification 341 | let mut stream = AsyncQueryResultStream::new(inner.clone(), con, p_sink); 342 | 343 | let elem = stream.next().await; 344 | assert!(elem.is_some()); 345 | 346 | assert_eq!(inner.0.lock().unwrap().is_done, false); 347 | // end the stream by dropping it 348 | drop(stream); 349 | 350 | // Check the "is_done" flag has been set as the SetStatus member was called. 351 | // This is not necessarily done on the same thread, wait a bit for the SetStatus function 352 | // to be called. 353 | for _ in 0..5 { 354 | if inner.0.lock().unwrap().is_done { 355 | break; 356 | } 357 | std::thread::sleep(std::time::Duration::from_millis(500)); 358 | } 359 | assert_eq!(inner.0.lock().unwrap().is_done, true); 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/result_enumerator.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | de::wbem_class_de::from_wbem_class_obj, safearray::safe_array_to_vec_of_strings, Variant, 3 | WMIError, WMIResult, 4 | }; 5 | use log::trace; 6 | use serde::{ 7 | de, 8 | ser::{Error, SerializeMap}, 9 | Serialize, 10 | }; 11 | use std::ptr::{self, NonNull}; 12 | use windows::Win32::System::Ole::SafeArrayDestroy; 13 | use windows::Win32::System::Variant::VARIANT; 14 | use windows::Win32::System::Wmi::{ 15 | IEnumWbemClassObject, IWbemClassObject, CIMTYPE_ENUMERATION, WBEM_FLAG_ALWAYS, 16 | WBEM_FLAG_NONSYSTEM_ONLY, WBEM_INFINITE, 17 | }; 18 | use windows::{ 19 | core::{BSTR, HSTRING, PCWSTR}, 20 | Win32::System::Wmi::WBEM_CONDITION_FLAG_TYPE, 21 | }; 22 | 23 | /// A wrapper around a [IWbemClassObject](https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nn-wbemcli-iwbemclassobject). 24 | /// 25 | #[derive(Clone, Debug, PartialEq, Eq)] 26 | pub struct IWbemClassWrapper { 27 | pub inner: IWbemClassObject, 28 | } 29 | 30 | impl IWbemClassWrapper { 31 | pub fn new(inner: IWbemClassObject) -> Self { 32 | Self { inner } 33 | } 34 | 35 | /// Return the names of all the properties of the object. 36 | /// See more at . 37 | pub fn list_properties(&self) -> WMIResult> { 38 | let p_names = unsafe { 39 | self.inner.GetNames( 40 | None, 41 | WBEM_CONDITION_FLAG_TYPE(WBEM_FLAG_ALWAYS.0 | WBEM_FLAG_NONSYSTEM_ONLY.0), 42 | ptr::null_mut(), 43 | ) 44 | }?; 45 | 46 | let p_names = NonNull::new(p_names).ok_or(WMIError::NullPointerResult)?; 47 | 48 | let res = unsafe { safe_array_to_vec_of_strings(p_names) }; 49 | 50 | unsafe { SafeArrayDestroy(p_names.as_ptr()) }?; 51 | 52 | res 53 | } 54 | 55 | /// Get the value of a property. 56 | /// See more at . 57 | pub fn get_property(&self, property_name: &str) -> WMIResult { 58 | let name_prop = HSTRING::from(property_name); 59 | 60 | let mut vt_prop = VARIANT::default(); 61 | 62 | let mut cim_type = 0; 63 | 64 | unsafe { 65 | self.inner.Get( 66 | PCWSTR::from_raw(name_prop.as_ptr()), 67 | 0, 68 | &mut vt_prop, 69 | Some(&mut cim_type), 70 | None, 71 | )?; 72 | } 73 | 74 | let property_value = Variant::from_variant(&vt_prop)?; 75 | 76 | property_value.convert_into_cim_type(CIMTYPE_ENUMERATION(cim_type)) 77 | } 78 | 79 | /// Set the value of a property. 80 | /// See more at . 81 | pub fn put_property(&self, property_name: &str, value: impl Into) -> WMIResult<()> { 82 | let name_prop = HSTRING::from(property_name); 83 | 84 | let value = value.into(); 85 | let vt_prop: VARIANT = value.try_into()?; 86 | 87 | // "In every other case, vtType must be 0 (zero)" 88 | // See more at . 89 | let cim_type = 0; 90 | 91 | unsafe { 92 | self.inner 93 | .Put(PCWSTR::from_raw(name_prop.as_ptr()), 0, &vt_prop, cim_type)?; 94 | } 95 | 96 | Ok(()) 97 | } 98 | 99 | /// Get the input signature class for the named method. 100 | /// See [`crate::WMIConnection::exec_method`] for a usage example. 101 | /// See more at . 102 | /// 103 | /// The method may have no input parameters, such as in this case: . 104 | /// In such cases, `None` is returned. 105 | pub fn get_method(&self, name: impl AsRef) -> WMIResult> { 106 | let method = BSTR::from(name.as_ref()); 107 | 108 | // Retrieve the input signature of the WMI method. 109 | // The fields of the resulting IWbemClassObject will have the names and types of the WMI method's input parameters 110 | let mut input_signature = None; 111 | 112 | unsafe { 113 | self.inner.GetMethod( 114 | &method, 115 | Default::default(), 116 | &mut input_signature, 117 | std::ptr::null_mut(), 118 | )?; 119 | } 120 | 121 | Ok(input_signature.map(IWbemClassWrapper::new)) 122 | } 123 | 124 | /// Get the input and output signature classes for the named method. 125 | /// See [`crate::WMIConnection::exec_method`] for a usage example. 126 | /// Note: GetMethod can only be called on a class definition. 127 | /// See more at . 128 | /// 129 | /// The method may have no in or out parameters, such as in this case: . 130 | /// In such cases, `None` is returned. 131 | pub fn get_method_in_out( 132 | &self, 133 | name: impl AsRef, 134 | ) -> WMIResult<(Option, Option)> { 135 | let method = BSTR::from(name.as_ref()); 136 | 137 | // Retrieve the input signature of the WMI method. 138 | // The fields of the resulting IWbemClassObject will have the names and types of the WMI method's input parameters 139 | let mut input_signature = None; 140 | let mut output_signature = None; 141 | 142 | unsafe { 143 | self.inner.GetMethod( 144 | &method, 145 | Default::default(), 146 | &mut input_signature, 147 | &mut output_signature, 148 | )?; 149 | } 150 | 151 | Ok(( 152 | input_signature.map(IWbemClassWrapper::new), 153 | output_signature.map(IWbemClassWrapper::new), 154 | )) 155 | } 156 | 157 | /// Create a new instance a class. See [`crate::WMIConnection::exec_method`] for a usage example. 158 | /// See more at . 159 | pub fn spawn_instance(&self) -> WMIResult { 160 | let inst = unsafe { self.inner.SpawnInstance(Default::default())? }; 161 | let inst = IWbemClassWrapper::new(inst); 162 | 163 | Ok(inst) 164 | } 165 | 166 | pub fn path(&self) -> WMIResult { 167 | self.get_property("__Path").and_then(Variant::try_into) 168 | } 169 | 170 | pub fn class(&self) -> WMIResult { 171 | self.get_property("__Class").and_then(Variant::try_into) 172 | } 173 | 174 | pub fn into_desr(self) -> WMIResult 175 | where 176 | T: de::DeserializeOwned, 177 | { 178 | from_wbem_class_obj(self) 179 | } 180 | } 181 | 182 | impl Serialize for IWbemClassWrapper { 183 | fn serialize(&self, serializer: S) -> Result 184 | where 185 | S: serde::Serializer, 186 | { 187 | let properties = self.list_properties().map_err(Error::custom)?; 188 | let mut s = serializer.serialize_map(Some(properties.len()))?; 189 | for property in properties.iter() { 190 | let value = self.get_property(property).unwrap(); 191 | s.serialize_entry(property, &value)?; 192 | } 193 | s.end() 194 | } 195 | } 196 | 197 | pub(crate) struct QueryResultEnumerator { 198 | p_enumerator: IEnumWbemClassObject, 199 | } 200 | 201 | impl QueryResultEnumerator { 202 | pub(crate) fn new(p_enumerator: IEnumWbemClassObject) -> Self { 203 | Self { p_enumerator } 204 | } 205 | } 206 | 207 | impl Iterator for QueryResultEnumerator { 208 | type Item = WMIResult; 209 | 210 | fn next(&mut self) -> Option { 211 | let mut objs = [None; 1]; 212 | let mut return_value = 0; 213 | 214 | let res = unsafe { 215 | self.p_enumerator 216 | .Next(WBEM_INFINITE, &mut objs, &mut return_value) 217 | }; 218 | 219 | if let Err(e) = res.ok() { 220 | return Some(Err(e.into())); 221 | } 222 | 223 | if return_value == 0 { 224 | return None; 225 | } 226 | 227 | trace!( 228 | "Got enumerator {:?} and obj {:?}", 229 | self.p_enumerator, 230 | &objs[0] 231 | ); 232 | 233 | let [obj] = objs; 234 | let pcls_ptr = obj.ok_or(WMIError::NullPointerResult); 235 | 236 | match pcls_ptr { 237 | Err(e) => Some(Err(e)), 238 | Ok(pcls_ptr) => Some(Ok(IWbemClassWrapper::new(pcls_ptr))), 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/safearray.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | utils::{WMIError, WMIResult}, 3 | variant::IUnknownWrapper, 4 | Variant, 5 | }; 6 | use std::{ 7 | iter::Iterator, 8 | ptr::{null_mut, NonNull}, 9 | }; 10 | use windows::Win32::System::Com::SAFEARRAY; 11 | use windows::Win32::System::Ole::{SafeArrayAccessData, SafeArrayUnaccessData}; 12 | use windows::Win32::System::Variant::*; 13 | use windows::{ 14 | core::{IUnknown, Interface, BSTR}, 15 | Win32::Foundation::VARIANT_BOOL, 16 | }; 17 | 18 | #[derive(Debug)] 19 | pub struct SafeArrayAccessor { 20 | arr: NonNull, 21 | p_data: *mut T, 22 | } 23 | 24 | /// An accessor to SafeArray, which: 25 | /// 1. Locks the array so the data can be read. 26 | /// 2. Unlocks the array once dropped. 27 | /// 28 | /// Pointers to a Safe Array can come from different places (like GetNames, WMI property value), 29 | /// which can have different drop behavior (GetNames require the caller to deallocate the array, 30 | /// while a WMI property must be deallocated via VariantClear). 31 | /// 32 | /// For this reason, we don't have a `struct SafeArray`. 33 | /// 34 | /// However, accessing the data of the array must be done using a lock, which is the responsibility 35 | /// of this struct. 36 | /// 37 | impl SafeArrayAccessor { 38 | /// Creates a new Accessor, locking the given array, 39 | /// 40 | /// # Safety 41 | /// 42 | /// This function is unsafe as it is the caller's responsibility to verify that the array is 43 | /// of items of type T. 44 | pub unsafe fn new(arr: NonNull) -> WMIResult { 45 | let mut p_data = null_mut(); 46 | 47 | if (*arr.as_ptr()).cDims != 1 { 48 | return Err(WMIError::UnimplementedArrayItem); 49 | } 50 | 51 | unsafe { SafeArrayAccessData(arr.as_ptr(), &mut p_data)? }; 52 | 53 | Ok(Self { 54 | arr, 55 | p_data: p_data as *mut T, 56 | }) 57 | } 58 | 59 | pub fn len(&self) -> u32 { 60 | unsafe { (*self.arr.as_ptr()).rgsabound[0].cElements } 61 | } 62 | 63 | pub fn is_empty(&self) -> bool { 64 | self.len() == 0 65 | } 66 | 67 | /// Return an iterator over the items of the array. 68 | pub fn iter(&self) -> impl Iterator + '_ { 69 | // Safety: See `iter_mut()`. 70 | let element_count = self.len(); 71 | 72 | (0..element_count).map(move |i| unsafe { &*self.p_data.offset(i as isize) }) 73 | } 74 | 75 | /// Return an iterator over the items of the array. 76 | pub fn iter_mut(&mut self) -> impl Iterator + '_ { 77 | // Safety: We required the caller of `new` to ensure that the array is valid and contains only items of type T (and is one dimensional). 78 | // `SafeArrayAccessData` returns a pointer to the data of the array, which can be accessed for `arr.rgsabound[0].cElements` elements. 79 | // See: https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-safearrayaccessdata#examples 80 | let element_count = self.len(); 81 | 82 | (0..element_count).map(move |i| unsafe { &mut *self.p_data.offset(i as isize) }) 83 | } 84 | } 85 | 86 | impl Drop for SafeArrayAccessor { 87 | fn drop(&mut self) { 88 | unsafe { 89 | let _result = SafeArrayUnaccessData(self.arr.as_ptr()); 90 | } 91 | } 92 | } 93 | 94 | /// # Safety 95 | /// 96 | /// The caller must ensure that the array is valid and contains only strings. 97 | pub unsafe fn safe_array_to_vec_of_strings(arr: NonNull) -> WMIResult> { 98 | let accessor = unsafe { SafeArrayAccessor::::new(arr)? }; 99 | 100 | accessor 101 | .iter() 102 | .map(|item| item.try_into().map_err(WMIError::from)) 103 | .collect() 104 | } 105 | 106 | /// # Safety 107 | /// 108 | /// The caller must ensure that the array is valid and contains elements on the specified type. 109 | pub unsafe fn safe_array_to_vec( 110 | arr: NonNull, 111 | item_type: VARENUM, 112 | ) -> WMIResult> { 113 | fn copy_type_to_vec( 114 | arr: NonNull, 115 | variant_builder: F, 116 | ) -> WMIResult> 117 | where 118 | T: Copy, 119 | F: Fn(T) -> Variant, 120 | { 121 | let accessor = unsafe { SafeArrayAccessor::::new(arr)? }; 122 | 123 | Ok(accessor.iter().map(|item| variant_builder(*item)).collect()) 124 | } 125 | 126 | match item_type { 127 | VT_I1 => copy_type_to_vec(arr, Variant::I1), 128 | VT_I2 => copy_type_to_vec(arr, Variant::I2), 129 | VT_I4 => copy_type_to_vec(arr, Variant::I4), 130 | VT_I8 => copy_type_to_vec(arr, Variant::I8), 131 | VT_UI1 => copy_type_to_vec(arr, Variant::UI1), 132 | VT_UI2 => copy_type_to_vec(arr, Variant::UI2), 133 | VT_UI4 => copy_type_to_vec(arr, Variant::UI4), 134 | VT_UI8 => copy_type_to_vec(arr, Variant::UI8), 135 | VT_R4 => copy_type_to_vec(arr, Variant::R4), 136 | VT_R8 => copy_type_to_vec(arr, Variant::R8), 137 | VT_BSTR => { 138 | let v = unsafe { safe_array_to_vec_of_strings(arr) }?; 139 | 140 | Ok(v.into_iter().map(Variant::String).collect()) 141 | } 142 | VT_BOOL => copy_type_to_vec::(arr, |item| Variant::Bool(item.as_bool())), 143 | VT_UNKNOWN => { 144 | // An array of `VT_UNKNOWN`s will release the references to the items once it is cleared. 145 | // Similar to how the docs of `VariantCopy` remark (https://learn.microsoft.com/en-us/windows/win32/api/oleauto/nf-oleauto-variantcopy#remarks), 146 | // we need to call `AddRef` to increment the object's reference count so it outlives the array. 147 | let accessor = unsafe { SafeArrayAccessor::<*mut _>::new(arr)? }; 148 | 149 | accessor 150 | .iter() 151 | .map(|item| { 152 | IUnknown::from_raw_borrowed(item) 153 | .cloned() 154 | .map(|item| Variant::Unknown(IUnknownWrapper::new(item))) 155 | .ok_or(WMIError::NullPointerResult) 156 | }) 157 | .collect() 158 | } 159 | // TODO: Add support for all other types of arrays. 160 | _ => Err(WMIError::UnimplementedArrayItem), 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/ser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod variant_ser; 2 | -------------------------------------------------------------------------------- /src/ser/variant_ser.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a custom serializer type, [`VariantStructSerializer`], 2 | //! to serialize a Rust struct into a HashMap mapping field name strings to [`Variant`] values 3 | use std::{any::type_name, fmt::Display}; 4 | 5 | use crate::{result_enumerator::IWbemClassWrapper, Variant, WMIConnection, WMIError}; 6 | use serde::{ 7 | ser::{Impossible, SerializeSeq, SerializeStruct}, 8 | Serialize, Serializer, 9 | }; 10 | use thiserror::Error; 11 | 12 | macro_rules! serialize_variant_err_stub { 13 | ($signature:ident, $type:ty) => { 14 | fn $signature(self, _v: $type) -> Result { 15 | Err(VariantSerializerError::UnsupportedVariantType( 16 | type_name::<$type>().to_string(), 17 | )) 18 | } 19 | }; 20 | } 21 | 22 | macro_rules! serialize_variant { 23 | ($signature:ident, $type:ty) => { 24 | fn $signature(self, v: $type) -> Result { 25 | Ok(Variant::from(v)) 26 | } 27 | }; 28 | } 29 | 30 | pub(crate) struct VariantSerializer<'a> { 31 | pub(crate) wmi: &'a WMIConnection, 32 | pub(crate) instance: Option, 33 | } 34 | 35 | impl<'a> Serializer for VariantSerializer<'a> { 36 | type Ok = Variant; 37 | type Error = VariantSerializerError; 38 | 39 | type SerializeSeq = VariantSeqSerializer<'a>; 40 | type SerializeTuple = Impossible; 41 | type SerializeTupleStruct = Impossible; 42 | type SerializeTupleVariant = Impossible; 43 | type SerializeMap = Impossible; 44 | type SerializeStruct = VariantInstanceSerializer<'a>; 45 | type SerializeStructVariant = Impossible; 46 | 47 | serialize_variant!(serialize_bool, bool); 48 | serialize_variant!(serialize_i8, i8); 49 | serialize_variant!(serialize_i16, i16); 50 | serialize_variant!(serialize_i32, i32); 51 | serialize_variant!(serialize_i64, i64); 52 | serialize_variant!(serialize_u8, u8); 53 | serialize_variant!(serialize_u16, u16); 54 | serialize_variant!(serialize_u32, u32); 55 | serialize_variant!(serialize_u64, u64); 56 | serialize_variant!(serialize_f32, f32); 57 | serialize_variant!(serialize_f64, f64); 58 | 59 | fn serialize_unit(self) -> Result { 60 | // When starting from an instance, deserializing a unit means returning the original instance unmodified. 61 | Ok(self.instance.map(Variant::from).unwrap_or(Variant::Empty)) 62 | } 63 | 64 | fn serialize_str(self, v: &str) -> Result { 65 | Ok(Variant::from(v.to_string())) 66 | } 67 | 68 | fn serialize_newtype_variant( 69 | self, 70 | name: &'static str, 71 | _variant_index: u32, 72 | variant: &'static str, 73 | _value: &T, 74 | ) -> Result 75 | where 76 | T: ?Sized + Serialize, 77 | { 78 | Err(VariantSerializerError::UnsupportedVariantType(format!( 79 | "{variant}::{name}" 80 | ))) 81 | } 82 | 83 | fn serialize_unit_struct(self, name: &'static str) -> Result { 84 | let ser = self.serialize_struct(name, 0)?; 85 | 86 | ser.end() 87 | } 88 | 89 | fn serialize_newtype_struct( 90 | self, 91 | _name: &'static str, 92 | value: &T, 93 | ) -> Result 94 | where 95 | T: ?Sized + Serialize, 96 | { 97 | value.serialize(self) 98 | } 99 | 100 | fn serialize_unit_variant( 101 | self, 102 | _name: &'static str, 103 | _variant_index: u32, 104 | variant: &'static str, 105 | ) -> Result { 106 | Ok(Variant::from(variant.to_string())) 107 | } 108 | 109 | // Generic serializer code not relevant to this use case 110 | 111 | serialize_variant_err_stub!(serialize_char, char); 112 | serialize_variant_err_stub!(serialize_bytes, &[u8]); 113 | 114 | fn serialize_none(self) -> Result { 115 | // we serialize to VT_NULL (explicit NULL semantic) rather than VT_EMPTY 116 | // (default state or uninitialized semantic) 117 | Ok(Variant::Null) 118 | } 119 | 120 | fn serialize_some(self, value: &T) -> Result 121 | where 122 | T: ?Sized + Serialize, 123 | { 124 | value.serialize(self) 125 | } 126 | 127 | fn serialize_seq(self, len: Option) -> Result { 128 | Ok(VariantSeqSerializer { 129 | seq: Vec::with_capacity(len.unwrap_or_default()), 130 | wmi: self.wmi, 131 | }) 132 | } 133 | 134 | fn serialize_tuple(self, _len: usize) -> Result { 135 | Err(VariantSerializerError::UnsupportedVariantType( 136 | "Tuple".to_string(), 137 | )) 138 | } 139 | 140 | fn serialize_tuple_struct( 141 | self, 142 | name: &'static str, 143 | _len: usize, 144 | ) -> Result { 145 | Err(VariantSerializerError::UnsupportedVariantType( 146 | name.to_string(), 147 | )) 148 | } 149 | 150 | fn serialize_tuple_variant( 151 | self, 152 | name: &'static str, 153 | _variant_index: u32, 154 | variant: &'static str, 155 | _len: usize, 156 | ) -> Result { 157 | Err(VariantSerializerError::UnsupportedVariantType(format!( 158 | "{variant}::{name}" 159 | ))) 160 | } 161 | 162 | fn serialize_map(self, _len: Option) -> Result { 163 | Err(VariantSerializerError::UnsupportedVariantType( 164 | "Map".to_string(), 165 | )) 166 | } 167 | 168 | fn serialize_struct( 169 | self, 170 | name: &'static str, 171 | _len: usize, 172 | ) -> Result { 173 | // We are only given an initialized instance when called from `exec_method`, 174 | // with the instance matching the method signature class. 175 | // Otherwise, we use the name of the struct to create. See test for `Win32_Process` with "Create" and `Win32_ProcessStartup`. 176 | let instance = match self.instance { 177 | Some(instance) => instance, 178 | None => self.wmi.get_object(name)?.spawn_instance()?, 179 | }; 180 | 181 | let ser = VariantInstanceSerializer { 182 | wmi: self.wmi, 183 | instance, 184 | }; 185 | 186 | Ok(ser) 187 | } 188 | 189 | fn serialize_struct_variant( 190 | self, 191 | name: &'static str, 192 | _variant_index: u32, 193 | variant: &'static str, 194 | _len: usize, 195 | ) -> Result { 196 | Err(VariantSerializerError::UnsupportedVariantType(format!( 197 | "{variant}::{name}" 198 | ))) 199 | } 200 | } 201 | 202 | #[derive(Debug, Error)] 203 | pub enum VariantSerializerError { 204 | #[error("Unknown error while serializing struct:\n{0}")] 205 | Unknown(String), 206 | #[error("{0} cannot be serialized to a Variant.")] 207 | UnsupportedVariantType(String), 208 | #[error("WMI error while serializing struct: \n {0}")] 209 | WMIError(#[from] WMIError), 210 | } 211 | 212 | impl serde::ser::Error for VariantSerializerError { 213 | fn custom(msg: T) -> Self 214 | where 215 | T: Display, 216 | { 217 | VariantSerializerError::Unknown(msg.to_string()) 218 | } 219 | } 220 | 221 | pub(crate) struct VariantInstanceSerializer<'a> { 222 | instance: IWbemClassWrapper, 223 | wmi: &'a WMIConnection, 224 | } 225 | 226 | impl<'a> SerializeStruct for VariantInstanceSerializer<'a> { 227 | type Ok = Variant; 228 | 229 | type Error = VariantSerializerError; 230 | 231 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> 232 | where 233 | T: ?Sized + Serialize, 234 | { 235 | let variant = value.serialize(VariantSerializer { 236 | wmi: self.wmi, 237 | instance: None, 238 | })?; 239 | 240 | self.instance.put_property(key, variant)?; 241 | 242 | Ok(()) 243 | } 244 | 245 | fn end(self) -> Result { 246 | Ok(Variant::Object(self.instance)) 247 | } 248 | } 249 | 250 | pub(crate) struct VariantSeqSerializer<'a> { 251 | seq: Vec, 252 | wmi: &'a WMIConnection, 253 | } 254 | 255 | impl<'a> SerializeSeq for VariantSeqSerializer<'a> { 256 | type Ok = Variant; 257 | type Error = VariantSerializerError; 258 | 259 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> 260 | where 261 | T: ?Sized + Serialize, 262 | { 263 | let variant = value.serialize(VariantSerializer { 264 | wmi: self.wmi, 265 | instance: None, 266 | })?; 267 | 268 | self.seq.push(variant); 269 | 270 | Ok(()) 271 | } 272 | 273 | fn end(self) -> Result { 274 | Ok(Variant::Array(self.seq)) 275 | } 276 | } 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use super::*; 281 | use crate::tests::fixtures::wmi_con; 282 | use serde::{Deserialize, Serialize}; 283 | use std::ptr; 284 | use windows::core::HSTRING; 285 | use windows::Win32::System::Wmi::{CIM_FLAG_ARRAY, CIM_SINT64, CIM_UINT64}; 286 | 287 | #[test] 288 | fn it_serialize_instance() { 289 | let wmi_con = wmi_con(); 290 | 291 | #[derive(Deserialize)] 292 | struct StdRegProv; 293 | 294 | #[derive(Serialize)] 295 | struct GetBinaryValue { 296 | sSubKeyName: String, 297 | sValueName: String, 298 | } 299 | 300 | let in_params = GetBinaryValue { 301 | sSubKeyName: r#"SYSTEM\CurrentControlSet\Control\Windows"#.to_string(), 302 | sValueName: "FullProcessInformationSID".to_string(), 303 | }; 304 | 305 | // Similar to how `exec_class_method` creates these objects. 306 | let method_instance = wmi_con 307 | .get_object("StdRegProv") 308 | .unwrap() 309 | .get_method("GetBinaryValue") 310 | .unwrap() 311 | .unwrap() 312 | .spawn_instance() 313 | .unwrap(); 314 | 315 | let instance_from_ser = in_params 316 | .serialize(VariantSerializer { 317 | wmi: &wmi_con, 318 | instance: Some(method_instance), 319 | }) 320 | .unwrap(); 321 | 322 | let instance_from_ser = match instance_from_ser { 323 | Variant::Object(instance_from_ser) => instance_from_ser, 324 | _ => panic!("Unexpected value {:?}", instance_from_ser), 325 | }; 326 | 327 | let expected_instance = wmi_con 328 | .get_object("StdRegProv") 329 | .unwrap() 330 | .get_method("GetBinaryValue") 331 | .unwrap() 332 | .unwrap() 333 | .spawn_instance() 334 | .unwrap(); 335 | 336 | assert_eq!( 337 | instance_from_ser.class().unwrap(), 338 | expected_instance.class().unwrap() 339 | ); 340 | 341 | assert_eq!( 342 | instance_from_ser.get_property("sSubKeyName").unwrap(), 343 | Variant::String(in_params.sSubKeyName) 344 | ); 345 | } 346 | 347 | fn spawn_instance(name: &str) -> IWbemClassWrapper { 348 | let wmi_con = wmi_con(); 349 | wmi_con.get_object(&name).unwrap().spawn_instance().unwrap() 350 | } 351 | 352 | #[test] 353 | fn it_can_get_and_put_strings() { 354 | let prop = spawn_instance("Win32_PnPDevicePropertyString"); 355 | let test_value = Variant::String("Some Title".to_string()); 356 | 357 | prop.put_property("Data", test_value.clone()).unwrap(); 358 | assert_eq!(prop.get_property("Data").unwrap(), test_value,); 359 | 360 | let prop = spawn_instance("Win32_PnPDevicePropertyStringArray"); 361 | let test_value = Variant::Array(vec![ 362 | Variant::String("X".to_string()), 363 | Variant::String("a".to_string()), 364 | ]); 365 | prop.put_property("Data", test_value.clone()).unwrap(); 366 | assert_eq!(prop.get_property("Data").unwrap(), test_value,); 367 | } 368 | 369 | #[test] 370 | fn it_can_get_and_put_numbers_and_bool() { 371 | let prop = spawn_instance("Win32_PnPDevicePropertyBoolean"); 372 | prop.put_property("Data", true).unwrap(); 373 | assert_eq!(prop.get_property("Data").unwrap(), Variant::Bool(true)); 374 | 375 | let prop = spawn_instance("Win32_PnPDevicePropertyUint8"); 376 | prop.put_property("Data", u8::MAX).unwrap(); 377 | assert_eq!(prop.get_property("Data").unwrap(), Variant::UI1(u8::MAX)); 378 | 379 | let prop = spawn_instance("Win32_PnPDevicePropertyUint16"); 380 | prop.put_property("Data", u16::MAX).unwrap(); 381 | assert_eq!(prop.get_property("Data").unwrap(), Variant::UI2(u16::MAX)); 382 | 383 | let prop = spawn_instance("Win32_PnPDevicePropertyUint32"); 384 | prop.put_property("Data", u32::MAX).unwrap(); 385 | assert_eq!(prop.get_property("Data").unwrap(), Variant::UI4(u32::MAX)); 386 | 387 | let prop = spawn_instance("Win32_PnPDevicePropertyUint64"); 388 | prop.put_property("Data", u64::MAX).unwrap(); 389 | assert_eq!(prop.get_property("Data").unwrap(), Variant::UI8(u64::MAX)); 390 | 391 | let prop = spawn_instance("Win32_PnPDevicePropertySint8"); 392 | prop.put_property("Data", i8::MAX).unwrap(); 393 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I1(i8::MAX)); 394 | prop.put_property("Data", i8::MIN).unwrap(); 395 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I1(i8::MIN)); 396 | 397 | let prop = spawn_instance("Win32_PnPDevicePropertySint16"); 398 | prop.put_property("Data", i16::MAX).unwrap(); 399 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I2(i16::MAX)); 400 | prop.put_property("Data", i16::MIN).unwrap(); 401 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I2(i16::MIN)); 402 | 403 | let prop = spawn_instance("Win32_PnPDevicePropertySint32"); 404 | prop.put_property("Data", i32::MAX).unwrap(); 405 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I4(i32::MAX)); 406 | prop.put_property("Data", i32::MIN).unwrap(); 407 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I4(i32::MIN)); 408 | 409 | let prop = spawn_instance("Win32_PnPDevicePropertySint64"); 410 | prop.put_property("Data", i64::MAX).unwrap(); 411 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I8(i64::MAX)); 412 | prop.put_property("Data", i64::MIN).unwrap(); 413 | assert_eq!(prop.get_property("Data").unwrap(), Variant::I8(i64::MIN)); 414 | 415 | let prop = spawn_instance("Win32_PnPDevicePropertyReal32"); 416 | prop.put_property("Data", 1.0f32).unwrap(); 417 | assert_eq!(prop.get_property("Data").unwrap(), Variant::R4(1.0)); 418 | 419 | let prop = spawn_instance("Win32_PnPDevicePropertyReal64"); 420 | prop.put_property("Data", 1.0f64).unwrap(); 421 | assert_eq!(prop.get_property("Data").unwrap(), Variant::R8(1.0)); 422 | } 423 | 424 | #[test] 425 | fn it_can_get_and_put_arrays() { 426 | let prop = spawn_instance("Win32_PnPDevicePropertyBooleanArray"); 427 | let test_value = vec![true, false, true]; 428 | prop.put_property("Data", test_value.clone()).unwrap(); 429 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 430 | 431 | // Test with an empty array as well. 432 | let prop = spawn_instance("Win32_PnPDevicePropertyBooleanArray"); 433 | let test_value = Variant::Array(vec![]); 434 | prop.put_property("Data", test_value.clone()).unwrap(); 435 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 436 | 437 | let prop = spawn_instance("Win32_PnPDevicePropertyBinary"); 438 | let test_value = vec![1u8, 2, u8::MAX]; 439 | prop.put_property("Data", test_value.clone()).unwrap(); 440 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 441 | 442 | let prop = spawn_instance("Win32_PnPDevicePropertyUint16Array"); 443 | let test_value = vec![1u16, 2, u16::MAX]; 444 | prop.put_property("Data", test_value.clone()).unwrap(); 445 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 446 | 447 | let prop = spawn_instance("Win32_PnPDevicePropertyUint32Array"); 448 | let test_value = vec![1u32, 2, u32::MAX]; 449 | prop.put_property("Data", test_value.clone()).unwrap(); 450 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 451 | 452 | let prop = spawn_instance("Win32_PnPDevicePropertySint8Array"); 453 | let test_value = vec![1i8, i8::MIN, i8::MAX]; 454 | prop.put_property("Data", test_value.clone()).unwrap(); 455 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 456 | 457 | let prop = spawn_instance("Win32_PnPDevicePropertySint16Array"); 458 | let test_value = vec![1i16, i16::MIN, i16::MAX]; 459 | prop.put_property("Data", test_value.clone()).unwrap(); 460 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 461 | 462 | let prop = spawn_instance("Win32_PnPDevicePropertySint32Array"); 463 | let test_value = vec![1i32, i32::MIN, i32::MAX]; 464 | prop.put_property("Data", test_value.clone()).unwrap(); 465 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 466 | 467 | let prop = spawn_instance("Win32_PnPDevicePropertyReal32Array"); 468 | let test_value = vec![1.0f32, 2.0, -1.0]; 469 | prop.put_property("Data", test_value.clone()).unwrap(); 470 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 471 | 472 | let prop = spawn_instance("Win32_PnPDevicePropertyReal64Array"); 473 | let test_value = vec![1.0f64, 2.0, -1.0]; 474 | prop.put_property("Data", test_value.clone()).unwrap(); 475 | assert_eq!(prop.get_property("Data").unwrap(), test_value.into()); 476 | } 477 | 478 | #[test] 479 | fn it_can_get_and_put_u64_i64_arrays() { 480 | // Since `Win32_PnPDeviceProperty{Uint64,Sint64}Array` are missing (documented, but do not exist in practice), 481 | // we create a new class and set custom properties with the needed array types. 482 | 483 | let wmi_con = wmi_con(); 484 | let new_cls_obj = wmi_con.get_object("").unwrap(); 485 | 486 | unsafe { 487 | new_cls_obj 488 | .inner 489 | .Put( 490 | &HSTRING::from("uValue"), 491 | 0, 492 | ptr::null(), 493 | CIM_UINT64.0 | CIM_FLAG_ARRAY.0, 494 | ) 495 | .unwrap() 496 | }; 497 | 498 | let test_value = vec![1u64, 2, u64::MAX]; 499 | new_cls_obj 500 | .put_property("uValue", test_value.clone()) 501 | .unwrap(); 502 | assert_eq!( 503 | new_cls_obj.get_property("uValue").unwrap(), 504 | test_value.into() 505 | ); 506 | 507 | unsafe { 508 | new_cls_obj 509 | .inner 510 | .Put( 511 | &HSTRING::from("iValue"), 512 | 0, 513 | ptr::null(), 514 | CIM_SINT64.0 | CIM_FLAG_ARRAY.0, 515 | ) 516 | .unwrap() 517 | }; 518 | 519 | let test_value = vec![1i64, i64::MIN, i64::MAX]; 520 | new_cls_obj 521 | .put_property("iValue", test_value.clone()) 522 | .unwrap(); 523 | assert_eq!( 524 | new_cls_obj.get_property("iValue").unwrap(), 525 | test_value.into() 526 | ); 527 | } 528 | 529 | #[test] 530 | fn it_serialize_instance_nested() { 531 | let wmi_con = wmi_con(); 532 | 533 | #[derive(Debug, Serialize, Default)] 534 | pub struct Win32_ProcessStartup { 535 | pub Title: String, 536 | pub ShowWindow: Option, 537 | pub CreateFlags: Option, 538 | } 539 | 540 | #[derive(Deserialize)] 541 | struct Win32_Process; 542 | 543 | #[derive(Serialize)] 544 | struct CreateInput { 545 | CommandLine: String, 546 | ProcessStartupInformation: Win32_ProcessStartup, 547 | } 548 | 549 | // Verify that `Win32_ProcessStartup` can be serialized. 550 | let startup_info = Win32_ProcessStartup { 551 | Title: "Pong".to_string(), 552 | ShowWindow: Some(3), 553 | CreateFlags: None, 554 | }; 555 | 556 | let startup_info_instance = startup_info 557 | .serialize(VariantSerializer { 558 | wmi: &wmi_con, 559 | instance: None, 560 | }) 561 | .unwrap(); 562 | 563 | let startup_info_instance = match startup_info_instance { 564 | Variant::Object(startup_info_instance) => startup_info_instance, 565 | _ => panic!("Unexpected value {:?}", startup_info_instance), 566 | }; 567 | 568 | assert_eq!( 569 | startup_info_instance.class().unwrap(), 570 | "Win32_ProcessStartup" 571 | ); 572 | assert_eq!( 573 | startup_info_instance.get_property("Title").unwrap(), 574 | Variant::String(startup_info.Title.clone()) 575 | ); 576 | 577 | assert_eq!( 578 | startup_info_instance.get_property("ShowWindow").unwrap(), 579 | Variant::UI2(3) 580 | ); 581 | assert_eq!( 582 | startup_info_instance.get_property("CreateFlags").unwrap(), 583 | Variant::Null 584 | ); 585 | 586 | let create_params = CreateInput { 587 | CommandLine: r#"ping -n 3 127.0.0.1"#.to_string(), 588 | ProcessStartupInformation: startup_info, 589 | }; 590 | 591 | // Similar to how `exec_class_method` creates these objects. 592 | let (method_in, method_out) = wmi_con 593 | .get_object("Win32_Process") 594 | .unwrap() 595 | .get_method_in_out("Create") 596 | .unwrap(); 597 | 598 | let method_in = method_in.unwrap().spawn_instance().unwrap(); 599 | let method_out = method_out.unwrap().spawn_instance().unwrap(); 600 | 601 | let instance_from_ser = create_params 602 | .serialize(VariantSerializer { 603 | wmi: &wmi_con, 604 | instance: Some(method_in), 605 | }) 606 | .unwrap(); 607 | 608 | let instance_from_ser = match instance_from_ser { 609 | Variant::Object(instance_from_ser) => instance_from_ser, 610 | _ => panic!("Unexpected value {:?}", instance_from_ser), 611 | }; 612 | 613 | assert_eq!( 614 | instance_from_ser.get_property("CommandLine").unwrap(), 615 | Variant::String(create_params.CommandLine) 616 | ); 617 | 618 | assert!(matches!( 619 | instance_from_ser 620 | .get_property("ProcessStartupInformation") 621 | .unwrap(), 622 | Variant::Object(_) 623 | )); 624 | 625 | assert_eq!( 626 | method_out.get_property("ReturnValue").unwrap(), 627 | Variant::Null 628 | ); 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{COMLibrary, WMIConnection, WMIError, WMIResult}; 2 | 3 | pub mod fixtures { 4 | use super::*; 5 | 6 | // This way we only setup COM security once per thread during tests. 7 | thread_local! { 8 | static COM_LIB: COMLibrary = COMLibrary::without_security().unwrap(); 9 | } 10 | 11 | pub fn wmi_con() -> WMIConnection { 12 | let com_lib = COM_LIB.with(|com| *com); 13 | 14 | WMIConnection::new(com_lib).unwrap() 15 | } 16 | } 17 | 18 | pub fn start_test_program() { 19 | std::process::Command::new("C:\\Windows\\System32\\cmd.exe") 20 | .args(["timeout", "1"]) 21 | .spawn() 22 | .expect("failed to run test program"); 23 | } 24 | 25 | pub fn ignore_access_denied(result: WMIResult<()>) -> WMIResult<()> { 26 | use windows::Win32::System::Wmi::WBEM_E_ACCESS_DENIED; 27 | 28 | if let Err(e) = result { 29 | if let WMIError::HResultError { hres } = e { 30 | if hres != WBEM_E_ACCESS_DENIED.0 { 31 | return Err(e); 32 | } 33 | } 34 | } 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use serde::{de, ser}; 2 | use std::fmt::{Debug, Display}; 3 | use thiserror::Error; 4 | 5 | #[derive(Debug, Error)] 6 | #[non_exhaustive] 7 | pub enum WMIError { 8 | /// You can find a useful resource for decoding error codes [here](https://docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-error-constants) 9 | /// (or a github version [here](https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/WmiSdk/wmi-error-constants.md)) 10 | #[error("HRESULT Call failed with: {hres:#X}")] 11 | HResultError { hres: i32 }, 12 | #[error(transparent)] 13 | ParseIntError(#[from] std::num::ParseIntError), 14 | #[error(transparent)] 15 | ParseFloatError(#[from] std::num::ParseFloatError), 16 | #[cfg(feature = "chrono")] 17 | #[error(transparent)] 18 | ParseDatetimeError(#[from] chrono::format::ParseError), 19 | #[cfg(feature = "chrono")] 20 | #[error("Cannot parse a non unique local timestamp")] 21 | ParseDatetimeLocalError, 22 | #[cfg(feature = "time")] 23 | #[error(transparent)] 24 | ParseOffsetDatetimeError(#[from] time::Error), 25 | #[error("Converting from variant type {0:#X} is not implemented yet")] 26 | ConvertError(u16), 27 | #[error("{0}")] 28 | ConvertVariantError(String), 29 | #[error("Invalid bool value: {0:#X}")] 30 | ConvertBoolError(i16), 31 | #[error(transparent)] 32 | ConvertStringError(#[from] std::string::FromUtf16Error), 33 | #[error("Expected {0:?} to be at least 21 chars")] 34 | ConvertDatetimeError(String), 35 | #[error("Expected {0:?} to be at 25 chars")] 36 | ConvertDurationError(String), 37 | #[error("Length {0} was too long to convert")] 38 | ConvertLengthError(u64), 39 | #[error("{0}")] 40 | SerdeError(String), 41 | #[error(transparent)] 42 | DeserializeValueError(#[from] de::value::Error), 43 | #[error("No results returned")] 44 | ResultEmpty, 45 | #[error("Null pointer was sent as part of query result")] 46 | NullPointerResult, 47 | #[error("Unimplemeted array item in query")] 48 | UnimplementedArrayItem, 49 | #[error("Invalid variant {0} during deserialization")] 50 | InvalidDeserializationVariantError(String), 51 | } 52 | 53 | impl From for WMIError { 54 | fn from(value: windows::core::Error) -> Self { 55 | Self::HResultError { 56 | hres: value.code().0, 57 | } 58 | } 59 | } 60 | 61 | impl de::Error for WMIError { 62 | #[cold] 63 | fn custom(msg: T) -> WMIError { 64 | Self::SerdeError(format!("{}", msg)) 65 | } 66 | } 67 | 68 | impl ser::Error for WMIError { 69 | #[cold] 70 | fn custom(msg: T) -> WMIError { 71 | Self::SerdeError(format!("{}", msg)) 72 | } 73 | } 74 | 75 | /// Alias type for `Result` 76 | pub type WMIResult = Result; 77 | --------------------------------------------------------------------------------