atomicshop 2.12.22__py3-none-any.whl → 2.12.23__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.12.22'
4
+ __version__ = '2.12.23'
@@ -1,2 +1,7 @@
1
- "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\bin\Hostx64\x64\cl.exe" /LD /EHsc c:\compile\process_list.cpp /Fo:c:\compile /Fe:c:\compile\process_list.dll /I c:\compile\inc\ /link /LIBPATH:c:\compile\lib\ Advapi32.lib ntdll.lib Psapi.lib
1
+ REM Install WIndows 10 SDK before running.
2
+ REM Execute this cmd in:
3
+ REM Start => All Apps => Visual Studio 2022 => x64 Native Tools Command Prompt for VS 2022
4
+ REM Run the prompt as admin.
5
+ REM "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.39.33519\bin\Hostx64\x64\cl.exe" /LD /EHsc c:\compile\process_list.cpp /Fo:c:\compile /Fe:c:\compile\process_list.dll /I c:\compile\inc\ /link /LIBPATH:c:\compile\lib\ Advapi32.lib ntdll.lib Psapi.lib
6
+ cl /LD /EHsc c:\compile\process_list.cpp /Fo:c:\compile /Fe:c:\compile\process_list.dll /I c:\compile\inc\ /link /LIBPATH:c:\compile\lib\ Advapi32.lib ntdll.lib Psapi.lib
2
7
  pause
@@ -3,6 +3,9 @@
3
3
  #include <tchar.h>
4
4
  #include <iostream>
5
5
  #include <tlhelp32.h>
6
+ #include <atomic>
7
+
8
+ std::atomic<bool> cancelRequested(false);
6
9
 
7
10
  typedef LONG NTSTATUS;
8
11
  typedef NTSTATUS (*PFN_NT_QUERY_INFORMATION_PROCESS)(HANDLE, UINT, PVOID, ULONG, PULONG);
@@ -121,6 +124,10 @@ bool GetProcessCommandLine(DWORD dwPid, wchar_t** ppszCmdLine, wchar_t* szProces
121
124
 
122
125
  typedef void(*CallbackFunc)(DWORD pid, wchar_t* process_name, wchar_t* cmdline);
123
126
 
127
+ extern "C" __declspec(dllexport) void RequestCancellation() {
128
+ cancelRequested.store(true);
129
+ }
130
+
124
131
  extern "C" __declspec(dllexport) void GetProcessDetails(CallbackFunc callback) {
125
132
  if (!EnableDebugPrivilege()) {
126
133
  std::wcout << L"Failed to enable debug privilege." << std::endl;
@@ -156,7 +163,7 @@ extern "C" __declspec(dllexport) void GetProcessDetails(CallbackFunc callback) {
156
163
  } else {
157
164
  callback(pe32.th32ProcessID, szProcessName, NULL);
158
165
  }
159
- } while (Process32NextW(hSnap, &pe32));
166
+ } while (Process32NextW(hSnap, &pe32) && !cancelRequested.load());
160
167
 
161
168
  CloseHandle(hSnap);
162
169
  }
@@ -0,0 +1,64 @@
1
+ import atexit
2
+ import signal
3
+ import sys
4
+ import platform
5
+
6
+ from ..print_api import print_api
7
+
8
+
9
+ def restart_function(callable_function, *args, **kwargs):
10
+ """
11
+ This function will run the callable function with the given arguments and keyword arguments.
12
+ If the function raises an exception, the function will be restarted.
13
+
14
+ :param callable_function: The function to run.
15
+ :param args: The arguments to pass to the function.
16
+ :param kwargs: The keyword arguments to pass to the function.
17
+ :return: The return value of the function.
18
+ """
19
+ while True:
20
+ try:
21
+ return callable_function(*args, **kwargs)
22
+ except Exception as e:
23
+ print(f"ERROR: {e}")
24
+ continue
25
+
26
+
27
+ def run_callable_on_exit_and_signals(
28
+ callable_function,
29
+ print_kwargs: dict = None,
30
+ *args,
31
+ **kwargs
32
+ ):
33
+ """
34
+ This function will run the callable function with the given arguments and keyword arguments.
35
+ If the function raises an exception, the function will be restarted.
36
+
37
+ :param callable_function: The function to run.
38
+ :param print_kwargs: print_api kwargs.
39
+ :param args: The arguments to pass to the function.
40
+ :param kwargs: The keyword arguments to pass to the function.
41
+ :return: The return value of the function.
42
+ """
43
+ def signal_handler(signum, frame):
44
+ print_api(f"Signal {signum} received, exiting.", **(print_kwargs or {}))
45
+ callable_function(*args, **kwargs)
46
+ input("Press Enter to exit.")
47
+ sys.exit(0)
48
+
49
+ def exit_handler():
50
+ print_api("Exiting.", **(print_kwargs or {}))
51
+ callable_function(*args, **kwargs)
52
+ input("Press Enter to exit.")
53
+ sys.exit(0)
54
+
55
+ signals = [signal.SIGINT, signal.SIGTERM]
56
+ if platform.system() != 'Windows':
57
+ signals.append(signal.SIGQUIT)
58
+ signals.append(signal.SIGHUP)
59
+ for sig in signals:
60
+ signal.signal(sig, signal_handler)
61
+
62
+ # signal.signal(signal.SIGINT, signal_handler)
63
+ # signal.signal(signal.SIGTERM, signal_handler)
64
+ atexit.register(exit_handler)
@@ -3,10 +3,11 @@ from .. import dns
3
3
  from ..wrappers.psutilw import psutilw
4
4
  from ..basics import dicts
5
5
  from ..process_poller import ProcessPollerPool
6
+ from ..print_api import print_api
6
7
 
7
8
 
8
9
  class DnsTrace:
9
- def __init__(self, enable_process_poller: bool = False, attrs: list = None):
10
+ def __init__(self, enable_process_poller: bool = False, attrs: list = None, session_name: str = None):
10
11
  """
11
12
  DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008.
12
13
 
@@ -14,6 +15,7 @@ class DnsTrace:
14
15
  every 100 ms. Since the DNS events doesn't contain the process name and command line, only PID.
15
16
  Then DNS events will be enriched with the process name and command line from the process poller.
16
17
  :param attrs: List of attributes to return. If None, all attributes will be returned.
18
+ :param session_name: The name of the session to create. If not provided, a UUID will be generated.
17
19
 
18
20
  Usage Example:
19
21
  from atomicshop.etw import dns_trace
@@ -31,9 +33,10 @@ class DnsTrace:
31
33
  self.attrs = attrs
32
34
 
33
35
  self.event_trace = etw.EventTrace(
34
- [(dns.ETW_DNS_INFO['provider_name'], dns.ETW_DNS_INFO['provider_guid'])],
36
+ providers=[(dns.ETW_DNS_INFO['provider_name'], dns.ETW_DNS_INFO['provider_guid'])],
35
37
  # lambda x: self.event_queue.put(x),
36
- event_id_filters=[dns.ETW_DNS_INFO['event_id']]
38
+ event_id_filters=[dns.ETW_DNS_INFO['event_id']],
39
+ session_name=session_name
37
40
  )
38
41
 
39
42
  if self.enable_process_poller:
@@ -117,7 +120,12 @@ class DnsTrace:
117
120
  event_dict['status'] = 'Error'
118
121
 
119
122
  if self.enable_process_poller:
120
- event_dict = psutilw.cross_single_connection_with_processes(event_dict, self.process_poller.processes)
123
+ processes = self.process_poller.processes
124
+
125
+ if isinstance(processes, BaseException):
126
+ raise processes
127
+
128
+ event_dict = psutilw.cross_single_connection_with_processes(event_dict, processes)
121
129
  # If it was impossible to get the process name from the process poller, get it from psutil.
122
130
  # if event_dict['name'].isnumeric():
123
131
  # event_dict['name'] = process_name
atomicshop/etw/etw.py CHANGED
@@ -3,9 +3,18 @@ import queue
3
3
  # Import FireEye Event Tracing library.
4
4
  import etw
5
5
 
6
+ from ..basics import atexits
7
+
6
8
 
7
9
  class EventTrace(etw.ETW):
8
- def __init__(self, providers: list, event_callback=None, event_id_filters: list = None):
10
+ def __init__(
11
+ self,
12
+ providers: list,
13
+ event_callback=None,
14
+ event_id_filters: list = None,
15
+ session_name: str = None,
16
+ stop_etw_tracing_on_exit: bool = False
17
+ ):
9
18
  """
10
19
  :param providers: List of tuples with provider name and provider GUID.
11
20
  tuple[0] = provider name
@@ -13,8 +22,21 @@ class EventTrace(etw.ETW):
13
22
  :param event_callback: Reference to the callable callback function that will be called for each occurring event.
14
23
  :param event_id_filters: List of event IDs that we want to filter. If not provided, all events will be returned.
15
24
  The default in the 'etw.ETW' method is 'None'.
25
+ :param session_name: The name of the session to create. If not provided, a UUID will be generated.
26
+ :param stop_etw_tracing_on_exit: If True, the ETW tracing will be stopped when the script exits.
27
+ 'pywintrace' module starts a new session for ETW tracing, and it will not stop the session when the script
28
+ exits or exception is raised.
29
+ This can cause problems when you want to start the script again, and the session is already running.
30
+ If the session is already running, and you start a new session with the same session name, you will get
31
+ the buffer from when you stopped getting the events from the buffer.
32
+ If you give different session name for new session, the previous session will still continue to run,
33
+ filling the buffer with events, until you will stop getting new events on all sessions or get an
34
+ exception that the buffer is full (WinError 1450).
16
35
  """
17
36
  self.event_queue = queue.Queue()
37
+ self.stop_etw_tracing_on_exit: bool = stop_etw_tracing_on_exit
38
+
39
+ self._set_atexit_and_signals()
18
40
 
19
41
  # If no callback function is provided, we will use the default one, which will put the event in the queue.
20
42
  if not event_callback:
@@ -29,7 +51,9 @@ class EventTrace(etw.ETW):
29
51
  etw_format_providers.append(etw.ProviderInfo(provider[0], etw.GUID(provider[1])))
30
52
 
31
53
  super().__init__(
32
- providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters)
54
+ providers=etw_format_providers, event_callback=function_callable, event_id_filters=event_id_filters,
55
+ session_name=session_name
56
+ )
33
57
 
34
58
  def start(self):
35
59
  try:
@@ -59,3 +83,37 @@ class EventTrace(etw.ETW):
59
83
  """
60
84
 
61
85
  return self.event_queue.get()
86
+
87
+ def _set_atexit_and_signals(self):
88
+ if self.stop_etw_tracing_on_exit:
89
+ atexits.run_callable_on_exit_and_signals(self.stop)
90
+
91
+
92
+ def find_sessions_by_provider(provider_name: str):
93
+ """
94
+ Find ETW session by provider name.
95
+
96
+ :param provider_name: The name of the provider to search for.
97
+ """
98
+
99
+ return
100
+
101
+
102
+ def get_all_providers_from_session(session_name: str):
103
+ """
104
+ Get all providers that ETW session uses.
105
+
106
+ :param session_name: The name of the session to get providers from.
107
+ """
108
+
109
+ return
110
+
111
+
112
+ def stop_session_by_name(session_name: str):
113
+ """
114
+ Stop ETW session by name.
115
+
116
+ :param session_name: The name of the session to stop.
117
+ """
118
+
119
+ return
@@ -40,28 +40,28 @@ class ChangeMonitor:
40
40
  'url_playwright_png',
41
41
  'url_playwright_jpeg'],
42
42
  None] = None,
43
- object_type_settings: dict = None
43
+ object_type_settings: dict = None,
44
+ etw_session_name: str = None
44
45
  ):
45
46
  """
46
47
  :param object_type: string, type of object to check. The type must be one of the following:
47
- 'file': 'check_object_list' must contain strings of full path to the file.
48
- 'dns': 'check_object_list' will be none, since the DNS events will be queried from the system.
49
- 'network': 'check_object_list' will be none, since the network events will be queried from the system.
50
- 'process_running': 'check_object_list' must contain strings of process names to check if they are running.
48
+ 'file': 'check_object' must contain string of full path to the file.
49
+ 'dns': 'check_object' will be none, since the DNS events will be queried from the system.
50
+ 'network': 'check_object' will be none, since the network events will be queried from the system.
51
+ 'process_running': 'check_object' must contain list of strings of process names to check if they are
52
+ running.
51
53
  Example: ['chrome.exe', 'firefox.exe']
52
54
  No file is written.
53
- 'url_urllib': 'check_object_list' must contain strings of full URL to a web page. The page will be
54
- downloaded using 'urllib' library in HTML.
55
- 'url_playwright_html': 'check_object_list' must contain strings of full URL to a web page. The page will
56
- be downloaded using 'playwright' library in HTML.
57
- 'url_playwright_pdf': 'check_object_list' must contain strings of full URL to a web page. The page will
58
- be downloaded using 'playwright' library in PDF.
59
- 'url_playwright_png': 'check_object_list' must contain strings of full URL to a web page. The page will
60
- be downloaded using 'playwright' library in PNG.
61
- 'url_playwright_jpeg': 'check_object_list' must contain strings of full URL to a web page. The page will
62
- be downloaded using 'playwright' library in JPEG.
55
+ 'url_*': 'check_object' must contain string of full URL to a web page to download.
56
+ 'url_urllib': download using 'urllib' library to HTML file.
57
+ 'url_playwright_html': download using 'playwright' library to HTML file.
58
+ 'url_playwright_pdf': download using 'playwright' library to PDF file.
59
+ 'url_playwright_png': download using 'playwright' library to PNG file.
60
+ 'url_playwright_jpeg': download using 'playwright' library to JPEG file.
63
61
  :param object_type_settings: dict, specific settings for the object type.
64
62
  'dns': Check the default settings example in 'DNS__DEFAULT_SETTINGS'.
63
+ 'file': Check the default settings example in 'FILE__URL__DEFAULT_SETTINGS'.
64
+ 'url_*': Check the default settings example in 'FILE__URL__DEFAULT_SETTINGS'.
65
65
  :param check_object: The object to check if changed.
66
66
  'dns': empty.
67
67
  'network': empty.
@@ -84,24 +84,10 @@ class ChangeMonitor:
84
84
  to the input file whether the object was updated.
85
85
  False: write to input file each time there is an update, and read each check cycle from the file and not
86
86
  from the memory.
87
- :param store_original_object: boolean, if True, the original object will be stored on the disk inside
88
- 'Original' folder, inside 'input_directory'.
89
- :param operation_type: string, type of operation to perform. The type must be one of the following:
90
- 'hit_statistics': will only store the statistics of the entries in the input file.
91
- 'all_objects': disable the DiffChecker features, meaning any new entries will be emitted as is.
92
- None: will use the default operation type, based on the object type.
93
- :param hit_statistics_input_file_rotation_cycle_hours:
94
- float, the amount of hours the input file will be rotated in the 'hit_statistics' operation type.
95
- str, (only 'midnight' is valid), the input file will be rotated daily at midnight.
96
- This is valid only for the 'hit_statistics' operation type.
97
- :param hit_statistics_enable_queue: boolean, if True, the statistics queue will be enabled.
98
- :param new_objects_hours_then_difference: float, currently works only for the 'dns' object_type.
99
- This is only for the 'new_objects' operation type.
100
- If the object is not in the list of objects, it will be added to the list.
101
- If the object is in the list of objects, it will be ignored.
102
- After the specified amount of hours, new objects will not be added to the input file list, so each new
103
- object will be outputted from the function. This is useful for checking new objects that are not
104
- supposed to be in the list of objects, but you want to know about them.
87
+ :param etw_session_name: string, the name of the ETW session. This should help you manage your ETW sessions
88
+ with logman and other tools: logman query -ets
89
+ If not provided, a default name will be generated.
90
+ 'dns': 'AtomicShopDnsTrace'
105
91
 
106
92
  If 'input_directory' is not specified, the 'input_file_name' is not specified, and
107
93
  'generate_input_file_name' is False, then the input file will not be used and the object will be stored
@@ -117,13 +103,14 @@ class ChangeMonitor:
117
103
  self.input_file_write_only: bool = input_file_write_only
118
104
  self.object_type = object_type
119
105
  self.object_type_settings: dict = object_type_settings
106
+ self.etw_session_name: str = etw_session_name
120
107
 
121
108
  # === Additional variables ========================================
122
109
 
123
110
  self.checks_instance = None
124
- self._setup_object()
111
+ self._setup_check()
125
112
 
126
- def _setup_object(self):
113
+ def _setup_check(self):
127
114
  if self.object_type == 'file':
128
115
  if not self.object_type_settings:
129
116
  self.object_type_settings = FILE__URL__DEFAULT_SETTINGS
@@ -8,6 +8,7 @@ from ...import diff_check
8
8
 
9
9
  INPUT_FILE_DEFAULT_NAME: str = 'known_domains.json'
10
10
  INPUT_STATISTICS_FILE_DEFAULT_NAME: str = 'dns_statistics.json'
11
+ ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopDnsTrace'
11
12
 
12
13
 
13
14
  class DnsCheck:
@@ -20,8 +21,17 @@ class DnsCheck:
20
21
  self.diff_checker_aggregation: Union[diff_check.DiffChecker, None] = None
21
22
  self.diff_checker_statistics: Union[diff_check.DiffChecker, None] = None
22
23
  self.settings: dict = change_monitor_instance.object_type_settings
24
+
25
+ if change_monitor_instance.etw_session_name:
26
+ self.etw_session_name: str = change_monitor_instance.etw_session_name
27
+ else:
28
+ self.etw_session_name: str = ETW_DEFAULT_SESSION_NAME
29
+
23
30
  self.fetch_engine: DnsTrace = (
24
- DnsTrace(enable_process_poller=True, attrs=['name', 'cmdline', 'domain', 'query_type']))
31
+ DnsTrace(
32
+ enable_process_poller=True, attrs=['name', 'cmdline', 'domain', 'query_type'],
33
+ session_name=self.etw_session_name)
34
+ )
25
35
 
26
36
  if self.settings['alert_always'] and self.settings['alert_about_missing_entries_after_learning']:
27
37
  raise ValueError(
@@ -1,19 +1,26 @@
1
- import pkg_resources
1
+ from importlib import resources
2
2
  import ctypes
3
3
  from ctypes import wintypes
4
4
  from typing import Literal
5
+ import threading
5
6
 
6
7
  from .basics import list_of_dicts
7
8
 
8
9
 
10
+ PACKAGE_DLL_PATH = 'addons/process_list/compiled/Win10x64/process_list.dll'
11
+
12
+
9
13
  class ProcessNameCmdline:
10
14
  def __init__(self, load_dll: bool = False):
11
15
  self.load_dll: bool = load_dll
12
- self.dll_path: str = pkg_resources.resource_filename(
13
- __package__, 'addons/process_list/compiled/Win10x64/process_list.dll')
16
+
14
17
  self.dll = None
15
18
  self.callback_output = None
16
19
  self.CALLBACKFUNC = None
20
+ self.CALLBACKFUNC_ref = None
21
+
22
+ with resources.path(__package__, PACKAGE_DLL_PATH) as dll_path:
23
+ self.dll_path = str(dll_path)
17
24
 
18
25
  if self.load_dll:
19
26
  self.load()
@@ -21,12 +28,16 @@ class ProcessNameCmdline:
21
28
  def load(self):
22
29
  """
23
30
  Load the DLL, initialize the callback function and ctypes.
24
- ctypes.WINFUCNTYPE is not thread safe. You should load it inside a thread / process and not outside.
31
+ ctypes.WINFUNCTYPE is not thread safe. You should load it inside a thread / process and not outside.
25
32
  """
26
33
  self.dll = ctypes.windll.LoadLibrary(self.dll_path)
27
34
  self.callback_output = OutputList()
28
35
  self.CALLBACKFUNC = ctypes.WINFUNCTYPE(None, wintypes.DWORD, wintypes.LPWSTR, wintypes.LPWSTR)
36
+ self.CALLBACKFUNC_ref = self.CALLBACKFUNC(self.callback_output.callback)
37
+
38
+ # Set the argument types for the export functions of the DLL.
29
39
  self.dll.GetProcessDetails.argtypes = [self.CALLBACKFUNC]
40
+ self.dll.RequestCancellation.restype = None
30
41
 
31
42
  def get_process_details(
32
43
  self,
@@ -49,23 +60,37 @@ class ProcessNameCmdline:
49
60
  }
50
61
  """
51
62
 
52
- self.dll.GetProcessDetails(self.CALLBACKFUNC(self.callback_output.callback))
63
+ def enumerate_process():
64
+ self.dll.GetProcessDetails(self.CALLBACKFUNC_ref)
65
+
66
+ thread = threading.Thread(target=enumerate_process)
67
+ thread.start()
68
+
69
+ try:
70
+ # This is needed to stop the thread if the main thread is interrupted.
71
+ # If we execute the 'self.dll.GetProcessDetails(self.CALLBACKFUNC_ref)' directly
72
+ # and we would like to KeyboardInterrupt, we will get an error:
73
+ # Exception ignored on calling ctypes callback function.
74
+ thread.join()
53
75
 
54
- processes = self.callback_output.data
76
+ processes = self.callback_output.data
55
77
 
56
- # Clear the callback output list, or it will be appended each time.
57
- self.callback_output.data = list()
78
+ # Clear the callback output list, or it will be appended each time.
79
+ self.callback_output.data = list()
58
80
 
59
- if sort_by:
60
- processes = list_of_dicts.sort_by_keys(processes, key_list=[sort_by])
81
+ if sort_by:
82
+ processes = list_of_dicts.sort_by_keys(processes, key_list=[sort_by])
61
83
 
62
- if as_dict:
63
- processes = convert_processes_to_dict(processes)
84
+ if as_dict:
85
+ processes = convert_processes_to_dict(processes)
64
86
 
65
- return processes
87
+ return processes
88
+ except KeyboardInterrupt:
89
+ self.dll.RequestCancellation()
90
+ raise
66
91
 
67
92
 
68
- def convert_processes_to_dict(process_list: dict) -> dict:
93
+ def convert_processes_to_dict(process_list: list[dict]) -> dict:
69
94
  """
70
95
  The function will convert the list of processes to dict, while 'pid' values will be converted to key.
71
96
  Example:
@@ -99,14 +124,23 @@ class OutputList:
99
124
 
100
125
  def callback(self, pid, process_name, cmdline):
101
126
  try:
127
+ process_name_decoded = process_name.decode("utf-16") if isinstance(process_name, bytes) else process_name
128
+ cmdline_decoded = cmdline.decode("utf-16") if isinstance(cmdline, bytes) else (
129
+ cmdline if cmdline else "Error")
102
130
  self.data.append({
103
131
  "pid": pid,
104
- "name": process_name.decode("utf-16"),
105
- "cmdline": cmdline.decode("utf-16") if cmdline else "Error"
132
+ "name": process_name_decoded,
133
+ "cmdline": cmdline_decoded
106
134
  })
107
- except AttributeError:
135
+ # except AttributeError:
136
+ # self.data.append({
137
+ # "pid": pid,
138
+ # "name": process_name,
139
+ # "cmdline": cmdline if cmdline else "Error"
140
+ # })
141
+ except Exception:
108
142
  self.data.append({
109
143
  "pid": pid,
110
- "name": process_name,
111
- "cmdline": cmdline if cmdline else "Error"
144
+ "name": "Error",
145
+ "cmdline": "Error"
112
146
  })
@@ -135,7 +135,7 @@ class ProcessPollerPool:
135
135
  self, store_cycles: int = 200,
136
136
  interval_seconds: Union[int, float] = 0,
137
137
  operation: Literal['thread', 'process'] = 'thread',
138
- poller_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll'
138
+ poller_method: Literal['psutil', 'pywin32', 'process_dll'] = 'process_dll',
139
139
  ):
140
140
  """
141
141
  :param store_cycles: int, how many cycles to store. Each cycle is polling processes.
@@ -162,6 +162,14 @@ class ProcessPollerPool:
162
162
  'psutil': Get the list of processes by 'psutil' library. Resource intensive and slow.
163
163
  'pywin32': Get the list of processes by 'pywin32' library, using WMI. Not resource intensive, but slow.
164
164
  'process_dll'. Not resource intensive and fast. Probably works only in Windows 10 x64.
165
+ ---------------------------------------------
166
+ If there is an exception, ProcessPollerPool.processes will be set to the exception.
167
+ While getting the processes you can use this to execute the exception:
168
+
169
+ processes = ProcessPollerPool.processes
170
+
171
+ if isinstance(processes, BaseException):
172
+ raise processes
165
173
  """
166
174
 
167
175
  self.store_cycles: int = store_cycles
@@ -193,44 +201,60 @@ class ProcessPollerPool:
193
201
  def _start_thread(self):
194
202
  self.running = True
195
203
  # threading.Thread(target=self._worker, args=(self.process_polling_instance,)).start()
196
- threading.Thread(target=self._worker).start()
204
+ thread = threading.Thread(target=self._worker)
205
+ thread.daemon = True
206
+ thread.start()
197
207
 
198
208
  def _start_process(self):
199
209
  self.running = True
200
- multiprocessing.Process(target=self._worker).start()
201
- threading.Thread(target=self._thread_get_queue).start()
210
+ stopping_queue = multiprocessing.Queue()
211
+ multiprocessing.Process(target=self._worker, args=(stopping_queue,)).start()
212
+
213
+ thread = threading.Thread(target=self._thread_get_queue)
214
+ thread.daemon = True
215
+ thread.start()
202
216
 
203
- # def _worker(self, process_polling_instance):
204
217
  def _worker(self):
205
218
  # We must initiate the connection inside the thread/process, because it is not thread-safe.
206
219
  self.get_processes_list.connect()
207
220
 
221
+ exception = None
208
222
  list_of_processes: list = list()
209
223
  while self.running:
210
- # If the list is full (to specified 'store_cycles'), remove the first element.
211
- if len(list_of_processes) == self.store_cycles:
212
- del list_of_processes[0]
213
-
214
- # Get the current processes and reinitialize the instance of the dict.
215
- current_processes: dict = dict(self.get_processes_list.get_processes())
216
-
217
- # Remove Command lines that contains only numbers, since they are useless.
218
- for pid, process_info in current_processes.items():
219
- if process_info['cmdline'].isnumeric():
220
- current_processes[pid]['cmdline'] = str()
221
- elif process_info['cmdline'] == 'Error':
222
- current_processes[pid]['cmdline'] = str()
223
-
224
- # Append the current processes to the list.
225
- list_of_processes.append(current_processes)
226
-
227
- # Merge all dicts in the list to one dict, updating with most recent PIDs.
228
- self.processes = list_of_dicts.merge_to_dict(list_of_processes)
229
-
230
- if self.operation == 'process':
231
- self.queue.put(self.processes)
232
-
233
- time.sleep(self.interval_seconds)
224
+ try:
225
+ # If the list is full (to specified 'store_cycles'), remove the first element.
226
+ if len(list_of_processes) == self.store_cycles:
227
+ del list_of_processes[0]
228
+
229
+ # Get the current processes and reinitialize the instance of the dict.
230
+ current_processes: dict = dict(self.get_processes_list.get_processes())
231
+
232
+ # Remove Command lines that contains only numbers, since they are useless.
233
+ for pid, process_info in current_processes.items():
234
+ if process_info['cmdline'].isnumeric():
235
+ current_processes[pid]['cmdline'] = str()
236
+ elif process_info['cmdline'] == 'Error':
237
+ current_processes[pid]['cmdline'] = str()
238
+
239
+ # Append the current processes to the list.
240
+ list_of_processes.append(current_processes)
241
+
242
+ # Merge all dicts in the list to one dict, updating with most recent PIDs.
243
+ self.processes = list_of_dicts.merge_to_dict(list_of_processes)
244
+
245
+ if self.operation == 'process':
246
+ self.queue.put(self.processes)
247
+
248
+ time.sleep(self.interval_seconds)
249
+ except KeyboardInterrupt as e:
250
+ self.running = False
251
+ exception = e
252
+ except Exception as e:
253
+ self.running = False
254
+ exception = e
255
+
256
+ if not self.running:
257
+ self.queue.put(exception)
234
258
 
235
259
  def _thread_get_queue(self):
236
260
  while True:
atomicshop/scheduling.py CHANGED
@@ -12,7 +12,7 @@ def periodic_task(interval, priority, function_ref, args=(), sched_object=None):
12
12
  sched_object.run()
13
13
 
14
14
 
15
- def threaded_periodic_task(interval, function_ref, args=(), kwargs=None, thread_name=None):
15
+ def threaded_periodic_task(interval, function_ref, args=(), kwargs=None, thread_name=None, daemon=True):
16
16
  """
17
17
  The function executes referenced function 'function_ref' with arguments 'args' each 'interval' in a new thread.
18
18
  The old thread is closed, each time the new is executed.
@@ -24,6 +24,10 @@ def threaded_periodic_task(interval, function_ref, args=(), kwargs=None, thread_
24
24
  :param thread_name: the name of the thread that will be created:
25
25
  threading.Thread(target=thread_timer, name=thread_name).start()
26
26
  The default parameter for 'Thread' 'name' is 'None', so if you don't specify the name it works as default.
27
+ :param daemon: bool, if True, the thread will be a daemon thread. Default is True.
28
+ Since this is a periodic task, we don't need to wait for the thread to finish, so we can set it to True.
29
+
30
+ :return: thread object.
27
31
  """
28
32
 
29
33
  # If 'kwargs' is not provided, we'll initialize it as an empty dictionary.
@@ -50,7 +54,13 @@ def threaded_periodic_task(interval, function_ref, args=(), kwargs=None, thread_
50
54
  time.sleep(interval)
51
55
 
52
56
  # Start in a new thread.
53
- threading.Thread(target=thread_timer, name=thread_name).start()
57
+ thread = threading.Thread(target=thread_timer, name=thread_name)
58
+
59
+ if daemon:
60
+ thread.daemon = True
61
+
62
+ thread.start()
63
+ return thread
54
64
 
55
65
 
56
66
  class ThreadLooper:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.12.22
3
+ Version: 2.12.23
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=g2vsW13t8sldcuaaZZDrssh2JaK2htDJxU8zZNrBFoM,124
1
+ atomicshop/__init__.py,sha256=8aamirvtHe04uZCHVaHjJnr7Y7nVlrXMflv_x72OTIo,124
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
@@ -25,13 +25,13 @@ atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erX
25
25
  atomicshop/permissions.py,sha256=P6tiUKV-Gw-c3ePEVsst9bqWaHJbB4ZlJB4xbDYVpEs,4436
26
26
  atomicshop/print_api.py,sha256=DhbCQd0MWZZ5GYEk4oTu1opRFC-b31g1VWZgTGewG2Y,11568
27
27
  atomicshop/process.py,sha256=Zgb4CUjy9gIBaawvtCOEcxGUCqvqPyARk0lpBjRzxWE,15950
28
- atomicshop/process_name_cmd.py,sha256=TNAK6kQZm5JKWzEW6QLqVHEG98ZLNDQiSS4YwDk8V8c,3830
29
- atomicshop/process_poller.py,sha256=WfmwCLALfTYOq8ri0vkPeqq8ruEyA_43DaN--CU2_XY,10854
28
+ atomicshop/process_name_cmd.py,sha256=YmAfxZ3fhND58ItJ1fA1C6tPqyNUBlolOM2smKVDK2o,5167
29
+ atomicshop/process_poller.py,sha256=Ya08tLe6NfbJ-U3EZWZ3j-FFqkDSy8zaTu1QeEsWC98,11754
30
30
  atomicshop/python_file_patcher.py,sha256=kd3rBWvTcosLEk-7TycNdfKW9fZbe161iVwmH4niUo0,5515
31
31
  atomicshop/python_functions.py,sha256=zJg4ogUwECxrDD7xdDN5JikIUctITM5lsyabr_ZNsRw,4435
32
32
  atomicshop/question_answer_engine.py,sha256=DuOn7QEgKKfqZu2cR8mVeFIfFgayfBHiW-jY2VPq_Fo,841
33
33
  atomicshop/queues.py,sha256=Al0fdC3ZJmdKfv-PyBeIck9lnfLr82BYchvzr189gsI,640
34
- atomicshop/scheduling.py,sha256=YiHOIAI7V20bH5qcqf3c1NNkmkLk3EYr3i19CrlTkLw,4214
34
+ atomicshop/scheduling.py,sha256=NF1_csXwez4RbHoRyUXQg1pGdswGmS1WdSGAQ54m6R8,4550
35
35
  atomicshop/script_as_string_processor.py,sha256=JKENiARNuKQ0UegP2GvIVgNPbr6CzGiMElUVTddOX3A,1776
36
36
  atomicshop/sound.py,sha256=KSzWRF8dkpEVXmFidIv-Eftc3kET-hQzQOxZRE7rMto,24297
37
37
  atomicshop/speech_recognize.py,sha256=55-dIjgkpF93mvJnJuxSFuft5H5eRvGNlUj9BeIOZxk,5903
@@ -64,11 +64,11 @@ atomicshop/addons/package_setup/CreateWheel.cmd,sha256=hq9aWBSH6iffYlZyaCNrFlA0v
64
64
  atomicshop/addons/package_setup/Setup in Edit mode.cmd,sha256=299RsExjR8Mup6YyC6rW0qF8lnwa3uIzwk_gYg_R_Ss,176
65
65
  atomicshop/addons/package_setup/Setup.cmd,sha256=IMm0PfdARH7CG7h9mbWwmWD9X47l7tddwQ2U4MUxy3A,213
66
66
  atomicshop/addons/process_list/ReadMe.txt,sha256=7H-pX7TEkn6-nYSvYc00U6B1JZvBpQvhuVBxCH8IDuc,1895
67
- atomicshop/addons/process_list/compile.cmd,sha256=oCXoYNx2RGmI4BfspYhYq9K6fM7D97jQ3WMu4XsNe1Y,281
68
- atomicshop/addons/process_list/process_list.cpp,sha256=e7olpLfLVg9vQnjEr5L2Y8aWGPGpkH39vmz51CaGFcc,5467
69
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll,sha256=SkAZvYAfSbzQTTq-5aL6_dYR2rA4DHbgyenFfgLFzW0,266752
70
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp,sha256=VTph513eqa6f6HmqAj_6mBS1Rf9G56bgYqZNuDePYcs,708
71
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib,sha256=n9c2MVPs3GBNoOQjMesAwzNpv5aFZsW8c-ADS7GYRhA,1886
67
+ atomicshop/addons/process_list/compile.cmd,sha256=cJSHinOXGl-WkPAq39GdEo5tLLZDg8zdNKIibqf568U,649
68
+ atomicshop/addons/process_list/process_list.cpp,sha256=kcrltiJhzRc2HmKy2Yxdbj7mFLN9O_b4NPDo1xPggM0,5660
69
+ atomicshop/addons/process_list/compiled/Win10x64/process_list.dll,sha256=rl74P7oh4UZyo3cILmtDFznurcHNRMrZ56tOTv5pJqk,276992
70
+ atomicshop/addons/process_list/compiled/Win10x64/process_list.exp,sha256=cbvukITcmtGm5uwOIuq8bKCE_LXIHvkMfLJQpBrJk64,842
71
+ atomicshop/addons/process_list/compiled/Win10x64/process_list.lib,sha256=T2Ncs0MwKlAaCq8UKFMvfQAfcJdDx-nWiHVBfglrLIU,2112
72
72
  atomicshop/archiver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  atomicshop/archiver/_search_in_zip.py,sha256=dd8qFSvIhcKmtnPj_uYNJFPmMwZp4tZys0kKgTw_ACw,8385
74
74
  atomicshop/archiver/archiver.py,sha256=BomnK7zT-nQXA1z0i2R2aTv8eu88wPx7tf2HtOdbmEc,1280
@@ -79,6 +79,7 @@ atomicshop/archiver/zips.py,sha256=k742K1bEDtc_4N44j_Waebi-uOkxxavqltvV6q-BLW4,1
79
79
  atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
80
  atomicshop/basics/ansi_escape_codes.py,sha256=WtIkm-BjSZS5J5irDUdAMBNvdX-qXFZcTX98jcBMpJE,3140
81
81
  atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
82
+ atomicshop/basics/atexits.py,sha256=w7ge8sjuqK2_jdkdP4F4OCltkenQud8AhVEzxhFDiao,2145
82
83
  atomicshop/basics/booleans.py,sha256=va3LYIaSOhjdifW4ZEesnIQxBICNHyQjUAkYelzchhE,2047
83
84
  atomicshop/basics/bytes_arrays.py,sha256=WvSRDhIGt1ywF95t-yNgpxLm1nlZUbM1Dz6QckcyE8Y,5915
84
85
  atomicshop/basics/classes.py,sha256=EijW_g4EhdNBnKPMG3nT3HjFspTchtM7to6zm9Ad_Mk,9771
@@ -101,8 +102,8 @@ atomicshop/basics/threads.py,sha256=xvgdDJdmgN0wmmARoZ-H7Kvl1GOcEbvgaeGL4M3Hcx8,
101
102
  atomicshop/basics/timeit_template.py,sha256=fYLrk-X_dhdVtnPU22tarrhhvlggeW6FdKCXM8zkX68,405
102
103
  atomicshop/basics/tracebacks.py,sha256=cNfh_oAwF55kSIdqtv3boHZQIoQI8TajxkTnwJwpweI,535
103
104
  atomicshop/etw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
- atomicshop/etw/dns_trace.py,sha256=I4OZsiZUDyj7B4fKTOqsB1tcX1DUMw9uh4CwXlcmHfY,5571
105
- atomicshop/etw/etw.py,sha256=xVJNbfCq4KgRfsDnul6CrIdAMl9xRBixZ-hUyqiB2g4,2403
105
+ atomicshop/etw/dns_trace.py,sha256=rv1A5akgebLzoT5EcvyuNwIztnA2xJYZlZlAbINFe88,5916
106
+ atomicshop/etw/etw.py,sha256=WEs1aqXowH-tKUbaJphhc1hQvpi4SqU3ltyBg5wGM0c,4470
106
107
  atomicshop/file_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
108
  atomicshop/file_io/csvs.py,sha256=y8cJtnlN-NNxNupzJgSeGq9aQ4wNxYLFPX9vNNlUiIc,5830
108
109
  atomicshop/file_io/docxs.py,sha256=6tcYFGp0vRsHR47VwcRqwhdt2DQOwrAUYhrwN996n9U,5117
@@ -132,9 +133,9 @@ atomicshop/mitm/engines/__reference_general/parser___reference_general.py,sha256
132
133
  atomicshop/mitm/engines/__reference_general/recorder___reference_general.py,sha256=KENDVf9OwXD9gwSh4B1XxACCe7iHYjrvnW1t6F64wdE,695
133
134
  atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha256=1AM49UaFTKA0AHw-k3SV3uH3QbG-o6ux0c-GoWkKNU0,6993
134
135
  atomicshop/monitor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
- atomicshop/monitor/change_monitor.py,sha256=mrLedkgXWxKzQLqjYd3bmD8-or6HlX9sejdXbSvhNMI,9016
136
+ atomicshop/monitor/change_monitor.py,sha256=dGhk5bJPxLCHa2FOVkort99E7vjVojra9GlvhpcKSqE,7551
136
137
  atomicshop/monitor/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
- atomicshop/monitor/checks/dns.py,sha256=bPqQACt8IgQ08U-x9G7pAN-iSfxbdOAubJTHZd-lJ84,6792
138
+ atomicshop/monitor/checks/dns.py,sha256=v3Pgdi8tmBmGAs7x7u6N5IiGyTv6GZOLEbrVSZZGE6s,7150
138
139
  atomicshop/monitor/checks/file.py,sha256=2tIDSlX2KZNc_9i9ji1tcOqupbFTIOj7cKXLyBEDWMk,3263
139
140
  atomicshop/monitor/checks/hash.py,sha256=r4mDOdjItN3eyaJk8TVz03f4bQ_uKZ4MDTXagjseazs,2011
140
141
  atomicshop/monitor/checks/network.py,sha256=CGZWl4WlQrxayZeVF9JspJXwYA-zWx8ECWTVGSlXc98,3825
@@ -254,8 +255,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=AhpurHJmP2kgzHaUbq5ey
254
255
  atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
255
256
  atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
256
257
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
257
- atomicshop-2.12.22.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
258
- atomicshop-2.12.22.dist-info/METADATA,sha256=754IBZW-ExlLv33i93QGyMKGQOEXOrw1uIsXmERVvKM,10479
259
- atomicshop-2.12.22.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
260
- atomicshop-2.12.22.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
261
- atomicshop-2.12.22.dist-info/RECORD,,
258
+ atomicshop-2.12.23.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
259
+ atomicshop-2.12.23.dist-info/METADATA,sha256=mDSQpG6SXbiytprFFrq70833uv_No7bDq5EtU_2q3xw,10479
260
+ atomicshop-2.12.23.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
261
+ atomicshop-2.12.23.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
262
+ atomicshop-2.12.23.dist-info/RECORD,,