Browsed by
Category: tips & tricks

Can’t download apps on Microsoft Store with error 0x80080204

Can’t download apps on Microsoft Store with error 0x80080204

TL;DR: Fall creator update SDK cause major issues if your application uses an background audio agent.

There is nothing more frustrating than receive a lot of bad reviews, not because your app crashes or is buggy but because users are not able to update or download your app because of Microsoft…

Here is my last (bad) experience with the Microsoft Store, but this time the culprit is… Visual Studio itself.

During the last few days, I’ve received several messages (1-star reviews, reddit, forums, Facebook) telling me that one of my applications is not downloadable from the Microsoft Store on Windows 10 Mobile or that updates are blocked.

In tech companies, when major issues as this one happen and once fixed, we write postmortem documents, explaining all errors we made and listing all tasks to do in order to be sure it will not happen again.

Usually, we keep them internally, but I’ve decided to share a part of mine in order to help other developers having this issue and give more information to users.

Reason: what cause the 0x80080204 error?

 
In our case, the culprit is Visual Studio 2017 + the Fall Creator Update SDK.

If your application contains a background audio agent, each time you edit your AppxManifest via the App Manifest Designer (the default viewer of this type of file) when you change the version number of your app (when you create a package for example), the appxmanifest becomes corrupted.

Before:


<Extension Category="windows.backgroundTasks" EntryPoint="TestApp.Agent">
  <BackgroundTasks>
    <Task Type="audio" />
  </BackgroundTasks>
</Extension>

 

After:


<Extension Category="windows.backgroundTasks" EntryPoint="TestApp.Agent">
  <BackgroundTasks/>
</Extension>

So when the Microsoft Store tries to install the application, it doesn’t know the type of the background agent and fail the installation (without an explicit reason).

Why we failed to detect it:

 
This problem is almost impossible to detect, cause:

  • Even with this faulty manifest, applications can be debugged or deployed from Visual Studio
    • Visual Studio doesn’t display warnings or errors
  • The manifest can become faulty at the last second, once all tests done, cause happening when we generate the package for the Microsoft Store
  • The Microsoft Store accepts the upload of this faulty manifest, validate it and publish it without a single warning
  • The error message displayed by the Store is not clear

How to fix it?

 
Microsoft is 100% responsible, they should:

  • Very urgent (less than 2 days): modify the Dev Center to reject every packages with background agents without types.
  • Urgent (less than 10 days): Update the SDK and/or Visual Studio to fix the issue.
  • Check every apps using Fall Creator Update SDK and verify the appxmanifest of these apps.
    • Contact developers and give them a documentation/guide to fix the issue.

Waiting these patches, I’ve created a little command-line tool verifying your appxmanifest and fixing it if needed.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace FixAudioAppManifest
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args == null || args.Length != 2)
            {
                Console.WriteLine("FixAudioAppManifest.exe <file path> <entry point name>");
                return;
            }

            var xmlModified = false;

            var xmlFilePath = args[0];
            var entryPointName = args[1];

            Console.WriteLine("@xmlFilePath = " + xmlFilePath);
            Console.WriteLine("@entryPointName = " + entryPointName);

            if (!File.Exists(xmlFilePath))
            {
                Console.WriteLine(xmlFilePath + " doesn't exist");
                return;
            }
            var xmlDoc = XDocument.Load(xmlFilePath);
            var windows10Namespace = XNamespace.Get("http://schemas.microsoft.com/appx/manifest/foundation/windows10");
            var nodeAgentToFix = xmlDoc.Descendants().Where(element => element.Name == windows10Namespace + "Extension" && element.Attribute("Category")?.Value == "windows.backgroundTasks" && element.Attribute("EntryPoint")?.Value == entryPointName).FirstOrDefault();
            if (nodeAgentToFix == null)
            {
                Console.WriteLine("Error: Entry point '" + entryPointName + "' not found");
                Console.WriteLine("FixAudioAppManifest.exe <file path> <entry point name>");
                return;
            }
            var backgroundTasksNode = nodeAgentToFix.Element(windows10Namespace + "BackgroundTasks");
            if (backgroundTasksNode == null)
            {
                Console.WriteLine("Error: BackgroundTasks element created cause missing");
                backgroundTasksNode = new XElement(windows10Namespace + "BackgroundTasks");
                nodeAgentToFix.Add(backgroundTasksNode);
                xmlModified = true;
            }

            var taskNode = backgroundTasksNode.Element(windows10Namespace + "Task");
            if (taskNode == null)
            {
                Console.WriteLine("Task element created cause missing");
                taskNode = new XElement(windows10Namespace + "Task");
                backgroundTasksNode.Add(taskNode);
                xmlModified = true;
            }

            if (taskNode.Attribute("Type") == null)
            {
                Console.WriteLine("Task:Type created cause missing");
                taskNode.SetAttributeValue("Type", "audio");
                xmlModified = true;
            }

            if (xmlModified)
            {
                xmlDoc.Save(xmlFilePath);
                Console.WriteLine("AppxManifest modified! The audio agent was correctly set.");
            }
            else
            {
                Console.WriteLine("AppxManifest not modified, the audio agent is correctly set.");
            }
        }
    }
}

Code available here:
FixAudioAppManifest.zip

To make sure your appxmanifest will not be corrupted by Visual Studio, first download the ZIP and compile the project inside.

Then, right click on your UWP project, select Properties, then Build events, and type in “Pre-build event command line”:

"c:\...\FixAudioAppManifest.exe" $(ProjectDir)Package.appxmanifest <entry_point_name>

Where entry_point_name is the name of the entry point in the appxmanifest related to your background audio agent.

Once done, the tool will check your appxmanifest every time you will compile your project, including when you will create your app package.

Comment charger proprement une page web sans cache ?

Comment charger proprement une page web sans cache ?

Une grande majorité des développeurs Windows Phone ont surement déjà rencontré ce problème : On souhaite requêter une page web, sauf que c’est une version provenant du cache que l’on nous retourne.

Un hack très répondu et à mon sens, pas propre, est d’ajouter un paramètre variable à l’Uri de sa requête comme par exemple :


var request = WebRequest.CreateHttp("<a href="http://www.feelmygeek.com/rand.php?s">http://www.rudyhuyn.com/?rand="+new</a> Random().Next());

ou encore


var request = WebRequest.CreateHttp("<a href="http://www.feelmygeek.com/rand.php?s">http://www.rudyhuyn.com/?rand="+DateTime.Now.Ticks</a>);

Personnellement, je ne trouve pas ça élégant, on est assez proche de MacGyver qui souhaite réparer sa voiture grâce à un trombone.

Le protocole de test

Pour effectuer mes tests, j’ai développé un petit script php:


<?php

header("Cache-Control: max-age=31536000");

header("Expires: Sun, 27 Oct 2013 14:50:11 GMT");

echo rand() . "n";

echo rand() . "n";

echo time();

?>

 

et un client Windows Phone :


var request = WebRequest.CreateHttp("http://www.feelmygeek.com/rand.php");
request.BeginGetResponse((iar) =>{
var response=request.EndGetResponse(iar).GetResponseStream();
var val = new StreamReader(response).ReadToEnd();
Deployment.Current.Dispatcher.BeginInvoke(() => {
MessageBox.Show(val);
});
}, null);

 

Si deux requêtes consécutifs retourne le même résultat, on pourra donc en déduire que le cache a été utilisé.

Alors comment le faire élégamment ?

 Cache-Control :no-cache ?

Ajoutons donc  la ligne suivante à notre test :


request.Headers[HttpRequestHeader.CacheControl] = "no-cache";

Logiquement, cela devrait fonctionner, mais… non, le message affiché est bien toujours le même, il faut donc trouver une autre solution.

Cache-control : max-age=0 ?


request.Headers[HttpRequestHeader.CacheControl] = "max-age=0";

Non plus… de toute évidence, Windows Phone ignore cette instruction.

Contournons donc le problème

Vu que cache-control ne semble pas pris en compte, trouvons une autre solution.

Au lieu de dire “n’utilise pas le cache”, nous allons plutôt dire, “retourne moi une nouvelle page si elle a été modifiée depuis 1970”.

Sachant qu’internet a été inventé en 1973, on devrait être tranquille.

Voici comment le faire :


request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.MinValue.ToString();

Note :

Si vous cherchez bien sur internet, vous trouverez probablement ce genre de solution :


request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.UtcNow.ToString();

Cette solution est a éviter et à bannir. Pour peu que votre serveur gère correctement le header http IfModifiedSince, il vous retournera très probablement un code 304 (not modified) car littéralement vous lui demandez :

Retourne moi la page, uniquement si elle a été modifié entre maintenant et maintenant

Ce qui n’est pas le plus efficace.

Conclusion

Si vous souhaitez éviter tout problème de cache, voici donc la méthode la plus élégante pour cela :


var request = WebRequest.CreateHttp("http://www.feelmygeek.com/rand.php");
request.Headers[HttpRequestHeader.IfModifiedSince] = DateTime.MinValue.ToString();
request.Headers[HttpRequestHeader.CacheControl] = "no-cache";
request.BeginGetResponse((iar) =>{
var response=request.EndGetResponse(iar).GetResponseStream();
var val = new StreamReader(response).ReadToEnd();
Deployment.Current.Dispatcher.BeginInvoke(() => {
MessageBox.Show(val);
});
}, null);

 

Un dernier point : pourquoi avoir laissé “CacheControl : no-cache” ?

Tout simplement pour éviter que des proxys vous retourne une ancienne version de la page entre le téléphone et le serveur !

Et nos amis de Windows 8 ?

Une petite note pour nos amis WinRT, HttpWebRequest dispose d’une propriété IfModifiedSince de type DateTime, sauf que ma solution ne fonctionne pas avec eux car comme l’indique la documentation :

Si la propriété IfModifiedSince a la valeur DateTime.MinValue, l’en-tête HTTP If-Modified-Since est supprimé de la propriété Headers et de WebHeaderCollection.

La solution est donc de contourner tout cela en écrivant :


request.IfModifiedSince = DateTime.MinValue.AddSecond(1);

Voilà !

L'intégration de votre application au hub Photo

L'intégration de votre application au hub Photo

Fréquentant quelques développeurs iphone (pas d’inquiétude, je me soigne), ces derniers se plaignent régulièrement de ne pas avoir la possibilité d’associer leurs applications comme flickr, facebook ou foodreporter avec le type images. Impossible donc pour eux de prendre une photo, puis de faire “envoyez vers picasa” par exemple. Malgré le peu de documentations, cette possibilité existe avec Windows Phone 7, et juste pour les dégoûter nos développeurs iPommé, il y a exactement deux façon de le faire. Lorsque vous prenez une photo ou que vous regardez une image dans le hub photo, un menu “…” est visible en bas ou à droite. En cliquant dessus, vous avez accès à différents menu, dont “partager” et “supplémentaire”. Nous réservons les applications sociales au premier menu, alors que les applications permettant la retouche ou le traitement de photos trouveront plus leurs places dans le second. Vous trouverez dans la suite de cet article comment se greffer à ces menus.

Read More Read More

Notification Push : Serveur apache/php

Notification Push : Serveur apache/php

Il existe quelques idées reçues vis à vis de la notification push, dont notamment qu’on a besoin d’un serveur IIS avec ASP.Net pour pouvoir en faire. La faute surement à la documentation MSDN qui ne donne que des exemples en Asp.Net.

La deuxième idée reçue est que le push est difficile à mettre en en place côté serveur.  Nous allons donc essayer de contredire tout cela, mais avant un peu de vulgarisation.

Comment fonctionne le push

Avant de commencer, voyons rapidement comment fonctionne le push sur windows phone 7.

 

4 étapes sont nécessaires :

  1. Le client via HttpNotificationChannel s’enregistre auprès sur service de notification de Microsoft en indiquant quel genre de notification l’application souhaite : Brute, Tuile ou Toast. Le serveur retourne alors une uri, que l’on va nommer channel_uri.
  2. L’application envoie à votre serveur channel_uri pour le stocker en base de données (ou autres).
  3. Lorsque le serveur voudra envoyer une notification push, il enverra à l’uri channel_uri (d’où l’intérêt de la stocker) un fichier XML avec les informations relatives au message push (+ quelques infos en header http)
  4. Le serveur push de Microsoft va ensuite envoyer la notification au téléphone.

 

Juste pour information, voici à quoi ressemble une notification toast :
Headers :

X-WindowsPhone-Target: toast
X-NotificationClass: 2

Contenu :

<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Toast>
<wp:Text1>Hello Steve</wp:Text1>
<wp:Text2>High five !</wp:Text2>
</wp:Toast>
</wp:Notification>

X-WindowsPhone-Target : la cible : ici un ‘toast’ (une popup)
X-NotificationClass : le type de notification : 2=toast immediat (12=toast dans 450 secondes par exemple)
Text1 : le titre
Text2 : le sous titre

Donc mise à part l’étape d’enregistrement (1 et 2),  envoyer une notification sur un téléphone, techniquement parlant c’est juste envoyer un XML à une uri précise, rien de bien compliqué donc ! Idée reçue numéro 2 down !

Notification Push avec PHP

Avoir un serveur web sous IIS n’est pas forcément accessible à tout le monde, à moins d’avoir son propre serveur dédié. Toutefois, il est tout à fait possible de gérer tout cela sous apache/php.

Pour vous aider, j’ai créé une classe php permettant de facilement envoyer des notifications toast/tile/raw. Vous la trouverez sous codeplex. Voici quelques exemples d’utilisations :

Envoi d’un notification de type toast

[code lang=”php”]

$uri=”http://db3.notify.live.net/….”; //uri sended by Microsoft plateform
$notif=new WindowsPhonePushNotification($uri);
$notif->push_toast(“this is a title”,”this is the sub title”);

[/code]

Envoi d’un notification de type tile


$uri="http://db3.notify.live.net/...."; //uri sended by Microsoft plateform
$notif=new WindowsPhonePushNotification($uri);
$notif->push_tile($image_uri,"mon titre",9);

Envoi d’un notification brute


$uri="http://db3.notify.live.net/...."; //uri sended by Microsoft plateform
$notif=new WindowsPhonePushNotification($uri);
$notif->push_raw("123,23,coronso");

Et deux cadeaux bonux pour le prix d’un :

Les trois méthodes push_tile, push_toast et push_raw ont deux paramètres optionnels qui pourront peut-être vous servir :

$delay : envoyer la notification tout de suite, dans 450 secondes (7 min30) ou dans 900 secondes (15 min).

$message_id : permet spécifier un UUID qui sera retourné ensuite par le serveur. Utile potentiellement pour le deboggage

 

Création d'application : Pensez à la rubrique "A propos"

Création d'application : Pensez à la rubrique "A propos"

Je viens de recevoir mon premier rapport négatif suite à la soumission d’une petite application que j’ai codé ce week-end nommée “letter clock”, une horloge design qui affiche l’heure sous forme textuelle.

Voilà donc à quoi ressemble un rapport de soumission (désolé pour vous si vous avez les idées mal placées !)

On y trouve donc les informations principales sur l’application : nom, numéro de version, date de soumission, etc.. mais aussi quelques informations qui peuvent être intéressante, on peut voir qu’il y a eu plus de 32000 soumissions d’applications ou de mises à jour depuis le début de la plateforme, ce qui est déjà pas mal !

Revenons à nos mammifères laineux, en bas de ce rapport on trouve un “Fail” et comme Microsoft est généreux, en rouge, gras et avec majuscule pour renforcer l’effet. La raison de ce FAIL: dans l’application, il faut que le nom de l’application, le numéro de version et un mail/formulaire de contact doit facilement être trouvable.

En effet, mon application ressemblait à ça :

et j’avais fait le choix de ne pas rajouter de menu pour ne pas casser le design de l’application.

Donc mon conseil du jour, n’oubliez pas la partie contact ou “à propos” dans votre application si vous souhaitez la soumettre ! Je reviendrais prochainement sur les points clés à retenir pour la soumission.

PS : non l’heure de la capture n’a pas été prise au hasard 😉