tis-template-microsoft-store-app
0-65
Once imported, run the update_package() function to automatically generate your package for a UWP application available on the Microsoft Store
4297 downloads
See build result See VirusTotal scan
![tis-template-microsoft-store-app icon](/wapt/icons/aea6d0c53867b3d774e670da830d8ef922bd93a4ca37ea565e7bbb8465152983.png)
Description
- package : tis-template-microsoft-store-app
- name : Template Microsoft Store App
- version : 0-65
- categories : Template
- maintainer : WAPT Team,Tranquil IT,Jimmy PELÉ
- installed_size :
- editor :
- licence : wapt_public
- signature_date : 2024-04-30T13:00:09.966496
- size : 30.98 Ko
- locale : all
- target_os : windows
- impacted_process :
- architecture : all
control
package : tis-template-microsoft-store-app
version : 0-65
architecture : all
section : base
priority : optional
name : Template Microsoft Store App
categories : Template
maintainer : WAPT Team,Tranquil IT,Jimmy PELÉ
description : Once imported, run the update_package() function to automatically generate your package for a UWP application available on the Microsoft Store
depends :
conflicts :
maturity : PROD
locale : all
target_os : windows
min_wapt_version : 2.3
sources :
installed_size :
impacted_process :
description_fr : Une fois importé, exécutez la fonction update_package() afin de générer automatiquement votre package pour une application UWP disponible sur le Microsoft Store.
description_pl : Po zaimportowaniu uruchom funkcję update_package(), aby automatycznie wygenerować pakiet dla aplikacji UWP dostępnej w sklepie Microsoft Store
description_de : Führen Sie nach dem Import die Funktion update_package() aus, um Ihr Paket für eine im Microsoft Store erhältliche UWP-Anwendung automatisch zu generieren
description_es : Una vez importado, ejecute la función update_package() para generar automáticamente su paquete para una aplicación UWP disponible en Microsoft Store
description_pt : Uma vez importado, execute a função update_package() para gerar automaticamente o seu pacote para uma aplicação UWP disponível na Microsoft Store
description_it : Una volta importato, eseguire la funzione update_package() per generare automaticamente il pacchetto per un'applicazione UWP disponibile sul Microsoft Store
description_nl : Voer na het importeren de functie update_package() uit om automatisch je pakket te genereren voor een UWP-toepassing die beschikbaar is in de Microsoft Store
description_ru : После импорта запустите функцию update_package() для автоматической генерации пакета для UWP-приложения, доступного в Microsoft Store
audit_schedule :
editor :
keywords :
licence : wapt_public
homepage :
package_uuid : fa6f8588-7e7a-4390-830e-4a7b4773cf27
valid_from :
valid_until :
forced_install_on :
changelog :
min_os_version : 10.0
max_os_version :
icon_sha256sum : aea6d0c53867b3d774e670da830d8ef922bd93a4ca37ea565e7bbb8465152983
signer : Tranquil IT
signer_fingerprint: 8c5127a75392be9cc9afd0dbae1222a673072c308c14d88ab246e23832e8c6bb
signature : RgNdhW76mZgdmY+fcEgOAUrVwzysqmR3h7mLH2W1TblrgoxPh+8qoLiYDpSwy8wbAjb8cj99ZzBy/FYTNYkwH0StsxAsfpsFzLBYa8G7kCxJobDoMTgaPmBLE4YVoGqrex9CGzmeQ+YE5RmOAfoVTaE+v7CjdskwIQ9Vl3Xya2PeX/iW01feKS8j9P5/ZIk2ubh5llAr3HvbESKLOtVVdwR4SSPwJTwSex0qwGBYwP//y9IB8GT1IkeaP2sezdivo6c0T6IhEf0F0/k1E12y+DnpYiHUQQ5iRJ4Fzu/zsNN1ZUSC9X1ySl7iM6d6VfKXwP4Z2nYTbEXzmd6H4oDf3Q==
signature_date : 2024-04-30T13:00:09.966496
signed_attributes : package,version,architecture,section,priority,name,categories,maintainer,description,depends,conflicts,maturity,locale,target_os,min_wapt_version,sources,installed_size,impacted_process,description_fr,description_pl,description_de,description_es,description_pt,description_it,description_nl,description_ru,audit_schedule,editor,keywords,licence,homepage,package_uuid,valid_from,valid_until,forced_install_on,changelog,min_os_version,max_os_version,icon_sha256sum,signer,signer_fingerprint,signature_date,signed_attributes
Setup.py
# -*- coding: utf-8 -*-
from setuphelpers import *
appx_package_name = ""
appx_dir = makepath(programfiles, "WindowsAppsInstallers")
def install():
# Declare local variables
bins_dir = control.package.split("-", 1)[1]
old_appx_bins_dir = makepath(appx_dir, bins_dir)
appx_bins_dir = makepath(os.getcwd(), bins_dir)
# Removing sources because they are no longer needed
if isdir(old_appx_bins_dir):
print("Removing: %s" % (old_appx_bins_dir))
remove_tree(old_appx_bins_dir)
if dir_is_empty(appx_dir):
print("Removing: %s since it is empty" % (appx_dir))
remove_tree(appx_dir)
# Removing binaries of different architectures
for f in glob.glob(appx_bins_dir + "/*"):
fname = f.split(os.sep)[-1]
if control.architecture == "all":
if "x86" in glob.glob(makepath(appx_bins_dir, f"{appx_package_name}_*"))[0].split(os.sep)[-1]:
if not "x86" in fname and not "neutral" in fname:
remove_file(f)
else:
if not get_host_architecture() in fname and not "neutral" in fname:
remove_file(f)
else:
if not get_host_architecture() in fname and not "neutral" in fname:
remove_file(f)
bin_path = glob.glob(makepath(appx_bins_dir, f"{appx_package_name}_*"))[0]
dependencies_pathes = ",".join([f'"{a}"' for a in glob.glob(makepath(appx_bins_dir, "*")) if not appx_package_name in a])
add_appx_cmd = f'Add-AppxProvisionedPackage -Online -PackagePath "{bin_path}" -SkipLicense'
if dependencies_pathes:
add_appx_cmd += f" -DependencyPackagePath {dependencies_pathes}"
# Installing the UWP application if needed
appxprovisionedpackage = run_powershell(f'Get-AppXProvisionedPackage -Online | Where-Object DisplayName -Like "{appx_package_name}"')
if appxprovisionedpackage is None:
remove_appx(appx_package_name, False)
appxprovisionedpackage = {"Version": "0"}
elif force:
uninstall()
if Version(appxprovisionedpackage["Version"], 4) < Version(control.get_software_version(), 4):
print(f"Installing: {bin_path.split(os.sep)[-1]} ({control.get_software_version()})")
killalltasks(ensure_list(control.impacted_process))
run_powershell(add_appx_cmd, output_format="text")
else:
print(f'{appxprovisionedpackage["PackageName"]} is already installed and up-to-date.')
def uninstall():
print(f"Removing AppX: {appx_package_name}")
remove_appx(appx_package_name)
def audit():
# Declaring local variables
audit_result = "OK"
audit_version = True
appxprovisionedpackage = run_powershell(f'Get-AppXProvisionedPackage -Online | Where-Object DisplayName -Like "{appx_package_name}"')
# Auditing software
if appxprovisionedpackage is None:
print(f"{appx_package_name} is not installed.")
audit_result = "ERROR"
elif audit_version:
if Version(appxprovisionedpackage.get("Version", "0"), 4) < Version(control.get_software_version(), 4):
print(
f'{appxprovisionedpackage["PackageName"]} is installed in version: {appxprovisionedpackage["Version"]} instead of: {control.get_software_version()}.'
)
audit_result = "WARNING"
else:
print(f'{appxprovisionedpackage["PackageName"]} is installed and up-to-date.')
else:
print(f'{appxprovisionedpackage["PackageName"]} is installed.')
return audit_result
def remove_appx(package, default_user=True):
"""Remove Windows AppX package from the computer environment, excluding NonRemovable packages.
Args:
package (str): AppX package name. You can use an asterisk (*) as a wildcard.
default_user (bool): Remove AppX package from the Windows image to prevent installation for new users.
.. versionadded:: 2.2
.. versionchanged:: 2.5
No longer try to remove NonRemovable AppX package
"""
if running_as_admin() or running_as_system():
if default_user:
run_powershell(
f'Get-AppXProvisionedPackage -Online | Where-Object DisplayName -Like "{package}" | Remove-AppxProvisionedPackage -Online -AllUsers',
output_format="text",
)
run_powershell(
r'Get-AppxPackage -Name "%s" -AllUsers | Where-Object {{ -not ($_.NonRemovable) }} | Remove-AppxPackage -AllUsers' % package,
output_format="text",
)
else:
run_powershell(r'Get-AppxPackage -Name "%s" | Where-Object {{ -not ($_.NonRemovable) }} | Remove-AppxPackage' % package, output_format="text")
update_package.py
# -*- coding: utf-8 -*-
from setuphelpers import *
import requests
import re
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
from setupdevhelpers import *
import xmltodict
def update_package():
# Declaring local variables
package_updated = False
proxies = get_proxies_from_wapt_console()
if not proxies:
proxies = get_proxies()
microsoft_store_url = control.sources
if not microsoft_store_url:
store_id = ask_input(
control.package,
"Enter the application ProductId or the link from the Microsoft Store (web address: https://apps.microsoft.com/store/apps)",
microsoft_store_url,
raise_error=True,
).split("/")[-1]
else:
store_id = microsoft_store_url.split("/")[-1]
store_id = store_id.split("?")[0].split("#")[0].upper()
if store_id:
microsoft_store_url = "https://apps.microsoft.com/store/detail/%s" % store_id
control.sources = microsoft_store_url
control.save_control_to_wapt()
package_prefix = control.package.split("-")[0]
selected = None
# check setup.py incase the package name does not match the installer file
appx_package_name = ""
with open("setup.py", "r", encoding="utf8") as f:
for line in f.readlines():
if line.startswith("appx_package_name"):
appx_package_name = line.split("=")[1].split('"')[1]
break
old_appx_package_name = appx_package_name
# Getting info from adguard api
res = requests.post(
"https://store.rg-adguard.net/api/GetFiles",
"type=ProductId&url=%s&ring=RP&lang=fr-FR" % store_id,
headers={"content-type": "application/x-www-form-urlencoded"},
proxies=proxies,
)
all_files = []
for bs_search in bs_find_all(res.text, "a"):
if not "BlockMap" in bs_search.text and not "eappxbundle" in bs_search.text and not "emsixbundle" in bs_search.text:
# bin_name = bs_search.text.replace("~", "_")
bin_name = str(bs_search.text)
if not get_file_extension(bin_name):
continue
version = bin_name.split("_")[1]
download_url = bs_search["href"]
pkg_splitted = re.split(r"_\d+\.", bin_name)[0]
package_name = package_prefix + "-" + pkg_splitted.split("_")[0].replace(".", "-").lower()
software_name = bin_name.split("_")[0].replace("-", " ").replace(".", " ")
package_arch = get_uwp_filename_arch(bin_name, appx_package_name)
file_dict = {
"version": version,
"bin_name": bin_name,
"package_name": package_name,
"software_name": software_name,
"package_arch": package_arch,
"download_url": download_url,
}
all_files.append(file_dict)
if (
"template-microsoft-store" in control.package
or not appx_package_name
or not len([a for a in all_files if appx_package_name in a["bin_name"]]) != 0
):
if not all_files:
error(f"API returned an empty list for URL: {microsoft_store_url}, please try again")
selected = ask_grid(
"Please select the App version that you want to package",
all_files,
"GRT_SELECTED",
'{"columns":[{"propertyname":"version","datatype":"String","required":false,"readonly":false,"width":130},{"propertyname":"bin_name","datatype":"String","required":false,"readonly":false,"width":420},{"propertyname":"package_name","datatype":"String","required":false,"readonly":false,"width":190},{"propertyname":"software_name","datatype":"String","required":false,"readonly":false,"width":172},{"propertyname":"package_arch","datatype":"String","required":false,"readonly":false,"width":88},{"propertyname":"download_url","datatype":"String","required":false,"readonly":false,"width":1472}]}',
)
appx_package_name = selected[0]["bin_name"].split("_")[0]
# Avoiding downloads loop and avoid keeping sources of other apps
bins_dir = control.package.split("-", 1)[1]
for dir_name in glob.glob("**/"):
dir_name = dir_name.split(os.sep)[0]
if dir_name in ["WAPT", "__pycache__"]:
continue
if dir_name == bins_dir:
for f in (
glob.glob(f"{dir_name}/*.appxbundle")
+ glob.glob(f"{dir_name}/*.msixbundle")
+ glob.glob(f"{dir_name}/*.appx")
+ glob.glob(f"{dir_name}/*.msix")
):
shutil.move(f, ".")
else:
remove_tree(dir_name)
if isfile("package.json"):
remove_file("package.json")
# Updating package.json ignored_versions list
if isfile("package.json"):
package_json = json_load_file("package.json")
else:
package_json = {"version_prefix": None, "ignored_versions": []}
if selected:
ignored_versions = list(
set(
package_json["ignored_versions"]
+ [v["version"] for v in all_files if appx_package_name in v["bin_name"] and v["version"] != selected[0]["version"]]
)
)
ignored_versions.sort()
package_json["ignored_versions"] = ignored_versions
json_write_file("package.json", package_json)
else:
ignored_versions = package_json["ignored_versions"]
version_prefix = package_json.get("version_prefix", None)
# Downloading app files
download_app_files(all_files, appx_package_name, proxies=proxies, ignored_versions=ignored_versions, version_prefix=version_prefix)
# # Import xmltodict for make_uwp_app_dict()
# if not isfile("xmltodict.py"):
# pip_download_xmltodict = "waptpython -m pip download xmltodict -d %s" % basedir
# if proxies:
# pip_download_xmltodict += " --proxy %s" % proxies["http"]
# run(pip_download_xmltodict)
# unzip(glob.glob("*.whl")[0], ".", "xmltodict.py")
# remove_file(glob.glob("*.whl")[0])
# Keep app files
uwp_app_dict = make_uwp_app_dict(appx_package_name)
if not uwp_app_dict:
# error(f'{appx_package_name} not found in "uwp_app_dict".\n\n{list(set([ua["bin_name"].split("_")[0] for ua in all_files]))}')
error(f'"{appx_package_name}" not found in "uwp_app_dict". This is probably caused by "package_json" filters:\n{package_json}')
newer_uwp_app = get_newer_uwp_app(uwp_app_dict)
version = newer_uwp_app["Version"]
# Downloading dependency files
dependencies_to_download = []
if newer_uwp_app["Dependencies"]:
for dependency in newer_uwp_app["Dependencies"]:
dependencies_to_download.append(get_newer_uwp_depency(dependency, all_files, min_version=dependency["MinVersion"]))
if dependencies_to_download:
for dependency_file in all_files:
latest_bin = dependency_file["bin_name"]
if not True in [d in latest_bin for d in dependencies_to_download]: # ignore unecessary dependencies
continue
if not "all" in ensure_list(control.architecture):
if not get_uwp_filename_arch(latest_bin) in ensure_list(control.architecture) and not "neutral" in latest_bin:
if isfile(latest_bin):
remove_file(latest_bin)
continue
if latest_bin.split(".")[-1].lower().startswith("e"): # ignore encrypted uwp apps
continue
download_url = dependency_file["download_url"]
if download_url.split("/")[2].endswith("microsoft.com"):
if not isfile(latest_bin):
print("Downloading: %s" % latest_bin)
wget(download_url, latest_bin, proxies=proxies)
else:
print("Binary is present: %s" % latest_bin)
else:
error("ERROR: The retrieved URL will not download from microsoft's servers")
# Applying or asking control informations once
if not old_appx_package_name or old_appx_package_name != appx_package_name:
# control.impacted_process and control.installed_size
if newer_uwp_app.get("b4:Dependencies", None):
if len(newer_uwp_app["b4:Dependencies"]) > 1:
appx_file = sorted(
newer_uwp_app["b4:Dependencies"],
key=lambda arch: ["neutral", "x64", "x86", "arm64", "arm"].index(arch.get("Architecture")),
)[0]["FileName"]
else:
appx_file = newer_uwp_app["b4:Dependencies"][0]["FileName"]
appx_dir = appx_file.rsplit(".", 1)[0]
unzip(newer_uwp_app["FileName"], ".", appx_file)
unzip(appx_file)
else:
appx_dir = newer_uwp_app["FileName"].rsplit(".", 1)[0]
unzip(newer_uwp_app["FileName"])
complete_control_impacted_process(control, appx_dir)
control.installed_size = get_size(appx_dir)
if isdir(appx_dir):
remove_tree(appx_dir)
# No dependencies since it will be contained
control.depends = ""
control.save_control_to_wapt()
# control.architecture
control.architecture = get_uwp_architectures(newer_uwp_app)
if "," in control.architecture:
complete_control_min_wapt_version(control, "2.4", True)
# control.min_os_version and control.max_os_version
set_control_os_version_uwp_app(newer_uwp_app)
# control.name
control_name = bs_find(microsoft_store_url + "?gl=US&hl=en-US", "meta", "property", "og:title", allow_redirects=True, proxies=proxies)
if control_name:
control_name = control_name["content"]
else:
control_name = control.name
control_name = control_name.split(" - Official app in the Microsoft Store")[0].split(" - Microsoft Apps")[0]
control_name = complete_control_name(control, control_name)
# control.package
if control_name:
control_package = package_prefix + "-" + control_name.lower()
else:
control_package = control.package
complete_control_package(control, control_package, params.get("running_as_luti"), True)
# control.description
control_description = bs_find(
microsoft_store_url + "?gl=US&hl=en-US", "meta", "property", "og:description", allow_redirects=True, proxies=proxies
)
if control_description:
control_description = control_description["content"]
else:
control_description = control.description
complete_control_description(control, control_description, params.get("running_as_luti"), "update_package", 250)
# control.description_fr = bs_find(microsoft_store_url + "?gl=FR&hl=fr-FR", "meta", "property", "og:description", allow_redirects=True, proxies=proxies)["content"]
# control.categories
complete_control_categories(control)
# Updating setup.py appx_package_name variable
new_lines = []
with open("setup.py", "r", encoding="utf8") as f:
for line in f.readlines():
if line.startswith("appx_package_name"):
line = 'appx_package_name = "%s"\n' % appx_package_name
new_lines.append(line)
with open("setup.py", "w", encoding="utf8", newline="\n") as f:
f.writelines(new_lines)
# Get icon.png
icon_png = []
if "b4:Dependencies" in newer_uwp_app:
if newer_uwp_app["b4:Dependencies"]:
temp_appx = unzip(newer_uwp_app["FileName"], ".", newer_uwp_app["b4:Dependencies"][0]["FileName"], False)
icon_png = unzip(newer_uwp_app["b4:Dependencies"][0]["FileName"], ".", "*.targetsize-48_altform-unplated.png", False)
if not icon_png:
icon_png = unzip(newer_uwp_app["b4:Dependencies"][0]["FileName"], ".", "*.targetsize-48.png", False)
if not icon_png:
icon_png = unzip(newer_uwp_app["b4:Dependencies"][0]["FileName"], ".", "**/PkgSmallLogo.png", False)
if icon_png:
shutil.move(icon_png[0], "WAPT\\icon.png")
for f in temp_appx:
if isfile(f):
remove_file(f)
if not icon_png:
icon_png = unzip(newer_uwp_app["FileName"], ".", "*.targetsize-48_altform-unplated.png", False)
if not icon_png:
icon_png = unzip(newer_uwp_app["FileName"], ".", "*.targetsize-48.png", False)
if icon_png:
shutil.move(icon_png[0], "WAPT\\icon.png")
[remove_file(a) for a in glob.glob("*.png")]
# Placing binaries in a dir ["appxbundle", "msixbundle", "appx", "msix]
for f in glob.glob("*.part"):
remove_file(f)
bins_dir = control.package.split("-", 1)[1]
if isdir(bins_dir):
remove_tree(bins_dir)
mkdirs(bins_dir)
for uwp_file in glob.glob(f'{newer_uwp_app["Name"]}*{newer_uwp_app["Version"]}*.*'):
if not isfile(makepath(bins_dir, uwp_file)):
shutil.move(uwp_file, bins_dir)
if uwp_app_dict[newer_uwp_app["FileName"]]["Dependencies"] is not None:
for l in [glob.glob(a["Name"] + "_*") for a in uwp_app_dict[newer_uwp_app["FileName"]]["Dependencies"]]:
for f in l:
if not isfile(makepath(bins_dir, f)):
shutil.move(f, bins_dir)
# Removing remaining files ["appxbundle", "msixbundle", "appx", "msix]
for f in glob.glob("*.appxbundle") + glob.glob("*.msixbundle") + glob.glob("*.appx") + glob.glob("*.msix"):
remove_file(f)
# Changing version of the package and validating update-package-sources
return complete_control_version(control, version)
def json_write_file(json_file, data, indent=4, sort_keys=False, encoding="utf-8", newline="\n"):
"""
Write dictionary to a JSON file.
Args:
json_file (str): Path to the JSON file.
data (dict): Dictionary content.
indent (int or str): Tabulation size for indentation; default: 4 spaces.
sort_keys (bool): Sort the keys alphabetically or not; default: False.
encoding (str): File encoding; default: 'utf-8'.
newline (str): Newline character(s) to use; default: 'LF'.
"""
with open(json_file, "w", encoding=encoding, newline=newline) as write_file:
json.dump(data, write_file, sort_keys=sort_keys, indent=indent)
def download_app_files(all_files, appx_package_name, proxies=None, ignored_versions=[], version_prefix=None):
for app_file in all_files:
latest_bin = app_file["bin_name"]
if not latest_bin.startswith(f"{appx_package_name}_"): # ignore other files
continue
if latest_bin.split(".")[-1].lower().startswith("e"): # ignore encrypted UWP apps
continue
if version_prefix is not None and not Version(app_file["version"], len(version_prefix.split("."))) == Version(
version_prefix, len(version_prefix.split("."))
):
continue
if any(
Version(app_file["version"], len(ignored_version.split("."))) == Version(ignored_version, len(ignored_version.split(".")))
for ignored_version in ignored_versions
):
continue
download_url = app_file["download_url"]
if download_url.split("/")[2].endswith("microsoft.com"):
if not isfile(latest_bin):
print("Downloading: %s" % latest_bin)
wget(download_url, latest_bin, proxies=proxies)
else:
print("Binary is present: %s" % latest_bin)
else:
error("ERROR: The retrieved URL will not download from Microsoft's servers")
def get_uwp_architectures(newer_uwp_app):
"""
Returns a comma-separated string of compatible architectures extracted from the 'control.architecture'
within the provided 'newer_uwp_app' dictionary.
Args:
newer_uwp_app (dict): A dictionary representing a Universal Windows Platform (UWP) app.
Returns:
str: A comma-separated string of compatible architectures (e.g., "x64,arm64,x86,arm"). If no
compatible architectures are found, it returns "all".
"""
architecture_list = []
if newer_uwp_app.get("b4:Dependencies", None):
for before_dependency in newer_uwp_app.get("b4:Dependencies"):
architecture_list.append(before_dependency["Architecture"])
sorted_architecture_list = sorted(architecture_list, key=lambda arch: ["neutral", "x64", "x86", "arm64", "arm"].index(arch))
control_architecture = ",".join(sorted_architecture_list)
if control_architecture.startswith("x64,x86") or "neutral" in control_architecture:
control_architecture = "all"
return control_architecture
else:
return get_uwp_filename_arch(newer_uwp_app["FileName"], newer_uwp_app["Name"])
def get_uwp_filename_arch(appx_filename, appx_package_name=None):
"""
Returns the architecture of a Universal Windows Platform (UWP) app based on the provided 'appx_filename'
and optionally, the 'appx_package_name'.
Args:
appx_filename (str): The filename of the UWP app package.
appx_package_name (str, optional): The package name of the UWP app. Defaults to None.
Returns:
str: The architecture of the UWP app, which can be "x64", "arm64", "x86", or "all" (if no specific
architecture is detected).
"""
if not appx_package_name:
appx_package_name = None
if len(glob.glob(f'{appx_package_name}*{appx_filename.split("_")[1]}*')) > 1:
pass
elif "arm64" in appx_filename:
return "arm64"
elif "arm" in appx_filename:
return "arm"
elif "x64" in appx_filename:
return "x64"
if appx_filename is not None and "x86" in appx_filename and (appx_package_name is None or not appx_package_name in appx_filename):
return "x86"
return "all"
def get_newer_uwp_app(uwp_app_dict, version_prefix=None, min_version=None, max_version=None, tdf_list=["Windows.Universal", "Windows.Desktop"]):
"""Get the newer Universal Windows Platform (UWP) app from a dictionary of UWP apps based on specified criteria.
Args:
uwp_app_dict (dict): A dictionary containing UWP app information with app IDs as keys and app details as values.
version_prefix (str, optional): The prefix of the version number to filter the apps. Defaults to None.
min_version (str, optional): The minimum version of the UWP app. Defaults to None.
max_version (str, optional): The maximum version of the UWP app. Defaults to None.
tdf_list (list, optional): A list of target device families to filter the apps. Defaults to ["Windows.Universal", "Windows.Desktop"].
Returns:
dict or None: The dictionary containing the details of the newer UWP app that matches the specified criteria, or None if no app is found.
"""
newer_uwp_app = None
newer_version = "0"
for uwp_app in uwp_app_dict.values():
to_skip = False # Initialize to_skip as False and set to True only if any condition fails
# Check version prefix
if version_prefix is not None and not Version(uwp_app["Version"], len(version_prefix.split("."))) == Version(
version_prefix, len(version_prefix.split("."))
):
to_skip = True
continue
# Check target device family
if tdf_list is not None and uwp_app["TargetDeviceFamily"]:
for tdf in tdf_list:
for uwp_app_tdf in uwp_app["TargetDeviceFamily"]:
if (
tdf == uwp_app_tdf["Name"]
and (min_version is None or Version(min_version, 3) == Version(uwp_app_tdf["MinVersion"], 3))
and (max_version is None or Version(max_version, 3) == Version(uwp_app_tdf["MaxVersionTested"], 3))
):
break
else:
continue
break
else:
to_skip = True
# Check max OS version
if control.max_os_version and not Version(uwp_app["MinVersion"], 3) < Version(control.max_os_version, 3):
to_skip = True
if to_skip:
continue
# Update the newer UWP app if the current app has a higher version
if Version(newer_version) < Version(uwp_app["Version"]):
newer_uwp_app = uwp_app
newer_version = newer_uwp_app["Version"]
return newer_uwp_app
def get_newer_uwp_depency(dependency_dict, all_files_dict, version_prefix=None, min_version=None):
"""Returns a list of the bin_name of the latest required dependencies"""
newer_version = "0"
for uwp_app in all_files_dict:
if not uwp_app["bin_name"].startswith(f'{dependency_dict["Name"]}_'):
continue
if version_prefix is not None and Version(uwp_app["version"], len(version_prefix.split("."))) != Version(
version_prefix, len(version_prefix.split("."))
):
continue
if Version(newer_version) < Version(uwp_app["version"]):
newer_uwp_depency = uwp_app
newer_version = uwp_app["version"]
return newer_uwp_depency["bin_name"].split(newer_version + "_")[0] + newer_version + "_"
def make_uwp_app_dict(appx_package_name):
ms_app_db = {}
# for ms_app_file in (
# glob.glob("*.appxbundle")
# + glob.glob("*.msixbundle")
# + glob.glob("*.appx")
# + glob.glob("*.msix")
# ):
for ms_app_file in (
glob.glob(f"{appx_package_name}*.appxbundle")
+ glob.glob(f"{appx_package_name}*.msixbundle")
+ glob.glob(f"{appx_package_name}*.appx")
+ glob.glob(f"{appx_package_name}*.msix")
):
data_dict = {}
before_dependencies = []
sub_dependencies = []
dependency_data_dict = None
manifest = unzip(ms_app_file, ".", "AppxManifest.xml", False)
bundle_manifest = unzip(ms_app_file, ".", "AppxMetadata\\AppxBundleManifest.xml", False)
ms_app_info = {
"FileName": ms_app_file,
}
if not manifest:
if bundle_manifest:
with open(bundle_manifest[0], encoding="utf8") as xml_file:
data_dict = xmltodict.parse(xml_file.read(), attr_prefix="")
if type(data_dict["Bundle"]["Packages"]["Package"]) == dict:
data_dict["Bundle"]["Packages"]["Package"] = [dict(data_dict["Bundle"]["Packages"]["Package"])]
for app_pkg in list(data_dict["Bundle"]["Packages"]["Package"]):
if app_pkg["Type"] == "application":
before_dependency_info = {
"FileName": app_pkg["FileName"],
"Version": app_pkg["Version"],
"Architecture": app_pkg["Architecture"],
}
before_dependencies.append(before_dependency_info)
if before_dependencies:
for dependency in before_dependencies:
unzip(
ms_app_file,
".",
makepath(
dependency["FileName"],
),
False,
)
manifest = unzip(dependency["FileName"], ".", makepath("AppxManifest.xml"), False)
if isfile(dependency["FileName"]):
remove_file(dependency["FileName"])
with open(manifest[0], encoding="utf8") as xml_file:
dependency_data_dict = xmltodict.parse(xml_file.read(), attr_prefix="")
if dependency_data_dict["Package"].get("Dependencies"):
if dependency_data_dict["Package"]["Dependencies"].get("PackageDependency"):
sub_dependencies = list(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"])
if type(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]) == dict:
sub_dependencies = [dict(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"])]
if type(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]) == list:
sub_dependencies = dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]
if dependency_data_dict["Package"]["Dependencies"].get("TargetDeviceFamily"):
if not "TargetDeviceFamily" in ms_app_info:
if type(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]) == dict:
ms_app_info["TargetDeviceFamily"] = [dict(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"])]
if type(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]) == list:
ms_app_info["TargetDeviceFamily"] = dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]
else:
error("nothing to parse")
else:
manifest = unzip(ms_app_file, ".", makepath("AppxManifest.xml"), False)
with open(manifest[0], encoding="utf8") as xml_file:
dependency_data_dict = xmltodict.parse(xml_file.read(), attr_prefix="")
if dependency_data_dict["Package"].get("Dependencies"):
if "PackageDependency" in dependency_data_dict["Package"]["Dependencies"]:
sub_dependencies = list(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"])
if type(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]) == dict:
sub_dependencies = [dict(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"])]
if type(dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]) == list:
sub_dependencies = dependency_data_dict["Package"]["Dependencies"]["PackageDependency"]
if "TargetDeviceFamily" in dependency_data_dict["Package"]["Dependencies"]:
if not "TargetDeviceFamily" in ms_app_info:
if type(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]) == dict:
ms_app_info["TargetDeviceFamily"] = [dict(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"])]
if type(dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]) == list:
ms_app_info["TargetDeviceFamily"] = dependency_data_dict["Package"]["Dependencies"]["TargetDeviceFamily"]
else:
ms_app_info.update({"Dependencies": None})
if ms_app_info.get("TargetDeviceFamily"):
ms_app_info.update(
{
"MinVersion": ms_app_info["TargetDeviceFamily"][0]["MinVersion"],
"MaxVersionTested": ms_app_info["TargetDeviceFamily"][0]["MaxVersionTested"],
}
)
elif dependency_data_dict is not None and dependency_data_dict["Package"].get("Prerequisites"):
ms_app_info.update(
{
"MinVersion": dependency_data_dict["Package"]["Prerequisites"]["OSMinVersion"],
"MaxVersionTested": dependency_data_dict["Package"]["Prerequisites"]["OSMaxVersionTested"],
}
)
else:
ms_app_info.update({"TargetDeviceFamily": None})
if data_dict:
ms_app_info.update(data_dict["Bundle"]["Identity"])
else:
ms_app_info.update(dependency_data_dict["Package"]["Identity"])
if "ProcessorArchitecture" in ms_app_info:
ms_app_info.update({"Architecture": ms_app_info["ProcessorArchitecture"]})
if before_dependencies:
ms_app_info["b4:Dependencies"] = before_dependencies
else:
ms_app_info.update({"b4:Dependencies": None})
if sub_dependencies:
ms_app_info["Dependencies"] = sub_dependencies
else:
ms_app_info.update({"Dependencies": None})
ms_app_db[ms_app_file] = ms_app_info
for xml_file in glob.glob("AppxManifest.xml") + glob.glob("AppxBundleManifest.xml"):
remove_file(xml_file)
return ms_app_db
def set_control_os_version_uwp_app(
newer_uwp_app,
force_values=False,
priority_windows=WindowsVersions.Windows11,
eol_windows=WindowsVersions.Windows10v2004,
silent=False,
):
if newer_uwp_app.get("MinVersion") and newer_uwp_app.get("MaxVersionTested"):
control.min_os_version = ".".join(
newer_uwp_app["MinVersion"].split(".")[: 2 if ".".join(newer_uwp_app["MinVersion"].split(".")[:3]).endswith("0") else 3]
)
# MaxVersionTested value seems not reliable, now asking to change control.max_os_version
if not Version(newer_uwp_app["MaxVersionTested"], 3) <= eol_windows: # Ignore end-of-life Windows versions
if control.max_os_version != ".".join(newer_uwp_app["MaxVersionTested"].split(".")[:3]):
if force_values:
control.max_os_version = ".".join(newer_uwp_app["MaxVersionTested"].split(".")[:3])
else:
if not silent:
control_max_os_version = ask_input(
control.package,
f'INFO: You may wanna change "control.max_os_version" by "{".".join(newer_uwp_app["MaxVersionTested"].split(".")[:3])}" or "{newer_uwp_app["MaxVersionTested"]}"',
"",
raise_error=False,
)
if control_max_os_version or control_max_os_version == "":
control.max_os_version = control_max_os_version
else:
print(
f'INFO: You may wanna change "control.max_os_version" by "{".".join(newer_uwp_app["MaxVersionTested"].split(".")[:3])}"'
)
else:
if control.max_os_version != "":
if force_values:
control.max_os_version = ""
else:
if not silent:
control_max_os_version = ask_message(
control.package,
f'INFO: You may wanna erase "control.max_os_version : {control.max_os_version}"',
3 + 32,
raise_error=False,
)
if control_max_os_version == 6:
control.max_os_version = ""
control.save_control_to_wapt()
34bb5a9a6e1ad8edaa45b965f47cfedd7eee0c17ac21540518d2afb05f08c11e : setup.py
2ec8874c3a300ac076e2ced2ac78451d507d403d08ab0344cf39c0d0d20be819 : update_package.py
bd43e5749147a75dbd658cac066506657073da0ee363e93418ed66427dae1bb6 : package.json
aea6d0c53867b3d774e670da830d8ef922bd93a4ca37ea565e7bbb8465152983 : WAPT/icon.png
a5a97261381e1d0ad46ee15916abec9c2631d0201f5cc50ceb0197a165a0bbbf : WAPT/certificate.crt
9152e6df5d42eac2be1f1e516539c54b794dcd93575152e02d81cc0181992a11 : WAPT/changelog.txt
364e01205533a0c6907fb9e8a8f70353985ae81ead35b71aade9a4253184f847 : xmltodict.py
c802d056a635d7c09e516c827ce4f49551b98844183adf0aa5896ea5bdfd2a03 : setupdevhelpers.py
4124587240319a35b49962bd8f94a00ea2176af2ffe1f33718462f6997d5f2b4 : luti.json
1cf1652be6ae18283b5ead4a73b30fa3352bd3a0aa046ef5fc0f827d95911c80 : WAPT/control
Changelog
0-64
===
if the package is not found in the uwp_app_dict an error is returned.
avoid crash with Add-AppxProvisionedPackage when AppxPackage only is already up-to-date.
0-62
===
"tis-template-microsoft-store-app": changed installation method from "Add-AppxPackage" to "Add-AppxProvisionedPackage". Whole setup.py have been reworked.
"tis-template-microsoft-store-dependency": is now used to install Dependencies/DLC/Add-on and stay based on "Add-AppxPackage" installation method.
now autocomplete control.impacted_process and control.installed_size
fix neutral Architecture
fix some crash in control informations asked once
proxy workarounds
fix control.name fetched from page
0-52
===
improve compatibility with Windows 8.x apps
improve complete_control_*() functions
includes xmltodict instead of running "waptpython -m pip download xmltodict"
now avoiding downloads loop and avoid keeping sources of other apps
check version before checking files in session_setup
implement version_prefix to select a preferred version
architecture is:
- no longer forced on update
- pre-populate multi-architectures when relevant
- only compatible architecture dependencies are now downloaded
0-47
===
now, you need to select the app version that you want to package, which enables you to:
- Download only useful files without editing the update_package.py file.
- Create the package for both Windows 11 and Windows 10 without editing the control file.
- Automatically define ignored_versions saved in the package.json file.
handle multi-architecture packages (only works on min_wapt_version: 2.4).
min_wapt_version: 2.3
adding remove_sources feature but do not using it by default since it may prevent multi-user installations.
now asking if user wanna change control.max_os_version instead of only printing it
full audit
by default control_max_os_version if not filled (safer)
0-43
===
fix get_newer_uwp_app and adding multiple options : (version_prefix, min_version, max_version)
create set_control_os_version_uwp_app function
now only download latest necessary dependencies
fix packages where control.architecture != "all"
smarter descriptions editing
min_wapt_version : 2.2.3
should now works for Windows 8.1+ apps
fix update_package when files without extensions have been detected
fix update_package when no dependencies are required
to avoid unnecessary downloads you can now use download_app_files(ignored_versions=["2017", "2018", "2019", "2020", "2020.1"])
better control completion
setupdevhelpers is now shipped with advanced functions to reduce code duplication
only complete_control once
0-32
===
now includes dependencies, so the tis-template-microsoft-store-dependency part is no longer needed
auto update icon.png
auto update control.min_os_version
smarter popup to ask for appx_package_name
handle "Windows.Desktop" and "Windows.Universal" TargetDeviceFamily
removes encrypted uwp apps
auto update control.max_os_version if relevant ; max_os_version fiabilized ; Ignore end-of-life Windows versions by default
0-25
===
fix rare case of incorrect glob.glob
better handling of binary suppression
0-24
===
fix silent mode
more information shown in grid_dialog
update_package() was inccorect for some architecture
prefix was forced to tis
now using full AppxPackage Name for dependencies
better handling of remove_outdated_binaries and add_depends
improve used regex
using functions for some popups, functions return associated str
more default actions on popups
fix update_package for 2.3 with setupdevhelpers import
fix package name change, and depencies warning not to block luti build
warn the end-user that dependencies are required and providing instructions on how to acquire and deploy them
0-12
===
asking for categories
asking for description
adding dependencies
changing setup.py appx_package variable
smart update_package() it do not popup anything to download updates
using Microsoft_Store_Fluent_Design_icon
remove_outdated_binaries handle extensions
removing template files when used
allow_remove option
specify arch
min_wapt_version : 2.2
ask to change package_name
fix re.split
fix multi download on neutral apps
only ask once for description