Question
A propos du post "COM Interop ou la galère ..."
- Laurent Dardenne
- Auteur du sujet
- Hors Ligne
- Modérateur
-
Réduire
Plus d'informations
- Messages : 6311
- Remerciements reçus 68
il y a 16 ans 40 minutes #6403
par Laurent Dardenne
Tutoriels PowerShell
A propos du post "COM Interop ou la galère ..." a été créé par Laurent Dardenne
Quelques remarques à propos de ce
post
.
>>Alors qu’en VBScript on pouvait le faire très simplement !
Oui c'est son point fort, car il est orienté COM. Il ne voit que des objets, là ou PowerShell voit des objets COM et des objets dotnet
>>Nous pouvons constater que le type de l’objet retourné est de type __ComObject...
La différence entre les deux exemples se situe dans la précision d'un GUID d'interface pour le premier, GUID que l'on retrouve dans la base de registre
[code:1]
HKEY_CLASSES_ROOT\Interface\{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}
[/code:1]
Le nom de la valeur par défaut est un nom d'interface, IWebBrowser2 .
>>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.
Ce n'est pas tout à fait ça, c'est la librairie de type (tlb) qui décrit un objet COM.
>>De plus il ne possède pas non plus DE « 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 ?
A partir du moment où on dispose d'une tlb on peut construire ce wrapper, de plus PowerShell n'est pas en cause dans ce cas.
Si tu utilises Visual Studio, celui-ci crée en interne ce Wrapper, je dirais donc que c'est Powershell qui ne dispose pas de la capacité de créer ce wrapper à partir d'un fichier .tlb.
Comment savoir si une tlb existe pour un objet COM (une interface) ?
En consultant la doc de l'interface IADsADSystemInfo , on peut voir en fin de page que le champ DLL indique \"Activeds.dll\".
A partir de là on recherche une librairie de type nommée \"Activeds.tlb\". Et ça match dans [code:1]\"C:\WINDOWS\system32\activeds.tlb\"[/code:1]
>>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.
L'abstraction est une notion de conception, peut être voulais-tu parler, dans le contexte de PowerShell, d'adaptation ?
>>plus exactement System.Type, ce qui est notre cas.
Sous PS il me semble que cela sera tjr le cas, puisque tout est adapté, voir aussi .
On peut, moyennant quelques étapes de développement, créer ce wrapper tant regretté. Il nous faut déjà connaitre le chemin du sdk 2.0 qui propose qq utilitaires :
[code:1]
$SDKFolder=(Get-item \"HKLM:\SOFTWARE\Microsoft\Microsoft SDKs\.NETFramework\v2.0\"|Get-ItemProperty).InstallationFolder
cd c:\temp
[/code:1]
Une fois ceci fait, on doit créer un fichier de signature afin d'autoriser l'insertion d'un assembly dans le GAC, à moins que votre organisation vous en propose un :
[code:1]
#Création d'une signature
&\"$SDKFolder\bin\sn.exe\" -k signature.snk
[/code:1]
Je n'ai pas testé, mais il me semble que l'on peut s'affranchir de cette étape, encore faut-il savoir ce que l'on fait.
Ensuite on crée un fichier interop, notre wrapper, en utilisant l'outil TlbImp (tlb import) :
[code:1]
#Création de l'assembly à partir d'une librairie de type (COM)
&\"$SDKFolder\bin\TlbImp\" C:\WINDOWS\system32\activeds.tlb /out:C:\temp\Interop.activeds.dll /namespace:\"Interop.activeds\" /primary /keyfile:C:\temp\signature.snk /asmversion:1.0.0
[/code:1]
En ligne de commande vous noterez quelques avertissements, là où Visual Studio n'en remonte aucun.
Ceci fait on peut copier le fichier dans le GAC (Global Assembly Cache) ou le charger directement :
[code:1]
$AdsAssembly=[Reflection.Assembly]::LoadFrom(\"$pwd\Interop.activeds.dll\"«»)
[/code:1]
Reste à tester l'usage de notre Wrapper :
[code:1]
$Path= new-object Interop.ActiveDs.IADsPathname
#New-Object : Constructeur introuvable. Impossible de trouver un constructeur approprié pour le type Interop.ActiveDs.IADsPathname.
[/code:1]
Cela ne fonctionne pas car on continue de référencer le nom de l'interface COM, alors que le wrapper nous propose des classes:
[code:1]
$Classes=$AdsAssembly.GetExportedTypes()|? {$_.name -match \"class$\"}
$Classes
[/code:1]
Dans la liste on trouve une classe PathnameClass en lieu et place de IADsPathname :
[code:1]
$Path= new-object Interop.ActiveDs.PathNameClass
$Path|gm
# TypeName: Interop.activeds.PathnameClass
#
# Name MemberType Definition
# ----
# AddLeafElement Method System.Void AddLeafElement(string bstrLeafElement)
# CopyPath Method System.Object CopyPath()
# CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
# Equals Method bool Equals(System.Object obj)
# GetElement Method string GetElement(int lnElementIndex)
# GetEscapedElement Method string GetEscapedElement(int lnReserved, string bstrInStr)
# ...
[/code:1]
Reste à confirmer le résultat de la méthode GetEscapedElement:
[code:1]
$CN = 'cn=jim/morisson=test'
$Path.GetEscapedElement(0,$CN)
#cn=jim\/morisson\=test
[/code:1]
Cela me semble correspondre.
Une fois notre wrapper construit plus besoin d'utiliser InvokeMember qu'on utilisera seulement s'il n'existe aucune TLB ou si son import n'est pas possible.
Notre classe .NET est en fait une coclass , une classe Composants, qui contient toutes les interfaces supportées par un objet :
[code:1]
$CoClasse=$Classes[0]
$CoClasse.GetInterfaces()
[/code:1]
Mais pas COM
ps:
Reste à demander à MS un cmdlet spécialisé pour cette opération...<br><br>Message édité par: Laurent Dardenne, à: 21/03/10 10:35
>>Alors qu’en VBScript on pouvait le faire très simplement !
Oui c'est son point fort, car il est orienté COM. Il ne voit que des objets, là ou PowerShell voit des objets COM et des objets dotnet
>>Nous pouvons constater que le type de l’objet retourné est de type __ComObject...
La différence entre les deux exemples se situe dans la précision d'un GUID d'interface pour le premier, GUID que l'on retrouve dans la base de registre
[code:1]
HKEY_CLASSES_ROOT\Interface\{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}
[/code:1]
Le nom de la valeur par défaut est un nom d'interface, IWebBrowser2 .
>>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.
Ce n'est pas tout à fait ça, c'est la librairie de type (tlb) qui décrit un objet COM.
>>De plus il ne possède pas non plus DE « 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 ?
A partir du moment où on dispose d'une tlb on peut construire ce wrapper, de plus PowerShell n'est pas en cause dans ce cas.
Si tu utilises Visual Studio, celui-ci crée en interne ce Wrapper, je dirais donc que c'est Powershell qui ne dispose pas de la capacité de créer ce wrapper à partir d'un fichier .tlb.
Comment savoir si une tlb existe pour un objet COM (une interface) ?
En consultant la doc de l'interface IADsADSystemInfo , on peut voir en fin de page que le champ DLL indique \"Activeds.dll\".
A partir de là on recherche une librairie de type nommée \"Activeds.tlb\". Et ça match dans [code:1]\"C:\WINDOWS\system32\activeds.tlb\"[/code:1]
>>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.
L'abstraction est une notion de conception, peut être voulais-tu parler, dans le contexte de PowerShell, d'adaptation ?
>>plus exactement System.Type, ce qui est notre cas.
Sous PS il me semble que cela sera tjr le cas, puisque tout est adapté, voir aussi .
On peut, moyennant quelques étapes de développement, créer ce wrapper tant regretté. Il nous faut déjà connaitre le chemin du sdk 2.0 qui propose qq utilitaires :
[code:1]
$SDKFolder=(Get-item \"HKLM:\SOFTWARE\Microsoft\Microsoft SDKs\.NETFramework\v2.0\"|Get-ItemProperty).InstallationFolder
cd c:\temp
[/code:1]
Une fois ceci fait, on doit créer un fichier de signature afin d'autoriser l'insertion d'un assembly dans le GAC, à moins que votre organisation vous en propose un :
[code:1]
#Création d'une signature
&\"$SDKFolder\bin\sn.exe\" -k signature.snk
[/code:1]
Je n'ai pas testé, mais il me semble que l'on peut s'affranchir de cette étape, encore faut-il savoir ce que l'on fait.
Ensuite on crée un fichier interop, notre wrapper, en utilisant l'outil TlbImp (tlb import) :
[code:1]
#Création de l'assembly à partir d'une librairie de type (COM)
&\"$SDKFolder\bin\TlbImp\" C:\WINDOWS\system32\activeds.tlb /out:C:\temp\Interop.activeds.dll /namespace:\"Interop.activeds\" /primary /keyfile:C:\temp\signature.snk /asmversion:1.0.0
[/code:1]
En ligne de commande vous noterez quelques avertissements, là où Visual Studio n'en remonte aucun.
Ceci fait on peut copier le fichier dans le GAC (Global Assembly Cache) ou le charger directement :
[code:1]
$AdsAssembly=[Reflection.Assembly]::LoadFrom(\"$pwd\Interop.activeds.dll\"«»)
[/code:1]
Reste à tester l'usage de notre Wrapper :
[code:1]
$Path= new-object Interop.ActiveDs.IADsPathname
#New-Object : Constructeur introuvable. Impossible de trouver un constructeur approprié pour le type Interop.ActiveDs.IADsPathname.
[/code:1]
Cela ne fonctionne pas car on continue de référencer le nom de l'interface COM, alors que le wrapper nous propose des classes:
[code:1]
$Classes=$AdsAssembly.GetExportedTypes()|? {$_.name -match \"class$\"}
$Classes
[/code:1]
Dans la liste on trouve une classe PathnameClass en lieu et place de IADsPathname :
[code:1]
$Path= new-object Interop.ActiveDs.PathNameClass
$Path|gm
# TypeName: Interop.activeds.PathnameClass
#
# Name MemberType Definition
# ----
# AddLeafElement Method System.Void AddLeafElement(string bstrLeafElement)
# CopyPath Method System.Object CopyPath()
# CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
# Equals Method bool Equals(System.Object obj)
# GetElement Method string GetElement(int lnElementIndex)
# GetEscapedElement Method string GetEscapedElement(int lnReserved, string bstrInStr)
# ...
[/code:1]
Reste à confirmer le résultat de la méthode GetEscapedElement:
[code:1]
$CN = 'cn=jim/morisson=test'
$Path.GetEscapedElement(0,$CN)
#cn=jim\/morisson\=test
[/code:1]
Cela me semble correspondre.
Une fois notre wrapper construit plus besoin d'utiliser InvokeMember qu'on utilisera seulement s'il n'existe aucune TLB ou si son import n'est pas possible.
Notre classe .NET est en fait une coclass , une classe Composants, qui contient toutes les interfaces supportées par un objet :
[code:1]
$CoClasse=$Classes[0]
$CoClasse.GetInterfaces()
[/code:1]
On peut s'arrêter là. PowerShell, c'est comment déjà ? Ah oui facilecoclass: A component object (an association between a class identifier (CLSID) and a set of named implementations of IUnknown) that is defined using the coclass keyword.
Mais pas COM
ps:
Reste à demander à MS un cmdlet spécialisé pour cette opération...<br><br>Message édité par: Laurent Dardenne, à: 21/03/10 10:35
Tutoriels PowerShell
Connexion ou Créer un compte pour participer à la conversation.
- Arnaud Petitjean
-
- Hors Ligne
- Modérateur
-
il y a 15 ans 11 mois #6404
par Arnaud Petitjean
MVP PowerShell et créateur de ce magnifique forum
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?
Réponse de Arnaud Petitjean sur le sujet Re:A propos du post \"COM Interop ou la galère ...\"
Salut Laurent,
Merci pour toutes ces précisions techniques. Je référencerais ton post à mon billet pour que les lecteurs aient une explication plus poussée du sujet.
Tu as très bien résumé la situation
Ce serait vraiment bien que MS nous mette ça dans la v3, je leur proposerait sur Connect.
Bon week-end,
Arnaud
Merci pour toutes ces précisions techniques. Je référencerais ton post à mon billet pour que les lecteurs aient une explication plus poussée du sujet.
On peut s'arrêter là. PowerShell, c'est comment déjà ? Ah oui facile
Mais pas COM
ps:
Reste à demander à MS un cmdlet spécialisé pour cette opération...
Tu as très bien résumé la situation
Ce serait vraiment bien que MS nous mette ça dans la v3, je leur proposerait sur Connect.
Bon week-end,
Arnaud
MVP PowerShell et créateur de ce magnifique forum
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?
Connexion ou Créer un compte pour participer à la conversation.
- Laurent Dardenne
- Auteur du sujet
- Hors Ligne
- Modérateur
-
Réduire
Plus d'informations
- Messages : 6311
- Remerciements reçus 68
il y a 15 ans 11 mois #6405
par Laurent Dardenne
Tutoriels PowerShell
Réponse de Laurent Dardenne sur le sujet Re:A propos du post \"COM Interop ou la galère ...\"
Arnaud écrit:
Sinon il reste un petit point à éclaircir sur l'instruction :
[code:1]
$objIE = New-Object -ComObject InternetExplorer.Application.1
[/code:1]
Internet Explorer étant lié aux composants du shell Windows, il est difficile de mettre en évidence la dll interop utilisée.
Si on utilise une application Office on voit que l'appel à New-Object -ComObject charge implicitement un fichier interop :
[code:1]
$before=[AppDomain]::CurrentDomain.GetAssemblies()
$ExcelIE = New-Object -ComObject Excel.Application
$After=[AppDomain]::CurrentDomain.GetAssemblies()
Compare-object $before $after -property location
# location SideIndicator
#
# C:\WINDOWS\assembly\GAC\Microsoft.Office.Interop.Excel\1... =>
[/code:1]
On comprend mieux pourquoi, avec une même instruction, dans certains cas cela fonctionne et pas dans d'autres.
Pour terminer certaines coclasses liées à IE sont dans les fichiers suivants :
[code:1]
cd \"$env:«»ProgramFiles\Microsoft.NET\Primary Interop Assemblies\"
&\"$SDKFolder\bin\ildasm.exe\" .\Microsoft.mshtml.dll
#C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\interop.shdocvw.dll
[/code:1]
Celles-ci devant être chargées explicitement, voir ce post .
Arnaud écrit:
Bonne idée, je ne l'ai pas fait, car j'ai un soucis d'indentifiant sur MSConnect.Ce serait vraiment bien que MS nous mette ça dans la v3, je leur proposerait sur Connect.
Sinon il reste un petit point à éclaircir sur l'instruction :
[code:1]
$objIE = New-Object -ComObject InternetExplorer.Application.1
[/code:1]
Internet Explorer étant lié aux composants du shell Windows, il est difficile de mettre en évidence la dll interop utilisée.
Si on utilise une application Office on voit que l'appel à New-Object -ComObject charge implicitement un fichier interop :
[code:1]
$before=[AppDomain]::CurrentDomain.GetAssemblies()
$ExcelIE = New-Object -ComObject Excel.Application
$After=[AppDomain]::CurrentDomain.GetAssemblies()
Compare-object $before $after -property location
# location SideIndicator
#
# C:\WINDOWS\assembly\GAC\Microsoft.Office.Interop.Excel\1... =>
[/code:1]
On comprend mieux pourquoi, avec une même instruction, dans certains cas cela fonctionne et pas dans d'autres.
Pour terminer certaines coclasses liées à IE sont dans les fichiers suivants :
[code:1]
cd \"$env:«»ProgramFiles\Microsoft.NET\Primary Interop Assemblies\"
&\"$SDKFolder\bin\ildasm.exe\" .\Microsoft.mshtml.dll
#C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\interop.shdocvw.dll
[/code:1]
Celles-ci devant être chargées explicitement, voir ce post .
Arnaud écrit:
Merci, à toi aussi.Bon week-end,
Tutoriels PowerShell
Connexion ou Créer un compte pour participer à la conversation.
Temps de génération de la page : 0.066 secondes
- Vous êtes ici :
-
Accueil
-
forum
-
PowerShell
-
Discussions générales
- A propos du post "COM Interop ou la galère ..."