diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
index c8f542052a1..4f51820524c 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
@@ -424,7 +424,7 @@ internal static bool IsMinimalProgressRenderingEnabled()
sb.Append(secRemain);
- if (PercentComplete > 0 && PercentComplete < 100 && barWidth > 0)
+ if (PercentComplete >= 0 && PercentComplete < 100 && barWidth > 0)
{
int barLength = PercentComplete * barWidth / 100;
if (barLength >= barWidth)
diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
index 353aa3e918e..60631f7478b 100644
--- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs
+++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -18,6 +19,7 @@
using System.Security;
using System.Security.AccessControl;
using System.Text;
+using System.Threading.Tasks;
using System.Xml;
using System.Xml.XPath;
@@ -58,6 +60,8 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider,
// copy script will accommodate the new value.
private const int FILETRANSFERSIZE = 4 * 1024 * 1024;
+ private const int COPY_FILE_ACTIVITY_ID = 0;
+
// The name of the key in an exception's Data dictionary when attempting
// to copy an item onto itself.
private const string SelfCopyDataKey = "SelfCopy";
@@ -3548,7 +3552,25 @@ protected override void CopyItem(
}
else // Copy-Item local
{
+ if (Context != null && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference && progressPreference == ActionPreference.Continue)
+ {
+ {
+ Task.Run(() =>
+ {
+ GetTotalFiles(path, recurse);
+ });
+ _copyStopwatch.Start();
+ }
+ }
+
CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null);
+ if (_totalFiles > 0)
+ {
+ _copyStopwatch.Stop();
+ var progress = new ProgressRecord(COPY_FILE_ACTIVITY_ID, " ", " ");
+ progress.RecordType = ProgressRecordType.Completed;
+ WriteProgress(progress);
+ }
}
}
@@ -3556,6 +3578,47 @@ protected override void CopyItem(
_excludeMatcher = null;
}
+ private void GetTotalFiles(string path, bool recurse)
+ {
+ bool isContainer = IsItemContainer(path);
+
+ try
+ {
+ if (isContainer)
+ {
+ var enumOptions = new EnumerationOptions()
+ {
+ IgnoreInaccessible = true,
+ AttributesToSkip = 0,
+ RecurseSubdirectories = recurse
+ };
+
+ var directory = new DirectoryInfo(path);
+ foreach (var file in directory.EnumerateFiles("*", enumOptions))
+ {
+ if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false))
+ {
+ _totalFiles++;
+ _totalBytes += file.Length;
+ }
+ }
+ }
+ else
+ {
+ var file = new FileInfo(path);
+ if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false))
+ {
+ _totalFiles++;
+ _totalBytes += file.Length;
+ }
+ }
+ }
+ catch
+ {
+ // ignore exception
+ }
+ }
+
private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession)
{
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
@@ -3864,6 +3927,22 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force,
FileInfo result = new FileInfo(destinationPath);
WriteItemObject(result, destinationPath, false);
+
+ if (_totalFiles > 0)
+ {
+ _copiedFiles++;
+ _copiedBytes += file.Length;
+ double speed = (double)(_copiedBytes / 1024 / 1024) / _copyStopwatch.Elapsed.TotalSeconds;
+ var progress = new ProgressRecord(
+ COPY_FILE_ACTIVITY_ID,
+ StringUtil.Format(FileSystemProviderStrings.CopyingLocalFileActivity, _copiedFiles, _totalFiles),
+ StringUtil.Format(FileSystemProviderStrings.CopyingLocalBytesStatus, Utils.DisplayHumanReadableFileSize(_copiedBytes), Utils.DisplayHumanReadableFileSize(_totalBytes), speed)
+ );
+ var percentComplete = (int)Math.Min(_copiedBytes * 100 / _totalBytes, 100);
+ progress.PercentComplete = percentComplete;
+ progress.RecordType = ProgressRecordType.Processing;
+ WriteProgress(progress);
+ }
}
else
{
@@ -4788,6 +4867,12 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId)
return pathIsReservedDeviceName;
}
+ private long _totalFiles;
+ private long _totalBytes;
+ private long _copiedFiles;
+ private long _copiedBytes;
+ private readonly Stopwatch _copyStopwatch = new Stopwatch();
+
#endregion CopyItem
#endregion ContainerCmdletProvider members
diff --git a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx
index 436e03df32e..6ffbce6b884 100644
--- a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx
+++ b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx
@@ -339,4 +339,10 @@
The target and path cannot be the same.
+
+ Copied {0} of {1} files
+
+
+ {0} of {1} ({2:0.0} MB/s)
+
diff --git a/test/xUnit/csharp/test_Prediction.cs b/test/xUnit/csharp/test_Prediction.cs
index e69f5b21a9d..6c78cf3a58f 100644
--- a/test/xUnit/csharp/test_Prediction.cs
+++ b/test/xUnit/csharp/test_Prediction.cs
@@ -79,7 +79,7 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex
{
// The delay is exaggerated to make the test reliable.
// xUnit must spin up a lot tasks, which makes the test unreliable when the time difference between 'delay' and 'timeout' is small.
- Thread.Sleep(2000);
+ Thread.Sleep(3000);
}
// You can get the user input from the AST.