Question [Fonction] Validation de règles de gestion

Plus d'informations
il y a 10 ans 9 mois #15268 par Laurent Dardenne
Voici une fonction de validation de règles de gestion portant sur un objet 'métier' ( groupe Ad, mailbox Exchange, fichier CSV,...).

Voir la documentation en ligne pour l'usage :
[code:1]
function Test-BusinessRules {
<#
.SYNOPSIS
Valide des règles de gestion sur un objet.

.DESCRIPTION
Cette fonction utilise une hashtable dont le contenu porte des règles de
gestion dédiées à une structure d'objet.
Valide des règles de gestion sur un objet de type PSObject.

.PARAMETER InputObject
Objet sur lequel appliquer des règles de gestion.

.PARAMETER Rules
Hashtable contenant des règles de gestion à appliquer sur l'objet courant.
Celles-ci permettent de valider le contenu d'une ou plusieurs propriétés de l'objet
référencé par le paramètre InputObject.
.
Sont autorisées pour ce paramètre toutes les instances de classe implémentant
l'interface [System.Collections.IDictionary].
.
La classe [System.Collections.Specialized.OrderedDictionary] définie une hashtable
respectant l'ordre d'insertion. Celle-ci permet, si besoin, d'ordonner vos règles de gestion.
.
Chaque entrée de la hashtable contenant une régle de gestion doit respecter
la structure suivante :
° Le nom d'une entrée est le nom de la régle de gestion à valider
° La valeur de cette entrée est une hashtable, les clés requises sont :
-- ErrorMessage Contient le message d'erreur à afficher, en cas d'invalidation
de la régle de gestion portée par l'entrée Code.
Cette clé est obligatoire.
Son type est [String].
.
-- Code Contient le code de validation du contenu d'une propriété.
Cette validation doit renvoyer la valeur $true ou $false.
Aucune contrôle n'est effectué sur la validité du code.
Cette clé est obligatoire.
Son type est [ScriptBlock].
.
Si cette entrée n'existe pas la fonction déclenchera
une exception :
« La référence d'objet n'est pas définie à une instance d'un objet. »
.
Note :
Pour chaque objet à valider, on lui appliquera toutes les règles de gestion
contenues dans la hashtable.

.PARAMETER Action
Contient le code à exécuter en cas d'invalidation d'une régle de gestion.
Par défaut en cas d'erreur, l'action est le résultat de l'exécution du bloc de
script suivant :
.
{ Write-Error \"Erreur de validation pour la régle : [$($_.Key)] $($_.Value.ErrorMessage)\" }
.
Où '$_.Key' représente le nom de la régle en cours d'exécution, et où $_.Value
référence la hashtable imbriquée @{ ErrorMessage=\"...\" ; Code={} }.

.PARAMETER ArgumentList
Contient les valeurs des variables déclarées dans la portée de l'appelant et
utilisées dans le scriptblock Action.

.PARAMETER RemainingVariable
Nom de la variable contenant la collection des objets invalidés par les règles
de gestion. La variable est créée automatiquement dans la portée de l'appelant.

Par défaut, la variable est créée à chaque appel. Pour compléter la collection,
ajoutez un signe plus (+) avant le nom de la variable.

Par exemple, la commande suivante crée la variable $Invalide :

Test-BusinessRules -InputObject $Donnees -Rules $Regles -RemainingVariable Invalide

La commande suivante ajoute des objets dans la variable $Invalide existante
(ou la crée si elle n'existe pas):

Test-BusinessRules -InputObject $Donnees -Rules $Regles -RemainingVariable +Invalide

.PARAMETER Clone
Permet de cloner l'objet contenu dans $InputObject avant l'exécution du
scriptblock $Action.
Vous pouvez modifier la structure de l'objet $InputObject dans le code du
scriptblock $Action, dans le cas où vous souhaiteriez disposer de deux collections
d'objets distinctes, utilisez ce paramètre.
.
ATTENTION l'opération de copie n'est pas une copie profonde de l'objet (deep-copy),
assurez vous d'utilisez uniquement ce paramètre sur des objets de type PSCustomObject.

.PARAMETER Passthru
En cas de réussite du test, émet l'objet validé dans le pipeline.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

if (Test-BusinessRules -InputObject $Donnees[0] -Rules $Rules)
{ Write-Host \"L'objet est valide.\" }
else
{ Write-Host \"L'objet est invalide.\" }
.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion et
un tableau regroupant deux objets à valider.
.
On valide un seul objet, l'appel de la fonction renvoie $false et génère
une erreur non-bloquante, car l'objet est invalide.
Le message \"L'objet est invalide.\" est affiché sur la console.
.
Notez que le scriptblock porté par l'entrée de la hastable Code, référence
le paramètre $InputObject de la fonction Test-BusinessRules.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$Valide=Test-BusinessRules -InputObject $Donnees[1] -Rules $Rules -Passthru
if ($Valide -ne $null)
{ Write-Host \"L'objet est valide.\" }
else
{ Write-Host \"L'objet est invalide.\" }
.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion et
un tableau regroupant deux objets à valider.
.
On valide un seul objet, l'appel de la fonction renvoie l'objet $Donnees[1]
qui est valide et l'affecte à la variable $Valide.
Le message \"L'objet est valide.\" est affiché sur la console.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$Valides=$Donnees|Test-BusinessRules -Rules $Rules -Passthru
if ($Valides.Count -eq $Donnees.Count)
{ Write-Host \"Tous les objets sont valides.\" }
else
{ Write-Host \"Certains objets sont invalides.\" }
.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion et
un tableau regroupant deux objets à valider.
.
On valide une collection d'objet, l'appel de la fonction renvoie une collection
d'objets valides que l'on affecte à la variable $Valides, et génère une
erreur non-bloquante pour l'objet invalide.
Le message \"Certains objets sont invalides.\" est affiché sur la console.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$Valides=$Donnees|Test-BusinessRules -Rules $Rules -Passthru -RemainingVariable +Invalides
Write-Host \"Objets valides : $Valides\"
Write-Host \"Objets invalides : $($Invalides)\"
.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion et
un tableau regroupant deux objets à valider.
.
On valide une collection d'objet, l'appel de la fonction renvoie une collection
d'objets valides que l'on affecte à la variable $Valides, et génère une
erreur non-bloquante pour l'objet invalide.
Les objets invalidés par les règles de gestion sont accessibles via la variable
automatique nommée $Invalides.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$path='C:\temp'
$logFile=\"$path\Erreurs.log\"
$DatasFile=\"$path\Datas.txt\"

$TraitementErreur={
$logFile=$ArgumentList[0]
$DatasFile=$ArgumentList[1]

\"Erreur de validation pour la régle : $($_.Key)\" >> $logFile
$InputObject >> $DatasFile
}

Remove-Item $logfile, $DatasFile -force -ErrorAction SilentlyContinue

$Donnees|Test-BusinessRules -Rules $Rules -Action $TraitementErreur -ArgumentList $logFile,$DatasFile

Type $logfile,$DatasFile

.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion,
un tableau regroupant deux objets à valider, les noms de deux fichiers
ainsi qu'un scriptblock.
.
On valide une collection d'objet en modifiant le code de gestion d'erreur
des objets invalides. Comme le code du scriptblock $TraitementErreur référence
des noms de fichiers déclarés dans la portée de l'appelant, on doit utiliser
le paramètre $ArgumentList pour propager ces noms de fichiers.
.
Le scriptblock $TraitementErreur crée deux fichiers, le premier contient
les messages d'erreur et le second la représentation textuelle des objets
en erreurs.
Ainsi aucune erreur non-bloquante n'est générée.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$TraitementErreur={
$Result=$InputObject.PSObject.Properties.Match('Erreurs')
$Texte=\"[$($_.Key)] $($_.Value.ErrorMessage)`r`n\"
Write-Warning $Texte
if ($result.Count -eq 0)
{
#Le membre supplémentaire n'existe pas on le crée
$InputObject|
Add-Member -membertype Noteproperty `
-name Erreurs `
-value $Texte
}
Else #Le membre supplémentaire existe, on complète son contenu
{ $InputObject.Erreurs +=$Texte }
}

$Valides=Test-BusinessRules -InputObject $Donnees[0] -Rules $Rules -Action $TraitementErreur -RemainingVariable Invalides -Passthru
$Invalides[0]
$Donnees[0]

$Donnees[0]= New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'}

$Valides=Test-BusinessRules -InputObject $Donnees[0] -Rules $Rules -Action $TraitementErreur -RemainingVariable Invalides -Passthru -Clone
$Invalides[0]
$Donnees[0]

Remove-Variable
$Valides=$Donnees|Test-BusinessRules -Rules $Rules -Action $TraitementErreur -RemainingVariable +Invalides -Clone -Passthru
($Valides.Count + $Invalides.Count) -eq $Donnees.count
.
Description
Ces commandes déclarent une hashtable contenant une régle de gestion,
un tableau regroupant deux objets à valider et un scriptblock.
.
On valide une collection d'objet en modifiant le code de gestion d'erreur
des objets invalides. Le code du scriptblock $TraitementErreur ajoute
à chaque objet invalide un membre nommé 'Erreurs'.
Celui-ci contiendra tous les messages d'erreur générés lors de l'exécution
des règles de validation.
.
Le premier appel de la fonction modifie l'objet d'origine, car l'objet
de la collection $Invalides pointe sur la même référence d'objet.
.
Le second appel de la fonction utilise le paramètre -Clone qui force la
copie de l'objet d'origine, ainsi la collection $Invalides contient des
objets distincts.
.
Le troisième appel de la fonction construit les collections $Valides et $Invalides
contenant respectivement les objets valides et ceux invalides.

.EXAMPLE
$Rules=@{
NoSpaceInGroupName=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
Erreur1=@{
Code={ -not ($InputObject.Groupe.Contains(\" \"«»)) }
}
Erreur2=@{
ErrorMessage=\"Le contenu de la propriété Groupe contient un caractère espace interdit.\"
}
Erreur3=@{
}
}

$Donnees=@(
(New-Object PSCustomObject -Property @{Nom='Test';Groupe='GRP-00 -USR-TEST'})
(New-Object PSCustomObject -Property @{Nom='Test2';Groupe='GRP-00-USR-TEST'})
)

$Validation=@{
ErrorMessageKey=@{
ErrorMessage=\"La hashtable ne contient de clé nommée ErrorMessage.\"
Code={ $InputObject.Value.Contains('ErrorMessage') }
}
CodeKey=@{
ErrorMessage=\"La hashtable ne contient de clé nommée Code.\"
Code={ $InputObject.Value.Contains('Code') }
}
}
$Action={ Write-Error \"Erreur l'entrée : [$($InputObject.Key)] $($_.Value.ErrorMessage)\" }
$Rules.GetEnumerator()| Test-BusinessRules -Rules $Validation -Action $Action
.
Description
Ces commandes valident la structure d'une hashtable contenant des régles de gestion.
On utilise la hashtable $Validation pour contrôler la structure de la hashtable $Rules,
car ses clés Erreur1, Erreur2 et Erreur1 ne sont pas correctement structurées.
.
Notez que dans ce cas, on référence dans le scriptblock du paramètre Action, l'objet
reçu dans $Inputobject ET le message d'erreur de la hashtable $Validation.

#>
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[ValidateNotNull()]
$InputObject,

[Parameter(Position=1, Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[System.Collections.IDictionary] $Rules,

[ValidateNotNullOrEmpty()]
[Parameter(Position=2)]
[ScriptBlock] $Action,

[Parameter(Position=3)]
[Object[]] $ArgumentList,

[Parameter(Position=4)]
[ValidateNotNullOrEmpty()]
[String] $RemainingVariable,

[switch] $Clone,
[switch] $Passthru
)
begin {
if($PSBoundParameters.ContainsKey(\"WarningAction\"«»)) #workaround
{ $local:WarningPreference = $PSBoundParameters }

$isRemaining=$PSBoundParameters.ContainsKey('RemainingVariable')
if ($isRemaining)
{
$isAddition=$RemainingVariable.StartsWith(\"+\", [StringComparison]::Ordinal)
$isCreated=$false
Write-Debug \"isAddition=$isAddition\" #<%REMOVE%>
Write-Debug \"isCreated=$isCreated\" #<%REMOVE%>
Write-Debug \"The variable name is '$RemainingVariable'\" #<%REMOVE%>

if ($isAddition)
{
Write-Debug \"Addition detected. Retrieve the PSVariable\" #<%REMOVE%>
$RemainingVariable=$RemainingVariable.Substring(1)
$RemainingDatas=$PSCmdlet.SessionState.PSVariable.GetValue($RemainingVariable)
}

if ($RemainingDatas -eq $null -and ($isAddition -eq $true) )
{
Write-Debug \"Create the variable $RemainingVariable\" #<%REMOVE%>
$RemainingDatas= New-Object System.Collections.ArrayList
$isCreated=$true
}
}

$SBAction=$null
if ($PSBoundParameters.ContainsKey('Action') )
{
if ($Action -ne $null)
{
Write-Debug \"Bound scriptblock Action\" #<%REMOVE%>
#Le scriptblock référence des paramètres de cette fonction.
#Il peut être déclaré dans un autre état de session, on force donc son
#exécution dans le contexte de ce module afin d'accéder au paramètre $InputObject.
#Si son code utilise des variables déclarées dans le contexte de l'appelant, on doit
#utiliser le paramètre $ArgumentList.
$SBAction= $MyInvocation.MyCommand.ScriptBlock.Module.NewBoundScriptBlock($Action)
}
else { Write-Debug \"Scriptblock Action is null\" } #<%REMOVE%>
}
else
{
#La variable $_ est locale au pipeline exécuté dans cette fonction : $Rules.GetEnumerator()| Foreach {
$SBAction={ Write-Error \"Erreur de validation pour la régle : [$($_.Key)] $($_.Value.ErrorMessage)\"}
}
}
process {
if ($isRemaining)
{
Write-Debug \"isAddition=$isAddition\" #<%REMOVE%>
Write-Debug \"isCreated=$isCreated\" #<%REMOVE%>

if ($isAddition -and $isCreated)
{
Write-Debug \"Inject the new variable $RemainingVariable in the caller's runspace\" #<%REMOVE%>
$PSCmdlet.SessionState.PSVariable.Set($RemainingVariable,$RemainingDatas)
$isCreated=$false
}
if ($isAddition -eq $false)
{
Write-Debug \"Recreate and inject the new variable $RemainingVariable in the caller's runspace\" #<%REMOVE%>
$RemainingDatas= New-Object System.Collections.ArrayList
$PSCmdlet.SessionState.PSVariable.Set($RemainingVariable,$RemainingDatas)
}
}
if ($Clone)
{
if ($InputObject -isnot [PSCustomObject] )
{ Write-Warning \"Attention la copie sur un objet dotNet n'est pas supportée.\" }
#Clone l'objet, permet de modifier sa structure
#sans modifier l'objet d'origine.
$InputObject=$InputObject.PSObject.Copy()
}
$ValidationErrors=0
Write-Debug \"Validate all rules\" #<%REMOVE%>
$Rules.GetEnumerator()|
Foreach {
#On lie explicitement le scriptblock dans la portée du module,
#sinon la variable $InputObject est recherchée dans la portée de l'appelant ce
#qui générerait l'erreur : VariableIsUndefined
if ((&($MyInvocation.MyCommand.ScriptBlock.Module.NewBoundScriptBlock($_.Value.Code))) -eq $false)
{
Write-Debug \"Error for the rule $($_.Key)\" #<%REMOVE%>
$ValidationErrors++
if ($SBAction -ne $null)
{ $null=&$sbAction}
}
}#foreach

$isError=$ValidationErrors -ne 0
if ($isRemaining -and $isError)
{
Write-Debug \"Add object into error collection '$RemainingVariable'\" #<%REMOVE%>
[void]$RemainingDatas.Add($InputObject)
}

#Emet l'objet si demandé et s'il n'y a pas eu d'erreur de validation
if ($Passthru -and -not $isError)
{ write-output $InputObject }
#Sinon émet un boolean
if (-not $Passthru )
{ write-output (-not $isError) }
}#process
} #Test-BusinessRules
[/code:1]
Note : il se peut que sous PS v3, l'aide en ligne ne s'affiche pas.

Pour ceux utilisant un mécanisme de log telque Log4net utilisez le paramètre -Action

Tutoriels PowerShell

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

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