atomicshop 2.19.6__py3-none-any.whl → 2.19.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.19.6'
4
+ __version__ = '2.19.8'
@@ -32,6 +32,9 @@ def main():
32
32
  print_api("PIP Installing Robocorp-Recognition.")
33
33
  subprocess.check_call(["pip", "install", "--upgrade", "rpaframework-recognition"])
34
34
 
35
+ print_api("PIP Installing pynput.")
36
+ subprocess.check_call(["pip", "install", "--upgrade", "pynput"])
37
+
35
38
  print_api("Installing Playwright browsers.")
36
39
  subprocess.check_call(["playwright", "install"])
37
40
 
@@ -1,3 +1,7 @@
1
+ from typing import Union
2
+ import string
3
+
4
+
1
5
  def get_single_byte_from_byte_string(byte_string, index: int):
2
6
  """
3
7
  Function extracts single byte as byte from byte string object.
@@ -179,3 +183,18 @@ def read_bytes_from_position(
179
183
  # Read the specified number of bytes.
180
184
  data = file.read(num_bytes)
181
185
  return data
186
+
187
+
188
+ def convert_bytes_to_printable_string_only(
189
+ byte_sequence: Union[bytes, bytearray],
190
+ non_printable_character: str = '.'
191
+ ) -> str:
192
+ """
193
+ Convert bytes to printable string. If byte is not printable, replace it with 'non_printable_character'.
194
+ :param byte_sequence: bytes or bytearray, sequence of bytes.
195
+ :param non_printable_character: string, character to replace non-printable characters.
196
+ :return:
197
+ """
198
+
199
+ printable = set(string.printable)
200
+ return ''.join(chr(byte) if chr(byte) in printable else non_printable_character for byte in byte_sequence)
atomicshop/etws/trace.py CHANGED
@@ -29,6 +29,8 @@ class EventTrace(etw.ETW):
29
29
  :param providers: List of tuples with provider name and provider GUID.
30
30
  tuple[0] = provider name
31
31
  tuple[1] = provider GUID
32
+
33
+ Example: [('Microsoft-Windows-DNS-Client', '{1c95126e-7ee8-4e23-86b2-6e7e4a5a8e9b}')]
32
34
  :param event_callback: Reference to the callable callback function that will be called for each occurring event.
33
35
  :param event_id_filters: List of event IDs that we want to filter. If not provided, all events will be returned.
34
36
  The default in the 'etw.ETW' method is 'None'.
@@ -11,10 +11,14 @@ EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1
11
11
 
12
12
  MAXIMUM_LOGGERS = 64
13
13
  ULONG64 = ctypes.c_uint64
14
+ UCHAR = ctypes.c_ubyte
14
15
 
15
16
  INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
16
17
  TRACEHANDLE = ULONG64
17
18
 
19
+ PROCESS_TRACE_MODE_EVENT_RECORD = 0x10000000 # new event-record callback
20
+ PROCESS_TRACE_MODE_REAL_TIME = 0x00000100
21
+ INVALID_PROCESSTRACE_HANDLE = 0xFFFFFFFFFFFFFFFF # Often -1 in 64-bit
18
22
 
19
23
 
20
24
  """
@@ -69,10 +73,7 @@ class EVENT_TRACE_PROPERTIES(ctypes.Structure):
69
73
  ("RealTimeBuffersLost", wintypes.ULONG),
70
74
  ("LoggerThreadId", wintypes.HANDLE),
71
75
  ("LogFileNameOffset", wintypes.ULONG),
72
- ("LoggerNameOffset", wintypes.ULONG),
73
- # Allocate space for the names at the end of the structure
74
- ("_LoggerName", wintypes.WCHAR * 1024),
75
- ("_LogFileName", wintypes.WCHAR * 1024)
76
+ ("LoggerNameOffset", wintypes.ULONG)
76
77
  ]
77
78
 
78
79
 
@@ -183,55 +184,59 @@ class EVENT_HEADER(ctypes.Structure):
183
184
  ("RelatedActivityId", GUID),
184
185
  ]
185
186
 
187
+
188
+ class ETW_BUFFER_CONTEXT(ctypes.Structure):
189
+ _fields_ = [('ProcessorNumber', ctypes.c_ubyte),
190
+ ('Alignment', ctypes.c_ubyte),
191
+ ('LoggerId', ctypes.c_ushort)]
192
+
193
+
194
+ class EVENT_HEADER_EXTENDED_DATA_ITEM(ctypes.Structure):
195
+ _fields_ = [
196
+ ('Reserved1', ctypes.c_ushort),
197
+ ('ExtType', ctypes.c_ushort),
198
+ ('Linkage', ctypes.c_ushort), # struct{USHORT :1, USHORT :15}
199
+ ('DataSize', ctypes.c_ushort),
200
+ ('DataPtr', ctypes.c_ulonglong)
201
+ ]
202
+
203
+
186
204
  class EVENT_RECORD(ctypes.Structure):
187
205
  _fields_ = [
188
- ("EventHeader", EVENT_HEADER),
189
- ("BufferContext", wintypes.ULONG),
190
- ("ExtendedDataCount", wintypes.USHORT),
191
- ("UserDataLength", wintypes.USHORT),
192
- ("ExtendedData", wintypes.LPVOID),
193
- ("UserData", wintypes.LPVOID),
194
- ("UserContext", wintypes.LPVOID)
206
+ ('EventHeader', EVENT_HEADER),
207
+ ('BufferContext', ETW_BUFFER_CONTEXT),
208
+ ('ExtendedDataCount', ctypes.c_ushort),
209
+ ('UserDataLength', ctypes.c_ushort),
210
+ ('ExtendedData', ctypes.POINTER(EVENT_HEADER_EXTENDED_DATA_ITEM)),
211
+ ('UserData', ctypes.c_void_p),
212
+ ('UserContext', ctypes.c_void_p)
195
213
  ]
196
214
 
197
215
 
198
- # class EVENT_TRACE_LOGFILE(ctypes.Structure):
199
- # _fields_ = [
200
- # ("LogFileName", wintypes.LPWSTR),
201
- # ("LoggerName", wintypes.LPWSTR),
202
- # ("CurrentTime", wintypes.LARGE_INTEGER),
203
- # ("BuffersRead", wintypes.ULONG),
204
- # ("ProcessTraceMode", wintypes.ULONG),
205
- # ("EventRecordCallback", wintypes.LPVOID),
206
- # ("BufferSize", wintypes.ULONG),
207
- # ("Filled", wintypes.ULONG),
208
- # ("EventsLost", wintypes.ULONG),
209
- # ("BuffersLost", wintypes.ULONG),
210
- # ("RealTimeBuffersLost", wintypes.ULONG),
211
- # ("LogBuffersLost", wintypes.ULONG),
212
- # ("BuffersWritten", wintypes.ULONG),
213
- # ("LogFileMode", wintypes.ULONG),
214
- # ("IsKernelTrace", wintypes.ULONG),
215
- # ("Context", wintypes.ULONG) # Placeholder for context pointer
216
- # ]
216
+ class EVENT_TRACE_LOGFILE(ctypes.Structure):
217
+ pass
218
+
219
+
220
+ EVENT_RECORD_CALLBACK = ctypes.WINFUNCTYPE(None, ctypes.POINTER(EVENT_RECORD))
221
+ EVENT_TRACE_BUFFER_CALLBACK = ctypes.WINFUNCTYPE(ctypes.c_ulong, ctypes.POINTER(EVENT_TRACE_LOGFILE))
217
222
 
218
223
 
219
224
  class EVENT_TRACE_LOGFILE(ctypes.Structure):
220
225
  _fields_ = [
221
- ("LogFileName", wintypes.LPWSTR),
222
- ("LoggerName", wintypes.LPWSTR),
223
- ("CurrentTime", wintypes.LARGE_INTEGER),
224
- ("BuffersRead", wintypes.ULONG),
225
- ("ProcessTraceMode", wintypes.ULONG),
226
- ("CurrentEvent", EVENT_RECORD),
227
- ("LogfileHeader", TRACE_LOGFILE_HEADER),
228
- ("BufferCallback", wintypes.LPVOID),
229
- ("BufferSize", wintypes.ULONG),
230
- ("Filled", wintypes.ULONG),
231
- ("EventsLost", wintypes.ULONG),
232
- ("EventCallback", ctypes.c_void_p),
233
- ("IsKernelTrace", wintypes.ULONG),
234
- ("Context", wintypes.LPVOID)
226
+ ('LogFileName', ctypes.c_wchar_p),
227
+ ('LoggerName', ctypes.c_wchar_p),
228
+ ('CurrentTime', ctypes.c_longlong),
229
+ ('BuffersRead', ctypes.c_ulong),
230
+ ('ProcessTraceMode', ctypes.c_ulong),
231
+ ('CurrentEvent', EVENT_TRACE),
232
+ ('LogfileHeader', TRACE_LOGFILE_HEADER),
233
+ ('BufferCallback', EVENT_TRACE_BUFFER_CALLBACK),
234
+ ('BufferSize', ctypes.c_ulong),
235
+ ('Filled', ctypes.c_ulong),
236
+ ('EventsLost', ctypes.c_ulong),
237
+ ('EventRecordCallback', EVENT_RECORD_CALLBACK),
238
+ ('IsKernelTrace', ctypes.c_ulong),
239
+ ('Context', ctypes.c_void_p)
235
240
  ]
236
241
 
237
242
 
@@ -255,7 +260,7 @@ class PROVIDER_INFORMATION(ctypes.Structure):
255
260
 
256
261
 
257
262
  # Load the necessary library
258
- advapi32 = ctypes.WinDLL('advapi32')
263
+ advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
259
264
  tdh = ctypes.windll.tdh
260
265
 
261
266
  # Define necessary TDH functions
@@ -263,6 +268,46 @@ tdh.TdhEnumerateProviders.argtypes = [ctypes.POINTER(PROVIDER_ENUMERATION_INFO),
263
268
  tdh.TdhEnumerateProviders.restype = ULONG
264
269
 
265
270
 
271
+ # Make sure StartTraceW has proper argtypes (if not set in consts)
272
+ StartTrace = advapi32.StartTraceW
273
+ StartTrace.argtypes = [
274
+ ctypes.POINTER(TRACEHANDLE),
275
+ wintypes.LPCWSTR,
276
+ ctypes.POINTER(EVENT_TRACE_PROPERTIES)
277
+ ]
278
+ StartTrace.restype = wintypes.ULONG
279
+
280
+
281
+ class EVENT_FILTER_DESCRIPTOR(ctypes.Structure):
282
+ _fields_ = [('Ptr', ctypes.c_ulonglong),
283
+ ('Size', ctypes.c_ulong),
284
+ ('Type', ctypes.c_ulong)]
285
+
286
+
287
+ class ENABLE_TRACE_PARAMETERS(ctypes.Structure):
288
+ _fields_ = [
289
+ ('Version', ctypes.c_ulong),
290
+ ('EnableProperty', ctypes.c_ulong),
291
+ ('ControlFlags', ctypes.c_ulong),
292
+ ('SourceId', GUID),
293
+ ('EnableFilterDesc', ctypes.POINTER(EVENT_FILTER_DESCRIPTOR)),
294
+ ('FilterDescCount', ctypes.c_ulong)
295
+ ]
296
+
297
+
298
+ EnableTraceEx2 = advapi32.EnableTraceEx2
299
+ EnableTraceEx2.argtypes = [
300
+ TRACEHANDLE, # TraceHandle (c_uint64)
301
+ ctypes.POINTER(GUID), # ProviderId
302
+ ctypes.c_ulong, # ControlCode
303
+ ctypes.c_char, # Level
304
+ ctypes.c_ulonglong, # MatchAnyKeyword
305
+ ctypes.c_ulonglong, # MatchAllKeyword
306
+ ctypes.c_ulong, # Timeout
307
+ ctypes.POINTER(ENABLE_TRACE_PARAMETERS)] # PENABLE_TRACE_PARAMETERS (optional) -> None or pointer
308
+ EnableTraceEx2.restype = ctypes.c_ulong
309
+
310
+
266
311
  # Define the function prototype
267
312
  QueryAllTraces = advapi32.QueryAllTracesW
268
313
  QueryAllTraces.argtypes = [
@@ -277,8 +322,13 @@ OpenTrace.argtypes = [ctypes.POINTER(EVENT_TRACE_LOGFILE)]
277
322
  OpenTrace.restype = wintypes.ULONG
278
323
 
279
324
  ProcessTrace = advapi32.ProcessTrace
280
- ProcessTrace.argtypes = [ctypes.POINTER(wintypes.ULONG), wintypes.ULONG, wintypes.LARGE_INTEGER, wintypes.LARGE_INTEGER]
281
- ProcessTrace.restype = wintypes.ULONG
325
+ ProcessTrace.argtypes = [
326
+ ctypes.POINTER(ctypes.c_uint64), # pointer to array of 64-bit handles
327
+ wintypes.ULONG, # handle count
328
+ ctypes.c_void_p, # LPFILETIME (start)
329
+ ctypes.c_void_p # LPFILETIME (end)
330
+ ]
331
+ ProcessTrace.restype = wintypes.ULONG
282
332
 
283
333
  CloseTrace = advapi32.CloseTrace
284
334
  CloseTrace.argtypes = [wintypes.ULONG]
@@ -1,5 +1,5 @@
1
1
  import ctypes
2
- from ctypes import wintypes
2
+ import queue
3
3
  from ctypes.wintypes import ULONG
4
4
  import uuid
5
5
  from typing import Literal
@@ -34,7 +34,7 @@ def start_etw_session(
34
34
  provider_name_list: list = None,
35
35
  verbosity_mode: int = 4,
36
36
  maximum_buffers: int = 38
37
- ):
37
+ ) -> const.TRACEHANDLE:
38
38
  """
39
39
  Start an ETW session and enable the specified provider.
40
40
 
@@ -77,57 +77,89 @@ def start_etw_session(
77
77
  for provider_name in provider_name_list:
78
78
  provider_guid_list.append(providers.get_provider_guid_by_name(provider_name))
79
79
 
80
- properties_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES) + (2 * wintypes.MAX_PATH)
81
- properties = (ctypes.c_byte * properties_size)()
82
- properties_ptr = ctypes.cast(properties, ctypes.POINTER(const.EVENT_TRACE_PROPERTIES))
83
-
84
- # Initialize the EVENT_TRACE_PROPERTIES structure
85
- properties_ptr.contents.Wnode.BufferSize = properties_size
86
- properties_ptr.contents.Wnode.Guid = const.GUID()
87
- properties_ptr.contents.Wnode.Flags = const.WNODE_FLAG_TRACED_GUID
88
- properties_ptr.contents.Wnode.ClientContext = 1 # QPC clock resolution
89
- properties_ptr.contents.BufferSize = 1024
90
- properties_ptr.contents.MinimumBuffers = 1
91
- properties_ptr.contents.MaximumBuffers = maximum_buffers
92
- properties_ptr.contents.MaximumFileSize = 0
93
- properties_ptr.contents.LogFileMode = const.EVENT_TRACE_REAL_TIME_MODE
94
- properties_ptr.contents.FlushTimer = 1
95
- properties_ptr.contents.EnableFlags = 0
96
-
97
- # Start the ETW session
98
- session_handle = wintypes.HANDLE()
99
- status = ctypes.windll.advapi32.StartTraceW(
100
- ctypes.byref(session_handle),
101
- ctypes.c_wchar_p(session_name),
102
- properties_ptr
103
- )
80
+ # (1) Allocate a buffer large enough for EVENT_TRACE_PROPERTIES + space for 2 strings
81
+ # The typical approach is to allow enough space for:
82
+ # - The structure
83
+ # - The "LoggerName" string
84
+ # - The "LogFileName" string (even if we don't use it, we must leave offset space)
85
+ #
86
+ props_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
87
+ # Add space for 2 x 1024 wide-chars (one for LoggerName, one for LogFileName)
88
+ # Each wide char = ctypes.sizeof(ctypes.c_wchar).
89
+ props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for logger name
90
+ props_size += (2 * 1024 * ctypes.sizeof(ctypes.c_wchar)) # for log file name
91
+
92
+ properties_buffer = ctypes.create_string_buffer(props_size)
93
+ properties_ptr = ctypes.cast(properties_buffer, ctypes.POINTER(const.EVENT_TRACE_PROPERTIES))
94
+ props = properties_ptr.contents
95
+
96
+ # (2) Fill in basic fields
97
+ props.Wnode.BufferSize = props_size
98
+ props.Wnode.Flags = const.WNODE_FLAG_TRACED_GUID # 0x00020000
99
+ props.Wnode.ClientContext = 1 # QPC clock
100
+ props.BufferSize = 1024
101
+ props.MinimumBuffers = 1
102
+ props.MaximumBuffers = maximum_buffers
103
+ props.MaximumFileSize = 0
104
+ props.LogFileMode = const.EVENT_TRACE_REAL_TIME_MODE # real-time
105
+ props.FlushTimer = 1
106
+ props.EnableFlags = 0
107
+
108
+ # (3) Indicate where in this allocated buffer the strings should go
109
+ struct_size = ctypes.sizeof(const.EVENT_TRACE_PROPERTIES)
110
+ props.LoggerNameOffset = struct_size
111
+ props.LogFileNameOffset = struct_size + (2 * 1024 * ctypes.sizeof(ctypes.c_wchar))
112
+
113
+ # (4) Copy the session name into the LoggerName space
114
+ logger_name_address = ctypes.addressof(properties_buffer) + props.LoggerNameOffset
115
+ session_name_wchar = ctypes.create_unicode_buffer(session_name)
116
+ ctypes.memmove(
117
+ logger_name_address,
118
+ session_name_wchar,
119
+ len(session_name) * ctypes.sizeof(ctypes.c_wchar)
120
+ )
104
121
 
105
- if status != 0:
106
- if status == 183:
107
- raise ETWSessionExists(f"ETW session [{session_name}] already exists")
108
- else:
109
- raise Exception(f"StartTraceW failed with error {status}")
110
-
111
- # Enable each provider
112
- for provider_guid in provider_guid_list:
113
- provider_guid_struct = _string_to_guid(provider_guid)
114
- status = ctypes.windll.advapi32.EnableTraceEx2(
115
- session_handle,
116
- ctypes.byref(provider_guid_struct),
117
- const.EVENT_CONTROL_CODE_ENABLE_PROVIDER,
118
- verbosity_mode,
119
- 0,
120
- 0,
121
- 0,
122
- None
122
+ # (5) Start the session
123
+ session_handle = const.TRACEHANDLE(0)
124
+ status = const.StartTrace(
125
+ ctypes.byref(session_handle),
126
+ session_name, # The session name as an LPCWSTR
127
+ properties_ptr # pointer to EVENT_TRACE_PROPERTIES
123
128
  )
124
129
 
125
130
  if status != 0:
126
- raise Exception(f"EnableTraceEx2 failed for provider {provider_guid} with error {status}")
127
-
128
- print("ETW session started successfully")
129
-
130
- return session_handle
131
+ # 183 => ERROR_ALREADY_EXISTS
132
+ if status == 183:
133
+ raise ETWSessionExists(f"ETW session [{session_name}] already exists")
134
+ else:
135
+ raise Exception(f"StartTraceW failed with error code {status}")
136
+
137
+ # (6) If we have providers to enable, enable each
138
+ if provider_guid_list:
139
+ for guid_str in provider_guid_list:
140
+ guid_struct = _string_to_guid(guid_str)
141
+
142
+ # Typically you'd do something like:
143
+ # advapi32.EnableTraceEx2(TRACEHANDLE, PGUID, CONTROL_CODE, LEVEL, KW, KW, TIMEOUT, FILTER)
144
+
145
+ enable_status = const.EnableTraceEx2(
146
+ session_handle, # The session handle
147
+ ctypes.byref(guid_struct), # The provider GUID
148
+ const.EVENT_CONTROL_CODE_ENABLE_PROVIDER, # 1 => enable
149
+ verbosity_mode, # level
150
+ 0xFFFFFFFFFFFFFFFF, # matchAnyKeyword
151
+ 0, # matchAllKeyword
152
+ 0, # timeout
153
+ None # enableParameters (optional)
154
+ )
155
+
156
+ if enable_status != 0:
157
+ raise Exception(
158
+ f"EnableTraceEx2 failed for provider {guid_str} (error={enable_status})"
159
+ )
160
+
161
+ print(f"ETW session '{session_name}' started successfully.")
162
+ return session_handle
131
163
 
132
164
 
133
165
  # Function to stop and delete ETW session
@@ -165,6 +197,103 @@ def stop_and_delete_etw_session(session_name: str) -> tuple[bool, int]:
165
197
  return True, status
166
198
 
167
199
 
200
+ @const.EVENT_CALLBACK_TYPE
201
+ def _default_callback(
202
+ event_record_ptr
203
+ ):
204
+ """
205
+ This function will be called by Windows for every incoming ETW event.
206
+ 'event_record_ptr' is a pointer to an EVENT_RECORD structure.
207
+ """
208
+ # Convert pointer to a Python EVENT_RECORD object
209
+ event_record = event_record_ptr.contents
210
+
211
+ # Do something with event_record (e.g., parse via TDH)
212
+ print("Received an ETW event!", event_record.EventHeader.ProviderId)
213
+
214
+
215
+ def start_etw_consumer(
216
+ session_name: str,
217
+ record_queue: queue.Queue
218
+ ):
219
+ """
220
+ Attach to an existing real-time ETW session by 'session_name'
221
+ using the "new" EVENT_RECORD callback approach.
222
+ This call blocks until the ETW session is stopped or an error occurs.
223
+ """
224
+ # 1) Create a closure callback that references the queue
225
+ # We define an inner function that sees 'record_queue' from outer scope.
226
+ # Then we wrap that in EVENT_CALLBACK_TYPE.
227
+ if record_queue is not None:
228
+ @const.EVENT_CALLBACK_TYPE
229
+ def _queue_callback(event_record_ptr):
230
+ event_record = event_record_ptr.contents
231
+ # # Example: put a simple dict with the provider ID & possibly more info
232
+ # record_queue.put({
233
+ # "provider_id": str(event_record.EventHeader.ProviderId),
234
+ # "process_id": event_record.EventHeader.ProcessId,
235
+ # "thread_id": event_record.EventHeader.ThreadId,
236
+ # # ... more fields or parse them with TdhGetEventInformation, etc.
237
+ # })
238
+ print("Received an ETW event!", event_record.EventHeader.ProviderId)
239
+ record_queue.put(event_record)
240
+ else:
241
+ _queue_callback = _default_callback
242
+
243
+ # Keep a reference to the callback so Python doesn't GC it.
244
+ start_etw_consumer._callback_ref = _queue_callback
245
+
246
+ # Prepare the EVENT_TRACE_LOGFILE structure
247
+ logfile = const.EVENT_TRACE_LOGFILE()
248
+ # You can also do: logfile.LoggerName = session_name
249
+ # If you assign session_name (a Python str), ctypes automatically converts it to a temporary c_wchar_p under the hood.
250
+ # logfile.LoggerName = ctypes.c_wchar_p(session_name)
251
+ logfile.LoggerName = session_name
252
+ logfile.LogFileName = None # Real-time, not from a file
253
+ logfile.ProcessTraceMode = (const.PROCESS_TRACE_MODE_REAL_TIME | const.PROCESS_TRACE_MODE_EVENT_RECORD)
254
+
255
+ # Point to our Python callback
256
+ logfile.EventRecordCallback = const.EVENT_RECORD_CALLBACK(_default_callback)
257
+
258
+ # Open the trace
259
+ trace_handle = const.OpenTrace(ctypes.byref(logfile))
260
+ if trace_handle == const.INVALID_PROCESSTRACE_HANDLE:
261
+ error_code = ctypes.get_last_error()
262
+ raise OSError(f"OpenTrace failed with error code: {error_code}")
263
+
264
+ # trace_handle is actually an unsigned long, but in 64-bit it's a 64-bit handle.
265
+ # We'll create an array of one handle for ProcessTrace:
266
+ trace_handle_array = (ctypes.c_uint64 * 1)(trace_handle)
267
+
268
+ # Blocking call - will not return until the session is stopped (or error).
269
+ status = const.ProcessTrace(
270
+ trace_handle_array,
271
+ 1, # HandleCount = 1
272
+ None, # StartTime = None
273
+ None # EndTime = None
274
+ )
275
+ if status != 0:
276
+ raise OSError(f"ProcessTrace failed with error code: {status}")
277
+
278
+ print("ProcessTrace returned. ETW consumer finished.")
279
+
280
+
281
+ def start_etw_consumer_in_thread(
282
+ session_name: str,
283
+ record_queue: queue.Queue
284
+ ):
285
+ """
286
+ Start an ETW consumer in a separate thread.
287
+ """
288
+ import threading
289
+
290
+ def _start_etw_consumer():
291
+ start_etw_consumer(session_name, record_queue)
292
+
293
+ thread = threading.Thread(target=_start_etw_consumer)
294
+ thread.start()
295
+
296
+
168
297
  def get_all_providers(key_as: Literal['name', 'guid'] = 'name') -> dict:
169
298
  """
170
299
  Get all ETW providers available on the system.
@@ -404,6 +404,33 @@ class MongoDBWrapper:
404
404
 
405
405
  return count
406
406
 
407
+ def aggregate_entries_in_collection(
408
+ self,
409
+ collection_name: str,
410
+ pipeline: list[dict]
411
+ ) -> list[dict]:
412
+ """
413
+ Aggregate entries in a MongoDB collection by query.
414
+
415
+ :param collection_name: str, the name of the collection.
416
+ :param pipeline: list of dictionaries, the pipeline to search for.
417
+ Example, search for all entries with column name 'name' equal to 'John':
418
+ pipeline = [{'$match': {'name': 'John'}}]
419
+ Example, return all entries from collection:
420
+ pipeline = []
421
+
422
+ :return: list of dictionaries, the list of entries that match the query.
423
+ """
424
+
425
+ self.connect()
426
+
427
+ aggregation: list[dict] = aggregate_entries_in_collection(
428
+ database=self.db, collection_name=collection_name,
429
+ pipeline=pipeline, mongo_client=self.client, close_client=False)
430
+
431
+ return aggregation
432
+
433
+
407
434
  def get_client(self):
408
435
  return self.client
409
436
 
@@ -1148,6 +1175,60 @@ def count_entries_in_collection(
1148
1175
  return count
1149
1176
 
1150
1177
 
1178
+ def aggregate_entries_in_collection(
1179
+ database: Union[str, pymongo.database.Database],
1180
+ collection_name: str,
1181
+ pipeline: list,
1182
+ mongo_client: pymongo.MongoClient = None,
1183
+ close_client: bool = False
1184
+ ) -> list:
1185
+ """
1186
+ Perform an aggregation pipeline operation on a MongoDB collection.
1187
+ For example, we count the number of entries with the same 'sha256' value that is provided in a list:
1188
+ pipeline = [
1189
+ {"$match": {"sha256": {"$in": ["hash1", "hash2"]}}},
1190
+ {"$group": {"_id": "$sha256", "count": {"$sum": 1}}}
1191
+ ]
1192
+ And we will get the result:
1193
+ [
1194
+ {"_id": "hash1", "count": 1},
1195
+ {"_id": "hash2", "count": 1}
1196
+ ]
1197
+ Meaning we will get separate counts for each 'sha256' value in the list.
1198
+
1199
+ :param database: String or the database object.
1200
+ str - the name of the database. In this case the database object will be created.
1201
+ pymongo.database.Database - the database object that will be used instead of creating a new one.
1202
+ :param collection_name: str, the name of the collection.
1203
+ :param pipeline: list, the aggregation pipeline to execute.
1204
+ Example:
1205
+ pipeline = [
1206
+ {"$match": {"sha256": {"$in": ["hash1", "hash2"]}}},
1207
+ {"$group": {"_id": "$sha256", "count": {"$sum": 1}}}
1208
+ ]
1209
+ :param mongo_client: pymongo.MongoClient, the connection object.
1210
+ If None, a new connection will be created to default URI.
1211
+ :param close_client: bool, if True, the connection will be closed after the operation.
1212
+
1213
+ :return: list, the results of the aggregation pipeline.
1214
+ """
1215
+ if not mongo_client:
1216
+ mongo_client = connect()
1217
+ close_client = True
1218
+
1219
+ db = _get_pymongo_db_from_string_or_pymongo_db(database, mongo_client)
1220
+ collection = db[collection_name]
1221
+
1222
+ # Perform aggregation
1223
+ results = collection.aggregate(pipeline)
1224
+
1225
+ if close_client:
1226
+ mongo_client.close()
1227
+
1228
+ # Return the results as a list
1229
+ return list(results)
1230
+
1231
+
1151
1232
  def delete_all_entries_from_collection(
1152
1233
  database: Union[str, pymongo.database.Database],
1153
1234
  collection_name: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.19.6
3
+ Version: 2.19.8
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=knCj__YmUuoKs1yCpFlwbRugLxYK9EFx2UTKQEkbSTc,123
1
+ atomicshop/__init__.py,sha256=Th-y97EjSR9zstMSKi94LqlaUJmHBUAuGgdCu-K5hrQ,123
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
@@ -57,7 +57,7 @@ atomicshop/a_installs/win/fibratus.py,sha256=TU4e9gdZ_zI73C40uueJ59pD3qmN-UFGdX5
57
57
  atomicshop/a_installs/win/mongodb.py,sha256=AqyItXu19aaoe49pppDxtEkXey6PMy0PoT2Y_RmPpPE,179
58
58
  atomicshop/a_installs/win/nodejs.py,sha256=U519Dyt4bsQPbEg_PwnZL5tsbfqDr1BbhxwoQFZsSKo,200
59
59
  atomicshop/a_installs/win/pycharm.py,sha256=j_RSd7aDOyC3yDd-_GUTMLlQTmDrqtVFG--oUfGLiZk,140
60
- atomicshop/a_installs/win/robocorp.py,sha256=tCUrBHFynAZK81To8vRBvchOwY6BWc4LhBgTxXb0az4,2132
60
+ atomicshop/a_installs/win/robocorp.py,sha256=2E28iaRlAZROoxmXwiXv8rqTjVcdBT2UJ3B8nxrtmkc,2245
61
61
  atomicshop/a_installs/win/wsl_ubuntu_lts.py,sha256=dZbPRLNKFeMd6MotjkE6UDY9cOiIaaclIdR1kGYWI50,139
62
62
  atomicshop/a_mains/dns_gateway_setting.py,sha256=ncc2rFQCChxlNP59UshwmTonLqC6MWblrVAzbbz-13M,149
63
63
  atomicshop/a_mains/msi_unpacker.py,sha256=5hrkqETYt9HIqR_3PMf32_q06kCrIcsdm_RJV9oY438,188
@@ -89,7 +89,7 @@ atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
89
89
  atomicshop/basics/ansi_escape_codes.py,sha256=uGVRW01v2O052701sOfAdCaM_GLF_cu_jrssuYiPbzA,3216
90
90
  atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
91
91
  atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
92
- atomicshop/basics/bytes_arrays.py,sha256=tU7KF0UAFSErGNcan4Uz1GJxeIYVRuUu9sHVZHgyNnw,6542
92
+ atomicshop/basics/bytes_arrays.py,sha256=xfFW9CBQyzf0iXNWpx-EfrxtN3-tiKzuczPzOb6cOdU,7190
93
93
  atomicshop/basics/classes.py,sha256=UayCzPs3eynI3wOzSu-2IJSmTwOB4HwPwgVI2F-7_lQ,12648
94
94
  atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14049
95
95
  atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
@@ -115,7 +115,7 @@ atomicshop/etws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  atomicshop/etws/const.py,sha256=v3x_IdCYeSKbCGywiZFOZln80ldpwKW5nuMDuUe51Jg,1257
116
116
  atomicshop/etws/providers.py,sha256=CXNx8pYdjtpLIpA66IwrnE64XhY4U5ExnFBMLEb8Uzk,547
117
117
  atomicshop/etws/sessions.py,sha256=b_KeiOvgOBJezJokN81TRlrvJiQNJlIWN4Z6UVjuxP0,1335
118
- atomicshop/etws/trace.py,sha256=WMOjdazK97UcIdhVgcnjh98OCbuEJcnm1Z_yPp_nE2c,7258
118
+ atomicshop/etws/trace.py,sha256=QcB_NYP-KPOaafYd6jCFdPPQsBu4jzac4QicJdWJAoE,7359
119
119
  atomicshop/etws/traces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  atomicshop/etws/traces/trace_dns.py,sha256=WvOZm7KNdP4r6ofkZhUGi9WjtYlkV3mUp_yxita3Qg4,6399
121
121
  atomicshop/etws/traces/trace_sysmon_process_creation.py,sha256=OM-bkK38uYMwWLZKNOTDa0Xdk3sO6sqsxoMUIiPvm5g,4656
@@ -206,8 +206,8 @@ atomicshop/wrappers/ctyping/file_details_winapi.py,sha256=dmdnCMwx4GgVEctiUIyniV
206
206
  atomicshop/wrappers/ctyping/process_winapi.py,sha256=QcXL-ETtlSSkoT8F7pYle97ubGWsjYp8cx8HxkVMgAc,2762
207
207
  atomicshop/wrappers/ctyping/win_console.py,sha256=uTtjkz9rY559AaV0dhyZYUSSEe9cn6Du2DgurdMtX-M,1158
208
208
  atomicshop/wrappers/ctyping/etw_winapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
209
- atomicshop/wrappers/ctyping/etw_winapi/const.py,sha256=stZHZ7tSiSAs04ikr7uH-Td_yBXxsF-bp2Q0F3K2fsM,9543
210
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py,sha256=Iwd0wIuoxpjMaaOfZZtT1bPtDTsMO8jjItBE5bvkocM,11546
209
+ atomicshop/wrappers/ctyping/etw_winapi/const.py,sha256=W-NMYlJ8RrMxLRjKcatEHe6wJwNxNMGSmYbXRXrlz6o,11223
210
+ atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py,sha256=EMdCjGd3x6aRcHkY2NRNTlrOuC7TY4rhfqfRtTWkbYU,17225
211
211
  atomicshop/wrappers/ctyping/msi_windows_installer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
212
  atomicshop/wrappers/ctyping/msi_windows_installer/base.py,sha256=Uu9SlWLsQQ6mjE-ek-ptHcmgiI3Ruah9bdZus70EaVY,4884
213
213
  atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py,sha256=htzwb2ROYI8yyc82xApStckPS2yCcoyaw32yC15KROs,3285
@@ -266,7 +266,7 @@ atomicshop/wrappers/mongodbw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
266
266
  atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=2eEOb35T259lhn5koynfTIm1hanxD02zN97ExGSBM2o,4021
267
267
  atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=64EUQYx7VuMC3ndO2x3nSErh5NZ_BsqMwGvPcybfC-Q,8499
268
268
  atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
269
- atomicshop/wrappers/mongodbw/mongodbw.py,sha256=ih3Gd45rg_70y4sGeu0eEJ3sJd9tEN4I5IqHZelRZJw,52854
269
+ atomicshop/wrappers/mongodbw/mongodbw.py,sha256=it1TDnOF64YgDbkkBvUmUb9XGuUg6SwGnHhuqar3aHE,55929
270
270
  atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
271
271
  atomicshop/wrappers/nodejsw/install_nodejs_ubuntu.py,sha256=wjpJdfAaY92RYl_L9esDIWuBMGeYH35RHJ5BVgMof8Y,6260
272
272
  atomicshop/wrappers/nodejsw/install_nodejs_windows.py,sha256=WvXIcEVnKcQYD-KNwhVP094s__1tt0Ir2Y87MABl8Nc,6283
@@ -325,8 +325,8 @@ atomicshop/wrappers/socketw/statistics_csv.py,sha256=fgMzDXI0cybwUEqAxprRmY3lqbh
325
325
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
326
326
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
327
327
  atomicshop/wrappers/winregw/winreg_network.py,sha256=AENV88H1qDidrcpyM9OwEZxX5svfi-Jb4N6FkS1xtqA,8851
328
- atomicshop-2.19.6.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
329
- atomicshop-2.19.6.dist-info/METADATA,sha256=Y0sI9FlA-ojjoCn5MnTdwd1xe6nC1Rs8tVkH9dxtyHo,10630
330
- atomicshop-2.19.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
331
- atomicshop-2.19.6.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
332
- atomicshop-2.19.6.dist-info/RECORD,,
328
+ atomicshop-2.19.8.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
329
+ atomicshop-2.19.8.dist-info/METADATA,sha256=8KxTUFok-Tb489g5Kw5R07DDTmO87b3cmQC1JfAaRlY,10630
330
+ atomicshop-2.19.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
331
+ atomicshop-2.19.8.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
332
+ atomicshop-2.19.8.dist-info/RECORD,,