Question [Fonction] Out-SplittingCSV
- Laurent Dardenne
- Auteur du sujet
- Hors Ligne
- Modérateur
Réduire
Plus d'informations
- Messages : 6302
- Remerciements reçus 68
il y a 10 ans 1 mois #18282
par Laurent Dardenne
Tutoriels PowerShell
[Fonction] Out-SplittingCSV a été créé par Laurent Dardenne
Powershell à un manque qui est qu'on ne peut écrire les objets reçus du pipeline dans différents fichier, tous le seront dans un seul fichier.
Par exemple Export-Csv utilise un seul fichier et celui-ci est fermé dans son bloc end, on ne peut donc utiliser un proxy de commande pour modifier son comportement.
Il est possible de regrouper temporairement une collection d'objets en mémoire, puis de l'écrire en une passe, et ce plusieurs fois.
L'inconvénient est que si on traite plusieurs centaines de milliers d'objets, par exemple des fichiers, l'occupation mémoire du script peut impacter d'autres traitements.
On peut bien évidement utiliser un seul fichier, puis le découper par bloc une fois celui-ci construit.
La fonction suivante permet de scinder une suite d'objet au format CSV dans plusieurs fichiers selon un nombre d'objets maximum sans utiliser de collection temporaire :
[code:1]
function Out-SplittingCSV {
<#
.SYNOPSIS
Ecrit dans un fichier des objets au format CSV.
Cette fonction peut générer plusieurs fichiers regroupant un nombre limité d'objets.
#>
[CmdletBinding()]
Param(
#Objet à convertir.
[ValidateNotNull()]
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
$InputObject,
#Nom de base du fichier.
#Chaque nom de fichier est incrémenté à partir de zéro.
#Si le fichier existe il est écrasé.
[ValidateNotNullOrEmpty()]
[Parameter(Position=0,Mandatory=$True)]
$Path,
#Nombre d'objet maximum à écrire dans un fichier.
#Voir le paramètre -Validation
[Parameter(Position=1,Mandatory=$True,ParameterSetName=\"Count\"«»)]
[ValidateScript({$_ -ge 1})]
[int]$Count,
#Noms des propriétés d'un objet à convertir.
#Peut être une hashtable normalisé, consulter la documentation du paramètre -Property du cmdlet Select-Object.
[ValidateNotNullOrEmpty()]
[Parameter(Position=2)]
[Object[]]$Property='*',
#Code de validation des clés permettant de 'regrouper' les objets reçu du pipeline.
#Son usage retarde la création d'un nouveau fichier tant que le code de validation renvoi $true alors que la valeur maximum de Count est atteinte ou dépassée.
#On évite ainsi de disperser des informations dont la clé est identique.
[ValidateNotNullOrEmpty()]
[Parameter(Position=3)]
[ScriptBlock] $Validation,
#Spécifie un délimiteur pour séparer les valeurs de propriété.
#Par défaut contient la valeur de la clé de registre suivante :
# hkcu:\Control Panel\International\sList
#Si cette clé n'existe pas la valeur par défaut sera la virgule ','
[ValidateNotNullOrEmpty()]
[Parameter(Position=4)]
[char] $Delimiter,
#Spécifie le type d'encodage pour le fichier cible.
#Les valeurs valides sont ASCII, UTF8, UTF7, UTF32, Unicode, BigEndianUnicode, Default et OEM.
#La valeur par défaut est UTF8.
[Parameter(Position=5)]
[Text.Encoding]$Encoding = [Text.Encoding]::UTF8,
#Omet l'en-tête d'informations de type de la sortie.
#Par défaut, la chaîne de la sortie contient « #TYPE », suivi du nom complet du type de l'objet .NET Framework.
#Le type inséré par ConvertTo-Csv correspond au premier objet reçu dans le pipeline.
#Si les objets reçus ne sont pas du même type, le typename affiché par Get-Member peut ne pas correspondre.
[switch] $NoTypeInformation
)
Begin {
$isValidateKey=$PSBoundParameters.ContainsKey('Validation')
if (!$isValidateKey)
{ $Validation= {$true} }
[Switch] $isVerbose= $null
[void]$PSBoundParameters.TryGetValue('Verbose',[REF]$isVerbose)
if ($isVerbose)
{ $VerbosePreference='Continue' }
$_EA= $null
[void]$PSBoundParameters.TryGetValue('ErrorAction',[REF]$_EA)
if ($_EA -eq $null)
{
#Récupère la valeur du contexte de l'appelant
$ErrorActionPreference=$PSCmdlet.SessionState.PSVariable.Get('ErrorActionPreference').Value
}
else
{
#Priorité: On remplace sa valeur
$ErrorActionPreference=$_EA
}
if (!$PSBoundParameters.ContainsKey('Delimiter'))
{
try {
$Delimiter=(Get-ItemProperty 'hkcu:\Control Panel\International' sList -ea Stop).sList
} catch {
#inexistent key or null value
$Delimiter=','
Write-Warning \"Use delimiter '$Demlimiter'.\"
}
}
function OpenStream {
Write-debug \"Increment=$Increment\" #<%REMOVE%>
$FileName='{0}\{1}{2}{3}' -F $FileInfo.Directory,$FileInfo.BaseName,((Get-Variable Increment -scope 1).Value++),$FileInfo.Extension
Write-Debug \"OpenStream $FileName\" #<%REMOVE%>
try {
$Writer=New-Object IO.StreamWriter $FileName, $false, $Encoding
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"OpenError\",
$FileName
)
#Stop the pipeline now !
$PSCmdlet.ThrowTerminatingError($Er)
}
try {
if ($TYPEInformation -ne [string]::Empty)
{ $Writer.WriteLine($TYPEInformation) }
if ($HeaderInformation -ne [string]::Empty)
{ $Writer.WriteLine($HeaderInformation) }
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"WriteError\",
$FileName
)
$PSCmdlet.ThrowTerminatingError($Er)
}
Write-Output $Writer
}#OpenStream
function CloseStream {
if ($Writer -ne $null)
{
Write-Verbose \"Out-SplittingCSV : $($Writer.BaseStream.Name)\"
try {
$Writer.Close()
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"CloseError\",
$FileName
)
$PSCmdlet.ThrowTerminatingError($Er)
}
}
}#CloseStream
$TYPEInformation=$HeaderInformation=[string]::Empty
$Increment=$Number=0
$Writer = $PreviousObject =$null
$isAssignHeader=$true
$FileInfo=New-object System.IO.FileInfo $Path
}
Process {
Write-Debug \"${Number}/$Count\" #<%REMOVE%>
if ($isAssignHeader)
{
$Csv=$InputObject|Select-Object -Property $Property|ConvertTo-Csv -Delimiter $Delimiter -NoTypeInformation:$NoTypeInformation
if ($NoTypeInformation)
{
$HeaderInformation =$Csv[0]
Write-Debug \"HeaderInformation= $HeaderInformation\" #<%REMOVE%>
}
else
{
$TYPEInformation=$Csv[0]
Write-Debug \"TYPEInformation= $TYPEInformation\" #<%REMOVE%>
$HeaderInformation =$Csv[1]
Write-Debug \"HeaderInformation= $HeaderInformation\" #<%REMOVE%>
}
$isAssignHeader=$false
$PreviousObject=$InputObject
$Writer = OpenStream
}
Write-debug \"$($Number -ge $Count) $(&$Validation -Current $InputObject -Previous $PreviousObject)\" #<%REMOVE%>
if (($Number -ge $Count) -and (&$Validation -Current $InputObject -Previous $PreviousObject))
{
Write-Debug \"Split CSV file\" #<%REMOVE%>
CloseStream
$Writer = OpenStream
$Number=0
}
if ($isValidateKey)
{ $PreviousObject=$InputObject }
try {
$Writer.WriteLine((($InputObject|Select-Object -Property $Property|ConvertTo-Csv -Delimiter $Delimiter -NoTypeInformation)[1]) )
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"WriteError\",
$Writer.BaseStream.Name
)
$PSCmdlet.ThrowTerminatingError($Er)
}
$Number++
}
End {
#Close Current stream
CloseStream
}
} #Out-SplittingCSV
[/code:1]
Un exemple simple (supprimer le paramètre -whatif):
[code:1]
del 'C:\temp\LgPath*.csv' -whatif
#On crée des fichier regroupant 10 objets max
dir $pshome -rec|Out-SplittingCSV -Path 'c:\temp\LgPath.csv' -Count 50 -Property PSPath,Name,Size -Verbose
Type 'c:\temp\LgPath0.csv
Type 'c:\temp\LgPath1.csv
#...
[/code:1]
Un exemple retardant la création d'un fichier selon une régle de validation :
[code:1]
$KeyValidation={
#La fonction doit déclarer deux paramètres
param ($Current, $Previous)
$Key='PSParentPath'
Write-debug \"Input : $($Current.$Key)\" #<%REMOVE%>
Write-debug \"Last : $($Previous.$Key)\" #<%REMOVE%>
$Current.$Key -ne $Previous.$Key
}
del 'C:\temp\LgPath*.csv' -whatif
#On crée des fichiers regroupant 10 objets ou plus tant que la propriété 'PSParentPath'
#de l'objet courant(reçu du pipeline) est identique à celle de l'objet traité précédement.
dir $pshome -rec|Out-SplittingCSV -Path 'c:\temp\LgPath.csv' -Count 50 -Validation $KeyValidation -Property PSPath,Name,Size -Verbose
Type 'c:\temp\LgPath0.csv
Type 'c:\temp\LgPath1.csv
#...
[/code:1]
Par exemple Export-Csv utilise un seul fichier et celui-ci est fermé dans son bloc end, on ne peut donc utiliser un proxy de commande pour modifier son comportement.
Il est possible de regrouper temporairement une collection d'objets en mémoire, puis de l'écrire en une passe, et ce plusieurs fois.
L'inconvénient est que si on traite plusieurs centaines de milliers d'objets, par exemple des fichiers, l'occupation mémoire du script peut impacter d'autres traitements.
On peut bien évidement utiliser un seul fichier, puis le découper par bloc une fois celui-ci construit.
La fonction suivante permet de scinder une suite d'objet au format CSV dans plusieurs fichiers selon un nombre d'objets maximum sans utiliser de collection temporaire :
[code:1]
function Out-SplittingCSV {
<#
.SYNOPSIS
Ecrit dans un fichier des objets au format CSV.
Cette fonction peut générer plusieurs fichiers regroupant un nombre limité d'objets.
#>
[CmdletBinding()]
Param(
#Objet à convertir.
[ValidateNotNull()]
[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
$InputObject,
#Nom de base du fichier.
#Chaque nom de fichier est incrémenté à partir de zéro.
#Si le fichier existe il est écrasé.
[ValidateNotNullOrEmpty()]
[Parameter(Position=0,Mandatory=$True)]
$Path,
#Nombre d'objet maximum à écrire dans un fichier.
#Voir le paramètre -Validation
[Parameter(Position=1,Mandatory=$True,ParameterSetName=\"Count\"«»)]
[ValidateScript({$_ -ge 1})]
[int]$Count,
#Noms des propriétés d'un objet à convertir.
#Peut être une hashtable normalisé, consulter la documentation du paramètre -Property du cmdlet Select-Object.
[ValidateNotNullOrEmpty()]
[Parameter(Position=2)]
[Object[]]$Property='*',
#Code de validation des clés permettant de 'regrouper' les objets reçu du pipeline.
#Son usage retarde la création d'un nouveau fichier tant que le code de validation renvoi $true alors que la valeur maximum de Count est atteinte ou dépassée.
#On évite ainsi de disperser des informations dont la clé est identique.
[ValidateNotNullOrEmpty()]
[Parameter(Position=3)]
[ScriptBlock] $Validation,
#Spécifie un délimiteur pour séparer les valeurs de propriété.
#Par défaut contient la valeur de la clé de registre suivante :
# hkcu:\Control Panel\International\sList
#Si cette clé n'existe pas la valeur par défaut sera la virgule ','
[ValidateNotNullOrEmpty()]
[Parameter(Position=4)]
[char] $Delimiter,
#Spécifie le type d'encodage pour le fichier cible.
#Les valeurs valides sont ASCII, UTF8, UTF7, UTF32, Unicode, BigEndianUnicode, Default et OEM.
#La valeur par défaut est UTF8.
[Parameter(Position=5)]
[Text.Encoding]$Encoding = [Text.Encoding]::UTF8,
#Omet l'en-tête d'informations de type de la sortie.
#Par défaut, la chaîne de la sortie contient « #TYPE », suivi du nom complet du type de l'objet .NET Framework.
#Le type inséré par ConvertTo-Csv correspond au premier objet reçu dans le pipeline.
#Si les objets reçus ne sont pas du même type, le typename affiché par Get-Member peut ne pas correspondre.
[switch] $NoTypeInformation
)
Begin {
$isValidateKey=$PSBoundParameters.ContainsKey('Validation')
if (!$isValidateKey)
{ $Validation= {$true} }
[Switch] $isVerbose= $null
[void]$PSBoundParameters.TryGetValue('Verbose',[REF]$isVerbose)
if ($isVerbose)
{ $VerbosePreference='Continue' }
$_EA= $null
[void]$PSBoundParameters.TryGetValue('ErrorAction',[REF]$_EA)
if ($_EA -eq $null)
{
#Récupère la valeur du contexte de l'appelant
$ErrorActionPreference=$PSCmdlet.SessionState.PSVariable.Get('ErrorActionPreference').Value
}
else
{
#Priorité: On remplace sa valeur
$ErrorActionPreference=$_EA
}
if (!$PSBoundParameters.ContainsKey('Delimiter'))
{
try {
$Delimiter=(Get-ItemProperty 'hkcu:\Control Panel\International' sList -ea Stop).sList
} catch {
#inexistent key or null value
$Delimiter=','
Write-Warning \"Use delimiter '$Demlimiter'.\"
}
}
function OpenStream {
Write-debug \"Increment=$Increment\" #<%REMOVE%>
$FileName='{0}\{1}{2}{3}' -F $FileInfo.Directory,$FileInfo.BaseName,((Get-Variable Increment -scope 1).Value++),$FileInfo.Extension
Write-Debug \"OpenStream $FileName\" #<%REMOVE%>
try {
$Writer=New-Object IO.StreamWriter $FileName, $false, $Encoding
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"OpenError\",
$FileName
)
#Stop the pipeline now !
$PSCmdlet.ThrowTerminatingError($Er)
}
try {
if ($TYPEInformation -ne [string]::Empty)
{ $Writer.WriteLine($TYPEInformation) }
if ($HeaderInformation -ne [string]::Empty)
{ $Writer.WriteLine($HeaderInformation) }
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"WriteError\",
$FileName
)
$PSCmdlet.ThrowTerminatingError($Er)
}
Write-Output $Writer
}#OpenStream
function CloseStream {
if ($Writer -ne $null)
{
Write-Verbose \"Out-SplittingCSV : $($Writer.BaseStream.Name)\"
try {
$Writer.Close()
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"CloseError\",
$FileName
)
$PSCmdlet.ThrowTerminatingError($Er)
}
}
}#CloseStream
$TYPEInformation=$HeaderInformation=[string]::Empty
$Increment=$Number=0
$Writer = $PreviousObject =$null
$isAssignHeader=$true
$FileInfo=New-object System.IO.FileInfo $Path
}
Process {
Write-Debug \"${Number}/$Count\" #<%REMOVE%>
if ($isAssignHeader)
{
$Csv=$InputObject|Select-Object -Property $Property|ConvertTo-Csv -Delimiter $Delimiter -NoTypeInformation:$NoTypeInformation
if ($NoTypeInformation)
{
$HeaderInformation =$Csv[0]
Write-Debug \"HeaderInformation= $HeaderInformation\" #<%REMOVE%>
}
else
{
$TYPEInformation=$Csv[0]
Write-Debug \"TYPEInformation= $TYPEInformation\" #<%REMOVE%>
$HeaderInformation =$Csv[1]
Write-Debug \"HeaderInformation= $HeaderInformation\" #<%REMOVE%>
}
$isAssignHeader=$false
$PreviousObject=$InputObject
$Writer = OpenStream
}
Write-debug \"$($Number -ge $Count) $(&$Validation -Current $InputObject -Previous $PreviousObject)\" #<%REMOVE%>
if (($Number -ge $Count) -and (&$Validation -Current $InputObject -Previous $PreviousObject))
{
Write-Debug \"Split CSV file\" #<%REMOVE%>
CloseStream
$Writer = OpenStream
$Number=0
}
if ($isValidateKey)
{ $PreviousObject=$InputObject }
try {
$Writer.WriteLine((($InputObject|Select-Object -Property $Property|ConvertTo-Csv -Delimiter $Delimiter -NoTypeInformation)[1]) )
}
catch {
$Er= New-Object System.Management.Automation.ErrorRecord(
$_.Exception,
\"IOError\",
\"WriteError\",
$Writer.BaseStream.Name
)
$PSCmdlet.ThrowTerminatingError($Er)
}
$Number++
}
End {
#Close Current stream
CloseStream
}
} #Out-SplittingCSV
[/code:1]
Un exemple simple (supprimer le paramètre -whatif):
[code:1]
del 'C:\temp\LgPath*.csv' -whatif
#On crée des fichier regroupant 10 objets max
dir $pshome -rec|Out-SplittingCSV -Path 'c:\temp\LgPath.csv' -Count 50 -Property PSPath,Name,Size -Verbose
Type 'c:\temp\LgPath0.csv
Type 'c:\temp\LgPath1.csv
#...
[/code:1]
Un exemple retardant la création d'un fichier selon une régle de validation :
[code:1]
$KeyValidation={
#La fonction doit déclarer deux paramètres
param ($Current, $Previous)
$Key='PSParentPath'
Write-debug \"Input : $($Current.$Key)\" #<%REMOVE%>
Write-debug \"Last : $($Previous.$Key)\" #<%REMOVE%>
$Current.$Key -ne $Previous.$Key
}
del 'C:\temp\LgPath*.csv' -whatif
#On crée des fichiers regroupant 10 objets ou plus tant que la propriété 'PSParentPath'
#de l'objet courant(reçu du pipeline) est identique à celle de l'objet traité précédement.
dir $pshome -rec|Out-SplittingCSV -Path 'c:\temp\LgPath.csv' -Count 50 -Validation $KeyValidation -Property PSPath,Name,Size -Verbose
Type 'c:\temp\LgPath0.csv
Type 'c:\temp\LgPath1.csv
#...
[/code:1]
Tutoriels PowerShell
Connexion ou Créer un compte pour participer à la conversation.
Temps de génération de la page : 0.099 secondes
- Vous êtes ici :
- Accueil
- forum
- PowerShell
- Contributions à la communauté
- [Fonction] Out-SplittingCSV