atomicshop 3.3.4__py3-none-any.whl → 3.3.6__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__ = '3.3.4'
4
+ __version__ = '3.3.6'
atomicshop/networks.py CHANGED
@@ -3,6 +3,7 @@ import time
3
3
  from typing import Union
4
4
  import os
5
5
  import psutil
6
+ import ctypes
6
7
 
7
8
  from icmplib import ping
8
9
  from icmplib.models import Host
@@ -20,9 +21,12 @@ MICROSOFT_LOOPBACK_DEVICE_HARDWARE_ID = "*MSLOOP"
20
21
  GUID_DEVCLASS_NET: str = '{4d36e972-e325-11ce-bfc1-08002be10318}'
21
22
 
22
23
 
23
- def is_ip_alive(ip_address: str, timeout: int = 1) -> bool:
24
+ def is_ip_in_use_ping(ip_address: str, timeout: int = 1) -> bool:
24
25
  """
25
- Returns True if icmplib.models.Host.is_alive returns True.
26
+ Returns True if the IP address is pingable, False otherwise.
27
+ :param ip_address: string, IP address to check.
28
+ :param timeout: int, timeout in seconds. Default is 1 second.
29
+ :return: bool, True if the IP address is pingable, False otherwise.
26
30
  """
27
31
 
28
32
  host_object: Host = ping(ip_address, count=1, timeout=timeout)
@@ -30,15 +34,72 @@ def is_ip_alive(ip_address: str, timeout: int = 1) -> bool:
30
34
  return host_object.is_alive
31
35
 
32
36
 
33
- def get_default_internet_ipv4() -> str:
37
+ def is_ip_in_use_arp(
38
+ ipv4: str,
39
+ gateway_ip: str = None
40
+ ) -> tuple[
41
+ Union[str, None],
42
+ Union[bool, None]
43
+ ]:
34
44
  """
45
+ Windows only.
46
+ Check if an IPv4 address is in use on the local network using ARP.
47
+ :param ipv4: string, IPv4 address to check.
48
+ :param gateway_ip: string, IPv4 address of the default gateway.
49
+ How it works: If you provide the gateway_ip, the function will get yje MAC of the gateway,
50
+ then it will get the MAC of the target IP address. If the MACs are the same, it means that the target IP's
51
+ ARP reply is an ARP proxy reply from the gateway.
52
+ :return: tuple (mac_address: str | None, via_gateway: bool | None)
53
+ If the IP address is in use, mac_address will be the MAC address of the device using the IP address,
54
+ else None. If gateway_ip is provided, via_gateway will be True if the MAC address is the same as the gateway's MAC address,
55
+ False if it's different, and None if gateway_ip is not provided.
56
+ """
57
+
58
+ iphlpapi = ctypes.windll.iphlpapi
59
+ ws2_32 = ctypes.windll.ws2_32
60
+
61
+ def _send_arp(ip: str) -> str | None:
62
+ """Return MAC string like 'aa:bb:cc:dd:ee:ff' if IP is claimed on the LAN, else None."""
63
+ # inet_addr returns DWORD in network byte order
64
+ dest_ip = ws2_32.inet_addr(ip.encode('ascii'))
65
+ if dest_ip == 0xFFFFFFFF: # INVALID
66
+ raise ValueError(f"Bad IPv4 address: {ip}")
67
+
68
+ mac_buf = ctypes.c_uint64(0) # storage for up to 8 bytes
69
+ mac_len = ctypes.c_ulong(ctypes.sizeof(mac_buf)) # in/out len
70
+ # SrcIP=0 lets Windows pick the right interface
71
+ rc = iphlpapi.SendARP(dest_ip, 0, ctypes.byref(mac_buf), ctypes.byref(mac_len))
72
+ if rc != 0: # Non-zero means no ARP reply / not on-link / other error
73
+ return None
74
+
75
+ # Extract the first 6 bytes from little-endian integer
76
+ mac_int = mac_buf.value
77
+ mac_bytes = mac_int.to_bytes(8, 'little')[:6]
78
+ return ':'.join(f'{b:02x}' for b in mac_bytes)
79
+
80
+ mac = _send_arp(ipv4)
81
+ if mac is None:
82
+ return None, None
83
+ via_gateway = None
84
+ if gateway_ip:
85
+ gw_mac = _send_arp(gateway_ip)
86
+ via_gateway = (gw_mac is not None and gw_mac.lower() == mac.lower())
87
+ return mac, via_gateway
88
+
89
+
90
+ def __get_default_internet_ipv4() -> str:
91
+ """
92
+ FOR REFERENCE ONLY, DO NOT USE.
93
+ DOESN'T WORK UNDER ALL CIRCUMSTANCES, CAN'T PINPOINT THE REASON.
94
+
35
95
  Get the default IPv4 address of the interface that is being used for internet.
36
96
  :return: string, default IPv4 address.
37
97
  """
38
98
 
39
99
  return socket.gethostbyname(socket.gethostname())
40
100
 
41
- def get_default_internet_ipv4_by_connect(target: str = "8.8.8.8") -> str:
101
+
102
+ def get_default_internet_ipv4(target: str = "8.8.8.8") -> str:
42
103
  with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
43
104
  s.connect((target, 80)) # no packet sent; OS just chooses a route
44
105
  return s.getsockname()[0] # local address of that route
@@ -296,9 +357,9 @@ def generate_unused_ipv4_addresses_by_vlan(
296
357
  for i in range(number_of_ips):
297
358
  # Create the IP address.
298
359
  while True:
299
- ip_address = f"{vlan}.{counter}"
360
+ ip_address: str = f"{vlan}.{counter}"
300
361
  counter += 1
301
- is_ip_in_use: bool = is_ip_alive(ip_address)
362
+ is_ip_in_use, _ = is_ip_in_use_arp(ip_address)
302
363
  if not is_ip_in_use and not ip_address in skip_ips:
303
364
  # print("[+] Found IP to assign: ", ip_address)
304
365
  generated_ips.append(ip_address)
@@ -506,7 +567,7 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
506
567
  dns_gateways = default_adapter_info['dns_gateways']
507
568
 
508
569
  # We will get the default IP address of the machine.
509
- default_ip_address: str = socket.gethostbyname(socket.gethostname())
570
+ default_ip_address: str = get_default_internet_ipv4()
510
571
  # So we can make it the first IP in the list, but first remove it from the list.
511
572
  _ = ips.pop(ips.index(default_ip_address))
512
573
  # At this point we will copy the list of IPs that we will set the SkipAsSource flag for.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomicshop
3
- Version: 3.3.4
3
+ Version: 3.3.6
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License-Expression: MIT
@@ -14,6 +14,7 @@ License-File: LICENSE.txt
14
14
  Requires-Dist: wheel
15
15
  Requires-Dist: beautifulsoup4
16
16
  Requires-Dist: cryptography
17
+ Requires-Dist: dkinst
17
18
  Requires-Dist: dnslib
18
19
  Requires-Dist: dnspython
19
20
  Requires-Dist: docker
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=-H5JgHG0iCCH8pC2RNRvzeaxBh2DD3LjRloFS8OdaVQ,122
1
+ atomicshop/__init__.py,sha256=PKxHKogNO1jVrjMDxPohtYhd5093pVEONPVfmujNgfw,122
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
@@ -23,7 +23,7 @@ atomicshop/http_parse.py,sha256=1Tna9YbOM0rE3t6i_M-klBlwd1KNSA9skA_BqKGXDFc,1186
23
23
  atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
24
24
  atomicshop/ip_addresses.py,sha256=penRFeJ1-LDVTko4Q0EwK4JiN5cU-KzCBR2VXg9qbUY,1238
25
25
  atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
26
- atomicshop/networks.py,sha256=dTh6T9vAnjYOEPdfpL6W6wN091HwRbVLeNscXWZX_DI,22151
26
+ atomicshop/networks.py,sha256=fwOMMHwn-XMq0WgYCFbo34NXW-Xu-4nDfWl3T5EsE9U,24807
27
27
  atomicshop/on_exit.py,sha256=9XlOnzoAG8zlI8wBF4AB8hyrC6Q1b84gkhqpAhhdN9g,6977
28
28
  atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
29
29
  atomicshop/print_api.py,sha256=SJNQIMqSLlYaPtjHnALySAI-jQYuYHOCGgfP7oe96fU,10957
@@ -54,13 +54,10 @@ atomicshop/a_installs/ubuntu/docker_rootless.py,sha256=9IPNtGZYjfy1_n6ZRt7gWz9KZ
54
54
  atomicshop/a_installs/ubuntu/docker_sudo.py,sha256=JzayxeyKDtiuT4Icp2L2LyFRbx4wvpyN_bHLfZ-yX5E,281
55
55
  atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py,sha256=yRB-l1zBxdiN6av-FwNkhcBlaeu4zrDPjQ0uPGgpK2I,244
56
56
  atomicshop/a_installs/ubuntu/mongodb.py,sha256=xuRJS1qqOZ0EZp7of5R3tTjSu6CwBIxYg8-NaM7othE,230
57
- atomicshop/a_installs/ubuntu/nodejs.py,sha256=Iax55VJpT2HZsdRv-JvxkLg6JPYpJ-M8_r-wI5ES_yQ,222
58
57
  atomicshop/a_installs/ubuntu/pycharm.py,sha256=Ld7YQBwPxrjuZcTG1K4kZqjbBdt8aooCVRa15u5FOmE,157
59
58
  atomicshop/a_installs/win/fibratus.py,sha256=TU4e9gdZ_zI73C40uueJ59pD3qmN-UFGdX5GFoVf6cM,179
60
59
  atomicshop/a_installs/win/mongodb.py,sha256=AqyItXu19aaoe49pppDxtEkXey6PMy0PoT2Y_RmPpPE,179
61
- atomicshop/a_installs/win/nodejs.py,sha256=U519Dyt4bsQPbEg_PwnZL5tsbfqDr1BbhxwoQFZsSKo,200
62
60
  atomicshop/a_installs/win/pycharm.py,sha256=j_RSd7aDOyC3yDd-_GUTMLlQTmDrqtVFG--oUfGLiZk,140
63
- atomicshop/a_installs/win/robocorp.py,sha256=Ob8X9Czwd-OR3pudFIFft1dcUjDM0Pw0X03yfPaf3yw,3280
64
61
  atomicshop/a_installs/win/wsl_ubuntu_lts.py,sha256=dZbPRLNKFeMd6MotjkE6UDY9cOiIaaclIdR1kGYWI50,139
65
62
  atomicshop/a_mains/dns_gateway_setting.py,sha256=ncc2rFQCChxlNP59UshwmTonLqC6MWblrVAzbbz-13M,149
66
63
  atomicshop/a_mains/github_wrapper.py,sha256=F-PoZknVCxWPN0PTO6l7ZNiaYvo7OVFKFI_zlPt56ps,169
@@ -198,7 +195,6 @@ atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK
198
195
  atomicshop/wrappers/cryptographyw.py,sha256=QEUpDn8vUvMg3ADz6-4oC2kbDNC_woDlw7C0zU7qFVM,14233
199
196
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
200
197
  atomicshop/wrappers/githubw.py,sha256=bds_8fgyFyHXKwty6-SBS3H3Ueta2IMM5UQFpiFmgHQ,27554
201
- atomicshop/wrappers/msiw.py,sha256=GQLqud72nfex3kvO1bJSruNriCYTYX1_G1gSf1MPkIA,6118
202
198
  atomicshop/wrappers/netshw.py,sha256=8WE_576XiiHykwFuE-VkCx5CydMpFlztX4frlEteCtI,6350
203
199
  atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc,1025
204
200
  atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
@@ -277,9 +273,6 @@ atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=2eEOb35T259lhn5koy
277
273
  atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=64EUQYx7VuMC3ndO2x3nSErh5NZ_BsqMwGvPcybfC-Q,8499
278
274
  atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
279
275
  atomicshop/wrappers/mongodbw/mongodbw.py,sha256=CHbDfW9CXzXUs3V97DYQpt-dYlt_gB60JhwqG2tVFQY,56049
280
- atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
281
- atomicshop/wrappers/nodejsw/install_nodejs_ubuntu.py,sha256=wjpJdfAaY92RYl_L9esDIWuBMGeYH35RHJ5BVgMof8Y,6260
282
- atomicshop/wrappers/nodejsw/install_nodejs_windows.py,sha256=WvXIcEVnKcQYD-KNwhVP094s__1tt0Ir2Y87MABl8Nc,6283
283
276
  atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
284
277
  atomicshop/wrappers/playwrightw/_tryouts.py,sha256=l1BLkFsiIMNlgv7nfZd1XGEvXQkIQkIcg48__9OaC00,4920
285
278
  atomicshop/wrappers/playwrightw/base.py,sha256=WeRpx8otdXuKSr-BjY-uCJTze21kbPpfitoOjKQz5-g,9818
@@ -337,9 +330,9 @@ atomicshop/wrappers/socketw/statistics_csv.py,sha256=_gA8bMX6Sw_UCXKi2y9wNAwlqif
337
330
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
338
331
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
339
332
  atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
340
- atomicshop-3.3.4.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
- atomicshop-3.3.4.dist-info/METADATA,sha256=AA-i4jzN5U9DaSNf-Hv_TuHfoMUeOR9-53KcyzR2xik,9288
342
- atomicshop-3.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
343
- atomicshop-3.3.4.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
344
- atomicshop-3.3.4.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
345
- atomicshop-3.3.4.dist-info/RECORD,,
333
+ atomicshop-3.3.6.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
334
+ atomicshop-3.3.6.dist-info/METADATA,sha256=IL5-xRKSd3KMQq1-z8F1hdeVTz9jNmhxtEdbXTAeKtM,9311
335
+ atomicshop-3.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
336
+ atomicshop-3.3.6.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
337
+ atomicshop-3.3.6.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
338
+ atomicshop-3.3.6.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- #! /usr/bin/env python3
2
- import sys
3
-
4
- from atomicshop.wrappers.nodejsw import install_nodejs_ubuntu
5
-
6
-
7
- def main():
8
- install_nodejs_ubuntu.install_nodejs_main()
9
-
10
-
11
- if __name__ == "__main__":
12
- sys.exit(main())
@@ -1,11 +0,0 @@
1
- import sys
2
-
3
- from atomicshop.wrappers.nodejsw import install_nodejs_windows
4
-
5
-
6
- def main():
7
- install_nodejs_windows.install_nodejs_windows()
8
-
9
-
10
- if __name__ == "__main__":
11
- sys.exit(main())
@@ -1,82 +0,0 @@
1
- import sys
2
- import subprocess
3
- import tempfile
4
-
5
- from atomicshop.print_api import print_api
6
- from atomicshop.wrappers import githubw
7
- from atomicshop.permissions import permissions
8
- from atomicshop.wrappers.nodejsw import install_nodejs_windows
9
-
10
-
11
- WINDOWS_TESSERACT_DEFAULT_INSTALLATION_DIRECTORY: str = r"C:\Program Files\Tesseract-OCR"
12
-
13
-
14
- def main():
15
- if not permissions.is_admin():
16
- print_api("Please run this script as an Administrator.", color="red")
17
- return 1
18
-
19
- if not install_nodejs_windows.is_nodejs_installed():
20
- install_nodejs_windows.install_nodejs_windows()
21
- install_nodejs_windows.add_nodejs_to_path()
22
- if not install_nodejs_windows.is_nodejs_installed():
23
- print_api("Node.js installation failed.", color="red")
24
- return 1
25
-
26
- print_api("PIP Installing Robocorp.", color="blue")
27
- subprocess.check_call(["pip", "install", "--upgrade", "rpaframework"])
28
-
29
- print_api("PIP Installing Robocorp-Browser.", color="blue")
30
- subprocess.check_call(["pip", "install", "--upgrade", "robotframework-browser"])
31
-
32
- print_api("PIP Installing Robocorp-Recognition.", color="blue")
33
- subprocess.check_call(["pip", "install", "--upgrade", "rpaframework-recognition"])
34
-
35
- print_api("PIP Installing pynput.", color="blue")
36
- subprocess.check_call(["pip", "install", "--upgrade", "pynput"])
37
-
38
- print_api("Installing Playwright browsers.", color="blue")
39
- subprocess.check_call(["playwright", "install"])
40
-
41
- print_api("Initializing Robocorp Browser.", color="blue")
42
- subprocess.check_call(["rfbrowser", "init"])
43
-
44
- print_api("Installing Additional modules.", color="blue")
45
- subprocess.check_call(["pip", "install", "--upgrade", "matplotlib", "imagehash"])
46
-
47
- print_api("Installing Tesseract OCR.", color="blue")
48
- github_wrapper = githubw.GitHubWrapper(
49
- user_name="tesseract-ocr",
50
- repo_name="tesseract",
51
- branch="main")
52
- github_wrapper.build_links_from_user_and_repo()
53
- temp_file_path: str = tempfile.gettempdir()
54
- tesseract_installer = github_wrapper.download_latest_release(
55
- target_directory=temp_file_path,
56
- string_pattern="*tesseract*exe")
57
-
58
- # The Admin needed to install Tesseract.
59
- subprocess.check_call([tesseract_installer, "/S"])
60
-
61
- # Add Tesseract to the PATH.
62
- subprocess.check_call(["setx", "PATH", f"%PATH%;{WINDOWS_TESSERACT_DEFAULT_INSTALLATION_DIRECTORY}"])
63
-
64
- # Patch robocorp: Remove mouse to the center of the screen on control command.
65
- # Import the library to find its path.
66
- print_api("Patching: .\RPA\Windows\keywords\window.py", color="blue")
67
- import RPA.Windows.keywords.window as window
68
- window_file_path = window.__file__
69
-
70
- # Patch the file.
71
- with open(window_file_path, "r") as file:
72
- file_content = file.read()
73
- file_content = file_content.replace(
74
- "window.item.MoveCursorToMyCenter(simulateMove=self.ctx.simulate_move)",
75
- "# window.item.MoveCursorToMyCenter(simulateMove=self.ctx.simulate_move) # Patched to remove center placement during foreground window control."
76
- )
77
- with open(window_file_path, "w") as file:
78
- file.write(file_content)
79
-
80
-
81
- if __name__ == '__main__':
82
- sys.exit(main())
@@ -1,148 +0,0 @@
1
- import subprocess
2
-
3
- from ..print_api import print_api
4
- from ..permissions import permissions
5
- from ..import get_process_list
6
- from .psutilw import processes
7
-
8
-
9
- ERROR_CODES = {
10
- '1603': 'The App is already installed or Insufficient permissions',
11
- '1619': 'This installation package could not be opened. Verify that the package exists and that you can '
12
- 'install it manually, also check the installation command line switches'
13
- }
14
-
15
-
16
- class MsiInstallationError(Exception):
17
- pass
18
-
19
-
20
- def get_current_msiexec_processes(msi_file_path: str = None) -> dict:
21
- """
22
- Get the current msiexec processes.
23
- :param msi_file_path: string, OPTIONAL path to the MSI file to check in the command line.
24
- :return: list of dicts, each key represents a pid and its values are process name and cmdline.
25
- """
26
-
27
- current_processes: dict = (
28
- get_process_list.GetProcessList(get_method='pywin32', connect_on_init=True).get_processes())
29
-
30
- current_msiexec_dict: dict = {}
31
- for pid, process_info in current_processes.items():
32
- if 'msiexec.exe' in process_info['name']:
33
- if msi_file_path:
34
- if msi_file_path in process_info['cmdline']:
35
- current_msiexec_dict[pid] = process_info
36
- else:
37
- current_msiexec_dict[pid] = process_info
38
-
39
- return current_msiexec_dict
40
-
41
-
42
- def wait_for_msiexec_processes_to_finish(msi_file_path: str):
43
- """
44
- Wait for the msiexec processes to finish.
45
- :param msi_file_path: string, path to the MSI file.
46
- :return:
47
- """
48
-
49
- current_msiexec: dict = get_current_msiexec_processes(msi_file_path)
50
- current_pid = list(current_msiexec.keys())[0]
51
-
52
- result_code = processes.wait_for_process(current_pid)
53
- if result_code != 0:
54
- raise Exception(f"MSI Installation failed. Return code: {result_code}")
55
-
56
-
57
- def install_msi(
58
- msi_path,
59
- silent_no_gui: bool = False,
60
- silent_progress_bar: bool = False,
61
- no_restart: bool = False,
62
- terminate_required_processes: bool = False,
63
- additional_args: str = None,
64
- create_log_near_msi: bool = False,
65
- log_file_path: str = None,
66
- scan_log_for_errors: bool = False,
67
- # as_admin=True,
68
- print_kwargs: dict = None):
69
- """
70
- Install an MSI file silently.
71
- :param msi_path: str, path to the MSI file.
72
- :param silent_no_gui: bool, whether to run the installation silently, without showing GUI.
73
- :param silent_progress_bar: bool, whether to show a progress bar during silent installation.
74
- :param no_restart: bool, whether to restart the computer after installation.
75
- :param terminate_required_processes: bool, whether to terminate processes that are required by the installation.
76
- :param additional_args: str, additional arguments to pass to the msiexec command.
77
- :param create_log_near_msi: bool, whether to create a log file near the MSI file.
78
- If the msi file located in 'c:\\path\\to\\file.msi', the log file will be created in 'c:\\path\\to\\file.log'.
79
- The log options that will be used: /l*v c:\\path\\to\\file.log
80
- :param log_file_path: str, path to the log file. Even if 'create_log_near_msi' is False, you can specify a custom
81
- path for the log file, and it will be created.
82
- The log options that will be used: /l*v c:\\path\\to\\file.log
83
- :param scan_log_for_errors: bool, whether to scan the log file for errors in case of failure.
84
- # :param as_admin: bool, whether to run the installation as administrator.
85
- :param print_kwargs: dict, print_api kwargs.
86
- :return:
87
- """
88
-
89
- if not permissions.is_admin():
90
- raise PermissionError("This function requires administrator privileges.")
91
-
92
- if silent_progress_bar and silent_no_gui:
93
- raise ValueError("silent_progress_bar and silent_no_gui cannot be both True.")
94
-
95
- if create_log_near_msi and log_file_path:
96
- raise ValueError("create_log_near_msi and log_file_path cannot be both set.")
97
-
98
- if create_log_near_msi:
99
- log_file_path = msi_path.replace('.msi', '.log')
100
-
101
- if scan_log_for_errors and not log_file_path:
102
- raise ValueError("[scan_log_for_errors] is set, but [log_file_path] or [create_log_near_msi] is not set.")
103
-
104
- # Define the msiexec command
105
- command = f'msiexec /i "{msi_path}"'
106
-
107
- if silent_no_gui:
108
- command = f"{command} /qn"
109
- if silent_progress_bar:
110
- command = f"{command} /qb"
111
- if no_restart:
112
- command = f"{command} /norestart"
113
-
114
- if log_file_path:
115
- command = f"{command} /l*v {log_file_path}"
116
-
117
- if terminate_required_processes:
118
- command = f"{command} REBOOT=ReallySuppress"
119
-
120
- if additional_args:
121
- if additional_args.startswith(' '):
122
- additional_args = additional_args[1:]
123
- command = f"{command} {additional_args}"
124
-
125
- # if as_admin:
126
- # command = win_permissions.get_command_to_run_as_admin_windows(command)
127
-
128
- # Run the command
129
- result = subprocess.run(command, capture_output=True, text=True)
130
-
131
- # Check the result
132
- if result.returncode == 0:
133
- print_api("MSI Installation completed.", color="green", **(print_kwargs or {}))
134
- else:
135
- message = (f"Installation failed. Return code: {result.returncode}\n{ERROR_CODES.get(str(result.returncode), '')}\n"
136
- f"MSI path: {msi_path}\nCommand: {command}\nOutput: {result.stdout}\nError: {result.stderr}")
137
-
138
- if scan_log_for_errors:
139
- with open(log_file_path, 'r', encoding='utf-16 le') as f:
140
- log_content = f.read()
141
- if 'error' in log_content.lower():
142
- # Get the error text of the lines that contain 'error'.
143
- error_lines = [line for line in log_content.split('\n') if 'error' in line.lower()]
144
- for line in error_lines:
145
- message += f"\n{line}"
146
-
147
- print_api(message, color="red", **(print_kwargs or {}))
148
- raise MsiInstallationError("MSI Installation Failed.")
File without changes
@@ -1,183 +0,0 @@
1
- import subprocess
2
- import requests
3
- import argparse
4
-
5
- from ...basics import booleans
6
- from .. import githubw, ubuntu_terminal
7
- from ...print_api import print_api
8
-
9
-
10
- def is_nodejs_installed():
11
- """
12
- The function will check if Node.js is installed.
13
- :return: bool.
14
- """
15
-
16
- try:
17
- # Run the command 'node -v'
18
- result = subprocess.run(['node', '-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
19
-
20
- # Check if the command was successful
21
- if result.returncode == 0:
22
- message = f"Node.js installed. Version: {result.stdout.strip()}"
23
- print_api(message, color='green')
24
- return True
25
- else:
26
- print_api("Node.js is not installed.")
27
- return False
28
- except FileNotFoundError:
29
- print_api("Node command not found. Node.js is not installed.")
30
- return False
31
-
32
-
33
- def get_nodejs_latest_version_number(
34
- by_github_api: bool = True,
35
- _by_nodejs_website: bool = False,
36
- get_major: bool = False
37
- ) -> str:
38
- """
39
- The function will get the latest version number of Node.js.
40
- :param by_github_api: bool, if True, the function will get the version number using the GitHub API.
41
- Limitations: rate limits apply.
42
- :param _by_nodejs_website: bool, if True, the function will get the version number using the Node.js website.
43
- Limitations: the website structure can change and the json file is relatively large.
44
- This is only for reference, it is not tested.
45
- :param get_major: bool, if True, the function will return only the major version number string.
46
- :return: str.
47
- """
48
-
49
- if by_github_api and _by_nodejs_website:
50
- raise ValueError("Only one of the arguments can be True.")
51
- elif not by_github_api and not _by_nodejs_website:
52
- raise ValueError("At least one of the arguments must be True.")
53
-
54
- latest_version = ''
55
- if by_github_api:
56
- github_wrapper = githubw.GitHubWrapper('nodejs', 'node')
57
- latest_version = github_wrapper.get_the_latest_release_version_number()
58
- elif _by_nodejs_website:
59
- url = "https://nodejs.org/dist/index.json"
60
- response = requests.get(url)
61
- versions = response.json()
62
- latest_version = versions[0]['version'] # Assuming the first one is the latest.
63
-
64
- if get_major:
65
- latest_version = latest_version.replace('v', '')
66
- latest_version = latest_version.split('.')[0]
67
-
68
- return latest_version
69
-
70
-
71
- def install_nodejs_ubuntu(
72
- install_latest_version: bool = False,
73
- install_lts: bool = True,
74
- install_by_version_number: str = None,
75
- force_install: bool = False
76
- ):
77
- """
78
- The function will install Node.js on Ubuntu.
79
-
80
- :param install_latest_version: bool, if True, the function will install the latest version of Node.js.
81
- :param install_lts: bool, if True, the function will install the LTS version of Node.js.
82
- :param install_by_version_number: str, the version number of Node.js to install.
83
- :param force_install: bool, if True, the function will install Node.js even if it is already installed.
84
-
85
- :return:
86
- """
87
-
88
- booleans.is_only_1_true_in_list(
89
- booleans_list_of_tuples=[
90
- (install_latest_version, 'install_latest_version'),
91
- (install_lts, 'install_lts'),
92
- (install_by_version_number, 'install_by_version_number')
93
- ],
94
- raise_if_all_false=True
95
- )
96
-
97
- # Check if Node.js is already installed.
98
- if is_nodejs_installed():
99
- if not force_install:
100
- return
101
-
102
- # NodeSource is listed as source under official Node.js GitHub repository:
103
- # https://github.com/nodejs/node?tab=readme-ov-file#current-and-lts-releases
104
- print_api("Adding NodeSource repository...")
105
-
106
- # Fetch and execute the NodeSource repository setup script.
107
- if install_latest_version:
108
- install_by_version_number: str = get_nodejs_latest_version_number(get_major=True)
109
-
110
- command: str = ''
111
- if install_latest_version or install_by_version_number:
112
- command = f"curl -fsSL https://deb.nodesource.com/setup_{install_by_version_number}.x | sudo -E bash -"
113
- elif install_lts:
114
- command = "curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash -"
115
-
116
- _ = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
117
-
118
- ubuntu_terminal.update_system_packages()
119
- ubuntu_terminal.install_packages(['nodejs'])
120
-
121
- # Check if Node.js is installed.
122
- is_nodejs_installed()
123
-
124
-
125
- def install_nodejs_main():
126
- """
127
- The function will install Node.js on Ubuntu.
128
- :return:
129
- """
130
-
131
- # Create the parser.
132
- parser = argparse.ArgumentParser(description="Install Node.js on Ubuntu.")
133
- parser.add_argument(
134
- '--latest',
135
- action='store_true',
136
- help="Install the latest version of Node.js."
137
- )
138
- parser.add_argument(
139
- '--lts',
140
- action='store_true',
141
- help="Install the LTS version of Node.js."
142
- )
143
- parser.add_argument(
144
- '--version',
145
- type=str,
146
- help="Install a specific version of Node.js."
147
- )
148
- parser.add_argument(
149
- '--force',
150
- action='store_true',
151
- help="Force the installation of Node.js."
152
- )
153
-
154
- # Parse the arguments.
155
- args = parser.parse_args()
156
-
157
- install_nodejs_ubuntu(
158
- install_latest_version=args.latest,
159
- install_lts=args.lts,
160
- install_by_version_number=args.version,
161
- force_install=args.force
162
- )
163
-
164
-
165
- def install_npm_package_ubuntu(package_name: str, sudo: bool = True):
166
- """
167
- The function will install a npm package on Ubuntu.
168
- :param package_name: str, the name of the package to install.
169
- :param sudo: bool, if True, the function will use sudo.
170
- NPM commands require sudo to install global packages.
171
- :return:
172
- """
173
-
174
- # Check if Node.js is installed.
175
- if not is_nodejs_installed():
176
- return
177
-
178
- command = f"npm install -g {package_name}"
179
-
180
- if sudo:
181
- command = f"sudo {command}"
182
-
183
- _ = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
@@ -1,180 +0,0 @@
1
- import subprocess
2
- import os
3
- import requests
4
- import time
5
- import tempfile
6
-
7
- from ... import web
8
- from ...permissions import permissions
9
- from .. import msiw
10
- from ...print_api import print_api
11
-
12
-
13
- WINDOWS_X64_SUFFIX: str = "x64.msi"
14
-
15
-
16
- class NodeJSWindowsInstallerNoVersionsFound(Exception):
17
- pass
18
-
19
-
20
- class NodeJSWindowsInstallerMoreThanOneVersionFound(Exception):
21
- pass
22
-
23
-
24
- class NodeJSWindowsInstallerFailedToExtractFileNameFromString(Exception):
25
- pass
26
-
27
-
28
- class NodeJSWindowsInstallerFailedToExtractVersionInString(Exception):
29
- pass
30
-
31
-
32
- def is_nodejs_installed():
33
- """
34
- Check if Node.js is installed by trying to run 'node -v'.
35
- """
36
- print_api("Checking if Node.js is installed...")
37
- try:
38
- try:
39
- node_version = subprocess.check_output(["node", "-v"], text=True)
40
- except FileNotFoundError:
41
- print_api(f"node.exe is not found.", color="red")
42
- raise
43
-
44
- node_version = node_version.replace("\n", "")
45
- print_api(f"node.exe is found. Version: {node_version}", color="green")
46
-
47
- try:
48
- npm_version = subprocess.check_output(["npm.cmd", "-v"], text=True).strip()
49
- except FileNotFoundError:
50
- print_api(f"npm.cmd is not found.", color="red")
51
- raise
52
-
53
- npm_version = npm_version.replace("\n", "")
54
- print_api(f"npm.cmd is found. Version: {npm_version}", color="green")
55
- print_api("Node.js is installed.")
56
- return True
57
- except FileNotFoundError:
58
- print_api("Node.js is not installed.")
59
- return False
60
-
61
-
62
- def add_nodejs_to_path():
63
- """
64
- Add Node.js to the PATH for the current CMD session.
65
- """
66
- print_api("Adding Node.js to the PATH for the current session...")
67
- # Get the installation directory from the default Node.js install path
68
- program_files = os.environ.get("ProgramFiles", "C:\\Program Files")
69
- nodejs_path = os.path.join(program_files, "nodejs")
70
-
71
- if os.path.exists(nodejs_path):
72
- print_api(f"Node.js installation found at: {nodejs_path}")
73
- current_path = os.environ.get("PATH", "")
74
- if nodejs_path not in current_path:
75
- # Add Node.js to the PATH for the current process
76
- os.environ["PATH"] = f"{nodejs_path};{current_path}"
77
- print_api("Node.js has been added to the PATH for this session.")
78
- else:
79
- print_api("Node.js is already in the PATH.")
80
- else:
81
- print_api("Node.js installation directory not found.")
82
-
83
-
84
- def get_latest_nodejs_version():
85
- """
86
- Fetch the latest Node.js version from the official Node.js website.
87
- """
88
- print_api("Fetching the latest Node.js version...")
89
- url = "https://nodejs.org/dist/latest/SHASUMS256.txt"
90
-
91
- response = requests.get(url, timeout=10)
92
- response.raise_for_status()
93
- # Parse the file for the Node.js version
94
- found_versions: list = []
95
- for line in response.text.splitlines():
96
- if line.endswith(WINDOWS_X64_SUFFIX):
97
- found_versions.append(line)
98
-
99
- if not found_versions:
100
- raise NodeJSWindowsInstallerNoVersionsFound("No Node.js versions found in [https://nodejs.org/dist/latest/SHASUMS256.txt]")
101
- elif len(found_versions) > 1:
102
- raise NodeJSWindowsInstallerMoreThanOneVersionFound(f"More than one Node.js version found:\n"
103
- f"{'\n'.join(found_versions)}")
104
-
105
- try:
106
- file_name = found_versions[0].split(" ")[-1]
107
- except IndexError:
108
- raise NodeJSWindowsInstallerFailedToExtractFileNameFromString("Failed to extract the file name from the string.")
109
-
110
- try:
111
- version = file_name.replace("node-v", "").replace(f"-{WINDOWS_X64_SUFFIX}", "")
112
- except Exception:
113
- raise NodeJSWindowsInstallerFailedToExtractVersionInString("Failed to extract the version from the string.")
114
-
115
- print_api(f"Latest Node.js version: {version}")
116
- return version
117
-
118
-
119
- def download_nodejs_installer(version):
120
- """
121
- Download the Node.js MSI installer for Windows.
122
- """
123
-
124
- version = f"v{version}"
125
- nodejs_base_url = f"https://nodejs.org/dist/{version}/"
126
- file_name = f"node-{version}-x64.msi"
127
- download_url = nodejs_base_url + file_name
128
- print_api(f"Downloading Node.js installer from: {download_url}")
129
-
130
- # Make temporary directory to store the installer
131
- temp_dir = tempfile.gettempdir()
132
- temp_file_path = web.download(download_url, temp_dir)
133
- return temp_file_path
134
-
135
-
136
- def clean_up(installer_path):
137
- """
138
- Remove the installer file after installation.
139
- """
140
- try:
141
- if os.path.exists(installer_path):
142
- os.remove(installer_path)
143
- print_api(f"Removed installer: {installer_path}")
144
- except Exception as e:
145
- print_api(f"Failed to clean up the installer: {e}")
146
-
147
-
148
- def install_nodejs_windows() -> int:
149
- """
150
- Install Node.js on Windows.
151
- If you're installing as part of a cmd script that continues and installs npm packages, you can do something like this:
152
- if not install_nodejs_windows.is_nodejs_installed():
153
- install_nodejs_windows.is_nodejs_installed()
154
- install_nodejs_windows.install_nodejs_windows()
155
- install_nodejs_windows.add_nodejs_to_path()
156
- if not install_nodejs_windows.is_nodejs_installed():
157
- print_api("Node.js installation failed.")
158
- return 1
159
- :return:
160
- """
161
- if not permissions.is_admin():
162
- print_api("This script requires administrative privileges to install Node.js.")
163
- return 1
164
-
165
- print_api("Starting Node.js installation process...")
166
- version = get_latest_nodejs_version()
167
- if not version:
168
- print_api("Exiting: Could not fetch the latest Node.js version.")
169
- return 1
170
-
171
- installer_path = download_nodejs_installer(version)
172
- if not installer_path:
173
- print_api("Exiting: Failed to download the Node.js installer.")
174
- return 1
175
-
176
- msiw.install_msi(installer_path, silent_progress_bar=True)
177
- time.sleep(5) # Wait a few seconds for the installation to complete
178
- clean_up(installer_path)
179
- print_api("Installation process finished.")
180
- return 0