Template Microsoft Store App
Paquet d’installation silencieuse pour Template Microsoft Store App
tis-template-microsoft-store-app Paquet modèle pour créer et maintenir des paquets d'applications Microsoft Store avec WAPT.
Procédure d'installation
Ce paquetage sert de modèle pour créer des paquets WAPT qui récupèrent, versionnent et empaquettent automatiquement les applications disponibles dans le Microsoft Store (UWP / MSIX / APPX).
Procédure d'utilisation
Étape 1 - Télécharger le paquet Téléchargez le paquet tis-template-microsoft-store-app dans votre console WAPT locale Étape 2 - Lancer le script de mise à jour Ouvrez la console WAPT et faites un clic droit sur le paquet → Lancez update_package. Ce script vous guidera dans la création de votre paquet d'applications Microsoft Store.
Questions interactives (posées par update_package.py)
Au cours du processus de mise à jour, plusieurs boîtes de dialogue apparaîtront pour personnaliser votre paquet : Étape Question / Dialogue Description
-1 Entrée de l'identifiant de l'application : Entrez l'identifiant de l'application du Microsoft Store (ex : https://apps.microsoft.com/store/detail/<APP_ID>) -2 Sélection du paquet : Une liste des binaires disponibles s'affiche. Sélectionnez la version .appx / .msixbundle appropriée. -3 Sélection de l'architecture : S'il existe plusieurs architectures (x64, arm64, etc.), sélectionnez celle à inclure. -4 Sélection de la catégorie : Choisissez une ou plusieurs catégories WAPT (Internet, Office, Media, etc.). -5 Nom du package : Vous pouvez renommer la valeur control.package (par défaut : générée automatiquement à partir du nom de l'application). -6 Nom d'affichage : Définissez le nom du contrôle affiché dans le Self-Service. -7 Description : Vous pouvez modifier ou accepter la description pré-remplie (extraite du Microsoft Store).
Video Explicative
Vous pouvez suivre cette vidéo pour plus d'explications :
- package: tis-template-microsoft-store-app
- name: Template Microsoft Store App
- version: 0-80
- categories: Template
- maintainer: WAPT Team,Tranquil IT,Jimmy Pelé
- licence: wapt_public
- locale: all
- target_os: windows
- architecture: all
- signature_date:
- size: 40.61 Ko
package : tis-template-microsoft-store-app
version : 0-80
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 : f1cd7a30-c76f-46e0-9eb5-95ec74495c74
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_date : 2025-10-09T20:02:33.000000
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
signature : ElZALz3X2TXo63CX6z5ieP0O67qKCRN9GfNuhygo7/pmATY0zAqiZ2RHmXEBbg/lb3M1FslsTjqA1/SdkkXtrmxExnF9jXQRb85vtpRfuUN+sCcMjXKEmj2+zIYAYKM9mi5n0bwKg0NPiWhO1OuFhntYHjp+9vmfUAJS4QUU0GS0GlpKbTfINtztgnLVj8SLI4iJxi7AS/14epNRG9/varSTRsnJ6gru9b6+ZAclRQNZDLZ2OYjdS9iSCmHkFL4biUjjHfG84fFldeECp9BsZBwnYTdSuF9uQCKV8RaxkL9efMT6Sj83ZuDOiSoV7JIb/OMpqYl5Wk/ub5ypoip1uw==
# -*- 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")
# -*- coding: utf-8 -*-
from setuphelpers import *
try:
from setupdevhelpers import *
except:
from bs4 import BeautifulSoup
import json
import waptguihelper
import html
from xml.dom import minidom
import requests
import json
import re
release_name_map = {"retail": "Retail", "RP": "Release Preview", "WIS": "Insider Slow", "WIF": "Insider Fast"}
release_type = "retail"
release_name = release_name_map[release_type]
arch = "x64"
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
import xmltodict
#For debug mode, set as True
debug_mode = False
#To make 5 api requests, set as True / Only use if retrieve binary is bad
loop_enable = False
def update_package():
# Declaring local variables
package_updated = False
proxies = get_proxies()
if not proxies:
proxies = get_proxies_from_wapt_console()
store_url = control.sources
if not store_url:
store_id = waptguihelper.input_dialog(
"Choice of app", "Enter the windows store app id (foundable in package url: https://apps.microsoft.com/store/apps)", store_url
).split("/")[-1]
else:
store_id = store_url.split("/")[-1]
store_id = store_id.split("?")[0]
url_ms = "https://apps.microsoft.com/store/detail/%s" % store_id
control.sources = url_ms
control.save_control_to_wapt()
package_json = get_json(store_id)
# Updating package.json ignored_versions list
if isfile("package.json"):
package_infos = json_load_file("package.json")
else:
package_infos = {"version_prefix": None, "ignored_versions": []}
ignored_versions = package_infos["ignored_versions"]
version_prefix = package_infos.get("version_prefix", None)
#Debug print package.json informations
if debug_mode:
print("\n\n=== CAREFUL : DEBUG MODE IS ACTIVE ===\n")
print(f"version prefix : {version_prefix}")
print(f"ignored versions : {ignored_versions}\n")
#Get files from Microsoft Store API
print("Retrieving informations from Microsoft Store API, please wait\n")
all_files_dict = get_all_files(url_ms,proxies)
#Debug print all_files_dict, contains all files from a Store url API request
if debug_mode:
all_files_dict_json = json.dumps(all_files_dict,indent=4)
print("*** Pretty return from get_all_files() ***")
print(f"all_files_dict : {all_files_dict_json}\n")
# check setup.py incase the package name doesnt match the installer file
with open("setup.py", "r") as f:
for line in f.readlines():
if line.startswith("appx_package"):
store_package_name = line.split("=")[1].split('"')[1]
break
#Find the last binaries or the one matching prefix inside all_files_dict
bin_selected_dict = ask_app_filename(all_files_dict, store_package_name,ignored_versions=ignored_versions,version_prefix=version_prefix)
#Debug print post filtering to see which files have been selected
if debug_mode:
bin_selected_dict_json = json.dumps(bin_selected_dict,indent=4)
print("*** Selected file from ask_app_file_name() ***")
print(f"bin_selected_dict : {bin_selected_dict_json}\n")
#Define binarie variables
appx_package = bin_selected_dict[0]["bin_name"].split("_")[0]
bin_selected = bin_selected_dict[0]
latest_bin = bin_selected["bin_name"]
version = bin_selected["version"]
download_url = bin_selected["download_url"]
package_name = bin_selected["package_name"]
software_name = bin_selected["software_name"]
package_arch = bin_selected["package_arch"]
#Download binarie file
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 file version corresponds to online version")
else:
print("ERROR: The retrieved url will not download from microsoft's servers")
# Asking pkg infos if needed and not running as luti
if not params.get("running_as_luti"):
if package_arch == "all":
ask_control_architecture(package_arch)
ask_control_categories()
ask_control_package(package_name, "template-microsoft-store", remove_base_files=True)
ask_control_name(software_name, "Template Microsoft Store")
# description from microsoft website
ask_control_description("update_package", get_description(package_json))
# Changing setup.py appx_package variable
new_lines = []
with open("setup.py", "r") as f:
for line in f.readlines():
if line.startswith("appx_package"):
line = 'appx_package_name = "%s"\n' % latest_bin.split("_")[0]
new_lines.append(line)
with open("setup.py", "w") as f:
f.writelines(new_lines)
#Making dependency dict from downloaded binary
uwp_app_dict = make_dependency_dict(appx_package)
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}" not found in "uwp_app_dict". This is probably caused by "package_json" filters:\n{package_json}')
#Download each dependency
newer_uwp_app = download_uwp_dependency(uwp_app_dict,all_files_dict,proxies)
#To make things pretty
if debug_mode:
print("\n")
# Changing version of the package
if Version(version) > Version(control.get_software_version()):
print("Software version updated (from: %s to: %s)" % (control.get_software_version(), Version(version)))
package_updated = True
else:
print("Software version up-to-date (%s)" % Version(version))
if debug_mode:
print("Deleting binary files and exiting")
for uwp_file in glob.glob(f'{newer_uwp_app["Name"]}*{newer_uwp_app["Version"]}*.*'):
remove_file(uwp_file)
return
control.set_software_version(version)
control.save_control_to_wapt()
# 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)
# Deleting outdated binaries
remove_outdated_binaries(version, ["appxbundle", "msixbundle", "appx", "msix", "part"], latest_bin)
# Validating or not update-package-sources
return package_updated
def get_json(app_id):
# download json from url and return it as dict
url = "https://storeedgefd.dsx.mp.microsoft.com/v9.0/products/%s?market=US&locale=en-us&deviceFamily=Windows.Desktop" % app_id
dict = json.loads(requests.get(url).text)
return dict
def get_all_files(store_id,proxies):
all_files = url_generator(
store_id,
proxies=proxies
)
#Debug print raw dict directly from API
if debug_mode:
all_files_json = json.dumps(all_files,indent=4)
print("*** Raw return from url_generator() ***")
print(f"all_files : {all_files_json}\n")
all_files_dict = []
# parse every file name and url in all_files and make a dict:
for i,download_url in all_files[0].items():
version = i.split("_")[1]
bin_name = i.replace("~", "_")
pkg_splitted = re.split(r"_\d+\.", bin_name)[0]
package_prefix = control.package.split("-")[0]
package_name = package_prefix + "-" + pkg_splitted.split("_")[0].replace(".", "-").lower()
software_name = bin_name.split("_")[0].replace("-", " ").replace(".", " ")
if "arm64" in bin_name:
package_arch = "arm64"
elif "arm" in bin_name:
package_arch = "arm"
elif "x64" in bin_name:
package_arch = "x64"
# elif "x86" in bin_name:
# package_arch = "all" # not x86 since it may be required for x64
else:
package_arch = "all"
file_dict = {
"version": version,
"bin_name": bin_name,
"package_name": package_name,
"software_name": software_name,
"package_arch": control.architecture,
"package_arch": package_arch,
"download_url": download_url,
}
all_files_dict.append(file_dict)
return all_files_dict
def url_generator(url,proxies):
def uwp_gen(data_list):
cat_id = data_list["WuCategoryId"]
main_file_name = data_list["PackageFamilyName"].split("_")[0]
release_type = "Retail"
#Ask Microsoft API for a cookie
with open(rf"{basedir}\data\xml\GetCookie.xml", "r") as f:
cookie_content = f.read()
out = requests.post(
"https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx",
data=cookie_content,
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
proxies=proxies,
verify=False
).text
doc = minidom.parseString(out)
cookie = doc.getElementsByTagName("EncryptedData")[0].firstChild.nodeValue
with open(rf"{basedir}\data\xml\WUIDRequest.xml", "r") as f:
cat_id_content = f.read().format(cookie, cat_id, release_type)
out = requests.post(
"https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx",
data=cat_id_content,
headers={"Content-Type": "application/soap+xml; charset=utf-8"},proxies=proxies,verify=False).text
doc = minidom.parseString(html.unescape(out))
if loop_enable:
#To avoid picking wrong miror, make 5 requests and merge responses
req = 0
for req in range(4):
out2 = requests.post(
"https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx",
data=cat_id_content,
headers={"Content-Type": "application/soap+xml; charset=utf-8"},proxies=proxies,verify=False).text
doc2 = minidom.parseString(html.unescape(out2))
imp = doc.importNode(doc2.childNodes[0],True)
doc.childNodes[0].appendChild(imp)
req += 1
filenames = {}
for node in doc.getElementsByTagName("Files"):
try:
filenames[
node.parentNode.parentNode.getElementsByTagName("ID")[0].firstChild.nodeValue
] = (
f"{node.firstChild.attributes['InstallerSpecificIdentifier'].value}_{node.firstChild.attributes['FileName'].value}",
node.firstChild.attributes["Modified"].value,
)
except KeyError:
continue
if not filenames:
raise Exception("server returned an empty list")
identities = {}
name_modified = {}
for node in doc.getElementsByTagName("SecuredFragment"):
try:
file_name, modifed = filenames[
node.parentNode.parentNode.parentNode.getElementsByTagName("ID")[0].firstChild.nodeValue
]
update_identity = node.parentNode.parentNode.firstChild
name_modified[file_name] = modifed
identities[file_name] = (
update_identity.attributes["UpdateID"].value,
update_identity.attributes["RevisionNumber"].value,
)
except KeyError:
continue
api_dict = {value: identities[value] for value in name_modified}
#Debug print raw dict directly from API
if debug_mode:
api_dict_json = json.dumps(api_dict,indent=4)
print("*** First API Return from url_generator() ***")
print(f"api_dict : {api_dict_json}\n")
with open(rf"{basedir}\data\xml\FE3FileUrl.xml", "r") as f:
file_content = f.read()
file_dict = {}
for file_name, (updateid, revisionnumber) in api_dict.items():
out = requests.post(
"https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx/secured",
data=file_content.format(updateid, revisionnumber, release_type),
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
proxies=proxies,
verify=False
).text
doc = minidom.parseString(out)
for i in doc.getElementsByTagName("FileLocation"):
url = i.getElementsByTagName("Url")[0].firstChild.nodeValue
if len(url) != 99:
file_dict[file_name] = url
if len(file_dict) != len(api_dict):
raise Exception("server returned an incomplete list")
return file_dict, main_file_name
def non_uwp_gen(product_id):
api = f"https://storeedgefd.dsx.mp.microsoft.com/v9.0/packageManifests//{product_id}?market=US&locale=en-us&deviceFamily=Windows.Desktop"
data = requests.get(api,proxies=proxies).text
datas = json.loads(data)
if not datas.get("Data"):
raise Exception("server returned an empty list")
file_name = datas["Data"]["Versions"][0]["DefaultLocale"]["PackageName"]
installer_list = datas["Data"]["Versions"][0]["Installers"]
download_data = set((d["Architecture"], d["InstallerLocale"], d["InstallerType"], d["InstallerUrl"]) for d in installer_list)
curr_arch = os_arc()
download_data = list(download_data)
arch, locale, installer_type, url = download_data[0]
if len(download_data) > 1:
for data in download_data[1:]:
if arch not in ("neutral", curr_arch) and data[0] in ("neutral", curr_arch):
arch, locale, installer_type, url = data
elif data[0] == arch and data[1] != locale and ("us" in data[1] or "en" in data[1]):
locale, installer_type, url = data[1], data[2], data[3]
break
main_file_name = clean_name(file_name) + "." + installer_type
file_dict = {main_file_name: url}
return file_dict, [main_file_name], main_file_name, False
def clean_name(badname):
name = "".join([(i if (64 < ord(i) < 91 or 96 < ord(i) < 123) else "") for i in badname])
return name.lower()
pattern = re.compile(r".+\/([^\/\?]+)(?:\?|$)")
matches = pattern.search(str(url))
if not matches:
raise Exception("No Data Found: --> [You Selected Wrong Page, Try Again!]")
product_id = matches.group(1)
details_api = f"https://storeedgefd.dsx.mp.microsoft.com/v9.0/products/{product_id}?market=US&locale=en-us&deviceFamily=Windows.Desktop"
data = requests.get(details_api,proxies=proxies).text
response = json.loads(data, object_hook=lambda obj: {k: json.loads(v) if k == "FulfillmentData" else v for k, v in obj.items()})
if not response.get("Payload"):
raise Exception("No Data Found: --> [You Selected Wrong Page, Try Again!]")
response_data = response["Payload"]["Skus"][0]
data_list = response_data.get("FulfillmentData")
if data_list:
return uwp_gen(data_list)
else:
return non_uwp_gen(product_id)
def ask_app_filename(all_files_dict, store_package_name, ignored_versions=[], version_prefix=None):
# input files dict and a store packa_name if it exist, if empty selct automatically the proper file if not ask user to select it
if "template-microsoft-store" in control.package:
selected = waptguihelper.grid_dialog(
"Please select the proper file",
json.dumps(all_files_dict),
waptguihelper.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}]}',
)
else:
selected = [a for a in all_files_dict if (a["package_arch"] in ensure_list(control.architecture) or control.architecture == "all" or a["package_arch"] == "all")]
if len(selected) > 1:
if version_prefix:
target_version = None
for app_file in all_files_dict:
latest_bin = app_file["bin_name"]
if not latest_bin.startswith(f"{store_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 Version(app_file["version"], len(version_prefix.split("."))) == Version(
version_prefix, len(version_prefix.split(".")) and target_version < Version(app_file["version"]) #Find the file matching version_prefix
):
target_version=app_file["version"]
break
selected = [a for a in selected if target_version == a["version"] and store_package_name in a["bin_name"]]
else:
higher_version = "0"
for a in all_files_dict:
if any(
Version(a["version"], len(ignored_version.split("."))) == Version(ignored_version, len(ignored_version.split(".")))
for ignored_version in ignored_versions #ignore files in ignored_versions
):
continue
if Version(higher_version) < Version(a["version"]) and store_package_name in a["bin_name"]: # Select the higher version
higher_version = a["version"]
continue
selected = [a for a in selected if higher_version == a["version"] and store_package_name in a["bin_name"]]
return selected
def ask_control_categories():
"""Requesting that the user supply package categories for the control.categories field if empty or Template is selected"""
if control.categories == "Template":
categories = waptguihelper.grid_dialog(
"Select package categories",
[
"Internet",
"Utilities",
"Messaging",
"Security",
"System and network",
"Media",
"Development",
"Office",
"Drivers",
"Education",
"Configuration",
"CAD",
"Template",
"Dependencies",
"Extensions",
],
waptguihelper.GRT_SELECTED,
)
if categories:
control.categories = ",".join([a["unknown"] for a in categories])
else:
control.categories = ""
control.save_control_to_wapt()
return control.categories
def ask_control_architecture(package_arch):
"""Requesting that the user supply package architecture for the control.architecture field if empty or Template is selected"""
if control.categories == "Template":
architecture = waptguihelper.grid_dialog(
f"Package Architecture available : {package_arch}",
[
"all",
"x64",
"x86",
"arm",
"arm64",
],
waptguihelper.GRT_SELECTED,
)
if architecture:
control.architecture = ",".join([a["unknown"] for a in architecture])
else:
control.architecture = package_arch
control.save_control_to_wapt()
return control.architecture
def ask_control_package(control_package, conditionnal_package_name=None, remove_base_files=False):
"""Requesting that the user provide a package name to be entered into the control.package field, and offering the possibility of removing the base files (icon.png and changelog.txt) for template package usage
Args:
control_package (str) : prefilled control_package (default: actual control_package)
conditionnal_package_name (str) : only ask when the control.package contains conditionnal_package_name (default: always ask for control_package)
remove_base_files (bool) : removes base files if parameter is True and conditionnal_package_name is provided (default: False)
"""
if conditionnal_package_name is None or conditionnal_package_name in control.package:
control.package = waptguihelper.input_dialog(control.package, "You can redefine the package name", control_package)
control.save_control_to_wapt()
# Removing template files
if conditionnal_package_name in control.package and remove_base_files:
if isfile("WAPT\\changelog.txt"):
remove_file("WAPT\\changelog.txt")
if isfile("WAPT\\icon.png"):
remove_file("WAPT\\icon.png")
return control.package
def ask_control_name(control_name, conditionnal_package_name=None):
"""Requesting that the user provide a package name to be entered into control.name field
Args:
control_name (str) : prefilled control_name (default: control.name)
conditionnal_package_name (str) : only ask when the control.name contains conditionnal_package_name (default: always ask for control_name)
"""
if conditionnal_package_name is None or conditionnal_package_name in control.name:
control.name = waptguihelper.input_dialog(control.name, "You can redefine the name for the self-service", control_name)
control.save_control_to_wapt()
return control.name
def ask_control_description(blank_str=None, description_from_store=""):
"""Requesting that the user supply package description for the control.description field
Args:
blank_str (str): The description will be cleared if it includes the specified string to avoid using the template description (default: do not clear description)
"""
if not control.description:
control.description = ask_dialog("Description", "Please fill the description", description_from_store)
control.save_control_to_wapt()
if blank_str is not None and blank_str in control.description:
control.description = ""
control.save_control_to_wapt()
return control.description
def ask_dialog(title, text, default="", stay_on_top=False):
"""This function opens a dialog box with a action input"""
# - Title: the title for the dialog
# - Text: indicates which value the user should type
# - Default: the default value, if the user do not want to type any
# Optional:
# - StayOnTop: indicates if the form will always stay on top - default is False
return waptguihelper.input_dialog(title, text, default, stay_on_top)
def get_description(dict):
# get the description from the dict
return dict["Payload"]["Skus"][0]["Description"]
def make_dependency_dict(appx_package_name):
#Make dependendy dict directly from binary file
ms_app_db = {}
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 = None
bundle_manifest = None
manifest_path = makepath(basedir,"AppxManifest.xml")
# Get AppxManifest, contains dependencies list
unzip_with_7zip(ms_app_file, ".", "AppxManifest.xml", False)
if isfile(manifest_path):
manifest = manifest_path
bundle_manifest_path = makepath(basedir,"AppxBundleManifest.xml")
# Get AppxBundleManifest, contains dependencies list
unzip_with_7zip(ms_app_file, ".", "AppxMetadata\\AppxBundleManifest.xml", False)
if isfile(bundle_manifest_path):
bundle_manifest = bundle_manifest_path
ms_app_info = {
"FileName": ms_app_file,
}
if not manifest:
if bundle_manifest:
with open(bundle_manifest, 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,
)
unzip(dependency["FileName"], ".", makepath("AppxManifest.xml"), False)
manifest = manifest_path = makepath(basedir,"AppxManifest.xml")
if isfile(dependency["FileName"]):
remove_file(dependency["FileName"])
with open(manifest, 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:
unzip_with_7zip(ms_app_file, ".", makepath("AppxManifest.xml"), False)
manifest = manifest_path = makepath(basedir,"AppxManifest.xml")
with open(manifest, 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 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 download_uwp_dependency(uwp_app_dict,all_files_dict,proxies):
#Return first dict from list of dict
newer_uwp_app = next(iter(uwp_app_dict.values()))
# Downloading dependency files
dependencies_to_download = []
if newer_uwp_app["Dependencies"]:
for dependency in newer_uwp_app["Dependencies"]:
#Find in all_files_dict, dependency needed in binary
dependencies_to_download.append(get_newer_uwp_depency(dependency, all_files_dict, min_version=dependency["MinVersion"]))
if dependencies_to_download:
#Trigger warning that nothing will be download
if debug_mode:
print("\nNO DOWNLOADED DEPENDENCY - ONLY PRINTING\n")
for dependency_file in all_files_dict:
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
#Only printing the dependencie without downloading
if debug_mode:
print(f"Dependencie to download : {latest_bin}")
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")
return newer_uwp_app
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 os_arc():
machine = control.architecture
if machine.endswith("arm64"):
return "arm64"
if machine.endswith("64"):
return "x64"
if machine.endswith("32") or machine.endswith("86"):
return "x86"
else:
return "arm"
9310b127f2eaea992f0e16a4808422260265f394f96ac795d183186a8456e347 : WAPT/README.md
7db26ee8da40cf7348a3fc7c167b6018a5e8c9c4356fb935fb6969b69f72195e : WAPT/README_fr.md
38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
3a6df07709510e8084cd91c49cc9f6b9ea7a96856e3625465fe4a64da84f5814 : WAPT/changelog.txt
8b3b1a90a6a077883022249becb446eae142278138e91cdcc9753ee5451df24f : WAPT/control
aea6d0c53867b3d774e670da830d8ef922bd93a4ca37ea565e7bbb8465152983 : WAPT/icon.png
87370426807b83b7916c4f43942cd9bf71b1eeaf3b44728d241efe107927e3a2 : data/xml/FE3FileUrl.xml
2d3dd983c9d83c2464afaa85ab49ad5c78a537a131ec61e0c32f6b446bed4f55 : data/xml/GetCookie.xml
f8a4681fbeafb4ddcaac37b406374143900d8885b59ba7a7a0ec782d79bacd9b : data/xml/WUIDRequest.xml
c09f54e4c4212c5e4d969f9fcb0e83f87f734e8d09afbdf38bc221add6ffdd90 : luti.json
bd43e5749147a75dbd658cac066506657073da0ee363e93418ed66427dae1bb6 : package.json
34bb5a9a6e1ad8edaa45b965f47cfedd7eee0c17ac21540518d2afb05f08c11e : setup.py
c802d056a635d7c09e516c827ce4f49551b98844183adf0aa5896ea5bdfd2a03 : setupdevhelpers.py
73a48df1936ca29d6843d8de319465926e5b23e685b3bc15f65a3346d66f6bf7 : update_package.py
364e01205533a0c6907fb9e8a8f70353985ae81ead35b71aade9a4253184f847 : xmltodict.py
0-74
==
Add possibilities to make multiple requests to get good version if some mirrors are bad
Improve test if target version specified
0-73
==
Add debug_mode to print informations
0-71
==
Rework to use Microsoft API
0-68
===
Add compatibility with .msixbundle not seen as zipfile
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