atomicshop 2.10.8__py3-none-any.whl → 2.11.1__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.10.8'
4
+ __version__ = '2.11.1'
@@ -2,7 +2,8 @@ from atomicshop.wrappers.dockerw import install_docker
2
2
 
3
3
 
4
4
  def main():
5
- install_docker.install_docker_ubuntu()
5
+ install_docker.install_docker_ubuntu(
6
+ use_docker_installer=True, rootless=False, add_current_user_to_docker_group_bool=True)
6
7
 
7
8
 
8
9
  if __name__ == '__main__':
@@ -0,0 +1,5 @@
1
+ from atomicshop.wrappers.ctyping.msi_windows_installer import extract_msi_main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ extract_msi_main.extract_files_from_msi_main()
@@ -0,0 +1,86 @@
1
+ import os
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+ from ..print_api import print_api
6
+ from .. import process, filesystem
7
+
8
+
9
+ def is_path_contains_7z_executable(sevenz_path: str) -> bool:
10
+ """
11
+ Checks if the path contains 7z executable.
12
+ :param sevenz_path: string, The path to the 7z executable.
13
+ :return: bool, True if the path contains 7z executable, False otherwise.
14
+ """
15
+ executable_path_parts: tuple = Path(sevenz_path).parts
16
+
17
+ if '7z' not in executable_path_parts[-1]:
18
+ return False
19
+ else:
20
+ return True
21
+
22
+
23
+ def is_executable_a_7z(sevenz_path: str) -> bool:
24
+ """
25
+ Checks if the 7z executable is installed.
26
+ :param sevenz_path: string, The path to the 7z executable.
27
+ :return: bool, True if the 7z executable is installed, False otherwise.
28
+ """
29
+
30
+ # Check if the process itself is installed.
31
+ if process.is_command_exists(sevenz_path):
32
+ # Check that this is the 7z executable.
33
+ try:
34
+ # Run '7z' command and capture output
35
+ result = subprocess.run([sevenz_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
36
+
37
+ # Check if the output contains identifying information
38
+ if b"7-Zip" in result.stdout or b"7-Zip" in result.stderr:
39
+ return True
40
+ except Exception:
41
+ return False
42
+
43
+ return False
44
+
45
+
46
+ def extract_file(
47
+ file_path: str,
48
+ extract_to: str,
49
+ sevenz_path: str = None,
50
+ force_overwrite: bool = False,
51
+ print_kwargs: dict = None
52
+ ):
53
+ """
54
+ Extracts a file to a directory using 7z executable.
55
+ :param file_path: string, The path to the file to extract.
56
+ :param extract_to: string, The directory to extract the file to.
57
+ :param sevenz_path: string, The path to the 7z executable.
58
+ If None, the default path is used, assuming you added 7z to the PATH environment variable.
59
+ :param force_overwrite: bool, If True, the files will be overwritten if they already exist in the output folder.
60
+ :param print_kwargs: dict, The keyword arguments to pass to the print function.
61
+ :return:
62
+ """
63
+
64
+ # Check if the path contains 7z executable.
65
+ if not is_path_contains_7z_executable(sevenz_path):
66
+ raise ValueError("The path to 7z does not contain 7z executable")
67
+
68
+ if not sevenz_path:
69
+ sevenz_path = '7z'
70
+
71
+ # Check if the 7z executable is installed.
72
+ if not is_executable_a_7z(sevenz_path):
73
+ raise RuntimeError("'7z executable' is not a 7z")
74
+
75
+ if not os.path.exists(extract_to):
76
+ os.makedirs(extract_to)
77
+
78
+ command = [f'{sevenz_path}', 'x', file_path, f'-o{extract_to}']
79
+ if force_overwrite:
80
+ command.append('-y')
81
+
82
+ try:
83
+ subprocess.run(command, check=True)
84
+ print_api(f"Extracted {file_path} to {extract_to}", **(print_kwargs or {}))
85
+ except subprocess.CalledProcessError as e:
86
+ print_api(f"An error occurred: {e}", **(print_kwargs or {}))
atomicshop/process.py CHANGED
@@ -3,6 +3,7 @@ import functools
3
3
  from typing import Union
4
4
  import shlex
5
5
  import subprocess
6
+ import shutil
6
7
 
7
8
  from .print_api import print_api
8
9
  from .inspect_wrapper import get_target_function_default_args_and_combine_with_current
@@ -12,6 +13,23 @@ if os.name == 'nt':
12
13
  from .process_poller import GetProcessList
13
14
 
14
15
 
16
+ def is_command_exists(cmd: str) -> bool:
17
+ """
18
+ The function checks if the command exists in the system.
19
+ :param cmd: string, the command to check.
20
+ :return: bool, True if the command exists, False otherwise.
21
+
22
+ With this you can also check if the command is in the PATH environment variable.
23
+ Example:
24
+ print(is_command_exists('7z')) # C:\\Program Files\\7-Zip\\7z.exe
25
+ """
26
+
27
+ if shutil.which(cmd):
28
+ return True
29
+ else:
30
+ return False
31
+
32
+
15
33
  def process_execution_decorator(function_name):
16
34
  @functools.wraps(function_name)
17
35
  def wrapper_process_execution_decorator(*args, **kwargs):
File without changes
@@ -0,0 +1,134 @@
1
+ from typing import Literal
2
+ import ctypes
3
+ from ctypes import wintypes
4
+
5
+
6
+ # Load Windows Installer functions
7
+ msi = ctypes.windll.msi
8
+
9
+
10
+ # Constants
11
+ MSIDBOPEN_READONLY = 0
12
+ ERROR_NO_MORE_ITEMS = 259
13
+
14
+
15
+ def create_open_db_handle(msi_filepath):
16
+ """Create a database handle for the specified MSI file."""
17
+ db_handle = ctypes.c_void_p()
18
+
19
+ result = msi.MsiOpenDatabaseW(msi_filepath, MSIDBOPEN_READONLY, ctypes.byref(db_handle))
20
+ if result != 0:
21
+ print(f"Failed to open MSI database. Error code: {result}")
22
+ raise ctypes.WinError(result)
23
+ return db_handle
24
+
25
+
26
+ def create_open_execute_view_handle(db_handle, query):
27
+ """Create a view handle for the specified query."""
28
+ # Create view handle.
29
+ view_handle = ctypes.c_void_p()
30
+
31
+ # Open the view.
32
+ result = msi.MsiDatabaseOpenViewW(db_handle, query, ctypes.byref(view_handle))
33
+ if result != 0:
34
+ print(f"Failed to open view. Error code: {result}")
35
+ msi.MsiCloseHandle(db_handle)
36
+ raise ctypes.WinError(result)
37
+
38
+ # Execute the view.
39
+ result = msi.MsiViewExecute(view_handle, None)
40
+ if result != 0:
41
+ print(f"Failed to execute view. Error code: {result}")
42
+ msi.MsiCloseHandle(view_handle)
43
+ msi.MsiCloseHandle(db_handle)
44
+ raise ctypes.WinError(result)
45
+
46
+ return view_handle
47
+
48
+
49
+ def create_fetch_record_from_view_handle(view_handle):
50
+ """Fetch a record from the specified view handle."""
51
+ # Fetch the record handle.
52
+ record_handle = ctypes.c_void_p()
53
+
54
+ # Fetch the record.
55
+ fetch_record_result = msi.MsiViewFetch(view_handle, ctypes.byref(record_handle))
56
+ if fetch_record_result == ERROR_NO_MORE_ITEMS:
57
+ msi.MsiCloseHandle(view_handle)
58
+ return record_handle
59
+ elif fetch_record_result != 0:
60
+ print(f"Failed to fetch record. Error code: {fetch_record_result}")
61
+ msi.MsiCloseHandle(view_handle)
62
+ raise ctypes.WinError(fetch_record_result)
63
+
64
+ return record_handle
65
+
66
+
67
+ def get_table_field_data_from_record(
68
+ record_handle,
69
+ field_index,
70
+ data_type: Literal['stringw', 'integer', 'stream'],
71
+ buffer_size: int = 2048
72
+ ):
73
+ """
74
+ Read data from a specific field in a record.
75
+ :param record_handle: Record handle.
76
+ :param field_index: The field index. Example: 2 for the second field.
77
+ Name,Data
78
+ field_index = 1 for Name
79
+ field_index = 2 for Data
80
+ :param data_type: The type of data to read.
81
+ stringw: Read the data as a wide string.
82
+ integer: Read the data as an integer.
83
+ stream: Read the data as a binary stream (bytes).
84
+ :param buffer_size: The size of the buffer to use when reading the data.
85
+ :return:
86
+ """
87
+
88
+ if data_type == 'stringw':
89
+ buf_size = wintypes.DWORD(buffer_size)
90
+ buf = ctypes.create_unicode_buffer(buf_size.value)
91
+ result = msi.MsiRecordGetStringW(record_handle, field_index, buf, ctypes.byref(buf_size))
92
+ if result != 0:
93
+ if result == 234:
94
+ print("Buffer size too small. Try again with a larger buffer.")
95
+ print(f"Failed to get string from record. Error code: {result}")
96
+ msi.MsiCloseHandle(record_handle)
97
+ raise ctypes.WinError(result)
98
+ return str(buf.value)
99
+
100
+ elif data_type == 'integer':
101
+ int_value = ctypes.c_int()
102
+ result = msi.MsiRecordGetInteger(record_handle, field_index, ctypes.byref(int_value))
103
+ if result != 0:
104
+ print(f"Failed to get integer from record. Error code: {result}")
105
+ msi.MsiCloseHandle(record_handle)
106
+ raise ctypes.WinError(result)
107
+ return int_value.value
108
+
109
+ elif data_type == 'stream':
110
+ # stream_size = wintypes.DWORD()
111
+ # result = msi.MsiRecordReadStream(record_handle, field_index, None, ctypes.byref(stream_size))
112
+ # if result != 0:
113
+ # print(f"Failed to read stream data. Error code: {result}")
114
+ # msi.MsiCloseHandle(record_handle)
115
+ # raise ctypes.WinError(result)
116
+ #
117
+ # stream_data = ctypes.create_string_buffer(stream_size.value)
118
+ # msi.MsiRecordReadStream(record_handle, field_index, stream_data, ctypes.byref(stream_size))
119
+ # return stream_data.raw
120
+
121
+ buffer = ctypes.create_string_buffer(buffer_size)
122
+ read_size = wintypes.DWORD(buffer_size)
123
+ data = bytearray()
124
+
125
+ while True:
126
+ result = msi.MsiRecordReadStream(record_handle, field_index, buffer, ctypes.byref(read_size))
127
+ if result != 0 or read_size.value == 0:
128
+ break
129
+ data.extend(buffer.raw[:read_size.value])
130
+
131
+ return data
132
+
133
+ else:
134
+ raise ValueError(f"Invalid data type: {data_type}. Valid options are 'stringw', 'integer', 'stream'.")
@@ -0,0 +1,75 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from . import tables
5
+ from ....archiver import sevenz_app_w
6
+
7
+
8
+ def resolve_directory_path(directory_info, directory_key):
9
+ parts = []
10
+ current_key = directory_key
11
+ while current_key:
12
+ entry = directory_info.get(current_key)
13
+ if entry:
14
+ parts.append(entry['default_dir'])
15
+ current_key = entry['parent']
16
+ else:
17
+ break
18
+ if not parts:
19
+ return ""
20
+ return str(os.path.join(*reversed(parts)))
21
+
22
+
23
+ def rename_extracted_files_by_file_table_info(
24
+ extracted_files_dir, file_table_info, component_table_info, directory_table_info):
25
+
26
+ for file_key, info in file_table_info.items():
27
+ component = info['component']
28
+ directory_key = component_table_info[component]['directory']
29
+ resolved_directory_path: str = resolve_directory_path(directory_table_info, directory_key)
30
+
31
+ # Divide the path into parts and remove the first part if it is a 'SourceDir' and the last part if it is a dot.
32
+ resolved_directory_parts = Path(resolved_directory_path).parts
33
+ if resolved_directory_parts[-1] == '.':
34
+ resolved_directory_parts = resolved_directory_parts[:-1]
35
+ if resolved_directory_parts[0] == 'SourceDir':
36
+ resolved_directory_parts = resolved_directory_parts[1:]
37
+ resolved_directory_path = str(os.path.join(*resolved_directory_parts))
38
+
39
+ extracted_path = os.path.join(extracted_files_dir, file_key)
40
+ if os.path.exists(extracted_path):
41
+ new_file_name = f"{info['file_name']}"
42
+ new_file_path = os.path.join(extracted_files_dir, resolved_directory_path, new_file_name)
43
+ os.makedirs(os.path.dirname(new_file_path), exist_ok=True)
44
+ os.rename(extracted_path, new_file_path)
45
+ print(f"Renamed: [{file_key}] to [{new_file_name}]")
46
+ else:
47
+ print(f"File not found: [{file_key}] in {extracted_path}")
48
+
49
+
50
+ def extract_files_from_cab(db_handle, main_cab_path: str, output_directory: str, sevenz_path: str = None):
51
+ """
52
+ Extracts the files from the CAB file using 7z executable and the MSI database handle to get the real file
53
+ names and paths.
54
+ :param db_handle: Database handle to the MSI database.
55
+ :param main_cab_path: string, The path to the main CAB file.
56
+ :param output_directory: string, The directory to extract the files to.
57
+ :param sevenz_path: string, Full path to the 7z executable.
58
+ :return:
59
+ """
60
+
61
+ # Get all the tables entries from the MSI database to correlate them while building the file paths and renaming.
62
+ file_table_info = tables.get_file_table_info(db_handle)
63
+ component_table_info = tables.get_component_table_info(db_handle)
64
+ directory_table_info = tables.get_directory_table_info(db_handle)
65
+
66
+ # Extract the contents of the CAB file (if there are several CAB files, they will be extracted as well).
67
+ sevenz_app_w.extract_file(
68
+ file_path=main_cab_path,
69
+ extract_to=output_directory,
70
+ sevenz_path=sevenz_path,
71
+ force_overwrite=True
72
+ )
73
+
74
+ rename_extracted_files_by_file_table_info(
75
+ output_directory, file_table_info, component_table_info, directory_table_info)
@@ -0,0 +1,136 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+
5
+ from .base import msi
6
+ from . import base, tables, cabs
7
+ from ... import olefilew
8
+ from ....print_api import print_api
9
+ from ....archiver import sevenz_app_w
10
+
11
+
12
+ # Directory names.
13
+ EXTRACTED_FILES: str = "Extracted_MSI_Installation_Files"
14
+ EMBEDDED_FILES: str = "Embedded_MSI_Files"
15
+ CAB_FILES: str = "CAB_Files"
16
+ BINARY_FILES: str = "Binary_Files"
17
+ TABLE_CONTENTS: str = "Table_Contents"
18
+ ICON_FILES: str = "Icon_Files"
19
+ ISSETUPFILES: str = "ISSETUPFILES"
20
+ OLE_METADATA: str = "OLE_Metadata"
21
+ REGISTRY_CHANGES: str = "Registry_Changes"
22
+
23
+
24
+ def parse_args():
25
+ parser = argparse.ArgumentParser(description="Extract files from the MSI file.")
26
+ parser.add_argument("--msi_filepath", "-m", type=str, help="The path to the MSI file.")
27
+ parser.add_argument("--output_directory", "-o", type=str, help="The main output directory.")
28
+ parser.add_argument("--sevenz_path", "-s", type=str, help="The path to the 7z executable.")
29
+ return parser.parse_args()
30
+
31
+
32
+ def extract_files_from_msi_main(
33
+ msi_filepath: str = None,
34
+ main_out_directory: str = None,
35
+ sevenz_path: str = None
36
+ ):
37
+ """
38
+ Extracts files from the MSI file using the MSI database.
39
+ What can be extracted:
40
+ - MSI file Table contents
41
+ - MSI file Binary files
42
+ - MSI file Icon files
43
+ - MSI file ISSETUPFILES
44
+ - MSI file Registry changes
45
+ - MSI file CAB files
46
+ - Application Installation files.
47
+ These are extracted from the CAB files and renamed to their real names from the DB.
48
+ - MSI file OLE metadata
49
+
50
+ Usage:
51
+ # You can create a python file with the following content and run it:
52
+ from atomicshop.wrappers.ctyping.msi_windows_installer import extract_msi_main
53
+
54
+
55
+ if __name__ == "__main__":
56
+ extract_msi_main.extract_files_from_msi_main()
57
+
58
+ Or you can just use the function with the appropriate parameters directly:
59
+ extract_msi_main.extract_files_from_msi_main(
60
+ msi_filepath=r"C:\\Setup.msi",
61
+ main_out_directory=r"c:\\unpacked_msi",
62
+ sevenz_path=r"C:\\7z.exe")
63
+
64
+ :param msi_filepath: string, The path to the MSI file.
65
+ :param main_out_directory: string, The main output directory.
66
+ :param sevenz_path: string, The path to the 7z executable. If None, the default path is used, which assumes that 7z
67
+ is in the PATH environment variable.
68
+ :return:
69
+ """
70
+
71
+ args = parse_args()
72
+
73
+ # If arguments to the function were not provided, use the arguments from the command line.
74
+ if not msi_filepath:
75
+ msi_filepath = args.msi_filepath
76
+ if not main_out_directory:
77
+ main_out_directory = args.output_directory
78
+ if not sevenz_path:
79
+ sevenz_path = args.sevenz_path
80
+
81
+ # If not provided, raise an error.
82
+ if not msi_filepath:
83
+ print_api("The path to the MSI file is not provided.", color="red")
84
+ sys.exit()
85
+ if not main_out_directory:
86
+ print_api("The main output directory is not provided.", color="red")
87
+ if not sevenz_path:
88
+ print_api(
89
+ "The path to the 7z executable is not provided. Assuming 7z is in the PATH environment variable.",
90
+ color="yellow")
91
+
92
+ if not sevenz_app_w.is_path_contains_7z_executable(sevenz_path):
93
+ print_api("The path to 7z does not contain 7z executable", color="red")
94
+ sys.exit()
95
+
96
+ if not os.path.isfile(sevenz_path):
97
+ print_api("The path to 7z executable doesn't exist.", color="red")
98
+ sys.exit()
99
+
100
+ if not sevenz_app_w.is_executable_a_7z(sevenz_path):
101
+ print_api("Provided 7z executable is not 7z.", color="red")
102
+ sys.exit()
103
+
104
+ # Create the main output directory.
105
+ os.makedirs(main_out_directory, exist_ok=True)
106
+
107
+ # Open the MSI database file.
108
+ db_handle = base.create_open_db_handle(msi_filepath)
109
+
110
+ embedded_files_directory = os.path.join(main_out_directory, EMBEDDED_FILES)
111
+
112
+ # Extract files using the MSI database handle.
113
+ tables.extract_table_contents_to_csv(db_handle, os.path.join(embedded_files_directory, TABLE_CONTENTS))
114
+ tables.extract_binary_table_entries(db_handle, os.path.join(embedded_files_directory, BINARY_FILES))
115
+ tables.extract_icon_table_entries(db_handle, os.path.join(embedded_files_directory, ICON_FILES))
116
+ tables.extract_issetupfiles_table_entries(db_handle, os.path.join(embedded_files_directory, ISSETUPFILES))
117
+ tables.extract_registry_changes(db_handle, os.path.join(embedded_files_directory, REGISTRY_CHANGES))
118
+
119
+ # Extract CAB files from the MSI file.
120
+ cab_files = tables.list_media_table_entries(db_handle)
121
+ print(f"CAB files found: {cab_files}")
122
+ extracted_cab_file_paths: list = tables.extract_cab_files_from_media(
123
+ db_handle, os.path.join(embedded_files_directory, CAB_FILES))
124
+
125
+ # Extract installation files from CAB files and rename them to their real names from DB.
126
+ cabs.extract_files_from_cab(
127
+ db_handle,
128
+ extracted_cab_file_paths[0],
129
+ os.path.join(main_out_directory, EXTRACTED_FILES),
130
+ sevenz_path=sevenz_path)
131
+
132
+ # Close the MSI database handle.
133
+ msi.MsiCloseHandle(db_handle)
134
+
135
+ # Extract OLE metadata from the MSI file.
136
+ olefilew.extract_ole_metadata(msi_filepath, os.path.join(embedded_files_directory, OLE_METADATA))