Skip to main content

windows_erg/evt/
mod.rs

1//! Windows Event Log (evt) - Query and read events from Windows Event Logs.
2//!
3//! This module provides ergonomic access to Windows Event Logs with support for:
4//! - Reading from event log channels (Security, System, Application, etc.)
5//! - Reading from .evtx log files
6//! - Flexible XPath-based or builder-based queries
7//! - Optional event message rendering with publisher metadata caching
8//! - Optional EventData extraction with field name interning
9//! - Custom parsing APIs for performance-critical scenarios
10//! - Detailed error reporting including corrupted event information
11//!
12//! # Choosing the Right API
13//!
14//! This module provides multiple ways to process events, each optimized for different use cases:
15//!
16//! ## Quick Start: Standard Event Processing
17//!
18//! Use the built-in [`Event`](types::Event) struct with builder options:
19//!
20//! ```no_run
21//! use windows_erg::evt::EventLog;
22//!
23//! # fn main() -> windows_erg::Result<()> {
24//! let log = EventLog::open("Security")?;
25//! let mut query = log.query_stream("*[System[EventID=4624]]")?
26//!     .with_event_data()   // Extract EventData key-value pairs (opt-in)
27//!     .with_message();     // Format event messages via EvtFormatMessage (opt-in)
28//!
29//! let mut batch = Vec::new();
30//! while query.next_batch(&mut batch)? > 0 {
31//!     for event in &batch {
32//!         println!("Event {}: {}", event.id, event.formatted_message.as_deref().unwrap_or(""));
33//!         if let Some(ref data) = event.data {
34//!             for (key, value) in data {
35//!                 println!("  {}: {}", key, value);  // Common fields use Cow::Borrowed (zero-copy)
36//!             }
37//!         }
38//!     }
39//! }
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! **When to use**: Most applications. Provides structured Event objects with opt-in features.
45//!
46//! ## Explicit Corruption Handling
47//!
48//! Use [`next_batch_with_results`](EventQuery::next_batch_with_results) when you need to handle corrupted events explicitly:
49//!
50//! ```no_run
51//! use windows_erg::evt::EventLog;
52//!
53//! # fn main() -> windows_erg::Result<()> {
54//! let log = EventLog::open("System")?;
55//! let mut query = log.query_stream("*")?;
56//! let mut batch = Vec::new();
57//!
58//! while query.next_batch_with_results(&mut batch)? > 0 {
59//!     for result in &batch {
60//!         match result {
61//!             Ok(event) => println!("Event: {}", event.id),
62//!             Err(corrupted) => {
63//!                 eprintln!("Corrupted event at record {}: {}",
64//!                     corrupted.record_id.unwrap_or(0),
65//!                     corrupted.reason
66//!                 );
67//!             }
68//!         }
69//!     }
70//! }
71//! # Ok(())
72//! # }
73//! ```
74//!
75//! **When to use**: When parsing untrusted or potentially corrupted logs. Returns both Ok(Event) and Err(CorruptedEvent) in the same vector.
76//!
77//! ## Custom Types: Maximum Performance
78//!
79//! Use [`next_batch_raw_with_filter`](EventQuery::next_batch_raw_with_filter) when you need custom event types without allocating intermediate Event structs:
80//!
81//! ```no_run
82//! use windows_erg::evt::{EventLog, types::{extract_event_id, extract_provider}};
83//!
84//! # fn main() -> windows_erg::Result<()> {
85//! #[derive(Debug)]
86//! struct LightweightEvent {
87//!     id: u32,
88//!     provider: String,
89//! }
90//!
91//! let log = EventLog::open("Application")?;
92//! let mut query = log.query_stream("*")?;
93//! let mut events = Vec::new();
94//!
95//! query.next_batch_raw_with_filter(
96//!     &mut events,
97//!     |handle| {
98//!         Ok(LightweightEvent {
99//!             id: extract_event_id(handle)?,
100//!             provider: extract_provider(handle)?,
101//!         })
102//!     },
103//!     |event| event.id < 1000,  // Filter AFTER conversion (on your custom type)
104//! )?;
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! **When to use**: High-throughput scenarios where you only need a subset of event fields. Avoids allocating full Event structs.
110//!
111//! ## Custom Types: Serde Deserialization
112//!
113//! Use [`next_batch_deserialize`](EventQuery::next_batch_deserialize) (requires `serde` feature) for owned XML deserialization:
114//!
115//! ```no_run
116//! # fn main() -> windows_erg::Result<()> {
117//! # #[cfg(feature = "serde")]
118//! # {
119//! use windows_erg::evt::EventLog;
120//! use serde::Deserialize;
121//!
122//! #[derive(Deserialize)]
123//! struct CustomEvent {
124//!     #[serde(rename = "System")]
125//!     system: SystemData,
126//! }
127//!
128//! #[derive(Deserialize)]
129//! struct SystemData {
130//!     #[serde(rename = "EventID")]
131//!     event_id: u32,
132//!     #[serde(rename = "Provider")]
133//!     provider: ProviderData,
134//! }
135//!
136//! #[derive(Deserialize)]
137//! struct ProviderData {
138//!     #[serde(rename = "@Name")]
139//!     name: String,
140//! }
141//!
142//! let log = EventLog::open("Security")?;
143//! let mut query = log.query_stream("*")?;
144//! let mut events: Vec<CustomEvent> = Vec::new();
145//!
146//! query.next_batch_deserialize(&mut events)?;
147//! # }
148//! # Ok(())
149//! # }
150//! ```
151//!
152//! **When to use**: When you need flexible custom event types with straightforward, owned Rust data structures.
153//!
154//! # Performance Considerations
155//!
156//! ## Field Name Interning
157//!
158//! Common EventData field names (e.g., "TargetUserName", "ProcessId", "CommandLine") are automatically interned as `Cow::Borrowed(&'static str)` via a compile-time match statement. This reduces allocations by ~30% for typical Windows security logs.
159//!
160//! ## Publisher Metadata Caching
161//!
162//! Event message formatting via `with_message()` caches publisher metadata handles with RwLock for concurrent read access. First message format per provider is ~5ms, subsequent formats are <0.1ms.
163//!
164//! ## Buffer Reuse
165//!
166//! All `next_batch_*` methods clear and reuse the provided output vector. Preallocate with `Vec::with_capacity(batch_size)` to avoid repeated allocations.
167//!
168//! # Examples
169//!
170//! ## Basic query
171//! ```no_run
172//! use windows_erg::evt::{EventLog, query::QueryBuilder};
173//!
174//! # fn main() -> windows_erg::Result<()> {
175//! let log = EventLog::open("Security")?;
176//! let query = QueryBuilder::new().event_id(4688);
177//! let result = log.query(&query)?;
178//!
179//! for event in result.events {
180//!     println!("Event ID: {}, Level: {}", event.id, event.level);
181//! }
182//! # Ok(())
183//! # }
184//! ```
185//!
186//! ## Batch processing with buffer reuse
187//! ```no_run
188//! use windows_erg::evt::{EventLog, EventQuery};
189//!
190//! # fn main() -> windows_erg::Result<()> {
191//! let log = EventLog::open("System")?;
192//! let mut query = log.query_stream("*")?;  // All events
193//! let mut batch = Vec::with_capacity(64);
194//!
195//! while query.next_batch(&mut batch)? > 0 {
196//!     for event in &batch {
197//!         println!("Event: {}", event.provider);
198//!     }
199//! }
200//! # Ok(())
201//! # }
202//! ```
203//!
204//! ## Query with filtering
205//! ```no_run
206//! use windows_erg::evt::{EventLog, types::EventLevel, query::QueryBuilder};
207//!
208//! # fn main() -> windows_erg::Result<()> {
209//! let log = EventLog::open("Application")?;
210//! let query = QueryBuilder::new()
211//!     .level(EventLevel::Error)
212//!     .provider("MyApp");
213//! let result = log.query(&query)?;
214//! # Ok(())
215//! # }
216//! ```
217
218pub mod query;
219pub mod render;
220pub mod types;
221
222use crate::error::{Error, EventLogError, EventLogQueryError, Result};
223use crate::utils::to_utf16_nul;
224use crate::wait::Wait;
225use query::QueryBuilder;
226use std::path::Path;
227use types::{ChannelFilter, Event, EventQueryResult, RenderFormat};
228use windows::Win32::Foundation::GetLastError;
229use windows::Win32::System::EventLog::*;
230use windows::core::PWSTR;
231
232/// Handle to an opened event log or log file.
233pub struct EventLog {
234    handle: EVT_HANDLE,
235    channel_or_path: String,
236    is_file: bool,
237}
238
239impl EventLog {
240    /// Open an event log channel by name.
241    ///
242    /// Examples: "Security", "System", "Application", "Windows PowerShell", etc.
243    pub fn open(channel_name: &str) -> Result<Self> {
244        let channel_wide: Vec<u16> = channel_name
245            .encode_utf16()
246            .chain(std::iter::once(0))
247            .collect();
248
249        let handle = unsafe {
250            EvtOpenLog(
251                EVT_HANDLE::default(), // Local computer
252                PWSTR(channel_wide.as_ptr() as *mut u16),
253                EvtOpenChannelPath.0,
254            )
255        }
256        .map_err(|_| {
257            Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
258                channel_name.to_string(),
259                "Channel not found or access denied",
260            )))
261        })?;
262
263        Ok(EventLog {
264            handle,
265            channel_or_path: channel_name.to_string(),
266            is_file: false,
267        })
268    }
269
270    /// Open an event log file (.evtx, .evt, or .etl format).
271    pub fn open_file(path: &Path) -> Result<Self> {
272        let path_str = path.to_str().ok_or_else(|| {
273            Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
274                "file_path",
275                "Invalid file path",
276            )))
277        })?;
278
279        let path_wide = to_utf16_nul(path_str);
280
281        let handle = unsafe {
282            EvtOpenLog(
283                EVT_HANDLE::default(), // Local computer
284                PWSTR(path_wide.as_ptr() as *mut u16),
285                EvtOpenFilePath.0,
286            )
287        }
288        .map_err(|_| {
289            Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
290                path_str.to_string(),
291                "Log file not found or cannot be read",
292            )))
293        })?;
294
295        Ok(EventLog {
296            handle,
297            channel_or_path: path_str.to_string(),
298            is_file: true,
299        })
300    }
301
302    /// List available event log channels.
303    pub fn list_channels() -> Result<Vec<String>> {
304        Self::list_channels_filtered(ChannelFilter::Operational)
305    }
306
307    /// List event log channels with a specific filter.
308    pub fn list_channels_filtered(filter: ChannelFilter) -> Result<Vec<String>> {
309        let mut channels = Vec::new();
310
311        let enum_handle =
312            unsafe { EvtOpenChannelEnum(EVT_HANDLE::default(), 0) }.map_err(|_| {
313                Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
314                    "channels",
315                    "Failed to enumerate channels",
316                )))
317            })?;
318
319        let mut buffer = [0u16; 1024];
320
321        loop {
322            let mut buffer_used = 0u32;
323            let result =
324                unsafe { EvtNextChannelPath(enum_handle, Some(&mut buffer[..]), &mut buffer_used) };
325
326            if result.is_err() {
327                break;
328            }
329
330            let channel_name = String::from_utf16_lossy(&buffer[..buffer_used as usize]);
331
332            // Apply filter
333            if should_include_channel(&channel_name, filter) {
334                channels.push(channel_name.to_string());
335            }
336        }
337
338        unsafe {
339            let _ = EvtClose(enum_handle);
340        }
341
342        Ok(channels)
343    }
344
345    /// Query events using a query builder.
346    ///
347    /// This returns all matching events at once. For large result sets,
348    /// prefer `query_stream()` with batch processing.
349    pub fn query(&self, builder: &QueryBuilder) -> Result<EventQueryResult> {
350        let mut result = EventQueryResult::default();
351        self.query_internal(builder, &mut result, None)?;
352        Ok(result)
353    }
354
355    /// Query events in a streaming fashion with batch processing.
356    ///
357    /// Returns an EventQuery handle for batch iteration with buffer reuse.
358    /// Use `query_stream()` for processing large logs efficiently.
359    pub fn query_stream(&self, xpath: &str) -> Result<EventQuery> {
360        let xpath_wide = to_utf16_nul(xpath);
361
362        let channel_wide: Vec<u16> = self
363            .channel_or_path
364            .encode_utf16()
365            .chain(std::iter::once(0))
366            .collect();
367
368        let flags = if self.is_file {
369            EvtQueryFilePath.0
370        } else {
371            EvtQueryChannelPath.0
372        };
373
374        let query_handle = unsafe {
375            EvtQuery(
376                EVT_HANDLE::default(), // Local computer
377                PWSTR(channel_wide.as_ptr() as *mut u16),
378                PWSTR(xpath_wide.as_ptr() as *mut u16),
379                flags,
380            )
381        }
382        .map_err(|_| {
383            Error::EventLog(EventLogError::QueryFailed(EventLogQueryError::new(
384                self.channel_or_path.clone(),
385                "Failed to create query handle",
386            )))
387        })?;
388
389        Ok(EventQuery {
390            handle: query_handle,
391            batch_buffer: vec![0isize; 64], // Changed to 0isize for EvtNext buffer
392            batch_timeout_ms: 1000,
393            render_format: RenderFormat::Values,
394            include_event_data: false,
395            parse_message: false,
396            #[cfg(feature = "serde")]
397            xml_buffer: String::with_capacity(16384),
398            variant_buffer: Vec::with_capacity(8192),
399            corrupted: Vec::new(),
400            total_processed: 0,
401        })
402    }
403
404    /// Internal query implementation.
405    fn query_internal(
406        &self,
407        builder: &QueryBuilder,
408        result: &mut EventQueryResult,
409        _max_events: Option<usize>,
410    ) -> Result<()> {
411        let mut query = self.query_stream(&builder.build_xpath())?;
412
413        // Fetch all batches
414        let mut batch = Vec::with_capacity(64);
415        while query.next_batch(&mut batch)? > 0 {
416            result.events.append(&mut batch);
417        }
418
419        result.corrupted = query.corrupted.clone();
420        result.total_processed = query.total_processed;
421
422        Ok(())
423    }
424}
425
426impl Drop for EventLog {
427    fn drop(&mut self) {
428        if !self.handle.is_invalid() {
429            unsafe {
430                let _ = EvtClose(self.handle);
431            }
432        }
433    }
434}
435
436/// Query result stream for batch iteration with reusable buffers.
437pub struct EventQuery {
438    handle: EVT_HANDLE,
439    batch_buffer: Vec<isize>, // Changed from Vec<EVT_HANDLE> to Vec<isize> for EvtNext
440    batch_timeout_ms: u32,
441    render_format: RenderFormat,
442    include_event_data: bool,
443    parse_message: bool,
444    #[cfg(feature = "serde")]
445    xml_buffer: String,
446    #[allow(dead_code)]
447    variant_buffer: Vec<u8>, // Reserved for future use
448    corrupted: Vec<crate::evt::types::CorruptedEvent>,
449    total_processed: usize,
450}
451
452impl EventQuery {
453    /// Set timeout used by `EvtNext` for each batch retrieval call.
454    pub fn set_batch_timeout(&mut self, timeout: std::time::Duration) {
455        self.batch_timeout_ms = timeout.as_millis().min(u32::MAX as u128) as u32;
456    }
457
458    /// Enable EventData extraction (opt-in).
459    ///
460    /// When enabled, events will have their `data` field populated with EventData key-value pairs.
461    /// Common field names (e.g., "TargetUserName", "ProcessId") are automatically interned
462    /// as `Cow::Borrowed(&'static str)` for zero-copy access.
463    pub fn with_event_data(mut self) -> Self {
464        self.include_event_data = true;
465        self
466    }
467
468    /// Enable message formatting via EvtFormatMessage (opt-in).
469    ///
470    /// When enabled, events will have their `formatted_message` field populated with the
471    /// human-readable event message. Publisher metadata is cached for performance.
472    pub fn with_message(mut self) -> Self {
473        self.parse_message = true;
474        self
475    }
476
477    /// Set the rendering format for events (default: Values).
478    pub fn set_render_format(&mut self, format: RenderFormat) {
479        self.render_format = format;
480    }
481
482    /// Fetch next batch of events into output buffer.
483    ///
484    /// Returns the count of events added to the buffer.
485    /// When no more events are available, returns 0.
486    pub fn next_batch(&mut self, out_events: &mut Vec<Event>) -> Result<usize> {
487        self.next_batch_with_filter(out_events, |_| true)
488    }
489
490    /// Fetch the next batch unless a cancel wait object is already signaled.
491    ///
492    /// Returns `0` when cancellation is requested.
493    pub fn next_batch_or_cancel(
494        &mut self,
495        out_events: &mut Vec<Event>,
496        cancel: &Wait,
497    ) -> Result<usize> {
498        if cancel.is_signaled()? {
499            out_events.clear();
500            return Ok(0);
501        }
502
503        self.next_batch(out_events)
504    }
505
506    /// Fetch next batch with filtering applied during enumeration.
507    ///
508    /// The filter function is called for each parsed event;
509    /// only events where the filter returns true are included.
510    pub fn next_batch_with_filter<F>(
511        &mut self,
512        out_events: &mut Vec<Event>,
513        filter: F,
514    ) -> Result<usize>
515    where
516        F: Fn(&Event) -> bool,
517    {
518        out_events.clear();
519
520        let mut returned = 0u32;
521        let result = unsafe {
522            EvtNext(
523                self.handle,
524                self.batch_buffer.as_mut_slice(),
525                self.batch_timeout_ms,
526                0,
527                &mut returned,
528            )
529        };
530
531        if result.is_err() {
532            // Check for normal end of stream
533            let error_code = unsafe { GetLastError() };
534            if error_code.0 == 259 {
535                // ERROR_NO_MORE_ITEMS
536                return Ok(0);
537            }
538            return Err(Error::EventLog(EventLogError::QueryFailed(
539                EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
540            )));
541        }
542
543        // Process fetched events
544        for i in 0..returned as usize {
545            let handle_val = self.batch_buffer[i];
546            let handle = EVT_HANDLE(handle_val);
547
548            match render::render_event(
549                handle,
550                self.render_format,
551                self.include_event_data,
552                self.parse_message,
553            ) {
554                Ok(event) => {
555                    if filter(&event) {
556                        out_events.push(event);
557                    }
558                    self.total_processed += 1;
559                }
560                Err(corruption_info) => {
561                    self.corrupted.push(corruption_info);
562                    self.total_processed += 1;
563                }
564            }
565
566            // Close the handle
567            unsafe {
568                let _ = EvtClose(handle);
569            }
570        }
571
572        Ok(out_events.len())
573    }
574
575    /// Process events with a custom converter and filter for each raw event handle.
576    ///
577    /// This allows custom event parsing without allocating the intermediate Event struct.
578    /// The converter receives the raw EVT_HANDLE and returns a custom type T.
579    /// The filter is applied after successful conversion.
580    ///
581    /// # Example
582    /// ```no_run
583    /// use windows_erg::evt::{EventLog, types::{extract_event_id, extract_provider}};
584    ///
585    /// # fn main() -> windows_erg::Result<()> {
586    /// #[derive(Debug)]
587    /// struct LightweightEvent {
588    ///     id: u32,
589    ///     provider: String,
590    /// }
591    ///
592    /// let log = EventLog::open("Security")?;
593    /// let mut query = log.query_stream("*[System[EventID=4624]]")?;
594    /// let mut events = Vec::new();
595    ///
596    /// query.next_batch_raw_with_filter(
597    ///     &mut events,
598    ///     |handle| {
599    ///         Ok(LightweightEvent {
600    ///             id: extract_event_id(handle)?,
601    ///             provider: extract_provider(handle)?,
602    ///         })
603    ///     },
604    ///     |event| event.id == 4624,
605    /// )?;
606    /// # Ok(())
607    /// # }
608    /// ```
609    pub fn next_batch_raw_with_filter<T, F, P>(
610        &mut self,
611        out_events: &mut Vec<T>,
612        mut converter: F,
613        filter: P,
614    ) -> Result<usize>
615    where
616        F: FnMut(EVT_HANDLE) -> Result<T>,
617        P: Fn(&T) -> bool,
618    {
619        out_events.clear();
620
621        let mut returned = 0u32;
622        let result = unsafe {
623            EvtNext(
624                self.handle,
625                self.batch_buffer.as_mut_slice(),
626                self.batch_timeout_ms,
627                0,
628                &mut returned,
629            )
630        };
631
632        if result.is_err() {
633            let error_code = unsafe { GetLastError() };
634            if error_code.0 == 259 {
635                return Ok(0);
636            }
637            return Err(Error::EventLog(EventLogError::QueryFailed(
638                EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
639            )));
640        }
641
642        for i in 0..returned as usize {
643            let handle_val = self.batch_buffer[i];
644            let handle = EVT_HANDLE(handle_val);
645
646            match converter(handle) {
647                Ok(event) => {
648                    if filter(&event) {
649                        out_events.push(event);
650                    }
651                    self.total_processed += 1;
652                }
653                Err(_) => {
654                    // User's converter handles errors - skip this event
655                    self.total_processed += 1;
656                }
657            }
658
659            unsafe {
660                let _ = EvtClose(handle);
661            }
662        }
663
664        Ok(out_events.len())
665    }
666
667    /// Fetch next batch with explicit corruption handling.
668    ///
669    /// Returns both successfully parsed events (Ok) and corrupted events (Err)
670    /// in a single vector, preserving event order.
671    ///
672    /// # Example
673    /// ```no_run
674    /// use windows_erg::evt::EventLog;
675    ///
676    /// # fn main() -> windows_erg::Result<()> {
677    /// let log = EventLog::open("System")?;
678    /// let mut query = log.query_stream("*")?;
679    /// let mut batch = Vec::new();
680    ///
681    /// while query.next_batch_with_results(&mut batch)? > 0 {
682    ///     for result in &batch {
683    ///         match result {
684    ///             Ok(event) => println!("Event: {}", event.id),
685    ///             Err(corrupted) => println!("Corrupted: {}", corrupted.reason),
686    ///         }
687    ///     }
688    /// }
689    /// # Ok(())
690    /// # }
691    /// ```
692    pub fn next_batch_with_results(
693        &mut self,
694        out_events: &mut Vec<std::result::Result<Event, types::CorruptedEvent>>,
695    ) -> Result<usize> {
696        out_events.clear();
697
698        let mut returned = 0u32;
699        let result = unsafe {
700            EvtNext(
701                self.handle,
702                self.batch_buffer.as_mut_slice(),
703                self.batch_timeout_ms,
704                0,
705                &mut returned,
706            )
707        };
708
709        if result.is_err() {
710            let error_code = unsafe { GetLastError() };
711            if error_code.0 == 259 {
712                return Ok(0);
713            }
714            return Err(Error::EventLog(EventLogError::QueryFailed(
715                EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
716            )));
717        }
718
719        for i in 0..returned as usize {
720            let handle_val = self.batch_buffer[i];
721            let handle = EVT_HANDLE(handle_val);
722
723            match render::render_event(
724                handle,
725                self.render_format,
726                self.include_event_data,
727                self.parse_message,
728            ) {
729                Ok(event) => {
730                    out_events.push(Ok(event));
731                    self.total_processed += 1;
732                }
733                Err(corruption_info) => {
734                    out_events.push(Err(corruption_info));
735                    self.total_processed += 1;
736                }
737            }
738
739            unsafe {
740                let _ = EvtClose(handle);
741            }
742        }
743
744        Ok(out_events.len())
745    }
746
747    /// Deserialize events to custom types using serde with owned parsing.
748    ///
749    /// Events are rendered as XML and deserialized into owned Rust values.
750    /// This keeps the API simple and avoids lifetime coupling to internal
751    /// buffers.
752    ///
753    /// Requires the `serde` feature.
754    ///
755    /// # Example
756    /// ```no_run
757    /// use windows_erg::evt::EventLog;
758    /// use serde::Deserialize;
759    ///
760    /// # fn main() -> windows_erg::Result<()> {
761    /// #[derive(Deserialize)]
762    /// struct CustomEvent {
763    ///     #[serde(rename = "System")]
764    ///     system: SystemData,
765    /// }
766    ///
767    /// #[derive(Deserialize)]
768    /// struct SystemData {
769    ///     #[serde(rename = "EventID")]
770    ///     event_id: u32,
771    ///     #[serde(rename = "Provider")]
772    ///     provider: ProviderData,
773    /// }
774    ///
775    /// #[derive(Deserialize)]
776    /// struct ProviderData {
777    ///     #[serde(rename = "@Name")]
778    ///     name: String,
779    /// }
780    ///
781    /// let log = EventLog::open("Application")?;
782    /// let mut query = log.query_stream("*")?;
783    /// let mut events: Vec<CustomEvent> = Vec::new();
784    ///
785    /// query.next_batch_deserialize(&mut events)?;
786    /// # Ok(())
787    /// # }
788    /// ```
789    #[cfg(feature = "serde")]
790    pub fn next_batch_deserialize<T>(&mut self, out_events: &mut Vec<T>) -> Result<usize>
791    where
792        T: serde::de::DeserializeOwned,
793    {
794        out_events.clear();
795
796        let mut returned = 0u32;
797        let result = unsafe {
798            EvtNext(
799                self.handle,
800                self.batch_buffer.as_mut_slice(),
801                self.batch_timeout_ms,
802                0,
803                &mut returned,
804            )
805        };
806
807        if result.is_err() {
808            let error_code = unsafe { GetLastError() };
809            if error_code.0 == 259 {
810                return Ok(0);
811            }
812            return Err(Error::EventLog(EventLogError::QueryFailed(
813                EventLogQueryError::with_code("", "EvtNext failed", error_code.0 as i32),
814            )));
815        }
816
817        for i in 0..returned as usize {
818            let handle_val = self.batch_buffer[i];
819            let handle = EVT_HANDLE(handle_val);
820
821            // Render to XML
822            self.xml_buffer.clear();
823            let mut buffer = vec![0u8; 16384];
824            let mut buffer_used = 0u32;
825            let mut prop_count = 0u32;
826
827            let render_result = unsafe {
828                EvtRender(
829                    EVT_HANDLE::default(),
830                    handle,
831                    EvtRenderEventXml.0,
832                    buffer.len() as u32,
833                    Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
834                    &mut buffer_used,
835                    &mut prop_count,
836                )
837            };
838
839            if render_result.is_ok() {
840                let xml_bytes = &buffer[..buffer_used as usize];
841                let xml_str = String::from_utf16_lossy(unsafe {
842                    std::slice::from_raw_parts(
843                        xml_bytes.as_ptr() as *const u16,
844                        xml_bytes.len() / 2,
845                    )
846                });
847                self.xml_buffer.clear();
848                self.xml_buffer.push_str(&xml_str);
849
850                // Deserialize from XML buffer
851                match quick_xml::de::from_str::<T>(&self.xml_buffer) {
852                    Ok(event) => {
853                        out_events.push(event);
854                        self.total_processed += 1;
855                    }
856                    Err(_) => {
857                        // Deserialization failed - skip this event
858                        self.total_processed += 1;
859                    }
860                }
861            }
862
863            unsafe {
864                let _ = EvtClose(handle);
865            }
866        }
867
868        Ok(out_events.len())
869    }
870}
871
872impl Drop for EventQuery {
873    fn drop(&mut self) {
874        if !self.handle.is_invalid() {
875            unsafe {
876                let _ = EvtClose(self.handle);
877            }
878        }
879    }
880}
881
882/// Determine if a channel should be included based on filter.
883fn should_include_channel(channel_name: &str, filter: ChannelFilter) -> bool {
884    match filter {
885        ChannelFilter::All => true,
886        ChannelFilter::Operational => {
887            // Operational channels: Application, System, Security, etc.
888            // Exclude Analytic and Debug
889            !channel_name.contains("Analytic") && !channel_name.contains("Debug")
890        }
891        ChannelFilter::AdminOrHigher => {
892            // Similar to Operational for now (can be refined)
893            !channel_name.contains("Analytic") && !channel_name.contains("Debug")
894        }
895        ChannelFilter::IncludeAnalytic => true,
896    }
897}