Question Nouveau projet Codeplex : Psionic

Plus d'informations
il y a 7 ans 5 mois #14145 par Laurent Dardenne
benduru écrit:

C'est génial ça pour 'trapper'

Attention tout de même le mécanisme est spécifique à l'événement ZipError de la classe Ionic.Zip.ZipFile.

Alors pourquoi avoir procédé ainsi ?
Dans nos tout premiers tests avec Matthew, nous avons tout de suite regardé les comportements proposées par cette DLL, notamment la classe ZipFile.
Celle-ci propose de nombreux événements, la première approche a été d'utiliser des jobs PS pour s'y abonner. Malheureusement l'event ZipError, qui renvoie des données, ne semble pas fonctionner avec les job PS liée à un abonnement d'événement.
Enfin nous n'y sommes pas arrivé. Première attitude, on passe par la porte d'entrée, ça ne fonctionne pas, on insiste. Certes, mais quand cela ne fonctionne pas, ça ne fonctionne pas !
Soit.
Seconde attitude, on essai de passer par la fenêtre. Celle-ci étant du code C#. Ceci dit, il reste toujours un doute sur notre approche...

Le principe de la 'fenêtre':
Un event est un délégué (Delegate), un pointeur de méthode donc.
Powershell ne permet pas nativement de s'abonner à ce type de delégué( sauf via un appel à Register-ObjectEvent), par contre en C# une seule ligne de code le permet :
[code:1]
Zip.ZipError += this.PSIonicZipErrorHandler;
[/code:1]
On a donc crée une 'classe porteuse' afin de disposer d'une méthode C# a appeler lors du déclenchement de l'événement ZipError.
Cette méthode doit pouvoir accéder au contexte d'exécution de Powershell, en gros l'environnement d'exécution de la console PS.
Ici on parle de la console texte, pas d'ISE ou de PowerGUI, nous n'avons pas encore testé sous ces environnements :/
L'accès au contexte d'exécution se fait via une variable automatique $ExecutionContext .

Avant d'accéder à ce context on doit le 'passer' à notre 'classe porteuse' puisque c'est elle qui doit y afficher qq chose.
On utilisera donc le constructeur de la classe :
[code:1]
// issu du code C#
public class PSZipError
{
private EngineIntrinsics ExecutionContext;

public PSZipError(EngineIntrinsics Context)
[/code:1]
On passe ainsi lors de la construction d'une instance de notre classe, le contexte d'exécution de PS qui est ciblé par l'événement :
[code:1]
#issu du code Powershell
$psZipErrorHandler=New-Object PSIonicTools.PSZipError($Context)
[/code:1]
Etant dans un module, on doit pointer sur le contexte de la session appelante et pas sur celui du module :
[code:1]
#issu du code Powershell
$Context=$PSCmdlet.SessionState.PSVariable.Get(\"ExecutionContext\"«»).Value
[/code:1]
Voir ce tutoriel sur les modules.
Cette variable $context pointe sur des méthodes d'affichage du host Powershell :
[code:1]
// issu du code C#
response = ExecutionContext.Host.UI.PromptForChoice
[/code:1]
Ainsi, de fil en aiguille, on configure les différents éléments nécessaires à la gestion de l'événement ZipError.

Et puisqu'on a précédemennt parlé de console texte, et que l'événement manipulé attend une réponse utilisateur, on contrôle son contexte d'exécution :
[code:1]
// issu du code C#
if (Context.Host.Name == \"ServerRemoteHost\"«»)
[/code:1]
On se prémuni d'une création d'instance dans le contexte d'exécution d'un job (local ou remote). Reste à tester si ce n'est pas aussi lors de l'exécution de l'event qu'il faille contrôler ce contexte...

Enfin, on essai tant que faire se peut de libérer ce qu'on a crée, d'ou la présence de méthode interne AddMethodPSDispose.
Mais, pour ceux qui sont encore en train me lire, ce sera pour la prochaine fois ;-)

benduru écrit:

Je vais me servir de ce module pour le taff, vous aurez des retours

C'est aussi ce que je compte faire, c'était l'objectif.
Attend encore une ou deux livraisons du module, celle en cours est déjà obsoléte, car buggée :-)
Les prochaines devraient disposer de la documentation, au moins en Fr. L'ensemble est stable, mais la couverture de test manque de profondeur.
On a donc fait le choix de livrer en l'état.

Il y a également un projet imbriqué qui concerne l'automatisation d'une livraison, tu devrais regarder.
Il faut un peu de temps pour aborder les outils utilisés, mais dans ce ce que tu fais, tu y trouveras matière à réflexions je pense. Même si ton métier n'est pas le développement.<br><br>Message édité par: Laurent Dardenne, à: 27/02/13 22:34

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 7 ans 4 mois #14333 par Laurent Dardenne
Nouvelle version 0.2.

Correction de bugs, tests des fonctions de base, ajout de la documentation Fr.

Cette version permet de créer dans une archive des entrées à partir d'une hastable et d'y insérer des objets sérialisés :
[code:1]
#Ajout
$H=@{
MyText=([string]$Text=Get-Content C:\Temp\Test.ps1|Out-String); #string à partir d'un fichier texte
'PSVersiontable.climxl'=(ConvertTo-CliXml $PSVersionTable); #string, objet sérialisé
File1=\&quot;G:\PS\psionic\Revisions.txt\&quot;; #string
File2=(get-item \&quot;G:\PS\psionic\Revisions.txt\&quot;«»); #fichier
}

$Hgen=New-Object \&quot;System.Collections.Generic.Dictionary``2System.String],[System.String\&quot;
$Hgen.Add(\&quot;Clés1\&quot;, \&quot;texte1\&quot;«»)
$Hgen.Add(\&quot;Clés2\&quot;, \&quot;texte2\&quot;«»)

$ZipFile=Get-Zipfile -Name C:\Temp\Test.zip
Add-ZipEntry -Object $h -ZipFile $ZipFile
Add-ZipEntry -Object $hgen -ZipFile $ZipFile
$ZipFile.Close()

#Extraction
$ZipFile=Get-Zipfile -Name C:\Temp\Test.zip
$MaTableDeVersion=Expand-Entry -Zip $ZipFile 'PSVersiontable.climxl'|ConvertFrom-CliXml
$CodePS=Expand-Entry -Zip $ZipFile MyText
$ZipFile.Close()

$MaTableDeVersion
$CodePS

#Extraction d'une liste d'entrées vers une hashtable
$ZipFile=Get-Zipfile -Name C:\Temp\Test.zip
$HashTable= $H.Keys|Expand-Entry -Zip $ZipFile -AsHashtable
$ZipFile.Close()
[/code:1]

Message édité par: Laurent Dardenne, à: 18/03/13 00:32<br><br>Message édité par: Laurent Dardenne, à: 23/01/14 19:54

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 7 ans 4 mois #14493 par Laurent Dardenne
Dans un post précédent je vous parlais de libérer les ressources que l'on a créées, d'où la présence de la méthode interne AddMethodPSDispose.

Pour resituer le contexte, la fonction SetZipErrorHandler abonne une instance de la classe ZipFile à un événement déclaré dans une autre classe.

Une fois l'instance inutilisée on doit libérer le pointeur sur le délégué, mais comme la classe ZipFile ne le fait pas, la charge en revient à celui qui l'a créé, ici l'utilisateur.

Chaque création d'instance de la classe ZipFile, à partir d'un fichier existant, verrouille le fichier en exclusif. On doit donc en fin de traitement le déverrouiller en appelant sa méthode Dispose().
Si on ne le fait pas le fichier reste verrouillé tant que la session PS existe.

Tant que l'on manipule une instance de la classe ZipFile dans une fonction, on sait mémoriser ce que l'on a créé et le supprimer, mais dès que l'on va émettre cette instance hors de la fonction ( -passthru), les variables pointant sur les ressources à libérer ne seront plus accessibles.

La première approche était de redéfinir via du code Powershell, la méthode Dispose() de chaque instance créée, mais en V2 le système d'extension de type pose un problème, seul le premier appel fonctionne :
[code:1]
ipmo .\PsIonic.psd1

$z=[Ionic.Zip.ZipFile]::Read('c:\temp\test.zip')
$z=$z -as [psobject]

#Appel PSbase.Dispose pour éviter un appel résursif fatal.
Add-Member -Inputobject $Z -Force ScriptMethod Dispose {\&quot;ZIP ok\&quot;;$this.PSBase.Dispose()}
trace-command MemberResolution,ets {$Z.Dispose()} -pshost
[/code:1]
En v3 ce code fonctionne. C'est récurrent lors du codage avancé sous Powershell, on doit d'abord résoudre ce type de pb avant d'aller plus avant.

Puisque le code doit être commun aux versions 2 et 3 de PS et que le nom de la méthode Dispose pose problème en V2, la solution est d'utiliser un autre nom, ici PSDispose() :
[code:1]
$z=$null
rv z
$z=[Ionic.Zip.ZipFile]::Read('c:\temp\test.zip')
$z=$z -as [psobject]

Add-Member -Inputobject $Z -Force ScriptMethod PSDispose {\&quot;ZIP ok\&quot;;$this.Dispose()}
trace-command MemberResolution,ets {$Z.PSDispose()} -pshost
[/code:1]
ETS fonctionne seulement sur les objets de type PSObject, comme PS retarde l'adaptation d'un objet dotNet en PSObject, on doit vérifier en interne quelle méthode de libération appeler :
[code:1]
function DisposeZip{
#Code commun de libération d'une instance ZipFile
if ($ZipFile -ne $null)
{
$Logger.Debug(\&quot;`t DisposeZip $($ZipFile.Name)\&quot;«») #&lt;%REMOVE%&gt;
if ( @($ZipFile.psobject.Members.Match('PSDispose','ScriptMethod')).Count -ne 0)
{ $ZipFile.PSDispose() }
else
{
$Logger.Debug(\&quot;`t $($ZipFile.Name) dont contains PSDispose method\&quot;«») #&lt;%REMOVE%&gt;
$ZipFile.Dispose()
}
$ZipFile=$null
}
}# DisposeZip
[/code:1]
Ainsi le système d'extension de type (ETS) fonctionne. Un problème de réglé.

Comme on utilisera plusieurs fonctions nécessitant de déclarer cette méthode, on crée une fonction interne AddMethodPSDispose que l'on appelera à chaque fois qu'on créera une instance de la classe ZipFile.

Cette fonction interne libère le TextWriter s'il existe (-Verbose) et s'il est de la classe PSIonicTools.PSVerboseTextWriter, sinon, comme on en connait pas l'usage, ce sera à l'utilisateur de s'occuper de cette libération.

Ensuite il \&quot;reste\&quot; à supprimer la référence du délégué qui n'est plus accessible, pour cela on utilise le système de réflexion de Dotnet, qui permet de récupérer les informations nécessaires à la suppression de l'abonnement :
[code:1]
#Récupère le type de l'instance
$MyType=$this.GetType()
#Récupère l'événement ZipError
$Event=$MyType.GetEvent('ZipError')
#Un event à un délégué privé
$bindingFlags = [Reflection.BindingFlags]\&quot;GetField,NonPublic,Instance\&quot;
#On récupère la valeur du délégué privé
$EventField = $MyType.GetField('ZipError',$bindingFlags)
$ZipErrorDeleguate=$EventField.GetValue($this)
if ($ZipErrorDeleguate -ne $null)
{
Write-Debug(\&quot;`t Dispose delegates\&quot;«») #&lt;%REMOVE%&gt;
#Récupère la liste des 'méthodes' à appeler
# Au moins un, peut être de notre type : $_.Target -is [PSIonicTools.PSZipError]
$ZipErrorDeleguate.GetInvocationList()|
Foreach {
Write-Debug(\&quot;`t Dispose $_\&quot;«») #&lt;%REMOVE%&gt;
#On supprime tous les abonnements
$Event.RemoveEventHandler($this,$_)
}
}
[/code:1]
Ce code est similaire à une suppression d'abonnement de délégué en C# qui lui propose pour cette opération l'opérateur ' -= '.

On peut très bien laisser à Powershell le soin de supprimer toutes les ressources lors de la fermeture de la session, mais dans notre cas nous préférons laisser l'OS dans l'état où nous l'avons trouvé.

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 7 ans 2 mois #14953 par Laurent Dardenne
Sur ce projet, étant donné que nous ciblions deux versions différentes de Powersehll, chacune utilisant une version du framework Dotnet spécifique, nous avons du proposer deux dll distinctes :
Une pour la framework 2.0, l'autre pour la 4.0.

Avec la version 2 de PS, j'utilisais précédemment l'appel au compilateur CSharp CSC.exe. La présence du SDK suffisait.
Devant utiliser 2 versions du compilateur cela c'est avéré plus délicat à mettre en œuvre.
Notamment sur la gestion des différents chemins d'accès au SDK. Après qq essais laborieux, j'ai essayé une autre approche basée sur le cmdlet Add-type qui n'est pas spécialement documenté.
Ce que l'on peut comprendre, car le public ciblé par Powershell n'est pas celui des développeurs. Mais comme il est conçu et réalisé par des développeurs, il nous propose l'usage suivant du cmdlet Add-Type.

Le paramètre CompilerParameters ajoute des directives de compilation lors de la création d'une DLL externe.
Pour notre projet nous comptions proposer une version de Debug et une version Release ce qu'autorise la création d'une instance du type System.CodeDom.Compiler.CompilerParameters, comme dans l'exemple suivant :
[code:1]
#Compile la dll psionic

$Files=@(
\&quot;$PsIonicBin\PSIonicTools.cs\&quot;,
\&quot;$PsIonicBin\AssemblyInfo.cs\&quot;
)

$cp = New-Object System.CodeDom.Compiler.CompilerParameters
$cp.IncludeDebugInformation = $Configuration -eq \&quot;Debug\&quot;
$cp.GenerateInMemory=$false

$cp.ReferencedAssemblies.Add(\&quot;$PsIonicBin\${Configuration}\Ionic.Zip.dll\&quot;«») &gt; $null
#Pointe sur la version adéquate de System.Management.Automation.dll
$cp.ReferencedAssemblies.Add([PSObject].Assembly.Location) &gt;$null
$cp.OutputAssembly=\&quot;$PsIonicLivraison\$PSVersion\PSIonicTools.dll\&quot;
#see to msdn.microsoft.com/en-us/library/6ds95cz0(v=vs.80).aspx
Add-Type -Path $Files -CompilerParameters $cp
Write-Host \&quot;Compilation réussie de $($cp.OutputAssembly) version $PSVersion\&quot;
[/code:1]

Ce code est imbriqué dans une tâche PSAKE, les informations référencées sont :

$Files : Contient les assemblies à compiler.
$PsIonicBin : Le répertoire de destination des binaires utilisés lors de la construction de la livraison (+- tous les fichiers du setup)
$Configuration : paramètre de compilation CSharp, contient \&quot;Debug\&quot; ou \&quot;Release\&quot; (arguments de la tâche)
$PSversion : 2.0 ou 3.0, issu de $PSversionTable

Notez, l'astuce du référencement de la DLL System.Management.Automation.dll spécifique au runtime dotnet utilisé par chaque version du host Powershell :
[code:1]$cp.ReferencedAssemblies.Add([PSObject].Assembly.Location)
[/code:1]
Cette approche permet également de résoudre un possible problème liè au cmdlet Add-Type.

Powershell ?
Ah oui, le truc de scripteur...

ps
Ce code a permit également de mettre en évidence une limite portée par un manifeste de module indiqué ici .
Dommage :whistle:<br><br>Message édité par: Laurent Dardenne, à: 27/05/13 19:45

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 6 ans 6 mois #16779 par Laurent Dardenne
Nouvelle version 0.3.


Le module ne contient plus de code lié à Log4Posh qui utilise la nouvelle version 1.2.12

Génération de l'aide XMl via HelpsAddon.ps1 (uniquement Fr pour le moment).

Ajout du 'delayed script block binding' sur la fonction Expand-ZipFile, paramètre -Destination.
Ce qui permet pour chaque fichier .Zip reçu, de l'extraire dans un répertoire différent.
Par exemple, pour trois fichier .zip 'C:\Temp\toDelete[1-3].zip' contenant chacun un fichier texte 'toDeleteN.txt', l'appel suivant :
[code:1]
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;C:\Temp\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
#-Verbose est géré via une classe du projet IOnic.
[/code:1]
affiche :

COMMENTAIRES : reading from C:\Temp\toDelete1.zip...
COMMENTAIRES : entry toDelete1.txt
COMMENTAIRES : read in 1 entries.
COMMENTAIRES :
Modified Size Ratio Packed Name
COMMENTAIRES :


COMMENTAIRES : 2014-01-13 19:28:48 0 0% 0 toDelete1.txt
COMMENTAIRES : extract file C:\\\\Temp\\\\TestZip\\\\toDelete1\\\\toDelete1.txt...
...
COMMENTAIRES : extract file C:\\\\Temp\\\\TestZip\\\\toDelete2\\\\toDelete2.txt...
...
COMMENTAIRES : extract file C:\\\\Temp\\\\TestZip\\\\toDelete3\\\\toDelete3.txt...


Les chemins UNC sont supportés :
[code:1]
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;FileSystem::\\localhost\c$\Temp\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
#COMMENTAIRES : extract file \\localhost\c$\Temp\TestZip\toDelete1\toDelete1.txt...
[/code:1]
Les chemins relatifs également :
[code:1]
cd C:\Windows
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;..\Temp\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
#COMMENTAIRES : extract file C:\Temp\TestZip\toDelete3\toDelete3.txt...
[/code:1]
La localisation courante est gérée :
[code:1]
cd C:\Temp
cd HKLM:
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;..\Temp\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
[/code:1]
Ici la résolution du chemin pointe sur le provider de la registry et génère une erreur, le nom de chemin est explicité :

Expand-ZipFile : Chemin invalide pour le provider FileSystem : Registry::HKEY_LOCAL_MACHINE\\\\Temp\\\\TestZip\\\\toDelete1
Expand-ZipFile : Chemin invalide pour le provider FileSystem : Registry::HKEY_LOCAL_MACHINE\\\\Temp\\\\TestZip\\\\toDelete2
Expand-ZipFile : Chemin invalide pour le provider FileSystem : Registry::HKEY_LOCAL_MACHINE\\\\Temp\\\\TestZip\\\\toDelete3

L'usage de chemins UNC depuis un provider autre que le FileSystem est supporté :
[code:1]
cd HKLM:
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;\\localhost\c$\Temp\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
COMMENTAIRES : extract file \\localhost\c$\Temp\TestZip\toDelete1\toDelete1.txt...
[/code:1]
On peut afficher les logs de log4net dans la console :
[code:1]
Start-ConsoleAppender
$DebugPreference='Continue'
cd c:\Temp:
#chemin impossible pour une extraction
$F='C:\temp\*'
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;$F\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently
#Expand-ZipFile : Chemin invalide pour le provider FileSystem : C:\temp\*\toDelete1
#etc

Stop-ConsoleAppender #On peut aussi les stopper
$DebugPreference='SilentlyContinue'
[/code:1]
Certaines fonctions étant utilisées dans d'autres contextes, des traces via Write-Debug sont également disponible.

Les drives pointant sur le FileSystem sont résolut en nom de répertoire Win32 :
[code:1]
$null=New-PsDrive -Scope Global -Name 'PsIonicTest' -PSProvider FileSystem -Root 'C:\Temp\'
Dir 'C:\Temp\toDelete?.zip'|
Expand-ZipFile -Destination {\&quot;PsIonicTest:\TestZip\$($_.BaseName)\&quot;} -Create -ExtractAction OverwriteSilently -Verbose
#COMMENTAIRES : the file C:\Temp\TestZip\toDelete1\toDelete1.txt exists; will overwrite it...
...
[/code:1]
Une dernière chose, ici toutes les extractions se font dans le répertoire courant :
[code:1]
'C:\Temp\toDelete?.zip'|Expand-ZipFile -Destination {\&quot;C:\Temp\TestZip\$($_.BaseName)\&quot;} -Create
[/code:1]
Ceci est dû au fait que la valeur émise est une chaîne de caractères, et pas un objet fichier, dans ce cas la fonction tente de résoudre les noms de fichiers au sein d'une boucle.
Ce qui implique que l'exécution du 'delayed script block binding' n'est déclenché qu'une seule fois, puisque la fonction ne reçoit qu'UN seul objet.

A suivre...

Message édité par: Laurent Dardenne, à: 17/01/14 18:28<br><br>Message édité par: Laurent Dardenne, à: 23/01/14 19:53

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 6 ans 6 mois #16813 par Laurent Dardenne
Nouvelle version 0.4.

Ajout de barre de progression sur les opérations de lecture et d'extraction.

Permet ceci :
[code:1]
$Filename='C:\Temp\FichierDeTest.zip'

$ZipFile=dir $filename|
Expand-ZipFile -OutputPath 'C:\Temp\TestZip\query' -create -ExtractAction OverwriteSilently -query 'name = *.txt' -Passthru -Progress 1

$ZipFile
$ZipFile.PsDispose()
[/code:1]
Le paramétre ProgressID autorise l'imbrication de barre de progression :
[code:1]
1..10|
Foreach -begin { $id=1 } -process {
[int]$PCpercent =(($_ / 10 ) * 100)

Write-Progress -id $id -Activity \&quot;Computer : \&quot; -Status \&quot;Serveur$_\&quot; -PercentComplete $PCpercent
Dir \&quot;*.zip\&quot;|
Expand-ZipFile -OutputPath 'C:\Temp\TestZip' -Create -ExtractAction OverwriteSilently -ProgressID 2
}#for
[/code:1]
L'usage de barre de progression dans un job reste possible, bien que l'information soit périmée une fois celui-ci terminé.

L'implicite remoting devrait à terme fonctionner, pour le moment la dll declenchera une exception. L'objectif étant d'interdire ce comportement pour les hosts qui n'implémenteraient pas de type d'affichage, par exemple celui d'Orchestrator 2012 ou Opalis.<br><br>Message édité par: Laurent Dardenne, à: 23/01/14 20:07

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.277 secondes
Propulsé par Kunena