Convert NetSh Trace ETL to PCAP with PowerShell

In-built packet capture in Windows 7 / Server 2008 R2 and later operating systems is incredibly useful tool with netsh trace. Unfortunately large ETL files are super slow to load in Microsoft Message Analyzer, and for many network analysis scenarios I prefer Wireshark’s layout. While Message Analyzer can open the ETL files and convert them to suitable format for Wireshark it is quite a bit of waiting, I just want to open the logs.

Here is a script I wrote to transform the .ETL file into a .PCAP file which can be opened in Wireshark and network packets inspected.

Currently I have only tested this with .ETL files generated from Windows 10.

In this case I had to use inline C# as using native PowerShell was far too slow.

Run this script then you can perform conversions with this command:

./ConvertEtl-ToPcap.ps1 -Path C:\support\NetTrace.etl -Destination C:\support\NetTrace.pcap

A version in C# for Visual Studio is available here https://github.com/chentiangemalc/EtlToCap

The compiled version of that is located here https://github.com/chentiangemalc/EtlToCap/raw/master/EtlToCap/bin/Debug/EtlToCap.exe

Utilise compiled version:
EtlToCap NetTrace.Etl NetTrace.pcap

Script is available here on GitHub https://github.com/chentiangemalc/PowerShellScripts/blob/master/ConvertEtl-ToPcap.ps1

[CmdletBinding()]
param(
[Parameter(Position=0)]
[ValidateScript({
    if( -Not ($_ | Test-Path) ){
        throw "File or folder $_ does not exist"
    }

    if($_.Extension -ne ".etl"){
        throw "Source file must be .etl file"
    }
    return $true
})]
[System.IO.FileInfo]$Path,

[Parameter(Position=1)]
[ValidateScript({
    if( -Not ($path.DirectoryName | Test-Path) ){
        throw "File or folder does not exist"
    }

    if($_.Extension -ne ".pcap") {
        throw "Estination file must be .pcap file"
    }
    return $true
})]
[System.IO.FileInfo]$Destination,

[Parameter(Position=2)]
[Uint32]$MaxPacketSizeBytes = 65536)


$csharp_code = @'
using System;
using System.Collections.Generic;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace chentiangemalc
{
    public static class NetworkRoutines
    {
    public static long ConvertEtlToPcap(string source, string destination, UInt32 maxPacketSize)
        {
            int result = 0;
            using (BinaryWriter writer = new BinaryWriter(File.Open(destination, FileMode.Create)))
            {

                UInt32 magic_number = 0xa1b2c3d4;
                UInt16 version_major = 2;
                UInt16 version_minor = 4;
                Int32 thiszone = 0;
                UInt32 sigfigs = 0;
                UInt32 snaplen = maxPacketSize;
                UInt32 network = 1; // LINKTYPE_ETHERNET

                writer.Write(magic_number);
                writer.Write(version_major);
                writer.Write(version_minor);
                writer.Write(thiszone);
                writer.Write(sigfigs);
                writer.Write(snaplen);
                writer.Write(network);

                long c = 0;
                long t = 0;
                using (var reader = new EventLogReader(source, PathType.FilePath))
                {
                    EventRecord record;
                    while ((record = reader.ReadEvent()) != null)
                    {
                        c++;
                        t++;
                        if (c == 10000)
                        {
                            Console.WriteLine(String.Format("Processed {0} events with {1} packets processed",t,result));
                            c = 0;
                        }
                        using (record)
                        {
                            if (record.ProviderName == "Microsoft-Windows-NDIS-PacketCapture")
                            {
                                result++;
                                DateTime timeCreated = (DateTime)record.TimeCreated;
                                UInt32 ts_sec = (UInt32)((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
                                UInt32 ts_usec = (UInt32)(((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds) - ((UInt32)((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalSeconds * 1000))) * 1000;
                                UInt32 incl_len = (UInt32)record.Properties[2].Value;
                                if (incl_len > maxPacketSize)
                                {
                                   Console.WriteLine(String.Format("Packet size of {0} exceeded max packet size {1}, packet ignored",incl_len,maxPacketSize));
                                }
                                UInt32 orig_len = incl_len;

                                writer.Write(ts_sec);
                                writer.Write(ts_usec);
                                writer.Write(incl_len);
                                writer.Write(orig_len);
                                writer.Write((byte[])record.Properties[3].Value);

                            }
                        }
                    }
                }
            }
            return result;
        }
    }
}
'@

Add-Type -Type $csharp_code

$result = [chentiangemalc.NetworkRoutines]::ConvertEtlToPcap($Path.FullName,$Destination.FullName,$MaxPacketSizeBytes)

Write-Host "$result packets converted."

Warning: IF you are a regular NetSh Trace user please remember to store log files in a path outside the user profile, or you may get numbers such as .000 appended to your user profile on Windows in-place upgrades.

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in Uncategorized. Bookmark the permalink.

19 Responses to Convert NetSh Trace ETL to PCAP with PowerShell

  1. ron says:

    Hi, do not work for me , I try etl from win10, win 2012, none are correctly convert, do you need the Microsoft Message Analyzer install anyway just as with powershell comand to convert the trace ?

    • provide me a sample trace and i’ll look into it. it worked with the trace i took, but I didn’t have any doco on how the format work, so maybe i am not handling some scenarios.

  2. Trentent says:

    Hey Ron,

    I found that netsh trace start capture=yes fails to initialize the NDIS provider. Using Powershell did work and it actually captured information. I think the fault lies with Microsoft here,

  3. Kirt Carson says:

    Works great. Just had to move up two lines of code against two commas in above code. Now I can run the netsh command on production servers to capture more detail (individual packets) in native ETL and convert it to pcap for wireshark use.

    • C. Starcher says:

      What,exactly,had to be changed into the code above to get this to work?

      • kkrt carson says:

        two spots where the line ended with a comma and the following line needed to be moved up next to the comma.

        Also $Destination is a keyword so in your example it don’t work. Change to $Dest.

        I just looked at it and it seems different.

        great scrjpt. thanks for postung it

  4. Daniel says:

    hello, I am getting a Error when trying to run this. The exception being thrown is

    Exception : System.Management.Automation.MethodInvocationException: Exception calling “ConvertEtlToPcap” with “3” argument(s): “Specified cast is not valid.” —>
    System.InvalidCastException: Specified cast is not valid.
    at chentiangemalc.NetworkRoutines.ConvertEtlToPcap(String source, String destination, UInt32 maxPacketSize)
    at CallSite.Target(Closure , CallSite , Type , Object , Object , UInt32 )
    — End of inner exception stack trace —
    at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception exception, Type typeToThrow, String methodName, Int32 numArgs,
    MemberInfo memberInfo)
    at CallSite.Target(Closure , CallSite , Type , Object , Object , UInt32 )
    at System.Dynamic.UpdateDelegates.UpdateAndExecute4[T0,T1,T2,T3,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
    at System.Management.Automation.Interpreter.DynamicInstruction`5.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    TargetObject :
    CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    FullyQualifiedErrorId : InvalidCastException
    ErrorDetails :
    InvocationInfo : System.Management.Automation.InvocationInfo

    Let me know if you need more information.

    • Siddharth Shettigar says:

      I am running into this same error message. Any ideas what might be the issue here?

      Exception calling “ConvertEtlToPcap” with “3” argument(s): “Specified cast is not valid.”
      At C:\Users\sid\stuff\CaseNotes\reproduce error\ConvertEtl-ToPcap.ps1:116 char:1
      + $result = [chentiangemalc.NetworkRoutines]::ConvertEtlToPcap($Path.Fu …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
      + FullyQualifiedErrorId : InvalidCastException

      packets converted.

      • kirt carson says:

        i got a question about a CAST error.
        i think the file can’t be filename but instead a file object. So $myfile = LS Myfile.etl instead of a string C:\windows\temp\Myfile.etl

      • kirt carson says:

        $destination is a key word. try $myfile instead

  5. VG says:

    Converting File test.etl to test.pcap

    Unhandled Exception: System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: index
    at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
    at System.Collections.Generic.List`1.get_Item(Int32 index)
    at EtlToCap.NetworkRoutines.ConvertEtlToPcap(String source, String destination, UInt32 maxPacketSize)
    at EtlToCap.NetworkRoutines.Main(String[] args)

  6. Siddharth Shettigar says:

    Thanks @Kirt!

  7. Kirt D Carson says:

    I keep getting emails to post complete script. i will but first I will provide some notes.
    SourceFile needs to be fullpath. DestFile is a rename of SourceFile. Change as you wish.

    I may have made changes for my use but if you compare the two the couple of things I changed will stand out. Not sure if the note above and $destination being a reserved variable was all that is needed to get it working as originally posted.

    [CmdletBinding()]
    param(
    [Parameter(Position=0)]
    [ValidateScript({
    if( -Not ($_ | Test-Path) ){
    throw “File or folder $_ does not exist”
    }

    if($_.Extension -ne “.etl”){
    throw “Source file must be .etl file”
    }
    return $true
    })]
    [System.IO.FileInfo]$Path,[Parameter(Position=1)]
    [ValidateScript({
    if( -Not ($path.DirectoryName | Test-Path) ){
    throw “File or folder does not exist”
    }

    if($_.Extension -ne “.pcap”) {
    throw “Estination file must be .pcap file”
    }
    return $true
    })]
    [System.IO.FileInfo]$Destination,[Parameter(Position=2)]
    [Uint32]$MaxPacketSizeBytes = 65536)

    $csharp_code = @’
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Eventing.Reader;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace chentiangemalc
    {
    public static class NetworkRoutines
    {
    public static long ConvertEtlToPcap(string source, string destination, UInt32 maxPacketSize)
    {
    int result = 0;
    using (BinaryWriter writer = new BinaryWriter(File.Open(destination, FileMode.Create)))
    {

    UInt32 magic_number = 0xa1b2c3d4;
    UInt16 version_major = 2;
    UInt16 version_minor = 4;
    Int32 thiszone = 0;
    UInt32 sigfigs = 0;
    UInt32 snaplen = maxPacketSize;
    UInt32 network = 1; // LINKTYPE_ETHERNET

    writer.Write(magic_number);
    writer.Write(version_major);
    writer.Write(version_minor);
    writer.Write(thiszone);
    writer.Write(sigfigs);
    writer.Write(snaplen);
    writer.Write(network);

    long c = 0;
    long t = 0;
    using (var reader = new EventLogReader(source, PathType.FilePath))
    {
    EventRecord record;
    while ((record = reader.ReadEvent()) != null)
    {
    c++;
    t++;
    if (c == 10000)
    {
    Console.WriteLine(String.Format(“Processed {0} events with {1} packets processed”,t,result));
    c = 0;
    }
    using (record)
    {
    if (record.ProviderName == “Microsoft-Windows-NDIS-PacketCapture”)
    {
    result++;
    DateTime timeCreated = (DateTime)record.TimeCreated;
    UInt32 ts_sec = (UInt32)((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
    UInt32 ts_usec = (UInt32)(((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds) – ((UInt32)((timeCreated.Subtract(new DateTime(1970, 1, 1))).TotalSeconds * 1000))) * 1000;
    UInt32 incl_len = (UInt32)record.Properties[2].Value;
    if (incl_len > maxPacketSize)
    {
    Console.WriteLine(String.Format(“Packet size of {0} exceeded max packet size {1}, packet ignored”,incl_len,maxPacketSize));
    }
    UInt32 orig_len = incl_len;

    writer.Write(ts_sec);
    writer.Write(ts_usec);
    writer.Write(incl_len);
    writer.Write(orig_len);
    writer.Write((byte[])record.Properties[3].Value);

    }
    }
    }
    }
    }
    return result;
    }
    }
    }
    ‘@

    $SourceFile = “C:\Windows\Temp\Net.etl”
    $destFile =$SourceFile.replace(‘.etl’,’.pcap’)
    Add-Type -Type $csharp_code
    $MaxPacketSizeBytes=64kb
    $result = [chentiangemalc.NetworkRoutines]::ConvertEtlToPcap($SourceFile,$DestFile,$MaxPacketSizeBytes)

    Write-Host “$result packets converted to $destFile”
    read-host “Press any key to exit`n”

    #[chentiangemalc.NetworkRoutines]::ConvertEtlToPcap(“C:\Windows\Temp\Net.etl”,”C:\Windows\Temp\Net.pcap”,64kb)

    I start using:
    netsh trace start traceFile=c:\windows\temp\net.etl capture=yes overwrite=yes correlation=no report=no

    Wait and the NetSh Trace Stop

    Thanks again to the author for this awesome tool.

  8. Arthur says:

    I am getting

    Exception calling “ConvertEtlToPcap” with “3” argument(s): “Unable to cast object of type ‘System.UInt16’ to type ‘System.Byte[]’.”
    At C:\Scripts\EtlToPcap2.ps1:112 char:1
    + $result = [chentiangemalc.NetworkRoutines]::ConvertEtlToPcap($SourceF …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : InvalidCastException

    • Jonas says:

      it is because he have added the 3 variable $SourceFile, $destFile and $MaxPacketSizeBytes inside the funtion right before the command [chentiangemalc.NetworkRoutines]::ConvertEtlToPcap
      And when used them instead of the variable $Path, $Destination which is the name of the variable from the paremeter.

      So if you change $SourceFile to $Path and $destFile to $Destination indside the command and delete the 3 variable right before the command, it will work.

  9. Elijah says:

    The cast error seems to happen because there are multiple types of “Microsoft-Windows-NDIS-PacketCapture” event Ids, and they have different Properties collections. If you get one where “record.Properties[2].value” can’t be cast to UInt32, you get the crash. My quick and dirty fix was limiting packets to Event ID 1001 (if (record.ProviderName == “Microsoft-Windows-NDIS-PacketCapture”&& record.Id == 1001)

    This works, but I’ve got no idea what I’m filtering out, so makes it unreliable.

  10. sebus says:

    Pity, had potential, but is rather useless in current form, does not work

Leave a comment