Powershell Copy-Item Script schneller machen
Hallo zusammen,
ich habe ein Powershell Script gebaut was Ordner basierend auf einer Liste von A nach B kopiert.
Teilweise kopiert das Script mehrere Millionen Dateien. Mir kommt der Kopiervorgang recht lang vor verglichen mit einem regulären Copy+Paste.
Hat jemand eine Idee wie man das Script performanter machen könnte?
Das Script holt sich den Dateinamen eines Ordners aus einer Liste und kopiert dann den gesamten Ordner an einen Zielort.
Macht evtl. der Recurse Parameter das ganze so langsam?
Jemand Ideen?
ich habe ein Powershell Script gebaut was Ordner basierend auf einer Liste von A nach B kopiert.
Teilweise kopiert das Script mehrere Millionen Dateien. Mir kommt der Kopiervorgang recht lang vor verglichen mit einem regulären Copy+Paste.
Hat jemand eine Idee wie man das Script performanter machen könnte?
Das Script holt sich den Dateinamen eines Ordners aus einer Liste und kopiert dann den gesamten Ordner an einen Zielort.
Macht evtl. der Recurse Parameter das ganze so langsam?
Jemand Ideen?
# Dieses Skript kopiert (!) Ordner basierend auf einer Liste von Quellort zu Zielort.
# Variablen
[STRING] $quelle = "D:\test\root"
[STRING] $ziel = "D:\test\target"
[STRING] $liste = "D:\liste.txt"
# Logik
$counter = 0
Get-Content $liste |
Foreach-Object {
copy-item -path "$quelle\$_" -destination "$ziel\$_" -Force -Recurse -verbose
}
# Piepton Benachrichtigung wenn fertig
[console]::beep(1000,700)
[console]::beep(1000,700)
[console]::beep(2000,1500)
pause
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 627793
Url: https://administrator.de/contentid/627793
Ausgedruckt am: 22.11.2024 um 00:11 Uhr
15 Kommentare
Neuester Kommentar
Hi,
meistens dauert die Kopie sehr vieler kleiner Dateien länger, als die von wenigen großen, auch wenn sie jeweils in Summe die selbe Anzahl Bytes haben.
Die könntest es mit Robocopy und Schalter /MT experimentieren.
Oder mehrere Kopie-Vorgänge gleichzeitig starten.
Das hängst jetzt auch davon ab, was Quelle und Ziel für Datenträger sind.
E.
meistens dauert die Kopie sehr vieler kleiner Dateien länger, als die von wenigen großen, auch wenn sie jeweils in Summe die selbe Anzahl Bytes haben.
Die könntest es mit Robocopy und Schalter /MT experimentieren.
Oder mehrere Kopie-Vorgänge gleichzeitig starten.
Das hängst jetzt auch davon ab, was Quelle und Ziel für Datenträger sind.
E.
Zitat von @emeriks:
Hi,
meistens dauert die Kopie sehr vieler kleiner Dateien länger, als die von wenigen großen, auch wenn sie jeweils in Summe die selbe Anzahl Bytes haben.
Hi,
meistens dauert die Kopie sehr vieler kleiner Dateien länger, als die von wenigen großen, auch wenn sie jeweils in Summe die selbe Anzahl Bytes haben.
Da hat emeriks mal wieder recht. Alternative wären mit compress-archive und expand-archive noch zwei Powershell befehle in Reichweite, die viele Dateien zu einer Zusammenpacken. ;)
Also Liste der Dateien nehmen, ein Zip-erstellen, rüber kopieren, entpacken. Ob das allerdings wirklich schneller als der reine Kopiervorgang ist, kann ich nicht abschätzen
Zitat von @Doskias:
Also Liste der Dateien nehmen, ein Zip-erstellen, rüber kopieren, entpacken. Ob das allerdings wirklich schneller als der reine Kopiervorgang ist, kann ich nicht abschätzen
Max. dann, wenn das Entpacken dann auf dem Zielrechner gestartet würde. Sonst macht das keinen Sinn.Also Liste der Dateien nehmen, ein Zip-erstellen, rüber kopieren, entpacken. Ob das allerdings wirklich schneller als der reine Kopiervorgang ist, kann ich nicht abschätzen
Zitat von @erikro:
ohja. Meide die Pipe auf foreach-object. So geht es (genug freien Arbeitsspeicher vorausgesetzt) wahrscheinlich erheblich schneller:
Kannst Du das bitte mal begründen?ohja. Meide die Pipe auf foreach-object. So geht es (genug freien Arbeitsspeicher vorausgesetzt) wahrscheinlich erheblich schneller:
Mal abgesehen davon, dass hier doch das Gros der Dauer durch die Kopiervorgänge verursacht werden dürfte und nicht durch die Verkettung der Ausgaben oder die Enumeration der Elemente.
Servus,
das ist eine schöne Aufgabe für Parallelisierung, natürlich immer nur so weit es Quell- und Zieldatenträger sowie Netzwerkbandbreite es auch zulassen. Threadanzahl lässt sich über den Parameter -ThrottleLimit weiter unten steuern.
Das Liste kann man natürlich alternativ auch an Robocopy verfüttern und es mit mehreren Threads laufen lassen (unterstützt es ja nativ via Parameter).
Grüße Uwe
das ist eine schöne Aufgabe für Parallelisierung, natürlich immer nur so weit es Quell- und Zieldatenträger sowie Netzwerkbandbreite es auch zulassen. Threadanzahl lässt sich über den Parameter -ThrottleLimit weiter unten steuern.
$quelle = 'D:\test\root'
$ziel = 'D:\test\target'
$liste = 'D:\liste.txt'
function %% {
[CmdletBinding()]
param(
[parameter(mandatory=$true,ValueFromPipeline=$true)][ValidateNotNullOrEmpty()][object[]]$InputObject,
[parameter(mandatory=$true)][ValidateNotNullOrEmpty()][scriptblock]$Process,
[parameter(mandatory=$false)][ValidateNotNullOrEmpty()][int]$ThrottleLimit = [System.Environment]::ProcessorCount,
[parameter(mandatory=$false)][switch]$showprogress,
[parameter(mandatory=$false)][ValidateNotNullOrEmpty()][hashtable]$params
)
begin{
$rspool = [runspacefactory]::CreateRunspacePool(1,$ThrottleLimit)
$rspool.ApartmentState = 'STA'
$rspool.Open()
$jobs = New-Object System.Collections.ArrayList
$objects = New-Object System.Collections.ArrayList
}
process{
$InputObject | %{[void]$objects.Add($_)}
}
end{
foreach ($obj in $objects){
$ps = [Powershell]::Create()
$ps.RunspacePool = $rspool
[void]$ps.AddScript($Process).AddParameters(@{'_'=$obj;params=$params})
$job = $ps.BeginInvoke()
[void]$jobs.Add(([pscustomobject]@{Handle = $job; Powershell = $ps}))
}
write-verbose "Waiting for all jobs to complete."
while(($jobs | ?{!$_.Handle.IsCompleted})){
if ($showprogress.IsPresent){
$completed = ($jobs | ?{$_.Handle.IsCompleted}).Count
Write-Progress -Activity $PSCmdlet.MyInvocation.InvocationName -Status "Total of $($objects.Count) objects." -PercentComplete (($completed / $objects.Count) * 100) -CurrentOperation "Completed $completed of $($objects.Count)."
}
}
# get results of jobs
$results = $jobs | %{
$_.Powershell.EndInvoke($_.handle)
$_.Powershell.Dispose()
}
# cleanup
$rspool.Close();$rspool.Dispose()
$results
}
}
gc $liste | ?{Test-Path "$quelle\$_"} | %% -ThrottleLimit 8 -showprogress -params @{quelle=$quelle;ziel=$ziel} -Process {
param($_,$params)
copy-item -LiteralPath "$($params.Quelle)\$_" -Destination $params.ziel -Recurse -Force
}
Das Liste kann man natürlich alternativ auch an Robocopy verfüttern und es mit mehreren Threads laufen lassen (unterstützt es ja nativ via Parameter).
Grüße Uwe
Moin,
Gerne. Guckst Du hier:
https://devblogs.microsoft.com/scripting/getting-to-know-foreach-and-for ...
Liebe Grüße
Erik
Zitat von @emeriks:
Zitat von @erikro:
ohja. Meide die Pipe auf foreach-object. So geht es (genug freien Arbeitsspeicher vorausgesetzt) wahrscheinlich erheblich schneller:
Kannst Du das bitte mal begründen?ohja. Meide die Pipe auf foreach-object. So geht es (genug freien Arbeitsspeicher vorausgesetzt) wahrscheinlich erheblich schneller:
Gerne. Guckst Du hier:
https://devblogs.microsoft.com/scripting/getting-to-know-foreach-and-for ...
Liebe Grüße
Erik
Danke!
Servus
Zitat von @Lupora:
Eine Frage: Ich habe herausgefunden das es mit Powershell 7.x eine ERweiterung für das for-each CMDLEt gibt.
Dort kann man ejtzt den Parameter -parallel mitgeben.
Wäre das dasselbe wie dein Script?
Ja, mein obiges Skript ist nur eine nachgebaute leicht abgewandelte Variante die auch in den älteren PS Varianten läuft, beide nutzen aber die Runspace Pools für das Multithreading.Eine Frage: Ich habe herausgefunden das es mit Powershell 7.x eine ERweiterung für das for-each CMDLEt gibt.
Dort kann man ejtzt den Parameter -parallel mitgeben.
Wäre das dasselbe wie dein Script?
Ja, erstens entferne die überflüssigen Variablendeklarationen in der Schleife die Kosten viel Zeit, zweitens lies den Hinweis der Kollegen zu Foreach-Object im Vergleich zu Foreach wenn es nicht mit -parallel Parameter ausgeführt wird. Foreach-Object ohne -parallel spart zwar Speicher ist dafür aber eben insgesamt langsamer, auch wenn es im Vergleich zu einem Foreach Konstrukt schneller anfängt zu verarbeiten, weil bei letzterem erst mal die gesamte Struktur durchlaufen wird und erst anschließend über ein Array itteriert wird.
SSDs haben meist einen integrierten DRAM Cache der für eine gewisse Datenmenge zu Beginn eine konstant hohe Schreibrate gewährleistet, wenn der aber voll ist fällt die Schreibrate meist stark ab, das kann eine Möglichkeit sein. Die zweite ist das sich durch das massenhafte erneute Zuweisen der Variablen der Speicher füllt und die Garbage-Collection die Verzögerung verursacht. Das alles kannst du im Performance Monitor überprüfen.