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 +1 -1
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +2 -1
- atomicshop/addons/mains/msi_unpacker.py +5 -0
- atomicshop/archiver/sevenz_app_w.py +86 -0
- atomicshop/process.py +18 -0
- atomicshop/wrappers/ctyping/__init__.py +0 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/__init__.py +0 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/base.py +134 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +75 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +136 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +419 -0
- atomicshop/wrappers/dockerw/install_docker.py +65 -31
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +9 -5
- atomicshop/wrappers/olefilew.py +63 -0
- atomicshop/wrappers/ubuntu_terminal.py +72 -9
- {atomicshop-2.10.8.dist-info → atomicshop-2.11.1.dist-info}/METADATA +2 -1
- {atomicshop-2.10.8.dist-info → atomicshop-2.11.1.dist-info}/RECORD +20 -11
- {atomicshop-2.10.8.dist-info → atomicshop-2.11.1.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.10.8.dist-info → atomicshop-2.11.1.dist-info}/WHEEL +0 -0
- {atomicshop-2.10.8.dist-info → atomicshop-2.11.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -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,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
|
|
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))
|