MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.Specialized;7using System.Diagnostics;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.Management.Automation;11using System.Resources;12using System.Threading;13using Microsoft.Powershell.Commands.GetCounter.PdhNative;14using Microsoft.PowerShell.Commands.Diagnostics.Common;15using Microsoft.PowerShell.Commands.GetCounter;16 17namespace Microsoft.PowerShell.Commands18{19 /// <summary>20 /// Class that implements the Get-Counter cmdlet.21 /// </summary>22 [Cmdlet(VerbsCommon.Get, "Counter", DefaultParameterSetName = "GetCounterSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109647")]23 public sealed class GetCounterCommand : PSCmdlet24 {25 //26 // ListSet parameter27 //28 [Parameter(29 Position = 0,30 Mandatory = true,31 ParameterSetName = "ListSetSet",32 ValueFromPipeline = true,33 ValueFromPipelineByPropertyName = false,34 HelpMessageBaseName = "GetEventResources")]35 [AllowEmptyCollection]36 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",37 Scope = "member",38 Target = "Microsoft.PowerShell.Commands.GetCounterCommand.ListSet",39 Justification = "A string[] is required here because that is the type Powershell supports")]40 public string[] ListSet { get; set; } = { "*" };41 42 //43 // Counter parameter44 //45 [Parameter(46 Position = 0,47 ParameterSetName = "GetCounterSet",48 ValueFromPipeline = true,49 ValueFromPipelineByPropertyName = true,50 HelpMessageBaseName = "GetEventResources")]51 [AllowEmptyCollection]52 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",53 Scope = "member",54 Target = "Microsoft.PowerShell.Commands.GetCounterCommand.ListSet",55 Justification = "A string[] is required here because that is the type Powershell supports")]56 public string[] Counter57 {58 get59 {60 return _counter;61 }62 63 set64 {65 _counter = value;66 _defaultCounters = false;67 }68 }69 70 private string[] _counter = {@"\network interface(*)\bytes total/sec",71 @"\processor(_total)\% processor time",72 @"\memory\% committed bytes in use",73 @"\memory\cache faults/sec",74 @"\physicaldisk(_total)\% disk time",75 @"\physicaldisk(_total)\current disk queue length"};76 77 private bool _defaultCounters = true;78 79 private readonly List<string> _accumulatedCounters = new();80 81 //82 // SampleInterval parameter.83 // Defaults to 1 second.84 //85 [Parameter(86 ParameterSetName = "GetCounterSet",87 ValueFromPipeline = false,88 ValueFromPipelineByPropertyName = false,89 HelpMessageBaseName = "GetEventResources")]90 [ValidateRange((int)1, int.MaxValue)]91 public int SampleInterval { get; set; } = 1;92 93 //94 // MaxSamples parameter95 //96 private const Int64 KEEP_ON_SAMPLING = -1;97 98 [Parameter(99 ParameterSetName = "GetCounterSet",100 ValueFromPipeline = false,101 ValueFromPipelineByPropertyName = false,102 HelpMessageBaseName = "GetEventResources")]103 [ValidateRange((Int64)1, Int64.MaxValue)]104 public Int64 MaxSamples105 {106 get107 {108 return _maxSamples;109 }110 111 set112 {113 _maxSamples = value;114 _maxSamplesSpecified = true;115 }116 }117 118 private Int64 _maxSamples = 1;119 private bool _maxSamplesSpecified = false;120 121 //122 // Continuous switch123 //124 [Parameter(ParameterSetName = "GetCounterSet")]125 public SwitchParameter Continuous126 {127 get { return _continuous; }128 129 set { _continuous = value; }130 }131 132 private bool _continuous = false;133 134 //135 // ComputerName parameter136 //137 [Parameter(138 ValueFromPipeline = false,139 ValueFromPipelineByPropertyName = false,140 HelpMessageBaseName = "GetEventResources",141 HelpMessageResourceId = "ComputerNameParamHelp")]142 [ValidateNotNull]143 [AllowEmptyCollection]144 [Alias("Cn")]145 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays",146 Scope = "member",147 Target = "Microsoft.PowerShell.Commands.GetCounterCommand.ComputerName",148 Justification = "A string[] is required here because that is the type Powershell supports")]149 public string[] ComputerName { get; set; } = Array.Empty<string>();150 151 private ResourceManager _resourceMgr = null;152 153 private PdhHelper _pdhHelper = null;154 155 private readonly EventWaitHandle _cancelEventArrived = new(false, EventResetMode.ManualReset);156 157 // Culture identifier(s)158 private const string FrenchCultureId = "fr-FR";159 // The localized Pdh resource strings might use Unicode characters that are different from160 // what the user can type with the keyboard to represent a special character.161 //162 // e.g. the apostrophe in French UI culture: it's [char]39 from keyboard, but it's [char]8217163 // in the resource strings.164 //165 // With this dictionary, we can add special mapping if we find other special cases in the future.166 private readonly Dictionary<string, List<Tuple<char, char>>> _cultureAndSpecialCharacterMap =167 new()168 {169 {170 FrenchCultureId, new List<Tuple<char, char>>()171 {172 // 'APOSTROPHE' to 'RIGHT SINGLE QUOTATION MARK'173 new Tuple<char, char>((char)0x0027, (char)0x2019),174 // 'MODIFIER LETTER APOSTROPHE' to 'RIGHT SINGLE QUOTATION MARK'175 new Tuple<char, char>((char)0x02BC, (char)0x2019),176 // 'HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT' to 'RIGHT SINGLE QUOTATION MARK'177 new Tuple<char, char>((char)0x275C, (char)0x2019),178 }179 }180 };181 182 //183 // BeginProcessing() is invoked once per pipeline184 //185 protected override void BeginProcessing()186 {187 if (Platform.IsIoT)188 {189 // IoT does not have the '$env:windir\System32\pdh.dll' assembly which is required by this cmdlet.190 throw new PlatformNotSupportedException();191 }192 193 _pdhHelper = new PdhHelper();194 _resourceMgr = Microsoft.PowerShell.Commands.Diagnostics.Common.CommonUtilities.GetResourceManager();195 196 uint res = _pdhHelper.ConnectToDataSource();197 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)198 {199 ReportPdhError(res, true);200 return;201 }202 203 if (Continuous.IsPresent && _maxSamplesSpecified)204 {205 Exception exc = new(string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterContinuousOrMaxSamples")));206 ThrowTerminatingError(new ErrorRecord(exc, "CounterContinuousOrMaxSamples", ErrorCategory.InvalidArgument, null));207 }208 }209 210 //211 // EndProcessing() is invoked once per pipeline212 //213 protected override void EndProcessing()214 {215 if (ParameterSetName == "GetCounterSet")216 {217 ProcessGetCounter();218 }219 220 _pdhHelper.Dispose();221 }222 223 //224 // Handle Control-C225 //226 protected override void StopProcessing()227 {228 _cancelEventArrived.Set();229 _pdhHelper.Dispose();230 }231 232 //233 // ProcessRecord() override.234 // This is the main entry point for the cmdlet.235 //236 protected override void ProcessRecord()237 {238 try239 {240 switch (ParameterSetName)241 {242 case "ListSetSet":243 ProcessListSet();244 break;245 246 case "GetCounterSet":247 AccumulatePipelineCounters();248 break;249 250 default:251 Debug.Fail(string.Create(CultureInfo.InvariantCulture, $"Invalid parameter set name: {ParameterSetName}"));252 break;253 }254 }255 catch (Exception exc)256 {257 ThrowTerminatingError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null));258 }259 }260 261 //262 // AccumulatePipelineCounters() accumulates counter paths in the pipeline scenario:263 // we do not want to start sampling until all the counters are supplied.264 //265 private void AccumulatePipelineCounters()266 {267 _accumulatedCounters.AddRange(_counter);268 }269 270 //271 // ProcessListSet() does the work to process ListSet parameter set.272 //273 private void ProcessListSet()274 {275 if (ComputerName.Length == 0)276 {277 ProcessListSetPerMachine(null);278 }279 else280 foreach (string machine in ComputerName)281 {282 ProcessListSetPerMachine(machine);283 }284 }285 286 //287 // ProcessListSetPerMachine() helper lists counter sets on a machine.288 // NOTE: machine argument should be NULL for the local machine289 //290 private void ProcessListSetPerMachine(string machine)291 {292 StringCollection counterSets = new();293 uint res = _pdhHelper.EnumObjects(machine, ref counterSets);294 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)295 {296 // add an error message297 string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("NoCounterSetsOnComputer"), machine, res);298 Exception exc = new(msg);299 WriteError(new ErrorRecord(exc, "NoCounterSetsOnComputer", ErrorCategory.InvalidResult, machine));300 return;301 }302 303 CultureInfo culture = GetCurrentCulture();304 List<Tuple<char, char>> characterReplacementList = null;305 StringCollection validPaths = new();306 307 _cultureAndSpecialCharacterMap.TryGetValue(culture.Name, out characterReplacementList);308 309 foreach (string pattern in ListSet)310 {311 bool bMatched = false;312 string normalizedPattern = pattern;313 314 if (characterReplacementList != null)315 {316 foreach (Tuple<char, char> pair in characterReplacementList)317 {318 normalizedPattern = normalizedPattern.Replace(pair.Item1, pair.Item2);319 }320 }321 322 WildcardPattern wildLogPattern = new(normalizedPattern, WildcardOptions.IgnoreCase);323 324 foreach (string counterSet in counterSets)325 {326 if (!wildLogPattern.IsMatch(counterSet))327 {328 continue;329 }330 331 StringCollection counterSetCounters = new();332 StringCollection counterSetInstances = new();333 334 res = _pdhHelper.EnumObjectItems(machine, counterSet, ref counterSetCounters, ref counterSetInstances);335 if (res == PdhResults.PDH_ACCESS_DENIED)336 {337 string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterSetEnumAccessDenied"), counterSet);338 Exception exc = new(msg);339 WriteError(new ErrorRecord(exc, "CounterSetEnumAccessDenied", ErrorCategory.InvalidResult, null));340 continue;341 }342 else if (res != PdhResults.PDH_CSTATUS_VALID_DATA)343 {344 ReportPdhError(res, false);345 continue;346 }347 348 string[] instanceArray = new string[counterSetInstances.Count];349 int i = 0;350 foreach (string instance in counterSetInstances)351 {352 instanceArray[i++] = instance;353 }354 355 //356 // Special case: no instances present: change to * to create a valid paths357 //358 if (instanceArray.Length == 1 &&359 instanceArray[0].Length == 0)360 {361 instanceArray[0] = "*";362 }363 364 Dictionary<string, string[]> counterInstanceMapping = new();365 foreach (string counter in counterSetCounters)366 {367 counterInstanceMapping.TryAdd(counter, instanceArray);368 }369 370 PerformanceCounterCategoryType categoryType = PerformanceCounterCategoryType.Unknown;371 if (counterSetInstances.Count > 1)372 {373 categoryType = PerformanceCounterCategoryType.MultiInstance;374 }375 else // if (counterSetInstances.Count == 1) //???376 {377 categoryType = PerformanceCounterCategoryType.SingleInstance;378 }379 380 string setHelp = _pdhHelper.GetCounterSetHelp(machine, counterSet);381 382 CounterSet setObj = new(counterSet, machine, categoryType, setHelp, ref counterInstanceMapping);383 WriteObject(setObj);384 bMatched = true;385 }386 387 if (!bMatched)388 {389 string msg = _resourceMgr.GetString("NoMatchingCounterSetsFound");390 Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg,391 machine ?? "localhost", normalizedPattern));392 WriteError(new ErrorRecord(exc, "NoMatchingCounterSetsFound", ErrorCategory.ObjectNotFound, null));393 }394 }395 }396 397 //398 // ProcessGetCounter()399 // Does the work to process GetCounterSet parameter set.400 //401 private void ProcessGetCounter()402 {403 // 1. Combine machine names with paths, if needed, to construct full paths404 // 2. Translate default paths into current locale405 // 3. Expand wildcards and validate the paths and write errors for any invalid paths406 // 4. OpenQuery/ AddCounters407 // 5. Skip the first reading408 409 CultureInfo culture = GetCurrentCulture();410 List<Tuple<char, char>> characterReplacementList = null;411 List<string> paths = CombineMachinesAndCounterPaths();412 413 if (!_defaultCounters)414 {415 _cultureAndSpecialCharacterMap.TryGetValue(culture.Name, out characterReplacementList);416 }417 418 StringCollection allExpandedPaths = new();419 uint res;420 foreach (string path in paths)421 {422 string localizedPath = path;423 if (_defaultCounters)424 {425 res = _pdhHelper.TranslateLocalCounterPath(path, out localizedPath);426 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)427 {428 string msg = string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterPathTranslationFailed"), res);429 Exception exc = new(msg);430 WriteError(new ErrorRecord(exc, "CounterPathTranslationFailed", ErrorCategory.InvalidResult, null));431 432 localizedPath = path;433 }434 }435 else if (characterReplacementList != null)436 {437 foreach (Tuple<char, char> pair in characterReplacementList)438 {439 localizedPath = localizedPath.Replace(pair.Item1, pair.Item2);440 }441 }442 443 StringCollection expandedPaths;444 res = _pdhHelper.ExpandWildCardPath(localizedPath, out expandedPaths);445 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)446 {447 WriteDebug("Could not expand path " + localizedPath);448 ReportPdhError(res, false);449 continue;450 }451 452 foreach (string expandedPath in expandedPaths)453 {454 if (!_pdhHelper.IsPathValid(expandedPath))455 {456 string msg = string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterPathIsInvalid"), localizedPath);457 Exception exc = new(msg);458 WriteError(new ErrorRecord(exc, "CounterPathIsInvalid", ErrorCategory.InvalidResult, null));459 460 continue;461 }462 463 allExpandedPaths.Add(expandedPath);464 }465 }466 467 if (allExpandedPaths.Count == 0)468 {469 return;470 }471 472 res = _pdhHelper.OpenQuery();473 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)474 {475 ReportPdhError(res, false);476 }477 478 res = _pdhHelper.AddCounters(ref allExpandedPaths, true);479 if (res != PdhResults.PDH_CSTATUS_VALID_DATA)480 {481 ReportPdhError(res, true);482 483 return;484 }485 486 PerformanceCounterSampleSet nextSet;487 488 bool bSkip = true;489 uint sampleReads = 0;490 491 if (Continuous.IsPresent)492 {493 _maxSamples = KEEP_ON_SAMPLING;494 }495 496 while (true)497 {498 // read the first set just to get the initial values499 res = _pdhHelper.ReadNextSet(out nextSet, bSkip);500 501 if (res == PdhResults.PDH_CSTATUS_VALID_DATA)502 {503 // Display valid data504 if (!bSkip)505 {506 WriteSampleSetObject(nextSet);507 sampleReads++;508 }509 510 // Don't need to skip anymore511 bSkip = false;512 }513 else if (res == PdhResults.PDH_NO_DATA || res == PdhResults.PDH_INVALID_DATA)514 {515 // The provider may not be running.516 // We should keep on trying - but skip the next valid reading.517 518 ReportPdhError(res, false);519 520 bSkip = true;521 522 // Count this failed attempt as a sample:523 sampleReads++;524 }525 else526 {527 // Unexpected error, return528 ReportPdhError(res, true);529 return;530 }531 532 if (_maxSamples != KEEP_ON_SAMPLING && sampleReads >= _maxSamples)533 {534 break;535 }536 537 bool cancelled = _cancelEventArrived.WaitOne((int)SampleInterval * 1000, true);538 if (cancelled)539 {540 break;541 }542 }543 }544 545 private void ReportPdhError(uint res, bool bTerminate)546 {547 string msg;548 uint formatRes = CommonUtilities.FormatMessageFromModule(res, "pdh.dll", out msg);549 if (formatRes != 0)550 {551 msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterApiError"), res);552 }553 554 Exception exc = new(msg);555 if (bTerminate)556 {557 ThrowTerminatingError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null));558 }559 else560 {561 WriteError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null));562 }563 }564 565 //566 // CombineMachinesAndCounterPaths() helper.567 // For paths that do not contain machine names, creates a path for each machine in machineNames.568 // Paths already containing a machine name will be preserved.569 //570 private List<string> CombineMachinesAndCounterPaths()571 {572 List<string> retColl = new();573 574 if (ComputerName.Length == 0)575 {576 retColl.AddRange(_accumulatedCounters);577 return retColl;578 }579 580 foreach (string path in _accumulatedCounters)581 {582 if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) // NOTE: can we do anything smarter here?583 {584 retColl.Add(path);585 }586 else587 {588 foreach (string machine in ComputerName)589 {590 string slashBeforePath = path.Length > 0 && path[0] == '\\' ? string.Empty : "\\";591 if (machine.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase))592 {593 retColl.Add(machine + slashBeforePath + path);594 }595 else596 {597 retColl.Add("\\\\" + machine + slashBeforePath + path);598 }599 }600 }601 }602 603 return retColl;604 }605 606 //607 // WriteSampleSetObject() helper.608 // In addition to writing the PerformanceCounterSampleSet object,609 // it writes a single error if one of the samples has an invalid (non-zero) status.610 //611 private void WriteSampleSetObject(PerformanceCounterSampleSet set)612 {613 foreach (PerformanceCounterSample sample in set.CounterSamples)614 {615 if (sample.Status != 0)616 {617 string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterSampleDataInvalid"));618 Exception exc = new(msg);619 WriteError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null));620 break;621 }622 }623 624 WriteObject(set);625 }626 627 private static CultureInfo GetCurrentCulture()628 {629 return Thread.CurrentThread.CurrentUICulture;630 }631 }632}633 