Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
ProgressRecord.cs563 linesDownload Raw Back to engine
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