Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ uv run powershell ./build_exe.ps1
```

Your executable will be in dist/.

### How to add dependencies

Copy your MSIX file and dependencies into root of this repo, then run the preparation step

```ps
uv run python extract_msix_data.py path_to_your_msix_file path_to_dependency path_to_second_dependency ...
```

The packages will be installed in reverse order with the last one specified installed first, until the
main package (first argument) is installed last.

There is no tested limit on the number of dependencies.
20 changes: 15 additions & 5 deletions build_exe.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
$path = python -c "import pickle; print(pickle.load(open('extracted/data.pkl', 'rb')).package_path)"
$basename = [System.IO.Path]::GetFileNameWithoutExtension($path)
# Get picked data to get name and icon
$paths_py = python -c "import pickle; m = pickle.load(open('extracted/data.pkl', 'rb')); print([a.package_path for a in m])"
$paths_py_doublequotes = $paths_py -replace "'", '"'
$paths_json = $paths_py_doublequotes | ConvertFrom-Json
$main_app_path = $paths_json[0]
$addDataArgs = $paths_json | ForEach-Object { "--add-data `'$($_):.`'" }
$addDataString = $addDataArgs -replace "`r?`n", ""

$basename = [System.IO.Path]::GetFileNameWithoutExtension($main_app_path)
$pathexe = $basename + ".exe"
$icon = python -c "import pickle; print(pickle.load(open('extracted/data.pkl', 'rb')).icon_path)"
$add_data_msix_path = $path + ":."
pyinstaller .\src\msix_global_installer\app.py --add-data 'extracted:extracted' --add-data "$add_data_msix_path" --onefile --name $pathexe --icon $icon --noconsole
$icon = python -c "import pickle; print(pickle.load(open('extracted/data.pkl', 'rb'))[0].icon_path)"

# Build command
# This only works when first stored as a string - some powershell string issue for the add-data commands
$command_str = "pyinstaller .\src\msix_global_installer\app.py --add-data 'extracted:extracted' $addDataString --onefile --name $pathexe --icon $icon --noconsole"
Invoke-Expression $command_str
13 changes: 12 additions & 1 deletion extract_msix_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
import sys
import pathlib


def get_metadata(paths: list[str]) -> list[msix.MsixMetadata]:
"""Get the metadata from a list of items."""
return [msix.get_msix_metadata(path) for path in paths]


path = sys.argv[1]
print("Extracting data from %s" % path)

Expand All @@ -18,6 +24,11 @@
data_file = data_output_path / "data.pkl"

metadata = msix.get_msix_metadata(path, data_output_path)
dependency_paths = sys.argv[2:]
dependency_metadata = []
if dependency_paths:
dependency_metadata = get_metadata(paths=dependency_paths)
all_metadata = [metadata] + dependency_metadata

# Scale the image, save and add to metadata
scaled_image = image.scale_image(metadata.icon_path, 100, 100)
Expand All @@ -27,4 +38,4 @@
image.save_image(scaled_image, scaled_image_path)
metadata.scaled_icon_path = scaled_image_path

pickler.save_metadata(data_file_path=data_file, metadata=metadata)
pickler.save_metadata(data_file_path=data_file, metadata_list=all_metadata)
24 changes: 21 additions & 3 deletions src/msix_global_installer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,27 @@ def process_event(event: events.Event):
elif event.name == events.EventType.INSTALL_MSIX:
install_globally = event.data["global"]
meta = pickler.load_metadata(config.EXTRACTED_DATA_PATH)
path = pyinstaller_helper.resource_path(meta.package_path)
logger.info("Installing app: %s", path)
meta = msix.install_msix(path=path, global_install=install_globally)
paths = [
(
metadata.package_name,
pyinstaller_helper.resource_path(metadata.package_path),
)
for metadata in meta
]
# TODO: Break this into a function in MSIX
paths.reverse()
number_of_packages = len(paths)
for i, path in enumerate(paths):
logger.info("Installing app: %s", path)
success = msix.install_msix(
path=path[1],
title=path[0],
global_install=install_globally,
packages_to_install=number_of_packages,
package_number=i + 1,
)
if not success:
break
logger.info("Installing app: %s... DONE", path)


Expand Down
54 changes: 38 additions & 16 deletions src/msix_global_installer/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, parent, *args, **kwargs):

def set_icon(self):
"""Set the window icon."""
meta = pickler.load_metadata(config.EXTRACTED_DATA_PATH)
meta = pickler.load_metadata(config.EXTRACTED_DATA_PATH)[0]
image_to_iconify = Image.open(pyinstaller_helper.resource_path(meta.icon_path))
icon_in_correct_format = ImageTk.PhotoImage(image_to_iconify)
self.parent.wm_iconphoto(False, icon_in_correct_format)
Expand Down Expand Up @@ -87,7 +87,8 @@ def __init__(self, parent: tkinter.Tk, *args, **kwargs):
def handle_event(self, event: events.Event):
"""Handle events on the queue."""
if event.name == events.EventType.MSIX_METADATA_RECEIVED:
metadata: msix.MsixMetadata = event.data
# Only need the image from the first metadata entry
metadata: msix.MsixMetadata = event.data[0]
image_path = pyinstaller_helper.resource_path(metadata.scaled_icon_path)
scaled_image = Image.open(image_path)
self.img = ImageTk.PhotoImage(scaled_image)
Expand Down Expand Up @@ -122,9 +123,14 @@ def __init__(self, parent: tkinter.Tk, *args, **kwargs):
self.version_content = ttk.Label(self, text="v0.0.0.0")
self.version_content.grid(row=2, column=1, sticky="W")

# Not sure what's causing these to get cut off
install_type_label = ttk.Label(self, text="Installnstall Globally")
install_type_label.grid(row=3, column=0, sticky="W")
dependency_count_title = ttk.Label(self, text="Dependencies:")
dependency_count_title.grid(row=3, column=0, sticky="W")

self.dependency_count = ttk.Label(self, text="0")
self.dependency_count.grid(row=3, column=1, sticky="W")

install_type_label = ttk.Label(self, text="Install for all users")
install_type_label.grid(row=4, column=0, sticky="W")

self.global_install_checkbox_state = tkinter.BooleanVar(self)
is_admin = pyuac.isUserAdmin()
Expand All @@ -134,14 +140,14 @@ def __init__(self, parent: tkinter.Tk, *args, **kwargs):
variable=self.global_install_checkbox_state,
command=self.on_checkbox_change,
)
global_install_checkbox.grid(row=3, column=0, sticky="W")
global_install_checkbox.grid(row=4, column=1, sticky="W")

button = ttk.Button(
self,
text="Install",
command=self.install,
)
button.grid(row=4, column=1)
button.grid(row=5, column=1)

def on_checkbox_change(self):
"""On change to checkbox."""
Expand All @@ -158,11 +164,14 @@ def on_checkbox_change(self):
def handle_event(self, event: events.Event):
"""Handle events on the queue."""
if event.name == events.EventType.MSIX_METADATA_RECEIVED:
data: msix.MsixMetadata = event.data
all_data: list[msix.MsixMetadata] = event.data
data = all_data[0]
title_text = f"Install {data.package_name}"
self.title.configure(text=title_text)
self.version_content.configure(text=data.version)
self.author_content.configure(text=data.publisher)
dep_count = len(all_data) - 1
self.dependency_count.configure(text=dep_count)

def install(self):
"""Install the MSIX."""
Expand All @@ -178,24 +187,37 @@ def __init__(self, parent, *args, **kwargs):
ttk.Frame.__init__(self, parent, *args, **kwargs)
self.parent: tkinter.Tk = parent

self.status = ttk.Label(self, text="Starting...")
self.status.grid(row=0, column=0)
self.title = ttk.Label(self, text="Starting...")
self.title.grid(row=0, column=0)

self.subtitle = ttk.Label(self, text="")
self.subtitle.grid(row=1, column=0)

self.progress = ttk.Progressbar(self, length=200, mode="indeterminate")
self.progress.grid(row=1, column=0)
self.progress.start(interval=200)
self.progress.grid(row=2, column=0)
self.progress.start(interval=2000)

done_button = ttk.Button(
self,
text="Done",
command=lambda: self.parent.switch_frame(InfoScreenContainer),
)
done_button.grid(row=2, column=0)
done_button.grid(row=3, column=0)

def handle_event(self, event):
def handle_event(self, event: events.Event):
if event.name == events.EventType.INSTALL_PROGRESS_TEXT:
text = event.data["text"]
self.status.configure(text=text)
try:
text = event.data["title"]
self.title.configure(text=text)
except KeyError:
# Main text wasn't included in the data
pass
try:
text = event.data["subtitle"]
self.subtitle.configure(text=text)
except KeyError:
# Sub text wasn't included in the data
pass
try:
progress_percentage = int(event.data["progress"])
logger.info("Updating progress bar to: %s", progress_percentage)
Expand Down
Loading
Loading