PowerShell noob ici.
J'ai deux dossiers différents avec des fichiers XML. Un dossier (folder2) contient les fichiers XML mis à jour et nouveaux par rapport à l'autre (folder1). J'ai besoin de savoir quels fichiers dans folder2 sont nouveaux/mis à jour par rapport à folder1 et les copier dans un troisième dossier (folder3). Quel est le meilleur moyen d'accomplir cela dans PowerShell?
OK, je ne vais pas tout coder pour vous (quel est le plaisir en cela?), Mais je vais vous aider à démarrer.
Premièrement, il y a deux façons de comparer le contenu. La méthode paresseuse/généralement correcte, qui consiste à comparer la longueur des fichiers; et le moyen précis mais plus complexe, qui consiste à comparer un hachage du contenu de chaque fichier.
Par souci de simplicité, faisons de manière simple et comparons la taille du fichier.
En gros, vous voulez deux objets représentant les dossiers source et cible:
$Folder1 = Get-childitem "C:\Folder1"
$Folder2 = Get-childitem "C:\Folder2"
Ensuite, vous pouvez utiliser Compare-Object
pour voir quels articles sont différents ...
Compare-Object $Folder1 $Folder2 -Property Name, Length
qui listera pour vous tout ce qui est différent en comparant seulement le nom et la longueur des objets de fichier de chaque collection.
Vous pouvez diriger cela vers un filtre Where-Object
pour choisir des éléments différents sur le côté gauche ...
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="}
Et puis dirigez cela vers un ForEach-Object
pour copier où vous voulez:
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="} | ForEach-Object {
Copy-Item "C:\Folder1\$($_.name)" -Destination "C:\Folder3" -Force
}
Voici un fichier diff pur (sans dépendance) récursif PowerShell v3 + qui calcule le hachage MD5 pour le contenu du fichier de chaque répertoire (gauche/droite). Peut éventuellement exporter des fichiers CSV avec un fichier texte de synthèse. Les résultats par défaut sont renvoyés sur stdout. Vous pouvez soit déposer le fichier rdiff.ps1 dans votre chemin, soit copier le contenu dans votre script.
USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir]
Voici le Gist . Il est recommandé d’utiliser la version de Gist car elle peut avoir des fonctionnalités supplémentaires au fil du temps. N'hésitez pas à envoyer des demandes de tirage.
#########################################################################
### USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir] ###
### ADD LOCATION OF THIS SCRIPT TO PATH ###
#########################################################################
[CmdletBinding()]
param (
[parameter(HelpMessage="Stores the execution working directory.")]
[string]$ExecutionDirectory=$PWD,
[parameter(Position=0,HelpMessage="Compare two directories recursively for differences.")]
[alias("c")]
[string[]]$Compare,
[parameter(HelpMessage="Export a summary to path.")]
[alias("s")]
[string]$ExportSummary
)
### FUNCTION DEFINITIONS ###
# SETS WORKING DIRECTORY FOR .NET #
function SetWorkDir($PathName, $TestPath) {
$AbsPath = NormalizePath $PathName $TestPath
Set-Location $AbsPath
[System.IO.Directory]::SetCurrentDirectory($AbsPath)
}
# RESTORES THE EXECUTION WORKING DIRECTORY AND EXITS #
function SafeExit() {
SetWorkDir /path/to/execution/directory $ExecutionDirectory
Exit
}
function Print {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Message to print.")]
[string]$Message,
[parameter(HelpMessage="Specifies a success.")]
[alias("s")]
[switch]$SuccessFlag,
[parameter(HelpMessage="Specifies a warning.")]
[alias("w")]
[switch]$WarningFlag,
[parameter(HelpMessage="Specifies an error.")]
[alias("e")]
[switch]$ErrorFlag,
[parameter(HelpMessage="Specifies a fatal error.")]
[alias("f")]
[switch]$FatalFlag,
[parameter(HelpMessage="Specifies a info message.")]
[alias("i")]
[switch]$InfoFlag = !$SuccessFlag -and !$WarningFlag -and !$ErrorFlag -and !$FatalFlag,
[parameter(HelpMessage="Specifies blank lines to print before.")]
[alias("b")]
[int]$LinesBefore=0,
[parameter(HelpMessage="Specifies blank lines to print after.")]
[alias("a")]
[int]$LinesAfter=0,
[parameter(HelpMessage="Specifies if program should exit.")]
[alias("x")]
[switch]$ExitAfter
)
PROCESS {
if($LinesBefore -ne 0) {
foreach($i in 0..$LinesBefore) { Write-Host "" }
}
if($InfoFlag) { Write-Host "$Message" }
if($SuccessFlag) { Write-Host "$Message" -ForegroundColor "Green" }
if($WarningFlag) { Write-Host "$Message" -ForegroundColor "Orange" }
if($ErrorFlag) { Write-Host "$Message" -ForegroundColor "Red" }
if($FatalFlag) { Write-Host "$Message" -ForegroundColor "Red" -BackgroundColor "Black" }
if($LinesAfter -ne 0) {
foreach($i in 0..$LinesAfter) { Write-Host "" }
}
if($ExitAfter) { SafeExit }
}
}
# VALIDATES STRING MIGHT BE A PATH #
function ValidatePath($PathName, $TestPath) {
If([string]::IsNullOrWhiteSpace($TestPath)) {
Print -x -f "$PathName is not a path"
}
}
# NORMALIZES RELATIVE OR ABSOLUTE PATH TO ABSOLUTE PATH #
function NormalizePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$TestPath = [System.IO.Path]::Combine((pwd).Path, $TestPath)
$NormalizedPath = [System.IO.Path]::GetFullPath($TestPath)
return $NormalizedPath
}
# VALIDATES STRING MIGHT BE A PATH AND RETURNS ABSOLUTE PATH #
function ResolvePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$ResolvedPath = NormalizePath $PathName $TestPath
return $ResolvedPath
}
# VALIDATES STRING RESOLVES TO A PATH AND RETURNS ABSOLUTE PATH #
function RequirePath($PathName, $TestPath, $PathType) {
ValidatePath $PathName $TestPath
If(!(Test-Path $TestPath -PathType $PathType)) {
Print -x -f "$PathName ($TestPath) does not exist as a $PathType"
}
$ResolvedPath = Resolve-Path $TestPath
return $ResolvedPath
}
# Like mkdir -p -> creates a directory recursively if it doesn't exist #
function MakeDirP {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path create.")]
[string]$Path
)
PROCESS {
New-Item -path $Path -itemtype Directory -force | Out-Null
}
}
# GETS ALL FILES IN A PATH RECURSIVELY #
function GetFiles {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get files for.")]
[string]$Path
)
PROCESS {
ls $Path -r | where { !$_.PSIsContainer }
}
}
# GETS ALL FILES WITH CALCULATED HASH PROPERTY RELATIVE TO A ROOT DIRECTORY RECURSIVELY #
# RETURNS LIST OF @{RelativePath, Hash, FullName}
function GetFilesWithHash {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get directories for.")]
[string]$Path,
[parameter(HelpMessage="The hash algorithm to use.")]
[string]$Algorithm="MD5"
)
PROCESS {
$OriginalPath = $PWD
SetWorkDir path/to/diff $Path
GetFiles $Path | select @{N="RelativePath";E={$_.FullName | Resolve-Path -Relative}},
@{N="Hash";E={(Get-FileHash $_.FullName -Algorithm $Algorithm | select Hash).Hash}},
FullName
SetWorkDir path/to/original $OriginalPath
}
}
# COMPARE TWO DIRECTORIES RECURSIVELY #
# RETURNS LIST OF @{RelativePath, Hash, FullName}
function DiffDirectories {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Directory to compare left.")]
[alias("l")]
[string]$LeftPath,
[parameter(Mandatory=$TRUE,Position=1,HelpMessage="Directory to compare right.")]
[alias("r")]
[string]$RightPath
)
PROCESS {
$LeftHash = GetFilesWithHash $LeftPath
$RightHash = GetFilesWithHash $RightPath
diff -ReferenceObject $LeftHash -DifferenceObject $RightHash -Property RelativePath,Hash
}
}
### END FUNCTION DEFINITIONS ###
### PROGRAM LOGIC ###
if($Compare.length -ne 2) {
Print -x "Compare requires passing exactly 2 path parameters separated by comma, you passed $($Compare.length)." -f
}
Print "Comparing $($Compare[0]) to $($Compare[1])..." -a 1
$LeftPath = RequirePath path/to/left $Compare[0] container
$RightPath = RequirePath path/to/right $Compare[1] container
$Diff = DiffDirectories $LeftPath $RightPath
$LeftDiff = $Diff | where {$_.SideIndicator -eq "<="} | select RelativePath,Hash
$RightDiff = $Diff | where {$_.SideIndicator -eq "=>"} | select RelativePath,Hash
if($ExportSummary) {
$ExportSummary = ResolvePath path/to/summary/dir $ExportSummary
MakeDirP $ExportSummary
$SummaryPath = Join-Path $ExportSummary summary.txt
$LeftCsvPath = Join-Path $ExportSummary left.csv
$RightCsvPath = Join-Path $ExportSummary right.csv
$LeftMeasure = $LeftDiff | measure
$RightMeasure = $RightDiff | measure
"== DIFF SUMMARY ==" > $SummaryPath
"" >> $SummaryPath
"-- DIRECTORIES --" >> $SummaryPath
"`tLEFT -> $LeftPath" >> $SummaryPath
"`tRIGHT -> $RightPath" >> $SummaryPath
"" >> $SummaryPath
"-- DIFF COUNT --" >> $SummaryPath
"`tLEFT -> $($LeftMeasure.Count)" >> $SummaryPath
"`tRIGHT -> $($RightMeasure.Count)" >> $SummaryPath
"" >> $SummaryPath
$Diff | Format-Table >> $SummaryPath
$LeftDiff | Export-Csv $LeftCsvPath -f
$RightDiff | Export-Csv $RightCsvPath -f
}
$Diff
SafeExit
En guise de réponse à la réponse de @ JNK, vous voudrez peut-être vous assurer de toujours utiliser des fichiers plutôt que la sortie moins intuitive de Compare-Object
. Vous devez juste utiliser le commutateur -PassThru
...
$Folder1 = Get-ChildItem "C:\Folder1"
$Folder2 = Get-ChildItem "C:\Folder2"
$Folder2 = "C:\Folder3\"
# Get all differences, i.e. from both "sides"
$AllDiffs = Compare-Object $Folder1 $Folder2 -Property Name,Length -PassThru
# Filter for new/updated files from $Folder2
$Changes = $AllDiffs | Where-Object {$_.Directory.Fullname -eq $Folder2}
# Copy to $Folder3
$Changes | Copy-Item -Destination $Folder3
Cela signifie au moins que vous n'avez pas à vous soucier de la direction indiquée par la flèche SideIndicator!
En outre, gardez à l’esprit que vous voudrez peut-être comparer sur LastWriteTime également.
La boucle récursive dans les sous-dossiers est un peu plus compliquée, car vous devrez probablement supprimer les chemins des dossiers racine respectifs du champ Nom complet avant de comparer les listes.
Vous pouvez le faire en ajoutant un nouveau ScriptProperty à vos listes Folder1 et Folder2:
$Folder1 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder1"),""}
$Folder2 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder2"),""}
Vous devriez alors pouvoir utiliser RelativePath en tant que propriété lors de la comparaison des deux objets et l'utiliser également pour rejoindre "C:\Dossier3" lors de la copie afin de conserver la structure de dossiers en place.
Faire ceci:
compare (Get-ChildItem D:\MyFolder\NewFolder) (Get-ChildItem \\RemoteServer\MyFolder\NewFolder)
Et même récursivement:
compare (Get-ChildItem -r D:\MyFolder\NewFolder) (Get-ChildItem -r \\RemoteServer\MyFolder\NewFolder)
et est même difficile à oublier :)
Comparaison simple au niveau fichier
Appelez-le comme PS > .\DirDiff.ps1 -a .\Old\ -b .\New\
Param(
[string]$a,
[string]$b
)
$fsa = Get-ChildItem -Recurse -path $a
$fsb = Get-ChildItem -Recurse -path $b
Compare-Object -Referenceobject $fsa -DifferenceObject $fsb
Sortie possible:
InputObject SideIndicator
----------- -------------
appsettings.Development.json <=
appsettings.Testing.json <=
Server.pdb =>
ServerClientLibrary.pdb =>