MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Runtime.Serialization;5 6using Dbg = System.Management.Automation.Diagnostics;7 8namespace System.Management.Automation9{10 /// <summary>11 /// Defines a data structure used to represent the status of an ongoing operation at a point in time.12 /// </summary>13 /// <remarks>14 /// ProgressRecords are passed to <see cref="System.Management.Automation.Cmdlet.WriteProgress(ProgressRecord)"/>,15 /// which, according to user preference, forwards that information on to the host for rendering to the user.16 /// </remarks>17 /// <seealso cref="System.Management.Automation.Cmdlet.WriteProgress(ProgressRecord)"/>18 [DataContract]19 public20 class ProgressRecord21 {22 #region Public API23 24 /// <summary>25 /// Initializes a new instance of the ProgressRecord class and defines the activity Id,26 /// activity description, and status description.27 /// </summary>28 /// <param name="activityId">29 /// A unique numeric key that identifies the activity to which this record applies.30 /// </param>31 /// <param name="activity">32 /// A description of the activity for which progress is being reported.33 /// </param>34 /// <param name="statusDescription">35 /// A description of the status of the activity.36 /// </param>37 public38 ProgressRecord(int activityId, string activity, string statusDescription)39 {40 if (activityId < 0)41 {42 // negative Ids are reserved to indicate "no id" for parent Ids.43 44 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(activityId), activityId, ProgressRecordStrings.ArgMayNotBeNegative, "activityId");45 }46 47 if (string.IsNullOrEmpty(activity))48 {49 throw PSTraceSource.NewArgumentException(nameof(activity), ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "activity");50 }51 52 if (string.IsNullOrEmpty(statusDescription))53 {54 throw PSTraceSource.NewArgumentException(nameof(activity), ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "statusDescription");55 }56 57 this.id = activityId;58 this.activity = activity;59 this.status = statusDescription;60 }61 62 /// <summary>63 /// Initializes a new instance of the ProgressRecord class and defines the activity Id.64 /// </summary>65 /// <param name="activityId">66 /// A unique numeric key that identifies the activity to which this record applies.67 /// </param>68 public69 ProgressRecord(int activityId)70 {71 if (activityId < 0)72 {73 // negative Ids are reserved to indicate "no id" for parent Ids.74 75 throw PSTraceSource.NewArgumentOutOfRangeException(nameof(activityId), activityId, ProgressRecordStrings.ArgMayNotBeNegative, "activityId");76 }77 78 this.id = activityId;79 }80 81 /// <summary>82 /// Cloning constructor (all fields are value types - can treat our implementation of cloning as "deep" copy)83 /// </summary>84 /// <param name="other"></param>85 internal ProgressRecord(ProgressRecord other)86 {87 this.activity = other.activity;88 this.currentOperation = other.currentOperation;89 this.id = other.id;90 this.parentId = other.parentId;91 this.percent = other.percent;92 this.secondsRemaining = other.secondsRemaining;93 this.status = other.status;94 this.type = other.type;95 }96 97 /// <summary>98 /// Gets the Id of the activity to which this record corresponds. Used as a 'key' for the99 /// linking of subordinate activities.100 /// </summary>101 public102 int103 ActivityId104 {105 get106 {107 return id;108 }109 }110 111 /// <summary>112 /// Gets and sets the Id of the activity for which this record is a subordinate.113 /// </summary>114 /// <remarks>115 /// Used to allow chaining of progress records (such as when one installation invokes a child installation). UI:116 /// normally not directly visible except as already displayed as its own activity. Usually a sub-activity will be117 /// positioned below and to the right of its parent.118 ///119 /// A negative value (the default) indicates that the activity is not a subordinate.120 ///121 /// May not be the same as ActivityId.122 /// <!--NTRAID#Windows OS Bugs-1161549 the default value for this should be picked up from a variable in the123 /// shell so that a script can set that variable, and have all subsequent calls to WriteProgress (the API) be124 /// subordinate to the "current parent id".-->125 /// </remarks>126 public127 int128 ParentActivityId129 {130 get131 {132 return parentId;133 }134 135 set136 {137 if (value == ActivityId)138 {139 throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ParentActivityIdCantBeActivityId);140 }141 142 parentId = value;143 }144 }145 146 /// <summary>147 /// Gets and sets the description of the activity for which progress is being reported.148 /// </summary>149 /// <remarks>150 /// States the overall intent of whats being accomplished, such as "Recursively removing item c:\temp." Typically151 /// displayed in conjunction with a progress bar.152 /// </remarks>153 public154 string155 Activity156 {157 get158 {159 return activity;160 }161 162 set163 {164 if (string.IsNullOrEmpty(value))165 {166 throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "value");167 }168 169 activity = value;170 }171 }172 173 /// <summary>174 /// Gets and sets the current status of the operation, e.g., "35 of 50 items Copied." or "95% completed." or "100 files purged."175 /// </summary>176 public177 string178 StatusDescription179 {180 get181 {182 return status;183 }184 185 set186 {187 if (string.IsNullOrEmpty(value))188 {189 throw PSTraceSource.NewArgumentException("value", ProgressRecordStrings.ArgMayNotBeNullOrEmpty, "value");190 }191 192 status = value;193 }194 }195 196 /// <summary>197 /// Gets and sets the current operation of the many required to accomplish the activity (such as "copying foo.txt"). Normally displayed198 /// below its associated progress bar, e.g., "deleting file foo.bar"199 /// Set to null or empty in the case a sub-activity will be used to show the current operation.200 /// </summary>201 public202 string203 CurrentOperation204 {205 get206 {207 return currentOperation;208 }209 210 set211 {212 // null or empty string is allowed213 214 currentOperation = value;215 }216 }217 218 /// <summary>219 /// Gets and sets the estimate of the percentage of total work for the activity that is completed. Typically displayed as a progress bar.220 /// Set to a negative value to indicate that the percentage completed should not be displayed.221 /// </summary>222 public223 int224 PercentComplete225 {226 get227 {228 return percent;229 }230 231 set232 {233 // negative values are allowed234 235 if (value > 100)236 {237 throw238 PSTraceSource.NewArgumentOutOfRangeException(239 "value", value, ProgressRecordStrings.PercentMayNotBeMoreThan100, "PercentComplete");240 }241 242 percent = value;243 }244 }245 246 /// <summary>247 /// Gets and sets the estimate of time remaining until this activity is completed. This can be based upon a measurement of time since248 /// started and the percent complete or another approach deemed appropriate by the caller.249 ///250 /// Normally displayed beside the progress bar, as "N seconds remaining."251 /// </summary>252 /// <remarks>253 /// A value less than 0 means "don't display a time remaining."254 /// </remarks>255 public256 int257 SecondsRemaining258 {259 get260 {261 return secondsRemaining;262 }263 264 set265 {266 // negative values are allowed267 268 secondsRemaining = value;269 }270 }271 272 /// <summary>273 /// Gets and sets the type of record represented by this instance.274 /// </summary>275 public276 ProgressRecordType277 RecordType278 {279 get280 {281 return type;282 }283 284 set285 {286 if (value != ProgressRecordType.Completed && value != ProgressRecordType.Processing)287 {288 throw PSTraceSource.NewArgumentException("value");289 }290 291 type = value;292 }293 }294 295 /// <summary>296 /// Overrides <see cref="object.ToString"/>297 /// </summary>298 /// <returns>299 /// "parent = a id = b act = c stat = d cur = e pct = f sec = g type = h" where300 /// a, b, c, d, e, f, and g are the values of ParentActivityId, ActivityId, Activity, StatusDescription,301 /// CurrentOperation, PercentComplete, SecondsRemaining and RecordType properties.302 /// </returns>303 public override304 string305 ToString()306 {307 return308 string.Format(309 System.Globalization.CultureInfo.CurrentCulture,310 "parent = {0} id = {1} act = {2} stat = {3} cur = {4} pct = {5} sec = {6} type = {7}",311 parentId,312 id,313 activity,314 status,315 currentOperation,316 percent,317 secondsRemaining,318 type);319 }320 321 #endregion322 323 #region Helper methods324 325 internal static int? GetSecondsRemaining(DateTime startTime, double percentageComplete)326 {327 Dbg.Assert(percentageComplete >= 0.0, "Caller should verify percentageComplete >= 0.0");328 Dbg.Assert(percentageComplete <= 1.0, "Caller should verify percentageComplete <= 1.0");329 Dbg.Assert(330 startTime.Kind == DateTimeKind.Utc,331 "DateTime arithmetic should always be done in utc mode [to avoid problems when some operands are calculated right before and right after switching to /from a daylight saving time");332 333 if ((percentageComplete < 0.00001) || double.IsNaN(percentageComplete))334 {335 return null;336 }337 338 DateTime now = DateTime.UtcNow;339 Dbg.Assert(startTime <= now, "Caller should pass a valid startTime");340 TimeSpan elapsedTime = now - startTime;341 342 TimeSpan totalTime;343 try344 {345 totalTime = TimeSpan.FromMilliseconds(elapsedTime.TotalMilliseconds / percentageComplete);346 }347 catch (OverflowException)348 {349 return null;350 }351 catch (ArgumentException)352 {353 return null;354 }355 356 TimeSpan remainingTime = totalTime - elapsedTime;357 358 return (int)(remainingTime.TotalSeconds);359 }360 361 /// <summary>362 /// Returns percentage complete when it is impossible to predict how long an operation might take.363 /// The percentage complete will slowly converge toward 100%.364 /// At the <paramref name="expectedDuration"/> the percentage complete will be 90%.365 /// </summary>366 /// <param name="startTime">When did the operation start.</param>367 /// <param name="expectedDuration">How long does the operation usually take.</param>368 /// <returns>Estimated percentage complete of the operation (always between 0 and 99% - never returns 100%).</returns>369 /// <exception cref="ArgumentOutOfRangeException">370 /// Thrown when371 /// 1) <paramref name="startTime"/> is in the future372 /// 2) <paramref name="expectedDuration"/> is negative or zero373 /// </exception>374 internal static int GetPercentageComplete(DateTime startTime, TimeSpan expectedDuration)375 {376 DateTime now = DateTime.UtcNow;377 378 Dbg.Assert(379 startTime.Kind == DateTimeKind.Utc,380 "DateTime arithmetic should always be done in utc mode [to avoid problems when some operands are calculated right before and right after switching to /from a daylight saving time");381 382 ArgumentOutOfRangeException.ThrowIfGreaterThan(startTime, now);383 ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(expectedDuration, TimeSpan.Zero);384 385 /*386 * According to the spec of Checkpoint-Computer387 * (http://cmdletdesigner/SpecViewer/Default.aspx?Project=PowerShell&Cmdlet=Checkpoint-Computer)388 * we have percentage remaining = f(t) where389 * f(inf) = 0%390 * f(0) = 100%391 * f(90) = <something small> = 10%392 *393 * The spec talks about exponential decay, but function based on 1/x seems better:394 * f(t) = a / (T + b)395 *396 * This by definition has f(inf) = 0, so we have to find a and b for the last 2 cases:397 * E1: f(0) = a / (0 + b) = 100398 * E2: f(T = 90) = a / (T + b) = 10399 *400 * From E1 we have a = 100 * b, which we can use in E2:401 * (100 * b) / (T + b) = 10402 * 100 * b = 10 * T + 10 * b403 * 90 * b = 10 * T404 * b = T / 9405 *406 * Some sample values (for T=90):407 * t | %rem408 * -----------409 * 0 | 100.0%410 * 5 | 66.6%411 * 10 | 50.0%412 * 30 | 25.0%413 * 70 | 12.5%414 * 90 | 10.0%415 * 300 | 3.2%416 * 600 | 1.6%417 * 3600| 0.2%418 */419 TimeSpan timeElapsed = now - startTime;420 double b = expectedDuration.TotalSeconds / 9.0;421 double a = 100.0 * b;422 double percentageRemaining = a / (timeElapsed.TotalSeconds + b);423 double percentageCompleted = 100.0 - percentageRemaining;424 425 return (int)Math.Floor(percentageCompleted);426 }427 428 #endregion429 430 #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell431 432 [DataMember]433 private readonly int id;434 435 [DataMember]436 private int parentId = -1;437 438 [DataMember]439 private string activity;440 441 [DataMember]442 private string status;443 444 [DataMember]445 private string currentOperation;446 447 [DataMember]448 private int percent = -1;449 450 [DataMember]451 private int secondsRemaining = -1;452 453 [DataMember]454 private ProgressRecordType type = ProgressRecordType.Processing;455 456 #endregion457 458 #region Serialization / deserialization for remoting459 460 /// <summary>461 /// Creates a ProgressRecord object from a PSObject property bag.462 /// PSObject has to be in the format returned by ToPSObjectForRemoting method.463 /// </summary>464 /// <param name="progressAsPSObject">PSObject to rehydrate.</param>465 /// <returns>466 /// ProgressRecord rehydrated from a PSObject property bag467 /// </returns>468 /// <exception cref="ArgumentNullException">469 /// Thrown if the PSObject is null.470 /// </exception>471 /// <exception cref="System.Management.Automation.Remoting.PSRemotingDataStructureException">472 /// Thrown when the PSObject is not in the expected format473 /// </exception>474 internal static ProgressRecord FromPSObjectForRemoting(PSObject progressAsPSObject)475 {476 if (progressAsPSObject == null)477 {478 throw PSTraceSource.NewArgumentNullException(nameof(progressAsPSObject));479 }480 481 string activity = RemotingDecoder.GetPropertyValue<string>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_Activity);482 int activityId = RemotingDecoder.GetPropertyValue<int>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_ActivityId);483 string statusDescription = RemotingDecoder.GetPropertyValue<string>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_StatusDescription);484 485 ProgressRecord result = new ProgressRecord(activityId, activity, statusDescription);486 487 result.CurrentOperation = RemotingDecoder.GetPropertyValue<string>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_CurrentOperation);488 result.ParentActivityId = RemotingDecoder.GetPropertyValue<int>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_ParentActivityId);489 result.PercentComplete = RemotingDecoder.GetPropertyValue<int>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_PercentComplete);490 result.RecordType = RemotingDecoder.GetPropertyValue<ProgressRecordType>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_Type);491 result.SecondsRemaining = RemotingDecoder.GetPropertyValue<int>(progressAsPSObject, RemoteDataNameStrings.ProgressRecord_SecondsRemaining);492 493 return result;494 }495 496 /// <summary>497 /// Returns this object as a PSObject property bag498 /// that can be used in a remoting protocol data object.499 /// </summary>500 /// <returns>This object as a PSObject property bag.</returns>501 internal PSObject ToPSObjectForRemoting()502 {503 // Activity used to be mandatory but that's no longer the case.504 // We ensure the string has a value to maintain compatibility with older versions.505 string activity = string.IsNullOrEmpty(Activity) ? " " : Activity;506 507 PSObject progressAsPSObject = RemotingEncoder.CreateEmptyPSObject();508 509 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_Activity, activity));510 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_ActivityId, this.ActivityId));511 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_StatusDescription, this.StatusDescription));512 513 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_CurrentOperation, this.CurrentOperation));514 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_ParentActivityId, this.ParentActivityId));515 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_PercentComplete, this.PercentComplete));516 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_Type, this.RecordType));517 progressAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.ProgressRecord_SecondsRemaining, this.SecondsRemaining));518 519 return progressAsPSObject;520 }521 522 #endregion523 }524 525 /// <summary>526 /// Defines two types of progress record that refer to the beginning (or middle) and end of an operation.527 /// </summary>528 public529 enum ProgressRecordType530 {531 /// <summary>532 /// <para>533 /// Operation just started or is not yet complete.534 /// </para>535 /// <para>536 /// A cmdlet can call WriteProgress with ProgressRecordType.Processing537 /// as many times as it wishes. However, at the end of the operation,538 /// it should call once more with ProgressRecordType.Completed.539 ///540 /// The first time that a host receives a progress record541 /// for a given activity, it will typically display a progress542 /// indicator for that activity. For each subsequent record543 /// of the same Id, the host will update that display.544 /// Finally, when the host receives a 'completed' record545 /// for that activity, it will remove the progress indicator.546 /// </para>547 /// </summary>548 Processing,549 550 /// <summary>551 /// <para>552 /// Operation is complete.553 /// </para>554 /// <para>555 /// If a cmdlet uses WriteProgress, it should use556 /// ProgressRecordType.Completed exactly once, in the last call557 /// to WriteProgress.558 /// </para>559 /// </summary>560 Completed561 }562}563 