Private/EtwShare.ps1

#Requires -Version 5.1

$script:SMBeatEtwDataRoot = $null
$script:SMBeatEtwLastLost = [int64]0
$script:SMBeatEtwSessionName = 'SMBeat-SMBServer'

function Initialize-SMBeatEtwShareNative {
    if ('SMBeat.Native.EtwShare' -as [type]) {
        return
    }

    $code = @'
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
 
namespace SMBeat.Native
{
    public sealed class ShareDelta
    {
        public string Name;
        public long Sent;
        public long Received;
    }
 
    public sealed class EtwShareSnapshot
    {
        public long Count4;
        public long Count104;
        public long Count111;
        public long Count112;
        public long Count600;
        public long LengthReadTotal;
        public long LengthWrittenTotal;
        public long MappedTrees;
        public long UnknownTrees;
        public long LostEvents;
        public string LastError;
    }
 
    public static class EtwShare
    {
        public const string SessionName = "SMBeat-SMBServer";
        public static readonly Guid ProviderId = new Guid("D48CE617-33A2-4BC3-A5C7-11AA4F29619E");
 
        public static long LostEvents;
        public static string LastError = "";
        public static bool IsRunning;
 
        public static long Count4;
        public static long Count104;
        public static long Count111;
        public static long Count112;
        public static long Count600;
        public static long LengthReadTotal;
        public static long LengthWrittenTotal;
        public static long UnknownTrees;
 
        private const uint WNODE_FLAG_TRACED_GUID = 0x00020000;
        private const uint EVENT_TRACE_REAL_TIME_MODE = 0x00000100;
        private const uint PROCESS_TRACE_MODE_REAL_TIME = 0x00000100;
        private const uint PROCESS_TRACE_MODE_EVENT_RECORD = 0x10000000;
        private const uint EVENT_TRACE_CONTROL_QUERY = 0;
        private const uint EVENT_TRACE_CONTROL_STOP = 1;
        private const uint EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1;
        private const uint ENABLE_TRACE_PARAMETERS_VERSION_2 = 2;
        private const uint EVENT_FILTER_TYPE_EVENT_ID = 0x80000200;
        private const uint ERROR_SUCCESS = 0;
        private const uint ERROR_ALREADY_EXISTS = 183;
        private const int TRACE_LEVEL_VERBOSE = 5;
        private const ulong INVALID_TRACE = 0xFFFFFFFFFFFFFFFF;
 
        private static readonly object Gate = new object();
        private static readonly ConcurrentDictionary<string, Box> Totals = new ConcurrentDictionary<string, Box>(StringComparer.OrdinalIgnoreCase);
        private static readonly ConcurrentDictionary<string, string> TreeToShare = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private static readonly ConcurrentDictionary<string, string> PendingPath = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
        private static EventRecordCallback Callback;
        private static Thread Worker;
        private static ulong SessionHandle;
        private static ulong TraceHandle = INVALID_TRACE;
        private static IntPtr NamePath;
        private static IntPtr NameSessionId;
        private static IntPtr NameMessageId;
        private static IntPtr NameStatus;
        private static IntPtr NameTreeGuid;
        private static IntPtr NameLengthRead;
        private static IntPtr NameLengthWritten;
        private static IntPtr NameShareName;
        private static bool NamesReady;
 
        private sealed class Box
        {
            public long Sent;
            public long Recv;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct WNODE_HEADER
        {
            public uint BufferSize;
            public uint ProviderId;
            public ulong HistoricalContext;
            public ulong TimeStamp;
            public Guid Guid;
            public uint ClientContext;
            public uint Flags;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_TRACE_PROPERTIES
        {
            public WNODE_HEADER Wnode;
            public uint BufferSize;
            public uint MinimumBuffers;
            public uint MaximumBuffers;
            public uint MaximumFileSize;
            public uint LogFileMode;
            public uint FlushTimer;
            public uint EnableFlags;
            public int AgeLimit;
            public uint NumberOfBuffers;
            public uint FreeBuffers;
            public uint EventsLost;
            public uint BuffersWritten;
            public uint LogBuffersLost;
            public uint RealTimeBuffersLost;
            public IntPtr LoggerThreadId;
            public uint LogFileNameOffset;
            public uint LoggerNameOffset;
        }
 
        [StructLayout(LayoutKind.Sequential, Size = 0xac, CharSet = CharSet.Unicode)]
        private struct TIME_ZONE_INFORMATION
        {
            public uint bias;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string standardName;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 8)]
            public ushort[] standardDate;
            public uint standardBias;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string daylightName;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 8)]
            public ushort[] daylightDate;
            public uint daylightBias;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_TRACE_HEADER
        {
            public ushort Size;
            public ushort FieldTypeFlags;
            public byte Type;
            public byte Level;
            public ushort Version;
            public int ThreadId;
            public int ProcessId;
            public long TimeStamp;
            public Guid Guid;
            public uint KernelTime;
            public uint UserTime;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct ETW_BUFFER_CONTEXT
        {
            public byte ProcessorNumber;
            public byte Alignment;
            public ushort LoggerId;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_TRACE
        {
            public EVENT_TRACE_HEADER Header;
            public uint InstanceId;
            public uint ParentInstanceId;
            public Guid ParentGuid;
            public IntPtr MofData;
            public int MofLength;
            public ETW_BUFFER_CONTEXT BufferContext;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct TRACE_LOGFILE_HEADER
        {
            public uint BufferSize;
            public uint Version;
            public uint ProviderVersion;
            public uint NumberOfProcessors;
            public long EndTime;
            public uint TimerResolution;
            public uint MaximumFileSize;
            public uint LogFileMode;
            public uint BuffersWritten;
            public uint StartBuffers;
            public uint PointerSize;
            public uint EventsLost;
            public uint CpuSpeedInMHz;
            public IntPtr LoggerName;
            public IntPtr LogFileName;
            public TIME_ZONE_INFORMATION TimeZone;
            public long BootTime;
            public long PerfFreq;
            public long StartTime;
            public uint ReservedFlags;
            public uint BuffersLost;
        }
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate void EventRecordCallback(IntPtr eventRecord);
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct EVENT_TRACE_LOGFILEW
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string LogFileName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string LoggerName;
            public long CurrentTime;
            public uint BuffersRead;
            public uint LogFileMode;
            public EVENT_TRACE CurrentEvent;
            public TRACE_LOGFILE_HEADER LogfileHeader;
            public IntPtr BufferCallback;
            public int BufferSize;
            public int Filled;
            public int EventsLost;
            public EventRecordCallback EventCallback;
            public int IsKernelTrace;
            public IntPtr Context;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_HEADER
        {
            public ushort Size;
            public ushort HeaderType;
            public ushort Flags;
            public ushort EventProperty;
            public int ThreadId;
            public int ProcessId;
            public long TimeStamp;
            public Guid ProviderId;
            public ushort Id;
            public byte Version;
            public byte Channel;
            public byte Level;
            public byte Opcode;
            public ushort Task;
            public ulong Keyword;
            public uint KernelTime;
            public uint UserTime;
            public Guid ActivityId;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_RECORD
        {
            public EVENT_HEADER EventHeader;
            public ETW_BUFFER_CONTEXT BufferContext;
            public ushort ExtendedDataCount;
            public ushort UserDataLength;
            public IntPtr ExtendedData;
            public IntPtr UserData;
            public IntPtr UserContext;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct EVENT_FILTER_DESCRIPTOR
        {
            public ulong Ptr;
            public uint Size;
            public uint Type;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct ENABLE_TRACE_PARAMETERS
        {
            public uint Version;
            public uint EnableProperty;
            public uint ControlFlags;
            public Guid SourceId;
            public IntPtr EnableFilterDesc;
            public int FilterDescCount;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        private struct PROPERTY_DATA_DESCRIPTOR
        {
            public ulong PropertyName;
            public uint ArrayIndex;
            public uint Reserved;
        }
 
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        private static extern int StartTraceW(out ulong sessionHandle, string sessionName, IntPtr properties);
 
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        private static extern int ControlTraceW(ulong sessionHandle, string sessionName, IntPtr properties, uint controlCode);
 
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        private static extern int EnableTraceEx2(ulong traceHandle, ref Guid providerId, uint controlCode, byte level, ulong matchAnyKeyword, ulong matchAllKeyword, uint timeout, IntPtr enableParameters);
 
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern ulong OpenTraceW(ref EVENT_TRACE_LOGFILEW logfile);
 
        [DllImport("advapi32.dll")]
        private static extern int ProcessTrace(ulong[] handleArray, uint handleCount, IntPtr startTime, IntPtr endTime);
 
        [DllImport("advapi32.dll")]
        private static extern int CloseTrace(ulong traceHandle);
 
        [DllImport("tdh.dll")]
        private static extern uint TdhGetPropertySize(IntPtr pEvent, uint contextCount, IntPtr context, uint propertyCount, ref PROPERTY_DATA_DESCRIPTOR propertyData, out uint propertySize);
 
        [DllImport("tdh.dll")]
        private static extern uint TdhGetProperty(IntPtr pEvent, uint contextCount, IntPtr context, uint propertyCount, ref PROPERTY_DATA_DESCRIPTOR propertyData, uint bufferSize, byte[] buffer);
 
        public static int Start()
        {
            lock (Gate)
            {
                if (IsRunning)
                {
                    return 0;
                }
 
                EnsureNames();
                ResetCounters();
                LastError = "";
                StopLeftover();
 
                int err = StartSession();
                if (err != 0)
                {
                    LastError = "StartTrace " + err.ToString();
                    return err;
                }
 
                err = EnableProvider(true);
                if (err != 0)
                {
                    err = EnableProvider(false);
                }
                if (err != 0)
                {
                    LastError = "EnableTraceEx2 " + err.ToString();
                    StopLeftover();
                    return err;
                }
 
                Callback = OnEvent;
                EVENT_TRACE_LOGFILEW log = new EVENT_TRACE_LOGFILEW();
                log.LoggerName = SessionName;
                log.LogFileMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
                log.EventCallback = Callback;
                TraceHandle = OpenTraceW(ref log);
                if (TraceHandle == INVALID_TRACE || TraceHandle == 0)
                {
                    int openErr = Marshal.GetLastWin32Error();
                    LastError = "OpenTrace " + openErr.ToString();
                    StopLeftover();
                    return openErr == 0 ? 6 : openErr;
                }
 
                Worker = new Thread(ProcessLoop);
                Worker.IsBackground = true;
                Worker.Name = "SMBeat-ETW";
                Worker.Start();
                IsRunning = true;
                return 0;
            }
        }
 
        public static void Stop()
        {
            lock (Gate)
            {
                StopLeftover();
                IsRunning = false;
            }
 
            Thread w = Worker;
            if (w != null && w.IsAlive)
            {
                if (!w.Join(8000))
                {
                    LastError = "ProcessTrace join timeout";
                }
            }
            Worker = null;
 
            lock (Gate)
            {
                if (TraceHandle != INVALID_TRACE && TraceHandle != 0)
                {
                    try { CloseTrace(TraceHandle); }
                    catch { }
                    TraceHandle = INVALID_TRACE;
                }
            }
        }
 
        public static void StopLeftover()
        {
            IntPtr props = AllocProperties();
            try
            {
                ControlTraceW(0, SessionName, props, EVENT_TRACE_CONTROL_STOP);
            }
            catch
            {
            }
            finally
            {
                Marshal.FreeHGlobal(props);
            }
            SessionHandle = 0;
        }
 
        public static ShareDelta[] Flush()
        {
            QueryLost();
            List<ShareDelta> list = new List<ShareDelta>();
            foreach (KeyValuePair<string, Box> kv in Totals)
            {
                long sent = Interlocked.Exchange(ref kv.Value.Sent, 0);
                long recv = Interlocked.Exchange(ref kv.Value.Recv, 0);
                if (sent == 0 && recv == 0)
                {
                    continue;
                }
                ShareDelta row = new ShareDelta();
                row.Name = kv.Key;
                row.Sent = sent;
                row.Received = recv;
                list.Add(row);
            }
            return list.ToArray();
        }
 
        public static EtwShareSnapshot Snapshot()
        {
            QueryLost();
            EtwShareSnapshot s = new EtwShareSnapshot();
            s.Count4 = Interlocked.Read(ref Count4);
            s.Count104 = Interlocked.Read(ref Count104);
            s.Count111 = Interlocked.Read(ref Count111);
            s.Count112 = Interlocked.Read(ref Count112);
            s.Count600 = Interlocked.Read(ref Count600);
            s.LengthReadTotal = Interlocked.Read(ref LengthReadTotal);
            s.LengthWrittenTotal = Interlocked.Read(ref LengthWrittenTotal);
            s.MappedTrees = TreeToShare.Count;
            s.UnknownTrees = Interlocked.Read(ref UnknownTrees);
            s.LostEvents = Interlocked.Read(ref LostEvents);
            s.LastError = LastError;
            return s;
        }
 
        public static string NormalizeShareName(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return "(unknown)";
            }
            string n = name.Trim().Trim('"');
            if (n.Length == 0 || string.Equals(n, "(unknown)", StringComparison.OrdinalIgnoreCase))
            {
                return "(unknown)";
            }
            if (n.StartsWith("\\\\", StringComparison.Ordinal))
            {
                string rest = n.TrimStart('\\');
                string[] parts = rest.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length >= 2)
                {
                    return "\\\\*\\" + parts[1];
                }
                if (parts.Length == 1)
                {
                    return "\\\\*\\" + parts[0];
                }
            }
            int slash = n.LastIndexOf('\\');
            if (slash >= 0 && slash < n.Length - 1)
            {
                n = n.Substring(slash + 1);
            }
            if (n.Length == 0)
            {
                return "(unknown)";
            }
            return "\\\\*\\" + n;
        }
 
        private static void ProcessLoop()
        {
            try
            {
                ProcessTrace(new ulong[] { TraceHandle }, 1, IntPtr.Zero, IntPtr.Zero);
            }
            catch (Exception ex)
            {
                LastError = ex.Message;
            }
        }
 
        private static void OnEvent(IntPtr pRecord)
        {
            if (pRecord == IntPtr.Zero)
            {
                return;
            }
 
            ushort id = (ushort)Marshal.ReadInt16(pRecord, 0x28);
            try
            {
                if (id == 4)
                {
                    Interlocked.Increment(ref Count4);
                    HandleTreeRequest(pRecord);
                }
                else if (id == 104)
                {
                    Interlocked.Increment(ref Count104);
                    HandleTreeResponse(pRecord);
                }
                else if (id == 111)
                {
                    Interlocked.Increment(ref Count111);
                    uint len = 0;
                    if (TryGetUInt32(pRecord, NameLengthRead, out len) && len > 0)
                    {
                        Interlocked.Add(ref LengthReadTotal, len);
                        AddBytes(ResolveShare(pRecord), len, 0);
                    }
                }
                else if (id == 112)
                {
                    Interlocked.Increment(ref Count112);
                    uint len = 0;
                    if (TryGetUInt32(pRecord, NameLengthWritten, out len) && len > 0)
                    {
                        Interlocked.Add(ref LengthWrittenTotal, len);
                        AddBytes(ResolveShare(pRecord), 0, len);
                    }
                }
                else if (id == 600)
                {
                    Interlocked.Increment(ref Count600);
                    HandleTreeAllocated(pRecord);
                }
            }
            catch (Exception ex)
            {
                LastError = ex.Message;
            }
        }
 
        private static void HandleTreeRequest(IntPtr pRecord)
        {
            ulong sessionId;
            ulong messageId;
            string path;
            if (!TryGetUInt64(pRecord, NameSessionId, out sessionId))
            {
                return;
            }
            if (!TryGetUInt64(pRecord, NameMessageId, out messageId))
            {
                return;
            }
            if (!TryGetString(pRecord, NamePath, out path))
            {
                return;
            }
            if (PendingPath.Count > 4096)
            {
                PendingPath.Clear();
            }
            PendingPath[sessionId.ToString("x") + ":" + messageId.ToString("x")] = path;
        }
 
        private static void HandleTreeResponse(IntPtr pRecord)
        {
            uint status = 1;
            TryGetUInt32(pRecord, NameStatus, out status);
            if (status != 0)
            {
                return;
            }
            Guid tree;
            if (!TryGetGuid(pRecord, NameTreeGuid, out tree))
            {
                return;
            }
            ulong sessionId;
            ulong messageId;
            string path = null;
            if (TryGetUInt64(pRecord, NameSessionId, out sessionId) && TryGetUInt64(pRecord, NameMessageId, out messageId))
            {
                string key = sessionId.ToString("x") + ":" + messageId.ToString("x");
                PendingPath.TryRemove(key, out path);
            }
            if (!string.IsNullOrEmpty(path))
            {
                TreeToShare[tree.ToString("D")] = NormalizeShareName(path);
            }
        }
 
        private static void HandleTreeAllocated(IntPtr pRecord)
        {
            Guid tree;
            string share;
            if (!TryGetGuid(pRecord, NameTreeGuid, out tree))
            {
                return;
            }
            if (!TryGetString(pRecord, NameShareName, out share))
            {
                return;
            }
            TreeToShare[tree.ToString("D")] = NormalizeShareName(share);
        }
 
        private static string ResolveShare(IntPtr pRecord)
        {
            Guid tree;
            if (TryGetGuid(pRecord, NameTreeGuid, out tree))
            {
                string name;
                if (TreeToShare.TryGetValue(tree.ToString("D"), out name) && !string.IsNullOrEmpty(name))
                {
                    return name;
                }
            }
            Interlocked.Increment(ref UnknownTrees);
            return "(unknown)";
        }
 
        private static void AddBytes(string share, long sent, long recv)
        {
            Box box = Totals.GetOrAdd(share, delegate { return new Box(); });
            if (sent != 0)
            {
                Interlocked.Add(ref box.Sent, sent);
            }
            if (recv != 0)
            {
                Interlocked.Add(ref box.Recv, recv);
            }
        }
 
        private static PROPERTY_DATA_DESCRIPTOR Desc(IntPtr name)
        {
            PROPERTY_DATA_DESCRIPTOR d = new PROPERTY_DATA_DESCRIPTOR();
            d.PropertyName = (ulong)name.ToInt64();
            d.ArrayIndex = 0xFFFFFFFF;
            return d;
        }
 
        private static bool TryGetUInt32(IntPtr pEvent, IntPtr name, out uint value)
        {
            value = 0;
            byte[] buf;
            if (!TryGetBytes(pEvent, name, out buf) || buf.Length < 4)
            {
                return false;
            }
            value = BitConverter.ToUInt32(buf, 0);
            return true;
        }
 
        private static bool TryGetUInt64(IntPtr pEvent, IntPtr name, out ulong value)
        {
            value = 0;
            byte[] buf;
            if (!TryGetBytes(pEvent, name, out buf) || buf.Length < 8)
            {
                return false;
            }
            value = BitConverter.ToUInt64(buf, 0);
            return true;
        }
 
        private static bool TryGetGuid(IntPtr pEvent, IntPtr name, out Guid value)
        {
            value = Guid.Empty;
            byte[] buf;
            if (!TryGetBytes(pEvent, name, out buf) || buf.Length < 16)
            {
                return false;
            }
            byte[] g = new byte[16];
            Buffer.BlockCopy(buf, 0, g, 0, 16);
            value = new Guid(g);
            return true;
        }
 
        private static bool TryGetString(IntPtr pEvent, IntPtr name, out string value)
        {
            value = null;
            byte[] buf;
            if (!TryGetBytes(pEvent, name, out buf) || buf.Length < 2)
            {
                return false;
            }
            value = Encoding.Unicode.GetString(buf).TrimEnd('\0');
            return !string.IsNullOrEmpty(value);
        }
 
        private static bool TryGetBytes(IntPtr pEvent, IntPtr name, out byte[] buffer)
        {
            buffer = null;
            PROPERTY_DATA_DESCRIPTOR d = Desc(name);
            uint size;
            uint st = TdhGetPropertySize(pEvent, 0, IntPtr.Zero, 1, ref d, out size);
            if (st != 0 || size == 0 || size > 65536)
            {
                return false;
            }
            buffer = new byte[size];
            st = TdhGetProperty(pEvent, 0, IntPtr.Zero, 1, ref d, size, buffer);
            return st == 0;
        }
 
        private static int StartSession()
        {
            IntPtr props = AllocProperties();
            try
            {
                int err = StartTraceW(out SessionHandle, SessionName, props);
                if ((uint)err == ERROR_ALREADY_EXISTS)
                {
                    StopLeftover();
                    Marshal.FreeHGlobal(props);
                    props = AllocProperties();
                    err = StartTraceW(out SessionHandle, SessionName, props);
                }
                return err;
            }
            finally
            {
                Marshal.FreeHGlobal(props);
            }
        }
 
        private static int EnableProvider(bool withFilter)
        {
            Guid id = ProviderId;
            IntPtr filterPayload = IntPtr.Zero;
            IntPtr filterDesc = IntPtr.Zero;
            IntPtr paramPtr = IntPtr.Zero;
            try
            {
                if (withFilter)
                {
                    ushort[] ids = new ushort[] { 4, 104, 111, 112, 600 };
                    int payloadSize = 4 + (2 * ids.Length);
                    filterPayload = Marshal.AllocHGlobal(payloadSize);
                    Marshal.WriteByte(filterPayload, 0, 1);
                    Marshal.WriteByte(filterPayload, 1, 0);
                    Marshal.WriteInt16(filterPayload, 2, (short)ids.Length);
                    for (int i = 0; i < ids.Length; i++)
                    {
                        Marshal.WriteInt16(filterPayload, 4 + (i * 2), (short)ids[i]);
                    }
 
                    EVENT_FILTER_DESCRIPTOR desc = new EVENT_FILTER_DESCRIPTOR();
                    desc.Ptr = (ulong)filterPayload.ToInt64();
                    desc.Size = (uint)payloadSize;
                    desc.Type = EVENT_FILTER_TYPE_EVENT_ID;
                    filterDesc = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(EVENT_FILTER_DESCRIPTOR)));
                    Marshal.StructureToPtr(desc, filterDesc, false);
 
                    ENABLE_TRACE_PARAMETERS p = new ENABLE_TRACE_PARAMETERS();
                    p.Version = ENABLE_TRACE_PARAMETERS_VERSION_2;
                    p.EnableFilterDesc = filterDesc;
                    p.FilterDescCount = 1;
                    paramPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ENABLE_TRACE_PARAMETERS)));
                    Marshal.StructureToPtr(p, paramPtr, false);
                }
 
                return EnableTraceEx2(
                    SessionHandle,
                    ref id,
                    EVENT_CONTROL_CODE_ENABLE_PROVIDER,
                    (byte)TRACE_LEVEL_VERBOSE,
                    ulong.MaxValue,
                    0,
                    0,
                    paramPtr);
            }
            finally
            {
                if (paramPtr != IntPtr.Zero) { Marshal.FreeHGlobal(paramPtr); }
                if (filterDesc != IntPtr.Zero) { Marshal.FreeHGlobal(filterDesc); }
                if (filterPayload != IntPtr.Zero) { Marshal.FreeHGlobal(filterPayload); }
            }
        }
 
        private static IntPtr AllocProperties()
        {
            int header = Marshal.SizeOf(typeof(EVENT_TRACE_PROPERTIES));
            int total = header + 2048;
            IntPtr ptr = Marshal.AllocHGlobal(total);
            byte[] zero = new byte[total];
            Marshal.Copy(zero, 0, ptr, total);
 
            EVENT_TRACE_PROPERTIES p = new EVENT_TRACE_PROPERTIES();
            p.Wnode.BufferSize = (uint)total;
            p.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
            p.Wnode.ClientContext = 1;
            p.BufferSize = 64;
            p.MinimumBuffers = 16;
            p.MaximumBuffers = 128;
            p.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
            p.LoggerNameOffset = (uint)header;
            Marshal.StructureToPtr(p, ptr, false);
 
            byte[] name = Encoding.Unicode.GetBytes(SessionName + "\0");
            Marshal.Copy(name, 0, IntPtr.Add(ptr, header), name.Length);
            return ptr;
        }
 
        private static void QueryLost()
        {
            IntPtr props = AllocProperties();
            try
            {
                int err = ControlTraceW(SessionHandle, SessionName, props, EVENT_TRACE_CONTROL_QUERY);
                if (err == 0)
                {
                    EVENT_TRACE_PROPERTIES p = (EVENT_TRACE_PROPERTIES)Marshal.PtrToStructure(props, typeof(EVENT_TRACE_PROPERTIES));
                    Interlocked.Exchange(ref LostEvents, p.EventsLost + p.RealTimeBuffersLost);
                }
            }
            catch
            {
            }
            finally
            {
                Marshal.FreeHGlobal(props);
            }
        }
 
        private static void ResetCounters()
        {
            Totals.Clear();
            TreeToShare.Clear();
            PendingPath.Clear();
            Interlocked.Exchange(ref Count4, 0);
            Interlocked.Exchange(ref Count104, 0);
            Interlocked.Exchange(ref Count111, 0);
            Interlocked.Exchange(ref Count112, 0);
            Interlocked.Exchange(ref Count600, 0);
            Interlocked.Exchange(ref LengthReadTotal, 0);
            Interlocked.Exchange(ref LengthWrittenTotal, 0);
            Interlocked.Exchange(ref UnknownTrees, 0);
            Interlocked.Exchange(ref LostEvents, 0);
        }
 
        private static void EnsureNames()
        {
            if (NamesReady)
            {
                return;
            }
            NamePath = Marshal.StringToHGlobalUni("Path");
            NameSessionId = Marshal.StringToHGlobalUni("SessionId");
            NameMessageId = Marshal.StringToHGlobalUni("MessageId");
            NameStatus = Marshal.StringToHGlobalUni("Status");
            NameTreeGuid = Marshal.StringToHGlobalUni("TreeConnectGUID");
            NameLengthRead = Marshal.StringToHGlobalUni("LengthRead");
            NameLengthWritten = Marshal.StringToHGlobalUni("LengthWritten");
            NameShareName = Marshal.StringToHGlobalUni("ShareName");
            NamesReady = true;
        }
    }
}
'@


    Add-Type -TypeDefinition $code -Language CSharp -ErrorAction Stop
}

function Get-SMBeatEtwShareNormalizedName {
    param(
        [string]$Name
    )

    if ([string]::IsNullOrWhiteSpace($Name)) {
        return '(unknown)'
    }

    $n = $Name.Trim().Trim('"')
    if ($n -eq '(unknown)') {
        return '(unknown)'
    }

    if ($n.StartsWith('\\')) {
        $rest = $n.TrimStart('\')
        $parts = @($rest.Split('\') | Where-Object { $_ })
        if ($parts.Count -ge 2) {
            return ('\\*\{0}' -f $parts[1])
        }
        if ($parts.Count -eq 1) {
            return ('\\*\{0}' -f $parts[0])
        }
    }

    $leaf = Get-SMBeatInstanceLeafName -Name $n
    if ([string]::IsNullOrWhiteSpace($leaf)) {
        return '(unknown)'
    }

    return ('\\*\{0}' -f $leaf)
}

function ConvertTo-SMBeatEtwShareFlushRecords {
    param(
        [AllowEmptyCollection()]
        [object[]]$Deltas,
        [Parameter(Mandatory = $true)]
        [string]$Server,
        [Parameter(Mandatory = $true)]
        [datetime]$Utc,
        [Parameter(Mandatory = $true)]
        [int]$IntervalSec,
        [switch]$IncludeAdminShares
    )

    $records = New-Object System.Collections.Generic.List[object]
    if ($null -eq $Deltas) {
        $Deltas = @()
    }

    foreach ($row in $Deltas) {
        $name = Get-SMBeatEtwShareNormalizedName -Name ([string]$row.Name)
        if (Test-SMBeatSkipInstance -Name $name -Kind 'share' -IncludeAdminShares:$IncludeAdminShares) {
            continue
        }

        $sent = [int64]0
        $recv = [int64]0
        if ($row.Sent) { $sent = [int64]$row.Sent }
        if ($row.Received) { $recv = [int64]$row.Received }
        if ($sent -eq 0 -and $recv -eq 0) {
            continue
        }

        $snap = New-SMBeatCounterSnapshot -Kind 'share' -Name $name `
            -ReadRaw 0 -WriteRaw 0 -SentRaw 0 -ReceivedRaw 0
        $records.Add((ConvertTo-SMBeatSampleRecord -Snapshot $snap -Server $Server -Utc $Utc -IntervalSec $IntervalSec `
                -ReadDelta 0 -WriteDelta 0 -SentDelta $sent -ReceivedDelta $recv)) | Out-Null
    }

    return $records.ToArray()
}

function Start-SMBeatEtwShare {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DataRoot
    )

    $script:SMBeatEtwDataRoot = $DataRoot
    $script:SMBeatEtwLastLost = 0

    try {
        Initialize-SMBeatEtwShareNative
    }
    catch {
        Write-SMBeatLog -DataRoot $DataRoot -Message ('ETW native compile failed: {0}' -f $_.Exception.Message)
        return $false
    }

    $err = [SMBeat.Native.EtwShare]::Start()
    if ($err -ne 0) {
        $detail = [SMBeat.Native.EtwShare]::LastError
        Write-SMBeatLog -DataRoot $DataRoot -Message ('ETW SMBServer start failed ({0}): {1}' -f $err, $detail)
        return $false
    }

    Write-SMBeatLog -DataRoot $DataRoot -Message 'ETW SMBServer session SMBeat-SMBServer started.'
    return $true
}

function Stop-SMBeatEtwShare {
    if (-not ('SMBeat.Native.EtwShare' -as [type])) {
        Stop-SMBeatEtwShareLeftover
        return
    }

    try {
        [SMBeat.Native.EtwShare]::Stop()
    }
    catch {
        Stop-SMBeatEtwShareLeftover
    }
}

function Stop-SMBeatEtwShareLeftover {
    if ('SMBeat.Native.EtwShare' -as [type]) {
        try {
            [SMBeat.Native.EtwShare]::StopLeftover()
        }
        catch {
        }
    }

    try {
        & logman.exe stop $script:SMBeatEtwSessionName -ets 2>$null | Out-Null
    }
    catch {
    }
}

function Test-SMBeatEtwShareRunning {
    if (-not ('SMBeat.Native.EtwShare' -as [type])) {
        return $false
    }

    return [bool][SMBeat.Native.EtwShare]::IsRunning
}

function Get-SMBeatCollectorMode {
    if (Test-SMBeatEtwShareRunning) {
        return 'tcp445+etw'
    }

    return 'tcp445'
}

function Get-SMBeatEtwSharePendingDeltas {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Server,
        [Parameter(Mandatory = $true)]
        [datetime]$Utc,
        [Parameter(Mandatory = $true)]
        [int]$IntervalSec,
        [switch]$IncludeAdminShares
    )

    $native = @()
    if (('SMBeat.Native.EtwShare' -as [type]) -and [SMBeat.Native.EtwShare]::IsRunning) {
        $native = @([SMBeat.Native.EtwShare]::Flush())
    }

    ConvertTo-SMBeatEtwShareFlushRecords -Deltas $native -Server $Server -Utc $Utc -IntervalSec $IntervalSec -IncludeAdminShares:$IncludeAdminShares
}

function Update-SMBeatEtwShareHook {
    if (-not $script:SMBeatEtwDataRoot) {
        return
    }
    if (-not (Test-SMBeatEtwShareRunning)) {
        return
    }

    $lost = [int64][SMBeat.Native.EtwShare]::LostEvents
    if ($lost -gt $script:SMBeatEtwLastLost) {
        Write-SMBeatLog -DataRoot $script:SMBeatEtwDataRoot -Message ('ETW SMBServer lost events: {0}' -f $lost)
        $script:SMBeatEtwLastLost = $lost
    }
}