@@ -2,12 +2,17 @@ use crate::{
22 error:: WasmError ,
33 hostcall:: handle_host_calls,
44 http_client:: GatewayHttpViaWSClient ,
5- types:: { SecurityWarningCallback , WasmCommandCompletion , WasmWinRmConfig } ,
6- JsSessionEvent , WasmPowerShellStream ,
5+ types:: {
6+ JsRunCommandEvent , SecurityWarningCallback , WasmCommandCompletion , WasmHostInformationMessage ,
7+ WasmInformationMessageData , WasmPsrpRecord , WasmPsrpRecordMeta , WasmWinRmConfig ,
8+ } ,
9+ JsPsValue , JsSessionEvent , WasmErrorRecord , WasmPowerShellStream ,
710} ;
811use futures:: StreamExt ;
912use ironposh_async:: RemoteAsyncPowershellClient ;
10- use ironposh_client_core:: { connector:: WinRmConfig , powershell:: PipelineHandle } ;
13+ use ironposh_client_core:: {
14+ connector:: WinRmConfig , powershell:: PipelineHandle , psrp_record:: PsrpRecord ,
15+ } ;
1116use js_sys:: { Array , Function , Promise } ;
1217use std:: convert:: TryFrom ;
1318use tracing:: { error, info, warn} ;
@@ -28,6 +33,9 @@ extern "C" {
2833
2934 #[ wasm_bindgen( typescript_type = "(session_event: JsSessionEvent) => void" ) ]
3035 pub type SessionEventHandler ;
36+
37+ #[ wasm_bindgen( typescript_type = "(event: JsRunCommandEvent) => void" ) ]
38+ pub type RunCommandCallback ;
3139}
3240
3341#[ wasm_bindgen]
@@ -243,6 +251,41 @@ impl WasmPowerShellClient {
243251 Ok ( stream)
244252 }
245253
254+ /// Execute a PowerShell script and emit structured pipeline events to the callback.
255+ /// This uses raw PSRP output (no Out-String) so callers can inspect JsPsValue.
256+ #[ wasm_bindgen( js_name = "runCommand" ) ]
257+ pub async fn run_command (
258+ & mut self ,
259+ script : String ,
260+ on_event : RunCommandCallback ,
261+ ) -> Result < ( ) , WasmError > {
262+ if !on_event. is_function ( ) {
263+ return Err ( WasmError :: InvalidArgument (
264+ "on_event must be a function" . into ( ) ,
265+ ) ) ;
266+ }
267+
268+ let script_len = script. len ( ) ;
269+ tracing:: info!( script_len = %script_len, "run_command requested" ) ;
270+
271+ let mut stream = self . client . send_script_raw ( script) . await . map_err ( |e| {
272+ tracing:: info!( error = ?e, "run_command failed to send script" ) ;
273+ e
274+ } ) ?;
275+
276+ let callback = on_event. unchecked_into :: < Function > ( ) ;
277+
278+ while let Some ( event) = stream. next ( ) . await {
279+
280+ let js_event = user_event_to_run_command_event ( & event) ?;
281+ if let Err ( e) = callback. call1 ( & JsValue :: NULL , & js_event. into ( ) ) {
282+ tracing:: info!( error = ?e, "run_command callback failed" ) ;
283+ }
284+ }
285+
286+ Ok ( ( ) )
287+ }
288+
246289 #[ wasm_bindgen]
247290 pub async fn tab_complete (
248291 & mut self ,
@@ -320,3 +363,123 @@ impl WasmPowerShellClient {
320363 } )
321364 }
322365}
366+
367+ fn user_event_to_run_command_event (
368+ event : & ironposh_client_core:: connector:: active_session:: UserEvent ,
369+ ) -> Result < JsRunCommandEvent , WasmError > {
370+ let res = match event {
371+ ironposh_client_core:: connector:: active_session:: UserEvent :: PipelineCreated { pipeline } => {
372+ JsRunCommandEvent :: PipelineCreated {
373+ pipeline_id : pipeline. id ( ) . to_string ( ) ,
374+ }
375+ }
376+ ironposh_client_core:: connector:: active_session:: UserEvent :: PipelineFinished { pipeline } => {
377+ JsRunCommandEvent :: PipelineFinished {
378+ pipeline_id : pipeline. id ( ) . to_string ( ) ,
379+ }
380+ }
381+ ironposh_client_core:: connector:: active_session:: UserEvent :: PipelineOutput {
382+ pipeline,
383+ output,
384+ } => JsRunCommandEvent :: PipelineOutput {
385+ pipeline_id : pipeline. id ( ) . to_string ( ) ,
386+ value : JsPsValue :: from ( output. data . clone ( ) ) ,
387+ } ,
388+ ironposh_client_core:: connector:: active_session:: UserEvent :: ErrorRecord {
389+ error_record,
390+ handle,
391+ } => JsRunCommandEvent :: PipelineError {
392+ pipeline_id : handle. id ( ) . to_string ( ) ,
393+ error : WasmErrorRecord :: from ( error_record) ,
394+ } ,
395+ ironposh_client_core:: connector:: active_session:: UserEvent :: PipelineRecord {
396+ pipeline,
397+ record,
398+ } => JsRunCommandEvent :: PipelineRecord {
399+ pipeline_id : pipeline. id ( ) . to_string ( ) ,
400+ record : psrp_record_to_wasm ( record) ,
401+ } ,
402+ } ;
403+
404+ Ok ( res)
405+ }
406+
407+ fn psrp_record_to_wasm ( record : & PsrpRecord ) -> WasmPsrpRecord {
408+ let meta = match record {
409+ PsrpRecord :: Debug { meta, .. }
410+ | PsrpRecord :: Verbose { meta, .. }
411+ | PsrpRecord :: Warning { meta, .. }
412+ | PsrpRecord :: Information { meta, .. }
413+ | PsrpRecord :: Progress { meta, .. }
414+ | PsrpRecord :: Unsupported { meta, .. } => meta,
415+ } ;
416+
417+ let meta = WasmPsrpRecordMeta {
418+ message_type : format ! ( "{:?}" , meta. message_type) ,
419+ message_type_value : meta. message_type_value ,
420+ stream : meta. stream . clone ( ) ,
421+ command_id : meta. command_id . map ( |id| id. to_string ( ) ) ,
422+ data_len : meta. data_len ,
423+ } ;
424+
425+ match record {
426+ PsrpRecord :: Debug { message, .. } => WasmPsrpRecord :: Debug {
427+ meta,
428+ message : message. clone ( ) ,
429+ } ,
430+ PsrpRecord :: Verbose { message, .. } => WasmPsrpRecord :: Verbose {
431+ meta,
432+ message : message. clone ( ) ,
433+ } ,
434+ PsrpRecord :: Warning { message, .. } => WasmPsrpRecord :: Warning {
435+ meta,
436+ message : message. clone ( ) ,
437+ } ,
438+ PsrpRecord :: Information { record, .. } => {
439+ let message_data = match & record. message_data {
440+ ironposh_psrp:: InformationMessageData :: String ( s) => {
441+ WasmInformationMessageData :: String { value : s. clone ( ) }
442+ }
443+ ironposh_psrp:: InformationMessageData :: HostInformationMessage ( m) => {
444+ WasmInformationMessageData :: HostInformationMessage {
445+ value : WasmHostInformationMessage {
446+ message : m. message . clone ( ) ,
447+ foreground_color : m. foreground_color ,
448+ background_color : m. background_color ,
449+ no_new_line : m. no_new_line ,
450+ } ,
451+ }
452+ }
453+ ironposh_psrp:: InformationMessageData :: Object ( v) => {
454+ WasmInformationMessageData :: Object {
455+ value : JsPsValue :: from ( v. clone ( ) ) ,
456+ }
457+ }
458+ } ;
459+ WasmPsrpRecord :: Information {
460+ meta,
461+ message_data,
462+ source : record. source . clone ( ) ,
463+ time_generated : record. time_generated . clone ( ) ,
464+ tags : record. tags . clone ( ) ,
465+ user : record. user . clone ( ) ,
466+ computer : record. computer . clone ( ) ,
467+ process_id : record. process_id ,
468+ }
469+ }
470+ PsrpRecord :: Progress { record, .. } => WasmPsrpRecord :: Progress {
471+ meta,
472+ activity : record. activity . clone ( ) ,
473+ activity_id : record. activity_id ,
474+ status_description : record. status_description . clone ( ) ,
475+ current_operation : record. current_operation . clone ( ) ,
476+ parent_activity_id : record. parent_activity_id ,
477+ percent_complete : record. percent_complete ,
478+ seconds_remaining : record. seconds_remaining ,
479+ } ,
480+ PsrpRecord :: Unsupported { data_preview, .. } => WasmPsrpRecord :: Unsupported {
481+ meta,
482+ data_preview : data_preview. clone ( ) ,
483+ } ,
484+ }
485+ }
0 commit comments