Private/EtwShare.ps1
|
#Requires -Version 5.1 $script:SMBeatEtwDataRoot = $null $script:SMBeatEtwLastLost = [int64]0 $script:SMBeatEtwSessionName = 'SMBeat-SMBServer' $script:SMBeatSmbSessionUserIps = @{} 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.Net; 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 Count552; public long Count500; public long Count553; public long LengthReadTotal; public long LengthWrittenTotal; public long MappedTrees; public long MappedSessions; public long MappedConnections; public long UnknownTrees; public long UnknownSessions; public long UnknownClients; 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 Count552; public static long Count500; public static long Count553; public static long LengthReadTotal; public static long LengthWrittenTotal; public static long UnknownTrees; public static long UnknownSessions; public static long UnknownClients; 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, Box> UserTotals = new ConcurrentDictionary<string, Box>(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary<string, Box> ClientTotals = 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> SessionToUser = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary<string, string> ConnectionToIp = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary<string, string> SessionToConnection = 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 IntPtr NameSessionGuid; private static IntPtr NameUserName; private static IntPtr NameDomainName; private static IntPtr NameConnectionGuid; private static IntPtr NameAddress; private static IntPtr NameClientAddress; 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 ShareDelta[] FlushUsers() { List<ShareDelta> list = new List<ShareDelta>(); foreach (KeyValuePair<string, Box> kv in UserTotals) { 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 ShareDelta[] FlushClients() { List<ShareDelta> list = new List<ShareDelta>(); foreach (KeyValuePair<string, Box> kv in ClientTotals) { 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.Count552 = Interlocked.Read(ref Count552); s.Count500 = Interlocked.Read(ref Count500); s.Count553 = Interlocked.Read(ref Count553); s.LengthReadTotal = Interlocked.Read(ref LengthReadTotal); s.LengthWrittenTotal = Interlocked.Read(ref LengthWrittenTotal); s.MappedTrees = TreeToShare.Count; s.MappedSessions = SessionToUser.Count; s.MappedConnections = ConnectionToIp.Count; s.UnknownTrees = Interlocked.Read(ref UnknownTrees); s.UnknownSessions = Interlocked.Read(ref UnknownSessions); s.UnknownClients = Interlocked.Read(ref UnknownClients); 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); AddUserBytes(ResolveUser(pRecord), len, 0); AddClientBytes(ResolveClient(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); AddUserBytes(ResolveUser(pRecord), 0, len); AddClientBytes(ResolveClient(pRecord), 0, len); } } else if (id == 600) { Interlocked.Increment(ref Count600); HandleTreeAllocated(pRecord); } else if (id == 552) { Interlocked.Increment(ref Count552); HandleSessionAuth(pRecord); } else if (id == 500) { Interlocked.Increment(ref Count500); HandleConnectionAccept(pRecord); } else if (id == 553) { Interlocked.Increment(ref Count553); HandleSessionBind(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 void HandleSessionAuth(IntPtr pRecord) { Guid session; if (!TryGetGuid(pRecord, NameSessionGuid, out session)) { return; } string user; string domain; TryGetString(pRecord, NameUserName, out user); TryGetString(pRecord, NameDomainName, out domain); string name = NormalizeUserName(domain, user); if (name != "(unknown)") { SessionToUser[session.ToString("D")] = name; } Guid conn; if (TryGetGuid(pRecord, NameConnectionGuid, out conn)) { SessionToConnection[session.ToString("D")] = conn.ToString("D"); RememberClientAddress(pRecord, conn); } } private static void HandleConnectionAccept(IntPtr pRecord) { Guid conn; if (!TryGetGuid(pRecord, NameConnectionGuid, out conn)) { return; } RememberClientAddress(pRecord, conn); } private static void RememberClientAddress(IntPtr pRecord, Guid conn) { string ip; if (!TryReadAddress(pRecord, out ip)) { return; } ConnectionToIp[conn.ToString("D")] = ip; } private static bool TryReadAddress(IntPtr pRecord, out string ip) { ip = null; byte[] raw; if (!TryGetBytes(pRecord, NameAddress, out raw)) { TryGetBytes(pRecord, NameClientAddress, out raw); } if (raw != null) { return TryFormatSockAddr(raw, out ip); } string text; if (TryGetString(pRecord, NameAddress, out text) || TryGetString(pRecord, NameClientAddress, out text)) { ip = text.Trim().Trim('"'); if (ip.StartsWith("\\")) { ip = ip.TrimStart('\\'); } return !string.IsNullOrEmpty(ip); } return false; } private static void HandleSessionBind(IntPtr pRecord) { Guid session; Guid conn; if (!TryGetGuid(pRecord, NameSessionGuid, out session)) { return; } if (!TryGetGuid(pRecord, NameConnectionGuid, out conn)) { return; } SessionToConnection[session.ToString("D")] = conn.ToString("D"); } private static bool TryFormatSockAddr(byte[] raw, out string ip) { ip = null; if (raw == null || raw.Length < 8) { return false; } if (raw.Length == 4) { ip = raw[0].ToString() + "." + raw[1].ToString() + "." + raw[2].ToString() + "." + raw[3].ToString(); return true; } int familyLe = raw[0] | (raw[1] << 8); int familyBe = raw[1] | (raw[0] << 8); int family = familyLe; if (familyLe != 2 && familyLe != 23 && (familyBe == 2 || familyBe == 23)) { family = familyBe; } if (family == 2 && raw.Length >= 8) { ip = raw[4].ToString() + "." + raw[5].ToString() + "." + raw[6].ToString() + "." + raw[7].ToString(); return true; } if (family == 23 && raw.Length >= 24) { byte[] v6 = new byte[16]; Buffer.BlockCopy(raw, 8, v6, 0, 16); if (IsMappedIpv4(v6)) { ip = v6[12].ToString() + "." + v6[13].ToString() + "." + v6[14].ToString() + "." + v6[15].ToString(); return true; } ip = new IPAddress(v6).ToString(); return true; } return false; } private static bool IsMappedIpv4(byte[] v6) { if (v6 == null || v6.Length < 16) { return false; } for (int i = 0; i < 10; i++) { if (v6[i] != 0) { return false; } } return v6[10] == 0xFF && v6[11] == 0xFF; } private static string NormalizeUserName(string domain, string user) { if (string.IsNullOrEmpty(user)) { return "(unknown)"; } user = user.Trim().Trim('"'); if (user.Length == 0) { return "(unknown)"; } if (!string.IsNullOrEmpty(domain)) { domain = domain.Trim().Trim('"'); if (domain.Length > 0) { return domain + "\\" + user; } } return user; } 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 string ResolveUser(IntPtr pRecord) { Guid session; if (TryGetGuid(pRecord, NameSessionGuid, out session)) { string name; if (SessionToUser.TryGetValue(session.ToString("D"), out name) && !string.IsNullOrEmpty(name)) { return name; } } Interlocked.Increment(ref UnknownSessions); return "(unknown)"; } private static string ResolveClient(IntPtr pRecord) { string direct; if (TryReadAddress(pRecord, out direct)) { return direct; } Guid conn; if (TryGetGuid(pRecord, NameConnectionGuid, out conn)) { string ip; if (ConnectionToIp.TryGetValue(conn.ToString("D"), out ip) && !string.IsNullOrEmpty(ip)) { return ip; } RememberClientAddress(pRecord, conn); if (ConnectionToIp.TryGetValue(conn.ToString("D"), out ip) && !string.IsNullOrEmpty(ip)) { return ip; } } Guid session; if (TryGetGuid(pRecord, NameSessionGuid, out session)) { string connKey; if (SessionToConnection.TryGetValue(session.ToString("D"), out connKey)) { string ip; if (ConnectionToIp.TryGetValue(connKey, out ip) && !string.IsNullOrEmpty(ip)) { return ip; } } } Interlocked.Increment(ref UnknownClients); 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 void AddUserBytes(string user, long sent, long recv) { Box box = UserTotals.GetOrAdd(user, delegate { return new Box(); }); if (sent != 0) { Interlocked.Add(ref box.Sent, sent); } if (recv != 0) { Interlocked.Add(ref box.Recv, recv); } } private static void AddClientBytes(string client, long sent, long recv) { Box box = ClientTotals.GetOrAdd(client, 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, 500, 552, 553, 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(); UserTotals.Clear(); ClientTotals.Clear(); TreeToShare.Clear(); SessionToUser.Clear(); ConnectionToIp.Clear(); SessionToConnection.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 Count552, 0); Interlocked.Exchange(ref Count500, 0); Interlocked.Exchange(ref Count553, 0); Interlocked.Exchange(ref LengthReadTotal, 0); Interlocked.Exchange(ref LengthWrittenTotal, 0); Interlocked.Exchange(ref UnknownTrees, 0); Interlocked.Exchange(ref UnknownSessions, 0); Interlocked.Exchange(ref UnknownClients, 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"); NameSessionGuid = Marshal.StringToHGlobalUni("SessionGUID"); NameUserName = Marshal.StringToHGlobalUni("UserName"); NameDomainName = Marshal.StringToHGlobalUni("DomainName"); NameConnectionGuid = Marshal.StringToHGlobalUni("ConnectionGUID"); NameAddress = Marshal.StringToHGlobalUni("Address"); NameClientAddress = Marshal.StringToHGlobalUni("ClientAddress"); 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 Get-SMBeatEtwUserNormalizedName { param( [string]$Name ) if ([string]::IsNullOrWhiteSpace($Name)) { return '(unknown)' } $n = $Name.Trim().Trim('"') if ($n.Length -eq 0) { return '(unknown)' } return $n } function ConvertTo-SMBeatEtwUserFlushRecords { param( [AllowEmptyCollection()] [object[]]$Deltas, [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec ) $records = New-Object System.Collections.Generic.List[object] if ($null -eq $Deltas) { $Deltas = @() } foreach ($row in $Deltas) { $name = Get-SMBeatEtwUserNormalizedName -Name ([string]$row.Name) if ($name.EndsWith('$')) { 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 'user' -Name $name ` -ReadRaw 0 -WriteRaw 0 -SentRaw 0 -ReceivedRaw 0 -User $name $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 Get-SMBeatEtwUserPendingDeltas { param( [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec ) $native = @() if (('SMBeat.Native.EtwShare' -as [type]) -and [SMBeat.Native.EtwShare]::IsRunning) { $native = @([SMBeat.Native.EtwShare]::FlushUsers()) } ConvertTo-SMBeatEtwUserFlushRecords -Deltas $native -Server $Server -Utc $Utc -IntervalSec $IntervalSec } function ConvertTo-SMBeatEtwClientFlushRecords { param( [AllowEmptyCollection()] [object[]]$Deltas, [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec ) $records = New-Object System.Collections.Generic.List[object] if ($null -eq $Deltas) { $Deltas = @() } foreach ($row in $Deltas) { $name = [string]$row.Name if ([string]::IsNullOrWhiteSpace($name) -or $name -eq '(unknown)') { 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 'client' -Name $name ` -ReadRaw 0 -WriteRaw 0 -SentRaw 0 -ReceivedRaw 0 -Client $name $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 Get-SMBeatEtwClientPendingDeltas { param( [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec ) $native = @() if (('SMBeat.Native.EtwShare' -as [type]) -and [SMBeat.Native.EtwShare]::IsRunning) { $native = @([SMBeat.Native.EtwShare]::FlushClients()) } ConvertTo-SMBeatEtwClientFlushRecords -Deltas $native -Server $Server -Utc $Utc -IntervalSec $IntervalSec } function Get-SMBeatSmbSessionClientName { param( [string]$Name ) if ([string]::IsNullOrWhiteSpace($Name)) { return '' } $n = $Name.Trim().Trim('"') if ($n.StartsWith('\\')) { $n = $n.Substring(2) } if ($n -match '(?i)^\[?::ffff:(\d+\.\d+\.\d+\.\d+)\]?$') { return $Matches[1] } return $n } function Add-SMBeatSmbSessionUserIp { param( [Parameter(Mandatory = $true)] [hashtable]$Map, [Parameter(Mandatory = $true)] [string]$User, [Parameter(Mandatory = $true)] [string]$Ip ) if (-not $Map.ContainsKey($User)) { $Map[$User] = New-Object System.Collections.Generic.List[string] } if (-not $Map[$User].Contains($Ip)) { [void]$Map[$User].Add($Ip) } $slash = $User.LastIndexOf('\') if ($slash -ge 0 -and $slash -lt ($User.Length - 1)) { $short = $User.Substring($slash + 1) if (-not $Map.ContainsKey($short)) { $Map[$short] = New-Object System.Collections.Generic.List[string] } if (-not $Map[$short].Contains($Ip)) { [void]$Map[$short].Add($Ip) } } } function Read-SMBeatSmbSessionUserIps { $map = @{} $cmd = Get-Command Get-SmbSession -ErrorAction SilentlyContinue if (-not $cmd) { return $map } try { $rows = @(Get-SmbSession -ErrorAction Stop) } catch { return $map } foreach ($row in $rows) { $user = Get-SMBeatEtwUserNormalizedName -Name ([string]$row.ClientUserName) if ($user -eq '(unknown)' -or $user.EndsWith('$')) { continue } $ip = Get-SMBeatSmbSessionClientName -Name ([string]$row.ClientComputerName) if ([string]::IsNullOrWhiteSpace($ip)) { continue } Add-SMBeatSmbSessionUserIp -Map $map -User $user -Ip $ip } return $map } function Update-SMBeatSmbSessionUserIpCache { $fresh = Read-SMBeatSmbSessionUserIps foreach ($key in @($fresh.Keys)) { foreach ($ip in @($fresh[$key])) { Add-SMBeatSmbSessionUserIp -Map $script:SMBeatSmbSessionUserIps -User $key -Ip $ip } } } function Get-SMBeatSmbSessionUserIps { param( [hashtable]$SessionMap ) if ($PSBoundParameters.ContainsKey('SessionMap')) { if ($null -eq $SessionMap) { return @{} } return $SessionMap } Update-SMBeatSmbSessionUserIpCache return $script:SMBeatSmbSessionUserIps } function Merge-SMBeatClientRecordsFromUserSessions { param( [AllowEmptyCollection()] [object[]]$UserRecords, [AllowEmptyCollection()] [object[]]$ClientRecords, [Parameter(Mandatory = $true)] [string]$Server, [Parameter(Mandatory = $true)] [datetime]$Utc, [Parameter(Mandatory = $true)] [int]$IntervalSec, [hashtable]$SessionMap ) $out = New-Object System.Collections.Generic.List[object] $existing = @{} if ($null -eq $ClientRecords) { $ClientRecords = @() } if ($null -eq $UserRecords) { $UserRecords = @() } foreach ($row in $ClientRecords) { $out.Add($row) | Out-Null $key = [string]$row.name if (-not [string]::IsNullOrWhiteSpace($key)) { $existing[$key] = $true } } $mapArgs = @{} if ($PSBoundParameters.ContainsKey('SessionMap')) { $mapArgs['SessionMap'] = $SessionMap } $map = Get-SMBeatSmbSessionUserIps @mapArgs foreach ($userRow in $UserRecords) { $user = [string]$userRow.name if ([string]::IsNullOrWhiteSpace($user) -or $user -eq '(unknown)') { continue } $ips = $null if ($map.ContainsKey($user)) { $ips = @($map[$user]) } else { $slash = $user.LastIndexOf('\') if ($slash -ge 0 -and $slash -lt ($user.Length - 1)) { $short = $user.Substring($slash + 1) if ($map.ContainsKey($short)) { $ips = @($map[$short]) } } } if ($null -eq $ips -or $ips.Count -eq 0) { continue } $fresh = New-Object System.Collections.Generic.List[string] foreach ($ip in $ips) { if ([string]::IsNullOrWhiteSpace($ip)) { continue } if ($existing.ContainsKey([string]$ip)) { continue } $fresh.Add([string]$ip) | Out-Null } if ($fresh.Count -eq 0) { continue } $sent = [int64]0 $recv = [int64]0 if ($userRow.sentBytesDelta) { $sent = [int64]$userRow.sentBytesDelta } if ($userRow.receivedBytesDelta) { $recv = [int64]$userRow.receivedBytesDelta } if ($sent -eq 0 -and $recv -eq 0) { continue } $n = $fresh.Count $sentEach = [int64][math]::Floor($sent / $n) $recvEach = [int64][math]::Floor($recv / $n) $sentRem = $sent - ($sentEach * $n) $recvRem = $recv - ($recvEach * $n) $i = 0 foreach ($ip in $fresh) { $s = $sentEach $r = $recvEach if ($i -eq 0) { $s += $sentRem $r += $recvRem } $i++ $snap = New-SMBeatCounterSnapshot -Kind 'client' -Name $ip ` -ReadRaw 0 -WriteRaw 0 -SentRaw 0 -ReceivedRaw 0 -Client $ip $out.Add((ConvertTo-SMBeatSampleRecord -Snapshot $snap -Server $Server -Utc $Utc -IntervalSec $IntervalSec ` -ReadDelta 0 -WriteDelta 0 -SentDelta $s -ReceivedDelta $r)) | Out-Null $existing[$ip] = $true } } return $out.ToArray() } 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 } } |