The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,861 other subscribers

Some links on getting the most recent defragmentation time of a Windows volume

Posted by jpluimers on 2026/02/25

This worked on the built-in Windows PowerShell to get the recommendation status:

$volume = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = 'C:'"
$analysis = $volume.DefragAnalysis()
$analysis.DefragAnalysis
$analysis.DefragRecommended

Without elevation token, $analysis.DefragAnalysis will be empty and $analysis.DefragRecommended will return False, but elevated it will return the analysis data and  $analysis.DefragRecommended will return False or True depending on the analysis result.,

And this gets the most recent defragmentation action from the event-log:

$defragEvents = Get-WinEvent -FilterHashtable @{LogName = 'Application'; ID = '258'; ProviderName='Microsoft-Windows-Defrag'} -MaxEvents 1

This is way faster than leaving out the -MaxEvents and works fine because if no event can be found, you will get this error:

Get-WinEvent : No events were found that match the specified selection criteria.

Now my goal was to figure out the most recent log entry that shows the real defragmentation of a specific drive from the event-log.

This turned out to be quite a bit more complex than I hoped for, hence this blog post.

In the end, the below notes, queries, links and Microsoft documentation got me this:

aaaaa___________________________________________________________a

The quest notes

Since the quest was quite long especially because of link rot and references that have moved to new places, but not being properly redirected to the new place.

  • The optimum filter syntaxes for these queries are because of historic reasons (both Windows Manage Instrumentation – usually called WMI – and the Windows Event Log predate .NET and PowerShell a lot) and take quite a bit of getting used to. That pays off in flexibility and speed.
  • Get-WinEvent has ways of filtering that vastly differ in usage and speed:
    • | Where-Object
    • -FilterHashtable (the most easy to read one, as it uses hash tables from PowerShell @ hash-table object, but getting Data= to work on complex XML a tad more difficult, see further below)
    • -FilterXPath (is easier to understand for old schoolers like me that have used XPath quite a bit, but only a subset of XPath 1.o is supported)
    • -FilterXML (the most flexible way, and compatible with the XML query syntax of the Windows built-in Event Viewer tool using a query formatted as XML; you can even use the XPath 1.0 subset here)

    | Where-Object is orders of magnitude slower than the other three, and of those, -FilterHashtable seems to be slower as well. The basic differences between the filter mechanisms are explained in anchor “Example 16: Filter event log results” of the Get-WinEvent link below. More elaborate filters can

  • Get-WmiObject has different options too, but because of the smaller data sets, absolute performance does not vary that much:
    • | Where-Object
    • -Filter
  • The | Format-List -Property * and | Get-Member are golden for  inspection of and spelunking into data structures you don’t yet are familiar with:
    • $events[0] | Format-List - Property *
    • $events[0] | Get-Member
  • Optimize-Volume does not return any output, so it is kind of useless for inspection

Queries

I could not archive the numerous queries as Google Search results cannot be archived any more and the Duck Duck Go queries don’t return the results I used.

Links

Note that some of the links below get their quoting in the source code wrong. For example

$drive = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = `e:'"

instead of

$drive = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = 'e:'"

Usually this is caused by encoding errors adding up because of historic migrations and nobody bothering enough to fully check out the results.

General links

  • [Wayback/Archive] windows server 2008 r2 – Find last run defrag WMI or Reg key – Server Fault (thanks [Wayback/Archive] Clayton for the Event ID 258 in the Application event log plus  -FilterHashtable tips and [Wayback/Archive] Nixphoe for the DefragAnalysis tip).
    Run a query for Event ID 258 in the Application event log
    PS F:\ps1> get-winevent -filterHashTable @{Logname = 'Application'; ID = '258';} | select-object -first 1 -prop machinename,timecreated,message | fl
    

    $driveLetter = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = 'C:'"
    $driveDefrag = $driveLetter.DefragAnalysis()
    $driveDefrag.DefragAnalysis
    
    $report.DefragRecommended will display if the OS thinks it needs a defrag and $report.DefragAnalysis has a lot of additional useful information as well.
  • [Wayback/Archive] Use PowerShell to Determine Fragmentation of Your Drive – Scripting Blog [archived] had encoding issues, but also taught me about Invoke-WmiMethod:

    PS C:\> $drive = Get-WmiObject -Class Win32_Volume -Filter “DriveLetter = ‘e:'”
    PS C:\> $report = $drive.DefragAnalysis()
    PS C:\> $report.DefragAnalysis

    To use the Invoke-WmiMethod cmdlet, I first use the Get-WmiObject cmdlet to retrieve an instance of the Win32_Volume WMI class that is associated with my drive E. Next, I call the Invoke-WmiMethod to pass the input object that is stored in the $drive variable, and I use the name parameter to call the DefragAnalysis method. This method requires no arguments. The command and its associated output are shown here.
    PS C:\> $drive = Get-WmiObject -Class Win32_Volume -Filter “DriveLetter = ‘e:'”
    PS C:\> Invoke-WmiMethod -InputObject $drive -Name DefragAnalysis
  • [Wayback/Archive] Optimize and Defrag Drives in Windows 10 – Page 13 – | Tutorials
  • [Wayback/Archive] Dymax I/O software – Improvement in Backup Throughput I/O? | Page 2 | Windows 11 Forum showing this script which does not work on my machine:
    Get-WinEvent -FilterHashtable @{LogName='Application';ProviderName='Microsoft-Windows-Defrag'} |% {$x=[xml]$_.ToXml(); If($_.Id -eq 258 -Or $_.Id -eq 264) {If($x.Event.EventData.Data[1] -ne $z) {$z=$x.Event.EventData.Data[1];$y='~'} else{$y=''}; $_ |select @{l='TimeCreated';e={$_.TimeCreated}}, @{l='Level';e={"$($_.Level) $($_.LevelDisplayName)"}}, Id, @{l='~';e={$y}}, @{l='Volume';e={$z}}, @{l='Hex';e={"$([String]$x.Event.EventData.Binary[10])$([String]$x.Event.EventData.Binary[11])$([String]$x.Event.EventData.Binary[8])$([String]$x.Event.EventData.Binary[9])"}}, @{l='Data';e={$x.Event.EventData.Data[0]}}, @{l='Message';e={$x.Event.EventData.Data[2]}} }} |? {$_.Hex -eq '02AD'} |ogv -Title 'Retrim'
  • [Wayback/Archive] What powershell scripts are you using | Windows 11 Forum has the same script as above.
  • [Wayback/Archive] PowerShell/Get-WinEventData.ps1 at master · RamblingCookieMonster/PowerShell · GitHub has this RAW source code file

    [Wayback/Archive] raw.githubusercontent.com/RamblingCookieMonster/PowerShell/refs/heads/master/Get-WinEventData.ps1

    That in turns is based on the now disappeared blog post [Wayback/Archive] PowerShell Get-WinEvent XML Madness: Getting details from event logs – Goatee PFE – Site Home – TechNet Blogs by early PowerShell guru [Wayback/Archive] GoateePFE (Ashley McGlone) · GitHub. The final location of the blog post before it got removed was [Wayback/Archive] PowerShell Get-WinEvent XML Madness: Getting details from event logs – GoateePFE – Archived with this interesting bit

    The ugly:  All of the juicy nuggets of event data in the message body are stored in XML.  And nearly every combination of event ID and provider has a unique event schema for storing the data we want.  Neo’s MSDN blog post gets us most of the way there.  AskDS and Hey Scripting Guy show how we can use the GUI to help write the XML filter syntax.  Now my head is spinning.  This is the farthest point from intuitive.  Don’t even get me started on XPATH.

    Note:  In all fairness to the product this data structure is necessary.  All events have a few common properties like provider, ID number, date/time, source, etc.  But in order to capture the unique details of each event we needed a way to store a variable number of properties.  So the design is good, just a bit complicated to script.

    I tracked down this blog post and the mentioned blog posts and put the most recent locations here (the archived links work). This blog-post:

    Mentioned blog posts are from the Microsoft documentation, so I quote from them in the Microsoft documentation section further below. There are the links though:

    The mentioned limitations section fails to mention that “starts-with” and “contains” are not supported in Windows Event Viewer and it’s API wrappers. Oh, and did I already mention I hate link rot?

  • [Wayback/Archive] Event Viewer XML Query Unnamed EventData – Programming & Development – Spiceworks Community
  • [Wayback/Archive] How to select for content contains substring in Windows Event Viewer using XPath 1.0? – Stack Overflow showing how to embed XPath in the XML Query Syntax (thanks [Wayback/Archive] Mark Moore and [Wayback/Archive] Martin Honnen) but confirms that it is an XPath 1.0 subset:

    Q

    <QueryList>
      <Query Id="0" Path="System">
        <Select Path="System">*[EventData[Data[@Name="updateTitle"]]]</Select>
      </Query>
    </QueryList>

    Edit the filter to change the Select element to <Select Path="System">*[EventData[contains(Data, "Intelligence Update")]]</Select>, click OK twice and you should get this error message:

    “The specified query is invalid”

    it definitely seems WEV does not accept a valid XPath 1.0 query (e.g. "*[EventData[Data[contains(., "Intelligence Update")]]]), so the implications is that WEV does not support any of the XPath 1.0 functions except the three mentioned in the restrictions.

    C

    Thanks @martin-honnen. It looks like the XPath implementation doesn’t support contains() even though they don’t document that limitation. Although it says “The specified query is invalid” and doesn’t work, I’ve accepted your answer. Maybe someday someone at Microsoft will respond with a better solution.

  • [Wayback/Archive] Cover page | xpath | W3C standards and drafts | W3C
  • [Wayback/Archive] windows – How to search string in Event Viewer XML Query? – Super User (thanks [Wayback/Archive] SchoolforDesign, [Wayback/Archive] w32sh and [Wayback/Archive] DavidPostill):

    C

    I think you may need to use PowerShell for that. stackoverflow.com/questions/8671194/…

    A

    I want to search all Events which contain LIKE 192.168.
    Unfortunately I don’t think that is directly possible, because:
    Windows Event Log supports a subset of XPath 1.0. There are limitations to what functions work in the query. For instance, you can use the “position”, “Band”, and “timediff” functions within the query but other functions like “starts-with” and “contains” are not currently supported.
    However, as w32sh pointed out in a comment, it is possible with PowerShell. See this Stack Overflow question: Using XPath starts-with or contains functions to search Windows event logs
    Note that almost at the same time as finding this SuperUser answer, I also had found another source mentioned in it. It’s referenced both above from Get-WinEventData.ps1 and below in the “Microsoft documentation” section.
  • [Wayback/Archive] xml – Using XPath starts-with or contains functions to search Windows event logs – Stack Overflow (thanks [Wayback/Archive] Keith Walton, [Wayback/Archive] Kirill Polishchuk and [Wayback/Archive] Richard Sandoz)

    Q

     I want to do a partial match:
    <QueryList>
      <Query Id="0" Path="Application">
        <Select Path="Application">*[EventData[Data and (Data[starts-with(.,"Session")])]]</Select>
      </Query>
    </QueryList>
    
    Event log gives me the error:
    The specified query is invalid
    Do I have the syntax wrong?

    A

    Windows Event Log supports a subset of XPath 1.0. It has only three functions: positionBandtimediff.

    A

    If you don’t mind two passes, you can always use a powershell script to re-filter the data as its -where operator supports -like-match, and -contains:
    nv.ps1
    $Query = @"
      <QueryList>
        <Query Id="0" Path="System">
          <Select Path="System">
            *[System[(EventID=20001)]]
          </Select>
        </Query>
      </QueryList>
    "@
    
    $events = Get-WinEvent -FilterXml $Query
    ForEach ($Event in $Events) {
        # Convert the event to XML
        $eventXML = [xml]$Event.ToXml()
        Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name  DriverVersion -Value $eventXML.Event.UserData.InstallDeviceID.DriverVersion
        Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name  DriverDescription -Value $eventXML.Event.UserData.InstallDeviceID.DriverDescription
        Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name  Data -Value $eventXML.Event.EventData.Data
    }
    $Events | Select TimeCreated, Id, DriverDescription, DriverVersion, ProviderName, @{Name="MessageData";Expression={$_.Message + $_.Data}} | Where {$_.DriverDescription -match "NVIDIA GeForce GTX*"} | Out-GridView
    pause
    
    A cmd to launch it (nv.cmd):
    powershell.exe -executionpolicy bypass "& '.\nv.ps1'"
    
  • [Wayback/Archive] powershell – Optimize Get-WinEvent to run through entries faster – Stack Overflow (thanks [Wayback/Archive] Salve, [Wayback/Archive] Theo and [Wayback/Archive] TheMadTechnician)

    A

    Almost forgot.. You can limit the events to be from the last 7 days by extending the filter:
    $filter = @{
        LogName      ='Security'
        ProviderName ='Microsoft-Windows-Security-Auditing'
        ID           = 4624
        StartTime    = (Get-Date).AddDays(-7).Date
    }

    A (formatted for readability)

    So after some heavy googling I’ve optimized the script to run in about 3min instead of 6 hours… Apparently the -FilterHashtable is known to be extremely slow, instead I’m using -FilterXPath.
    Another benefit to that is the param -LogName (that is not available with -FilterHashtable) that cuts down the time the most since instead of filtering the entire log I’m only looking into the specific log I’m interested in.
    Here is the final code:
    $startTime = (Get-Date)
    $XPath = "*[EventData[Data[@Name='TargetUserName'] and (Data='User1' or Data='User2' or Data='User3')] and System[TimeCreated[timediff(@SystemTime) <= 86400000]]]"
    
    $entries = foreach ($entry in (Get-WinEvent -LogName 'Security' -FilterXPath $XPath -ComputerName 'localhost')){
        $eventXml = ([xml]$entry.ToXml()).Event
        $userName = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
        $computer = ($eventXml.EventData.Data | Where-Object { $_.Name -eq 'WorkstationName' }).'#text'
    
            [PSCustomObject]@{
                Time     = [DateTime]$eventXml.System.TimeCreated.SystemTime
                UserName = $userName
                Computer = $computer
                }
    }
    $filetime = Get-Date -Format "yyyyMMddHHmm"
    $entries | Export-Csv -Path "C:\Temp\UsedAccounts$filetime.csv" -UseCulture -NoTypeInformation
    $endTime = (Get-Date)
    'Duration: {0:hh}h {0:mm}min {0:ss}sec' -f ($endTime-$startTime)

    A

    Theo was faster than me, but in addition to his suggestions I would recommend using -FilterXml If you change to using XML for your filter instead of a hashtable you can definitely improve your filter options, including specifying how recent your results are.
    $filter = @'
    <QueryList>
      <Query Id="0" Path="Security">
        <Select Path="Security">*[System[TimeCreated[@SystemTime&gt;='2021-09-15T17:39:00.000Z']]]</Select>
      </Query>
    </QueryList>
    '@
    
    $entries = Get-WinEvent -FilterXml $filter

    Past that I would really consider just running a regex match against the message of the event rather than converting everything to XML. Admittedly, the bulk of your time is just getting the events into PowerShell, but processing them is going to take time too, and just running a regex match against the message rather than converting to XML and parsing that takes (on my dev box at least) 60x longer to convert to XML and parse that.

    $filtered=ForEach($entry in $entries){
        if($entry.Message -match "(?ms)Target Subject:.+?   Account Name:\s+(user1|user2|user3)"){
            [pscustomobject]@{
                TimeCreated=$entry.TimeCreated
                User=$Matches[1]
                Computer=$env:COMPUTERNAME
            }
        }
    }
    $filetime = Get-Date -Format "ddMMyyyyHHmm"
    $filtered | Out-File "C:\Temp\UsedAccounts$filetime.txt"
  • [Wayback/Archive] Need faster Get-WinEvent execution : PowerShell
    You should be adding a ‘StartTime’ key/value in the FilterHashtable. The way you’re doing it now collects all past matching security events then filters them.
    Get-WinEvent -FilterHashtable @{
        LogName='Security'
        Id=5157,5152,5031,5150,5151,5155,5159,5153
        StartTime = [datetime]::Now.AddHours(-8)
     }

    Add the hashtable keys which Get-WinEvent -FilterHashtable recognises to tab complete when writing the hashtable.

  • [Wayback/Archive] c# – Get EventData from Get-WinEvent from File – How to group by EventData.Data? – Stack Overflow (thanks [Wayback/Archive] surfmuggle and [Wayback/Archive] mike crowley)

    A

    Events have a toxml method which makes this possible:
    $event.toxml()
    You could continue to work with the XML directly, or convert it to a PS Object, which is what I’ve done ([xml]$event.toxml()). You can swap out your event data values as appropriate.
    $filter = @{     
        ID        = 1000, 1003
        startTime = [datetime]"1/1/2023"
        path      = "C:\tmp\SavedEvents.evtx"
    } 
        
    $Events = Get-WinEvent -FilterHashtable $filter
        
    $CustomEventObjects = foreach ($event in $events) {
        $xmlevent = [xml]$event.toxml()
        
        [pscustomobject]@{
            TimeCreated = $event.TimeCreated
            RequestPath = ($xmlevent.Event.EventData.Data | Where-Object name -eq RequestPath).'#text'
            ActionName  = ($xmlevent.Event.EventData.Data | Where-Object name -eq ActionName).'#text'
            ActionId    = ($xmlevent.Event.EventData.Data | Where-Object name -eq ActionId).'#text'
        }
    }
    
    #possible outputs, but you can take it from here:
    $CustomEventObjects | Group-Object RequestPath
    $CustomEventObjects | Group-Object ActionName
    $CustomEventObjects | Group-Object ActionId
    
  • [Wayback/Archive] Powershell -get-eventdata – Stack Overflow reminded me there are limitation parameters -Newest and -MaxEvent (thanks [Wayback/Archive] TessellatingHeckler, but please next time get your casing consistent):

    A

    # Get the most recent event 4776
    $event = Get-EventLog -LogName Security -InstanceId 4776 -newest 1
    

    $event = Get-WinEvent -FilterHashtable @{id=4776} -LogName Security -maxevent 1
    $event.ToXML()

  • [Wayback/Archive] powershell – get-winevent: add parts of extended data to csv-columns – Stack Overflow taught me about the much under-documented EventPropertySelector (thanks [Wayback/Archive] Peter Core and [Wayback/Archive] Mathias R. Jessen), but I think it has the same XPath 1.0 limitations as the rest of the Windows Event Log suffers from. Anyway:
    Any ideas how to write them (only this fields, not the additional ones) to a csv file with headernames the same as the selected data-items?
    Plenty!
    Here’s the safest (although probably least obvious) – use an EventPropertySelector!
    Get-WinEvent -FilterHashtable @{Path="c:\temp\raw_data\SavedSecurity.evtx";} |Where {($_.id -eq "4624" -and $_.properties[8].value -in 10)} |%{
        $SelectorStrings = [string[]]@(
            'Event/EventData/Data[@Name="TargetUserName"]',
            'Event/EventData/Data[@Name="TargetDomainName"]',
            'Event/EventData/Data[@Name="TargetLogonId"]',
            'Event/EventData/Data[@Name="LogonType"]',
            'Event/EventData/Data[@Name="WorkstationName"]',
            'Event/EventData/Data[@Name="IpAddress"]',
            'Event/EventData/Data[@Name="IpPort"]'
        )
        $PropertySelector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new($SelectorStrings)
    
        $UserName,$Domain,$LogonId,$LogonType,$ComputerName,$IPAddres,$Port = $_.GetPropertyValues($PropertySelector)
    
        New-Object psobject -Property @{
            Message      = $_.Message
            UserName     = $UserName
            Domain       = $Domain
            LogonId      = $LogonId
            LogonType    = $LogonType
            ComputerName = $ComputerName
            IPAddres     = $IPAddres
            Port         = $Port
            TimeCreated  = $_.TimeCreated
        }
    }
    
    In the above code snippet, we use XPath location selectors to grab the relevant Data nodes from the Event’s XML structure. If any of them don’t exist, the corresponding variable will simply be an empty string
  • [Wayback/Archive] xslt – Why do indexes in XPath start with 1 and not 0? – Stack Overflow (thanks [Wayback/Archive] Edward Tanguay and [Wayback/Archive] Andrew) referencing [Wayback/Archive] Mukul Gandhi: One-based indexes in XPath quoting Michael Howard Kay (developer of Saxon XSLT and XQuery processing software, and editor of the XSLT 2.0 and 3.0 language specifications for XML transformation)

    …1-based logic was the right choice for XPath and XSLT…because the language was designed for users, not for programmers, and users still have this old-fashioned habit of referring to the first chapter in a book as Chapter One…

    then followed by Andrew’s answer:

    To answer this question, we must examine the history of some technologies.

    RSS XML XSLT and XPath History

    Version 0.9 of RSS was originally released as RDF Site Summary in 1999 by a couple of guys at Netscape for Netscape’s my.netscape.com portal. Later that year, it was renamed to RSS (Rich Site Summary) with the v0.91 update. Development of the project changed hands several times, but RSS version 1.0 was released by December of 2000. With the v1.0 update, RSS included support for XML.

    During 2002 v2.0 was released in September as RSS (Really Simple Syndication) and began to evolve into a major internet technology. In it’s early history, RSS feeds (and the XML data they contained) were read by humans in the raw format. Blogs and other news sources used RSS feeds and XML to output continuously updated information. Since XML was being read by mere mortals (non-programmers), XPath and XSLT also needed to be easily understandable, so that these mere mortals would not be overwhelmed by complexity when interacting with it. That is why XPath mimics the style of URIs, which is something that end-users were already familiar with. One of the concessions made for the purpose of readability by users, was to use old-fashioned numbering techniques i.e. 1-based indexes instead of 0-based indexes. That is the same concession that you mentioned with VBScript, and it was made for similar reasons.

    Although RSS feeds and XML were made to be readable for most people, RSS readers were developed to provide a more pleasant interface for humans to read RSS feeds. Now, raw RSS and XML data are read almost exclusively with some sort of reader or graphical interface. XML is still in frequent (perhaps permanent) use across the web, but it is masked by fancy graphical user interfaces to provide a better experience for end users.

    *The term, “mere mortals,” refers to humans who are not programers

The last main bullets finally shows how to query EventData.Data mimicking contains: a two-stage solution doing the contains part in PowerShell itself.

Microsoft documentation links

I tried to keep these in lexicographic order.

Filtering on the EventData bit

This is the XML of one eventlog entry:

<?xml version="1.0"?>
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Microsoft-Windows-Defrag"/>
    <EventID Qualifiers="0">258</EventID>
    <Version>0</Version>
    <Level>0</Level>
    <Task>0</Task>
    <Opcode>0</Opcode>
    <Keywords>0x80000000000000</Keywords>
    <TimeCreated SystemTime="2025-09-10T08:58:33.1052311Z"/>
    <EventRecordID>141094</EventRecordID>
    <Correlation/>
    <Execution ProcessID="0" ThreadID="0"/>
    <Channel>Application</Channel>
    <Computer>DESKTOP-i7-3770</Computer>
    <Security/>
  </System>
  <EventData>
    <Data>defragmentation</Data>
    <Data>(C:)</Data>
    <Binary>0000000034020000FD0100000000000022B651A2296BEDD85E9C8D030000000000000000</Binary>
  </EventData>
</Event>

I want to filter on the first and second EventData.Data elements which contain the defragmentation type and drive. So a filter like:

  • Event.System.Provider Name="Microsoft-Windows-Defrag"
  • Event.System.EventID="258"
  • Event.EventData.Data[1]="defragmentation"
  • Event.EventData.Data[2]="(#:)" where # is the drive letter

In this notation, Event.System.TimeCreated is the defragmentation timestamp.

Oh, and this takes into account that XPath array indices are one-based, see my blog-post last week titled “Programming languages where the default array starting index is one“.

Queries that return results with “Information” level (Level=4 or Level=0) and EventId=258 (the EventId for Microsoft Windows Defrag  events):

    1. All Microsoft-Windows-Defrag events:
      <QueryList>
        <Query Id="0" Path="Application">
          <Select Path="Application">
      *[System[Provider[@Name='Microsoft-Windows-Defrag'] and (Level=4 or Level=0) and (EventID=258)]]
      and
      *[EventData[Data]]
          </Select>
        </Query>
      </QueryList>
    2. Microsoft-Windows-Defrag events having one or more EventData.Data with any node(s) having the value “defragmentation
      <QueryList>
        <Query Id="0" Path="Application">
          <Select Path="Application">
      *[System[Provider[@Name='Microsoft-Windows-Defrag'] and (Level=4 or Level=0) and (EventID=258)]]
      and
      *[EventData[Data='defragmentation']]
          </Select>
        </Query>
      </QueryList>
    3. Microsoft-Windows-Defrag events having the first EventData.Data node having the value “defragmentation” (yes, XPath indexing starts at one – see the referenced links about one based languages)
      <QueryList>
        <Query Id="0" Path="Application">
          <Select Path="Application">
      *[System[Provider[@Name='Microsoft-Windows-Defrag'] and (Level=4 or Level=0) and (EventID=258)]]
      and
      *[EventData[Data[1]='defragmentation']]
          </Select>
        </Query>
      </QueryList>

      Note: this is the place where you can see that XPath array indices are one-based.

    4. Microsoft-Windows-Defrag events having the first EventData.Data node having the value “defragmentation” and the second second EventData.Data node having the value “(C:)” which allows filtering on the XML side with a specific drive letter:
      <QueryList>
        <Query Id="0" Path="Application">
          <Select Path="Application">
      *[System[Provider[@Name='Microsoft-Windows-Defrag'] and (Level=4 or Level=0) and (EventID=258)]]
      and
      *[EventData[Data[1]='defragmentation']]
      and
      *[EventData[Data[2]='(C:)']]
          </Select>
        </Query>
      </QueryList>

With that XML query syntax using XPath queries, I finally could assemble a PowerShell fragment that does what I want (adapting it to a parameterised function is as an exercise to the student <g>) using the -FilterXML parameter of the Get-WinEvent cmdlet:

 

 

 

--jeroen

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.