script - powershell tutorial
Barra de progreso personalizada de RoboCopy en PowerShell (6)
Me interesa un script de PowerShell que copie una gran cantidad de archivos de un servidor a diario y me interesa implementar una barra de progreso en la consola como
File copy status - XX% complete.
donde XX%
actualiza en la misma línea en lugar de nueva línea después de nueva línea. Decidí ir con RoboCopy por el momento. Actualmente tengo
ROBOCOPY ''C:/Users/JMondy/Desktop/Sample1'' ''C:/Users/JMondy/Desktop/Sample2'' . /E /IS /NFL /NJH
¿Cuál es el próximo paso?
¿Tienes que usar robocopy?
Si no, puede llamar al código en este hilo para cada archivo: Progreso durante la copia de archivos de gran tamaño (Copiar elemento y Escribir-Progreso?)
Alternativamente, utilice el modificador / L de robocopy como se llama desde powershell para obtener la lista de archivos que robocopy habría copiado y utilice un bucle for-each para ejecutar cada archivo a través de esa función de copia.
Incluso puede anidar comandos de progreso de escritura para que pueda informar "archivo x de y - XX% completado"
Algo como esto debería funcionar, necesita un poco de trabajo para los subdirectorios (sospecho que no se trata solo de agregar -curse al comando gci) sino que lo orientará en la dirección correcta.
NOTA: Estoy escribiendo esto en un teléfono, el código aún no ha sido probado ...
function Copy-File {
param( [string]$from, [string]$to)
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
Write-Progress `
-Activity ("Copying file " + $filecount + " of " + $files.count) `
-status ($from.Split("/")|select -last 1) `
-PercentComplete 0
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew();
[byte[]]$buff = new-object byte[] 65536
[long]$total = [long]$count = 0
do {
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
if ($total % 1mb -eq 0) {
if([int]($total/$ffile.Length* 100) -gt 0)`
{[int]$secsleft = ([int]$sw.Elapsed.Seconds/([int]($total/$ffile.Length* 100))*100)
} else {
[int]$secsleft = 0};
Write-Progress `
-Activity ([string]([int]($total/$ffile.Length* 100)) + "% Copying file")`
-status ($from.Split("/")|select -last 1) `
-PercentComplete ([int]($total/$ffile.Length* 100))`
-SecondsRemaining $secsleft;
}
} while ($count -gt 0)
$sw.Stop();
$sw.Reset();
}
finally {
$ffile.Close()
$tofile.Close()
}
}
$srcdir = "C:/Source;
$destdir = "C:/Dest";
[int]$filecount = 0;
$files = (Get-ChildItem $SrcDir | where-object {-not ($_.PSIsContainer)});
$files|foreach($_){
$filecount++
if ([system.io.file]::Exists($destdir+$_.name)){
[system.io.file]::Delete($destdir+$_.name)}
Copy-File -from $_.fullname -to ($destdir+$_.name)
};
Personalmente utilizo este código para pequeñas copias en una memoria USB, pero utilizo robocopy en un script powershell para copias de seguridad de PC.
Aquí está la versión nativa de PowerShell GUI de RoboCopy. (NO archivo EXE)
Espero que esto ayude a alguien.
https://gallery.technet.microsoft.com/PowerShell-Robocopy-GUI-08c9cacb
FYI: ¿Hay alguien que pueda combinar la herramienta GUI de PowerCopy con la barra Copy-WithProgress?
Escribí una función de PowerShell llamada Copy-WithProgress
que logrará lo que Copy-WithProgress
. Como usted indicó específicamente que estaba usando robocopy, construí una función de PowerShell que encapsula la funcionalidad robocopy (al menos, partes de ella).
Permítame mostrarle cómo funciona. También grabé y publiqué un video de YouTube que demuestra cómo está diseñada la función para funcionar e invoco una ejecución de prueba.
La función está dividida en regiones:
- Parámetros robocopy comunes
- Puesta en escena (donde se calcula el tamaño del trabajo robocopy)
- Copiar (donde se inicia el trabajo robocopy)
- Barra de progreso (donde se supervisa el progreso robocopy)
- Salida de función (donde se generan algunas estadísticas útiles, para usar en el resto de su secuencia de comandos)
Hay varios parámetros en la función.
- Fuente : el directorio fuente
- Destino : el directorio de destino
- Brecha : El "espacio entre paquetes" en milisegundos admitido por robocopy, que ralentiza artificialmente la copia, para probarla
- ReportGap : el intervalo (en milisegundos) para verificar el progreso de robocopy
En la parte inferior del script (después de la definición de la función), se encuentra un ejemplo completo de cómo llamarlo. Debería funcionar en su computadora, ya que todo es de tamaño variable. Hay cinco pasos:
- Genera un directorio fuente aleatorio
- Genera un directorio de destino
- Llamar a la función
Copy-WithProgress
- Crea algunos archivos fuente adicionales (para emular los cambios a lo largo del tiempo)
- Llame de nuevo a la función
Copy-WithProgress
y valide solo se replican los cambios
Aquí hay una captura de pantalla de cómo se ve el resultado de la función. Puede dejar fuera el parámetro -Verbose
, si no desea toda la información de depuración. La PSCustomObject
devuelve un PSCustomObject
, que le dice:
- Cuantos bytes se copiaron
- Cuantos archivos fueron copiados
Aquí hay una captura de pantalla de la barra de progreso de PowerShell en PowerShell ISE y el host de la consola de PowerShell.
Aquí está el código:
function Copy-WithProgress {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $Source
, [Parameter(Mandatory = $true)]
[string] $Destination
, [int] $Gap = 200
, [int] $ReportGap = 2000
)
# Define regular expression that will gather number of bytes copied
$RegexBytes = ''(?<=/s+)/d+(?=/s+)'';
#region Robocopy params
# MIR = Mirror mode
# NP = Don''t show progress percentage in log
# NC = Don''t log file classes (existing, new file, etc.)
# BYTES = Show file sizes in bytes
# NJH = Do not display robocopy job header (JH)
# NJS = Do not display robocopy job summary (JS)
# TEE = Display log in stdout AND in target log file
$CommonRobocopyParams = ''/MIR /NP /NDL /NC /BYTES /NJH /NJS'';
#endregion Robocopy params
#region Robocopy Staging
Write-Verbose -Message ''Analyzing robocopy job ...'';
$StagingLogPath = ''{0}/temp/{1} robocopy staging.log'' -f $env:windir, (Get-Date -Format ''yyyy-MM-dd hh-mm-ss'');
$StagingArgumentList = ''"{0}" "{1}" /LOG:"{2}" /L {3}'' -f $Source, $Destination, $StagingLogPath, $CommonRobocopyParams;
Write-Verbose -Message (''Staging arguments: {0}'' -f $StagingArgumentList);
Start-Process -Wait -FilePath robocopy.exe -ArgumentList $StagingArgumentList -NoNewWindow;
# Get the total number of files that will be copied
$StagingContent = Get-Content -Path $StagingLogPath;
$TotalFileCount = $StagingContent.Count - 1;
# Get the total number of bytes to be copied
[RegEx]::Matches(($StagingContent -join "`n"), $RegexBytes) | % { $BytesTotal = 0; } { $BytesTotal += $_.Value; };
Write-Verbose -Message (''Total bytes to be copied: {0}'' -f $BytesTotal);
#endregion Robocopy Staging
#region Start Robocopy
# Begin the robocopy process
$RobocopyLogPath = ''{0}/temp/{1} robocopy.log'' -f $env:windir, (Get-Date -Format ''yyyy-MM-dd hh-mm-ss'');
$ArgumentList = ''"{0}" "{1}" /LOG:"{2}" /ipg:{3} {4}'' -f $Source, $Destination, $RobocopyLogPath, $Gap, $CommonRobocopyParams;
Write-Verbose -Message (''Beginning the robocopy process with arguments: {0}'' -f $ArgumentList);
$Robocopy = Start-Process -FilePath robocopy.exe -ArgumentList $ArgumentList -Verbose -PassThru -NoNewWindow;
Start-Sleep -Milliseconds 100;
#endregion Start Robocopy
#region Progress bar loop
while (!$Robocopy.HasExited) {
Start-Sleep -Milliseconds $ReportGap;
$BytesCopied = 0;
$LogContent = Get-Content -Path $RobocopyLogPath;
$BytesCopied = [Regex]::Matches($LogContent, $RegexBytes) | ForEach-Object -Process { $BytesCopied += $_.Value; } -End { $BytesCopied; };
$CopiedFileCount = $LogContent.Count - 1;
Write-Verbose -Message (''Bytes copied: {0}'' -f $BytesCopied);
Write-Verbose -Message (''Files copied: {0}'' -f $LogContent.Count);
$Percentage = 0;
if ($BytesCopied -gt 0) {
$Percentage = (($BytesCopied/$BytesTotal)*100)
}
Write-Progress -Activity Robocopy -Status ("Copied {0} of {1} files; Copied {2} of {3} bytes" -f $CopiedFileCount, $TotalFileCount, $BytesCopied, $BytesTotal) -PercentComplete $Percentage
}
#endregion Progress loop
#region Function output
[PSCustomObject]@{
BytesCopied = $BytesCopied;
FilesCopied = $CopiedFileCount;
};
#endregion Function output
}
# 1. TESTING: Generate a random, unique source directory, with some test files in it
$TestSource = ''{0}/{1}'' -f $env:temp, [Guid]::NewGuid().ToString();
$null = mkdir -Path $TestSource;
# 1a. TESTING: Create some test source files
1..20 | % -Process { Set-Content -Path $TestSource/$_.txt -Value (''A''*(Get-Random -Minimum 10 -Maximum 2100)); };
# 2. TESTING: Create a random, unique target directory
$TestTarget = ''{0}/{1}'' -f $env:temp, [Guid]::NewGuid().ToString();
$null = mkdir -Path $TestTarget;
# 3. Call the Copy-WithProgress function
Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose;
# 4. Add some new files to the source directory
21..40 | % -Process { Set-Content -Path $TestSource/$_.txt -Value (''A''*(Get-Random -Minimum 950 -Maximum 1400)); };
# 5. Call the Copy-WithProgress function (again)
Copy-WithProgress -Source $TestSource -Destination $TestTarget -Verbose;
Estas soluciones son excelentes, pero una manera rápida y fácil de obtener un progreso flotante para todos los archivos es la siguiente:
robocopy <source> <destination> /MIR /NDL /NJH /NJS | %{$data = $_.Split([char]9); if("$($data[4])" -ne "") { $file = "$($data[4])"} ;Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue; }
Las barras de progreso son agradables y casi todas, pero al copiar cientos de archivos, mostrar el progreso ralentiza la operación, en algunos casos bastante. Esta es una de las razones por las que la ayuda robocopy indica que el indicador / MT debe redirigir la salida al registro para obtener un mejor rendimiento.
Terminé usando esto en base a la respuesta sugerida de Amrinder:
robocopy.exe $Source $Destination $PatternArg $MirrorArg /NDL /NJH /NJS | ForEach-Object -Process {
$data = $_.Split([char]9);
if (($data.Count -gt 4) -and ("$($data[4])" -ne ""))
{
$file = "$($data[4])"
Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)" -ErrorAction SilentlyContinue;
}
else
{
Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)"
}
}
# Robocopy has a bitmask set of exit codes, so only complain about failures:
[int] $exitCode = $global:LastExitCode;
[int] $someCopyErrors = $exitCode -band 8;
[int] $seriousError = $exitCode -band 16;
if (($someCopyErrors -ne 0) -or ($seriousError -ne 0))
{
Write-Error "ERROR: robocopy failed with a non-successful exit code: $exitCode"
exit 1
}
Fyi, Bill