Flash info

"Problems cannot be solved by the same level of thinking that created them."

- Albert Einstein
 
Accueil arrow Articles / Tutoriaux arrow COM Interop ou la galère pour manipuler les objets COM
COM Interop ou la galère pour manipuler les objets COM
Écrit par Arnaud Petitjean [MVP]   
17-03-2010

COM Interop ou la galère pour manipuler les objets COM 

PowerShell est très puissant et sa puissance nous permet de dégager de substantiels gains de productivité (n’hésitez pas à en parler à votre chef pour qu’il vous donne une prime, je vous appuierais à 100% J). Ce point là, vous l’aurez tous très vite compris et en particulier les utilisateurs, qui comme moi, viennent du monde  VBScript.

Ceci étant, la perfection n’étant pas de ce monde, il y a un point qui régulièrement m’agace lorsqu’il s’agit de manipuler des objets COM. En effet, certaines fois tout marche comme sur des roulettes, comme par exemple lorsque l’on manipule les objets COM Office ou Internet Explorer, mais certaines fois il m’arrive de passer un temps infini pour faire une petite chose simple, voire même de ne pas y arriver du tout. Alors qu’en VBScript on pouvait le faire très simplement ! (Rassurez-vous tout de suite, je ne regrette pas pour autant VBScript, bien au contraire !!! Wink)

L’objet COM d’Internet Explorer

 

Pour illustrer mes propos, prenons un exemple simple : ouvrir Internet Explorer sur une adresse particulière.

Tout d’abord créons une instance de l’objet COM Internet Explorer et « envoyons-là » dans une variable, comme ceci :

$objIE = New-Object -ComObject InternetExplorer.Application.1 

A présent, comme d’habitude, demandons les membres de notre objet afin de voir ce que nous allons pouvoir en tirer :

PS > $objIE | Get-Member

     TypeName: System.__ComObject#{d30c1661-cdaf-11d0-8a3e-00c04fc9e26e}

 Name                 MemberType Definition
 ----                 ---------- ----------
 ClientToWindow       Method     void ClientToWindow (int, int)
 ...
 Navigate             Method     void Navigate (string, Variant, Variant, Variant, Variant)
 Navigate2            Method     void Navigate2 (Variant, Variant, Variant, Variant, Variant)
 PutProperty          Method     void PutProperty (string, Variant)
 QueryStatusWB        Method     OLECMDF QueryStatusWB (OLECMDID)                           
 Quit                 Method     void Quit ()                                               
 Refresh              Method     void Refresh ()                                             
 Refresh2             Method     void Refresh2 (Variant)                                    
 ShowBrowserBar       Method     void ShowBrowserBar (Variant, Variant, Variant)            
 Stop                 Method     void Stop ()                                                
 AddressBar           Property   bool AddressBar () {get} {set}                             
 Application          Property   IDispatch Application () {get}                             
 ...
 Type                 Property   string Type () {get}                                       
 Visible              Property   bool Visible () {get} {set}                                
 Width                Property   int Width () {get} {set}
 

Nous avons donc accès à de nombreuses propriétés et méthodes et nous allons pour l’exemple faire appel à la méthode Navigate, comme ceci :

PS > $objIE.Navigate('http://powershell-scripting.com') 

Fantastique, cela fonctionne !!!

cominterop_ie.jpg 
 
  

 

Nous pouvons cependant nous rendre compte que contrairement à un objet .NET, Get-Member nous retourne nettement moins d’informations concernant les types et l’utilisation des différentes propriétés et méthodes. Cela est probablement dû à la définition de notre objet COM, mais nous allons voir que ce n’est déjà pas si mal que ça, car la suite va se compliquer quelque peu…

Jusque là, dans cet exemple, nous n’avons pas rencontré de soucis particulier et nous avons manipulé notre objet COM comme n’importe quel objet sans vraiment avoir fait de différence.

L’objet COM ADSystemInfo

 

Là où les choses se gâtent c’est par exemple avec les objets COM ADSI et notamment avec l’objet ADSystemInfo.

Grâce à cet objet, il est possible de récupérer des informations intéressantes auprès d’un annuaire Active Directory, comme : le nom du domaine courant et du site AD, le Distinguished Name de l’ordinateur local et le Distinguished Name de l’utilisateur connecté, ainsi que bien d’autres informations utiles. L’objet COM ADSystemInfo réside dans la DLL ADSLDP.DLL laquelle est présente depuis Windows 2000.

Observons le petit script VBScript suivant qui nous retourne le Distinguished Name de l’utilisateur qui éxécute le script :

Set oADSystemInfo = CreateObject("ADSystemInfo")
WScript.Echo oADSystemInfo.UserName 

Résultat :

CN=Administrator,CN=Users,DC=powershell-scripting,DC=com 

 Ceci est bien beau, mais essayons de faire de même en PowerShell :

PS > $oADSystemInfo = New-Object -ComObject ADSystemInfo
PS > $oADSystemInfo.UserName 

Résultat : Néant

Alors que nous faisons la même opération qu’avec le script VBS précédent, nous serions en droit de nous attendre au même résultat, et bien non ! Il se passe bien un problème…

Essayons de voir quels sont les membres de la variable $oADSystemInfo en utilisant une fois encore la commande Get-Member :

PS > $oADSystemInfo | Get-Member

    TypeName: System.__ComObject

 Name                      MemberType Definition
 ----                      ---------- ----------
 CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
 Equals                    Method     bool Equals(System.Object obj)
 GetHashCode               Method     int GetHashCode()
 GetLifetimeService        Method     System.Object GetLifetimeService()
 GetType                   Method     type GetType()
 InitializeLifetimeService Method     System.Object InitializeLifetimeService()
 ToString                  Method     string ToString() 

Nous pouvons constater que le type de l’objet retourné est de type __ComObject, ce qui nous permet d’affirmer que l’objet est bien créé, mais que ce dernier ne possède pas la propriété que nous cherchons. Du moins en apparence…

En effet, il se trouve que cet objet COM (mais ce n’est malheureusement pas un cas isolé) n’implémente pas de bibliothèque de type, c'est-à-dire qu’il n’expose ni propriétés, ni méthodes. De plus il ne possède pas non plus « wrapper » qui permet à cet objet d’être accessible facilement depuis un langage .NET tel que PowerShell. N’étant pas développeur, je vais m’arrêter là pour les explications techniques sur le sujet. Que faire donc ?

Et bien il va falloir ruser et essayer de passer outre les mécanismes d’abstraction de .NET pour accéder directement à notre objet COM. Pour ce faire, nous utiliserons une méthode nommée InvokeMember qui s’applique à tous les objets hérités de la classe Type ou plus exactement System.Type, ce qui est notre cas. Pour obtenir une référence à l’objet Type nous utiliserons la méthode GetType, comme ceci :

PS > $oADSystemInfo.GetType()

 IsPublic IsSerial Name                                     BaseType
 -------- -------- ----                                     --------
 True     False    __ComObject                              System.MarshalByRefObject 

 

A présent, nous pouvons appliquer la méthode  InvokeMember dont nous détaillerons les paramètres :

PS > $oADSystemInfo.GetType().InvokeMember( (<name>, <invokeAttr>, <binder>, <target>, <args>) )

  • name : Nom de méthode ou de propriété à appeler (type String)
  •         invokeAttr : Enumération de type Binding Flags décrivant l’invocation à effectuer dans notre cas,
            les valeurs seront généralement : GetProperty, ou InvokeMethod
  •         binder : En général ce paramètre prend la valeur Null pour appeler le Binder par défaut
  •         target : Objet sur lequel porte l’argument name
  •         args : arguments de type tableau d’objets (Object[]) à passer au membre appelé

 

Appel d’une propriété

 

 

Fort de ces informations théoriques, nous allons pouvoir à présent tenter d’extorquer des informations de notre objet COM.

PS > $oADSystemInfo = New-Object -ComObject ADSystemInfo
PS > [System.__ComObject].InvokeMember('UserName',[System.Reflection.BindingFlags]::GetProperty,$null,$oADSystemInfo,$null) 

CN=Administrator,CN=Users,DC=powershell-scripting,DC=com 

 Victoire, cela fonctionne !!!

Afin de simplifier l’écriture, nous pouvons aussi utiliser la commande suivante :

PS > [System.__ComObject].InvokeMember('UserName','GetProperty',$null,$oADSystemInfo,$null) 

Ou encore

PS > [__ComObject].InvokeMember('UserName','GetProperty',$null,$oADSystemInfo,$null) 

 Une autre façon de faire, qui revient au même que la précédente mais que l’on rencontre assez régulièrement sur Internet est la suivante :

PS >$oADSystemInfo.GetType().InvokeMember'UserName','GetProperty',$null,$oADSystemInfo,$null) 

 

Vous l’aurez compris, l’idée est simplement d’appeller la méthode InvokeMember sur un objet COM, et ce quelle que soit la façon d’obtenir une référence à la classe générique __ComObject. Pour cette raison, je préfère la forme précédente qui est pour moi plus concise mais surtout plus explicite.

Appel d’une méthode

 

 

Si l’on se réfère à la documentation de référence qui est MSDN sur l’objet COM ADSystemInfo, on constate que ses méthodes retournent la même chose que ses propriétés, ce qui n’est pas d’un grand intérêt. Nous allons donc préférer prendre un nouvel exemple, avec cette fois un objet COM nommé PathName.

Cet objet COM permet, entre autre, d’insérer des caractères d’échappement dans une chaîne de caractères afin d’en faire une chaine de caractère valide au format X.500 (ou LDAP). Cela permet de se reposer sur une API, plutôt que de réaliser l’opération manuellement au risque de ne pas traiter tous les cas de figures et d’introduire des erreurs.

Bien qu’il ne soit pas spécialement recommandé d’avoir des caractères spéciaux dans un Common Name tels que le signe égal (=), la virgule (,) et le slash (/) cela peut arriver. Et quand cela se produit, il faut « échapper » la chaine afin que les caractères spéciaux ne soient pas interprétés. L’échappement dans la norme X.500 se fait avec le caractère anti-slash (\).

Exemple 1:

Chaine initiale         : cn=jim,morisson
Chaine échappée (X.500) : cn=jim\,morisson 

Exemple 2:

Chaine initiale         : cn=jim/morisson=test
Chaine échappée (X.500) : cn=jim\/morisson\=test 

Voilà pour l’introduction de cet objet COM, mais là n’est pas la question. La question est plutôt : comment parvenir à appeller la méthode GetEscapedElement de cet objet ?

Comment savons-nous qu’il faut appeler cette méthode ? Tout simplement parce que l’aide de cet objet COM proposée par MSDN nous l’a indiqué.

Revenons maintenant au code PowerShell. Nous avons vu que pour appeller une méthode ou une propriété, il faut agir sur la valeur du paramètre InvokeAttr de la méthode InvokeMember. C’est précisément ce que nous allons faire en lui donnant la valeur « InvokeMethod » pour faire appel à une méthode :

PS > $CN = 'cn=jim/morisson=test'
PS > $IADsPathName = New-Object -ComObject Pathname
PS > [__ComObject].InvokeMember('GetEscapedElement','InvokeMethod',$null,$IADsPathName,(0, $CN))

cn=jim\/morisson\=test 

 

Notez que pour que cela fonctionne, la chaine de caractères à échapper doit toujours commencer par « cn= ».

 

Comment découvrir les membres d’un objet COM ?

 

 

Les membres sont les propriétés et méthodes que possède d’un objet. Pour les trouver, lorsque tout se passe bien, la commandelette Get-Member est là pour ça ; mais lorsqu’elle ne retourne rien c’est là que les ennuis commencent...

Et là, vous avez deux cas de figure :

  • l’objet COM est un objet fourni par Microsoft,
  • l’objet COM est fourni par un éditeur tiers.

Dans second cas, vous n’avez d’autre choix que de vous référer à la documentation de l’éditeur ; et dans le premier cas, qui est le plus courrant, Google/Bing et MSDN sont vos amis.

Exemple de mots clés pertinents pour une recherche internet : MSDN ADSI ADSystemInfo 'COM object'

Voici le premier résultat que nous retourne tout bon moteur de recherche :

 

cominterop_sur_msdn.jpg 

 

Comme nous savons à l’avance que ce que nous recherchons se trouve dans la base de connaissance MSDN, il peut être judicieux d’effectuer sa recherche directement dans le moteur de recherches interne au site.

Une fois la rubrique d’aide trouvée, vous aurez accès à de l’information très détaillée sur : la liste des propriétés et méthodes de l’objet COM ainsi que sur le fonctionnement de ces dernières. Une fois que vous aurez identifié la propriété ou la méthode qui convient à votre besoin, vous n’aurez plus qu’à modifier votre script PowerShell en conséquence. En en particulier l’appel à la méthode InvokeMember ; ce n’est pas plus compliqué que cela...

 

Conclusion

 

Vous l’aurez compris, l’utilisation de certains objets COM à partir d’un langage s’appuyant sur .NET, tel que PowerShell, peut parfois poser quelques problèmes. Ceci est dû à ce que les développeurs appellent « COM interop », la technologie qui permet d’appeller des composants COM à partir de .NET. Ce problème se produit quand il manque certaines informations sur des objets COM. J’imagine que cela arrive essentiellement sur des vieux objets COM, comme ceux relatifs à ADSI car ils n’ont pas évolué depuis environ 10 ans : soit depuis l’arrivée de Windows 2000 et d’Active Directory. A cette époque-là on ne parlait pas encore de Framework .NET et encore moins de PowerShell !

Espèrons donc que nous serons de moins en moins confrontés à ces problèmes ; ceci étant maintenant vous saurez les contourner lorsqu’ils surviendrons.

 

A lire sur le sujet...

 

 

En anglais : COM interop issues in PowerShell  

En Français : DOTNET : COM interop - Développez.com - J-M. Rabilloud

 
Dernière mise à jour : ( 17-03-2010 )
 
© 2017 PowerShell-Scripting.com