Template Microsoft Store App
Silent install package for Template Microsoft Store App
tis-template-microsoft-store-app
Template package to create and maintain Microsoft Store application packages with WAPT.
Installation Procedure
This package serves as a template for creating WAPT packages that automatically retrieve, version, and package applications available in the Microsoft Store (UWP / MSIX / APPX).
Usage Procedure
Step 1 – Download the package
Download the tis-template-microsoft-store-app package to your local WAPT console
Step 2 – Launch the Update Script
Open WAPT Console and right-click on the package → Launch update_package.
This script will guide you through the creation of your Microsoft Store app package.
Interactive Questions (Asked by update_package.py)
During the update process, several dialogs will appear to customize your package:
Step Question / Dialog Description
1 App ID input Enter the Microsoft Store app ID (ex: https://apps.microsoft.com/store/detail/<APP_ID>)
2 Package selection A list of available binaries is shown. Select the correct .appx / .msixbundle version.
3 Architecture selection If multiple architectures exist (x64, arm64, etc.), select the one to include.
4 Category selection Choose one or more WAPT categories (Internet, Office, Media, etc.).
5 Package name You can rename the control.package value (default: auto-generated from app name).
6 Display name Define the control.name shown in Self-Service.
7 Description You can edit or accept the pre-filled description (retrieved from Microsoft Store).
Explanation Video
You can follow this video for more explanation:
- package: tis-template-microsoft-store-app
- name: Template Microsoft Store App
- version: 0-88
- categories: Template
- maintainer: WAPT Team,Tranquil IT,Jimmy Pelé
- licence: wapt_public
- locale: all
- target_os: windows
- architecture: all
- signature_date:
- size: 40.31 Ko
package : tis-template-microsoft-store-app
version : 0-88
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 : 73cf4d9f-485a-426e-b1ce-75b277e0ac05
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 : 2026-03-26T19:00:30.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 : PEmYX2jwn3Z3wGK4nStJXI2pK4Mq0buStmYHb46z+C8I4ApION7FXa9EXC/7EdAl5gCrg1f7Is23Jm+LenPvtkUlUuEzi51OVQRto1p+azQDFCeyn2xgw4BqjTdLWGbJfHm4P6ZMVEwocWYLdNMNqVrA2dIdz5vjRZPflgY+mh1k14pHYaK4WqVIPi4xCXYdmXQKClnOdafaOthUf+e8GHebYRixS2+mjxAJS6ExD8iC6JWcD119KXqo5vjwnjax1jQ4WDBxRhwEH2aBAjnlJ70q/dF2eGUNkP003cfC2QtTLdeaQHEN1/IKGQzld1bz1J4KdNn5mD5Wz9CCvm7stg==
# -*- 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)
shared_appx_bins_dir = makepath(appx_dir, 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 isdir(appx_dir) and 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 "x86" not in fname and "neutral" not in fname:
remove_file(f)
else:
if get_host_architecture() not in fname and "neutral" not in fname:
remove_file(f)
else:
if get_host_architecture() not in fname and "neutral" not in fname:
remove_file(f)
bin_path = glob.glob(makepath(appx_bins_dir, f"{appx_package_name}_*"))[0]
dependencies_paths = [a for a in glob.glob(makepath(appx_bins_dir, "*")) if appx_package_name not in a]
is_emsixbundle = bin_path.lower().endswith(".emsixbundle")
# 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"Preparing installation: {bin_path.split(os.sep)[-1]} ({control.get_software_version()})")
killalltasks(ensure_list(control.impacted_process))
# Cas EMSIXBUNDLE :
# - on copie le bundle dans un emplacement accessible à tous
# - on installe les dépendances si possible en install()
# - le bundle principal sera installé dans session_setup()
if is_emsixbundle:
if not isdir(shared_appx_bins_dir):
mkdirs(shared_appx_bins_dir)
shared_bin_path = makepath(shared_appx_bins_dir, os.path.basename(bin_path))
print(f"Copying EMSIX bundle to: {shared_bin_path}")
filecopyto(bin_path, shared_bin_path)
# Copier aussi les dépendances pour session_setup au cas où elles seraient utiles
for dep in dependencies_paths:
dep_target = makepath(shared_appx_bins_dir, os.path.basename(dep))
print(f"Copying dependency to: {dep_target}")
filecopyto(dep, dep_target)
# Installer les dépendances en machine si possible
for dep in dependencies_paths:
dep_name = os.path.basename(dep)
if dep.lower().endswith((".appx", ".msix", ".appxbundle", ".msixbundle")):
print(f"Installing dependency: {dep_name}")
run_powershell(
f'Add-AppxProvisionedPackage -Online -PackagePath "{dep}" -SkipLicense',
output_format="text",
)
else:
print(f"Skipping dependency (unsupported for install phase): {dep_name}")
else:
dependencies_pathes = ",".join([f'"{a}"' for a in dependencies_paths])
add_appx_cmd = f'Add-AppxProvisionedPackage -Online -PackagePath "{bin_path}" -SkipLicense'
if dependencies_pathes:
add_appx_cmd += f" -DependencyPackagePath {dependencies_pathes}"
print(f"Installing: {bin_path.split(os.sep)[-1]} ({control.get_software_version()})")
run_powershell(add_appx_cmd, output_format="text")
else:
print(f'{appxprovisionedpackage["PackageName"]} is already installed and up-to-date.')
def session_setup():
bins_dir = control.package.split("-", 1)[1]
shared_appx_bins_dir = makepath(appx_dir, bins_dir)
candidates = glob.glob(makepath(shared_appx_bins_dir, f"{appx_package_name}_*"))
if not candidates:
print(f"No session package found for {appx_package_name} in {shared_appx_bins_dir}")
return
bin_path = candidates[0]
if bin_path.lower().endswith(".emsixbundle"):
print(f"Installing EMSIX bundle in session: {bin_path}")
run_powershell(f'Add-AppPackage "{bin_path}"', output_format="text")
def uninstall():
print(f"Removing AppX: {appx_package_name}")
remove_appx(appx_package_name)
bins_dir = control.package.split("-", 1)[1]
shared_appx_bins_dir = makepath(appx_dir, bins_dir)
if isdir(shared_appx_bins_dir):
print(f"Removing cached installer directory: {shared_appx_bins_dir}")
remove_tree(shared_appx_bins_dir)
if isdir(appx_dir) and dir_is_empty(appx_dir):
print("Removing: %s since it is empty" % appx_dir)
remove_tree(appx_dir)
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",
)
def session_cleanup():
print(f"Removing AppX from session: {appx_package_name}")
remove_appx(appx_package_name, default_user=False)
# -*- coding: utf-8 -*-
from setuphelpers import *
try:
from setupdevhelpers import *
except:
pass
import json
import waptguihelper
import html
from xml.dom import minidom
import requests
import re
from datetime import datetime
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"
# 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
download_all_dependencies = True
REQUEST_TIMEOUT = 60
def http_get(url, proxies=None, verify=True, **kwargs):
return requests.get(
url,
proxies=proxies,
verify=verify,
timeout=REQUEST_TIMEOUT,
**kwargs,
)
def http_post(url, data=None, headers=None, proxies=None, verify=True, **kwargs):
return requests.post(
url,
data=data,
headers=headers,
proxies=proxies,
verify=verify,
timeout=REQUEST_TIMEOUT,
**kwargs,
)
def parse_iso_datetime(iso_str):
if not iso_str:
return datetime.min
try:
value = iso_str.strip()
if value.endswith("Z"):
value = value[:-1] + "+00:00"
# Normalize too many fractional second digits if present
m = re.match(r"(.+\.\d{6})\d+(.*)$", value)
if m:
value = m.group(1) + m.group(2)
return datetime.fromisoformat(value)
except Exception:
return datetime.min
def get_setup_appx_package_name():
if not isfile("setup.py"):
return None
with open("setup.py", "r", encoding="utf-8") as f:
for line in f.readlines():
m = re.match(r'\s*appx_package_name\s*=\s*["\']([^"\']+)["\']', line)
if m:
return m.group(1)
return None
def set_setup_appx_package_name(appx_package_name):
if not isfile("setup.py"):
return
new_lines = []
found = False
with open("setup.py", "r", encoding="utf-8") as f:
for line in f.readlines():
if re.match(r'\s*appx_package_name\s*=', line):
line = 'appx_package_name = "%s"\n' % appx_package_name
found = True
new_lines.append(line)
if not found:
new_lines.append('\nappx_package_name = "%s"\n' % appx_package_name)
with open("setup.py", "w", encoding="utf-8") as f:
f.writelines(new_lines)
def get_target_arch():
archs = ensure_list(control.architecture)
if "x64" in archs:
return "x64"
if "x86" in archs:
return "x86"
if "arm64" in archs:
return "arm64"
if "arm" in archs:
return "arm"
if "all" in archs:
return "all"
return os_arc()
def get_arch_score(file_arch, wanted_arch):
if wanted_arch == "all":
return 2
if file_arch == wanted_arch:
return 3
if file_arch in ("all", "neutral"):
return 2
return 0
def get_type_score(filename):
ext = os.path.splitext(filename.lower())[1]
ext_priority = {
".msixbundle": 100,
".appxbundle": 90,
".msix": 80,
".appx": 70,
".emsixbundle": 60,
".eappxbundle": 50,
".emsix": 40,
".eappx": 30,
}
return ext_priority.get(ext, 0)
def update_package():
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, proxies=proxies)
package_json_fr = get_json_fr(store_id, proxies=proxies)
if isfile("package.json"):
package_infos = json_load_file("package.json")
else:
package_infos = {
"version_prefix": None,
"ignored_versions": [],
"package_name_filter": None,
}
ignored_versions = package_infos.get("ignored_versions", [])
version_prefix = package_infos.get("version_prefix", None)
package_name_filter = package_infos.get("package_name_filter", None)
setup_appx_package_name = get_setup_appx_package_name()
if not package_name_filter and setup_appx_package_name:
package_name_filter = setup_appx_package_name
if debug_mode:
print("\n\n=== CAREFUL : DEBUG MODE IS ACTIVE ===\n")
print(f"package_name_filter : {package_name_filter}")
print(f"setup_appx_package_name : {setup_appx_package_name}")
print(f"version_prefix : {version_prefix}")
print(f"ignored_versions : {ignored_versions}\n")
print("Retrieving informations from Microsoft Store API, please wait\n")
all_files_dict = get_all_files(url_ms, proxies)
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")
bin_selected_dict = ask_app_filename(
all_files_dict,
package_name_filter=package_name_filter,
ignored_versions=ignored_versions,
version_prefix=version_prefix,
)
if not bin_selected_dict:
error("No matching package found from API response")
if debug_mode:
bin_selected_dict_json = json.dumps(bin_selected_dict, indent=4)
print("*** Selected file from ask_app_filename() ***")
print(f"bin_selected_dict : {bin_selected_dict_json}\n")
selected_package_name = bin_selected_dict[0]["bin_name"].split("_")[0]
set_setup_appx_package_name(selected_package_name)
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"]
if bin_selected.get("is_encrypted"):
print("Selected encrypted package: %s" % latest_bin)
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:
error("ERROR: The retrieved url will not download from microsoft's servers")
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")
ask_control_description("update_package", get_description(package_json))
ask_control_description_fr("update_package", get_description_fr(package_json_fr))
uwp_app_dict = make_dependency_dict(selected_package_name, all_files_dict)
if not uwp_app_dict:
error(f'"{selected_package_name}" not found in API file list. This is probably caused by package.json filters:\n{package_infos}')
newer_uwp_app = next(iter(uwp_app_dict.values()))
downloaded_dependencies = download_api_dependencies(bin_selected, all_files_dict, proxies)
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)
for dep in downloaded_dependencies:
if isfile(dep["bin_name"]):
remove_file(dep["bin_name"])
return
control.set_software_version(version)
control.save_control_to_wapt()
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 isfile(uwp_file) and not isfile(makepath(bins_dir, uwp_file)):
shutil.move(uwp_file, bins_dir)
for dep in downloaded_dependencies:
dep_file = dep["bin_name"]
if isfile(dep_file) and not isfile(makepath(bins_dir, dep_file)):
shutil.move(dep_file, bins_dir)
remove_outdated_binaries(
version,
["appxbundle", "msixbundle", "appx", "msix", "eappx", "emsix", "eappxbundle", "emsixbundle", "part"],
latest_bin,
)
return package_updated
def get_json(app_id, proxies=None):
url = "https://storeedgefd.dsx.mp.microsoft.com/v9.0/products/%s?market=US&locale=en-us&deviceFamily=Windows.Desktop" % app_id
data = json.loads(http_get(url, proxies=proxies).text)
return data
def get_json_fr(app_id, proxies=None):
url = "https://storeedgefd.dsx.mp.microsoft.com/v9.0/products/%s?market=FR&locale=fr-fr&deviceFamily=Windows.Desktop" % app_id
data_fr = json.loads(http_get(url, proxies=proxies).text)
return data_fr
def get_all_files(store_id, proxies):
all_files, modified_dict, main_file_name = url_generator(
store_id,
proxies=proxies
)
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 = []
encrypted_exts = {
".eappx",
".emsix",
".eappxbundle",
".emsixbundle",
}
for i, download_url in all_files.items():
bin_name = i.replace("~", "_")
version_match = re.search(r"_(\d+(?:\.\d+)+)", bin_name)
version = version_match.group(1) if version_match else "0"
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(".", " ")
lower_name = bin_name.lower()
file_ext = os.path.splitext(lower_name)[1]
is_encrypted = file_ext in encrypted_exts
if "arm64" in lower_name:
package_arch = "arm64"
elif "arm" in lower_name:
package_arch = "arm"
elif "x64" in lower_name:
package_arch = "x64"
elif "x86" in lower_name:
package_arch = "x86"
elif "neutral" in lower_name:
package_arch = "all"
else:
package_arch = "all"
file_dict = {
"version": version,
"bin_name": bin_name,
"package_name": package_name,
"software_name": software_name,
"package_arch": package_arch,
"download_url": download_url,
"file_ext": file_ext,
"is_encrypted": is_encrypted,
"modified": modified_dict.get(i),
"main_file_name": main_file_name,
}
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"
with open(rf"{basedir}\data\xml\GetCookie.xml", "r") as f:
cookie_content = f.read()
out = http_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 = http_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:
for _ in range(4):
out2 = http_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)
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, AttributeError, IndexError):
continue
if not filenames:
raise Exception("server returned an empty list")
identities = {}
modified_dict = {}
for node in doc.getElementsByTagName("SecuredFragment"):
try:
file_name, modified = filenames[
node.parentNode.parentNode.parentNode.getElementsByTagName("ID")[0].firstChild.nodeValue
]
update_identity = node.parentNode.parentNode.firstChild
modified_dict[file_name] = modified
identities[file_name] = (
update_identity.attributes["UpdateID"].value,
update_identity.attributes["RevisionNumber"].value,
)
except (KeyError, AttributeError, IndexError):
continue
api_dict = {value: identities[value] for value in modified_dict}
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 = http_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_item = i.getElementsByTagName("Url")[0].firstChild.nodeValue
if len(url_item) != 99:
file_dict[file_name] = url_item
if len(file_dict) != len(api_dict):
raise Exception("server returned an incomplete list")
return file_dict, modified_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 = http_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_item, locale, installer_type, url_item = download_data[0]
if len(download_data) > 1:
for data_item in download_data[1:]:
if arch_item not in ("neutral", curr_arch) and data_item[0] in ("neutral", curr_arch):
arch_item, locale, installer_type, url_item = data_item
elif data_item[0] == arch_item and data_item[1] != locale and ("us" in data_item[1] or "en" in data_item[1]):
locale, installer_type, url_item = data_item[1], data_item[2], data_item[3]
break
main_file_name = clean_name(file_name) + "." + installer_type
file_dict = {main_file_name: url_item}
return file_dict, {}, main_file_name
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 = http_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, package_name_filter=None, ignored_versions=None, version_prefix=None):
if ignored_versions is None:
ignored_versions = []
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"
)
]
candidates = []
for app_file in selected:
if package_name_filter:
if not app_file["bin_name"].startswith(f"{package_name_filter}_"):
continue
if version_prefix is not None:
n = len(version_prefix.split("."))
if Version(app_file["version"], n) != Version(version_prefix, n):
continue
if any(
Version(app_file["version"], len(v.split("."))) == Version(v, len(v.split(".")))
for v in ignored_versions
):
continue
candidates.append(app_file)
if not candidates:
candidates = selected
if not candidates:
return []
grouped = {}
for item in candidates:
pkg_name = item["bin_name"].split("_")[0]
grouped.setdefault(pkg_name, []).append(item)
target_arch = get_target_arch()
if len(grouped) == 1:
best = select_best_match(candidates, wanted_arch=target_arch)
return [best] if best else []
if "template-microsoft-store" in control.package:
return waptguihelper.grid_dialog(
"Please select the proper file",
json.dumps(candidates),
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}]}',
)
best = select_best_match(candidates, wanted_arch=target_arch)
return [best] if best else []
def ask_control_categories():
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):
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):
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()
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):
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=""):
control.description = ask_dialog("Description (en)", "Please fill the description (english)", description_from_store)
control.save_control_to_wapt()
return control.description
def ask_control_description_fr(blank_str=None, description_from_store=""):
control.description_fr = ask_dialog("Description (fr)", "Merci de remplir la description (français)", description_from_store)
control.description_pl = ""
control.description_de = ""
control.description_es = ""
control.description_pt = ""
control.description_it = ""
control.description_nl = ""
control.description_ru = ""
control.save_control_to_wapt()
return control.description_fr
def ask_dialog(title, text, default="", stay_on_top=False):
return waptguihelper.input_dialog(title, text, default, stay_on_top)
def get_description(data):
return data["Payload"]["Skus"][0]["Description"].replace("\r\n", "").replace("\n", "").replace("\r", "")
def get_description_fr(data_fr):
return data_fr["Payload"]["Skus"][0]["Description"].replace("\r\n", "").replace("\n", "").replace("\r", "")
def get_package_ext_priority(filename):
ext_priority = {
".msixbundle": 100,
".appxbundle": 90,
".msix": 80,
".appx": 70,
".emsixbundle": 60,
".eappxbundle": 50,
".emsix": 40,
".eappx": 30,
}
return ext_priority.get(os.path.splitext(filename.lower())[1], 0)
def select_best_match(files_list, wanted_arch=None):
if not files_list:
return None
if wanted_arch is None:
wanted_arch = get_target_arch()
compatible = []
for item in files_list:
if wanted_arch == "all" or item["package_arch"] in (wanted_arch, "all"):
compatible.append(item)
if not compatible:
compatible = files_list
def score(x):
return (
get_arch_score(x["package_arch"], wanted_arch),
get_type_score(x["bin_name"]),
parse_iso_datetime(x.get("modified")),
Version(x["version"]),
)
return max(compatible, key=score)
def is_framework_package(item):
framework_prefixes = (
"Microsoft.VCLibs",
"Microsoft.UI.Xaml",
"Microsoft.NET.Native.Framework",
"Microsoft.NET.Native.Runtime",
"Microsoft.Services.Store.Engagement",
"Microsoft.WindowsAppRuntime",
)
return item["bin_name"].startswith(framework_prefixes)
def same_or_compatible_arch(main_arch, dep_arch):
if main_arch == "all":
return True
if dep_arch == "all":
return True
return dep_arch == main_arch
def make_dependency_dict(selected_package_name, all_files_dict):
candidates = [
f for f in all_files_dict
if f["bin_name"].startswith(f"{selected_package_name}_")
]
best = select_best_match(candidates, wanted_arch=get_target_arch())
if not best:
return {}
return {
best["bin_name"]: {
"FileName": best["bin_name"],
"Name": best["bin_name"].split("_")[0],
"Version": best["version"],
"Architecture": best["package_arch"],
"Dependencies": None,
}
}
def get_newer_uwp_depency(package_name, all_files_dict, package_arch=None, version_prefix=None, min_version=None):
candidates = []
for uwp_app in all_files_dict:
if not uwp_app["bin_name"].startswith(f"{package_name}_"):
continue
if package_arch is not None and not same_or_compatible_arch(package_arch, uwp_app["package_arch"]):
continue
if version_prefix is not None:
n = len(version_prefix.split("."))
if Version(uwp_app["version"], n) != Version(version_prefix, n):
continue
if min_version is not None and Version(uwp_app["version"]) < Version(min_version):
continue
candidates.append(uwp_app)
return select_best_match(candidates, wanted_arch=package_arch or get_target_arch())
def resolve_framework_dependencies(main_package, all_files_dict):
main_arch = main_package["package_arch"]
framework_groups = {
"Microsoft.VCLibs": [],
"Microsoft.UI.Xaml": [],
"Microsoft.NET.Native.Framework": [],
"Microsoft.NET.Native.Runtime": [],
"Microsoft.Services.Store.Engagement": [],
"Microsoft.WindowsAppRuntime": [],
}
for item in all_files_dict:
if not is_framework_package(item):
continue
if not same_or_compatible_arch(main_arch, item["package_arch"]):
continue
for prefix in framework_groups:
if item["bin_name"].startswith(prefix + "_"):
framework_groups[prefix].append(item)
break
resolved = []
for _, matches in framework_groups.items():
best = select_best_match(matches, wanted_arch=main_arch)
if best:
resolved.append(best)
return resolved
def resolve_all_dependencies(main_package, all_files_dict):
main_name = main_package["bin_name"].split("_")[0]
main_arch = main_package["package_arch"]
grouped = {}
for item in all_files_dict:
pkg_name = item["bin_name"].split("_")[0]
if pkg_name == main_name:
continue
if not same_or_compatible_arch(main_arch, item["package_arch"]):
continue
grouped.setdefault(pkg_name, []).append(item)
resolved = []
for _, matches in grouped.items():
best = select_best_match(matches, wanted_arch=main_arch)
if best:
resolved.append(best)
return resolved
def resolve_dependencies(main_package, all_files_dict, all_dependencies=False):
if all_dependencies:
return resolve_all_dependencies(main_package, all_files_dict)
return resolve_framework_dependencies(main_package, all_files_dict)
def download_api_dependencies(main_package, all_files_dict, proxies):
downloaded_dependencies = []
dependencies = resolve_dependencies(
main_package,
all_files_dict,
all_dependencies=download_all_dependencies,
)
if dependencies and debug_mode:
print("\nResolved API dependencies:")
for dep in dependencies:
print(f' - {dep["bin_name"]}')
print("")
for dep in dependencies:
latest_bin = dep["bin_name"]
download_url = dep["download_url"]
if download_url.split("/")[2].endswith("microsoft.com"):
if not isfile(latest_bin):
print("Downloading dependency: %s" % latest_bin)
wget(download_url, latest_bin, proxies=proxies)
else:
print("Dependency already present: %s" % latest_bin)
downloaded_dependencies.append(dep)
else:
error("ERROR: The retrieved URL will not download from microsoft's servers")
return downloaded_dependencies
def get_uwp_filename_arch(appx_filename, appx_package_name=None):
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 appx_package_name not 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"
b3100f592fa1a3b867363176529f2df82d51bbcc9221dfc95a54f1392a58cf9d : WAPT/README.md
deb8ca89091812a8663d6e901430325f9de1d067e1e1d4f92ac0522e585f9ce9 : WAPT/README_fr.md
38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
3a6df07709510e8084cd91c49cc9f6b9ea7a96856e3625465fe4a64da84f5814 : WAPT/changelog.txt
19b70467c3a2cecc663a991738a1cb4354d6b4a68a346a4fb0c26bc398f24c9b : WAPT/control
aea6d0c53867b3d774e670da830d8ef922bd93a4ca37ea565e7bbb8465152983 : WAPT/icon.png
87370426807b83b7916c4f43942cd9bf71b1eeaf3b44728d241efe107927e3a2 : data/xml/FE3FileUrl.xml
2d3dd983c9d83c2464afaa85ab49ad5c78a537a131ec61e0c32f6b446bed4f55 : data/xml/GetCookie.xml
f8a4681fbeafb4ddcaac37b406374143900d8885b59ba7a7a0ec782d79bacd9b : data/xml/WUIDRequest.xml
14d227a2bfacb5437e7304c4522903b46de4e21687c7105dc633c4a5f36c92f5 : luti.json
bd43e5749147a75dbd658cac066506657073da0ee363e93418ed66427dae1bb6 : package.json
b012c7506e1aae774e030f73a8b09bf3a8798b33b1307a51c2464c843f73197b : setup.py
c802d056a635d7c09e516c827ce4f49551b98844183adf0aa5896ea5bdfd2a03 : setupdevhelpers.py
430b03a6bdd562e9c5ff06ba4aec2ac054cf2c9e01ed3cb6e3575bd675d5d1fd : 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