1# 2# Copyright (c) 2012-2014, Ashok P. Nadkarni 3# All rights reserved. 4# 5# See the file LICENSE for license 6 7# Event log handling for Vista and later 8 9namespace eval twapi { 10 variable _evt; # See _evt_init 11 12 # System event fields in order returned by _evt_decode_event_system_fields 13 twapi::record evt_system_fields { 14 -providername -providerguid -eventid -qualifiers -level -task 15 -opcode -keywordmask -timecreated -eventrecordid -activityid 16 -relatedactivityid -pid -tid -channel 17 -computer -sid -version 18 } 19 20 proc _evt_init {} { 21 variable _evt 22 23 # Various structures that we maintain / cache for efficiency as they 24 # are commonly used are kept in the _evt array with the following keys: 25 26 # system_render_context_handle - is the handle to a rendering 27 # context for the system portion of an event 28 set _evt(system_render_context_handle) [evt_render_context_system] 29 30 # user_render_context_handle - is the handle to a rendering 31 # context for the user data portion of an event 32 set _evt(user_render_context_handle) [evt_render_context_user] 33 34 # render_buffer - is NULL or holds a pointer to the buffer used to 35 # retrieve values so does not have to be reallocated every time. 36 set _evt(render_buffer) NULL 37 38 # publisher_handles - caches publisher names to their meta information. 39 # This is a dictionary indexed with nested keys - 40 # publisher, session, lcid. TBD - need a mechanism to clear ? 41 set _evt(publisher_handles) [dict create] 42 43 # -levelname - dict of publisher name / level number to level names 44 set _evt(-levelname) {} 45 46 # -taskname - dict of publisher name / task number to task name 47 set _evt(-taskname) {} 48 49 # -opcodename - dict of publisher name / opcode number to opcode name 50 set _evt(-opcodename) {} 51 52 # No-op the proc once init is done 53 proc _evt_init {} {} 54 } 55} 56 57# TBD - document 58proc twapi::evt_local_session {} { 59 return NULL 60} 61 62# TBD - document 63proc twapi::evt_local_session? {hsess} { 64 return [pointer_null? $hsess] 65} 66 67# TBD - document 68proc twapi::evt_open_session {server args} { 69 array set opts [parseargs args { 70 user.arg 71 domain.arg 72 password.arg 73 {authtype.arg 0} 74 } -nulldefault -maxleftover 0] 75 76 if {![string is integer -strict $opts(authtype)]} { 77 set opts(authtype) [dict get {default 0 negotiate 1 kerberos 2 ntlm 3} [string tolower $opts(authtype)]] 78 } 79 80 return [EvtOpenSession 1 [list $server $opts(user) $opts(domain) $opts(password) $opts(authtype)] 0 0] 81} 82 83# TBD - document 84proc twapi::evt_close_session {hsess} { 85 if {![evt_local_session? $hsess]} { 86 evt_close $hsess 87 } 88} 89 90proc twapi::evt_channels {{hevtsess NULL}} { 91 # TBD - document hevtsess 92 set chnames {} 93 set hevt [EvtOpenChannelEnum $hevtsess 0] 94 trap { 95 while {[set chname [EvtNextChannelPath $hevt]] ne ""} { 96 lappend chnames $chname 97 } 98 } finally { 99 evt_close $hevt 100 } 101 102 return $chnames 103} 104 105proc twapi::evt_clear_log {chanpath args} { 106 # TBD - document -session 107 array set opts [parseargs args { 108 {session.arg NULL} 109 {backup.arg ""} 110 } -maxleftover 0] 111 112 return [EvtClearLog $opts(session) $chanpath [_evt_normalize_path $opts(backup)] 0] 113} 114 115# TBD - document 116proc twapi::evt_archive_exported_log {logpath args} { 117 array set opts [parseargs args { 118 {session.arg NULL} 119 {lcid.int 0} 120 } -maxleftover 0] 121 122 return [EvtArchiveExportedLog $opts(session) [_evt_normalize_path $logpath] $opts(lcid) 0] 123} 124 125proc twapi::evt_export_log {outfile args} { 126 # TBD - document -session 127 array set opts [parseargs args { 128 {session.arg NULL} 129 file.arg 130 channel.arg 131 {query.arg *} 132 {ignorequeryerrors 0 0x1000} 133 } -maxleftover 0] 134 135 if {([info exists opts(file)] && [info exists opts(channel)]) || 136 ! ([info exists opts(file)] || [info exists opts(channel)])} { 137 error "Exactly one of -file or -channel must be specified." 138 } 139 140 if {[info exists opts(file)]} { 141 set path [_evt_normalize_path $opts(file)] 142 incr opts(ignorequeryerrors) 2 143 } else { 144 set path $opts(channel) 145 incr opts(ignorequeryerrors) 1 146 } 147 148 return [EvtExportLog $opts(session) $path $opts(query) [_evt_normalize_path $outfile] $opts(ignorequeryerrors)] 149} 150 151# TBD - document 152proc twapi::evt_create_bookmark {{mark ""}} { 153 return [EvtCreateBookmark $mark] 154} 155 156# TBD - document 157proc twapi::evt_render_context_xpaths {xpaths} { 158 return [EvtCreateRenderContext $xpaths 0] 159} 160 161# TBD - document 162proc twapi::evt_render_context_system {} { 163 return [EvtCreateRenderContext {} 1] 164} 165 166# TBD - document 167proc twapi::evt_render_context_user {} { 168 return [EvtCreateRenderContext {} 2] 169} 170 171# TBD - document 172proc twapi::evt_open_channel_config {chanpath args} { 173 array set opts [parseargs args { 174 {session.arg NULL} 175 } -maxleftover 0] 176 177 return [EvtOpenChannelConfig $opts(session) $chanpath 0] 178} 179 180# TBD - document 181proc twapi::evt_get_channel_config {hevt args} { 182 set result {} 183 foreach opt $args { 184 lappend result $opt \ 185 [EvtGetChannelConfigProperty $hevt \ 186 [_evt_map_channel_config_property $hevt $propid]] 187 } 188 return $result 189} 190 191# TBD - document 192proc twapi::evt_set_channel_config {hevt propid val} { 193 return [EvtSetChannelConfigProperty $hevt [_evt_map_channel_config_property $propid 0 $val]] 194} 195 196 197# TBD - document 198proc twapi::_evt_map_channel_config_property {propid} { 199 if {[string is integer -strict $propid]} { 200 return $propid 201 } 202 203 # Note: values are from winevt.h, Win7 SDK has typos for last few 204 return [dict get { 205 -enabled 0 206 -isolation 1 207 -type 2 208 -owningpublisher 3 209 -classiceventlog 4 210 -access 5 211 -loggingretention 6 212 -loggingautobackup 7 213 -loggingmaxsize 8 214 -logginglogfilepath 9 215 -publishinglevel 10 216 -publishingkeywords 11 217 -publishingcontrolguid 12 218 -publishingbuffersize 13 219 -publishingminbuffers 14 220 -publishingmaxbuffers 15 221 -publishinglatency 16 222 -publishingclocktype 17 223 -publishingsidtype 18 224 -publisherlist 19 225 -publishingfilemax 20 226 } $propid] 227} 228 229# TBD - document 230proc twapi::evt_event_info {hevt args} { 231 set result {} 232 foreach opt $args { 233 lappend result $opt [EvtGetEventInfo $hevt \ 234 [dict get {-queryids 0 -path 1} $opt]] 235 } 236 return $result 237} 238 239 240# TBD - document 241proc twapi::evt_event_metadata_property {hevt args} { 242 set result {} 243 foreach opt $args { 244 lappend result $opt \ 245 [EvtGetEventMetadataProperty $hevt \ 246 [dict get { 247 -id 0 -version 1 -channel 2 -level 3 248 -opcode 4 -task 5 -keyword 6 -messageid 7 -template 8 249 } $opt]] 250 } 251 return $result 252} 253 254 255# TBD - document 256proc twapi::evt_open_log_info {args} { 257 array set opts [parseargs args { 258 {session.arg NULL} 259 file.arg 260 channel.arg 261 } -maxleftover 0] 262 263 if {([info exists opts(file)] && [info exists opts(channel)]) || 264 ! ([info exists opts(file)] || [info exists opts(channel)])} { 265 error "Exactly one of -file or -channel must be specified." 266 } 267 268 if {[info exists opts(file)]} { 269 set path [_evt_normalize_path $opts(file)] 270 set flags 0x2 271 } else { 272 set path $opts(channel) 273 set flags 0x1 274 } 275 276 return [EvtOpenLog $opts(session) $path $flags] 277} 278 279# TBD - document 280proc twapi::evt_log_info {hevt args} { 281 set result {} 282 foreach opt $args { 283 lappend result $opt [EvtGetLogInfo $hevt [dict get { 284 -creationtime 0 -lastaccesstime 1 -lastwritetime 2 285 -filesize 3 -attributes 4 -numberoflogrecords 5 286 -oldestrecordnumber 6 -full 7 287 } $opt]] 288 } 289 return $result 290} 291 292# TBD - document 293proc twapi::evt_publisher_metadata_property {hpub args} { 294 set result {} 295 foreach opt $args { 296 set val [EvtGetPublisherMetadataProperty $hpub [dict get { 297 -publisherguid 0 -resourcefilepath 1 -parameterfilepath 2 298 -messagefilepath 3 -helplink 4 -publishermessageid 5 299 -channelreferences 6 -levels 12 -tasks 16 300 -opcodes 21 -keywords 25 301 } $opt] 0] 302 if {$opt ni {-channelreferences -levels -tasks -opcodes -keywords}} { 303 lappend result $opt $val 304 continue 305 } 306 set n [EvtGetObjectArraySize $val] 307 set val2 {} 308 for {set i 0} {$i < $n} {incr i} { 309 set rec {} 310 foreach {opt2 iopt} [dict get { 311 -channelreferences { -channelreferencepath 7 312 -channelreferenceindex 8 -channelreferenceid 9 313 -channelreferenceflags 10 -channelreferencemessageid 11} 314 -levels { -levelname 13 -levelvalue 14 -levelmessageid 15 } 315 -tasks { -taskname 17 -taskeventguid 18 -taskvalue 19 316 -taskmessageid 20} 317 -opcodes {-opcodename 22 -opcodevalue 23 -opcodemessageid 24} 318 -keywords {-keywordname 26 -keywordvalue 27 319 -keywordmessageid 28} 320 } $opt] { 321 lappend rec $opt2 [EvtGetObjectArrayProperty $val $iopt $i] 322 } 323 lappend val2 $rec 324 } 325 326 evt_close $val 327 lappend result $opt $val2 328 } 329 return $result 330} 331 332# TBD - document 333proc twapi::evt_query_info {hq args} { 334 set result {} 335 foreach opt $args { 336 lappend result $opt [EvtGetQueryInfo $hq [dict get { 337 -names 1 statuses 2 338 } $opt]] 339 } 340 return $result 341} 342 343# TBD - document 344proc twapi::evt_object_array_size {hevt} { 345 return [EvtGetObjectArraySize $hevt] 346} 347 348# TBD - document 349proc twapi::evt_object_array_property {hevt index args} { 350 set result {} 351 352 foreach opt $args { 353 lappend result $opt \ 354 [EvtGetObjectArrayProperty $hevt [dict get { 355 -channelreferencepath 7 356 -channelreferenceindex 8 -channelreferenceid 9 357 -channelreferenceflags 10 -channelreferencemessageid 11 358 -levelname 13 -levelvalue 14 -levelmessageid 15 359 -taskname 17 -taskeventguid 18 -taskvalue 19 360 -taskmessageid 20 -opcodename 22 361 -opcodevalue 23 -opcodemessageid 24 362 -keywordname 26 -keywordvalue 27 -keywordmessageid 28 363 }] $index] 364 } 365 return $result 366} 367 368proc twapi::evt_publishers {{hsess NULL}} { 369 set pubs {} 370 set hevt [EvtOpenPublisherEnum $hsess 0] 371 trap { 372 while {[set pub [EvtNextPublisherId $hevt]] ne ""} { 373 lappend pubs $pub 374 } 375 } finally { 376 evt_close $hevt 377 } 378 379 return $pubs 380} 381 382# TBD - document 383proc twapi::evt_open_publisher_metadata {pub args} { 384 array set opts [parseargs args { 385 {session.arg NULL} 386 logfile.arg 387 lcid.int 388 } -nulldefault -maxleftover 0] 389 390 return [EvtOpenPublisherMetadata $opts(session) $pub $opts(logfile) $opts(lcid) 0] 391} 392 393# TBD - document 394proc twapi::evt_publisher_events_metadata {hpub args} { 395 set henum [EvtOpenEventMetadataEnum $hpub] 396 397 # It is faster to build a list and then have Tcl shimmer to a dict when 398 # required 399 set meta {} 400 trap { 401 while {[set hmeta [EvtNextEventMetadata $henum 0]] ne ""} { 402 lappend meta [evt_event_metadata_property $hmeta {*}$args] 403 evt_close $hmeta 404 } 405 } finally { 406 evt_close $henum 407 } 408 409 return $meta 410} 411 412proc twapi::evt_query {args} { 413 array set opts [parseargs args { 414 {session.arg NULL} 415 file.arg 416 channel.arg 417 {query.arg *} 418 {ignorequeryerrors 0 0x1000} 419 {direction.sym forward {forward 0x100 reverse 0x200 backward 0x200}} 420 } -maxleftover 0] 421 422 if {([info exists opts(file)] && [info exists opts(channel)]) || 423 ! ([info exists opts(file)] || [info exists opts(channel)])} { 424 error "Exactly one of -file or -channel must be specified." 425 } 426 427 set flags $opts(ignorequeryerrors) 428 incr flags $opts(direction) 429 430 if {[info exists opts(file)]} { 431 set path [_evt_normalize_path $opts(file)] 432 incr flags 0x2 433 } else { 434 set path $opts(channel) 435 incr flags 0x1 436 } 437 438 return [EvtQuery $opts(session) $path $opts(query) $flags] 439} 440 441proc twapi::evt_next {hresultset args} { 442 array set opts [parseargs args { 443 {timeout.int -1} 444 {count.int 1} 445 {status.arg} 446 } -maxleftover 0] 447 448 if {[info exists opts(status)]} { 449 upvar 1 $opts(status) status 450 return [EvtNext $hresultset $opts(count) $opts(timeout) 0 status] 451 } else { 452 return [EvtNext $hresultset $opts(count) $opts(timeout) 0] 453 } 454} 455 456twapi::proc* twapi::_evt_decode_event_system_fields {hevt} { 457 _evt_init 458} { 459 variable _evt 460 set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(system_render_context_handle) $hevt $_evt(render_buffer)] 461 set rec [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] 462 return [evt_system_fields set $rec \ 463 -providername [atomize [evt_system_fields -providername $rec]] \ 464 -providerguid [atomize [evt_system_fields -providerguid $rec]] \ 465 -channel [atomize [evt_system_fields -channel $rec]] \ 466 -computer [atomize [evt_system_fields -computer $rec]]] 467} 468 469# TBD - document. Returns a list of user data values 470twapi::proc* twapi::evt_decode_event_userdata {hevt} { 471 _evt_init 472} { 473 variable _evt 474 set _evt(render_buffer) [Twapi_EvtRenderValues $_evt(user_render_context_handle) $hevt $_evt(render_buffer)] 475 return [Twapi_ExtractEVT_RENDER_VALUES $_evt(render_buffer)] 476} 477 478twapi::proc* twapi::evt_decode_events {hevts args} { 479 _evt_init 480} { 481 variable _evt 482 483 array set opts [parseargs args { 484 {values.arg NULL} 485 {session.arg NULL} 486 {logfile.arg ""} 487 {lcid.int 0} 488 ignorestring.arg 489 message 490 levelname 491 taskname 492 opcodename 493 keywords 494 xml 495 } -ignoreunknown -hyphenated] 496 497 # SAME ORDER AS _evt_decode_event_system_fields 498 set decoded_fields [evt_system_fields] 499 set decoded_events {} 500 501 # ORDER MUST BE SAME AS order in which values are appended below 502 foreach opt {-levelname -taskname -opcodename -keywords -xml -message} { 503 if {$opts($opt)} { 504 lappend decoded_fields $opt 505 } 506 } 507 508 foreach hevt $hevts { 509 set decoded [_evt_decode_event_system_fields $hevt] 510 # Get publisher from hevt 511 set publisher [evt_system_fields -providername $decoded] 512 513 if {! [dict exists $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)]} { 514 if {[catch { 515 dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) [EvtOpenPublisherMetadata $opts(-session) $publisher $opts(-logfile) $opts(-lcid) 0] 516 }]} { 517 # TBD - debug log 518 dict set _evt(publisher_handles) $publisher $opts(-session) $opts(-lcid) NULL 519 } 520 } 521 set hpub [dict get $_evt(publisher_handles) $publisher $opts(-session) $opts(-lcid)] 522 523 # See if cached values are present for -levelname -taskname 524 # and -opcodename. TBD - can -keywords be added to this ? 525 foreach {intopt opt callflag} {-level -levelname 2 -task -taskname 3 -opcode -opcodename 4} { 526 if {$opts($opt)} { 527 set ival [evt_system_fields $intopt $decoded] 528 if {[dict exists $_evt($opt) $publisher $ival]} { 529 lappend decoded [dict get $_evt($opt) $publisher $ival] 530 } else { 531 # Not cached. Look it up. Value of 0 -> null so 532 # just use ignorestring if specified. 533 if {$ival == 0 && [info exists opts(-ignorestring)]} { 534 set optval $opts(-ignorestring) 535 } else { 536 if {[info exists opts(-ignorestring)]} { 537 if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { 538 dict set _evt($opt) $publisher $ival $optval 539 } else { 540 # Note result not cached if not found since 541 # ignorestring may be different on every call 542 set optval $opts(-ignorestring) 543 } 544 } else { 545 # -ignorestring not specified so 546 # will raise error if not found 547 set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] 548 dict set _evt($opt) $publisher $ival [atomize $optval] 549 } 550 } 551 lappend decoded $optval 552 } 553 } 554 } 555 556 # Non-cached fields 557 # ORDER MUST BE SAME AS decoded_fields ABOVE 558 foreach {opt callflag} { 559 -keywords 5 560 -xml 9 561 } { 562 if {$opts($opt)} { 563 if {[info exists opts(-ignorestring)]} { 564 if {! [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag optval]} { 565 set optval $opts(-ignorestring) 566 } 567 } else { 568 set optval [EvtFormatMessage $hpub $hevt 0 $opts(-values) $callflag] 569 } 570 lappend decoded $optval 571 } 572 } 573 574 # We treat -message differently because on failure we want 575 # to extract the user data. -ignorestring is not used for this 576 # unless user data extraction also fails 577 if {$opts(-message)} { 578 if {[EvtFormatMessage $hpub $hevt 0 $opts(-values) 1 message]} { 579 lappend decoded $message 580 } else { 581 # TBD - make sure we have a test for this case. 582 # TBD - log 583 if {[catch { 584 lappend decoded "Message for event could not be found. Event contained user data: [join [evt_decode_event_userdata $hevt] ,]" 585 } message]} { 586 if {[info exists opts(-ignorestring)]} { 587 lappend decoded $opts(-ignorestring) 588 } else { 589 error $message 590 } 591 } 592 } 593 } 594 595 lappend decoded_events $decoded 596 } 597 598 return [list $decoded_fields $decoded_events] 599} 600 601proc twapi::evt_decode_event {hevt args} { 602 return [recordarray index [evt_decode_events [list $hevt] {*}$args] 0 -format dict] 603} 604 605# TBD - document 606proc twapi::evt_format_publisher_message {hpub msgid args} { 607 608 array set opts [parseargs args { 609 {values.arg NULL} 610 } -maxleftover 0] 611 612 return [EvtFormatMessage $hpub NULL $msgid $opts(values) 8] 613} 614 615# TBD - document 616# Where is this used? 617proc twapi::evt_free_EVT_VARIANT_ARRAY {p} { 618 evt_free $p 619} 620 621# TBD - document 622# Where is this used? 623proc twapi::evt_free_EVT_RENDER_VALUES {p} { 624 evt_free $p 625} 626 627# TBD - document 628proc twapi::evt_seek {hresults pos args} { 629 array set opts [parseargs args { 630 {origin.arg first {first last current}} 631 bookmark.arg 632 {strict 0 0x10000} 633 } -maxleftover 0] 634 635 if {[info exists opts(bookmark)]} { 636 set flags 4 637 } else { 638 set flags [lsearch -exact {first last current} $opts(origin)] 639 incr flags; # 1 -> first, 2 -> last, 3 -> current 640 set opts(bookmark) NULL 641 } 642 643 incr flags $opts(strict) 644 645 EvtSeek $hresults $pos $opts(bookmark) 0 $flags 646} 647 648proc twapi::evt_subscribe {path args} { 649 # TBD - document -session and -bookmark and -strict 650 array set opts [parseargs args { 651 {session.arg NULL} 652 {query.arg *} 653 bookmark.arg 654 includeexisting 655 {ignorequeryerrors 0 0x1000} 656 {strict 0 0x10000} 657 } -maxleftover 0] 658 659 set flags [expr {$opts(ignorequeryerrors) | $opts(strict)}] 660 if {[info exists opts(bookmark)]} { 661 set flags [expr {$flags | 3}] 662 set bookmark $opts(origin) 663 } else { 664 set bookmark NULL 665 if {$opts(includeexisting)} { 666 set flags [expr {$flags | 2}] 667 } else { 668 set flags [expr {$flags | 1}] 669 } 670 } 671 672 set hevent [lindex [CreateEvent [_make_secattr {} 0] 0 0 ""] 0] 673 if {[catch { 674 EvtSubscribe $opts(session) $hevent $path $opts(query) $bookmark $flags 675 } hsubscribe]} { 676 set erinfo $::errorInfo 677 set ercode $::errorCode 678 CloseHandle $hevent 679 error $hsubscribe $erinfo $ercode 680 } 681 682 return [list $hsubscribe $hevent] 683} 684 685proc twapi::_evt_normalize_path {path} { 686 # Do not want to rely on [file normalize] returning "" for "" 687 if {$path eq ""} { 688 return "" 689 } else { 690 return [file nativename [file normalize $path]] 691 } 692} 693 694proc twapi::_evt_dump {args} { 695 array set opts [parseargs args { 696 {outfd.arg stdout} 697 count.int 698 } -ignoreunknown] 699 700 set hq [evt_query {*}$args] 701 trap { 702 while {[llength [set hevts [evt_next $hq]]]} { 703 trap { 704 foreach ev [recordarray getlist [evt_decode_events $hevts -message -ignorestring None.] -format dict] { 705 if {[info exists opts(count)] && 706 [incr opts(count) -1] < 0} { 707 return 708 } 709 puts $opts(outfd) "[dict get $ev -timecreated] [dict get $ev -eventrecordid] [dict get $ev -providername]: [dict get $ev -eventrecordid] [dict get $ev -message]" 710 } 711 } finally { 712 evt_close {*}$hevts 713 } 714 } 715 } finally { 716 evt_close $hq 717 } 718} 719