tis-microsoft-photos icon

Microsoft Photos

Silent install package for Microsoft Photos

2025.11070.24004.0-65

  • package: tis-microsoft-photos
  • name: Microsoft Photos
  • version: 2025.11070.24004.0-65
  • categories: Utilities,Media
  • maintainer: WAPT Team,Tranquil IT,Jimmy PELÉ
  • editor: Microsoft
  • licence: proprietary_free,wapt_public
  • locale: all
  • target_os: windows
  • impacted_process: PhotosApp,PhotosCopilotPlugin,PhotosService
  • architecture: all
  • signature_date:
  • size: 1.26 Go
  • installed_size: 775.28 Mo

package           : tis-microsoft-photos
version           : 2025.11070.24004.0-65
architecture      : all
section           : base
priority          : optional
name              : Microsoft Photos
categories        : Utilities,Media
maintainer        : WAPT Team,Tranquil IT,Jimmy PELÉ
description       : Microsoft Photos is a rich media experience that empowers you to do more with your photos and videos. The newly redesigned and reengineered Photos app is intuitive, elegant, and seamlessly woven into the fabric of Windows
depends           : 
conflicts         : 
maturity          : PROD
locale            : all
target_os         : windows
min_wapt_version  : 2.4
sources           : https://apps.microsoft.com/store/detail/9WZDNCRFJBH4
installed_size    : 775282078
impacted_process  : PhotosApp,PhotosCopilotPlugin,PhotosService
description_fr    : Microsoft Photos est une expérience multimédia riche qui vous permet d'en faire plus avec vos photos et vos vidéos. L'application Photos, récemment redessinée et repensée, est intuitive, élégante et parfaitement intégrée à la structure de Windows
description_pl    : Microsoft Photos to bogate środowisko multimedialne, które pozwala zrobić więcej ze swoimi zdjęciami i filmami. Nowo przeprojektowana i przeprojektowana aplikacja Zdjęcia jest intuicyjna, elegancka i płynnie wpleciona w strukturę systemu Windows
description_de    : Microsoft Fotos ist ein reichhaltiges Medienerlebnis, das Ihnen die Möglichkeit gibt, mehr aus Ihren Fotos und Videos zu machen. Die neu gestaltete und überarbeitete Fotos-App ist intuitiv, elegant und nahtlos in die Struktur von Windows eingebettet
description_es    : Microsoft Photos es una rica experiencia multimedia que te permite hacer más con tus fotos y vídeos. La aplicación Fotos, recientemente rediseñada y rediseñada, es intuitiva, elegante y está perfectamente integrada en la estructura de Windows
description_pt    : O Microsoft Photos é uma experiência multimédia avançada que lhe permite fazer mais com as suas fotografias e vídeos. A aplicação Fotos, recentemente redesenhada e reformulada, é intuitiva, elegante e está perfeitamente integrada na estrutura do Windows
description_it    : Microsoft Photos è un'esperienza multimediale ricca che consente di fare di più con le foto e i video. L'app Foto, recentemente ridisegnata e reingegnerizzata, è intuitiva, elegante e perfettamente integrata nel tessuto di Windows
description_nl    : Microsoft Foto's is een rijke media-ervaring waarmee je meer met je foto's en video's kunt doen. De nieuw ontworpen en opnieuw ontworpen Foto's-app is intuïtief, elegant en naadloos verweven in de structuur van Windows
description_ru    : Microsoft Photos - это богатый мультимедийный опыт, позволяющий делать больше со своими фотографиями и видео. Обновленное и переработанное приложение Photos отличается интуитивностью, элегантностью и органично вписывается в структуру Windows
audit_schedule    : 
editor            : Microsoft
keywords          : 
licence           : proprietary_free,wapt_public
homepage          : 
package_uuid      : e0680f4a-6132-498d-926b-4a71fdef2c07
valid_from        : 
valid_until       : 
forced_install_on : 
changelog         : 
min_os_version    : 10.0.22000
max_os_version    : 
icon_sha256sum    : 355bf9fbac8644b2130caf1b7193938bef2be8c8067f6f6b2a755a52323de028
signer            : Tranquil IT
signer_fingerprint: 8c5127a75392be9cc9afd0dbae1222a673072c308c14d88ab246e23832e8c6bb
signature_date    : 2025-08-26T02:02:53.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         : GlpqqERS+qKf+blMt+xt0C4ZpZ0rbuMLUqN0OktN5ZVnFOeA5jsJ7Cg7wwsnNGY2CwtZFcgRq+ouiUpougO6VmEN19j4Grw6J1CbJAaohQcjveRx/JUlPGhY2iF7G65rzAB9IB7XUEvzll8Vk8C3f55HiJALAdCOJE6qfFp8NpdSGaAQJ7+YxEQwUGbQkbku/CJ0ZGTlfNCo0y/y29S99ANPsXPbbFtScOPKsJO0/uM8z7/fiy4mJCZ4Az8TskALlwA9olgCQNNa7/oWoJYnejpDCghTB+bChQAArFXLeh+Cp349PGpMCgspz4ZK7P2Jjdu5fTrdx0m7JhJyaHgfbA==

# -*- coding: utf-8 -*-
from setuphelpers import *


appx_package_name = "Microsoft.Windows.Photos"
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 *
import requests

try:
    from setupdevhelpers import *

except:
    from bs4 import BeautifulSoup
import json
import waptguihelper
import html
import datetime
from xml.dom import minidom
from requests import Session
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


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_prefix = control.package.split("-")[0]
    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 = None
    ignored_versions = package_infos["ignored_versions"]
    version_prefix = package_infos.get("version_prefix", None)

    version_pref = False
    if version_prefix:
        version_pref = True

    all_files_dict = get_all_files(url_ms,version_pref,proxies)
    
	# 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
    
    bin_selected_dict = ask_app_filename(all_files_dict, store_package_name,ignored_versions=ignored_versions,version_prefix=version_prefix)
    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"]
    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")

    # # Adding dependencies
    # control.depends = ""
    # for dependency in list(set(d["package_name"] for d in all_files)):
    #     if dependency not in control.depends and package_name != dependency:
    #         control.add_depends(dependency)

    # Warn end-user that depencies are required
    # missing_depends = []
    # for d in control.depends.split(","):
    #     if not WAPT.is_available(d) and d != "":
    #         missing_depends.append(d)
    # if missing_depends and "template" in control.package:
    #     waptguihelper.message_dialog(
    #         control.package,
    #         'The following packages are not found in your repo, you must import them or use "template-microsoft-store-dependency" to create them with the same Store ID as this package\n\n%s'
    #         % "\n".join(a for a in missing_depends),
    #     )

    control.architecture = package_arch
    # Asking pkg infos if needed and not running as luti
    if not params.get("running_as_luti"):
        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)

	# Keep app files
    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}')
    newer_uwp_app = get_uwp_app(uwp_app_dict)

    # 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_dict, min_version=dependency["MinVersion"]))
    if dependencies_to_download:
        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
            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")

    # 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))
    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 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_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_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 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
    print(f"url used is {url}")
    dict = json.loads(requests.get(url).text)
    return dict


def get_cat_id(dict):
    # get the category id from the dict, the ca_id is used to retrieve files names and url
    fullfillment_data_string = dict["Payload"]["Skus"][0]["FulfillmentData"]
    # transform fullfillment data to dict
    fullfillment_data_dict = json.loads(fullfillment_data_string)
    cat_id = fullfillment_data_dict["WuCategoryId"]
    return cat_id


def get_description(dict):
    # get the description from the dict
    return dict["Payload"]["Skus"][0]["Description"]


def get_min_os_version(dict):
    # get the min os version from the dict
    return dict["Payload"]["Skus"][0]["PackageRequirements"][0]["PlatformDependencies"][0]["MinVersion"]


def url_generator(url, version_pref, all_dependencies,proxies):
    def uwp_gen(data_list):

        def parse_dict(main_dict, file_name):
            file_name = clean_name(file_name.split("-")[0])
            pattern = re.compile(r".+\.BlockMap")
            full_data = {}

            for key, value in main_dict.items():
                if not pattern.search(str(key)):
                    temp = key.split("_")
                    content_lst = (
                        clean_name(temp[0]),
                        temp[2].lower(),
                        temp[-1].split(".")[1].lower(),
                        value,
                        temp[1],
                    )
                    full_data[content_lst] = key

            names_dict = {}
            for v in full_data:
                names_dict.setdefault(v[0], []).append(v[1:])

            file_arch, main_file_name, main_file_name_key = None, None, None
            pat_main = re.compile(file_name)
            sys_arch = os_arc()
            for k in names_dict:
                if pat_main.search(k):
                    content_list = names_dict[k]
                    main_file_name_key = k
                    arch, ext, modifed, ver = select_latest(content_list, sys_arch)
                    main_file_name = full_data[(k, arch, ext, modifed, ver)]
                    file_arch = sys_arch if arch == "neutral" else arch
                    break

            if main_file_name_key in names_dict:
                del names_dict[main_file_name_key]

            final_list = []
            for k in names_dict:
                content_list = names_dict[k]
                if all_dependencies:
                    for data in content_list:
                        final_list.append(full_data[(k, *data)])
                else:
                    arch, ext, modifed, ver = select_latest(content_list, file_arch)
                    final_list.append(full_data[(k, arch, ext, modifed, ver)])

            if main_file_name:
                final_list.append(main_file_name)
                file_name = main_file_name
            else:
                file_name = final_list[0] if final_list else file_name

            return final_list, file_name

        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 = 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))

        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

        parse_names, main_file_name = parse_dict(name_modified, main_file_name)
        
        if version_pref:
            final_dict = {value: identities[value] for value in name_modified}
        else:
            final_dict = {value: identities[value] for value in parse_names}

        with open(rf"{basedir}\data\xml\FE3FileUrl.xml", "r") as f:
            file_content = f.read()

        file_dict = {}

        for file_name, (updateid, revisionnumber) in final_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(final_dict):
            raise Exception("server returned an incomplete list")


        return file_dict, parse_names, 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 get_all_files(store_id,version_pref,proxies):
    all_files = url_generator(
        store_id,
        version_pref=version_pref,
        all_dependencies=True,
        proxies=proxies
    )
    all_files_dict = []
    # parse every file name and url in all_files:

    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 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 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:
                for app_file in all_files_dict:
                    latest_bin = app_file["bin_name"]
                    target_version = None
                    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("."))
                    ):
                        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
                    ):
                        continue
                    if Version(higher_version) < Version(a["version"]) and store_package_name in a["bin_name"]:
                        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 get_uwp_app(uwp_app_dict):
    """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():
        newer_uwp_app = uwp_app

    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 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"
    
def select_latest(content_list, curr_arch, ignore_ver=False):
    def score(item):
        fav_type = {"appx", "msix", "msixbundle", "appxbundle"}
        arch, ext, modified_str, version_str = item
        arch_score = 2 if arch == curr_arch else (1 if arch == "neutral" else 0)
        type_score = 1 if ext in fav_type else 0
        if ignore_ver:
            dt = 0
            ver_tuple = (0, 0, 0, 0)
        else:
            dt = datetime.datetime.fromisoformat(modified_str.rsplit('.',1)[0])
            ver_tuple = tuple(map(int, version_str.split(".")))
        return (arch_score, type_score, dt, ver_tuple)

    candidates = [item for item in content_list if item[0] in (curr_arch, "neutral")]
    if not candidates:
        candidates = content_list
    best = max(candidates, key=score)
    return best

def make_dependency_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 = None
        bundle_manifest = None

        manifest_path = makepath(basedir,"AppxManifest.xml")
        unzip_with_7zip(ms_app_file, ".", "AppxManifest.xml", False)
        if isfile(manifest_path):
            manifest = manifest_path
        bundle_manifest_path = makepath(basedir,"AppxBundleManifest.xml")
        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

38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
9ab2918789ada3f432395de911303e942a4720bc8ddce7b6c7c9a4f013bd9cf7 : WAPT/control
355bf9fbac8644b2130caf1b7193938bef2be8c8067f6f6b2a755a52323de028 : WAPT/icon.png
87370426807b83b7916c4f43942cd9bf71b1eeaf3b44728d241efe107927e3a2 : data/xml/FE3FileUrl.xml
2d3dd983c9d83c2464afaa85ab49ad5c78a537a131ec61e0c32f6b446bed4f55 : data/xml/GetCookie.xml
f8a4681fbeafb4ddcaac37b406374143900d8885b59ba7a7a0ec782d79bacd9b : data/xml/WUIDRequest.xml
1c5ddedcd1d402a5cab83260c55e12b4bcfbfa6fbcb5dba7326f34d578d4c2db : luti.json
831118f2f5cac8e29284e4ecc23bf4236436ccc2ea4a7c225c4946f8cfd29dd3 : microsoft-photos/Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_arm64__8wekyb3d8bbwe_8b19ae8f-1fe9-45e1-aed3-1672ee33bf71.appx
df3d87926a1d9780b7a76e145c3eae17f58e825fd7a9c1b2017608929da0d86f : microsoft-photos/Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_arm__8wekyb3d8bbwe_ba323f94-e23e-40ef-a6b5-917c577c939a.appx
077a3d1a5d0622bd3004dca85f5e192d6e98ec79b83d4aa06766759ea6c09c3d : microsoft-photos/Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64__8wekyb3d8bbwe_27ca12bc-f81d-45ff-95d0-12ad79f15735.appx
596c7175c0b0fa27ed292a44f4681f687720a77e34e007bf6c403d1940e46005 : microsoft-photos/Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x86__8wekyb3d8bbwe_2a0007f4-9769-4709-9244-a28f54f70828.appx
c73d0f55dda331f9dcbefc99ff5a420b62120773d2917387639382aa478533ee : microsoft-photos/Microsoft.VCLibs.140.00_14.0.33519.0_arm64__8wekyb3d8bbwe_4aa2f4c4-ecd2-41d1-8089-304a95524359.appx
2c422523ff693689a84c109585cfa444143ac3b6b7a5cadf4858afc6a3cb750f : microsoft-photos/Microsoft.VCLibs.140.00_14.0.33519.0_arm__8wekyb3d8bbwe_f52e53fc-9664-4720-800e-a637f6f5c125.appx
9c17b521f9d690a1f504da5108ed6eec5669eb3a8fd1331eef43e40d84e74283 : microsoft-photos/Microsoft.VCLibs.140.00_14.0.33519.0_x64__8wekyb3d8bbwe_c452d4ef-2486-4efe-9c99-36b3d23e0160.appx
7ba6ea7bc32cd58b7e0683da588796086accfb74efb7a3e525e9f8014d2ad663 : microsoft-photos/Microsoft.VCLibs.140.00_14.0.33519.0_x86__8wekyb3d8bbwe_9683459a-02fa-4bd6-9ae6-af8ddfbeef35.appx
aee8ff6896d1b0abdb374355916932464604f51de1a4e7c6e0ae92eacd57a337 : microsoft-photos/Microsoft.Windows.Photos_2025.11070.24004.0_neutral___8wekyb3d8bbwe_84c77d92-0c59-4469-9e37-a235bae1a15b.msixbundle
b0d9942670d8c9c5d4aed34754e54c32be51167d742ef625a2c6aa2c3b373410 : microsoft-photos/Microsoft.WindowsAppRuntime.1.7_7000.522.1444.0_arm64__8wekyb3d8bbwe_1727045e-d056-4bf6-a641-662cb1ae4c6e.msix
f36dd4fb89eebcae9cbb81b11dd44aa183ab57561e9c47a1fd03cc89c6ca59a3 : microsoft-photos/Microsoft.WindowsAppRuntime.1.7_7000.522.1444.0_x64__8wekyb3d8bbwe_274832df-4eea-46b0-9246-3644ce98c428.msix
30ecfeb4ad114d2ec767072d2f3393be238eebc3fcccbb8daae172f5d1b29cb0 : microsoft-photos/Microsoft.WindowsAppRuntime.1.7_7000.522.1444.0_x86__8wekyb3d8bbwe_d75c1eca-4e15-4c29-92dc-8a57f322325a.msix
f3be858fdbf16b1e2863b0d7c7a85e4346d265a3d12656b2f040e35ea8f97b30 : package.json
3e02940284851274cb55289133ab5b7ac093255b6c531eb498f38954c9e7582a : setup.py
9f0ed37b5f8b76bac9ae97ec08f2437c3960ba1e061a8a445f8a1274c0650959 : setupdevhelpers.py
96cdee1565e7a9486ec126271b5613ef6c983e226758acfe5dc33c374a57bc04 : update_package.py
364e01205533a0c6907fb9e8a8f70353985ae81ead35b71aade9a4253184f847 : xmltodict.py