2 votes

Problème pour parcourir les fichiers en boucle (à l'aide de Powershell) et les déplacer un par un, en fonction du résultat de l'erreur.

J'utilise Powershell pour valider plusieurs fichiers XML par rapport à plusieurs XSD. Cette partie du code fonctionne comme prévu, mais je dois également déplacer tout fichier XML qui ne parvient pas à être validé vers un dossier "Invalide". J'essaie de parcourir ces fichiers en boucle à l'aide de ForEach, puis, à l'aide d'une instruction If, de déplacer tout fichier ayant échoué. Le problème est que tous les fichiers sont déplacés, qu'ils aient échoué ou non.

J'ai écrit cette boucle de toutes les manières possibles et imaginables, mais je n'obtiens pas le résultat escompté. (J'ai également parcouru le Web pendant des jours pour trouver la réponse.) J'ai besoin que ForEach applique le code à chaque fichier, un par un. S'agit-il d'un problème de syntaxe ? Peut-être que quelque chose de très évident m'échappe, mais je ne sais plus où j'en suis.

J'utilise cette fonction (trouvée sur Stack Overflow, et comme indiqué ici, légèrement modifiée) pour valider les XML.

function Test-XmlFile
{
    <#
    .Synopsis
        Validates an xml file against an xml schema file.
    .Example
        PS> dir *.xml | Test-XmlFile schema.xsd
    #>
    [CmdletBinding()]
    param (     
        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [SupportsWildcards()]
        $SchemaFile,

        [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [SupportsWildcards()]
        [alias('Fullname')]
        $XmlFile,

        [scriptblock] $ValidationEventHandler = { Write-Error $args[1].Exception }
    )

    begin {
        $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile
        $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler)
    }

    process {
        $ret = $true
        try {
            $xml = New-Object System.Xml.XmlDocument
            $xml.Schemas.Add($schema) | Out-Null
            $xml.Load($XmlFile)
            $xml.Validate({
                    throw ([PsCustomObject] @{
                        SchemaFile = $SchemaFile
                        XmlFile = $XmlFile
                        Exception = $args[1].Exception
                    })
                })
        } catch {
            Write-Error $_
            $ret = $false
        }
        $ret
    }

    end {
        $schemaReader.Close()
    }
}

Et voici comment je sélectionne les XML pour les valider par rapport à leurs schémas respectifs.

$allfiles = "..\Schema Validation\XMLs\*.xml"

$xml1 = Get-ChildItem $allfiles -Recurse | Select-String "<UniqueElement>" -List | Resolve-Path
$xml2 = Get-ChildItem $allfiles -Recurse | Select-String "<UniqueElement>" -List | Resolve-Path

$xsd1 = "..\Schema Validation\Schemas\Schema1.xsd"
$xsd2 = "..\Schema Validation\Schemas\Schema2.xsd"

Et voici la boucle ForEach qui ne fonctionne pas pour moi. (Dans sa configuration actuelle, bien que je l'aie écrite d'une douzaine de façons différentes).

ForEach ($xml in $xml1) {
$xml | Test-XmlFile $xsd1
If ($Error) {
$Error[0].Exception, "`r" | Out-File "..\Schema Validation\Results\log.txt"
Move-Item $xml -Destination "..\Schema Validation\Invalid"
}}

La boucle ForEach ci-dessus est également répétée pour les variables $xml2 et $xsd2. (Et comme vous pouvez le voir dans le fichier de sortie, je capture également le message d'exception dans un fichier texte pour une sorte de journal).

Je m'attendais à ce que seuls les XML présentant une erreur et une exception soient déplacés, en raison de l'instruction " If ($Error) " et du fait que j'essaie de parcourir les fichiers un par un ; or, ce qui se passe, c'est que tout XML contenant la chaîne unique qui l'identifie comme faisant partie du groupe $xml1 ou $xml2 est déplacé vers le dossier Invalid, erreur ou pas. Alors quelle est la chose douloureusement évidente que j'ai manquée ? (Incidemment, le texte de l'exception alimente le journal des erreurs comme prévu, donc au moins cette partie fonctionne comme je l'espérais).

EDIT : En y réfléchissant, je ne devrais pas dire que le texte d'exception remplit le journal "comme prévu". Il remplit le journal, mais il écrit le message dans le fichier journal une fois pour chaque fichier inclus dans la variable (chaque fichier dans $xml1, par exemple), que le fichier ait erré ou non. Ainsi, s'il y a deux fichiers dans $xml1, mais qu'un seul est invalide, le message d'exception unique pour ce seul fichier invalide sera écrit deux fois dans le journal. Donc, il écrit quelque chose pour chaque fichier qui est bouclé, indépendamment de la validité ou des erreurs. J'espère que cela a du sens.

2voto

arco444 Points 16314

$Error vous donne une liste des dernier il n'est pas effacé si l'opération précédente a réussi - il contiendra toujours les dernières erreurs rencontrées. Ainsi, chaque fichier sera copié dans le répertoire 'invalid' après la première erreur.

Vous disposez déjà d'une gestion des erreurs dans votre cmdlet, vous pourriez donc modifier ce code pour qu'il gère également le déplacement du fichier vers l'autre répertoire.

0voto

mklement0 Points 12597

Votre Test-XmlFile produit un booléen indiquant l'état de réussite de la validation, je suggère donc d'utiliser directement cette fonction pour déterminer la réussite ou l'échec de la validation ; en outre, pour saisir les détails de l'échec de la validation, utilisez la fonction -ErrorVariable paramètre commun :

ForEach ($xml in $xml1) {
  if (-not (Test-XmlFile -XmlFile $xml -SchemaFile $xsd1 -ErrorVariable err)) {
    $err.Exception, "`n" | Out-File -Append "..\Schema Validation\Results\log.txt"
    Move-Item $xml -Destination "..\Schema Validation\Invalid"
  }
}

Notez que j'ai ajouté -Append à votre Out-File pour s'assurer que les échecs multiples ne s'écrasent pas les uns sur les autres dans le fichier journal. De plus, il est préférable d'utiliser "`n" o "`r`n" ou, de manière appropriée à la plate-forme, [Environment]::NewLine] pour créer une nouvelle ligne (saut de ligne).


Quant à ce que vous avez essayé :

L'automatique $Error est une collection (de type [System.Collections.ArrayList] ) de tous les erreurs qui se sont produites dans le session actuelle par erreur chronologique inverse.

Ainsi, si vous utilisez if ($Error) vous demandez en fait si au moins une erreur s'est produite. dans la session jusqu'à présent, et non pas si la dernière commande exécutée a signalé des erreurs (dont la dernière serait reflétée dans $Error[0] ), car $Error dans un contexte booléen, évalue à $true une fois que la collection a au moins une entrée.

Il y a aussi l'automatique $? qui est un booléen indiquant si la dernière commande exécutée a signalé une erreur (au moins une erreur) - malheureusement, ce comportement est entaché d'incohérences (toute expression réinitialise $? à $true et Write-Error hace no set $? à $false contrairement à cmdlets compilés ), il est donc préférable de s'appuyer sur la valeur de retour booléenne explicite de l'option Test-XmlFile dans votre cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X