MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#if !UNIX5 6using System;7using System.Collections;8using System.Collections.Generic;9using System.Collections.ObjectModel;10using System.ComponentModel;11using System.Diagnostics.CodeAnalysis;12using System.Globalization;13using System.IO;14using System.Linq;15using System.Management.Automation;16using System.Management.Automation.Internal;17using System.Net;18using System.Runtime.Serialization;19using System.Security.Cryptography;20using System.Text;21using System.Threading;22using Microsoft.Management.Infrastructure;23using Microsoft.Management.Infrastructure.Options;24using Microsoft.Win32;25using Dbg = System.Management.Automation;26 27namespace Microsoft.PowerShell.Commands28{29 #region Restart-Computer30 31 /// <summary>32 /// This exception is thrown when the timeout expires before a computer finishes restarting.33 /// </summary>34 public sealed class RestartComputerTimeoutException : RuntimeException35 {36 /// <summary>37 /// Name of the computer that is restarting.38 /// </summary>39 public string ComputerName { get; }40 41 /// <summary>42 /// The timeout value specified by the user. It indicates the seconds to wait before timeout.43 /// </summary>44 public int Timeout { get; }45 46 /// <summary>47 /// Construct a RestartComputerTimeoutException.48 /// </summary>49 /// <param name="computerName"></param>50 /// <param name="timeout"></param>51 /// <param name="message"></param>52 /// <param name="errorId"></param>53 internal RestartComputerTimeoutException(string computerName, int timeout, string message, string errorId)54 : base(message)55 {56 SetErrorId(errorId);57 SetErrorCategory(ErrorCategory.OperationTimeout);58 ComputerName = computerName;59 Timeout = timeout;60 }61 62 /// <summary>63 /// Construct a RestartComputerTimeoutException.64 /// </summary>65 public RestartComputerTimeoutException() : base() { }66 67 /// <summary>68 /// Constructs a RestartComputerTimeoutException.69 /// </summary>70 /// <param name="message">71 /// The message used in the exception.72 /// </param>73 public RestartComputerTimeoutException(string message) : base(message) { }74 75 /// <summary>76 /// Constructs a RestartComputerTimeoutException.77 /// </summary>78 /// <param name="message">79 /// The message used in the exception.80 /// </param>81 /// <param name="innerException">82 /// An exception that led to this exception.83 /// </param>84 public RestartComputerTimeoutException(string message, Exception innerException) : base(message, innerException) { }85 }86 87 /// <summary>88 /// Defines the services that Restart-Computer can wait on.89 /// </summary>90 [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]91 public enum WaitForServiceTypes92 {93 /// <summary>94 /// Wait for the WMI service to be ready.95 /// </summary>96 Wmi = 0x0,97 98 /// <summary>99 /// Wait for the WinRM service to be ready.100 /// </summary>101 WinRM = 0x1,102 103 /// <summary>104 /// Wait for the PowerShell to be ready.105 /// </summary>106 PowerShell = 0x2,107 }108 109 /// <summary>110 /// Restarts the computer.111 /// </summary>112 [Cmdlet(VerbsLifecycle.Restart, "Computer", SupportsShouldProcess = true, DefaultParameterSetName = DefaultParameterSet,113 HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097060", RemotingCapability = RemotingCapability.OwnedByCommand)]114 public class RestartComputerCommand : PSCmdlet, IDisposable115 {116 #region "Parameters and PrivateData"117 118 private const string DefaultParameterSet = "DefaultSet";119 private const int forcedReboot = 6; // see https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx120 121 /// <summary>122 /// The authentication options for CIM_WSMan connection.123 /// </summary>124 [Parameter(ParameterSetName = DefaultParameterSet)]125 [ValidateSet(126 "Default",127 "Basic",128 "Negotiate", // can be used with and without credential (without -> PSRP mapped to NegotiateWithImplicitCredential)129 "CredSSP",130 "Digest",131 "Kerberos")] // can be used with explicit or implicit credential132 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]133 public string WsmanAuthentication { get; set; }134 135 /// <summary>136 /// Specifies the computer (s)Name on which this command is executed.137 /// When this parameter is omitted, this cmdlet restarts the local computer.138 /// Type the NETBIOS name, IP address, or fully-qualified domain name of one139 /// or more computers in a comma-separated list. To specify the local computer, type the computername or "localhost".140 /// </summary>141 [Parameter(Position = 0, ValueFromPipeline = true,142 ValueFromPipelineByPropertyName = true)]143 [ValidateNotNullOrEmpty]144 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]145 [Alias("CN", "__SERVER", "Server", "IPAddress")]146 public string[] ComputerName { get; set; } = new string[] { "." };147 148 private List<string> _validatedComputerNames = new();149 private readonly List<string> _waitOnComputers = new();150 private readonly HashSet<string> _uniqueComputerNames = new(StringComparer.OrdinalIgnoreCase);151 152 /// <summary>153 /// The following is the definition of the input parameter "Credential".154 /// Specifies a user account that has permission to perform this action. Type a155 /// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential156 /// object, such as one from the Get-Credential cmdlet.157 /// </summary>158 [Parameter(Position = 1)]159 [ValidateNotNullOrEmpty]160 [Credential]161 public PSCredential Credential { get; set; }162 163 /// <summary>164 /// Using Force in conjunction with Reboot on a165 /// remote computer immediately reboots the remote computer.166 /// </summary>167 [Parameter]168 [Alias("f")]169 public SwitchParameter Force { get; set; }170 171 /// <summary>172 /// Specify the Wait parameter. Prompt will be blocked is the Timeout is not 0.173 /// </summary>174 [Parameter(ParameterSetName = DefaultParameterSet)]175 public SwitchParameter Wait { get; set; }176 177 /// <summary>178 /// Specify the Timeout parameter.179 /// Negative value indicates wait infinitely.180 /// Positive value indicates the seconds to wait before timeout.181 /// </summary>182 [Parameter(ParameterSetName = DefaultParameterSet)]183 [Alias("TimeoutSec")]184 [ValidateRange(-1, int.MaxValue)]185 public int Timeout186 {187 get188 {189 return _timeout;190 }191 192 set193 {194 _timeout = value;195 _timeoutSpecified = true;196 }197 }198 199 private int _timeout = -1;200 private bool _timeoutSpecified = false;201 202 /// <summary>203 /// Specify the For parameter.204 /// Wait for the specific service before unblocking the prompt.205 /// </summary>206 [Parameter(ParameterSetName = DefaultParameterSet)]207 public WaitForServiceTypes For208 {209 get210 {211 return _waitFor;212 }213 214 set215 {216 _waitFor = value;217 _waitForSpecified = true;218 }219 }220 221 private WaitForServiceTypes _waitFor = WaitForServiceTypes.PowerShell;222 private bool _waitForSpecified = false;223 224 /// <summary>225 /// Specify the Delay parameter.226 /// The specific time interval (in second) to wait between network pings or service queries.227 /// </summary>228 [Parameter(ParameterSetName = DefaultParameterSet)]229 [ValidateRange(1, short.MaxValue)]230 public short Delay231 {232 get233 {234 return (short)_delay;235 }236 237 set238 {239 _delay = value;240 _delaySpecified = true;241 }242 }243 244 private int _delay = 5;245 private bool _delaySpecified = false;246 247 /// <summary>248 /// Script to test if the PowerShell is ready.249 /// </summary>250 private const string TestPowershellScript = @"251$array = @($input)252$result = @{}253foreach ($computerName in $array[1])254{255 $ret = $null256 $arguments = @{257 ComputerName = $computerName258 ScriptBlock = { $true }259 260 SessionOption = New-PSSessionOption -NoMachineProfile261 ErrorAction = 'SilentlyContinue'262 }263 264 if ( $null -ne $array[0] )265 {266 $arguments['Credential'] = $array[0]267 }268 269 $result[$computerName] = (Invoke-Command @arguments) -as [bool]270}271$result272";273 274 /// <summary>275 /// The indicator to use when show progress.276 /// </summary>277 private readonly string[] _indicator = { "|", "/", "-", "\\" };278 279 /// <summary>280 /// The activity id.281 /// </summary>282 private int _activityId;283 284 /// <summary>285 /// After call 'Shutdown' on the target computer, wait a few286 /// seconds for the restart to begin.287 /// </summary>288 private const int SecondsToWaitForRestartToBegin = 25;289 290 /// <summary>291 /// Actual time out in seconds.292 /// </summary>293 private int _timeoutInMilliseconds;294 295 /// <summary>296 /// Indicate to exit.297 /// </summary>298 private bool _exit, _timeUp;299 private readonly CancellationTokenSource _cancel = new();300 301 /// <summary>302 /// A waithandler to wait on. Current thread will wait on it during the delay interval.303 /// </summary>304 private readonly ManualResetEventSlim _waitHandler = new(false);305 private readonly Dictionary<string, ComputerInfo> _computerInfos = new(StringComparer.OrdinalIgnoreCase);306 307 // CLR 4.0 Port note - use https://msdn.microsoft.com/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx308 private readonly string _shortLocalMachineName = Dns.GetHostName();309 310 // And for this, use PsUtils.GetHostname()311 private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync(string.Empty).Result.HostName;312 313 private int _percent;314 private string _status;315 private string _activity;316 private Timer _timer;317 private System.Management.Automation.PowerShell _powershell;318 319 private const string StageVerification = "VerifyStage";320 private const string WmiConnectionTest = "WMI";321 private const string WinrmConnectionTest = "WinRM";322 private const string PowerShellConnectionTest = "PowerShell";323 324 #endregion "parameters and PrivateData"325 326 #region "IDisposable Members"327 328 /// <summary>329 /// Dispose Method.330 /// </summary>331 public void Dispose()332 {333 this.Dispose(true);334 // Use SuppressFinalize in case a subclass335 // of this type implements a finalizer.336 GC.SuppressFinalize(this);337 }338 339 /// <summary>340 /// Dispose Method.341 /// </summary>342 /// <param name="disposing"></param>343 public void Dispose(bool disposing)344 {345 if (disposing)346 {347 _timer?.Dispose();348 _waitHandler.Dispose();349 _cancel.Dispose();350 _powershell?.Dispose();351 }352 }353 354 #endregion "IDisposable Members"355 356 #region "Private Methods"357 358 /// <summary>359 /// Validate parameters for 'DefaultSet'360 /// 1. When the Wait is specified, the computername cannot contain the local machine361 /// 2. If the local machine is present, make sure it is at the end of the list (so the remote ones get restarted before the local machine reboot).362 /// </summary>363 private void ValidateComputerNames()364 {365 bool containLocalhost = false;366 _validatedComputerNames.Clear();367 368 foreach (string name in ComputerName)369 {370 ErrorRecord error = null;371 string targetComputerName = ComputerWMIHelper.ValidateComputerName(name, _shortLocalMachineName, _fullLocalMachineName, ref error);372 if (targetComputerName == null)373 {374 if (error != null)375 {376 WriteError(error);377 }378 379 continue;380 }381 382 if (targetComputerName.Equals(ComputerWMIHelper.localhostStr, StringComparison.OrdinalIgnoreCase))383 {384 containLocalhost = true;385 }386 else if (!_uniqueComputerNames.Contains(targetComputerName))387 {388 _validatedComputerNames.Add(targetComputerName);389 _uniqueComputerNames.Add(targetComputerName);390 }391 }392 393 // Force wait with a test hook even if we're on the local computer394 if (!InternalTestHooks.TestWaitStopComputer && Wait && containLocalhost)395 {396 // The local machine will be ignored, and an error will be emitted.397 InvalidOperationException ex = new(ComputerResources.CannotWaitLocalComputer);398 WriteError(new ErrorRecord(ex, "CannotWaitLocalComputer", ErrorCategory.InvalidOperation, null));399 containLocalhost = false;400 }401 402 // Add the localhost to the end of the list, so we will restart remote machines403 // before we restart the local one.404 if (containLocalhost)405 {406 _validatedComputerNames.Add(ComputerWMIHelper.localhostStr);407 }408 }409 410 /// <summary>411 /// Write out progress.412 /// </summary>413 /// <param name="activity"></param>414 /// <param name="status"></param>415 /// <param name="percent"></param>416 /// <param name="progressRecordType"></param>417 private void WriteProgress(string activity, string status, int percent, ProgressRecordType progressRecordType)418 {419 ProgressRecord progress = new(_activityId, activity, status);420 progress.PercentComplete = percent;421 progress.RecordType = progressRecordType;422 WriteProgress(progress);423 }424 425 /// <summary>426 /// Calculate the progress percentage.427 /// </summary>428 /// <param name="currentStage"></param>429 /// <returns></returns>430 private int CalculateProgressPercentage(string currentStage)431 {432 switch (currentStage)433 {434 case StageVerification:435 return _waitFor.Equals(WaitForServiceTypes.Wmi) || _waitFor.Equals(WaitForServiceTypes.WinRM)436 ? 33437 : 20;438 case WmiConnectionTest:439 return _waitFor.Equals(WaitForServiceTypes.Wmi) ? 66 : 40;440 case WinrmConnectionTest:441 return _waitFor.Equals(WaitForServiceTypes.WinRM) ? 66 : 60;442 case PowerShellConnectionTest:443 return 80;444 default:445 break;446 }447 448 Dbg.Diagnostics.Assert(false, "CalculateProgressPercentage should never hit the default case");449 return 0;450 }451 452 /// <summary>453 /// Event handler for the timer.454 /// </summary>455 /// <param name="s"></param>456 private void OnTimedEvent(object s)457 {458 _exit = _timeUp = true;459 _cancel.Cancel();460 _waitHandler.Set();461 462 if (_powershell != null)463 {464 _powershell.Stop();465 _powershell.Dispose();466 }467 }468 469 private sealed class ComputerInfo470 {471 internal string LastBootUpTime;472 internal bool RebootComplete;473 }474 475 private List<string> TestRestartStageUsingWsman(IEnumerable<string> computerNames, List<string> nextTestList, CancellationToken token)476 {477 var restartStageTestList = new List<string>();478 var operationOptions = new CimOperationOptions479 {480 Timeout = TimeSpan.FromMilliseconds(2000),481 CancellationToken = token482 };483 foreach (var computer in computerNames)484 {485 try486 {487 if (token.IsCancellationRequested)488 {489 break;490 }491 492 using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token))493 {494 bool itemRetrieved = false;495 IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(496 ComputerWMIHelper.CimOperatingSystemNamespace,497 ComputerWMIHelper.CimQueryDialect,498 "Select * from " + ComputerWMIHelper.WMI_Class_OperatingSystem,499 operationOptions);500 foreach (CimInstance os in mCollection)501 {502 itemRetrieved = true;503 string newLastBootUpTime = os.CimInstanceProperties["LastBootUpTime"].Value.ToString();504 string oldLastBootUpTime = _computerInfos[computer].LastBootUpTime;505 506 if (!string.Equals(newLastBootUpTime, oldLastBootUpTime, StringComparison.OrdinalIgnoreCase))507 {508 _computerInfos[computer].RebootComplete = true;509 nextTestList.Add(computer);510 }511 else512 {513 restartStageTestList.Add(computer);514 }515 }516 517 if (!itemRetrieved)518 {519 restartStageTestList.Add(computer);520 }521 }522 }523 catch (CimException)524 {525 restartStageTestList.Add(computer);526 }527 catch (Exception)528 {529 restartStageTestList.Add(computer);530 }531 }532 533 return restartStageTestList;534 }535 536 private List<string> SetUpComputerInfoUsingWsman(IEnumerable<string> computerNames, CancellationToken token)537 {538 var validComputerNameList = new List<string>();539 var operationOptions = new CimOperationOptions540 {541 Timeout = TimeSpan.FromMilliseconds(2000),542 CancellationToken = token543 };544 foreach (var computer in computerNames)545 {546 try547 {548 using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token))549 {550 bool itemRetrieved = false;551 IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(552 ComputerWMIHelper.CimOperatingSystemNamespace,553 ComputerWMIHelper.CimQueryDialect,554 "Select * from " + ComputerWMIHelper.WMI_Class_OperatingSystem,555 operationOptions);556 foreach (CimInstance os in mCollection)557 {558 itemRetrieved = true;559 if (!_computerInfos.ContainsKey(computer))560 {561 var info = new ComputerInfo562 {563 LastBootUpTime = os.CimInstanceProperties["LastBootUpTime"].Value.ToString(),564 RebootComplete = false565 };566 _computerInfos.Add(computer, info);567 validComputerNameList.Add(computer);568 }569 }570 571 if (!itemRetrieved)572 {573 string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ComputerResources.CannotGetOperatingSystemObject);574 var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",575 ErrorCategory.OperationStopped, computer);576 this.WriteError(error);577 }578 }579 }580 catch (CimException ex)581 {582 string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ex.Message);583 var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",584 ErrorCategory.OperationStopped, computer);585 this.WriteError(error);586 }587 catch (Exception ex)588 {589 string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ex.Message);590 var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",591 ErrorCategory.OperationStopped, computer);592 this.WriteError(error);593 }594 }595 596 return validComputerNameList;597 }598 599 private void WriteOutTimeoutError(IEnumerable<string> computerNames)600 {601 const string errorId = "RestartComputerTimeout";602 foreach (string computer in computerNames)603 {604 string errorMsg = StringUtil.Format(ComputerResources.RestartcomputerFailed, computer, ComputerResources.TimeoutError);605 var exception = new RestartComputerTimeoutException(computer, Timeout, errorMsg, errorId);606 var error = new ErrorRecord(exception, errorId, ErrorCategory.OperationTimeout, computer);607 if (!InternalTestHooks.TestWaitStopComputer)608 {609 WriteError(error);610 }611 }612 }613 614 #endregion "Private Methods"615 616 #region "Internal Methods"617 618 internal static List<string> TestWmiConnectionUsingWsman(List<string> computerNames, List<string> nextTestList, PSCredential credential, string wsmanAuthentication, PSCmdlet cmdlet, CancellationToken token)619 {620 // Check if the WMI service "Winmgmt" is started621 const string wmiServiceQuery = "Select * from " + ComputerWMIHelper.WMI_Class_Service + " Where name = 'Winmgmt'";622 var wmiTestList = new List<string>();623 var operationOptions = new CimOperationOptions624 {625 Timeout = TimeSpan.FromMilliseconds(2000),626 CancellationToken = token627 };628 foreach (var computer in computerNames)629 {630 try631 {632 if (token.IsCancellationRequested)633 {634 break;635 }636 637 using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, isLocalHost: false, cmdlet, token))638 {639 bool itemRetrieved = false;640 IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(641 ComputerWMIHelper.CimOperatingSystemNamespace,642 ComputerWMIHelper.CimQueryDialect,643 wmiServiceQuery,644 operationOptions);645 foreach (CimInstance service in mCollection)646 {647 itemRetrieved = true;648 if (LanguagePrimitives.IsTrue(service.CimInstanceProperties["Started"].Value))649 {650 nextTestList.Add(computer);651 }652 else653 {654 wmiTestList.Add(computer);655 }656 }657 658 if (!itemRetrieved)659 {660 wmiTestList.Add(computer);661 }662 }663 }664 catch (CimException)665 {666 wmiTestList.Add(computer);667 }668 catch (Exception)669 {670 wmiTestList.Add(computer);671 }672 }673 674 return wmiTestList;675 }676 677 /// <summary>678 /// Test the PowerShell state for the restarting computer.679 /// </summary>680 /// <param name="computerNames"></param>681 /// <param name="nextTestList"></param>682 /// <param name="powershell"></param>683 /// <param name="credential"></param>684 /// <returns></returns>685 internal static List<string> TestPowerShell(List<string> computerNames, List<string> nextTestList, System.Management.Automation.PowerShell powershell, PSCredential credential)686 {687 List<string> psList = new();688 689 try690 {691 Collection<PSObject> psObjectCollection = powershell.Invoke(new object[] { credential, computerNames.ToArray() });692 if (psObjectCollection == null)693 {694 Dbg.Diagnostics.Assert(false, "This should never happen. Invoke should never return null.");695 }696 697 // If ^C or timeout happens when we are in powershell.Invoke(), the psObjectCollection might be empty698 if (psObjectCollection.Count == 0)699 {700 return computerNames;701 }702 703 object result = PSObject.Base(psObjectCollection[0]);704 Hashtable data = result as Hashtable;705 706 Dbg.Diagnostics.Assert(data != null, "data should never be null");707 Dbg.Diagnostics.Assert(data.Count == computerNames.Count, "data should contain results for all computers in computerNames");708 709 foreach (string computer in computerNames)710 {711 if (LanguagePrimitives.IsTrue(data[computer]))712 {713 nextTestList.Add(computer);714 }715 else716 {717 psList.Add(computer);718 }719 }720 }721 catch (PipelineStoppedException)722 {723 // powershell.Stop() is invoked because timeout expires, or Ctrl+C is pressed724 }725 catch (ObjectDisposedException)726 {727 // powershell.dispose() is invoked because timeout expires, or Ctrl+C is pressed728 }729 730 return psList;731 }732 733 #endregion "Internal Methods"734 735 #region "Overrides"736 737 /// <summary>738 /// BeginProcessing method.739 /// </summary>740 protected override void BeginProcessing()741 {742 // Timeout, For, Delay, Progress cannot be present if Wait is not present743 if ((_timeoutSpecified || _waitForSpecified || _delaySpecified) && !Wait)744 {745 InvalidOperationException ex = new(ComputerResources.RestartComputerInvalidParameter);746 ThrowTerminatingError(new ErrorRecord(ex, "RestartComputerInvalidParameter", ErrorCategory.InvalidOperation, null));747 }748 749 if (Wait)750 {751 _activityId = Random.Shared.Next();752 if (_timeout == -1 || _timeout >= int.MaxValue / 1000)753 {754 _timeoutInMilliseconds = int.MaxValue;755 }756 else757 {758 _timeoutInMilliseconds = _timeout * 1000;759 }760 761 // We don't support combined service types for now762 switch (_waitFor)763 {764 case WaitForServiceTypes.Wmi:765 case WaitForServiceTypes.WinRM:766 break;767 case WaitForServiceTypes.PowerShell:768 _powershell = System.Management.Automation.PowerShell.Create();769 _powershell.AddScript(TestPowershellScript);770 break;771 default:772 InvalidOperationException ex = new(ComputerResources.NoSupportForCombinedServiceType);773 ErrorRecord error = new(ex, "NoSupportForCombinedServiceType", ErrorCategory.InvalidOperation, (int)_waitFor);774 ThrowTerminatingError(error);775 break;776 }777 }778 }779 780 /// <summary>781 /// ProcessRecord method.782 /// </summary>783 [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]784 protected override void ProcessRecord()785 {786 // Validate parameters787 ValidateComputerNames();788 789 object[] flags = new object[] { 2, 0 };790 if (Force)791 {792 flags[0] = forcedReboot;793 }794 795 if (ParameterSetName.Equals(DefaultParameterSet, StringComparison.OrdinalIgnoreCase))796 {797 if (Wait && _timeout != 0)798 {799 _validatedComputerNames =800 SetUpComputerInfoUsingWsman(_validatedComputerNames, _cancel.Token);801 }802 803 foreach (string computer in _validatedComputerNames)804 {805 bool isLocal = false;806 string compname;807 808 if (computer.Equals("localhost", StringComparison.OrdinalIgnoreCase))809 {810 compname = _shortLocalMachineName;811 isLocal = true;812 }813 else814 {815 compname = computer;816 }817 818 // Generate target and action strings819 string action =820 StringUtil.Format(821 ComputerResources.RestartComputerAction,822 isLocal ? ComputerResources.LocalShutdownPrivilege : ComputerResources.RemoteShutdownPrivilege);823 string target =824 isLocal ? StringUtil.Format(ComputerResources.DoubleComputerName, "localhost", compname) : compname;825 826 if (!ShouldProcess(target, action))827 {828 continue;829 }830 831 bool isSuccess =832 ComputerWMIHelper.InvokeWin32ShutdownUsingWsman(this, isLocal, compname, flags, Credential, WsmanAuthentication, ComputerResources.RestartcomputerFailed, "RestartcomputerFailed", _cancel.Token);833 834 if (isSuccess && Wait && _timeout != 0)835 {836 _waitOnComputers.Add(computer);837 }838 }839 840 if (_waitOnComputers.Count > 0)841 {842 var restartStageTestList = new List<string>(_waitOnComputers);843 var wmiTestList = new List<string>();844 var winrmTestList = new List<string>();845 var psTestList = new List<string>();846 var allDoneList = new List<string>();847 848 bool isForWmi = _waitFor.Equals(WaitForServiceTypes.Wmi);849 bool isForWinRm = _waitFor.Equals(WaitForServiceTypes.WinRM);850 bool isForPowershell = _waitFor.Equals(WaitForServiceTypes.PowerShell);851 852 int indicatorIndex = 0;853 int machineCompleteRestart = 0;854 int actualDelay = SecondsToWaitForRestartToBegin;855 bool first = true;856 bool waitComplete = false;857 858 _percent = 0;859 _status = ComputerResources.WaitForRestartToBegin;860 _activity = _waitOnComputers.Count == 1 ?861 StringUtil.Format(ComputerResources.RestartSingleComputerActivity, _waitOnComputers[0]) :862 ComputerResources.RestartMultipleComputersActivity;863 864 _timer = new Timer(OnTimedEvent, null, _timeoutInMilliseconds, System.Threading.Timeout.Infinite);865 866 while (true)867 {868 // (delay * 1000)/250ms869 int loopCount = actualDelay * 4;870 while (loopCount > 0)871 {872 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);873 874 loopCount--;875 _waitHandler.Wait(250);876 if (_exit)877 {878 break;879 }880 }881 882 if (first)883 {884 actualDelay = _delay;885 first = false;886 887 if (_waitOnComputers.Count > 1)888 {889 _status = StringUtil.Format(ComputerResources.WaitForMultipleComputers, machineCompleteRestart, _waitOnComputers.Count);890 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);891 }892 }893 894 do895 {896 // Test restart stage.897 // We check if the target machine has already rebooted by querying the LastBootUpTime from the Win32_OperatingSystem object.898 // So after this step, we are sure that both the Network and the WMI or WinRM service have already come up.899 if (_exit)900 {901 break;902 }903 904 if (restartStageTestList.Count > 0)905 {906 if (_waitOnComputers.Count == 1)907 {908 _status = ComputerResources.VerifyRebootStage;909 _percent = CalculateProgressPercentage(StageVerification);910 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);911 }912 913 List<string> nextTestList = (isForWmi || isForPowershell) ? wmiTestList : winrmTestList;914 restartStageTestList = TestRestartStageUsingWsman(restartStageTestList, nextTestList, _cancel.Token);915 }916 917 // Test WMI service918 if (_exit)919 {920 break;921 }922 923 if (wmiTestList.Count > 0)924 {925 // This statement block executes for both CLRs.926 // In the "full" CLR, it serves as the else case.927 {928 if (_waitOnComputers.Count == 1)929 {930 _status = ComputerResources.WaitForWMI;931 _percent = CalculateProgressPercentage(WmiConnectionTest);932 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);933 }934 935 wmiTestList = TestWmiConnectionUsingWsman(wmiTestList, winrmTestList, Credential, WsmanAuthentication, this, _cancel.Token);936 }937 }938 939 if (isForWmi)940 {941 break;942 }943 944 // Test WinRM service945 if (_exit)946 {947 break;948 }949 950 if (winrmTestList.Count > 0)951 {952 // This statement block executes for both CLRs.953 // In the "full" CLR, it serves as the else case.954 {955 // CIM-WSMan in use. In this case, restart stage checking is done by using WMIv2,956 // so the WinRM service on the target machine is already up at this point.957 psTestList.AddRange(winrmTestList);958 winrmTestList.Clear();959 960 if (_waitOnComputers.Count == 1)961 {962 // This is to simulate the test for WinRM service963 _status = ComputerResources.WaitForWinRM;964 _percent = CalculateProgressPercentage(WinrmConnectionTest);965 966 loopCount = actualDelay * 4; // (delay * 1000)/250ms967 while (loopCount > 0)968 {969 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);970 971 loopCount--;972 _waitHandler.Wait(250);973 if (_exit)974 {975 break;976 }977 }978 }979 }980 }981 982 if (isForWinRm)983 {984 break;985 }986 987 // Test PowerShell988 if (_exit)989 {990 break;991 }992 993 if (psTestList.Count > 0)994 {995 if (_waitOnComputers.Count == 1)996 {997 _status = ComputerResources.WaitForPowerShell;998 _percent = CalculateProgressPercentage(PowerShellConnectionTest);999 WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);1000 }1001 1002 psTestList = TestPowerShell(psTestList, allDoneList, _powershell, this.Credential);1003 }1004 } while (false);1005 1006 // if time is up or Ctrl+c is typed, break out1007 if (_exit)1008 {1009 break;1010 }1011 1012 // Check if the restart completes1013 switch (_waitFor)1014 {1015 case WaitForServiceTypes.Wmi:1016 waitComplete = (winrmTestList.Count == _waitOnComputers.Count);1017 machineCompleteRestart = winrmTestList.Count;1018 break;1019 case WaitForServiceTypes.WinRM:1020 waitComplete = (psTestList.Count == _waitOnComputers.Count);1021 machineCompleteRestart = psTestList.Count;1022 break;1023 case WaitForServiceTypes.PowerShell:1024 waitComplete = (allDoneList.Count == _waitOnComputers.Count);1025 machineCompleteRestart = allDoneList.Count;1026 break;1027 }1028 1029 // Wait is done or time is up1030 if (waitComplete || _exit)1031 {1032 if (waitComplete)1033 {1034 _status = ComputerResources.RestartComplete;1035 WriteProgress(_indicator[indicatorIndex % 4] + _activity, _status, 100, ProgressRecordType.Completed);1036 _timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);1037 }1038 1039 break;1040 }1041 1042 if (_waitOnComputers.Count > 1)1043 {1044 _status = StringUtil.Format(ComputerResources.WaitForMultipleComputers, machineCompleteRestart, _waitOnComputers.Count);1045 _percent = machineCompleteRestart * 100 / _waitOnComputers.Count;1046 }1047 }1048 1049 if (_timeUp)1050 {1051 // The timeout expires. Write out timeout error messages for the computers that haven't finished restarting1052 do1053 {1054 if (restartStageTestList.Count > 0)1055 {1056 WriteOutTimeoutError(restartStageTestList);1057 }1058 1059 if (wmiTestList.Count > 0)1060 {1061 WriteOutTimeoutError(wmiTestList);1062 }1063 1064 // Wait for WMI. All computers that finished restarting are put in "winrmTestList"1065 if (isForWmi)1066 {1067 break;1068 }1069 1070 // Wait for WinRM. All computers that finished restarting are put in "psTestList"1071 if (winrmTestList.Count > 0)1072 {1073 WriteOutTimeoutError(winrmTestList);1074 }1075 1076 if (isForWinRm)1077 {1078 break;1079 }1080 1081 if (psTestList.Count > 0)1082 {1083 WriteOutTimeoutError(psTestList);1084 }1085 1086 // Wait for PowerShell. All computers that finished restarting are put in "allDoneList"1087 } while (false);1088 }1089 }1090 }1091 }1092 1093 /// <summary>1094 /// To implement ^C.1095 /// </summary>1096 protected override void StopProcessing()1097 {1098 _exit = true;1099 _cancel.Cancel();1100 _waitHandler.Set();1101 1102 _timer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);1103 1104 if (_powershell != null)1105 {1106 _powershell.Stop();1107 _powershell.Dispose();1108 }1109 }1110 1111 #endregion "Overrides"1112 }1113 1114 #endregion Restart-Computer1115 1116 #region Stop-Computer1117 1118 /// <summary>1119 /// Cmdlet to stop computer.1120 /// </summary>1121 [Cmdlet(VerbsLifecycle.Stop, "Computer", SupportsShouldProcess = true,1122 HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097151", RemotingCapability = RemotingCapability.SupportedByCommand)]1123 public sealed class StopComputerCommand : PSCmdlet, IDisposable1124 {1125 #region Private Members1126 1127 private readonly CancellationTokenSource _cancel = new();1128 1129 private const int forcedShutdown = 5; // See https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx1130 1131 #endregion1132 1133 #region "Parameters"1134 1135 /// <summary>1136 /// The authentication options for CIM_WSMan connection.1137 /// </summary>1138 [Parameter]1139 [ValidateSet(1140 "Default",1141 "Basic",1142 "Negotiate", // can be used with and without credential (without -> PSRP mapped to NegotiateWithImplicitCredential)1143 "CredSSP",1144 "Digest",1145 "Kerberos")] // can be used with explicit or implicit credential1146 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]1147 public string WsmanAuthentication { get; set; } = "Default";1148 1149 /// <summary>1150 /// The following is the definition of the input parameter "ComputerName".1151 /// Value of the address requested. The form of the value can be either the1152 /// computer name ("wxyz1234"), IPv4 address ("192.168.177.124"), or IPv61153 /// address ("2010:836B:4179::836B:4179").1154 /// </summary>1155 [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)]1156 [ValidateNotNullOrEmpty]1157 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]1158 [Alias("CN", "__SERVER", "Server", "IPAddress")]1159 public string[] ComputerName { get; set; } = new string[] { "." };1160 1161 /// <summary>1162 /// The following is the definition of the input parameter "Credential".1163 /// Specifies a user account that has permission to perform this action. Type a1164 /// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential1165 /// object, such as one from the Get-Credential cmdlet.1166 /// </summary>1167 [Parameter(Position = 1)]1168 [ValidateNotNullOrEmpty]1169 [Credential]1170 public PSCredential Credential { get; set; }1171 1172 /// <summary>1173 /// Force the operation to take place if possible.1174 /// </summary>1175 [Parameter]1176 public SwitchParameter Force { get; set; } = false;1177 1178 #endregion "parameters"1179 1180 #region "IDisposable Members"1181 1182 /// <summary>1183 /// Dispose Method.1184 /// </summary>1185 public void Dispose()1186 {1187 _cancel.Dispose();1188 }1189 1190 #endregion "IDisposable Members"1191 1192 #region "Overrides"1193 1194 /// <summary>1195 /// ProcessRecord.1196 /// </summary>1197 [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]1198 protected override void ProcessRecord()1199 {1200 object[] flags = new object[] { 1, 0 };