├── .github └── CODEOWNERS ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── build.rs ├── dotnet ├── Bindings.cs └── Bindings.csproj └── src ├── bindings.rs ├── cli_xml.rs ├── context.rs ├── delegate_loader.rs ├── error.rs ├── host_detect.rs ├── host_exit_code.rs ├── hostfxr.rs ├── lib.rs ├── loader.rs ├── pdcstring ├── error.rs ├── mod.rs ├── other.rs ├── shared.rs └── windows.rs ├── tests.rs └── time.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # File auto-generated and managed by Devops 2 | /.github/ @devolutions/devops 3 | /.github/dependabot.yml @devolutions/security-managers 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | build/ 4 | target/ 5 | Cargo.lock 6 | Makefile 7 | CMakeCache.txt 8 | CMakeFiles/ 9 | cmake_install.cmake 10 | 11 | .idea/ 12 | cmake-build-debug/ 13 | resources/ 14 | 15 | pwsh-host-rs.sln 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pwsh-host" 3 | version = "0.3.0" 4 | edition = "2018" 5 | license = "MIT/Apache-2.0" 6 | homepage = "https://github.com/awakecoding/pwsh-host-rs" 7 | repository = "https://github.com/awakecoding/pwsh-host-rs" 8 | authors = ["Marc-André Moreau "] 9 | keywords = ["dotnet", "pwsh", "host", "powershell"] 10 | description = "Rust PowerShell Hosting Library" 11 | 12 | [dependencies] 13 | cfg-if = "0.1" 14 | libc = "0.2" 15 | thiserror = "1.0" 16 | dlopen = "0.1" 17 | dlopen_derive = "0.1" 18 | num_enum = "0.5" 19 | quick-error = "2.0" 20 | quick-xml = "0.26" 21 | decimal = { version = "2.1", default-features = false } 22 | which = { version = "3.0", default-features = false, features = [] } 23 | time = { version = "0.3", features = ["formatting", "parsing", "macros"] } 24 | iso8601-duration = "0.1" 25 | base64 = "0.13" 26 | uuid = "1.2" 27 | url = "2.3" 28 | 29 | [target.'cfg(windows)'.dependencies] 30 | widestring = "0.4" 31 | u16cstr = "0.2" 32 | 33 | [target.'cfg(not(windows))'.dependencies] 34 | cstr = "0.2" 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | 5 | fn main() { 6 | let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); 7 | let mut dotnet_source_dir = manifest_dir.clone(); 8 | dotnet_source_dir.push("dotnet"); 9 | 10 | let _output = Command::new("dotnet") 11 | .arg("build") 12 | .arg(dotnet_source_dir.as_path().to_str().unwrap()) 13 | .arg("-c") 14 | .arg("Release") 15 | .output() 16 | .expect("failed to execute dotnet build command"); 17 | } 18 | -------------------------------------------------------------------------------- /dotnet/Bindings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Collections.ObjectModel; 4 | using System.Management.Automation; 5 | 6 | namespace NativeHost 7 | { 8 | // PowerShell Class 9 | // https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell 10 | 11 | public static class Bindings 12 | { 13 | [UnmanagedCallersOnly] 14 | public static IntPtr PowerShell_Create() 15 | { 16 | // https://stackoverflow.com/a/32108252 17 | PowerShell ps = PowerShell.Create(); 18 | GCHandle gch = GCHandle.Alloc(ps, GCHandleType.Normal); 19 | IntPtr ptrHandle = GCHandle.ToIntPtr(gch); 20 | return ptrHandle; 21 | } 22 | 23 | [UnmanagedCallersOnly] 24 | public static void PowerShell_AddArgument_String(IntPtr ptrHandle, IntPtr ptrArgument) 25 | { 26 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 27 | PowerShell ps = (PowerShell) gch.Target; 28 | string argument = Marshal.PtrToStringUTF8(ptrArgument); 29 | ps.AddArgument(argument); 30 | } 31 | 32 | [UnmanagedCallersOnly] 33 | public static void PowerShell_AddParameter_String(IntPtr ptrHandle, IntPtr ptrName, IntPtr ptrValue) 34 | { 35 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 36 | PowerShell ps = (PowerShell) gch.Target; 37 | string name = Marshal.PtrToStringUTF8(ptrName); 38 | string value = Marshal.PtrToStringUTF8(ptrValue); 39 | ps.AddParameter(name, value); 40 | } 41 | 42 | [UnmanagedCallersOnly] 43 | public static void PowerShell_AddParameter_Int(IntPtr ptrHandle, IntPtr ptrName, int value) 44 | { 45 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 46 | PowerShell ps = (PowerShell) gch.Target; 47 | string name = Marshal.PtrToStringUTF8(ptrName); 48 | ps.AddParameter(name, value); 49 | } 50 | 51 | [UnmanagedCallersOnly] 52 | public static void PowerShell_AddParameter_Long(IntPtr ptrHandle, IntPtr ptrName, long value) 53 | { 54 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 55 | PowerShell ps = (PowerShell) gch.Target; 56 | string name = Marshal.PtrToStringUTF8(ptrName); 57 | ps.AddParameter(name, value); 58 | } 59 | 60 | [UnmanagedCallersOnly] 61 | public static void PowerShell_AddCommand(IntPtr ptrHandle, IntPtr ptrCommand) 62 | { 63 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 64 | PowerShell ps = (PowerShell) gch.Target; 65 | string command = Marshal.PtrToStringUTF8(ptrCommand); 66 | ps.AddCommand(command); 67 | } 68 | 69 | [UnmanagedCallersOnly] 70 | public static void PowerShell_AddScript(IntPtr ptrHandle, IntPtr ptrScript) 71 | { 72 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 73 | PowerShell ps = (PowerShell) gch.Target; 74 | string script = Marshal.PtrToStringUTF8(ptrScript); 75 | ps.AddScript(script); 76 | } 77 | 78 | [UnmanagedCallersOnly] 79 | public static void PowerShell_AddStatement(IntPtr ptrHandle) 80 | { 81 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 82 | PowerShell ps = (PowerShell) gch.Target; 83 | ps.AddStatement(); 84 | } 85 | 86 | [UnmanagedCallersOnly] 87 | public static void PowerShell_Invoke(IntPtr ptrHandle) 88 | { 89 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 90 | PowerShell ps = (PowerShell) gch.Target; 91 | ps.Invoke(); 92 | } 93 | 94 | [UnmanagedCallersOnly] 95 | public static void PowerShell_Clear(IntPtr ptrHandle) 96 | { 97 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 98 | PowerShell ps = (PowerShell) gch.Target; 99 | ps.Commands.Clear(); 100 | } 101 | 102 | [UnmanagedCallersOnly] 103 | public static IntPtr PowerShell_ExportToXml(IntPtr ptrHandle, IntPtr ptrName) 104 | { 105 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 106 | PowerShell ps = (PowerShell) gch.Target; 107 | string name = Marshal.PtrToStringUTF8(ptrName); 108 | ps.AddScript(string.Format("[System.Management.Automation.PSSerializer]::Serialize(${0})", name)); 109 | ps.AddStatement(); 110 | Collection results = ps.Invoke(); 111 | string result = results[0].ToString().Trim(); 112 | ps.Commands.Clear(); 113 | return Marshal.StringToCoTaskMemUTF8(result); 114 | } 115 | 116 | [UnmanagedCallersOnly] 117 | public static IntPtr PowerShell_ExportToJson(IntPtr ptrHandle, IntPtr ptrName) 118 | { 119 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 120 | PowerShell ps = (PowerShell) gch.Target; 121 | string name = Marshal.PtrToStringUTF8(ptrName); 122 | ps.AddScript(string.Format("${0} | ConvertTo-Json", name)); 123 | ps.AddStatement(); 124 | Collection results = ps.Invoke(); 125 | string result = results[0].ToString().Trim(); 126 | ps.Commands.Clear(); 127 | return Marshal.StringToCoTaskMemUTF8(result); 128 | } 129 | 130 | [UnmanagedCallersOnly] 131 | public static IntPtr PowerShell_ExportToString(IntPtr ptrHandle, IntPtr ptrName) 132 | { 133 | GCHandle gch = GCHandle.FromIntPtr(ptrHandle); 134 | PowerShell ps = (PowerShell) gch.Target; 135 | string name = Marshal.PtrToStringUTF8(ptrName); 136 | ps.AddScript(string.Format("${0} | Out-String", name)); 137 | ps.AddStatement(); 138 | Collection results = ps.Invoke(); 139 | string result = results[0].ToString().Trim(); 140 | ps.Commands.Clear(); 141 | return Marshal.StringToCoTaskMemUTF8(result); 142 | } 143 | 144 | // Marshal Class 145 | // https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal 146 | 147 | [UnmanagedCallersOnly] 148 | public static void Marshal_FreeCoTaskMem(IntPtr ptr) 149 | { 150 | Marshal.FreeCoTaskMem(ptr); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /dotnet/Bindings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | true 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::delegate_loader::{AssemblyDelegateLoader, MethodWithUnknownSignature}; 4 | use crate::error::Error; 5 | use crate::loader::get_assembly_delegate_loader; 6 | use crate::pdcstr; 7 | use crate::pdcstring::{PdCStr, PdCString}; 8 | use std::ffi::{CStr, CString}; 9 | 10 | pub type PowerShellHandle = *mut libc::c_void; 11 | 12 | pub type FnPowerShellCreate = unsafe extern "system" fn() -> PowerShellHandle; 13 | 14 | pub type FnPowerShellAddArgumentString = 15 | unsafe extern "system" fn(handle: PowerShellHandle, argument: *const libc::c_char); 16 | 17 | pub type FnPowerShellAddParameterString = unsafe extern "system" fn( 18 | handle: PowerShellHandle, 19 | name: *const libc::c_char, 20 | value: *const libc::c_char, 21 | ); 22 | 23 | pub type FnPowerShellAddParameterInt = 24 | unsafe extern "system" fn(handle: PowerShellHandle, name: *const libc::c_char, value: i32); 25 | 26 | pub type FnPowerShellAddParameterLong = 27 | unsafe extern "system" fn(handle: PowerShellHandle, name: *const libc::c_char, value: i64); 28 | 29 | pub type FnPowerShellAddCommand = 30 | unsafe extern "system" fn(handle: PowerShellHandle, command: *const libc::c_char); 31 | 32 | pub type FnPowerShellAddScript = 33 | unsafe extern "system" fn(handle: PowerShellHandle, script: *const libc::c_char); 34 | 35 | pub type FnPowerShellAddStatement = 36 | unsafe extern "system" fn(handle: PowerShellHandle) -> PowerShellHandle; 37 | 38 | pub type FnPowerShellInvoke = unsafe extern "system" fn(handle: PowerShellHandle); 39 | 40 | pub type FnPowerShellClear = unsafe extern "system" fn(handle: PowerShellHandle); 41 | 42 | pub type FnPowerShellExportToXml = unsafe extern "system" fn( 43 | handle: PowerShellHandle, 44 | name: *const libc::c_char, 45 | ) -> *const libc::c_char; 46 | 47 | pub type FnPowerShellExportToJson = unsafe extern "system" fn( 48 | handle: PowerShellHandle, 49 | name: *const libc::c_char, 50 | ) -> *const libc::c_char; 51 | 52 | pub type FnPowerShellExportToString = unsafe extern "system" fn( 53 | handle: PowerShellHandle, 54 | name: *const libc::c_char, 55 | ) -> *const libc::c_char; 56 | 57 | pub type FnMarshalFreeCoTaskMem = unsafe extern "system" fn(ptr: *mut libc::c_void); 58 | 59 | struct Bindings { 60 | create_fn: FnPowerShellCreate, 61 | add_argument_string_fn: FnPowerShellAddArgumentString, 62 | add_parameter_string_fn: FnPowerShellAddParameterString, 63 | add_parameter_int_fn: FnPowerShellAddParameterInt, 64 | add_parameter_long_fn: FnPowerShellAddParameterLong, 65 | add_command_fn: FnPowerShellAddCommand, 66 | add_script_fn: FnPowerShellAddScript, 67 | add_statement_fn: FnPowerShellAddStatement, 68 | invoke_fn: FnPowerShellInvoke, 69 | clear_fn: FnPowerShellClear, 70 | export_to_xml_fn: FnPowerShellExportToXml, 71 | export_to_json_fn: FnPowerShellExportToJson, 72 | export_to_string_fn: FnPowerShellExportToString, 73 | marshal_free_co_task_mem_fn: FnMarshalFreeCoTaskMem, 74 | } 75 | 76 | impl Bindings { 77 | pub fn new() -> Result { 78 | let fn_loader = get_assembly_delegate_loader(); 79 | Self::new_with_loader(&fn_loader) 80 | } 81 | 82 | pub fn new_with_loader(fn_loader: &AssemblyDelegateLoader) -> Result { 83 | fn get_function_pointer( 84 | fn_loader: &AssemblyDelegateLoader, 85 | type_name: impl AsRef, 86 | method_name: impl AsRef, 87 | ) -> Result { 88 | fn_loader.get_function_pointer_for_unmanaged_callers_only_method(type_name, method_name) 89 | } 90 | 91 | let pwsh = Self { 92 | create_fn: { 93 | let fn_ptr = get_function_pointer( 94 | fn_loader, 95 | pdcstr!("NativeHost.Bindings, Bindings"), 96 | pdcstr!("PowerShell_Create"), 97 | )?; 98 | unsafe { std::mem::transmute(fn_ptr) } 99 | }, 100 | add_argument_string_fn: { 101 | let fn_ptr = get_function_pointer( 102 | fn_loader, 103 | pdcstr!("NativeHost.Bindings, Bindings"), 104 | pdcstr!("PowerShell_AddArgument_String"), 105 | )?; 106 | unsafe { std::mem::transmute(fn_ptr) } 107 | }, 108 | add_parameter_string_fn: { 109 | let fn_ptr = get_function_pointer( 110 | fn_loader, 111 | pdcstr!("NativeHost.Bindings, Bindings"), 112 | pdcstr!("PowerShell_AddParameter_String"), 113 | )?; 114 | unsafe { std::mem::transmute(fn_ptr) } 115 | }, 116 | add_parameter_int_fn: { 117 | let fn_ptr = get_function_pointer( 118 | fn_loader, 119 | pdcstr!("NativeHost.Bindings, Bindings"), 120 | pdcstr!("PowerShell_AddParameter_Int"), 121 | )?; 122 | unsafe { std::mem::transmute(fn_ptr) } 123 | }, 124 | add_parameter_long_fn: { 125 | let fn_ptr = get_function_pointer( 126 | fn_loader, 127 | pdcstr!("NativeHost.Bindings, Bindings"), 128 | pdcstr!("PowerShell_AddParameter_Long"), 129 | )?; 130 | unsafe { std::mem::transmute(fn_ptr) } 131 | }, 132 | add_command_fn: { 133 | let fn_ptr = get_function_pointer( 134 | fn_loader, 135 | pdcstr!("NativeHost.Bindings, Bindings"), 136 | pdcstr!("PowerShell_AddCommand"), 137 | )?; 138 | unsafe { std::mem::transmute(fn_ptr) } 139 | }, 140 | add_script_fn: { 141 | let fn_ptr = get_function_pointer( 142 | fn_loader, 143 | pdcstr!("NativeHost.Bindings, Bindings"), 144 | pdcstr!("PowerShell_AddScript"), 145 | )?; 146 | unsafe { std::mem::transmute(fn_ptr) } 147 | }, 148 | add_statement_fn: { 149 | let fn_ptr = get_function_pointer( 150 | fn_loader, 151 | pdcstr!("NativeHost.Bindings, Bindings"), 152 | pdcstr!("PowerShell_AddStatement"), 153 | )?; 154 | unsafe { std::mem::transmute(fn_ptr) } 155 | }, 156 | invoke_fn: { 157 | let fn_ptr = get_function_pointer( 158 | fn_loader, 159 | pdcstr!("NativeHost.Bindings, Bindings"), 160 | pdcstr!("PowerShell_Invoke"), 161 | )?; 162 | unsafe { std::mem::transmute(fn_ptr) } 163 | }, 164 | clear_fn: { 165 | let fn_ptr = get_function_pointer( 166 | fn_loader, 167 | pdcstr!("NativeHost.Bindings, Bindings"), 168 | pdcstr!("PowerShell_Clear"), 169 | )?; 170 | unsafe { std::mem::transmute(fn_ptr) } 171 | }, 172 | export_to_xml_fn: { 173 | let fn_ptr = get_function_pointer( 174 | fn_loader, 175 | pdcstr!("NativeHost.Bindings, Bindings"), 176 | pdcstr!("PowerShell_ExportToXml"), 177 | )?; 178 | unsafe { std::mem::transmute(fn_ptr) } 179 | }, 180 | export_to_json_fn: { 181 | let fn_ptr = get_function_pointer( 182 | fn_loader, 183 | pdcstr!("NativeHost.Bindings, Bindings"), 184 | pdcstr!("PowerShell_ExportToJson"), 185 | )?; 186 | unsafe { std::mem::transmute(fn_ptr) } 187 | }, 188 | export_to_string_fn: { 189 | let fn_ptr = get_function_pointer( 190 | fn_loader, 191 | pdcstr!("NativeHost.Bindings, Bindings"), 192 | pdcstr!("PowerShell_ExportToString"), 193 | )?; 194 | unsafe { std::mem::transmute(fn_ptr) } 195 | }, 196 | marshal_free_co_task_mem_fn: { 197 | let fn_ptr = get_function_pointer( 198 | fn_loader, 199 | pdcstr!("NativeHost.Bindings, Bindings"), 200 | pdcstr!("Marshal_FreeCoTaskMem"), 201 | )?; 202 | unsafe { std::mem::transmute(fn_ptr) } 203 | }, 204 | }; 205 | Ok(pwsh) 206 | } 207 | } 208 | 209 | pub struct PowerShell { 210 | inner: Bindings, 211 | handle: PowerShellHandle, 212 | } 213 | 214 | impl PowerShell { 215 | pub fn new() -> Option { 216 | let bindings = Bindings::new().ok()?; 217 | let handle = unsafe { (bindings.create_fn)() }; 218 | Some(Self { 219 | inner: bindings, 220 | handle: handle, 221 | }) 222 | } 223 | 224 | pub fn add_argument_string(&self, argument: &str) { 225 | let argument_cstr = CString::new(argument).unwrap(); 226 | unsafe { 227 | (self.inner.add_argument_string_fn)(self.handle, argument_cstr.as_ptr()); 228 | } 229 | } 230 | 231 | pub fn add_parameter_string(&self, name: &str, value: &str) { 232 | let name_cstr = CString::new(name).unwrap(); 233 | let value_cstr = CString::new(value).unwrap(); 234 | unsafe { 235 | (self.inner.add_parameter_string_fn)( 236 | self.handle, 237 | name_cstr.as_ptr(), 238 | value_cstr.as_ptr(), 239 | ); 240 | } 241 | } 242 | 243 | pub fn add_parameter_int(&self, name: &str, value: i32) { 244 | let name_cstr = CString::new(name).unwrap(); 245 | unsafe { 246 | (self.inner.add_parameter_int_fn)(self.handle, name_cstr.as_ptr(), value); 247 | } 248 | } 249 | 250 | pub fn add_parameter_long(&self, name: &str, value: i64) { 251 | let name_cstr = CString::new(name).unwrap(); 252 | unsafe { 253 | (self.inner.add_parameter_long_fn)(self.handle, name_cstr.as_ptr(), value); 254 | } 255 | } 256 | 257 | pub fn add_command(&self, command: &str) { 258 | let command_cstr = CString::new(command).unwrap(); 259 | unsafe { 260 | (self.inner.add_command_fn)(self.handle, command_cstr.as_ptr()); 261 | } 262 | } 263 | 264 | pub fn add_script(&self, script: &str) { 265 | let script_cstr = CString::new(script).unwrap(); 266 | unsafe { 267 | (self.inner.add_script_fn)(self.handle, script_cstr.as_ptr()); 268 | } 269 | } 270 | 271 | pub fn add_statement(&self) { 272 | unsafe { 273 | (self.inner.add_statement_fn)(self.handle); 274 | } 275 | } 276 | 277 | pub fn invoke(&self, clear: bool) { 278 | unsafe { 279 | (self.inner.invoke_fn)(self.handle); 280 | if clear { 281 | (self.inner.clear_fn)(self.handle); 282 | } 283 | } 284 | } 285 | 286 | pub fn clear(&self) { 287 | unsafe { 288 | (self.inner.clear_fn)(self.handle); 289 | } 290 | } 291 | 292 | pub fn export_to_xml(&self, name: &str) -> String { 293 | unsafe { 294 | let name_cstr = CString::new(name).unwrap(); 295 | let cstr_ptr = (self.inner.export_to_xml_fn)(self.handle, name_cstr.as_ptr()); 296 | let cstr = CStr::from_ptr(cstr_ptr); 297 | let rstr = String::from_utf8_lossy(cstr.to_bytes()).to_string(); 298 | self.marshal_free_co_task_mem(cstr_ptr as *mut libc::c_void); 299 | rstr 300 | } 301 | } 302 | 303 | pub fn export_to_json(&self, name: &str) -> String { 304 | unsafe { 305 | let name_cstr = CString::new(name).unwrap(); 306 | let cstr_ptr = (self.inner.export_to_json_fn)(self.handle, name_cstr.as_ptr()); 307 | let cstr = CStr::from_ptr(cstr_ptr); 308 | let rstr = String::from_utf8_lossy(cstr.to_bytes()).to_string(); 309 | self.marshal_free_co_task_mem(cstr_ptr as *mut libc::c_void); 310 | rstr 311 | } 312 | } 313 | 314 | pub fn export_to_string(&self, name: &str) -> String { 315 | unsafe { 316 | let name_cstr = CString::new(name).unwrap(); 317 | let cstr_ptr = (self.inner.export_to_string_fn)(self.handle, name_cstr.as_ptr()); 318 | let cstr = CStr::from_ptr(cstr_ptr); 319 | let rstr = String::from_utf8_lossy(cstr.to_bytes()).to_string(); 320 | self.marshal_free_co_task_mem(cstr_ptr as *mut libc::c_void); 321 | rstr 322 | } 323 | } 324 | 325 | pub fn marshal_free_co_task_mem(&self, ptr: *mut libc::c_void) { 326 | unsafe { 327 | (self.inner.marshal_free_co_task_mem_fn)(ptr); 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/cli_xml.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::time::parse_iso8601_duration; 4 | use crate::time::DateTime; 5 | use decimal::d128; 6 | use quick_xml::events; 7 | use quick_xml::events::Event; 8 | use quick_xml::reader::Reader; 9 | use std::time::Duration; 10 | use url::Url; 11 | use uuid::Uuid; 12 | 13 | // [MS-PSRP]: PowerShell Remoting Protocol 14 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp 15 | 16 | // [XMLSCHEMA2]: XML Schema Part 2: Datatypes Second Edition 17 | // https://www.w3.org/TR/xmlschema-2/ 18 | 19 | // Serialization of Primitive Type Objects: 20 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/c8c85974-ffd7-4455-84a8-e49016c20683 21 | 22 | // Serialization of Complex Objects: 23 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/406ad572-1ede-43e0-b063-e7291cda3e63 24 | 25 | // Object type () 26 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/3e107e78-3f28-4f85-9e25-493fd9b09726 27 | 28 | #[derive(Debug, Clone, Default)] 29 | pub struct CliObject { 30 | pub name: Option, 31 | pub values: Vec, 32 | pub ref_id: Option, 33 | pub type_names: Vec, 34 | pub string_repr: Option, 35 | } 36 | 37 | impl CliObject { 38 | pub fn new( 39 | name: Option<&str>, 40 | value: Vec, 41 | ref_id: Option<&str>, 42 | type_names: Vec, 43 | string_repr: Option<&str>, 44 | ) -> CliObject { 45 | CliObject { 46 | name: name.map(|s| s.to_string()), 47 | values: value, 48 | ref_id: ref_id.map(|s| s.to_string()), 49 | type_names: type_names, 50 | string_repr: string_repr.map(|s| s.to_string()), 51 | } 52 | } 53 | } 54 | 55 | // Type Names (, , ) 56 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/2784bd9c-267d-4297-b603-722c727f85f1 57 | 58 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 59 | pub struct CliTypeName { 60 | pub name: Option, 61 | pub values: Vec, 62 | pub ref_id: String, 63 | } 64 | 65 | impl CliTypeName { 66 | pub fn new(name: Option<&str>, value: Vec, ref_id: &str) -> CliTypeName { 67 | CliTypeName { 68 | name: name.map(|s| s.to_string()), 69 | values: value, 70 | ref_id: ref_id.to_string(), 71 | } 72 | } 73 | } 74 | 75 | // Null value () 76 | // Example: 77 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/402f2a78-5771-45ae-bf33-59f6e57767ca 78 | 79 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 80 | pub struct CliNull { 81 | pub name: Option, 82 | } 83 | 84 | impl CliNull { 85 | pub fn new(name: Option<&str>) -> CliNull { 86 | CliNull { 87 | name: name.map(|s| s.to_string()), 88 | } 89 | } 90 | } 91 | 92 | // String type () 93 | // Example: This is a string 94 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/052b8c32-735b-49c0-8c24-bb32a5c871ce 95 | 96 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 97 | pub struct CliString { 98 | pub value: String, 99 | pub name: Option, 100 | } 101 | 102 | impl CliString { 103 | pub fn new(name: Option<&str>, value: &str) -> CliString { 104 | CliString { 105 | name: name.map(|s| s.to_string()), 106 | value: value.to_string(), 107 | } 108 | } 109 | } 110 | 111 | // Character type () 112 | // Example: 97 113 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/ff6f9767-a0a5-4cca-b091-4f15afc6e6d8 114 | 115 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 116 | pub struct CliChar { 117 | pub value: char, 118 | pub name: Option, 119 | } 120 | 121 | impl CliChar { 122 | pub fn new(name: Option<&str>, value: char) -> CliChar { 123 | CliChar { 124 | name: name.map(|s| s.to_string()), 125 | value: value, 126 | } 127 | } 128 | 129 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 130 | let value = value.parse::().ok()?; 131 | let mut chars = std::char::decode_utf16(vec![value]).collect::>(); 132 | let value: char = chars.pop().unwrap().ok()?; 133 | Some(Self::new(name, value)) 134 | } 135 | } 136 | 137 | // Boolean type () 138 | // Example: true 139 | // https://www.w3.org/TR/xmlschema-2/#boolean 140 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/8b4b1067-4b58-46d5-b1c9-b881b6e7a0aa 141 | 142 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 143 | pub struct CliBool { 144 | pub value: bool, 145 | pub name: Option, 146 | } 147 | 148 | impl CliBool { 149 | pub fn new(name: Option<&str>, value: bool) -> CliBool { 150 | CliBool { 151 | name: name.map(|s| s.to_string()), 152 | value: value, 153 | } 154 | } 155 | 156 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 157 | let value = value.parse::().ok()?; 158 | Some(Self::new(name, value)) 159 | } 160 | } 161 | 162 | // Date/Time type (
) 163 | // Example:
2008-04-11T10:42:32.2731993-07:00
164 | // https://www.w3.org/TR/xmlschema-2/#dateTime 165 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/a3b75b8d-ad7e-4649-bb82-cfa70f54fb8c 166 | 167 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 168 | pub struct CliDateTime { 169 | pub value: DateTime, 170 | pub name: Option, 171 | } 172 | 173 | impl CliDateTime { 174 | pub fn new(name: Option<&str>, value: DateTime) -> CliDateTime { 175 | CliDateTime { 176 | name: name.map(|s| s.to_string()), 177 | value: value, 178 | } 179 | } 180 | 181 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 182 | let value = DateTime::parse(value)?; 183 | Some(Self::new(name, value)) 184 | } 185 | } 186 | 187 | // Duration type () 188 | // Example: PT9.0269026S 189 | // https://www.w3.org/TR/xmlschema-2/#duration 190 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/434cd15d-8fb3-462c-a004-bcd0d3a60201 191 | 192 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 193 | pub struct CliDuration { 194 | pub value: Duration, 195 | pub name: Option, 196 | } 197 | 198 | impl CliDuration { 199 | pub fn new(name: Option<&str>, value: Duration) -> CliDuration { 200 | CliDuration { 201 | name: name.map(|s| s.to_string()), 202 | value: value, 203 | } 204 | } 205 | 206 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 207 | let value = parse_iso8601_duration(value)?; 208 | Some(Self::new(name, value)) 209 | } 210 | } 211 | 212 | // Unsigned Byte type () 213 | // Example: 254 214 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/6e25153d-77b6-4e21-b5fa-6f986895171a 215 | 216 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 217 | pub struct CliUInt8 { 218 | pub value: u8, 219 | pub name: Option, 220 | } 221 | 222 | impl CliUInt8 { 223 | pub fn new(name: Option<&str>, value: u8) -> CliUInt8 { 224 | CliUInt8 { 225 | name: name.map(|s| s.to_string()), 226 | value: value, 227 | } 228 | } 229 | 230 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 231 | let value = value.parse::().ok()?; 232 | Some(Self::new(name, value)) 233 | } 234 | } 235 | 236 | // Signed Byte type () 237 | // Example: -127 238 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/8046c418-1531-4c43-9b9d-fb9bceace0db 239 | 240 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 241 | pub struct CliInt8 { 242 | pub value: i8, 243 | pub name: Option, 244 | } 245 | 246 | impl CliInt8 { 247 | pub fn new(name: Option<&str>, value: i8) -> CliInt8 { 248 | CliInt8 { 249 | name: name.map(|s| s.to_string()), 250 | value: value, 251 | } 252 | } 253 | 254 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 255 | let value = value.parse::().ok()?; 256 | Some(Self::new(name, value)) 257 | } 258 | } 259 | 260 | // Unsigned Short type () 261 | // Example: 65535 262 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/33751ca7-90d0-4b5e-a04f-2d8798cfb419 263 | 264 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 265 | pub struct CliUInt16 { 266 | pub value: u16, 267 | pub name: Option, 268 | } 269 | 270 | impl CliUInt16 { 271 | pub fn new(name: Option<&str>, value: u16) -> CliUInt16 { 272 | CliUInt16 { 273 | name: name.map(|s| s.to_string()), 274 | value: value, 275 | } 276 | } 277 | 278 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 279 | let value = value.parse::().ok()?; 280 | Some(Self::new(name, value)) 281 | } 282 | } 283 | 284 | // Signed Short type () 285 | // Example: -16767 286 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/e0ed596d-0aea-40bb-a254-285b71188214 287 | 288 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 289 | pub struct CliInt16 { 290 | pub value: i16, 291 | pub name: Option, 292 | } 293 | 294 | impl CliInt16 { 295 | pub fn new(name: Option<&str>, value: i16) -> CliInt16 { 296 | CliInt16 { 297 | name: name.map(|s| s.to_string()), 298 | value: value, 299 | } 300 | } 301 | 302 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 303 | let value = value.parse::().ok()?; 304 | Some(Self::new(name, value)) 305 | } 306 | } 307 | 308 | // Unsigned Int type () 309 | // Example: 4294967295 310 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/7b904471-3519-4a6a-900b-8053ad975c08 311 | 312 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 313 | pub struct CliUInt32 { 314 | pub value: u32, 315 | pub name: Option, 316 | } 317 | 318 | impl CliUInt32 { 319 | pub fn new(name: Option<&str>, value: u32) -> CliUInt32 { 320 | CliUInt32 { 321 | name: name.map(|s| s.to_string()), 322 | value: value, 323 | } 324 | } 325 | 326 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 327 | let value = value.parse::().ok()?; 328 | Some(Self::new(name, value)) 329 | } 330 | } 331 | 332 | // Signed Int type () 333 | // Example: -2147483648 334 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/9eef96ba-1876-427b-9450-75a1b28f5668 335 | 336 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 337 | pub struct CliInt32 { 338 | pub value: i32, 339 | pub name: Option, 340 | } 341 | 342 | impl CliInt32 { 343 | pub fn new(name: Option<&str>, value: i32) -> CliInt32 { 344 | CliInt32 { 345 | name: name.map(|s| s.to_string()), 346 | value: value, 347 | } 348 | } 349 | 350 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 351 | let value = value.parse::().ok()?; 352 | Some(Self::new(name, value)) 353 | } 354 | } 355 | 356 | // Unsigned Long type () 357 | // Example: 18446744073709551615 358 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/d92cd5d2-59c6-4a61-b517-9fc48823cb4d 359 | 360 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 361 | pub struct CliUInt64 { 362 | pub value: u64, 363 | pub name: Option, 364 | } 365 | 366 | impl CliUInt64 { 367 | pub fn new(name: Option<&str>, value: u64) -> CliUInt64 { 368 | CliUInt64 { 369 | name: name.map(|s| s.to_string()), 370 | value: value, 371 | } 372 | } 373 | 374 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 375 | let value = value.parse::().ok()?; 376 | Some(Self::new(name, value)) 377 | } 378 | } 379 | 380 | // Signed Long type () 381 | // Example: -9223372036854775808 382 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/de124e86-3f8c-426a-ab75-47fdb4597c62 383 | 384 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 385 | pub struct CliInt64 { 386 | pub value: i64, 387 | pub name: Option, 388 | } 389 | 390 | impl CliInt64 { 391 | pub fn new(name: Option<&str>, value: i64) -> CliInt64 { 392 | CliInt64 { 393 | name: name.map(|s| s.to_string()), 394 | value: value, 395 | } 396 | } 397 | 398 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 399 | let value = value.parse::().ok()?; 400 | Some(Self::new(name, value)) 401 | } 402 | } 403 | 404 | // Float type () 405 | // Example: 12.34 406 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/d8a5a9ab-5f52-4175-96a3-c29afb7b82b8 407 | 408 | #[derive(Debug, Clone, PartialEq, Default)] 409 | pub struct CliFloat { 410 | pub value: f32, 411 | pub name: Option, 412 | } 413 | 414 | impl CliFloat { 415 | pub fn new(name: Option<&str>, value: f32) -> CliFloat { 416 | CliFloat { 417 | name: name.map(|s| s.to_string()), 418 | value: value, 419 | } 420 | } 421 | 422 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 423 | let value = value.parse::().ok()?; 424 | Some(Self::new(name, value)) 425 | } 426 | } 427 | 428 | // Double type () 429 | // Example: 12.34 430 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/02fa08c5-139c-4e98-a13e-45784b4eabde 431 | 432 | #[derive(Debug, Clone, PartialEq, Default)] 433 | pub struct CliDouble { 434 | pub value: f64, 435 | pub name: Option, 436 | } 437 | 438 | impl CliDouble { 439 | pub fn new(name: Option<&str>, value: f64) -> CliDouble { 440 | CliDouble { 441 | name: name.map(|s| s.to_string()), 442 | value: value, 443 | } 444 | } 445 | 446 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 447 | let value = value.parse::().ok()?; 448 | Some(Self::new(name, value)) 449 | } 450 | } 451 | 452 | // Decimal type () 453 | // Example: 12.34 454 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/0f760f90-fa46-49bd-8868-001e2c29eb50 455 | 456 | #[derive(Debug, Clone, PartialEq, Default)] 457 | pub struct CliDecimal { 458 | pub value: d128, 459 | pub name: Option, 460 | } 461 | 462 | impl CliDecimal { 463 | pub fn new(name: Option<&str>, value: d128) -> CliDecimal { 464 | CliDecimal { 465 | name: name.map(|s| s.to_string()), 466 | value: value, 467 | } 468 | } 469 | 470 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 471 | let value = value.parse::().ok()?; 472 | Some(Self::new(name, value)) 473 | } 474 | } 475 | 476 | // Array of Bytes type () 477 | // Example: AQIDBA== 478 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/489ed886-34d2-4306-a2f5-73843c219b14 479 | 480 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 481 | pub struct CliBuffer { 482 | pub value: Vec, 483 | pub name: Option, 484 | } 485 | 486 | impl CliBuffer { 487 | pub fn new(name: Option<&str>, value: Vec) -> CliBuffer { 488 | CliBuffer { 489 | name: name.map(|s| s.to_string()), 490 | value: value, 491 | } 492 | } 493 | 494 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 495 | let value = base64::decode(value).ok()?; 496 | Some(Self::new(name, value)) 497 | } 498 | } 499 | 500 | // GUID type () 501 | // Example: 792e5b37-4505-47ef-b7d2-8711bb7affa8 502 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/c30c37fa-692d-49c7-bb86-b3179a97e106 503 | 504 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 505 | pub struct CliGuid { 506 | pub value: Uuid, 507 | pub name: Option, 508 | } 509 | 510 | impl CliGuid { 511 | pub fn new(name: Option<&str>, value: Uuid) -> CliGuid { 512 | CliGuid { 513 | name: name.map(|s| s.to_string()), 514 | value: value, 515 | } 516 | } 517 | 518 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 519 | let value = Uuid::parse_str(value).ok()?; 520 | Some(Self::new(name, value)) 521 | } 522 | } 523 | 524 | // URI type () 525 | // Example: http://www.microsoft.com/ 526 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/4ac73ac2-5cf7-4669-b4de-c8ba19a13186 527 | 528 | #[derive(Debug, Clone, PartialEq, Eq)] 529 | pub struct CliUri { 530 | pub value: Url, 531 | pub name: Option, 532 | } 533 | 534 | impl CliUri { 535 | pub fn new(name: Option<&str>, value: Url) -> CliUri { 536 | CliUri { 537 | name: name.map(|s| s.to_string()), 538 | value: value, 539 | } 540 | } 541 | 542 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 543 | let value = Url::parse(value).ok()?; 544 | Some(Self::new(name, value)) 545 | } 546 | } 547 | 548 | impl Default for CliUri { 549 | fn default() -> Self { 550 | CliUri::new(None, Url::parse("http://default").unwrap()) 551 | } 552 | } 553 | 554 | // Version type () 555 | // Example: 6.2.1.3 556 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/390db910-e035-4f97-80fd-181a008ff6f8 557 | 558 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 559 | pub struct CliVersion { 560 | pub value: String, 561 | pub name: Option, 562 | } 563 | 564 | impl CliVersion { 565 | pub fn new(name: Option<&str>, value: &str) -> CliVersion { 566 | CliVersion { 567 | name: name.map(|s| s.to_string()), 568 | value: value.to_string(), 569 | } 570 | } 571 | 572 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 573 | Some(Self::new(name, value)) 574 | } 575 | } 576 | 577 | // XML Document type () 578 | // Example: <name attribute="value">Content</name> 579 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/df5908ab-bb4d-45e4-8adc-7258e5a9f537 580 | 581 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 582 | pub struct CliXmlDocument { 583 | pub value: String, 584 | pub name: Option, 585 | } 586 | 587 | impl CliXmlDocument { 588 | pub fn new(name: Option<&str>, value: &str) -> CliXmlDocument { 589 | CliXmlDocument { 590 | name: name.map(|s| s.to_string()), 591 | value: value.to_string(), 592 | } 593 | } 594 | 595 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 596 | Some(Self::new(name, value)) 597 | } 598 | } 599 | 600 | // Script Block type () 601 | // Example: get-command -type cmdlet 602 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/306af1be-6be5-4074-acc9-e29bd32f3206 603 | 604 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 605 | pub struct CliScriptBlock { 606 | pub value: String, 607 | pub name: Option, 608 | } 609 | 610 | impl CliScriptBlock { 611 | pub fn new(name: Option<&str>, value: &str) -> CliScriptBlock { 612 | CliScriptBlock { 613 | name: name.map(|s| s.to_string()), 614 | value: value.to_string(), 615 | } 616 | } 617 | 618 | pub fn new_from_str(name: Option<&str>, value: &str) -> Option { 619 | Some(Self::new(name, value)) 620 | } 621 | } 622 | 623 | // Generic CLI XML Value type 624 | 625 | #[derive(Debug, Clone)] 626 | pub enum CliValue { 627 | CliObject(CliObject), 628 | CliNull(CliNull), 629 | CliString(CliString), 630 | CliChar(CliChar), 631 | CliBool(CliBool), 632 | CliDateTime(CliDateTime), 633 | CliDuration(CliDuration), 634 | CliUInt8(CliUInt8), 635 | CliInt8(CliInt8), 636 | CliUInt16(CliUInt16), 637 | CliInt16(CliInt16), 638 | CliUInt32(CliUInt32), 639 | CliInt32(CliInt32), 640 | CliUInt64(CliUInt64), 641 | CliInt64(CliInt64), 642 | CliFloat(CliFloat), 643 | CliDouble(CliDouble), 644 | CliDecimal(CliDecimal), 645 | CliBuffer(CliBuffer), 646 | CliGuid(CliGuid), 647 | CliUri(CliUri), 648 | CliVersion(CliVersion), 649 | CliXmlDocument(CliXmlDocument), 650 | CliScriptBlock(CliScriptBlock), 651 | } 652 | 653 | impl CliValue { 654 | pub fn get_name(&self) -> Option<&str> { 655 | match &*self { 656 | CliValue::CliObject(prop) => prop.name.as_deref(), 657 | CliValue::CliNull(prop) => prop.name.as_deref(), 658 | CliValue::CliString(prop) => prop.name.as_deref(), 659 | CliValue::CliChar(prop) => prop.name.as_deref(), 660 | CliValue::CliBool(prop) => prop.name.as_deref(), 661 | CliValue::CliDateTime(prop) => prop.name.as_deref(), 662 | CliValue::CliDuration(prop) => prop.name.as_deref(), 663 | CliValue::CliUInt8(prop) => prop.name.as_deref(), 664 | CliValue::CliInt8(prop) => prop.name.as_deref(), 665 | CliValue::CliUInt16(prop) => prop.name.as_deref(), 666 | CliValue::CliInt16(prop) => prop.name.as_deref(), 667 | CliValue::CliUInt32(prop) => prop.name.as_deref(), 668 | CliValue::CliInt32(prop) => prop.name.as_deref(), 669 | CliValue::CliUInt64(prop) => prop.name.as_deref(), 670 | CliValue::CliInt64(prop) => prop.name.as_deref(), 671 | CliValue::CliFloat(prop) => prop.name.as_deref(), 672 | CliValue::CliDouble(prop) => prop.name.as_deref(), 673 | CliValue::CliDecimal(prop) => prop.name.as_deref(), 674 | CliValue::CliBuffer(prop) => prop.name.as_deref(), 675 | CliValue::CliGuid(prop) => prop.name.as_deref(), 676 | CliValue::CliUri(prop) => prop.name.as_deref(), 677 | CliValue::CliVersion(prop) => prop.name.as_deref(), 678 | CliValue::CliXmlDocument(prop) => prop.name.as_deref(), 679 | CliValue::CliScriptBlock(prop) => prop.name.as_deref(), 680 | } 681 | } 682 | 683 | pub fn is_null(&self) -> bool { 684 | match *self { 685 | CliValue::CliNull(_) => true, 686 | _ => false, 687 | } 688 | } 689 | 690 | pub fn is_object(&self) -> bool { 691 | match *self { 692 | CliValue::CliObject(_) => true, 693 | _ => false, 694 | } 695 | } 696 | 697 | pub fn as_object(&self) -> Option<&CliObject> { 698 | match &*self { 699 | CliValue::CliObject(prop) => Some(&prop), 700 | _ => None, 701 | } 702 | } 703 | 704 | pub fn is_string(&self) -> bool { 705 | match *self { 706 | CliValue::CliString(_) => true, 707 | _ => false, 708 | } 709 | } 710 | 711 | pub fn as_str(&self) -> Option<&str> { 712 | match &*self { 713 | CliValue::CliString(prop) => Some(&prop.value), 714 | _ => None, 715 | } 716 | } 717 | 718 | pub fn is_char(&self) -> bool { 719 | match *self { 720 | CliValue::CliChar(_) => true, 721 | _ => false, 722 | } 723 | } 724 | 725 | pub fn as_char(&self) -> Option { 726 | match &*self { 727 | CliValue::CliChar(prop) => Some(prop.value), 728 | _ => None, 729 | } 730 | } 731 | 732 | pub fn is_bool(&self) -> bool { 733 | match *self { 734 | CliValue::CliBool(_) => true, 735 | _ => false, 736 | } 737 | } 738 | 739 | pub fn as_bool(&self) -> Option { 740 | match &*self { 741 | CliValue::CliBool(prop) => Some(prop.value), 742 | _ => None, 743 | } 744 | } 745 | 746 | pub fn is_datetime(&self) -> bool { 747 | match *self { 748 | CliValue::CliDateTime(_) => true, 749 | _ => false, 750 | } 751 | } 752 | 753 | pub fn as_datetime(&self) -> Option<&DateTime> { 754 | match &*self { 755 | CliValue::CliDateTime(prop) => Some(&prop.value), 756 | _ => None, 757 | } 758 | } 759 | 760 | pub fn is_duration(&self) -> bool { 761 | match *self { 762 | CliValue::CliDuration(_) => true, 763 | _ => false, 764 | } 765 | } 766 | 767 | pub fn as_duration(&self) -> Option<&Duration> { 768 | match &*self { 769 | CliValue::CliDuration(prop) => Some(&prop.value), 770 | _ => None, 771 | } 772 | } 773 | 774 | pub fn is_uint8(&self) -> bool { 775 | match *self { 776 | CliValue::CliUInt8(_) => true, 777 | _ => false, 778 | } 779 | } 780 | 781 | pub fn as_u8(&self) -> Option { 782 | match &*self { 783 | CliValue::CliUInt8(prop) => Some(prop.value), 784 | _ => None, 785 | } 786 | } 787 | 788 | pub fn is_int8(&self) -> bool { 789 | match *self { 790 | CliValue::CliInt8(_) => true, 791 | _ => false, 792 | } 793 | } 794 | 795 | pub fn as_i8(&self) -> Option { 796 | match &*self { 797 | CliValue::CliInt8(prop) => Some(prop.value), 798 | _ => None, 799 | } 800 | } 801 | 802 | pub fn is_uint16(&self) -> bool { 803 | match *self { 804 | CliValue::CliUInt16(_) => true, 805 | _ => false, 806 | } 807 | } 808 | 809 | pub fn as_u16(&self) -> Option { 810 | match &*self { 811 | CliValue::CliUInt8(prop) => Some(prop.value as u16), 812 | CliValue::CliUInt16(prop) => Some(prop.value), 813 | _ => None, 814 | } 815 | } 816 | 817 | pub fn is_int16(&self) -> bool { 818 | match *self { 819 | CliValue::CliInt16(_) => true, 820 | _ => false, 821 | } 822 | } 823 | 824 | pub fn as_i16(&self) -> Option { 825 | match &*self { 826 | CliValue::CliInt8(prop) => Some(prop.value as i16), 827 | CliValue::CliInt16(prop) => Some(prop.value), 828 | _ => None, 829 | } 830 | } 831 | 832 | pub fn is_uint32(&self) -> bool { 833 | match *self { 834 | CliValue::CliUInt32(_) => true, 835 | _ => false, 836 | } 837 | } 838 | 839 | pub fn as_u32(&self) -> Option { 840 | match &*self { 841 | CliValue::CliUInt8(prop) => Some(prop.value as u32), 842 | CliValue::CliUInt16(prop) => Some(prop.value as u32), 843 | CliValue::CliUInt32(prop) => Some(prop.value), 844 | _ => None, 845 | } 846 | } 847 | 848 | pub fn is_int32(&self) -> bool { 849 | match *self { 850 | CliValue::CliInt32(_) => true, 851 | _ => false, 852 | } 853 | } 854 | 855 | pub fn as_i32(&self) -> Option { 856 | match &*self { 857 | CliValue::CliInt8(prop) => Some(prop.value as i32), 858 | CliValue::CliInt16(prop) => Some(prop.value as i32), 859 | CliValue::CliInt32(prop) => Some(prop.value), 860 | _ => None, 861 | } 862 | } 863 | 864 | pub fn is_uint64(&self) -> bool { 865 | match *self { 866 | CliValue::CliUInt64(_) => true, 867 | _ => false, 868 | } 869 | } 870 | 871 | pub fn as_u64(&self) -> Option { 872 | match &*self { 873 | CliValue::CliUInt8(prop) => Some(prop.value as u64), 874 | CliValue::CliUInt16(prop) => Some(prop.value as u64), 875 | CliValue::CliUInt32(prop) => Some(prop.value as u64), 876 | CliValue::CliUInt64(prop) => Some(prop.value), 877 | _ => None, 878 | } 879 | } 880 | 881 | pub fn is_int64(&self) -> bool { 882 | match *self { 883 | CliValue::CliInt64(_) => true, 884 | _ => false, 885 | } 886 | } 887 | 888 | pub fn as_i64(&self) -> Option { 889 | match &*self { 890 | CliValue::CliInt8(prop) => Some(prop.value as i64), 891 | CliValue::CliInt16(prop) => Some(prop.value as i64), 892 | CliValue::CliInt32(prop) => Some(prop.value as i64), 893 | CliValue::CliInt64(prop) => Some(prop.value), 894 | _ => None, 895 | } 896 | } 897 | 898 | pub fn is_float(&self) -> bool { 899 | match *self { 900 | CliValue::CliFloat(_) => true, 901 | _ => false, 902 | } 903 | } 904 | 905 | pub fn as_float(&self) -> Option { 906 | match &*self { 907 | CliValue::CliFloat(prop) => Some(prop.value), 908 | _ => None, 909 | } 910 | } 911 | 912 | pub fn is_double(&self) -> bool { 913 | match *self { 914 | CliValue::CliDouble(_) => true, 915 | _ => false, 916 | } 917 | } 918 | 919 | pub fn as_double(&self) -> Option { 920 | match &*self { 921 | CliValue::CliFloat(prop) => Some(prop.value as f64), 922 | CliValue::CliDouble(prop) => Some(prop.value), 923 | _ => None, 924 | } 925 | } 926 | 927 | pub fn is_decimal(&self) -> bool { 928 | match *self { 929 | CliValue::CliDecimal(_) => true, 930 | _ => false, 931 | } 932 | } 933 | 934 | pub fn as_d128(&self) -> Option { 935 | match &*self { 936 | CliValue::CliDecimal(prop) => Some(prop.value), 937 | _ => None, 938 | } 939 | } 940 | 941 | pub fn is_buffer(&self) -> bool { 942 | match *self { 943 | CliValue::CliBuffer(_) => true, 944 | _ => false, 945 | } 946 | } 947 | 948 | pub fn as_bytes(&self) -> Option<&Vec> { 949 | match &*self { 950 | CliValue::CliBuffer(prop) => Some(&prop.value), 951 | _ => None, 952 | } 953 | } 954 | 955 | pub fn is_guid(&self) -> bool { 956 | match *self { 957 | CliValue::CliGuid(_) => true, 958 | _ => false, 959 | } 960 | } 961 | 962 | pub fn as_guid(&self) -> Option<&Uuid> { 963 | match &*self { 964 | CliValue::CliGuid(prop) => Some(&prop.value), 965 | _ => None, 966 | } 967 | } 968 | 969 | pub fn is_uri(&self) -> bool { 970 | match *self { 971 | CliValue::CliUri(_) => true, 972 | _ => false, 973 | } 974 | } 975 | 976 | pub fn as_uri(&self) -> Option<&Url> { 977 | match &*self { 978 | CliValue::CliUri(prop) => Some(&prop.value), 979 | _ => None, 980 | } 981 | } 982 | 983 | pub fn is_version(&self) -> bool { 984 | match *self { 985 | CliValue::CliVersion(_) => true, 986 | _ => false, 987 | } 988 | } 989 | 990 | pub fn as_version(&self) -> Option<&str> { 991 | match &*self { 992 | CliValue::CliVersion(prop) => Some(&prop.value), 993 | _ => None, 994 | } 995 | } 996 | 997 | pub fn is_xml_document(&self) -> bool { 998 | match *self { 999 | CliValue::CliXmlDocument(_) => true, 1000 | _ => false, 1001 | } 1002 | } 1003 | 1004 | pub fn as_xml_document(&self) -> Option<&str> { 1005 | match &*self { 1006 | CliValue::CliXmlDocument(prop) => Some(&prop.value), 1007 | _ => None, 1008 | } 1009 | } 1010 | 1011 | pub fn is_script_block(&self) -> bool { 1012 | match *self { 1013 | CliValue::CliScriptBlock(_) => true, 1014 | _ => false, 1015 | } 1016 | } 1017 | 1018 | pub fn as_script_block(&self) -> Option<&str> { 1019 | match &*self { 1020 | CliValue::CliScriptBlock(prop) => Some(&prop.value), 1021 | _ => None, 1022 | } 1023 | } 1024 | } 1025 | 1026 | fn try_get_ref_id_attr(reader: &Reader, event: &events::BytesStart) -> Option { 1027 | let attr = event.try_get_attribute("RefId").ok().unwrap()?; 1028 | let value = attr.decode_and_unescape_value(&reader).ok()?; 1029 | Some(value.to_string()) 1030 | } 1031 | 1032 | fn try_get_name_attr(reader: &Reader, event: &events::BytesStart) -> Option { 1033 | let attr = event.try_get_attribute("N").ok().unwrap()?; 1034 | let value = attr.decode_and_unescape_value(&reader).ok()?; 1035 | Some(value.to_string()) 1036 | } 1037 | 1038 | pub fn parse_cli_xml(cli_xml: &str) -> Vec { 1039 | let mut reader = Reader::from_str(cli_xml); 1040 | reader.expand_empty_elements(true); 1041 | reader.trim_text(true); 1042 | 1043 | let mut objs: Vec = Vec::new(); 1044 | let mut obj = CliObject::default(); 1045 | 1046 | loop { 1047 | let event = reader.read_event(); 1048 | match event { 1049 | Ok(Event::Start(event)) => { 1050 | match event.name().as_ref() { 1051 | b"Objs" => {} 1052 | b"Obj" => { 1053 | if let Some(ref_id) = try_get_ref_id_attr(&reader, &event) { 1054 | obj.ref_id = Some(ref_id); 1055 | } 1056 | } 1057 | b"TN" => { 1058 | if let Some(_ref_id) = try_get_ref_id_attr(&reader, &event) { 1059 | //println!("TN RefId={}", ref_id); 1060 | } 1061 | } 1062 | b"T" => { 1063 | let txt = reader.read_text(event.name()).unwrap(); 1064 | obj.type_names.push(txt.to_string()); 1065 | } 1066 | b"TNRef" => { 1067 | if let Some(_ref_id) = try_get_ref_id_attr(&reader, &event) { 1068 | //println!("TNRef RefId={}", ref_id); 1069 | } 1070 | } 1071 | b"ToString" => { 1072 | let txt = reader.read_text(event.name()).unwrap(); 1073 | obj.string_repr = Some(txt.to_string()); 1074 | } 1075 | b"Props" => { 1076 | // Adapted Properties 1077 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/173c30d7-b0a6-4aad-9b00-9891c441b0f3 1078 | } 1079 | b"MS" => { 1080 | // Extended Properties 1081 | // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/4cca6d92-4a8e-4406-91cb-0235a98f7d6f 1082 | } 1083 | b"LST" => {} 1084 | b"IE" => {} 1085 | b"B" => { 1086 | let txt = reader.read_text(event.name()).unwrap(); 1087 | let prop_name = try_get_name_attr(&reader, &event); 1088 | let val = CliBool::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1089 | obj.values.push(CliValue::CliBool(val)); 1090 | } 1091 | b"S" => { 1092 | let txt = reader.read_text(event.name()).unwrap(); 1093 | let prop_name = try_get_name_attr(&reader, &event); 1094 | let val = CliString::new(prop_name.as_deref(), &txt); 1095 | obj.values.push(CliValue::CliString(val)); 1096 | } 1097 | b"C" => { 1098 | let txt = reader.read_text(event.name()).unwrap(); 1099 | let prop_name = try_get_name_attr(&reader, &event); 1100 | let val = CliChar::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1101 | obj.values.push(CliValue::CliChar(val)); 1102 | } 1103 | b"By" => { 1104 | let txt = reader.read_text(event.name()).unwrap(); 1105 | let prop_name = try_get_name_attr(&reader, &event); 1106 | let val = CliUInt8::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1107 | obj.values.push(CliValue::CliUInt8(val)); 1108 | } 1109 | b"SB" => { 1110 | let txt = reader.read_text(event.name()).unwrap(); 1111 | let prop_name = try_get_name_attr(&reader, &event); 1112 | let val = CliInt8::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1113 | obj.values.push(CliValue::CliInt8(val)); 1114 | } 1115 | b"U16" => { 1116 | let txt = reader.read_text(event.name()).unwrap(); 1117 | let prop_name = try_get_name_attr(&reader, &event); 1118 | let val = CliUInt16::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1119 | obj.values.push(CliValue::CliUInt16(val)); 1120 | } 1121 | b"I16" => { 1122 | let txt = reader.read_text(event.name()).unwrap(); 1123 | let prop_name = try_get_name_attr(&reader, &event); 1124 | let val = CliInt16::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1125 | obj.values.push(CliValue::CliInt16(val)); 1126 | } 1127 | b"U32" => { 1128 | let txt = reader.read_text(event.name()).unwrap(); 1129 | let prop_name = try_get_name_attr(&reader, &event); 1130 | let val = CliUInt32::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1131 | obj.values.push(CliValue::CliUInt32(val)); 1132 | } 1133 | b"I32" => { 1134 | let txt = reader.read_text(event.name()).unwrap(); 1135 | let prop_name = try_get_name_attr(&reader, &event); 1136 | let val = CliInt32::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1137 | obj.values.push(CliValue::CliInt32(val)); 1138 | } 1139 | b"U64" => { 1140 | let txt = reader.read_text(event.name()).unwrap(); 1141 | let prop_name = try_get_name_attr(&reader, &event); 1142 | let val = CliUInt64::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1143 | obj.values.push(CliValue::CliUInt64(val)); 1144 | } 1145 | b"I64" => { 1146 | let txt = reader.read_text(event.name()).unwrap(); 1147 | let prop_name = try_get_name_attr(&reader, &event); 1148 | let val = CliInt64::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1149 | obj.values.push(CliValue::CliInt64(val)); 1150 | } 1151 | b"DT" => { 1152 | let txt = reader.read_text(event.name()).unwrap(); 1153 | let prop_name = try_get_name_attr(&reader, &event); 1154 | let val = CliDateTime::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1155 | obj.values.push(CliValue::CliDateTime(val)); 1156 | } 1157 | b"TS" => { 1158 | let txt = reader.read_text(event.name()).unwrap(); 1159 | let prop_name = try_get_name_attr(&reader, &event); 1160 | let val = CliDuration::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1161 | obj.values.push(CliValue::CliDuration(val)); 1162 | } 1163 | b"Sg" => { 1164 | let txt = reader.read_text(event.name()).unwrap(); 1165 | let prop_name = try_get_name_attr(&reader, &event); 1166 | let val = CliFloat::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1167 | obj.values.push(CliValue::CliFloat(val)); 1168 | } 1169 | b"Db" => { 1170 | let txt = reader.read_text(event.name()).unwrap(); 1171 | let prop_name = try_get_name_attr(&reader, &event); 1172 | let val = CliDouble::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1173 | obj.values.push(CliValue::CliDouble(val)); 1174 | } 1175 | b"D" => { 1176 | let txt = reader.read_text(event.name()).unwrap(); 1177 | let prop_name = try_get_name_attr(&reader, &event); 1178 | let val = CliDecimal::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1179 | obj.values.push(CliValue::CliDecimal(val)); 1180 | } 1181 | b"BA" => { 1182 | let txt = reader.read_text(event.name()).unwrap(); 1183 | let prop_name = try_get_name_attr(&reader, &event); 1184 | let val = CliBuffer::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1185 | obj.values.push(CliValue::CliBuffer(val)); 1186 | } 1187 | b"G" => { 1188 | let txt = reader.read_text(event.name()).unwrap(); 1189 | let prop_name = try_get_name_attr(&reader, &event); 1190 | let val = CliGuid::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1191 | obj.values.push(CliValue::CliGuid(val)); 1192 | } 1193 | b"URI" => { 1194 | let txt = reader.read_text(event.name()).unwrap(); 1195 | let prop_name = try_get_name_attr(&reader, &event); 1196 | let val = CliUri::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1197 | obj.values.push(CliValue::CliUri(val)); 1198 | } 1199 | b"Version" => { 1200 | let txt = reader.read_text(event.name()).unwrap(); 1201 | let prop_name = try_get_name_attr(&reader, &event); 1202 | let val = CliVersion::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1203 | obj.values.push(CliValue::CliVersion(val)); 1204 | } 1205 | b"XD" => { 1206 | let txt = reader.read_text(event.name()).unwrap(); 1207 | let prop_name = try_get_name_attr(&reader, &event); 1208 | let val = CliXmlDocument::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1209 | obj.values.push(CliValue::CliXmlDocument(val)); 1210 | } 1211 | b"SBK" => { 1212 | let txt = reader.read_text(event.name()).unwrap(); 1213 | let prop_name = try_get_name_attr(&reader, &event); 1214 | let val = CliScriptBlock::new_from_str(prop_name.as_deref(), &txt).unwrap(); 1215 | obj.values.push(CliValue::CliScriptBlock(val)); 1216 | } 1217 | b"Nil" => { 1218 | let prop_name = try_get_name_attr(&reader, &event); 1219 | let val = CliNull::new(prop_name.as_deref()); 1220 | obj.values.push(CliValue::CliNull(val)); 1221 | } 1222 | _ => { 1223 | let event_name = event.name(); 1224 | let tag_name = String::from_utf8_lossy(event_name.as_ref()); 1225 | eprintln!("unsupported: {}", &tag_name); 1226 | } 1227 | } 1228 | } 1229 | Ok(Event::End(event)) => match event.name().as_ref() { 1230 | b"Obj" => { 1231 | objs.push(obj); 1232 | obj = CliObject::default(); 1233 | } 1234 | _ => {} 1235 | }, 1236 | Ok(Event::Text(_event)) => {} 1237 | Ok(Event::Eof) => break, 1238 | Err(event) => panic!( 1239 | "Error at position {}: {:?}", 1240 | reader.buffer_position(), 1241 | event 1242 | ), 1243 | _ => (), 1244 | } 1245 | } 1246 | 1247 | objs 1248 | } 1249 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::delegate_loader::{AssemblyDelegateLoader, DelegateLoader, MethodWithUnknownSignature}; 2 | use crate::error::Error; 3 | use crate::host_exit_code::HostExitCode; 4 | use crate::hostfxr::{ 5 | GetFunctionPointerFn, Hostfxr, HostfxrDelegateType, Hostfxrhandle, 6 | LoadAssemblyAndGetFunctionPointerFn, 7 | }; 8 | use crate::pdcstring::PdCStr; 9 | use core::{mem, ptr}; 10 | use std::borrow::BorrowMut; 11 | use std::{marker::PhantomData, ptr::NonNull}; 12 | 13 | /// A marker struct indicating that the context was initialized for the dotnet command line. 14 | /// This means that it is possible to run the application associated with the context. 15 | #[allow(dead_code)] 16 | pub struct InitializedForCommandLine; 17 | 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct HostfxrHandle(NonNull<()>); 20 | 21 | impl HostfxrHandle { 22 | #[allow(dead_code)] 23 | pub unsafe fn new_unchecked(ptr: Hostfxrhandle) -> Self { 24 | Self(NonNull::new_unchecked(ptr as *mut _)) 25 | } 26 | 27 | #[allow(dead_code)] 28 | pub fn as_raw(&self) -> Hostfxrhandle { 29 | self.0.as_ptr() as Hostfxrhandle 30 | } 31 | } 32 | 33 | #[derive(Clone)] 34 | pub struct HostfxrContext<'a, I> { 35 | handle: HostfxrHandle, 36 | hostfxr: &'a Hostfxr, 37 | context_type: PhantomData<&'a I>, 38 | } 39 | 40 | impl<'a, I> HostfxrContext<'a, I> { 41 | #[allow(dead_code)] 42 | pub fn new(handle: HostfxrHandle, hostfxr: &'a Hostfxr) -> Self { 43 | Self { 44 | handle, 45 | hostfxr, 46 | context_type: PhantomData, 47 | } 48 | } 49 | 50 | #[allow(dead_code)] 51 | pub fn get_runtime_delegate( 52 | &self, 53 | delegate_type: HostfxrDelegateType, 54 | ) -> Result { 55 | let mut delegate = ptr::null::<*mut libc::c_void>() as *mut libc::c_void; 56 | let result = unsafe { 57 | self.hostfxr.lib.hostfxr_get_runtime_delegate( 58 | self.handle.as_raw(), 59 | delegate_type, 60 | delegate.borrow_mut() as *mut _ as *mut libc::c_void, //Initialise nullptr 61 | ) 62 | }; 63 | HostExitCode::from(result).into_result()?; 64 | Ok(delegate) 65 | } 66 | 67 | #[allow(dead_code)] 68 | fn get_load_assembly_and_get_function_pointer_delegate( 69 | &self, 70 | ) -> Result { 71 | unsafe { 72 | self.get_runtime_delegate(HostfxrDelegateType::LoadAssemblyAndGetFunctionPointer) 73 | .map(|ptr| mem::transmute(ptr)) 74 | } 75 | } 76 | 77 | #[allow(dead_code)] 78 | fn get_get_function_pointer_delegate(&self) -> Result { 79 | unsafe { 80 | self.get_runtime_delegate(HostfxrDelegateType::GetFunctionPointer) 81 | .map(|ptr| mem::transmute(ptr)) 82 | } 83 | } 84 | 85 | #[allow(dead_code)] 86 | pub fn get_delegate_loader(&self) -> Result { 87 | Ok(DelegateLoader { 88 | get_load_assembly_and_get_function_pointer: self 89 | .get_load_assembly_and_get_function_pointer_delegate()?, 90 | get_function_pointer: self.get_get_function_pointer_delegate()?, 91 | }) 92 | } 93 | 94 | #[allow(dead_code)] 95 | pub fn get_delegate_loader_for_assembly>( 96 | &self, 97 | assembly_path: A, 98 | ) -> Result, Error> { 99 | self.get_delegate_loader() 100 | .map(|loader| AssemblyDelegateLoader::new(loader, assembly_path)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/delegate_loader.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::host_exit_code::HostExitCode; 3 | use crate::hostfxr::{ 4 | char_t, GetFunctionPointerFn, LoadAssemblyAndGetFunctionPointerFn, 5 | UNMANAGED_CALLERS_ONLY_METHOD, 6 | }; 7 | use crate::pdcstring::PdCStr; 8 | use core::ptr; 9 | use std::borrow::BorrowMut; 10 | 11 | #[derive(Copy, Clone)] 12 | pub struct DelegateLoader { 13 | pub get_load_assembly_and_get_function_pointer: LoadAssemblyAndGetFunctionPointerFn, 14 | pub get_function_pointer: GetFunctionPointerFn, 15 | } 16 | 17 | pub type MethodWithUnknownSignature = *mut libc::c_void; 18 | 19 | impl DelegateLoader { 20 | #[allow(dead_code)] 21 | pub fn load_assembly_and_get_function_pointer( 22 | &self, 23 | assembly_path: impl AsRef, 24 | type_name: impl AsRef, 25 | method_name: impl AsRef, 26 | delegate_type_name: impl AsRef, 27 | ) -> Result { 28 | unsafe { 29 | self._load_assembly_and_get_function_pointer( 30 | assembly_path.as_ref().as_ptr(), 31 | type_name.as_ref().as_ptr(), 32 | method_name.as_ref().as_ptr(), 33 | delegate_type_name.as_ref().as_ptr(), 34 | ) 35 | } 36 | } 37 | 38 | unsafe fn _load_assembly_and_get_function_pointer( 39 | &self, 40 | assembly_path: *const char_t, 41 | type_name: *const char_t, 42 | method_name: *const char_t, 43 | delegate_type_name: *const char_t, 44 | ) -> Result { 45 | let mut delegate = ptr::null::<*mut libc::c_void>() as *mut libc::c_void; 46 | 47 | let result = (self.get_load_assembly_and_get_function_pointer)( 48 | assembly_path, 49 | type_name, 50 | method_name, 51 | delegate_type_name, 52 | ptr::null(), 53 | delegate.borrow_mut() as *mut _ as *mut libc::c_void, //Initialise nullptr 54 | ); 55 | HostExitCode::from(result).into_result()?; 56 | Ok(delegate) 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub fn get_function_pointer( 61 | &self, 62 | type_name: impl AsRef, 63 | method_name: impl AsRef, 64 | delegate_type_name: impl AsRef, 65 | ) -> Result { 66 | unsafe { 67 | self._get_function_pointer( 68 | type_name.as_ref().as_ptr(), 69 | method_name.as_ref().as_ptr(), 70 | delegate_type_name.as_ref().as_ptr(), 71 | ) 72 | } 73 | } 74 | 75 | unsafe fn _get_function_pointer( 76 | &self, 77 | type_name: *const char_t, 78 | method_name: *const char_t, 79 | delegate_type_name: *const char_t, 80 | ) -> Result { 81 | let mut delegate = ptr::null::<*mut libc::c_void>() as *mut libc::c_void; 82 | 83 | let result = (self.get_function_pointer)( 84 | type_name, 85 | method_name, 86 | delegate_type_name, 87 | ptr::null(), 88 | ptr::null(), 89 | delegate.borrow_mut() as *mut _ as *mut libc::c_void, //Initialise nullptr 90 | ); 91 | HostExitCode::from(result).into_result()?; 92 | Ok(delegate) 93 | } 94 | 95 | #[allow(dead_code)] 96 | pub fn load_assembly_and_get_function_pointer_for_unmanaged_callers_only_method( 97 | &self, 98 | assembly_path: impl AsRef, 99 | type_name: impl AsRef, 100 | method_name: impl AsRef, 101 | ) -> Result { 102 | unsafe { 103 | self._load_assembly_and_get_function_pointer( 104 | assembly_path.as_ref().as_ptr(), 105 | type_name.as_ref().as_ptr(), 106 | method_name.as_ref().as_ptr(), 107 | UNMANAGED_CALLERS_ONLY_METHOD, 108 | ) 109 | } 110 | } 111 | 112 | #[allow(dead_code)] 113 | pub fn get_function_pointer_for_unmanaged_callers_only_method( 114 | &self, 115 | type_name: impl AsRef, 116 | method_name: impl AsRef, 117 | ) -> Result { 118 | unsafe { 119 | self._get_function_pointer( 120 | type_name.as_ref().as_ptr(), 121 | method_name.as_ref().as_ptr(), 122 | UNMANAGED_CALLERS_ONLY_METHOD, 123 | ) 124 | } 125 | } 126 | } 127 | 128 | pub struct AssemblyDelegateLoader> { 129 | loader: DelegateLoader, 130 | assembly_path: A, 131 | } 132 | 133 | impl> AssemblyDelegateLoader { 134 | pub fn new(loader: DelegateLoader, assembly_path: A) -> Self { 135 | Self { 136 | loader, 137 | assembly_path, 138 | } 139 | } 140 | 141 | #[allow(dead_code)] 142 | pub fn get_function_pointer( 143 | &self, 144 | type_name: impl AsRef, 145 | method_name: impl AsRef, 146 | delegate_type_name: impl AsRef, 147 | ) -> Result { 148 | self.loader.load_assembly_and_get_function_pointer( 149 | self.assembly_path.as_ref(), 150 | type_name, 151 | method_name, 152 | delegate_type_name, 153 | ) 154 | } 155 | 156 | #[allow(dead_code)] 157 | pub fn get_function_pointer_for_unmanaged_callers_only_method( 158 | &self, 159 | type_name: impl AsRef, 160 | method_name: impl AsRef, 161 | ) -> Result { 162 | self.loader 163 | .load_assembly_and_get_function_pointer_for_unmanaged_callers_only_method( 164 | self.assembly_path.as_ref(), 165 | type_name, 166 | method_name, 167 | ) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::host_exit_code::HostExitCode; 2 | use std::io; 3 | 4 | quick_error! { 5 | /// An error struct encompassing all possible errors of this crate. 6 | #[derive(Debug)] 7 | pub enum Error { 8 | DlOpen(err: dlopen::Error) { 9 | from() 10 | display("dlopen error: {}", err) 11 | source(err) 12 | } 13 | IO(err: io::Error) { 14 | from() 15 | display("io error: {}", err) 16 | source(err) 17 | } 18 | Hostfxr(error_code: HostExitCode) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/host_detect.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | use thiserror::Error; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Debug, Error, PartialEq)] 7 | pub enum EnvError { 8 | #[error("PATH undefined or unset in the environment.")] 9 | UndefOrUnset, 10 | #[error("PowerShell install dir not found in PATH")] 11 | Missing, 12 | } 13 | 14 | pub fn find_pwsh_exe() -> Option { 15 | if let Ok(pwsh_exe) = which::which("pwsh") { 16 | if let Ok(pwsh_link) = fs::read_link(&pwsh_exe) { 17 | return Some(pwsh_link); 18 | } else { 19 | return Some(pwsh_exe); 20 | } 21 | } 22 | None 23 | } 24 | 25 | pub fn find_pwsh_dir() -> Option { 26 | if let Some(mut pwsh_exe) = find_pwsh_exe() { 27 | pwsh_exe.pop(); 28 | return Some(pwsh_exe); 29 | } 30 | None 31 | } 32 | 33 | #[allow(dead_code)] 34 | pub fn pwsh_host_detect() -> Result { 35 | find_pwsh_dir().ok_or(EnvError::Missing) 36 | } 37 | -------------------------------------------------------------------------------- /src/host_exit_code.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use num_enum::TryFromPrimitive; 3 | use std::{ 4 | convert::{TryFrom, TryInto}, 5 | num::TryFromIntError, 6 | }; 7 | /// The special error or exit codes for hostfxr operations. 8 | /// 9 | /// Source: [https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md](https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md) 10 | #[non_exhaustive] 11 | #[derive(Debug, PartialEq, Eq, Clone, Copy, TryFromPrimitive)] 12 | #[repr(u32)] 13 | pub enum KnownHostExitCode { 14 | /// Operation was successful. 15 | Success = 0x00000000, // 0 16 | /// Initialization was successful, but another host context is already initialized, so the returned context is "secondary". The requested context was otherwise fully compatible with the already initialized context. This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`. 17 | SuccessHostAlreadyInitialized = 0x00000001, // 1 18 | /// Initialization was successful, but another host context is already initialized and the requested context specified some runtime properties which are not the same (either in value or in presence) to the already initialized context. This is returned by `hostfxr_initialize_for_runtime_config` if it's called when the host is already initialized in the process. Comes from `corehost_initialize` in `hostpolicy`. 19 | SuccessDifferentRuntimeProperties = 0x00000002, // 2 20 | /// One of the specified arguments for the operation is invalid." 21 | InvalidArgFailure = 0x80008081, // -2147450751 22 | /// There was a failure loading a dependent library. If any of the hosting components calls `LoadLibrary`/`dlopen` on a dependent library and the call fails, this error code is returned. The most common case for this failure is if the dependent library is missing some of its dependencies (for example the necessary CRT is missing on the machine), likely corrupt or incomplete install. This error code is also returned from `corehost_resolve_component_dependencies` if it's called on a `hostpolicy` which has not been initialized via the hosting layer. This would typically happen if `coreclr` is loaded directly without the hosting layer and then `AssemblyDependencyResolver` is used (which is an unsupported scenario). 23 | CoreHostLibLoadFailure = 0x80008082, // -2147450750 24 | /// One of the dependent libraries is missing. Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic libraries are not present in the expected locations. Probably means corrupted or incomplete installation. 25 | CoreHostLibMissingFailure = 0x80008083, // -2147450749 26 | /// One of the dependent libraries is missing a required entry point. 27 | CoreHostEntryPointFailure = 0x80008084, // -2147450748 28 | /// If the hosting component is trying to use the path to the current module (the hosting component itself) and from it deduce the location of the installation. Either the location of the current module could not be determined (some weird OS call failure) or the location is not in the right place relative to other expected components. For example the `hostfxr` may look at its location and try to deduce the location of the `shared` folder with the framework from it. It assumes the typical install layout on disk. If this doesn't work, this error will be returned. 29 | CoreHostCurHostFindFailure = 0x80008085, // -2147450747 30 | /// If the `coreclr` library could not be found. The hosting layer (`hostpolicy`) looks for `coreclr` library either next to the app itself (for self-contained) or in the root framework (for framework-dependent). This search can be done purely by looking at disk or more commonly by looking into the respective `.deps.json`. If the `coreclr` library is missing in `.deps.json` or it's there but doesn't exist on disk, this error is returned. 31 | CoreClrResolveFailure = 0x80008087, // -2147450745 32 | /// The loaded `coreclr` library doesn't have one of the required entry points. 33 | CoreClrBindFailure = 0x80008088, // -2147450744 34 | /// The call to `coreclr_initialize` failed. The actual error returned by `coreclr` is reported in the error message. 35 | CoreClrInitFailure = 0x80008089, // -2147450743 36 | /// The call to `coreclr_execute_assembly` failed. Note that this does not mean anything about the app's exit code, this failure occurs if `coreclr` failed to run the app itself. 37 | CoreClrExeFailure = 0x8000808a, // -2147450742 38 | /// Initialization of the `hostpolicy` dependency resolver failed. This can be:
  • One of the frameworks or the app is missing a required `.deps.json` file.
  • One of the `.deps.json` files is invalid (invalid JSON, or missing required properties and so on).
39 | ResolverInitFailure = 0x8000808b, // -2147450741 40 | /// Resolution of dependencies in `hostpolicy` failed. This can mean many different things, but in general one of the processed `.deps.json` contains entry for a file which could not found, or its resolution failed for some other reason (conflict for example). 41 | ResolverResolveFailure = 0x8000808c, // -2147450740 42 | /// Failure to determine the location of the current executable. The hosting layer uses the current executable path to deduce the install location in some cases. If this path can't be obtained (OS call fails, or the returned path doesn't exist), this error is returned. 43 | LibHostCurExeFindFailure = 0x8000808d, // -2147450739 44 | /// Initialization of the `hostpolicy` library failed. The `corehost_load` method takes a structure with lot of initialization parameters. If the version of this structure doesn't match the expected value, this error code is returned. This would in general mean incompatibility between the `hostfxr` and `hostpolicy`, which should really only happen if somehow a newer `hostpolicy` is used by older `hostfxr`. This typically means corrupted installation. 45 | LibHostInitFailure = 0x8000808e, // -2147450738 46 | /// Failure to find the requested SDK. This happens in the `hostfxr` when an SDK (also called CLI) command is used with `dotnet`. In this case the hosting layer tries to find an installed .NET SDK to run the command on. The search is based on deduced install location and on the requested version from potential `global.json` file. If either no matching SDK version can be found, or that version exists, but it's missing the `dotnet.dll` file, this error code is returned. 47 | LibHostSdkFindFailure = 0x80008091, // -2147450735 48 | /// Arguments to `hostpolicy` are invalid. This is used in three unrelated places in the `hostpolicy`, but in all cases it means the component calling `hostpolicy` did something wrong:
  • Command line arguments for the app - the failure would typically mean that wrong argument was passed or such. For example if the application main assembly is not specified on the command line. On its own this should not happen as `hostfxr` should have parsed and validated all command line arguments.
  • `hostpolicy` context's `get_delegate` - if the requested delegate enum value is not recognized. Again this would mean `hostfxr` passed the wrong value.
  • `corehost_resolve_component_dependencies` - if something went wrong initializing `hostpolicy` internal structures. Would happen for example when the `component_main_assembly_path` argument is wrong.
49 | LibHostInvalidArgs = 0x80008092, // -2147450734 50 | /// The `.runtimeconfig.json` file is invalid. The reasons for this failure can be among these:
  • Failure to read from the file
  • Invalid JSON
  • Invalid value for a property (for example number for property which requires a string)
  • Missing required property
  • Other inconsistencies (for example `rollForward` and `applyPatches` are not allowed to be specified in the same config file)
  • Any of the above failures reading the `.runtimecofig.dev.json` file
  • Self-contained `.runtimeconfig.json` used in `hostfxr_initialize_for_runtime_config`. Note that missing `.runtimconfig.json` is not an error (means self-contained app). This error code is also used when there is a problem reading the CLSID map file in `comhost`.
51 | InvalidConfigFile = 0x80008093, // -2147450733 52 | /// Used internally when the command line for `dotnet.exe` doesn't contain path to the application to run. In such case the command line is considered to be a CLI/SDK command. This error code should never be returned to external caller. 53 | AppArgNotRunnable = 0x80008094, // -2147450732 54 | /// `apphost` failed to determine which application to run. This can mean:
  • The `apphost` binary has not been imprinted with the path to the app to run (so freshly built `apphost.exe` from the branch will fail to run like this)
  • The `apphost` is a bundle (single-file exe) and it failed to extract correctly.
55 | AppHostExeNotBoundFailure = 0x80008095, // -2147450731 56 | /// It was not possible to find a compatible framework version. This originates in `hostfxr` (`resolve_framework_reference`) and means that the app specified a reference to a framework in its `.runtimeconfig.json` which could not be resolved. The failure to resolve can mean that no such framework is available on the disk, or that the available frameworks don't match the minimum version specified or that the roll forward options specified excluded all available frameworks. Typically this would be used if a 3.0 app is trying to run on a machine which has no 3.0 installed. It would also be used for example if a 32bit 3.0 app is running on a machine which has 3.0 installed but only for 64bit. 57 | FrameworkMissingFailure = 0x80008096, // -2147450730 58 | /// Returned by `hostfxr_get_native_search_directories` if the `hostpolicy` could not calculate the `NATIVE_DLL_SEARCH_DIRECTORIES`. 59 | HostApiFailed = 0x80008097, // -2147450729 60 | /// Returned when the buffer specified to an API is not big enough to fit the requested value. Can be returned from:
  • `hostfxr_get_runtime_properties`
  • `hostfxr_get_native_search_directories`
  • `get_hostfxr_path`
61 | HostApiBufferTooSmall = 0x80008098, // -2147450728 62 | /// Returned by `hostpolicy` if the `corehost_main_with_output_buffer` is called with unsupported host command. This error code means there is incompatibility between the `hostfxr` and `hostpolicy`. In reality this should pretty much never happen. 63 | LibHostUnknownCommand = 0x80008099, // -2147450727 64 | /// Returned by `apphost` if the imprinted application path doesn't exist. This would happen if the app is built with an executable (the `apphost`) and the main `app.dll` is missing. 65 | LibHostAppRootFindFailure = 0x8000809a, // -2147450726 66 | /// Returned from `hostfxr_resolve_sdk2` when it fails to find matching SDK. Similar to `LibHostSdkFindFailure` but only used in the `hostfxr_resolve_sdk2`. 67 | SdkResolverResolveFailure = 0x8000809b, // -2147450725 68 | /// During processing of `.runtimeconfig.json` there were two framework references to the same framework which were not compatible. This can happen if the app specified a framework reference to a lower-level framework which is also specified by a higher-level framework which is also used by the app. For example, this would happen if the app referenced `Microsoft.AspNet.App` version 2.0 and `Microsoft.NETCore.App` version 3.0. In such case the `Microsoft.AspNet.App` has `.runtimeconfig.json` which also references `Microsoft.NETCore.App` but it only allows versions 2.0 up to 2.9 (via roll forward options). So the version 3.0 requested by the app is incompatible. 69 | FrameworkCompatFailure = 0x8000809c, // -2147450724 70 | /// Error used internally if the processing of framework references from `.runtimeconfig.json` reached a point where it needs to reprocess another already processed framework reference. If this error is returned to the external caller, it would mean there's a bug in the framework resolution algorithm. 71 | FrameworkCompatRetry = 0x8000809d, // -2147450723 72 | /// Error reading the bundle footer metadata from a single-file `apphost`. This would mean a corrupted `apphost`. 73 | AppHostExeNotBundle = 0x8000809e, // -2147450722 74 | /// Error extracting single-file `apphost` bundle. This is used in case of any error related to the bundle itself. Typically would mean a corrupted bundle. 75 | BundleExtractionFailure = 0x8000809f, // -2147450721 76 | /// Error reading or writing files during single-file `apphost` bundle extraction. 77 | BundleExtractionIOError = 0x800080a0, // -2147450720 78 | /// The `.runtimeconfig.json` specified by the app contains a runtime property which is also produced by the hosting layer. For example if the `.runtimeconfig.json` would specify a property `TRUSTED_PLATFORM_ROOTS`, this error code would be returned. It is not allowed to specify properties which are otherwise populated by the hosting layer (`hostpolicy`) as there is not good way to resolve such conflicts. 79 | LibHostDuplicateProperty = 0x800080a1, // -2147450719 80 | /// Feature which requires certain version of the hosting layer binaries was used on a version which doesn't support it. For example if COM component specified to run on 2.0 `Microsoft.NETCore.App` - as that contains older version of `hostpolicy` which doesn't support the necessary features to provide COM services. 81 | HostApiUnsupportedVersion = 0x800080a2, // -2147450718 82 | /// Error code returned by the hosting APIs in `hostfxr` if the current state is incompatible with the requested operation. There are many such cases, please refer to the documentation of the hosting APIs for details. For example if `hostfxr_get_runtime_property_value` is called with the `host_context_handle` `nullptr` (meaning get property from the active runtime) but there's no active runtime in the process. 83 | HostInvalidState = 0x800080a3, // -2147450717 84 | /// property requested by `hostfxr_get_runtime_property_value` doesn't exist. 85 | HostPropertyNotFound = 0x800080a4, // -2147450716 86 | /// Error returned by `hostfxr_initialize_for_runtime_config` if the component being initialized requires framework which is not available or incompatible with the frameworks loaded by the runtime already in the process. For example trying to load a component which requires 3.0 into a process which is already running a 2.0 runtime. 87 | CoreHostIncompatibleConfig = 0x800080a5, // -2147450715 88 | /// Error returned by `hostfxr_get_runtime_delegate` when `hostfxr` doesn't currently support requesting the given delegate type using the given context. 89 | HostApiUnsupportedScenario = 0x800080a6, // -2147450714 90 | } 91 | 92 | impl KnownHostExitCode { 93 | #[inline] 94 | pub fn is_success(&self) -> bool { 95 | matches!( 96 | self, 97 | KnownHostExitCode::Success 98 | | KnownHostExitCode::SuccessDifferentRuntimeProperties 99 | | KnownHostExitCode::SuccessHostAlreadyInitialized 100 | ) 101 | } 102 | 103 | #[inline] 104 | pub fn is_error(&self) -> bool { 105 | !self.is_success() 106 | } 107 | 108 | #[inline] 109 | pub fn value(&self) -> u32 { 110 | *self as u32 111 | } 112 | 113 | #[inline] 114 | pub fn into_result(self) -> Result<(), Error> { 115 | if self.is_success() { 116 | Ok(()) 117 | } else { 118 | Err(Error::Hostfxr(HostExitCode::Known(self))) 119 | } 120 | } 121 | } 122 | 123 | impl From for u32 { 124 | fn from(code: KnownHostExitCode) -> Self { 125 | code.value() 126 | } 127 | } 128 | 129 | /// Represents an exit code from a hostfxr operation. 130 | /// This may be one of the special code from [`KnownHostExitCode`], 131 | /// the exit code of the application or an error code resulting from violating 132 | /// some unchecked hostfxr constraint. 133 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 134 | pub enum HostExitCode { 135 | Known(KnownHostExitCode), 136 | Unknown(u32), 137 | } 138 | 139 | impl HostExitCode { 140 | /// Constructs a new instance from the given value. 141 | pub fn new(value: u32) -> Self { 142 | match value.try_into() { 143 | Ok(known) => HostExitCode::Known(known), 144 | Err(_) => HostExitCode::Unknown(value), 145 | } 146 | } 147 | 148 | #[inline] 149 | pub fn is_success(&self) -> bool { 150 | match self { 151 | HostExitCode::Known(code) => code.is_success(), 152 | HostExitCode::Unknown(_) => false, 153 | } 154 | } 155 | 156 | #[inline] 157 | pub fn is_error(&self) -> bool { 158 | !self.is_success() 159 | } 160 | 161 | #[inline] 162 | pub fn value(&self) -> u32 { 163 | match self { 164 | HostExitCode::Known(code) => code.value(), 165 | HostExitCode::Unknown(code) => *code, 166 | } 167 | } 168 | 169 | #[inline] 170 | pub fn into_result(self) -> Result<(), Error> { 171 | match self { 172 | HostExitCode::Known(code) => code.into_result(), 173 | HostExitCode::Unknown(_) => Err(Error::Hostfxr(self)), 174 | } 175 | } 176 | } 177 | 178 | impl From for HostExitCode { 179 | fn from(value: i32) -> Self { 180 | HostExitCode::new(value as u32) 181 | } 182 | } 183 | 184 | impl From for HostExitCode { 185 | fn from(value: u32) -> Self { 186 | HostExitCode::new(value) 187 | } 188 | } 189 | 190 | impl TryFrom for i32 { 191 | type Error = TryFromIntError; 192 | 193 | fn try_from(code: HostExitCode) -> Result { 194 | Self::try_from(code.value()) 195 | } 196 | } 197 | 198 | impl From for u32 { 199 | fn from(code: HostExitCode) -> Self { 200 | code.value() 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/hostfxr.rs: -------------------------------------------------------------------------------- 1 | use crate::context::{HostfxrContext, HostfxrHandle, InitializedForCommandLine}; 2 | use crate::host_detect::pwsh_host_detect; 3 | use crate::pdcstring::{PdCStr, PdCString}; 4 | use dlopen::wrapper::{Container, WrapperApi}; 5 | use std::borrow::BorrowMut; 6 | use std::ffi::OsStr; 7 | 8 | #[cfg(windows)] 9 | #[allow(non_camel_case_types)] 10 | pub type char_t = u16; 11 | /// The char type used in nethost and hostfxr. Either u8 on unix systems or u16 on windows. 12 | #[allow(non_camel_case_types)] 13 | #[cfg(not(windows))] 14 | pub type char_t = i8; 15 | 16 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 17 | pub const UNMANAGED_CALLERS_ONLY_METHOD: *const char_t = usize::MAX as *const _; 18 | 19 | #[repr(i32)] 20 | #[allow(dead_code)] 21 | pub enum HostfxrDelegateType { 22 | ComActivation, 23 | LoadInMemoryAssembly, 24 | WinrtActivation, 25 | ComRegister, 26 | ComUnregister, 27 | LoadAssemblyAndGetFunctionPointer, 28 | GetFunctionPointer, 29 | } 30 | 31 | #[repr(C)] 32 | pub struct HostfxrInitializeParameters { 33 | pub size: usize, 34 | pub host_path: Box, //*const char_t, 35 | pub dotnet_root: Box, //*const char_t, 36 | } 37 | 38 | pub type Hostfxrhandle = *mut libc::c_void; 39 | 40 | #[derive(WrapperApi)] 41 | pub struct HostfxrLib { 42 | hostfxr_initialize_for_dotnet_command_line: unsafe extern "C" fn( 43 | argc: i32, 44 | argv: *const *const char_t, 45 | parameters: *const HostfxrInitializeParameters, 46 | host_context_handle: Hostfxrhandle, 47 | ) -> i32, 48 | hostfxr_initialize_for_runtime_config: unsafe extern "C" fn( 49 | runtime_config_path: *const char_t, 50 | parameters: *const HostfxrInitializeParameters, 51 | host_context_handle: *mut Hostfxrhandle, 52 | ) -> i32, 53 | hostfxr_get_runtime_property_value: unsafe extern "C" fn( 54 | host_context_handle: Hostfxrhandle, 55 | name: *const char_t, 56 | value: *mut *const char_t, 57 | ) -> i32, 58 | hostfxr_set_runtime_property_value: unsafe extern "C" fn( 59 | host_context_handle: Hostfxrhandle, 60 | name: *const char_t, 61 | value: *const char_t, 62 | ) -> i32, 63 | hostfxr_get_runtime_properties: unsafe extern "C" fn( 64 | host_context_handle: Hostfxrhandle, 65 | count: *mut libc::size_t, 66 | keys: *mut *const char_t, 67 | values: *mut *const char_t, 68 | ) -> i32, 69 | hostfxr_run_app: unsafe extern "C" fn(host_context_handle: Hostfxrhandle) -> i32, 70 | hostfxr_get_runtime_delegate: unsafe extern "C" fn( 71 | host_context_handle: Hostfxrhandle, 72 | delegate_type: HostfxrDelegateType, 73 | delegate: *mut libc::c_void, 74 | ) -> i32, 75 | hostfxr_close: unsafe extern "C" fn(host_context_handle: Hostfxrhandle) -> i32, 76 | } 77 | 78 | impl HostfxrLib { 79 | #[allow(dead_code)] 80 | fn load_lib(path: impl AsRef) -> Result, Box> { 81 | Ok(unsafe { Container::load(path)? }) 82 | } 83 | } 84 | 85 | pub type LoadAssemblyAndGetFunctionPointerFn = unsafe extern "system" fn( 86 | assembly_path: *const char_t, 87 | type_name: *const char_t, 88 | method_name: *const char_t, 89 | delegate_type_name: *const char_t, 90 | reserved: *const (), 91 | delegate: *mut libc::c_void, 92 | ) -> i32; 93 | 94 | pub type GetFunctionPointerFn = unsafe extern "system" fn( 95 | type_name: *const char_t, 96 | method_name: *const char_t, 97 | delegate_type_name: *const char_t, 98 | load_context: *const (), 99 | reserved: *const (), 100 | delegate: *mut libc::c_void, 101 | ) -> i32; 102 | 103 | pub struct Hostfxr { 104 | pub lib: Container, 105 | } 106 | 107 | impl Hostfxr { 108 | #[allow(dead_code)] 109 | pub fn load_from_path(path: impl AsRef) -> Result> { 110 | Ok(Self { 111 | lib: HostfxrLib::load_lib(path)?, 112 | }) 113 | } 114 | 115 | #[allow(dead_code)] 116 | pub fn initialize_for_dotnet_command_line( 117 | &self, 118 | pwsh_path: impl AsRef, 119 | ) -> Result, Box> { 120 | use crate::host_exit_code::HostExitCode; 121 | use std::ptr; 122 | 123 | let args = &[PdCString::from_os_str(pwsh_path)?]; 124 | let mut host_context_handle = ptr::null::() as Hostfxrhandle; 125 | 126 | let result = unsafe { 127 | self.lib.hostfxr_initialize_for_dotnet_command_line( 128 | args.len() as i32, 129 | args.as_ptr() as *const *const char_t, 130 | ptr::null(), 131 | host_context_handle.borrow_mut() as *mut _ as Hostfxrhandle, //Initialise nullptr 132 | ) 133 | }; 134 | 135 | HostExitCode::from(result).into_result()?; 136 | 137 | Ok(HostfxrContext::new( 138 | unsafe { HostfxrHandle::new_unchecked(host_context_handle) }, 139 | self, 140 | )) 141 | } 142 | 143 | #[allow(dead_code)] 144 | pub fn initialize_for_runtime_config( 145 | &self, 146 | runtime_config_path: impl AsRef, 147 | parameters: Box, //*const HostfxrInitializeParameters, 148 | host_context_handle: *mut Hostfxrhandle, 149 | ) -> i32 { 150 | unsafe { 151 | self.lib.hostfxr_initialize_for_runtime_config( 152 | runtime_config_path.as_ref().as_ptr(), 153 | parameters.as_ref(), 154 | host_context_handle, 155 | ) 156 | } 157 | } 158 | 159 | #[allow(dead_code)] 160 | pub fn get_runtime_property_value( 161 | &self, 162 | host_context_handle: Hostfxrhandle, 163 | name: impl AsRef, //*const char_t, 164 | value: impl AsRef, //*mut *const char_t, 165 | ) -> i32 { 166 | unsafe { 167 | self.lib.hostfxr_get_runtime_property_value( 168 | host_context_handle, 169 | name.as_ref().as_ptr(), 170 | value.as_ref().as_ptr().borrow_mut(), 171 | ) 172 | } 173 | } 174 | 175 | #[allow(dead_code)] 176 | pub fn set_runtime_property_value( 177 | &self, 178 | host_context_handle: Hostfxrhandle, 179 | name: impl AsRef, //*const char_t, 180 | value: impl AsRef, //*const char_t, 181 | ) -> i32 { 182 | unsafe { 183 | self.lib.hostfxr_set_runtime_property_value( 184 | host_context_handle, 185 | name.as_ref().as_ptr(), 186 | value.as_ref().as_ptr(), 187 | ) 188 | } 189 | } 190 | 191 | #[allow(dead_code)] 192 | pub fn get_runtime_properties( 193 | &self, 194 | host_context_handle: Hostfxrhandle, 195 | count: &mut usize, //*mut libc::size_t, 196 | keys: impl AsRef, //*mut *const char_t, 197 | values: impl AsRef, //*mut *const char_t, 198 | ) -> i32 { 199 | unsafe { 200 | self.lib.hostfxr_get_runtime_properties( 201 | host_context_handle, 202 | count, 203 | keys.as_ref().as_ptr().borrow_mut(), 204 | values.as_ref().as_ptr().borrow_mut(), 205 | ) 206 | } 207 | } 208 | 209 | #[allow(dead_code)] 210 | pub fn run_app(&self, host_context_handle: Hostfxrhandle) -> i32 { 211 | unsafe { self.lib.hostfxr_run_app(host_context_handle) } 212 | } 213 | 214 | #[allow(dead_code)] 215 | pub fn get_runtime_delegate( 216 | &self, 217 | host_context_handle: Hostfxrhandle, 218 | delegate_type: HostfxrDelegateType, 219 | delegate: &mut libc::c_void, //*mut libc::c_void, 220 | ) -> i32 { 221 | unsafe { 222 | self.lib 223 | .hostfxr_get_runtime_delegate(host_context_handle, delegate_type, delegate) 224 | } 225 | } 226 | 227 | #[allow(dead_code)] 228 | pub fn close(&self, host_context_handle: Hostfxrhandle) -> i32 { 229 | unsafe { self.lib.hostfxr_close(host_context_handle) } 230 | } 231 | } 232 | 233 | #[allow(dead_code)] 234 | pub fn load_hostfxr() -> Result> { 235 | let pwsh_path = pwsh_host_detect()?; 236 | Hostfxr::load_from_path(pwsh_path.join(if cfg!(target_os = "windows") { 237 | "hostfxr.dll" 238 | } else if cfg!(target_os = "linux") { 239 | "libhostfxr.so" 240 | } else { 241 | "libhostfxr.dylib" 242 | })) 243 | } 244 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bindings; 2 | mod cli_xml; 3 | mod context; 4 | mod delegate_loader; 5 | mod error; 6 | mod host_detect; 7 | mod host_exit_code; 8 | mod hostfxr; 9 | mod loader; 10 | mod tests; 11 | mod time; 12 | 13 | extern crate libc; 14 | #[macro_use] 15 | extern crate dlopen_derive; 16 | extern crate dlopen; 17 | #[macro_use] 18 | extern crate quick_error; 19 | 20 | /// Module for a platform dependent c-like string type. 21 | #[macro_use] 22 | mod pdcstring; 23 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::delegate_loader::AssemblyDelegateLoader; 2 | use crate::host_detect::pwsh_host_detect; 3 | use crate::host_exit_code::HostExitCode; 4 | use crate::hostfxr::load_hostfxr; 5 | use crate::pdcstr; 6 | use crate::pdcstring::PdCString; 7 | 8 | pub const BINDINGS_DLL: &[u8] = include_bytes!("../dotnet/bin/Release/net6.0/Bindings.dll"); 9 | 10 | pub fn get_assembly_delegate_loader() -> AssemblyDelegateLoader { 11 | let pwsh_path = pwsh_host_detect(); 12 | assert!(pwsh_path.is_ok()); 13 | let pwsh_path = pwsh_path.unwrap(); 14 | 15 | let hostfxr = load_hostfxr(); 16 | assert!(hostfxr.is_ok()); 17 | let hostfxr = hostfxr.unwrap(); 18 | 19 | let ctx = hostfxr.initialize_for_dotnet_command_line(pwsh_path.join("pwsh.dll")); 20 | assert!(ctx.is_ok()); 21 | let ctx = ctx.unwrap(); 22 | 23 | let assembly_path = PdCString::from_os_str( 24 | pwsh_path 25 | .join("System.Management.Automation.dll") 26 | .into_os_string(), 27 | ); 28 | assert!(assembly_path.is_ok()); 29 | let assembly_path = assembly_path.unwrap(); 30 | 31 | let fn_loader = ctx.get_delegate_loader_for_assembly(assembly_path); 32 | assert!(fn_loader.is_ok()); 33 | let fn_loader = fn_loader.unwrap(); 34 | 35 | let load_assembly_from_native_memory = fn_loader.get_function_pointer_for_unmanaged_callers_only_method( 36 | pdcstr!("System.Management.Automation.PowerShellUnsafeAssemblyLoad, System.Management.Automation"), 37 | pdcstr!("LoadAssemblyFromNativeMemory")); 38 | assert!(load_assembly_from_native_memory.is_ok()); 39 | let load_assembly_from_native_memory = load_assembly_from_native_memory.unwrap(); 40 | 41 | let load_assembly_from_native_memory: extern "system" fn( 42 | bytes: *const libc::c_uchar, 43 | size: libc::c_uint, 44 | ) -> i32 = unsafe { std::mem::transmute(load_assembly_from_native_memory) }; 45 | let result = 46 | (load_assembly_from_native_memory)(BINDINGS_DLL.as_ptr(), BINDINGS_DLL.len() as u32); 47 | HostExitCode::from(result).into_result().unwrap(); 48 | 49 | fn_loader 50 | } 51 | -------------------------------------------------------------------------------- /src/pdcstring/error.rs: -------------------------------------------------------------------------------- 1 | use super::PdUChar; 2 | use std::{error::Error, fmt}; 3 | 4 | // same definition as ffi::NulError and widestring::NulError 5 | #[derive(Clone, PartialEq, Eq, Debug)] 6 | pub struct NulError(usize, Vec); 7 | 8 | impl NulError { 9 | pub fn new(nul_position: usize, data: Vec) -> Self { 10 | Self(nul_position, data) 11 | } 12 | 13 | pub fn nul_position(&self) -> usize { 14 | self.0 15 | } 16 | 17 | pub fn into_vec(self) -> Vec { 18 | self.1 19 | } 20 | } 21 | 22 | #[cfg(not(windows))] 23 | impl From for NulError { 24 | fn from(err: std::ffi::NulError) -> Self { 25 | Self::new(err.nul_position(), err.into_vec()) 26 | } 27 | } 28 | 29 | #[cfg(windows)] 30 | impl From> for NulError { 31 | fn from(err: widestring::NulError) -> Self { 32 | Self::new(err.nul_position(), err.into_vec()) 33 | } 34 | } 35 | 36 | impl Error for NulError { 37 | fn description(&self) -> &str { 38 | "nul value found in data" 39 | } 40 | } 41 | 42 | impl fmt::Display for NulError { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | write!(f, "nul byte found in provided data at position: {}", self.0) 45 | } 46 | } 47 | 48 | impl From for Vec { 49 | fn from(e: NulError) -> Vec { 50 | e.into_vec() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/pdcstring/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | pub use error::*; 3 | #[allow(dead_code)] 4 | pub type PdChar = crate::hostfxr::char_t; 5 | #[cfg(windows)] 6 | pub type PdUChar = u16; 7 | #[cfg(not(windows))] 8 | pub type PdUChar = u8; 9 | 10 | #[cfg(windows)] 11 | mod windows; 12 | #[cfg(windows)] 13 | use windows::*; 14 | 15 | #[cfg(not(windows))] 16 | mod other; 17 | #[cfg(not(windows))] 18 | use other::*; 19 | 20 | mod shared; 21 | pub use shared::*; 22 | -------------------------------------------------------------------------------- /src/pdcstring/other.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{self, CStr, CString, OsStr, OsString}; 2 | use std::os::unix::ffi::OsStrExt; 3 | use std::str::{self, FromStr}; 4 | 5 | use super::{NulError, PdCStr, PdCString}; 6 | 7 | pub type PdCStringInner = CString; 8 | pub type PdCStrInner = CStr; 9 | 10 | pub extern crate cstr; 11 | 12 | #[macro_export] 13 | macro_rules! pdcstr { 14 | ($expression:expr) => { 15 | $crate::pdcstring::PdCStr::from_c_str(::cstr::cstr!($expression)) 16 | }; 17 | } 18 | 19 | impl PdCString { 20 | pub fn from_c_string(s: CString) -> Self { 21 | PdCString::from_inner(s) 22 | } 23 | pub fn into_c_string(self) -> CString { 24 | self.into_inner() 25 | } 26 | } 27 | 28 | impl PdCString { 29 | pub fn from_os_str(s: impl AsRef) -> Result { 30 | PdCString::from_vec(s.as_ref().as_bytes().to_vec()) 31 | } 32 | } 33 | 34 | impl FromStr for PdCString { 35 | type Err = NulError; 36 | 37 | fn from_str(s: &str) -> Result { 38 | PdCString::from_vec(s.as_bytes().to_vec()) 39 | } 40 | } 41 | 42 | // methods not used by this crate 43 | impl PdCString { 44 | pub fn from_vec(vec: impl Into>) -> Result { 45 | let inner = CString::new(vec)?; 46 | Ok(PdCString::from_inner(inner)) 47 | } 48 | pub fn into_vec(self) -> Vec { 49 | self.0.into_bytes() 50 | } 51 | pub fn into_vec_with_nul(self) -> Vec { 52 | self.0.into_bytes_with_nul() 53 | } 54 | } 55 | 56 | impl PdCStr { 57 | pub fn from_c_str(s: &CStr) -> &Self { 58 | PdCStr::from_inner(s) 59 | } 60 | pub fn to_c_str(&self) -> &CStr { 61 | self.as_inner() 62 | } 63 | } 64 | 65 | // methods used by this crate 66 | impl PdCStr { 67 | pub fn as_ptr(&self) -> *const i8 { 68 | self.0.as_ptr() 69 | } 70 | 71 | pub unsafe fn from_slice_with_nul_unchecked(slice: &[u8]) -> &Self { 72 | let inner = CStr::from_bytes_with_nul_unchecked(slice); 73 | PdCStr::from_inner(inner) 74 | } 75 | pub fn to_os_string(&self) -> OsString { 76 | self.to_os_str().to_owned() 77 | } 78 | } 79 | 80 | // methods not used by this crate 81 | impl PdCStr { 82 | pub fn to_os_str(&self) -> &OsStr { 83 | OsStr::from_bytes(self.0.to_bytes()) 84 | } 85 | // TODO: use abstract error type 86 | pub fn from_slice_with_nul(slice: &[u8]) -> Result<&Self, ffi::FromBytesWithNulError> { 87 | CStr::from_bytes_with_nul(slice).map(|s| PdCStr::from_inner(s)) 88 | } 89 | pub fn to_slice(&self) -> &[u8] { 90 | self.0.to_bytes() 91 | } 92 | pub fn to_slice_with_nul(&self) -> &[u8] { 93 | self.0.to_bytes_with_nul() 94 | } 95 | pub fn len(&self) -> usize { 96 | self.0.to_bytes().len() 97 | } 98 | pub fn to_string(&self) -> Result { 99 | self.0.to_str().map(|s| s.to_string()) 100 | } 101 | pub fn to_string_lossy(&self) -> String { 102 | self.0.to_string_lossy().to_string() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/pdcstring/shared.rs: -------------------------------------------------------------------------------- 1 | use super::{NulError, PdCStrInner, PdCStringInner, PdUChar}; 2 | use std::{ 3 | borrow::Borrow, 4 | convert::TryFrom, 5 | fmt::{self, Debug, Display, Formatter}, 6 | ops::Deref, 7 | str::FromStr, 8 | }; 9 | 10 | /// A platform-dependent c-like string type for interacting with the hostfxr API. 11 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] 12 | #[repr(transparent)] 13 | pub struct PdCString(pub PdCStringInner); 14 | 15 | impl PdCString { 16 | pub fn from_inner(inner: PdCStringInner) -> Self { 17 | Self(inner) 18 | } 19 | pub fn into_inner(self) -> PdCStringInner { 20 | self.0 21 | } 22 | } 23 | 24 | /// A borrowed slice of a PdCString. 25 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 26 | #[repr(transparent)] 27 | pub struct PdCStr(pub PdCStrInner); 28 | 29 | impl PdCStr { 30 | pub fn from_inner(inner: &PdCStrInner) -> &Self { 31 | // Safety: 32 | // Safe because PdCStr has the same layout as PdCStrInner 33 | unsafe { &*(inner as *const PdCStrInner as *const PdCStr) } 34 | } 35 | 36 | pub fn as_inner(&self) -> &PdCStrInner { 37 | // Safety: 38 | // Safe because PdCStr has the same layout as PdCStrInner 39 | unsafe { &*(self as *const PdCStr as *const PdCStrInner) } 40 | } 41 | } 42 | 43 | impl Borrow for PdCString { 44 | fn borrow(&self) -> &PdCStr { 45 | PdCStr::from_inner(self.0.borrow()) 46 | } 47 | } 48 | 49 | impl AsRef for PdCString { 50 | fn as_ref(&self) -> &PdCStr { 51 | self.borrow() 52 | } 53 | } 54 | 55 | impl Deref for PdCString { 56 | type Target = PdCStr; 57 | fn deref(&self) -> &Self::Target { 58 | self.borrow() 59 | } 60 | } 61 | 62 | impl Default for PdCString { 63 | fn default() -> Self { 64 | Self(Default::default()) 65 | } 66 | } 67 | 68 | impl Display for PdCStr { 69 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 70 | self.0.fmt(f) 71 | } 72 | } 73 | 74 | impl From for PdCString { 75 | fn from(s: PdCStringInner) -> Self { 76 | Self::from_inner(s) 77 | } 78 | } 79 | 80 | impl From for PdCStringInner { 81 | fn from(s: PdCString) -> Self { 82 | s.into_inner() 83 | } 84 | } 85 | 86 | impl<'a> From<&'a PdCStrInner> for &'a PdCStr { 87 | fn from(s: &'a PdCStrInner) -> Self { 88 | PdCStr::from_inner(s) 89 | } 90 | } 91 | 92 | impl<'a> From<&'a PdCStr> for &'a PdCStrInner { 93 | fn from(s: &'a PdCStr) -> Self { 94 | s.as_inner() 95 | } 96 | } 97 | 98 | impl<'a> TryFrom<&'a str> for PdCString { 99 | type Error = NulError; 100 | 101 | fn try_from(s: &'a str) -> Result { 102 | Self::from_str(s) 103 | } 104 | } 105 | 106 | impl TryFrom> for PdCString { 107 | type Error = NulError; 108 | 109 | fn try_from(vec: Vec) -> Result { 110 | Self::from_vec(vec) 111 | } 112 | } 113 | 114 | impl From for Vec { 115 | fn from(s: PdCString) -> Vec { 116 | s.into_vec() 117 | } 118 | } 119 | 120 | impl AsRef for PdCStr { 121 | fn as_ref(&self) -> &Self { 122 | self 123 | } 124 | } 125 | 126 | impl ToOwned for PdCStr { 127 | type Owned = PdCString; 128 | fn to_owned(&self) -> Self::Owned { 129 | PdCString::from_inner(self.0.to_owned()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/pdcstring/windows.rs: -------------------------------------------------------------------------------- 1 | use super::{NulError, PdCStr, PdCString}; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::str::FromStr; 4 | use std::string; 5 | use widestring::{U16CStr, U16CString}; 6 | 7 | pub type PdCStringInner = U16CString; 8 | pub type PdCStrInner = U16CStr; 9 | 10 | pub extern crate u16cstr; 11 | #[macro_export] 12 | macro_rules! pdcstr { 13 | ($expression:expr) => { 14 | $crate::pdcstring::PdCStr::from_u16_c_str(::u16cstr::u16cstr!($expression)) 15 | }; 16 | } 17 | 18 | impl PdCString { 19 | pub fn from_u16_c_string(s: U16CString) -> Self { 20 | PdCString::from_inner(s) 21 | } 22 | pub fn into_u16_c_string(self) -> U16CString { 23 | self.into_inner() 24 | } 25 | } 26 | 27 | // methods used by this crate 28 | impl PdCString { 29 | pub fn from_os_str(s: impl AsRef) -> Result { 30 | let inner = U16CString::from_os_str(s)?; 31 | Ok(PdCString::from_u16_c_string(inner)) 32 | } 33 | } 34 | 35 | impl FromStr for PdCString { 36 | type Err = NulError; 37 | fn from_str(s: &str) -> Result { 38 | let inner = U16CString::from_str(s)?; 39 | Ok(PdCString::from_u16_c_string(inner)) 40 | } 41 | } 42 | 43 | // methods not used by this crate 44 | impl PdCString { 45 | pub fn from_vec(vec: impl Into>) -> Result { 46 | let inner = U16CString::new(vec)?; 47 | Ok(PdCString::from_inner(inner)) 48 | } 49 | 50 | pub fn into_vec(self) -> Vec { 51 | self.0.into_vec() 52 | } 53 | pub fn into_vec_with_nul(self) -> Vec { 54 | self.0.into_vec_with_nul() 55 | } 56 | } 57 | 58 | // conversions to and from inner 59 | impl PdCStr { 60 | pub fn from_u16_c_str(s: &U16CStr) -> &Self { 61 | PdCStr::from_inner(s) 62 | } 63 | pub fn to_u16_c_str(&self) -> &U16CStr { 64 | self.as_inner() 65 | } 66 | } 67 | 68 | // methods used by this crate 69 | impl PdCStr { 70 | pub fn as_ptr(&self) -> *const u16 { 71 | self.0.as_ptr() 72 | } 73 | 74 | pub unsafe fn from_slice_with_nul_unchecked(slice: &[u16]) -> &Self { 75 | let inner = U16CStr::from_slice_with_nul_unchecked(slice); 76 | PdCStr::from_inner(inner) 77 | } 78 | 79 | pub fn to_os_string(&self) -> OsString { 80 | self.0.to_os_string() 81 | } 82 | } 83 | 84 | // methods not used by this crate 85 | impl PdCStr { 86 | // TODO: use abstract error type 87 | pub fn from_slice_with_nul(slice: &[u16]) -> Result<&Self, widestring::MissingNulError> { 88 | U16CStr::from_slice_with_nul(slice).map(|s| PdCStr::from_inner(s)) 89 | } 90 | 91 | pub fn to_slice(&self) -> &[u16] { 92 | self.0.as_slice() 93 | } 94 | pub fn to_slice_with_nul(&self) -> &[u16] { 95 | self.0.as_slice_with_nul() 96 | } 97 | pub fn len(&self) -> usize { 98 | self.0.len() 99 | } 100 | pub fn to_string(&self) -> Result { 101 | self.0.to_string() 102 | } 103 | pub fn to_string_lossy(&self) -> String { 104 | self.0.to_string_lossy() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod pwsh { 3 | use crate::bindings::PowerShell; 4 | use crate::cli_xml::{parse_cli_xml, CliObject}; 5 | use uuid::Uuid; 6 | 7 | #[test] 8 | fn load_pwsh_sdk_invoke_api() { 9 | let pwsh = PowerShell::new().unwrap(); 10 | 11 | // Get-Command -CommandType Cmdlet -Name *-Object -Module Microsoft.PowerShell.Utility | 12 | // Select-Object -ExpandProperty Name | Set-Variable -Name UtilityCommands 13 | pwsh.add_command("Get-Command"); 14 | pwsh.add_parameter_string("-CommandType", "Cmdlet"); 15 | pwsh.add_parameter_string("-Name", "*-Object"); 16 | pwsh.add_parameter_string("-Module", "Microsoft.PowerShell.Utility"); 17 | pwsh.add_command("Select-Object"); 18 | pwsh.add_parameter_string("-ExpandProperty", "Name"); 19 | pwsh.add_command("Set-Variable"); 20 | pwsh.add_parameter_string("-Name", "UtilityCommands"); 21 | pwsh.add_statement(); 22 | pwsh.invoke(true); 23 | 24 | let cmds_txt = pwsh.export_to_string("UtilityCommands"); 25 | let pwsh_cmds: Vec<&str> = cmds_txt.lines().collect(); 26 | 27 | println!("\nCommands (text):"); 28 | for pwsh_cmd in &pwsh_cmds { 29 | println!("{}", &pwsh_cmd); 30 | } 31 | 32 | assert_eq!(pwsh_cmds.len(), 7); 33 | assert_eq!(pwsh_cmds.get(0), Some(&"Compare-Object")); 34 | assert_eq!(pwsh_cmds.get(1), Some(&"Group-Object")); 35 | assert_eq!(pwsh_cmds.get(2), Some(&"Measure-Object")); 36 | 37 | // Get-Date -UnixTimeSeconds 1577836800 | Set-Variable -Name Date 38 | pwsh.add_command("Get-Date"); 39 | pwsh.add_parameter_long("-UnixTimeSeconds", 1577836800); 40 | pwsh.add_command("Set-Variable"); 41 | pwsh.add_parameter_string("-Name", "Date"); 42 | pwsh.add_statement(); 43 | pwsh.invoke(true); 44 | 45 | let date_json = pwsh.export_to_json("Date"); 46 | println!("\nDate (JSON):\n{}", &date_json); 47 | assert_eq!(&date_json, "\"2019-12-31T19:00:00-05:00\""); 48 | 49 | // Get-Verb -Verb Test | Set-Variable -Name Verb 50 | pwsh.add_script("Get-Verb -Verb Test"); 51 | pwsh.add_command("Set-Variable"); 52 | pwsh.add_parameter_string("-Name", "Verb"); 53 | pwsh.add_statement(); 54 | pwsh.invoke(true); 55 | 56 | let verb_xml = pwsh.export_to_xml("Verb"); 57 | println!("\nVerb (XML):\n{}", &verb_xml); 58 | assert!(verb_xml.starts_with( 59 | "" 60 | )); 61 | assert!(verb_xml 62 | .find("System.Management.Automation.VerbInfo") 63 | .is_some()); 64 | assert!(verb_xml 65 | .find("System.Management.Automation.VerbInfo") 66 | .is_some()); 67 | } 68 | 69 | /* 70 | $MyObj = [PSCustomObject]@{ 71 | MyString = "Purée" 72 | MyChar = [char] 'à' 73 | MyBool = [bool] $true 74 | MyDateTime = [System.DateTime]::new(633435181522731993) 75 | MyDuration = [System.TimeSpan]::new(90269026) 76 | MyU8 = [byte] 254 77 | MyI8 = [sbyte] -127 78 | MyU16 = [ushort] 65535 79 | MyI16 = [short] -32767 80 | MyU32 = [uint] 4294967295 81 | MyI32 = [int] -2147483648 82 | MyU64 = [ulong] 18446744073709551615 83 | MyI64 = [long] -9223372036854775808 84 | MyFloat = [float] 12.34 85 | MyDouble = [double] 34.56 86 | MyDecimal = [decimal] 56.78 87 | MyBuffer = [System.Convert]::FromBase64String('AQIDBA==') 88 | MyGuid = [System.Guid]::new('792e5b37-4505-47ef-b7d2-8711bb7affa8') 89 | MyUri = [System.Uri]::new('http://www.microsoft.com/') 90 | MyVersion = [System.Version]::new('6.2.1.3') 91 | MyXmlDocument = [xml] 'laptop' 92 | MyScriptBlock = [scriptblock] {Get-Command -Type Cmdlet} 93 | MyNull = $null 94 | } 95 | [System.Management.Automation.PSSerializer]::Serialize($MyObj) 96 | */ 97 | 98 | #[test] 99 | fn test_cli_xml_primitive() { 100 | let obj_xml = r#" 101 | 102 | 103 | 104 | System.Management.Automation.PSCustomObject 105 | System.Object 106 | 107 | 108 | Purée 109 | 224 110 | true 111 |
2008-04-11T13:42:32.2731993
112 | PT9.0269026S 113 | 254 114 | -127 115 | 65535 116 | -32767 117 | 4294967295 118 | -2147483648 119 | 18446744073709551615 120 | -9223372036854775808 121 | 12.34 122 | 34.56 123 | 56.78 124 | AQIDBA== 125 | 792e5b37-4505-47ef-b7d2-8711bb7affa8 126 | http://www.microsoft.com/ 127 | 6.2.1.3 128 | <item><name>laptop</name></item> 129 | Get-Command -Type Cmdlet 130 | 131 |
132 |
133 |
"#; 134 | 135 | let objs: Vec = parse_cli_xml(obj_xml); 136 | 137 | let obj = objs.get(0).unwrap(); 138 | 139 | let string_prop = obj.values.get(0).unwrap(); 140 | assert!(string_prop.is_string()); 141 | assert_eq!(string_prop.as_str(), Some("Purée")); 142 | 143 | let char_prop = obj.values.get(1).unwrap(); 144 | assert!(char_prop.is_char()); 145 | assert_eq!(char_prop.as_char(), Some('à')); 146 | 147 | let bool_prop = obj.values.get(2).unwrap(); 148 | assert!(bool_prop.is_bool()); 149 | assert_eq!(bool_prop.as_bool(), Some(true)); 150 | 151 | let datetime_prop = obj.values.get(3).unwrap(); 152 | assert!(datetime_prop.is_datetime()); 153 | 154 | let duration_prop = obj.values.get(4).unwrap(); 155 | assert!(duration_prop.is_duration()); 156 | 157 | let uint8_prop = obj.values.get(5).unwrap(); 158 | assert!(uint8_prop.is_uint8()); 159 | assert_eq!(uint8_prop.as_u8(), Some(254)); 160 | 161 | let int8_prop = obj.values.get(6).unwrap(); 162 | assert!(int8_prop.is_int8()); 163 | assert_eq!(int8_prop.as_i8(), Some(-127)); 164 | 165 | let uint16_prop = obj.values.get(7).unwrap(); 166 | assert!(uint16_prop.is_uint16()); 167 | assert_eq!(uint16_prop.as_u16(), Some(65535)); 168 | 169 | let int16_prop = obj.values.get(8).unwrap(); 170 | assert!(int16_prop.is_int16()); 171 | assert_eq!(int16_prop.as_i16(), Some(-32767)); 172 | 173 | let uint32_prop = obj.values.get(9).unwrap(); 174 | assert!(uint32_prop.is_uint32()); 175 | assert_eq!(uint32_prop.as_u32(), Some(4294967295)); 176 | 177 | let int32_prop = obj.values.get(10).unwrap(); 178 | assert!(int32_prop.is_int32()); 179 | assert_eq!(int32_prop.as_i32(), Some(-2147483648)); 180 | 181 | let uint64_prop = obj.values.get(11).unwrap(); 182 | assert!(uint64_prop.is_uint64()); 183 | assert_eq!(uint64_prop.as_u64(), Some(18446744073709551615)); 184 | 185 | let int64_prop = obj.values.get(12).unwrap(); 186 | assert!(int64_prop.is_int64()); 187 | assert_eq!(int64_prop.as_i64(), Some(-9223372036854775808)); 188 | 189 | let float_prop = obj.values.get(13).unwrap(); 190 | assert!(float_prop.is_float()); 191 | assert_eq!(float_prop.as_float(), Some(12.34)); 192 | 193 | let double_prop = obj.values.get(14).unwrap(); 194 | assert!(double_prop.is_double()); 195 | assert_eq!(double_prop.as_double(), Some(34.56)); 196 | 197 | let decimal_prop = obj.values.get(15).unwrap(); 198 | assert!(decimal_prop.is_decimal()); 199 | 200 | let buffer_prop = obj.values.get(16).unwrap(); 201 | assert!(buffer_prop.is_buffer()); 202 | assert_eq!(buffer_prop.as_bytes(), Some(vec![1, 2, 3, 4u8].as_ref())); 203 | 204 | let guid_prop = obj.values.get(17).unwrap(); 205 | assert!(guid_prop.is_guid()); 206 | assert_eq!( 207 | guid_prop.as_guid(), 208 | uuid::Uuid::parse_str("792e5b37-4505-47ef-b7d2-8711bb7affa8") 209 | .ok() 210 | .as_ref() 211 | ); 212 | 213 | let uri_prop = obj.values.get(18).unwrap(); 214 | assert!(uri_prop.is_uri()); 215 | assert_eq!( 216 | uri_prop.as_uri(), 217 | url::Url::parse("http://www.microsoft.com/").ok().as_ref() 218 | ); 219 | 220 | let version_prop = obj.values.get(19).unwrap(); 221 | assert!(version_prop.is_version()); 222 | assert_eq!(version_prop.as_version(), Some("6.2.1.3")); 223 | 224 | let xml_document_prop = obj.values.get(20).unwrap(); 225 | assert!(xml_document_prop.is_xml_document()); 226 | assert_eq!( 227 | xml_document_prop.as_xml_document(), 228 | Some("<item><name>laptop</name></item>") 229 | ); 230 | 231 | let script_block_prop = obj.values.get(21).unwrap(); 232 | assert!(script_block_prop.is_script_block()); 233 | assert_eq!( 234 | script_block_prop.as_script_block(), 235 | Some("Get-Command -Type Cmdlet") 236 | ); 237 | 238 | let null_prop = obj.values.get(22).unwrap(); 239 | assert!(null_prop.is_null()); 240 | } 241 | 242 | #[test] 243 | fn test_cli_xml_complex() { 244 | // Get-VM IT-HELP-DVLS | Select-Object -Property VMId, VMName, State, Uptime, Status, Version 245 | let vm_xml = r#" 246 | 247 | 248 | Selected.Microsoft.HyperV.PowerShell.VirtualMachine 249 | System.Management.Automation.PSCustomObject 250 | System.Object 251 | 252 | 253 | fbac8867-40ca-4032-a8e0-901c7f004cd7 254 | IT-HELP-DVLS 255 | 256 | 257 | Microsoft.HyperV.PowerShell.VMState 258 | System.Enum 259 | System.ValueType 260 | System.Object 261 | 262 | Off 263 | 3 264 | 265 | PT0S 266 | Operating normally 267 | 10.0 268 | 269 | 270 | "#; 271 | 272 | let objs: Vec = parse_cli_xml(vm_xml); 273 | 274 | let vm_obj = objs.get(0).unwrap(); 275 | 276 | let vmid_prop = vm_obj.values.get(0).unwrap(); 277 | assert!(vmid_prop.is_guid()); 278 | assert_eq!(vmid_prop.get_name(), Some("VMId")); 279 | assert_eq!( 280 | vmid_prop.as_guid(), 281 | Uuid::parse_str("fbac8867-40ca-4032-a8e0-901c7f004cd7") 282 | .ok() 283 | .as_ref() 284 | ); 285 | 286 | let vmname_prop = vm_obj.values.get(1).unwrap(); 287 | assert!(vmname_prop.is_string()); 288 | assert_eq!(vmname_prop.get_name(), Some("VMName")); 289 | assert_eq!(vmname_prop.as_str(), Some("IT-HELP-DVLS")); 290 | 291 | let cmd_xml = r#" 292 | 293 | 294 | System.Diagnostics.Process 295 | System.ComponentModel.Component 296 | System.MarshalByRefObject 297 | System.Object 298 | 299 | System.Diagnostics.Process (cmd) 300 | 301 | Microsoft.Win32.SafeHandles.SafeProcessHandle 302 | 3084 303 | 8 304 | false 305 |
2022-11-08T20:17:17.4042801-05:00
306 | 17804 307 | . 308 | 1413120 309 | 204800 310 | 311 | 312 | System.Diagnostics.ProcessModuleCollection 313 | System.Collections.ReadOnlyCollectionBase 314 | System.Object 315 | 316 | 317 | System.Diagnostics.ProcessModule (cmd.exe) 318 | System.Diagnostics.ProcessModule (ntdll.dll) 319 | System.Diagnostics.ProcessModule (KERNEL32.dll) 320 | System.Diagnostics.ProcessModule (hmpalert.dll) 321 | System.Diagnostics.ProcessModule (KERNELBASE.dll) 322 | System.Diagnostics.ProcessModule (msvcrt.dll) 323 | System.Diagnostics.ProcessModule (combase.dll) 324 | System.Diagnostics.ProcessModule (ucrtbase.dll) 325 | System.Diagnostics.ProcessModule (RPCRT4.dll) 326 | System.Diagnostics.ProcessModule (winbrand.dll) 327 | System.Diagnostics.ProcessModule (shcore.dll) 328 | System.Diagnostics.ProcessModule (msvcp_win.dll) 329 | 330 | 331 | 6560 332 | 6560 333 | 5468160 334 | 5468160 335 | 49056 336 | 49056 337 | 5468160 338 | 5468160 339 | 5857280 340 | 5857280 341 | 2203383934976 342 | 65712128 343 | true 344 | Normal 345 | 5468160 346 | 5468160 347 | cmd 348 | 65535 349 | 1 350 | 351 | 352 | System.Diagnostics.ProcessThreadCollection 353 | System.Collections.ReadOnlyCollectionBase 354 | System.Object 355 | 356 | 357 | System.Diagnostics.ProcessThread 358 | 359 | 360 | 76 361 | 2203383930880 362 | 65708032 363 | false 364 | 5857280 365 | 5857280 366 | 367 | System.Diagnostics.ProcessModule (cmd.exe) 368 | PT0S 369 | PT0.015625S 370 | PT0.015625S 371 | 264750 372 | Command Prompt 373 | true 374 | 375 | 376 |
377 | 378 | cmd 379 | 1 380 | 76 381 | 2203383930880 382 | 5857280 383 | 5468160 384 | 6560 385 | C:\WINDOWS\system32\cmd.exe 386 | "C:\WINDOWS\system32\cmd.exe" 387 | System.Diagnostics.Process (explorer) 388 | Microsoft Corporation 389 | 0.015625 390 | 10.0.22000.1 (WinBuild.160101.0800) 391 | 10.0.22000.1 392 | Windows Command Processor 393 | Microsoft® Windows® Operating System 394 | Process 395 | 396 |
397 |
398 | "#; 399 | 400 | let _objs: Vec = parse_cli_xml(cmd_xml); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | use time::format_description::well_known::Iso8601; 2 | use time::macros::{date, time, format_description}; 3 | use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub struct DateTime { 7 | inner: OffsetDateTime, 8 | } 9 | 10 | #[allow(dead_code)] 11 | impl DateTime { 12 | pub fn parse(input: &str) -> Option { 13 | // Try parsing as OffsetDateTime (with timezone) 14 | if let Ok(inner) = OffsetDateTime::parse(input, &Iso8601::DEFAULT) { 15 | return Some(Self { inner }); 16 | } 17 | 18 | // If parsing without timezone, assume UTC (Offset +00:00) 19 | let format = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]"); 20 | if let Ok(primitive) = PrimitiveDateTime::parse(input, &format) { 21 | let inner = primitive.assume_offset(UtcOffset::UTC); 22 | return Some(Self { inner }); 23 | } 24 | 25 | None 26 | } 27 | 28 | pub fn format(&self) -> String { 29 | let format = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:7][offset_hour sign:mandatory]:[offset_minute]"); 30 | self.inner.format(&format).unwrap() 31 | } 32 | } 33 | 34 | impl Default for DateTime { 35 | fn default() -> Self { 36 | let inner = OffsetDateTime::new_utc(date!(2000 - 01 - 01), time!(0:00)); 37 | DateTime { inner: inner } 38 | } 39 | } 40 | 41 | pub fn parse_iso8601_duration(input: &str) -> Option { 42 | iso8601_duration::Duration::parse(input) 43 | .ok() 44 | .map(|x| x.to_std()) 45 | } 46 | 47 | #[cfg(test)] 48 | mod pwsh { 49 | use crate::time::parse_iso8601_duration; 50 | use crate::time::DateTime; 51 | 52 | #[test] 53 | fn parse_duration() { 54 | // 0 seconds 55 | assert_eq!( 56 | parse_iso8601_duration("PT0S"), 57 | Some(std::time::Duration::new(0, 0)) 58 | ); 59 | 60 | // 9 seconds, 26.9026 milliseconds 61 | assert_eq!( 62 | parse_iso8601_duration("PT9.0269026S"), 63 | Some(std::time::Duration::from_secs_f32(9.0269026)) 64 | ); 65 | } 66 | 67 | #[test] 68 | fn parse_datetime() { 69 | assert_eq!( 70 | DateTime::parse("2024-09-17T10:55:56.7639518-04:00").unwrap().format(), 71 | "2024-09-17T10:55:56.7639518-04:00".to_string() 72 | ) 73 | } 74 | } 75 | --------------------------------------------------------------------------------