atomicshop 2.15.11__py3-none-any.whl → 3.10.5__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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
atomicshop/on_exit.py CHANGED
@@ -91,25 +91,55 @@ class ExitHandler:
91
91
  # Exit the process gracefully
92
92
  raise SystemExit(0)
93
93
 
94
- def register_handlers(self):
95
- win32api.SetConsoleCtrlHandler(self.console_handler, True)
96
- atexit.register(self.atexit_handler)
97
- signal.signal(signal.SIGINT, self.signal_handler)
98
- signal.signal(signal.SIGTERM, self.signal_handler)
99
-
100
-
101
- def register_exit_handler(clean_up_function, *args, **kwargs):
94
+ def register_handlers(
95
+ self,
96
+ at_exit: bool = True,
97
+ console_close: bool = True,
98
+ kill_signal: bool = True
99
+ ):
100
+ """
101
+ Register the exit handlers.
102
+
103
+ :param at_exit: Register the atexit handler.
104
+ Just remember that the atexit handler will be called right away on [Ctrl+C], meaning if you want to do
105
+ something specifically on KeyboardInterrupt, you should handle it separately and set this parameter to False.
106
+ Same goes for all the exceptions.
107
+ :param console_close: Register the console close handler.
108
+ :param kill_signal: Register the kill signal handler.
109
+ """
110
+ if at_exit:
111
+ atexit.register(self.atexit_handler)
112
+ if console_close:
113
+ win32api.SetConsoleCtrlHandler(self.console_handler, True)
114
+ if kill_signal:
115
+ signal.signal(signal.SIGINT, self.signal_handler)
116
+ signal.signal(signal.SIGTERM, self.signal_handler)
117
+
118
+
119
+ def register_exit_handler(
120
+ clean_up_function,
121
+ at_exit: bool = True,
122
+ console_close: bool = True,
123
+ kill_signal: bool = True,
124
+ *args, **kwargs):
102
125
  """
103
126
  This function will register the exit handler to handle exit events: Closing the console, pressing 'CTRL+C',
104
127
  Killing the process.
105
128
 
106
129
  :param clean_up_function: The action to run when one of exit types is triggered.
130
+ :param at_exit: Register the atexit handler.
131
+ Just remember that the atexit handler will be called right away on [Ctrl+C], meaning if you want to do something
132
+ specifically on KeyboardInterrupt, you should handle it separately and set this parameter to False.
133
+ Same goes for all the exceptions.
134
+ :param console_close: Register the console close handler.
135
+ :param kill_signal: Register the kill signal handler.
136
+ Same problem as with atexit handler, it will be called right away on [Ctrl+C].
107
137
  :param args: The arguments to pass to the cleanup action.
108
138
  :param kwargs: The keyword arguments to pass to the cleanup action.
109
139
  """
110
140
  global EXIT_HANDLER_INSTANCE
111
141
  EXIT_HANDLER_INSTANCE = ExitHandler(clean_up_function, args, kwargs)
112
- EXIT_HANDLER_INSTANCE.register_handlers()
142
+ EXIT_HANDLER_INSTANCE.register_handlers(at_exit=at_exit, console_close=console_close, kill_signal=kill_signal)
113
143
 
114
144
 
115
145
  def restart_function(callable_function, *args, **kwargs):
@@ -0,0 +1,84 @@
1
+ """Loading resources using stdlib importlib.resources APIs (Python 3.7+)
2
+ https://docs.python.org/3/library/importlib.html#module-importlib.resources"""
3
+ import importlib.resources
4
+ from contextlib import redirect_stdout
5
+ import io
6
+ import subprocess
7
+ import sys
8
+ from typing import Callable
9
+
10
+
11
+ class PackageMainsProcessor:
12
+ def __init__(
13
+ self,
14
+ script_file_stem: str = None
15
+ ):
16
+ self.script_file_stem: str = script_file_stem
17
+ self.resources_directory_name: str = 'a_mains'
18
+
19
+ def get_resource_path(self) -> str:
20
+ return f'{__package__}.{self.resources_directory_name}'
21
+
22
+ def read_script_file_to_string(self) -> str:
23
+ script_string = importlib.resources.read_text(self.get_resource_path(), f'{self.script_file_stem}.py')
24
+
25
+ return script_string
26
+
27
+ def execute_script_file(
28
+ self,
29
+ function_name: str = 'main',
30
+ args: tuple = None,
31
+ kwargs: dict = None,
32
+ get_printed_output: bool = False
33
+ ) -> str:
34
+ """
35
+ Execute a script file from the package resources and get result as string.
36
+
37
+ :param function_name: Name of the function to call within the script.
38
+ :param args: Tuple of positional arguments to pass to the function.
39
+ :param kwargs: Dictionary of keyword arguments to pass to the function.
40
+ :param get_printed_output: If True, captures and returns printed output instead of return value.
41
+
42
+ :return: Output of the script execution as a string.
43
+ """
44
+
45
+ if not args:
46
+ args = ()
47
+ if not kwargs:
48
+ kwargs = {}
49
+
50
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}" # script_file_name WITHOUT ".py"
51
+
52
+ module = importlib.import_module(module_name)
53
+ callable_function: Callable = getattr(module, function_name)
54
+
55
+ if get_printed_output:
56
+ with io.StringIO() as buffer, redirect_stdout(buffer):
57
+ callable_function(*args, **kwargs)
58
+ result = buffer.getvalue()
59
+ else:
60
+ result = callable_function(*args, **kwargs)
61
+
62
+ return result
63
+
64
+ def execute_script_with_subprocess(
65
+ self,
66
+ arguments: list = None
67
+ ) -> tuple[str, str, int]:
68
+ """
69
+ Execute a script file from the package resources using subprocess and get result as string.
70
+ :param arguments: Dictionary of arguments to pass to the script.
71
+ Example: ['--port', '8080', '-v']
72
+ :return: Tuple containing (stdout, stderr, returncode).
73
+ """
74
+
75
+ # script_file_name WITHOUT ".py"
76
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}"
77
+
78
+ command = [sys.executable, "-m", module_name]
79
+ if arguments:
80
+ command.extend(arguments)
81
+
82
+ result = subprocess.run(command, capture_output=True, text=True)
83
+
84
+ return result.stdout, result.stderr, result.returncode
@@ -0,0 +1,22 @@
1
+ import os
2
+ import ctypes
3
+
4
+
5
+ def is_admin() -> bool:
6
+ """
7
+ Function checks on Windows or POSIX OSes if the script is executed under Administrative Privileges.
8
+ :return: True / False.
9
+ """
10
+
11
+ if os.name == 'nt':
12
+ if ctypes.windll.shell32.IsUserAnAdmin() == 0:
13
+ result = False
14
+ else:
15
+ result = True
16
+ else:
17
+ if 'SUDO_USER' in os.environ and os.geteuid() == 0:
18
+ result = True
19
+ else:
20
+ result = False
21
+
22
+ return result
@@ -0,0 +1,239 @@
1
+ import os
2
+ import stat
3
+ import contextlib
4
+ import subprocess
5
+ import getpass
6
+
7
+ # Import pwd only on linux.
8
+ if os.name == 'posix':
9
+ import pwd
10
+
11
+
12
+ def get_sudo_executer_username() -> str:
13
+ """
14
+ Function gets the username of the user who executed the script with sudo.
15
+ :return: str, username.
16
+ """
17
+
18
+ if 'SUDO_USER' in os.environ:
19
+ return os.environ['SUDO_USER']
20
+ else:
21
+ return ''
22
+
23
+
24
+ def detect_current_user(
25
+ optional_env_user_var: str = 'CUSTOM_SCRIPTED_USER'
26
+ ) -> str:
27
+ """
28
+ Try to robustly determine the 'real' installing user.
29
+
30
+ Priority:
31
+ 1. FDB_INSTALL_USER env var (explicit override).
32
+ 2. If running as root with sudo: use SUDO_USER.
33
+ 3. Otherwise: use effective uid.
34
+ 4. Fallbacks: getpass.getuser() / $USER.
35
+
36
+ :param optional_env_user_var: str, name of the environment variable that can override the user detection.
37
+ :return: str, username.
38
+ """
39
+
40
+ # 1. Explicit override for weird environments (CI, containers, etc.)
41
+ env_user = os.getenv(optional_env_user_var)
42
+ if env_user:
43
+ return env_user
44
+
45
+ # 2. If we are root, prefer the sudo caller if any
46
+ try:
47
+ euid = os.geteuid()
48
+ except AttributeError: # non-POSIX, very unlikely here
49
+ euid = None
50
+
51
+ if euid == 0:
52
+ sudo_user = os.environ.get("SUDO_USER")
53
+ if sudo_user:
54
+ return sudo_user
55
+
56
+ # 3. Normal case: effective uid -> username
57
+ if euid is not None:
58
+ try:
59
+ return pwd.getpwuid(euid).pw_name
60
+ except Exception:
61
+ pass
62
+
63
+ # 4. Fallbacks that don’t depend on utmp/tty
64
+ try:
65
+ return getpass.getuser()
66
+ except Exception:
67
+ return os.environ.get("USER", "unknown")
68
+
69
+
70
+ def set_executable(file_path: str):
71
+ """
72
+ Function sets the executable permission on a file.
73
+ Equivalent to: chmod +x <file_path>
74
+
75
+ :param file_path: str, path to the file.
76
+ :return:
77
+ """
78
+
79
+ # os.chmod(file_path, os.stat(file_path).st_mode | 0o111)
80
+ os.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR)
81
+
82
+
83
+ def set_trusted_executable(file_path: str):
84
+ """
85
+ Function sets the executable permission on a file and marks it as trusted.
86
+ :param file_path: str, path to the file.
87
+ :return:
88
+ """
89
+
90
+ # Check if the file exists
91
+ if not os.path.exists(file_path):
92
+ raise FileNotFoundError(f"The file does not exist: {file_path} ")
93
+
94
+ # Execute the `gio set` command
95
+ subprocess.run(
96
+ ["gio", "set", file_path, "metadata::trusted", "true"],
97
+ check=True
98
+ )
99
+
100
+
101
+ def set_xfce_exe_checksum(desktop_file_path):
102
+ # Expand `~` to the full home directory path
103
+ desktop_file_path = os.path.expanduser(desktop_file_path)
104
+
105
+ # Ensure the file exists
106
+ if not os.path.exists(desktop_file_path):
107
+ raise FileNotFoundError(f"The file does not exist: {desktop_file_path} ")
108
+
109
+ # Calculate the SHA256 checksum of the file
110
+ result = subprocess.run(
111
+ ["sha256sum", desktop_file_path],
112
+ stdout=subprocess.PIPE,
113
+ check=True,
114
+ text=True
115
+ )
116
+ checksum = result.stdout.split()[0]
117
+
118
+ # Set the metadata::xfce-exe-checksum attribute using `gio`
119
+ subprocess.run(
120
+ ["gio", "set", "-t", "string", desktop_file_path, "metadata::xfce-exe-checksum", checksum],
121
+ check=True
122
+ )
123
+
124
+
125
+ def change_file_owner(file_path: str, username: str):
126
+ """
127
+ Function changes the owner of the file to the specified user.
128
+ :param file_path: str, path to the file.
129
+ :param username: str, username of the new owner.
130
+ :return:
131
+ """
132
+
133
+ uid = pwd.getpwnam(username).pw_uid
134
+ os.chown(file_path, uid, -1)
135
+
136
+
137
+ def is_executable(file_path: str) -> bool:
138
+ """
139
+ Function checks if the file has the executable permission.
140
+ Equivalent to: stat -c "%a %n" <file_path>
141
+
142
+ :param file_path: str, path to the file.
143
+ :return: bool, True / False.
144
+ """
145
+
146
+ return bool(os.stat(file_path).st_mode & stat.S_IXUSR)
147
+
148
+
149
+ def run_as_root(command):
150
+ subprocess.check_call(['sudo'] + command)
151
+
152
+
153
+ @contextlib.contextmanager
154
+ def temporary_regular_permissions():
155
+ """
156
+ This function is used to temporarily change the effective user and group ID to the original user's.
157
+ This is used to run commands with the original user's permissions.
158
+ If you executed a script with 'sudo' and wanted certain action to execute as regular user and not root.
159
+
160
+ Example:
161
+ with temporary_regular_permissions():
162
+ # Do something with regular permissions.
163
+ pass
164
+
165
+ :return:
166
+ """
167
+ # Save the current effective user and group ID
168
+ original_euid, original_egid = os.geteuid(), os.getegid()
169
+
170
+ try:
171
+ # Get the original user's UID and GID
172
+ orig_uid = int(os.environ.get('SUDO_UID', os.getuid()))
173
+ orig_gid = int(os.environ.get('SUDO_GID', os.getgid()))
174
+
175
+ # Set the effective user and group ID to the original user's
176
+ os.setegid(orig_gid)
177
+ os.seteuid(orig_uid)
178
+
179
+ # Provide the context to do something with these permissions
180
+ yield
181
+ finally:
182
+ # Revert to the original effective user and group ID
183
+ os.seteuid(original_euid)
184
+ os.setegid(original_egid)
185
+
186
+
187
+ def expand_user_path(user_name, path):
188
+ pwnam = pwd.getpwnam(user_name)
189
+ home_dir = pwnam.pw_dir
190
+ return path.replace("~", home_dir)
191
+
192
+
193
+ def set_folder_permissions(
194
+ folder_path: str,
195
+ username: str = None,
196
+ logged_in_non_sudo_user: bool = False
197
+ ):
198
+ """
199
+ Set ownership and permissions for an existing folder.
200
+
201
+ :param folder_path: Path to the folder (must already exist)
202
+ :param username: Username to assign ownership to (ignored if non_sudo_user=True)
203
+ :param logged_in_non_sudo_user: If True, use the current logged-in user unless running under sudo
204
+ """
205
+
206
+ if not username and not logged_in_non_sudo_user:
207
+ raise ValueError("A username must be provided, or 'non_sudo_user' must be set to True.")
208
+
209
+ # Handle non_sudo_user case
210
+ if logged_in_non_sudo_user:
211
+ # Get the current logged-in user
212
+ username = pwd.getpwuid(os.getuid())[0]
213
+
214
+ # Get the UID and GID of the specified user
215
+ user_info = pwd.getpwnam(username)
216
+ user_uid = user_info.pw_uid
217
+ user_gid = user_info.pw_gid
218
+
219
+ # Change ownership of the folder to the specified user
220
+ # print(f"Changing ownership of {folder_path} to user '{username}'...")
221
+ os.chown(folder_path, user_uid, user_gid)
222
+
223
+ # Set appropriate permissions (read, write, execute for the owner)
224
+ # print(f"Setting permissions for {folder_path}...")
225
+ os.chmod(folder_path, 0o755) # Owner rwx, group r-x, others r-x
226
+
227
+ # print(f"Ownership and permissions updated for folder: '{folder_path}'")
228
+
229
+
230
+ def is_directory_owner(directory_path: str, username: str) -> bool:
231
+ """
232
+ Function checks if the directory is owned by the specified user.
233
+ :param directory_path: str, path to the directory.
234
+ :param username: str, username of the user.
235
+ :return: bool, True / False.
236
+ """
237
+
238
+ uid = pwd.getpwnam(username).pw_uid
239
+ return os.stat(directory_path).st_uid == uid
@@ -0,0 +1,33 @@
1
+ import subprocess
2
+
3
+
4
+ def unblock_file_windows(file_path):
5
+ """
6
+ Unblock a file on Windows. This is used to unblock files downloaded from the internet.
7
+ When you Right-click then navigate to Properties, you will see the Unblock checkbox.
8
+ :param file_path:
9
+ :return:
10
+ """
11
+ try:
12
+ subprocess.run(["powershell", "-Command", f"Unblock-File -Path '{file_path}'"], check=True)
13
+ print(f"Successfully unblocked the file: {file_path}")
14
+ except subprocess.CalledProcessError as e:
15
+ print(f"Failed to unblock the file: {file_path}\nError: {e}")
16
+
17
+
18
+ def get_command_to_run_as_admin_windows(command: str) -> str:
19
+ """
20
+ Function returns the command to run a command as administrator on Windows.
21
+ NOTE: When you run something this way, the parent will be the powershell.exe process.
22
+ If you need a status result directly of the executed command, you need to use subprocess.run() instead.
23
+
24
+ :param command: str, command to run.
25
+ :return: str, command to run as administrator.
26
+ """
27
+
28
+ executable = command.split()[0]
29
+ command = (
30
+ f"powershell -Command "
31
+ f"\"Start-Process {executable} -ArgumentList '{' '.join(command.split()[1:])}' -Verb RunAs\"")
32
+
33
+ return command
atomicshop/print_api.py CHANGED
@@ -1,17 +1,18 @@
1
1
  import sys
2
+ import logging
3
+ from typing import Any
2
4
 
3
- from .basics.ansi_escape_codes import ColorsBasic, get_colors_basic_dict
5
+ from .basics import ansi_escape_codes
4
6
  from .basics import tracebacks
5
7
 
6
8
 
7
- # noinspection PyUnusedLocal,PyIncorrectDocstring
8
9
  def print_api(
9
- message: any,
10
- color: any = None,
10
+ message: Any,
11
+ color: Any = None,
11
12
  print_end: str = '\n',
12
13
  rtl: bool = False,
13
14
  error_type: bool = False,
14
- logger: object = None,
15
+ logger: logging.Logger = None,
15
16
  logger_method: str = 'info',
16
17
  stdout: bool = True,
17
18
  stderr: bool = True,
@@ -19,8 +20,6 @@ def print_api(
19
20
  traceback_string: bool = False,
20
21
  oneline: bool = False,
21
22
  oneline_end: str = '',
22
- # raise_exception: bool = True,
23
- # exit_on_error: bool = False,
24
23
  **kwargs: object) -> None:
25
24
  """
26
25
  Function of custom api that is responsible for printing messages to console.
@@ -82,18 +81,26 @@ def print_api(
82
81
 
83
82
  # This section takes care of different types of string manipulations for message.
84
83
 
85
- # If 'exit_on_error' is set to 'True', we'll add 'exit_message' on new line after 'message'.
86
- # if error_type and exit_on_error and raise_exception:
87
- # message = message + '\n' + exit_message
88
-
89
84
  # If 'rtl' is set to 'True', we'll add Right-To-Left text conversion to 'message'.
90
85
  if rtl:
91
- # Lazy importing of 'bidi' library. It's not a problem since python caches the library after first import.
92
- # Off-course, it will be imported from the cache each time this section is triggered.
93
86
  # pip install python-bidi
94
87
  from bidi.algorithm import get_display
95
88
  message = get_display(message)
96
89
 
90
+ if logger_method == 'error' or logger_method == 'critical':
91
+ error_type = True
92
+
93
+ # If exception was raised and 'stderr=True'.
94
+ if sys.exc_info()[0] is not None and stderr and traceback_string:
95
+ # If 'traceback' is set to 'True', we'll output traceback of exception.
96
+ if traceback_string:
97
+ if message:
98
+ message = f'{message}\n{tracebacks.get_as_string()}{message}'
99
+ else:
100
+ message = tracebacks.get_as_string()
101
+
102
+ color = 'red'
103
+
97
104
  # If 'stdcolor' is 'True', the console output will be colored.
98
105
  if stdcolor:
99
106
  # If 'logger.error' should be outputted to console, and 'color' wasn't selected, then set color to 'yellow'.
@@ -103,19 +110,8 @@ def print_api(
103
110
  elif logger_method == 'critical' and not color:
104
111
  color = 'red'
105
112
 
106
- if color:
107
- message = get_colors_basic_dict(color) + message + ColorsBasic.END
108
-
109
- if logger_method == 'error' or logger_method == 'critical':
110
- error_type = True
111
-
112
- # If exception was raised and 'stderr=True'.
113
- if sys.exc_info()[0] is not None and stderr:
114
- # If 'traceback' is set to 'True', we'll output traceback of exception.
115
- if traceback_string:
116
- message = f'{message} | Exception: {tracebacks.get_as_string()}'
117
-
118
- color = 'red'
113
+ if color is not None:
114
+ message = ansi_escape_codes.get_colors_basic_dict(color) + message + ansi_escape_codes.ColorsBasic.END
119
115
 
120
116
  # If 'online' is set to 'True', we'll output message as oneline.
121
117
  if oneline:
@@ -130,14 +126,14 @@ def print_api(
130
126
  if print_end == '\n':
131
127
  # Use logger to output message.
132
128
  getattr(logger, logger_method)(message)
129
+ else:
130
+ raise ValueError("Logger can't output messages with 'print_end' other than '\\n'.")
133
131
  # If logger wasn't passed.
134
132
  else:
135
133
  # Use print to output the message.
136
134
  print(message, end=print_end)
137
135
 
138
136
  # = Main Section with printing cases ===============================================================================
139
- # exit_message: str = 'Exiting...'
140
-
141
137
  # Convert message to string.
142
138
  message = str(message)
143
139
 
@@ -151,20 +147,6 @@ def print_api(
151
147
  if error_type:
152
148
  print_or_logger()
153
149
 
154
- # ==================================
155
- # This section is responsible for ending the script.
156
-
157
- # Check if we're inside exception. In this case each of 3 entries in 'sys.exc_info()' tuple will not equal
158
- # to 'None', so picked only the first one.
159
- # if sys.exc_info()[0] and not exit_on_error:
160
- # # If 'raise_exception' is set to 'True', we'll end the script with exception.
161
- # if pass_exception:
162
- # pass
163
-
164
- # If 'exit_on_error' is set to 'True', we'll end the script.
165
- # if exit_on_error and error_type:
166
- # sys.exit()
167
-
168
150
 
169
151
  def print_status(
170
152
  prefix_string: str,
atomicshop/process.py CHANGED
@@ -82,7 +82,18 @@ def execute_with_live_output(
82
82
  :return: Boolean, If execution was successful, return True, if not - False.
83
83
  """
84
84
 
85
- cmd = _execution_parameters_processing(cmd, wsl)
85
+ if isinstance(cmd, str):
86
+ shell = True
87
+ elif isinstance(cmd, list):
88
+ shell = False
89
+ else:
90
+ raise TypeError(f'cmd must be a string or list, not {type(cmd)}')
91
+
92
+ if wsl:
93
+ if isinstance(cmd, str):
94
+ cmd = 'wsl ' + cmd
95
+ elif isinstance(cmd, list):
96
+ cmd = ['wsl'] + cmd
86
97
 
87
98
  # Needed imports:
88
99
  # from subprocess import Popen, PIPE, STDOUT
@@ -103,7 +114,7 @@ def execute_with_live_output(
103
114
  # The buffer size is system-dependent and usually chosen by the underlying implementation to optimize performance.
104
115
  # # bufsize=0: This means no buffering.
105
116
  # The I/O is unbuffered, and data is written or read from the stream immediately.
106
- with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, text=True) as process:
117
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, text=True, shell=shell) as process:
107
118
  # We'll count the number of lines from 'process.stdout'.
108
119
  counter: int = 0
109
120
  # And also get list of all the lines.
@@ -305,14 +316,21 @@ def kill_process_by_filename_pattern(pattern: str):
305
316
  processes.kill_process_by_pid(running_processes[0]['pid'])
306
317
 
307
318
 
308
- def run_powershell_command(command):
319
+ def run_powershell_command(
320
+ command: str
321
+ ):
309
322
  try:
310
323
  result = subprocess.run(["powershell", "-Command", command], capture_output=True, text=True, check=True)
311
- print_api(result.stdout)
324
+ if result.stdout:
325
+ print_api(result.stdout)
326
+ if result.stderr: # PS can write warnings to stderr even on success
327
+ print_api(result.stderr, color='yellow')
312
328
  return result.stdout
313
329
  except subprocess.CalledProcessError as e:
314
- print_api(f"An error occurred: {e}", color='red', error_type=True)
315
- return e
330
+ # e.stderr and e.stdout are populated because capture_output=True
331
+ msg = (e.stderr or e.stdout or f"PowerShell exited with code {e.returncode}")
332
+ print_api(msg, color='red', error_type=True)
333
+ return msg
316
334
 
317
335
 
318
336
  """
@@ -3,7 +3,6 @@ import multiprocessing
3
3
  import time
4
4
  from typing import Literal, Union
5
5
 
6
- from ..print_api import print_api
7
6
  from .tracers import sysmon_etw, event_log
8
7
  from .pollers import psutil_pywin32wmi_dll
9
8
  from ..wrappers.pywin32w.win_event_log.subscribes import process_terminate