Bonjour à tous,
Avant tout, je parle ci-après des AddIns Visual Studio MAIS je pense que mon problème est générique et n'a pas grand chose à voir avec le fait qu'il s'agit d'un AddIn. D'ailleur je poste ici car c'est en C# (et aussi parce que j'arrive pas à poster dans le .Net général) mais mon problème n'est pas lié au langage.

Je suis bloqué sur un problème technique assez commun :
J'ai créé un AddIn pour Visual Studio pour ma boite, le truc c'est qu'on travail tous sur la même machine via TSE (ou Citrix, au choix).
En conséquence mon AddIn est toujours chargé et donc impossible de le remplacer simplement sans demander a tout le monde de s'arrêter, sortir de tous leurs Visual Studio, attendre que tout le monde l'ai fait, déployer la mise à jour et refaire le tour de la boite pour indiquer a tout le monde qu'il peuvent à nouveau bosser.

En faisant quelques recherches pour solutionner le problème je suis tombé sur les ShadowCopy. Le principe est assez simple, on créer un AppDomain en spécifiant qu'il fait de la ShadowCopy, ensuite quand un assembly est chargé dans ce domaine, le fichier est d'abord copier dans un répertoire temporaire puis chargé de la-bas. Par conséquent la copie originale n'est pas verrouillée et peux être mise à jour. La mise à jour sera effective pour les utilisateur au prochain démarrage de l'AddIn.

Voilà, sur le papier c'est exactement ce qu'il me faut. Sauf que ...

Quand on lance une appli .Net (y compris les AddIn) l'AppDomain existe déjà et apparement il ne peut pas être reconfiguré. En conséquence j'ai splité en AddIn en deux assembly. Le premier est celui chargé par Visual Studio, son unique rôle est de créer l'AppDomain qui sera en ShadowCopy et d'y transmettre les appels faits par Visual Studio.
La création du nouvel AddDomain fonctionne. Par contre je n'arrive pas à y charger l'assembly.

Pour être plus clair, voici le code complet du premier assembly (qui ne contient qu'une classe donc) :
Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
 
#region Références
using  System;
using Extensibility;
 
using System.Reflection;
using  System.Runtime.Serialization;
using System.IO;
#endregion
 
///  <summary>Objet permettant d'implémenter un  complément.</summary>
/// <seealso class='IDTExtensibility2'  />
public partial class ADT
    : IDTExtensibility2
{
     #region Attribtus
    private AppDomain                _RealDomain;
     private Assembly                _ShadowAssembly;
    private  IDTExtensibility2        _ADTVS;
    #endregion
 
    #region  Propriétés
    #endregion
 
    #region Constructeur
    ///  <summary>
    /// Implémente le constructeur de l'objet Add-in.  Placez votre code
    /// d'initialisation dans cette méthode.
     /// </summary>
    public ADT()
    {
        try
         {
            // Création d'un nouveau domaine pour y charger  ADT.VS en shadow copy.
            AppDomainSetup setup = new  AppDomainSetup();
            string CurrentLocation =  Path.GetDirectoryName(typeof(ADT).Assembly.Location);
             setup.ApplicationBase = CurrentLocation;
             setup.ShadowCopyFiles = "true";
            _RealDomain =  AppDomain.CreateDomain("ADTShadowCopyDomain", null, setup);
 
             // Charge les assemblys
             LoadAssemblyInDomain(_RealDomain, Path.Combine(CurrentLocation,  "nop.net.vshelper.dll"));
            _ShadowAssembly =  LoadAssemblyInDomain(_RealDomain, Path.Combine(CurrentLocation,  "ADT.VS.dll"));
 
            // Parcourt les types de l'assembly  pour en obtenir un qui implémente IDTExtensibility2
             Type[] exposedTypes = _ShadowAssembly.GetExportedTypes();
             Type SearchForType = typeof(IDTExtensibility2);
            foreach  (Type t in exposedTypes)
            {
                if  (SearchForType.IsAssignableFrom(t))
                {
                     _ADTVS =  (IDTExtensibility2)_ShadowAssembly.CreateInstance(t.FullName);
                     break;
                }
            }
        }
         catch (Exception ex)
        {
        }
    }
     #endregion
 
    #region Méthodes
    /// <summary>
     /// Obtient le contenu d'un fichier sous forme d'un tableau d'octets.
     /// </summary>
    /// <param name="filename">Nom du  fichier.</param>
    /// <returns>Tableau d'octet  contenant le fichier ou null si la méthode à échoué.</returns>
     private static byte[] GetFileContent(string filename)
    {
         if (File.Exists(filename) == false) return null;
        byte[]  buffer = null;
        using (FileStream fs = new  FileStream(filename, FileMode.Open))
        {
            buffer =  new byte[fs.Length];
            fs.Read(buffer, 0, buffer.Length);
             fs.Close();
        }
        return buffer;
    }
 
     private static Assembly LoadAssemblyInDomain(AppDomain appd, string  filename)
    {
        string pbdfile =  Path.Combine(Path.GetDirectoryName(filename), String.Format("{0}.pdb",  Path.GetFileNameWithoutExtension(filename)));
        byte[] rawAsm =  GetFileContent(filename);
        byte[] rawPdb =  GetFileContent(pbdfile);
        return appd.Load(rawAsm, rawPdb);
     }
    #endregion
 
    #region Implémentation de l'interface  IDTExtensibility2
    /// <summary>
    /// Implémente la  méthode OnConnection de l'interface IDTExtensibility2.
    /// Reçoit  une notification indiquant que le complément est en cours de
    ///  chargement.
    /// </summary>
    /// <param  term='application'>
    /// Objet racine de l'application hôte.
     /// </param>
    /// <param term='connectMode'>
     /// Décrit la manière dont le complément est
    /// chargé.
     /// </param>
    /// <param term='addInInst'>Objet  représentant ce complément.</param>
    /// <seealso  class='IDTExtensibility2' />
    public void OnConnection(object  application, ext_ConnectMode connectMode, object addInInst, ref Array  custom)
    {
        _ADTVS.OnConnection(application,  connectMode, addInInst, ref custom);
    }
 
    ///  <summary>
    /// Implémente la méthode OnDisconnection de  l'interface IDTExtensibility2.
    /// Reçoit une notification  indiquant que le complément est en cours de
    /// déchargement.
     /// </summary>
    /// <param term='disconnectMode'>
     /// Décrit la manière dont le complément est déchargé.
    ///  </param>
    /// <param term='custom'>
    /// Tableau  des paramètres propres à l'application hôte.
    /// </param>
     /// <seealso class='IDTExtensibility2' />
    public void  OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
     {
        _ADTVS.OnDisconnection(disconnectMode, ref custom);
     }
 
    /// <summary>
    /// Implémente la méthode  OnAddInsUpdate de l'interface IDTExtensibility2.
    /// Reçoit une  notification lorsque la collection de compléments a été
    ///  modifiée.
    /// </summary>
    /// <param  term='custom'>
    /// Tableau des paramètres propres à  l'application hôte.
    /// </param>
    /// <seealso  class='IDTExtensibility2' />        
    public void  OnAddInsUpdate(ref Array custom)
    {
         _ADTVS.OnAddInsUpdate(ref custom);
    }
 
    ///  <summary>
    /// Implémente la méthode OnStartupComplete de  l'interface
    /// IDTExtensibility2. Reçoit une notification  indiquant que le chargement
    /// de l'application hôte est  terminé.</summary>
    /// <param term='custom'>
     /// Tableau des paramètres propres à l'application hôte.
    ///  </param>
    /// <seealso class='IDTExtensibility2' />
     public void OnStartupComplete(ref Array custom)
    {
         _ADTVS.OnStartupComplete(ref custom);
    }
 
    ///  <summary>
    /// Implémente la méthode OnBeginShutdown de  l'interface IDTExtensibility2.
    /// Reçoit une notification  indiquant que l'application hôte est en cours
    /// de  déchargement.
    /// </summary>
    /// <param  term='custom'>
    /// Tableau des paramètres propres à  l'application hôte.
    /// </param>
    /// <seealso  class='IDTExtensibility2' />
    public void OnBeginShutdown(ref  Array custom)
    {
        _ADTVS.OnBeginShutdown(ref custom);
     }
    #endregion
}

Comme vous pouvez le voir je tente de charger deux assembly dans l'AppDomain créé. La première est une dépendance de la seconde. Je ne suis pas sur que je soit tenu de la charger à la main.
Bref, sur AppDomain.Load() , j'ai l'exception suivante :
System.IO.FileNotFoundException: Impossible de charger le fichier ou l'assembly 'nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null' ou une de ses dépendances. Le fichier spécifié est introuvable.
Nom du fichier : 'nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null'
à System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
à System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
à System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
à System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
à System.Reflection.Assembly.Load(String assemblyString)
à System.UnitySerializationHolder.GetRealObject(StreamingContext context)

à System.AppDomain.Load(Byte[] rawAssembly, Byte[] rawSymbolStore)
à ADT.LoadAssemblyInDomain(AppDomain appd, String filename) dans E:\[chemin masqué]\Utilitaires\ADT\ADT\ADT.cs:ligne 86
à ADT..ctor() dans E:\[chemin masqué]\Utilitaires\ADT\ADT\ADT.cs:ligne 41

=== Informations d'état de liaison préalable ===
JRN : utilisateur = [donnée masquée]
JRN : DisplayName = nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null
(Fully-specified)
JRN : Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio 9.0/Common7/IDE/
JRN : PrivatePath initial = NULL
Assembly appelant : (Unknown).
===
JRN : cette liaison démarre dans le contexte de chargement de default.
JRN : utilisation du fichier de configuration de l'application : C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.Config
JRN : utilisation du fichier de configuration de l'ordinateur à partir de C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
JRN : stratégie non appliquée à la référence à ce stade (liaison d'assembly privée, personnalisée, partielle ou basée sur l'emplacement).
JRN : la même liaison a été vue avant et a échoué avec hr = 0x80070002.
J'ai tenté plusieurs méthodes de chargement mais rien y fait. Ma solution serait a priori de poser un handler sur _RealDomain.AssemblyResolve mais pour ca il faut que je rende ma classe sérializable et je n'ai pas trop d'idée de comment. Enfin si je sais, je doit implémenter ISerializable et poser l'attribut Serializable sur la classe, sauf que je n'ai pas d'idée de ce qu'il faut mettre dans la méthode ISerializable.GetObjectData.

Bref, si vous avez une idées de où ca peut venir, comment corriger, ou même si vous connaissez une autre manière de répondre à ma problématique initiale, je suis tout ouïe.