Partager via


Vue d’ensemble de l’identité de package dans les applications Windows

L’identité de package est un identificateur unique dans l’espace et le temps. Tout comme votre ADN vous identifie de manière unique, l’identité de package identifie de manière unique un package.

Un package est associé à un ensemble de bits (fichiers, etc.). Deux packages n’ont pas la même identité et toutes les modifications apportées aux bits associés à un package nécessitent une identité différente.

Qu’est-ce que l’identité de package ?

Une identité de package est une construction logique qui identifie un package de manière unique. L’identité comporte 5 parties :

  • Nom : il s’agit d’un nom choisi par le développeur d’application. Le Microsoft Store applique l’unicité de tous les noms d’applications pour tous les développeurs d’applications dans le Store, mais l’unicité des noms n’est pas garantie à l’échelle de l’écosystème général.
  • Version : numéro de version du package. Le développeur d’applications peut choisir des numéros de version arbitraires, mais doit s’assurer que les numéros de version augmentent avec les mises à jour.
  • Architecture : architecture du processeur ciblée par le package. Une même application peut être créée en ciblant différentes architectures de processeur, chaque build résidant dans son propre package.
  • ResourceId : chaîne choisie par le développeur d’application pour identifier de manière unique les packages de ressources, par exemple différents langages ou différentes échelles d’affichage. Les packages de ressources sont généralement neutres sur le plan de l’architecture. Pour les bundles, le ResourceId est toujours ~.
  • Éditeur : nom d’objet du développeur d’application identifié par son certificat de signature. Ce nom est théoriquement unique pour chaque développeur d’application, car les autorités de certification réputées utilisent des noms et des identités réels uniques pour remplir le champ de nom d’objet du certificat.

Cette construction est parfois appelée tuple à 5 parties.

Notes

Les packages non signés (1) nécessitent toujours un éditeur, (2) l’éditeur doit contenir le marqueur Unsigned (OID.2.25.311729368913984317654407730594956997722=1), (3) le marqueur Unsigned doit être le dernier champ de la chaîne de l’éditeur et (4) il n’existe aucun certificat ou signature pour un package non signé.

Limites des champs d’identité de package

Champ Type de données Limites Commentaires
Nom Chaîne de package Min : 3
Max : 50
Valeurs autorisées par API de validation (voir Chaîne de package)
Version DotQuad Min : 0.0.0.0
Max : 65535.65535.65535.65535
La forme de la chaîne utilise une notation en pointillés de base 10, « version_majeure.version_mineure.build.révision »
Architecture Énumération Min : N/A
Max : N/A
Les valeurs autorisées sont « neutral », « x86 », « x64 », « arm », « arm64 », « x86a64 »
ResourceId Chaîne de package Min : 0
Max : 30
Valeurs autorisées par API de validation (voir Chaîne de package)
Publisher String Min : 1
Max : 8192
Valeurs autorisées par X.509
PublisherId String Min : 13
Max : 13
Variante de Crockford encodée en Base32, c’est-à-dire [a-hjkmnp-tv-z0-9]

Qu’est-ce qu’une « chaîne de package » ?

Une chaîne de package est une chaîne qui autorise les caractères suivants :

  • Caractères d’entrée autorisés (sous-ensemble ASCII)
    • Majuscules (U+0041 à U+005A)
    • Minuscules (U+0061 à U+007A)
    • Nombres (U+0030 à U+0039)
    • Point (U+002E)
    • Tiret (U+002D)

Les valeurs suivantes ne peuvent pas être utilisées comme chaînes de package :

Condition Valeurs interdites
Ne peut pas être égale à « . », « .. », « con », « prn », « aux », « nul », « com1 », « com2 », « com3 », « com4 », « com5 », « com6 », « com7 », « com8 », « com9 », « lpt1 », « lpt2 », « lpt3 », « lpt4 », « lpt5 », « lpt6 », « lpt7 », « lpt8 », « lpt9 »
Ne peut pas commencer par « con », « prn. », « aux. », « nul. », « com1. », « com2. », « com3. », « com4. », « com5. », « com6. », « com7. », « com8. », « com9. », « lpt1. », « lpt2. », « lpt3. », « lpt4. », « lpt5. », « lpt6. », « lpt7. », « lpt8. », « lpt9. », « xn-- »
Ne peut pas finir par "."
Ne peut pas contenir « .xn-- »

Une chaîne de package doit être comparée en utilisant une API de comparaison de chaînes ordinales qui ne respectent pas la casse (par exemple, _wcsicmp).

Les champs name et resourceid de l’identité de package sont des chaînes de package.

Objet PackageId

Un PackageId est un objet contenant le tuple en 5 parties sous forme de champs individuels (Name, Version, Architecture, ResourceId, Publisher).

Nom complet de package

Un nom complet de package est une chaîne opaque dérivée des 5 parties de l’identité d’un package (nom, version, architecture, resourceid, éditeur)

<Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>

Par exemple, un nom complet de package pour l’application Photos Windows est « Microsoft.Windows.Photos_2020.20090.1002.0_x64__8wekyb3d8bbwe », où « Microsoft.Windows.Photos » est le nom, « 2020.20090.1002.0 » est le numéro de version, « x64 » est l’architecture du processeur cible, l’ID de ressource est vide (aucun contenu entre les deux derniers traits de soulignement) et « 8wekyb3d8bbwe » est l’ID d’éditeur pour Microsoft.

Le nom complet de package identifie de manière unique un bundle ou un package MSIX. Le fait d’avoir deux packages ou bundles avec un contenu différent, mais avec le même nom complet de package est une erreur.

Notes

MSIX remplace le terme précédent APPX. Pour plus d’informations, consultez Qu’est-ce que MSIX ?.

Nom de famille du package

Un nom de famille de packages est une chaîne opaque dérivée de seulement deux parties d’une identité de package : le nom et l’éditeur :

<Name>_<PublisherId>

Par exemple, le nom de famille de packages de l’application Photos Windows est « Microsoft.Windows.Photos_8wekyb3d8bbwe », où « Microsoft.Windows.Photos » est le nom et « 8wekyb3d8bbwe » est l’ID d’éditeur pour Microsoft.

Le nom de famille de packages est souvent appelé « nom complet du package sans version ».

Notes

Cela n’est pas tout à fait vrai, car l’architecture et l’ID de ressource font également défaut dans le nom de famille de packages.

Notes

Les données et la sécurité sont généralement limitées à une famille de packages. Par exemple, l’expérience serait médiocre si vous avez configuré l’application Bloc-notes installée à partir d’un package Bloc-notes version 1.0.0.0 pour activer le retour automatique à la ligne. En effet, après la mise à jour de Bloc-notes vers la version 1.0.0.1, vos données de configuration n’ont pas été propagées à cette version plus récente du package.

ID de l’éditeur

Un nom de famille de packages est une chaîne au format :

<name>_<publisherid>

où l’ID de l’éditeur a des propriétés très spécifiques :

  • Dérivé de l’éditeur
  • MinLength = MaxLength = 13 caractères [taille fixe]
  • Caractères autorisés (en tant que regex) : a-hj-km-np-tv-z0-9
    • Variante de Crockford en Base 32, c’est-à-dire caractères alphanumériques (A-Z0-9) sauf I (i majuscule), L, O (o majuscule) ou U
  • Non-respect de la casse pour les comparaisons ordinales --- ABCDEFABCDEFG == abcdefabcdefg

Vous ne verrez donc jamais % : \ / " ? ou d’autres caractères dans un ID d’éditeur.

Pour plus d’informations, consultez PackageFamilyNameFromId et PackageNameAndPublisherIdFromFamilyName.

L’ID d’éditeur est souvent appelé PublisherId.

Pourquoi l’ID d’éditeur existe-t-il ?

L’ID d’éditeur existe, car l’éditeur doit correspondre au nom/signataire X.509 de votre certificat, ainsi :

  • Il peut être très grand (longueur <= 8 192 caractères)
  • Il peut inclure des caractères embarrassants ou restreints (barre oblique inverse, etc.)

Ces problèmes peuvent rendre certaines chaînes X.509 difficiles ou impossibles à utiliser dans le système de fichiers, le Registre, les URL et d’autres contextes.

Comment créer un PublisherId ?

Utilisez PackageNameAndPublisherIdFromFamilyName pour extraire le PublisherId d’un PackageFamilyName.

Utilisez PackageIdFromFullName pour extraire le PublisherId d’un PackageFullName.

Il est rare de devoir créer un PublisherId à partir de Publisher, mais cela peut être effectué avec les API disponibles :

#include <appmodel.h>

HRESULT PublisherIdFromPublisher(
    _In_ PCWSTR publisher,
    _Out_writes_(PACKAGE_PUBLISHERID_MAX_LENGTH + 1) PWSTR publisherId)
{
    PCWSTR name{ L"xyz" };
    const size_t nameLength{ ARRAYSIZE(L"xyz") - 1 };
    const size_t offsetToPublisherId{ name + 1 }; // xyz_...publisherid...
    PACKAGE_ID id{};
    id.name = name;
    id.publisher = publisher;
 
    WCHAR familyName[PACKAGE_PUBLISHERID_MAX_LENGTH + 1]{};
    UINT32 n{ ARRAYSIZE(familyName) };
    RETURN_IF_WIN32_ERROR(PackageFamilyNameFromId(&id, &n, familyName);
    RETURN_IF_FAILED(StringCchCopyW(publisherId, PACKAGE_PUBLISHERID_MAX_LENGTH + 1, familyName + offsetToPublisherId));
    return S_OK;
}

Voici une implémentation Windows C classique de la même opération :

#include <appmodel.h>

HRESULT PublisherIdFromPublisher(
    _In_ PCWSTR publisher,
    _Out_writes_(PACKAGE_PUBLISHERID_MAX_LENGTH + 1) PWSTR publisherId)
{
    const WCHAR c_name[]{ L"xyz" };
    const UINT32 c_nameLength{ ARRAYSIZE(c_nameForPublisherToPublisherId) - 1 };

    PACKAGE_ID id{};
    id.name = c_name;
    id.publisher = publisher;
    WCHAR familyName[PACKAGE_PUBLISHERID_MAX_LENGTH + 1]{};
    UINT32 n{ ARRAYSIZE(familyName) };
    RETURN_IF_WIN32_ERROR(PackageFamilyNameFromId(&id, &n, familyName));
    RETURN_IF_FAILED(StringCchCopyW(publisherId, PACKAGE_PUBLISHERID_MAX_LENGTH + 1,  familyName + c_nameLength + 1);
    return S_OK;
}

Le PublisherId est ainsi créé en convertissant un ID de package en nom de famille de packages avec le format résultant xyz_<publisherid>. Cette recette est stable et fiable.

Pour cela, vous devez uniquement compiler avec appmodel.h à partir du SDK et établir un lien avec kernel32.lib (ou kernelbase.lib, onecore.lib ou api-ms-win-appmodel-runtime-l1.lib si vous utilisez des ensembles d’API).

Présentation de l’architecture du processeur dans l’identité de package

On pense souvent, à tort, que Architecture=x64 signifie que le package ne peut contenir que du code x64. Ce n’est pas vrai. Cela signifie que le package fonctionne sur les systèmes prenant en charge le code x64 et peut être utilisé par les applications x64. Vous pouvez créer un package contenant uniquement des fichiers PDF, mais le déclarer avec <Identity Architecture=x64...>, car il est uniquement destiné à être installé sur des systèmes compatibles x64 (par exemple, les packages x64 ne peuvent être installés que sur les systèmes x64 et, à compter de Windows 11, Arm64, car les systèmes x86, Arm et Windows 10 Arm64 ne prennent pas en charge les systèmes x64).

On pense encore plus à tort que Architecture=neutral signifie que le package ne contient pas de code exécutable. Cela signifie que le package fonctionne sur toutes les architectures. Par exemple, vous pouvez créer un package contenant une API de chiffrement AES écrite en JavaScript, Python, C#, etc., mais les performances ne sont pas acceptables sur les systèmes Arm64. Ainsi, vous incluez un binaire Arm64 optimisé et implémentez l’API pour le gérer :

void Encrypt(...)
{
    HANDLE h{};
    if (GetCpu() == arm64)
    {
        h = LoadLibrary(GetCurrentPackagePath() + "\bin\encrypt-arm64.dll")
        p = GetProcAddress(h, "Encrypt")
        return (*p)(...)
    }
    else
    {
        // ...call other implementation...
    }
}

Vous pouvez également créer un package neutre avec plusieurs variantes :

\
    bin\
        encrypt-x86.dll
        encrypt-x64.dll
        encrypt-arm.dll
        encrypt-arm64.dll

Les développeurs peuvent ensuite utiliser LoadLibrary("bin\encrypt-" + cpu + ".dll") pour obtenir le fichier binaire approprié pour leur processus au moment de l’exécution.

En règle générale, les packages neutres n’ont pas de contenu par architecture, mais ils le peuvent. Il existe des limites à ce que l’on peut faire (par exemple, vous pouvez créer un package Bloc-notes contenant notepad.exe compilé pour x86 + x64 + arm + arm64, mais appxmanifest.xml ne peut déclarer <Application Executable=...> qu’en pointant vers l’un d’eux). Étant donné qu’il existe des bundles qui vous permettent d’installer uniquement les bits nécessaires, il s’agit d’une chose très rare à faire. Ce n’est pas illégal, juste complexe et inhabituel.

En outre, Architecture=x86 (ou x64|arm|arm64) ne signifie pas que le package contient uniquement du code exécutable pour l’architecture spécifiée. C’est même le cas très courant.

Notes

Quand nous parlons de « code » ou de « code exécutable » dans ce contexte, nous faisons référence aux fichiers exécutables portables (PE).

L’identité de package respecte-t-elle la casse ?

La plupart du temps, non, mais Publisher respecte la casse.

Les champs restants (Name, ResourceId, PublisherId,PackageFullName et PackageFamilyName) ne le font pas. Ces champs conservent la casse, mais ne la respectent pas lors d’opérations de comparaison.

Voir aussi

Identité du package

PackageFamilyNameFromId

PackageNameAndPublisherIdFromFamilyName