PowerShell/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs

2275 lines
90 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#if !UNIX
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using Microsoft.PowerShell.Commands.Internal;
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Options;
using System.Linq;
using Dbg = System.Management.Automation;
// FxCop suppressions for resource strings:
[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "unjoined")]
[module: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "UpTime")]
namespace Microsoft.PowerShell.Commands
{
#region Restart-Computer
/// <summary>
/// This exception is thrown when the timeout expires before a computer finishes restarting.
/// </summary>
[Serializable]
public sealed class RestartComputerTimeoutException : RuntimeException
{
/// <summary>
/// Name of the computer that is restarting.
/// </summary>
public string ComputerName { get; private set; }
/// <summary>
/// The timeout value specified by the user. It indicates the seconds to wait before timeout.
/// </summary>
public int Timeout { get; private set; }
/// <summary>
/// Construct a RestartComputerTimeoutException.
/// </summary>
/// <param name="computerName"></param>
/// <param name="timeout"></param>
/// <param name="message"></param>
/// <param name="errorId"></param>
internal RestartComputerTimeoutException(string computerName, int timeout, string message, string errorId)
: base(message)
{
SetErrorId(errorId);
SetErrorCategory(ErrorCategory.OperationTimeout);
ComputerName = computerName;
Timeout = timeout;
}
/// <summary>
/// Construct a RestartComputerTimeoutException.
/// </summary>
public RestartComputerTimeoutException() : base() { }
/// <summary>
/// Constructs a RestartComputerTimeoutException.
/// </summary>
/// <param name="message">
/// The message used in the exception.
/// </param>
public RestartComputerTimeoutException(string message) : base(message) { }
/// <summary>
/// Constructs a RestartComputerTimeoutException.
/// </summary>
/// <param name="message">
/// The message used in the exception.
/// </param>
/// <param name="innerException">
/// An exception that led to this exception.
/// </param>
public RestartComputerTimeoutException(string message, Exception innerException) : base(message, innerException) { }
#region Serialization
/// <summary>
/// Serialization constructor for class RestartComputerTimeoutException.
/// </summary>
/// <param name="info">
/// serialization information
/// </param>
/// <param name="context">
/// streaming context
/// </param>
private RestartComputerTimeoutException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
if (info == null)
{
throw new PSArgumentNullException("info");
}
ComputerName = info.GetString("ComputerName");
Timeout = info.GetInt32("Timeout");
}
/// <summary>
/// Serializes the RestartComputerTimeoutException.
/// </summary>
/// <param name="info">
/// serialization information
/// </param>
/// <param name="context">
/// streaming context
/// </param>
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new PSArgumentNullException("info");
}
base.GetObjectData(info, context);
info.AddValue("ComputerName", ComputerName);
info.AddValue("Timeout", Timeout);
}
#endregion Serialization
}
/// <summary>
/// Defines the services that Restart-Computer can wait on.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
public enum WaitForServiceTypes
{
/// <summary>
/// Wait for the WMI service to be ready.
/// </summary>
Wmi = 0x0,
/// <summary>
/// Wait for the WinRM service to be ready.
/// </summary>
WinRM = 0x1,
/// <summary>
/// Wait for the PowerShell to be ready.
/// </summary>
PowerShell = 0x2,
}
/// <summary>
/// Restarts the computer.
/// </summary>
[Cmdlet(VerbsLifecycle.Restart, "Computer", SupportsShouldProcess = true, DefaultParameterSetName = DefaultParameterSet,
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097060", RemotingCapability = RemotingCapability.OwnedByCommand)]
public class RestartComputerCommand : PSCmdlet, IDisposable
{
#region "Parameters and PrivateData"
private const string DefaultParameterSet = "DefaultSet";
private const int forcedReboot = 6; // see https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx
/// <summary>
/// The authentication options for CIM_WSMan connection.
/// </summary>
[Parameter(ParameterSetName = DefaultParameterSet)]
[ValidateSet(
"Default",
"Basic",
"Negotiate", // can be used with and without credential (without -> PSRP mapped to NegotiateWithImplicitCredential)
"CredSSP",
"Digest",
"Kerberos")] // can be used with explicit or implicit credential
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public string WsmanAuthentication { get; set; }
/// <summary>
/// Specifies the computer (s)Name on which this command is executed.
/// When this parameter is omitted, this cmdlet restarts the local computer.
/// Type the NETBIOS name, IP address, or fully-qualified domain name of one
/// or more computers in a comma-separated list. To specify the local computer, type the computername or "localhost".
/// </summary>
[Parameter(Position = 0, ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
[Alias("CN", "__SERVER", "Server", "IPAddress")]
public string[] ComputerName { get; set; } = new string[] { "." };
private List<string> _validatedComputerNames = new List<string>();
private readonly List<string> _waitOnComputers = new List<string>();
private readonly HashSet<string> _uniqueComputerNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The following is the definition of the input parameter "Credential".
/// Specifies a user account that has permission to perform this action. Type a
/// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential
/// object, such as one from the Get-Credential cmdlet.
/// </summary>
[Parameter(Position = 1)]
[ValidateNotNullOrEmpty]
[Credential]
public PSCredential Credential { get; set; }
/// <summary>
/// Using Force in conjunction with Reboot on a
/// remote computer immediately reboots the remote computer.
/// </summary>
[Parameter]
[Alias("f")]
public SwitchParameter Force { get; set; }
/// <summary>
/// Specify the Wait parameter. Prompt will be blocked is the Timeout is not 0.
/// </summary>
[Parameter(ParameterSetName = DefaultParameterSet)]
public SwitchParameter Wait { get; set; }
/// <summary>
/// Specify the Timeout parameter.
/// Negative value indicates wait infinitely.
/// Positive value indicates the seconds to wait before timeout.
/// </summary>
[Parameter(ParameterSetName = DefaultParameterSet)]
[Alias("TimeoutSec")]
[ValidateRange(-1, int.MaxValue)]
public int Timeout
{
get { return _timeout; }
set
{
_timeout = value;
_timeoutSpecified = true;
}
}
private int _timeout = -1;
private bool _timeoutSpecified = false;
/// <summary>
/// Specify the For parameter.
/// Wait for the specific service before unblocking the prompt.
/// </summary>
[Parameter(ParameterSetName = DefaultParameterSet)]
public WaitForServiceTypes For
{
get { return _waitFor; }
set
{
_waitFor = value;
_waitForSpecified = true;
}
}
private WaitForServiceTypes _waitFor = WaitForServiceTypes.PowerShell;
private bool _waitForSpecified = false;
/// <summary>
/// Specify the Delay parameter.
/// The specific time interval (in second) to wait between network pings or service queries.
/// </summary>
[Parameter(ParameterSetName = DefaultParameterSet)]
[ValidateRange(1, Int16.MaxValue)]
public Int16 Delay
{
get { return (Int16)_delay; }
set
{
_delay = value;
_delaySpecified = true;
}
}
private int _delay = 5;
private bool _delaySpecified = false;
/// <summary>
/// Script to test if the PowerShell is ready.
/// </summary>
private const string TestPowershellScript = @"
$array = @($input)
$result = @{}
foreach ($computerName in $array[1])
{
$ret = $null
$arguments = @{
ComputerName = $computerName
ScriptBlock = { $true }
SessionOption = NewPSSessionOption -NoMachineProfile
ErrorAction = 'SilentlyContinue'
}
if ( $null -ne $array[0] )
{
$arguments['Credential'] = $array[0]
}
$result[$computerName] = (Invoke-Command @arguments) -as [bool]
}
$result
";
/// <summary>
/// The indicator to use when show progress.
/// </summary>
private string[] _indicator = { "|", "/", "-", "\\" };
/// <summary>
/// The activity id.
/// </summary>
private int _activityId;
/// <summary>
/// After call 'Shutdown' on the target computer, wait a few
/// seconds for the restart to begin.
/// </summary>
private const int SecondsToWaitForRestartToBegin = 25;
/// <summary>
/// Actual time out in seconds.
/// </summary>
private int _timeoutInMilliseconds;
/// <summary>
/// Indicate to exit.
/// </summary>
private bool _exit, _timeUp;
private readonly CancellationTokenSource _cancel = new CancellationTokenSource();
/// <summary>
/// A waithandler to wait on. Current thread will wait on it during the delay interval.
/// </summary>
private readonly ManualResetEventSlim _waitHandler = new ManualResetEventSlim(false);
private readonly Dictionary<string, ComputerInfo> _computerInfos = new Dictionary<string, ComputerInfo>(StringComparer.OrdinalIgnoreCase);
// CLR 4.0 Port note - use https://msdn.microsoft.com/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx
private readonly string _shortLocalMachineName = Dns.GetHostName();
// And for this, use PsUtils.GetHostname()
private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync(string.Empty).Result.HostName;
private int _percent;
private string _status;
private string _activity;
private Timer _timer;
private System.Management.Automation.PowerShell _powershell;
private const string StageVerification = "VerifyStage";
private const string WmiConnectionTest = "WMI";
private const string WinrmConnectionTest = "WinRM";
private const string PowerShellConnectionTest = "PowerShell";
#endregion "parameters and PrivateData"
#region "IDisposable Members"
/// <summary>
/// Dispose Method.
/// </summary>
public void Dispose()
{
this.Dispose(true);
// Use SuppressFinalize in case a subclass
// of this type implements a finalizer.
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose Method.
/// </summary>
/// <param name="disposing"></param>
public void Dispose(bool disposing)
{
if (disposing)
{
if (_timer != null)
{
_timer.Dispose();
}
_waitHandler.Dispose();
_cancel.Dispose();
if (_powershell != null)
{
_powershell.Dispose();
}
}
}
#endregion "IDisposable Members"
#region "Private Methods"
/// <summary>
/// Validate parameters for 'DefaultSet'
/// 1. When the Wait is specified, the computername cannot contain the local machine
/// 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).
/// </summary>
private void ValidateComputerNames()
{
bool containLocalhost = false;
_validatedComputerNames.Clear();
foreach (string name in ComputerName)
{
ErrorRecord error = null;
string targetComputerName = ComputerWMIHelper.ValidateComputerName(name, _shortLocalMachineName, _fullLocalMachineName, ref error);
if (targetComputerName == null)
{
if (error != null)
{
WriteError(error);
}
continue;
}
if (targetComputerName.Equals(ComputerWMIHelper.localhostStr, StringComparison.OrdinalIgnoreCase))
{
containLocalhost = true;
}
else if (!_uniqueComputerNames.Contains(targetComputerName))
{
_validatedComputerNames.Add(targetComputerName);
_uniqueComputerNames.Add(targetComputerName);
}
}
// Force wait with a test hook even if we're on the local computer
if (!InternalTestHooks.TestWaitStopComputer && Wait && containLocalhost)
{
// The local machine will be ignored, and an error will be emitted.
InvalidOperationException ex = new InvalidOperationException(ComputerResources.CannotWaitLocalComputer);
WriteError(new ErrorRecord(ex, "CannotWaitLocalComputer", ErrorCategory.InvalidOperation, null));
containLocalhost = false;
}
// Add the localhost to the end of the list, so we will restart remote machines
// before we restart the local one.
if (containLocalhost)
{
_validatedComputerNames.Add(ComputerWMIHelper.localhostStr);
}
}
/// <summary>
/// Write out progress.
/// </summary>
/// <param name="activity"></param>
/// <param name="status"></param>
/// <param name="percent"></param>
/// <param name="progressRecordType"></param>
private void WriteProgress(string activity, string status, int percent, ProgressRecordType progressRecordType)
{
ProgressRecord progress = new ProgressRecord(_activityId, activity, status);
progress.PercentComplete = percent;
progress.RecordType = progressRecordType;
WriteProgress(progress);
}
/// <summary>
/// Calculate the progress percentage.
/// </summary>
/// <param name="currentStage"></param>
/// <returns></returns>
private int CalculateProgressPercentage(string currentStage)
{
switch (currentStage)
{
case StageVerification:
return _waitFor.Equals(WaitForServiceTypes.Wmi) || _waitFor.Equals(WaitForServiceTypes.WinRM)
? 33
: 20;
case WmiConnectionTest:
return _waitFor.Equals(WaitForServiceTypes.Wmi) ? 66 : 40;
case WinrmConnectionTest:
return _waitFor.Equals(WaitForServiceTypes.WinRM) ? 66 : 60;
case PowerShellConnectionTest:
return 80;
default:
break;
}
Dbg.Diagnostics.Assert(false, "CalculateProgressPercentage should never hit the default case");
return 0;
}
/// <summary>
/// Event handler for the timer.
/// </summary>
/// <param name="s"></param>
private void OnTimedEvent(object s)
{
_exit = _timeUp = true;
_cancel.Cancel();
_waitHandler.Set();
if (_powershell != null)
{
_powershell.Stop();
_powershell.Dispose();
}
}
private class ComputerInfo
{
internal string LastBootUpTime;
internal bool RebootComplete;
}
private List<string> TestRestartStageUsingWsman(IEnumerable<string> computerNames, List<string> nextTestList, CancellationToken token)
{
var restartStageTestList = new List<string>();
var operationOptions = new CimOperationOptions
{
Timeout = TimeSpan.FromMilliseconds(2000),
CancellationToken = token
};
foreach (var computer in computerNames)
{
try
{
if (token.IsCancellationRequested) { break; }
using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, token, this))
{
bool itemRetrieved = false;
IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(
ComputerWMIHelper.CimOperatingSystemNamespace,
ComputerWMIHelper.CimQueryDialect,
"Select * from " + ComputerWMIHelper.WMI_Class_OperatingSystem,
operationOptions);
foreach (CimInstance os in mCollection)
{
itemRetrieved = true;
string newLastBootUpTime = os.CimInstanceProperties["LastBootUpTime"].Value.ToString();
string oldLastBootUpTime = _computerInfos[computer].LastBootUpTime;
if (string.Compare(newLastBootUpTime, oldLastBootUpTime, StringComparison.OrdinalIgnoreCase) != 0)
{
_computerInfos[computer].RebootComplete = true;
nextTestList.Add(computer);
}
else
{
restartStageTestList.Add(computer);
}
}
if (!itemRetrieved)
{
restartStageTestList.Add(computer);
}
}
}
catch (CimException)
{
restartStageTestList.Add(computer);
}
catch (Exception)
{
restartStageTestList.Add(computer);
}
}
return restartStageTestList;
}
private List<string> SetUpComputerInfoUsingWsman(IEnumerable<string> computerNames, CancellationToken token)
{
var validComputerNameList = new List<string>();
var operationOptions = new CimOperationOptions
{
Timeout = TimeSpan.FromMilliseconds(2000),
CancellationToken = token
};
foreach (var computer in computerNames)
{
try
{
using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, token, this))
{
bool itemRetrieved = false;
IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(
ComputerWMIHelper.CimOperatingSystemNamespace,
ComputerWMIHelper.CimQueryDialect,
"Select * from " + ComputerWMIHelper.WMI_Class_OperatingSystem,
operationOptions);
foreach (CimInstance os in mCollection)
{
itemRetrieved = true;
if (!_computerInfos.ContainsKey(computer))
{
var info = new ComputerInfo
{
LastBootUpTime = os.CimInstanceProperties["LastBootUpTime"].Value.ToString(),
RebootComplete = false
};
_computerInfos.Add(computer, info);
validComputerNameList.Add(computer);
}
}
if (!itemRetrieved)
{
string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ComputerResources.CannotGetOperatingSystemObject);
var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",
ErrorCategory.OperationStopped, computer);
this.WriteError(error);
}
}
}
catch (CimException ex)
{
string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ex.Message);
var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",
ErrorCategory.OperationStopped, computer);
this.WriteError(error);
}
catch (Exception ex)
{
string errMsg = StringUtil.Format(ComputerResources.RestartComputerSkipped, computer, ex.Message);
var error = new ErrorRecord(new InvalidOperationException(errMsg), "RestartComputerSkipped",
ErrorCategory.OperationStopped, computer);
this.WriteError(error);
}
}
return validComputerNameList;
}
private void WriteOutTimeoutError(IEnumerable<string> computerNames)
{
const string errorId = "RestartComputerTimeout";
foreach (string computer in computerNames)
{
string errorMsg = StringUtil.Format(ComputerResources.RestartcomputerFailed, computer, ComputerResources.TimeoutError);
var exception = new RestartComputerTimeoutException(computer, Timeout, errorMsg, errorId);
var error = new ErrorRecord(exception, errorId, ErrorCategory.OperationTimeout, computer);
if (!InternalTestHooks.TestWaitStopComputer)
{
WriteError(error);
}
}
}
#endregion "Private Methods"
#region "Internal Methods"
internal static List<string> TestWmiConnectionUsingWsman(List<string> computerNames, List<string> nextTestList, CancellationToken token, PSCredential credential, string wsmanAuthentication, PSCmdlet cmdlet)
{
// Check if the WMI service "Winmgmt" is started
const string wmiServiceQuery = "Select * from " + ComputerWMIHelper.WMI_Class_Service + " Where name = 'Winmgmt'";
var wmiTestList = new List<string>();
var operationOptions = new CimOperationOptions
{
Timeout = TimeSpan.FromMilliseconds(2000),
CancellationToken = token
};
foreach (var computer in computerNames)
{
try
{
if (token.IsCancellationRequested) { break; }
using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, isLocalHost: false, token, cmdlet))
{
bool itemRetrieved = false;
IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(
ComputerWMIHelper.CimOperatingSystemNamespace,
ComputerWMIHelper.CimQueryDialect,
wmiServiceQuery,
operationOptions);
foreach (CimInstance service in mCollection)
{
itemRetrieved = true;
if (LanguagePrimitives.IsTrue(service.CimInstanceProperties["Started"].Value))
{
nextTestList.Add(computer);
}
else
{
wmiTestList.Add(computer);
}
}
if (!itemRetrieved)
{
wmiTestList.Add(computer);
}
}
}
catch (CimException)
{
wmiTestList.Add(computer);
}
catch (Exception)
{
wmiTestList.Add(computer);
}
}
return wmiTestList;
}
/// <summary>
/// Test the PowerShell state for the restarting computer.
/// </summary>
/// <param name="computerNames"></param>
/// <param name="nextTestList"></param>
/// <param name="powershell"></param>
/// <param name="credential"></param>
/// <returns></returns>
internal static List<string> TestPowerShell(List<string> computerNames, List<string> nextTestList, System.Management.Automation.PowerShell powershell, PSCredential credential)
{
List<string> psList = new List<string>();
try
{
Collection<PSObject> psObjectCollection = powershell.Invoke(new object[] { credential, computerNames.ToArray() });
if (psObjectCollection == null)
{
Dbg.Diagnostics.Assert(false, "This should never happen. Invoke should never return null.");
}
// If ^C or timeout happens when we are in powershell.Invoke(), the psObjectCollection might be empty
if (psObjectCollection.Count == 0)
{
return computerNames;
}
object result = PSObject.Base(psObjectCollection[0]);
Hashtable data = result as Hashtable;
Dbg.Diagnostics.Assert(data != null, "data should never be null");
Dbg.Diagnostics.Assert(data.Count == computerNames.Count, "data should contain results for all computers in computerNames");
foreach (string computer in computerNames)
{
if (LanguagePrimitives.IsTrue(data[computer]))
{
nextTestList.Add(computer);
}
else
{
psList.Add(computer);
}
}
}
catch (PipelineStoppedException)
{
// powershell.Stop() is invoked because timeout expires, or Ctrl+C is pressed
}
catch (ObjectDisposedException)
{
// powershell.dispose() is invoked because timeout expires, or Ctrl+C is pressed
}
return psList;
}
#endregion "Internal Methods"
#region "Overrides"
/// <summary>
/// BeginProcessing method.
/// </summary>
protected override void BeginProcessing()
{
// Timeout, For, Delay, Progress cannot be present if Wait is not present
if ((_timeoutSpecified || _waitForSpecified || _delaySpecified) && !Wait)
{
InvalidOperationException ex = new InvalidOperationException(ComputerResources.RestartComputerInvalidParameter);
ThrowTerminatingError(new ErrorRecord(ex, "RestartComputerInvalidParameter", ErrorCategory.InvalidOperation, null));
}
if (Wait)
{
_activityId = (new Random()).Next();
if (_timeout == -1 || _timeout >= int.MaxValue / 1000)
{
_timeoutInMilliseconds = int.MaxValue;
}
else
{
_timeoutInMilliseconds = _timeout * 1000;
}
// We don't support combined service types for now
switch (_waitFor)
{
case WaitForServiceTypes.Wmi:
case WaitForServiceTypes.WinRM:
break;
case WaitForServiceTypes.PowerShell:
_powershell = System.Management.Automation.PowerShell.Create();
_powershell.AddScript(TestPowershellScript);
break;
default:
InvalidOperationException ex = new InvalidOperationException(ComputerResources.NoSupportForCombinedServiceType);
ErrorRecord error = new ErrorRecord(ex, "NoSupportForCombinedServiceType", ErrorCategory.InvalidOperation, (int)_waitFor);
ThrowTerminatingError(error);
break;
}
}
}
/// <summary>
/// ProcessRecord method.
/// </summary>
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
protected override void ProcessRecord()
{
// Validate parameters
ValidateComputerNames();
object[] flags = new object[] { 2, 0 };
if (Force) flags[0] = forcedReboot;
if (ParameterSetName.Equals(DefaultParameterSet, StringComparison.OrdinalIgnoreCase))
{
if (Wait && _timeout != 0)
{
_validatedComputerNames =
SetUpComputerInfoUsingWsman(_validatedComputerNames, _cancel.Token);
}
foreach (string computer in _validatedComputerNames)
{
bool isLocal = false;
string compname;
if (computer.Equals("localhost", StringComparison.OrdinalIgnoreCase))
{
compname = _shortLocalMachineName;
isLocal = true;
}
else
{
compname = computer;
}
// Generate target and action strings
string action =
StringUtil.Format(
ComputerResources.RestartComputerAction,
isLocal ? ComputerResources.LocalShutdownPrivilege : ComputerResources.RemoteShutdownPrivilege);
string target =
isLocal ? StringUtil.Format(ComputerResources.DoubleComputerName, "localhost", compname) : compname;
if (!ShouldProcess(target, action))
{
continue;
}
bool isSuccess =
ComputerWMIHelper.InvokeWin32ShutdownUsingWsman(this, isLocal, compname, flags, Credential, WsmanAuthentication, ComputerResources.RestartcomputerFailed, "RestartcomputerFailed", _cancel.Token);
if (isSuccess && Wait && _timeout != 0)
{
_waitOnComputers.Add(computer);
}
}
if (_waitOnComputers.Count > 0)
{
var restartStageTestList = new List<string>(_waitOnComputers);
var wmiTestList = new List<string>();
var winrmTestList = new List<string>();
var psTestList = new List<string>();
var allDoneList = new List<string>();
bool isForWmi = _waitFor.Equals(WaitForServiceTypes.Wmi);
bool isForWinRm = _waitFor.Equals(WaitForServiceTypes.WinRM);
bool isForPowershell = _waitFor.Equals(WaitForServiceTypes.PowerShell);
int indicatorIndex = 0;
int machineCompleteRestart = 0;
int actualDelay = SecondsToWaitForRestartToBegin;
bool first = true;
bool waitComplete = false;
_percent = 0;
_status = ComputerResources.WaitForRestartToBegin;
_activity = _waitOnComputers.Count == 1 ?
StringUtil.Format(ComputerResources.RestartSingleComputerActivity, _waitOnComputers[0]) :
ComputerResources.RestartMultipleComputersActivity;
_timer = new Timer(OnTimedEvent, null, _timeoutInMilliseconds, System.Threading.Timeout.Infinite);
while (true)
{
int loopCount = actualDelay * 4; // (delay * 1000)/250ms
while (loopCount > 0)
{
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
loopCount--;
_waitHandler.Wait(250);
if (_exit) { break; }
}
if (first)
{
actualDelay = _delay;
first = false;
if (_waitOnComputers.Count > 1)
{
_status = StringUtil.Format(ComputerResources.WaitForMultipleComputers, machineCompleteRestart, _waitOnComputers.Count);
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
}
}
do
{
// Test restart stage.
// We check if the target machine has already rebooted by querying the LastBootUpTime from the Win32_OperatingSystem object.
// So after this step, we are sure that both the Network and the WMI or WinRM service have already come up.
if (_exit) { break; }
if (restartStageTestList.Count > 0)
{
if (_waitOnComputers.Count == 1)
{
_status = ComputerResources.VerifyRebootStage;
_percent = CalculateProgressPercentage(StageVerification);
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
}
List<string> nextTestList = (isForWmi || isForPowershell) ? wmiTestList : winrmTestList;
restartStageTestList = TestRestartStageUsingWsman(restartStageTestList, nextTestList, _cancel.Token);
}
// Test WMI service
if (_exit) { break; }
if (wmiTestList.Count > 0)
{
// This statement block executes for both CLRs.
// In the "full" CLR, it serves as the else case.
{
if (_waitOnComputers.Count == 1)
{
_status = ComputerResources.WaitForWMI;
_percent = CalculateProgressPercentage(WmiConnectionTest);
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
}
wmiTestList = TestWmiConnectionUsingWsman(wmiTestList, winrmTestList, _cancel.Token, Credential, WsmanAuthentication, this);
}
}
if (isForWmi) { break; }
// Test WinRM service
if (_exit) { break; }
if (winrmTestList.Count > 0)
{
// This statement block executes for both CLRs.
// In the "full" CLR, it serves as the else case.
{
// CIM-WSMan in use. In this case, restart stage checking is done by using WMIv2,
// so the WinRM service on the target machine is already up at this point.
psTestList.AddRange(winrmTestList);
winrmTestList.Clear();
if (_waitOnComputers.Count == 1)
{
// This is to simulate the test for WinRM service
_status = ComputerResources.WaitForWinRM;
_percent = CalculateProgressPercentage(WinrmConnectionTest);
loopCount = actualDelay * 4; // (delay * 1000)/250ms
while (loopCount > 0)
{
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
loopCount--;
_waitHandler.Wait(250);
if (_exit) { break; }
}
}
}
}
if (isForWinRm) { break; }
// Test PowerShell
if (_exit) { break; }
if (psTestList.Count > 0)
{
if (_waitOnComputers.Count == 1)
{
_status = ComputerResources.WaitForPowerShell;
_percent = CalculateProgressPercentage(PowerShellConnectionTest);
WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing);
}
psTestList = TestPowerShell(psTestList, allDoneList, _powershell, this.Credential);
}
} while (false);
// if time is up or Ctrl+c is typed, break out
if (_exit) { break; }
// Check if the restart completes
switch (_waitFor)
{
case WaitForServiceTypes.Wmi:
waitComplete = (winrmTestList.Count == _waitOnComputers.Count);
machineCompleteRestart = winrmTestList.Count;
break;
case WaitForServiceTypes.WinRM:
waitComplete = (psTestList.Count == _waitOnComputers.Count);
machineCompleteRestart = psTestList.Count;
break;
case WaitForServiceTypes.PowerShell:
waitComplete = (allDoneList.Count == _waitOnComputers.Count);
machineCompleteRestart = allDoneList.Count;
break;
}
// Wait is done or time is up
if (waitComplete || _exit)
{
if (waitComplete)
{
_status = ComputerResources.RestartComplete;
WriteProgress(_indicator[indicatorIndex % 4] + _activity, _status, 100, ProgressRecordType.Completed);
_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}
break;
}
if (_waitOnComputers.Count > 1)
{
_status = StringUtil.Format(ComputerResources.WaitForMultipleComputers, machineCompleteRestart, _waitOnComputers.Count);
_percent = machineCompleteRestart * 100 / _waitOnComputers.Count;
}
}
if (_timeUp)
{
// The timeout expires. Write out timeout error messages for the computers that haven't finished restarting
do
{
if (restartStageTestList.Count > 0) { WriteOutTimeoutError(restartStageTestList); }
if (wmiTestList.Count > 0) { WriteOutTimeoutError(wmiTestList); }
// Wait for WMI. All computers that finished restarting are put in "winrmTestList"
if (isForWmi) { break; }
// Wait for WinRM. All computers that finished restarting are put in "psTestList"
if (winrmTestList.Count > 0) { WriteOutTimeoutError(winrmTestList); }
if (isForWinRm) { break; }
if (psTestList.Count > 0) { WriteOutTimeoutError(psTestList); }
// Wait for PowerShell. All computers that finished restarting are put in "allDoneList"
} while (false);
}
}
}
}
/// <summary>
/// To implement ^C.
/// </summary>
protected override void StopProcessing()
{
_exit = true;
_cancel.Cancel();
_waitHandler.Set();
if (_timer != null)
{
_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}
if (_powershell != null)
{
_powershell.Stop();
_powershell.Dispose();
}
}
#endregion "Overrides"
}
#endregion Restart-Computer
#region Stop-Computer
/// <summary>
/// Cmdlet to stop computer.
/// </summary>
[Cmdlet(VerbsLifecycle.Stop, "Computer", SupportsShouldProcess = true,
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097151", RemotingCapability = RemotingCapability.SupportedByCommand)]
public sealed class StopComputerCommand : PSCmdlet, IDisposable
{
#region Private Members
private readonly CancellationTokenSource _cancel = new CancellationTokenSource();
private const int forcedShutdown = 5; // See https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx
#endregion
#region "Parameters"
/// <summary>
/// The authentication options for CIM_WSMan connection.
/// </summary>
[Parameter]
[ValidateSet(
"Default",
"Basic",
"Negotiate", // can be used with and without credential (without -> PSRP mapped to NegotiateWithImplicitCredential)
"CredSSP",
"Digest",
"Kerberos")] // can be used with explicit or implicit credential
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public string WsmanAuthentication { get; set; } = "Default";
/// <summary>
/// The following is the definition of the input parameter "ComputerName".
/// Value of the address requested. The form of the value can be either the
/// computer name ("wxyz1234"), IPv4 address ("192.168.177.124"), or IPv6
/// address ("2010:836B:4179::836B:4179").
/// </summary>
[Parameter(Position = 0, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
[Alias("CN", "__SERVER", "Server", "IPAddress")]
public string[] ComputerName { get; set; } = new string[] { "." };
/// <summary>
/// The following is the definition of the input parameter "Credential".
/// Specifies a user account that has permission to perform this action. Type a
/// user-name, such as "User01" or "Domain01\User01", or enter a PSCredential
/// object, such as one from the Get-Credential cmdlet.
/// </summary>
[Parameter(Position = 1)]
[ValidateNotNullOrEmpty]
[Credential]
public PSCredential Credential { get; set; }
/// <summary>
/// Force the operation to take place if possible.
/// </summary>
[Parameter]
public SwitchParameter Force { get; set; } = false;
#endregion "parameters"
#region "IDisposable Members"
/// <summary>
/// Dispose Method.
/// </summary>
public void Dispose()
{
_cancel.Dispose();
}
#endregion "IDisposable Members"
#region "Overrides"
/// <summary>
/// ProcessRecord.
/// </summary>
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
protected override void ProcessRecord()
{
object[] flags = new object[] { 1, 0 };
if (Force.IsPresent)
flags[0] = forcedShutdown;
ProcessWSManProtocol(flags);
}
/// <summary>
/// To implement ^C.
/// </summary>
protected override void StopProcessing()
{
try
{
_cancel.Cancel();
}
catch (ObjectDisposedException) { }
catch (AggregateException) { }
}
#endregion "Overrides"
#region Private Methods
private void ProcessWSManProtocol(object[] flags)
{
foreach (string computer in ComputerName)
{
string compname = string.Empty;
string strLocal = string.Empty;
bool isLocalHost = false;
if (_cancel.Token.IsCancellationRequested) { break; }
if ((computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase)))
{
compname = Dns.GetHostName();
strLocal = "localhost";
isLocalHost = true;
}
else
{
compname = computer;
}
if (!ShouldProcess(StringUtil.Format(ComputerResources.DoubleComputerName, strLocal, compname)))
{
continue;
}
else
{
ComputerWMIHelper.InvokeWin32ShutdownUsingWsman(
this,
isLocalHost,
compname,
flags,
Credential,
WsmanAuthentication,
ComputerResources.StopcomputerFailed,
"StopComputerException",
_cancel.Token);
}
}
}
#endregion
}
#endregion
#region Rename-Computer
/// <summary>
/// Renames a domain computer and its corresponding domain account or a
/// workgroup computer. Use this command to rename domain workstations and local
/// machines only. It cannot be used to rename Domain Controllers.
/// </summary>
[Cmdlet(VerbsCommon.Rename, "Computer", SupportsShouldProcess = true,
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097054", RemotingCapability = RemotingCapability.SupportedByCommand)]
public class RenameComputerCommand : PSCmdlet
{
#region Private Members
private bool _containsLocalHost = false;
private string _newNameForLocalHost = null;
private readonly string _shortLocalMachineName = Dns.GetHostName();
private readonly string _fullLocalMachineName = Dns.GetHostEntryAsync(string.Empty).Result.HostName;
#endregion
#region Parameters
/// <summary>
/// Target computers to rename.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string ComputerName { get; set; } = "localhost";
/// <summary>
/// Emit the output.
/// </summary>
// [Alias("Restart")]
[Parameter]
public SwitchParameter PassThru { get; set; }
/// <summary>
/// The domain credential of the domain the target computer joined.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
[Credential]
public PSCredential DomainCredential { get; set; }
/// <summary>
/// The administrator credential of the target computer.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
[Credential]
public PSCredential LocalCredential { get; set; }
/// <summary>
/// New names for the target computers.
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string NewName { get; set; }
/// <summary>
/// Suppress the ShouldContinue.
/// </summary>
[Parameter]
public SwitchParameter Force
{
get { return _force; }
set { _force = value; }
}
private bool _force;
/// <summary>
/// To restart the target computer after rename it.
/// </summary>
[Parameter]
public SwitchParameter Restart
{
get { return _restart; }
set { _restart = value; }
}
private bool _restart;
/// <summary>
/// The authentication options for CIM_WSMan connection.
/// </summary>
[Parameter]
[ValidateSet(
"Default",
"Basic",
"Negotiate", // can be used with and without credential (without -> PSRP mapped to NegotiateWithImplicitCredential)
"CredSSP",
"Digest",
"Kerberos")] // can be used with implicit or explicit credential
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public string WsmanAuthentication { get; set; } = "Default";
#endregion
#region "Private Methods"
/// <summary>
/// Check to see if the target computer is the local machine.
/// </summary>
private string ValidateComputerName()
{
// Validate target name.
ErrorRecord targetError = null;
string targetComputer = ComputerWMIHelper.ValidateComputerName(ComputerName, _shortLocalMachineName, _fullLocalMachineName, ref targetError);
if (targetComputer == null)
{
if (targetError != null)
{
WriteError(targetError);
}
return null;
}
// Validate *new* name. Validate the format of the new name. Check if the old name is the same as the
// new name later.
if (!ComputerWMIHelper.IsComputerNameValid(NewName))
{
bool isLocalhost = targetComputer.Equals(ComputerWMIHelper.localhostStr, StringComparison.OrdinalIgnoreCase);
string errMsg = StringUtil.Format(ComputerResources.InvalidNewName, isLocalhost ? _shortLocalMachineName : targetComputer, NewName);
ErrorRecord error = new ErrorRecord(
new InvalidOperationException(errMsg), "InvalidNewName",
ErrorCategory.InvalidArgument, NewName);
WriteError(error);
return null;
}
return targetComputer;
}
private void DoRenameComputerAction(string computer, string newName, bool isLocalhost)
{
string computerName = isLocalhost ? _shortLocalMachineName : computer;
if (!ShouldProcess(computerName))
{
return;
}
// Check the length of the new name
if (newName != null && newName.Length > ComputerWMIHelper.NetBIOSNameMaxLength)
{
string truncatedName = newName.Substring(0, ComputerWMIHelper.NetBIOSNameMaxLength);
string query = StringUtil.Format(ComputerResources.TruncateNetBIOSName, truncatedName);
string caption = ComputerResources.TruncateNetBIOSNameCaption;
if (!Force && !ShouldContinue(query, caption))
{
return;
}
}
DoRenameComputerWsman(computer, computerName, newName, isLocalhost);
}
private void DoRenameComputerWsman(string computer, string computerName, string newName, bool isLocalhost)
{
bool successful = false;
int retVal;
PSCredential credToUse = isLocalhost ? null : (LocalCredential ?? DomainCredential);
try
{
using (CancellationTokenSource cancelTokenSource = new CancellationTokenSource())
using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credToUse, WsmanAuthentication, isLocalhost, cancelTokenSource.Token, this))
{
var operationOptions = new CimOperationOptions
{
Timeout = TimeSpan.FromMilliseconds(10000),
CancellationToken = cancelTokenSource.Token,
// This prefix works against all versions of the WinRM server stack, both win8 and win7
ResourceUriPrefix = new Uri(ComputerWMIHelper.CimUriPrefix)
};
IEnumerable<CimInstance> mCollection = cimSession.QueryInstances(
ComputerWMIHelper.CimOperatingSystemNamespace,
ComputerWMIHelper.CimQueryDialect,
"Select * from " + ComputerWMIHelper.WMI_Class_ComputerSystem,
operationOptions);
foreach (CimInstance cimInstance in mCollection)
{
var oldName = cimInstance.CimInstanceProperties["DNSHostName"].Value.ToString();
if (oldName.Equals(newName, StringComparison.OrdinalIgnoreCase))
{
string errMsg = StringUtil.Format(ComputerResources.NewNameIsOldName, computerName, newName);
ErrorRecord error = new ErrorRecord(
new InvalidOperationException(errMsg), "NewNameIsOldName",
ErrorCategory.InvalidArgument, newName);
WriteError(error);
continue;
}
// If the target computer is in a domain, always use the DomainCred. If the DomainCred is not given,
// use null for UserName and Password, so the context of the caller will be used.
// If the target computer is not in a domain, just use null for the UserName and Password
string dUserName = null;
string dPassword = null;
if (((bool)cimInstance.CimInstanceProperties["PartOfDomain"].Value) && (DomainCredential != null))
{
dUserName = DomainCredential.UserName;
dPassword = Utils.GetStringFromSecureString(DomainCredential.Password);
}
var methodParameters = new CimMethodParametersCollection();
methodParameters.Add(CimMethodParameter.Create(
"Name",
newName,
Microsoft.Management.Infrastructure.CimType.String,
CimFlags.None));
methodParameters.Add(CimMethodParameter.Create(
"UserName",
dUserName,
Microsoft.Management.Infrastructure.CimType.String,
(dUserName == null) ? CimFlags.NullValue : CimFlags.None));
methodParameters.Add(
CimMethodParameter.Create(
"Password",
dPassword,
Microsoft.Management.Infrastructure.CimType.String,
(dPassword == null) ? CimFlags.NullValue : CimFlags.None));
if (!InternalTestHooks.TestRenameComputer)
{
CimMethodResult result = cimSession.InvokeMethod(
ComputerWMIHelper.CimOperatingSystemNamespace,
cimInstance,
"Rename",
methodParameters,
operationOptions);
retVal = Convert.ToInt32(result.ReturnValue.Value, CultureInfo.CurrentCulture);
}
else
{
retVal = InternalTestHooks.TestRenameComputerResults;
}
if (retVal != 0)
{
var ex = new Win32Exception(retVal);
string errMsg = StringUtil.Format(ComputerResources.FailToRename, computerName, newName, ex.Message);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "FailToRenameComputer", ErrorCategory.OperationStopped, computerName);
WriteError(error);
}
else
{
successful = true;
}
if (PassThru)
{
WriteObject(ComputerWMIHelper.GetRenameComputerStatusObject(retVal, newName, computerName));
}
if (successful)
{
if (_restart)
{
// If successful and the Restart parameter is specified, restart the computer
object[] flags = new object[] { 6, 0 };
ComputerWMIHelper.InvokeWin32ShutdownUsingWsman(
this,
isLocalhost,
computerName,
flags,
credToUse,
WsmanAuthentication,
ComputerResources.RestartcomputerFailed,
"RestartcomputerFailed",
cancelTokenSource.Token);
}
else
{
WriteWarning(StringUtil.Format(ComputerResources.RestartNeeded, null, computerName));
}
}
}
}
}
catch (CimException ex)
{
string errMsg = StringUtil.Format(ComputerResources.FailToConnectToComputer, computerName, ex.Message);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "RenameComputerException",
ErrorCategory.OperationStopped, computerName);
WriteError(error);
}
catch (Exception ex)
{
string errMsg = StringUtil.Format(ComputerResources.FailToConnectToComputer, computerName, ex.Message);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "RenameComputerException",
ErrorCategory.OperationStopped, computerName);
WriteError(error);
}
}
#endregion "Private Methods"
#region "Override Methods"
/// <summary>
/// ProcessRecord method.
/// </summary>
protected override void ProcessRecord()
{
string targetComputer = ValidateComputerName();
if (targetComputer == null) return;
bool isLocalhost = targetComputer.Equals("localhost", StringComparison.OrdinalIgnoreCase);
if (isLocalhost)
{
if (!_containsLocalHost)
_containsLocalHost = true;
_newNameForLocalHost = NewName;
return;
}
DoRenameComputerAction(targetComputer, NewName, false);
}
/// <summary>
/// EndProcessing method.
/// </summary>
protected override void EndProcessing()
{
if (!_containsLocalHost) return;
DoRenameComputerAction("localhost", _newNameForLocalHost, true);
}
#endregion "Override Methods"
}
#endregion Rename-Computer
#region "Public API"
/// <summary>
/// The object returned by SAM Computer cmdlets representing the status of the target machine.
/// </summary>
public sealed class ComputerChangeInfo
{
private const string MatchFormat = "{0}:{1}";
/// <summary>
/// The HasSucceeded which shows the operation was success or not.
/// </summary>
public bool HasSucceeded { get; set; }
/// <summary>
/// The ComputerName on which the operation is done.
/// </summary>
public string ComputerName { get; set; }
/// <summary>
/// Returns the string representation of this object.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return FormatLine(this.HasSucceeded.ToString(), this.ComputerName);
}
/// <summary>
/// Formats a line for use in ToString.
/// </summary>
/// <param name="HasSucceeded"></param>
/// <param name="computername"></param>
/// <returns></returns>
private string FormatLine(string HasSucceeded, string computername)
{
return StringUtil.Format(MatchFormat, HasSucceeded, computername);
}
}
/// <summary>
/// The object returned by Rename-Computer cmdlet representing the status of the target machine.
/// </summary>
public sealed class RenameComputerChangeInfo
{
private const string MatchFormat = "{0}:{1}:{2}";
/// <summary>
/// The status which shows the operation was success or failure.
/// </summary>
public bool HasSucceeded { get; set; }
/// <summary>
/// The NewComputerName which represents the target machine.
/// </summary>
public string NewComputerName { get; set; }
/// <summary>
/// The OldComputerName which represented the target machine.
/// </summary>
public string OldComputerName { get; set; }
/// <summary>
/// Returns the string representation of this object.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return FormatLine(this.HasSucceeded.ToString(), this.NewComputerName, this.OldComputerName);
}
/// <summary>
/// Formats a line for use in ToString.
/// </summary>
/// <param name="HasSucceeded"></param>
/// <param name="newcomputername"></param>
/// <param name="oldcomputername"></param>
/// <returns></returns>
private string FormatLine(string HasSucceeded, string newcomputername, string oldcomputername)
{
return StringUtil.Format(MatchFormat, HasSucceeded, newcomputername, oldcomputername);
}
}
#endregion "Public API"
#region Helper
/// <summary>
/// Helper Class used by Stop-Computer,Restart-Computer and Test-Connection
/// Also Contain constants used by System Restore related Cmdlets.
/// </summary>
internal static class ComputerWMIHelper
{
/// <summary>
/// The maximum length of a valid NetBIOS name.
/// </summary>
internal const int NetBIOSNameMaxLength = 15;
/// <summary>
/// System Restore Class used by Cmdlets.
/// </summary>
internal const string WMI_Class_SystemRestore = "SystemRestore";
/// <summary>
/// OperatingSystem WMI class used by Cmdlets.
/// </summary>
internal const string WMI_Class_OperatingSystem = "Win32_OperatingSystem";
/// <summary>
/// Service WMI class used by Cmdlets.
/// </summary>
internal const string WMI_Class_Service = "Win32_Service";
/// <summary>
/// Win32_ComputerSystem WMI class used by Cmdlets.
/// </summary>
internal const string WMI_Class_ComputerSystem = "Win32_ComputerSystem";
/// <summary>
/// Ping Class used by Cmdlet.
/// </summary>
internal const string WMI_Class_PingStatus = "Win32_PingStatus";
/// <summary>
/// CIMV2 path.
/// </summary>
internal const string WMI_Path_CIM = "\\root\\cimv2";
/// <summary>
/// Default path.
/// </summary>
internal const string WMI_Path_Default = "\\root\\default";
/// <summary>
/// The error says The interface is unknown.
/// </summary>
internal const int ErrorCode_Interface = 1717;
/// <summary>
/// This error says An instance of the service is already running.
/// </summary>
internal const int ErrorCode_Service = 1056;
/// <summary>
/// The name of the privilege to shutdown a local system.
/// </summary>
internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
/// <summary>
/// The name of the privilege to shutdown a remote system.
/// </summary>
internal const string SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege";
/// <summary>
/// CimUriPrefix.
/// </summary>
internal const string CimUriPrefix = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2";
/// <summary>
/// CimOperatingSystemNamespace.
/// </summary>
internal const string CimOperatingSystemNamespace = "root/cimv2";
/// <summary>
/// CimOperatingSystemShutdownMethod.
/// </summary>
internal const string CimOperatingSystemShutdownMethod = "Win32shutdown";
/// <summary>
/// CimQueryDialect.
/// </summary>
internal const string CimQueryDialect = "WQL";
/// <summary>
/// Local host name.
/// </summary>
internal const string localhostStr = "localhost";
/// <summary>
/// Get the local admin user name from a local NetworkCredential.
/// </summary>
/// <param name="computerName"></param>
/// <param name="psLocalCredential"></param>
/// <returns></returns>
internal static string GetLocalAdminUserName(string computerName, PSCredential psLocalCredential)
{
string localUserName = null;
// The format of local admin username should be "ComputerName\AdminName"
if (psLocalCredential.UserName.Contains("\\"))
{
localUserName = psLocalCredential.UserName;
}
else
{
int dotIndex = computerName.IndexOf('.');
if (dotIndex == -1)
{
localUserName = computerName + "\\" + psLocalCredential.UserName;
}
else
{
localUserName = computerName.Substring(0, dotIndex) + "\\" + psLocalCredential.UserName;
}
}
return localUserName;
}
/// <summary>
/// Generate a random password.
/// </summary>
/// <param name="passwordLength"></param>
/// <returns></returns>
internal static string GetRandomPassword(int passwordLength)
{
const int charMin = 32, charMax = 122;
const int allowedCharsCount = charMax - charMin + 1;
byte[] randomBytes = new byte[passwordLength];
char[] chars = new char[passwordLength];
RandomNumberGenerator rng = RandomNumberGenerator.Create();
rng.GetBytes(randomBytes);
for (int i = 0; i < passwordLength; i++)
{
chars[i] = (char)(randomBytes[i] % allowedCharsCount + charMin);
}
return new string(chars);
}
/// <summary>
/// Gets the Scope.
/// </summary>
/// <param name="computer"></param>
/// <param name="namespaceParameter"></param>
/// <returns></returns>
internal static string GetScopeString(string computer, string namespaceParameter)
{
StringBuilder returnValue = new StringBuilder("\\\\");
if (computer.Equals("::1", StringComparison.OrdinalIgnoreCase) || computer.Equals("[::1]", StringComparison.OrdinalIgnoreCase))
{
returnValue.Append("localhost");
}
else
{
returnValue.Append(computer);
}
returnValue.Append(namespaceParameter);
return returnValue.ToString();
}
/// <summary>
/// Returns true if it is a valid drive on the system.
/// </summary>
/// <param name="drive"></param>
/// <returns></returns>
internal static bool IsValidDrive(string drive)
{
DriveInfo[] drives = DriveInfo.GetDrives();
foreach (DriveInfo logicalDrive in drives)
{
if (logicalDrive.DriveType.Equals(DriveType.Fixed))
{
if (drive.ToString().Equals(logicalDrive.Name.ToString(), System.StringComparison.OrdinalIgnoreCase))
return true;
}
}
return false;
}
/// <summary>
/// Checks whether string[] contains System Drive.
/// </summary>
/// <param name="drives"></param>
/// <param name="sysdrive"></param>
/// <returns></returns>
internal static bool ContainsSystemDrive(string[] drives, string sysdrive)
{
string driveApp;
foreach (string drive in drives)
{
if (!drive.EndsWith('\\'))
{
driveApp = string.Concat(drive, "\\");
}
else
driveApp = drive;
if (driveApp.Equals(sysdrive, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
/// <summary>
/// Returns the given computernames in a string.
/// </summary>
/// <param name="computerNames"></param>
internal static string GetMachineNames(string[] computerNames)
{
string separator = ",";
RegistryKey regKey = Registry.CurrentUser.OpenSubKey(@"Control Panel\International");
if (regKey != null)
{
object sListValue = regKey.GetValue("sList");
if (sListValue != null)
{
separator = sListValue.ToString();
}
}
string compname = string.Empty;
StringBuilder strComputers = new StringBuilder();
int i = 0;
foreach (string computer in computerNames)
{
if (i > 0)
{
strComputers.Append(separator);
}
else
{
i++;
}
if ((computer.Equals("localhost", StringComparison.OrdinalIgnoreCase)) || (computer.Equals(".", StringComparison.OrdinalIgnoreCase)))
{
compname = Dns.GetHostName();
}
else
{
compname = computer;
}
strComputers.Append(compname);
}
return strComputers.ToString();
}
internal static ComputerChangeInfo GetComputerStatusObject(int errorcode, string computername)
{
ComputerChangeInfo computerchangeinfo = new ComputerChangeInfo();
computerchangeinfo.ComputerName = computername;
if (errorcode != 0)
{
computerchangeinfo.HasSucceeded = false;
}
else
{
computerchangeinfo.HasSucceeded = true;
}
return computerchangeinfo;
}
internal static RenameComputerChangeInfo GetRenameComputerStatusObject(int errorcode, string newcomputername, string oldcomputername)
{
RenameComputerChangeInfo renamecomputerchangeinfo = new RenameComputerChangeInfo();
renamecomputerchangeinfo.OldComputerName = oldcomputername;
renamecomputerchangeinfo.NewComputerName = newcomputername;
if (errorcode != 0)
{
renamecomputerchangeinfo.HasSucceeded = false;
}
else
{
renamecomputerchangeinfo.HasSucceeded = true;
}
return renamecomputerchangeinfo;
}
internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, string computername)
{
Win32Exception ex = new Win32Exception(errorcode);
string additionalmessage = string.Empty;
if (ex.NativeErrorCode.Equals(0x00000035))
{
additionalmessage = StringUtil.Format(ComputerResources.NetworkPathNotFound, computername);
}
string message = StringUtil.Format(ComputerResources.OperationFailed, ex.Message, computername, additionalmessage);
ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, computername);
cmdlet.WriteError(er);
}
/// <summary>
/// Check whether the new computer name is valid.
/// </summary>
/// <param name="computerName"></param>
/// <returns></returns>
internal static bool IsComputerNameValid(string computerName)
{
bool allDigits = true;
if (computerName.Length >= 64)
return false;
foreach (char t in computerName)
{
if (t >= 'A' && t <= 'Z' ||
t >= 'a' && t <= 'z')
{
allDigits = false;
continue;
}
else if (t >= '0' && t <= '9')
{
continue;
}
else if (t == '-')
{
allDigits = false;
continue;
}
else
{
return false;
}
}
return !allDigits;
}
/// <summary>
/// System Restore APIs are not supported on the ARM platform. Skip the system restore operation is necessary.
/// </summary>
/// <param name="cmdlet"></param>
/// <returns></returns>
internal static bool SkipSystemRestoreOperationForARMPlatform(PSCmdlet cmdlet)
{
bool retValue = false;
if (PsUtils.IsRunningOnProcessorArchitectureARM())
{
var ex = new InvalidOperationException(ComputerResources.SystemRestoreNotSupported);
var er = new ErrorRecord(ex, "SystemRestoreNotSupported", ErrorCategory.InvalidOperation, null);
cmdlet.WriteError(er);
retValue = true;
}
return retValue;
}
/// <summary>
/// Invokes the Win32Shutdown command on provided target computer using WSMan
/// over a CIMSession. The flags parameter determines the type of shutdown operation
/// such as shutdown, reboot, force etc.
/// </summary>
/// <param name="cmdlet">Cmdlet host for reporting errors.</param>
/// <param name="isLocalhost">True if local host computer.</param>
/// <param name="computerName">Target computer.</param>
/// <param name="flags">Win32Shutdown flags.</param>
/// <param name="credential">Optional credential.</param>
/// <param name="authentication">Optional authentication.</param>
/// <param name="formatErrorMessage">Error message format string that takes two parameters.</param>
/// <param name="ErrorFQEID">Fully qualified error Id.</param>
/// <param name="cancelToken">Cancel token.</param>
/// <returns>True on success.</returns>
internal static bool InvokeWin32ShutdownUsingWsman(
PSCmdlet cmdlet,
bool isLocalhost,
string computerName,
object[] flags,
PSCredential credential,
string authentication,
string formatErrorMessage,
string ErrorFQEID,
CancellationToken cancelToken)
{
Dbg.Diagnostics.Assert(flags.Length == 2, "Caller need to verify the flags passed in");
bool isSuccess = false;
string targetMachine = isLocalhost ? "localhost" : computerName;
string authInUse = isLocalhost ? null : authentication;
PSCredential credInUse = isLocalhost ? null : credential;
var currentPrivilegeState = new PlatformInvokes.TOKEN_PRIVILEGE();
var operationOptions = new CimOperationOptions
{
Timeout = TimeSpan.FromMilliseconds(10000),
CancellationToken = cancelToken,
// This prefix works against all versions of the WinRM server stack, both win8 and win7
ResourceUriPrefix = new Uri(ComputerWMIHelper.CimUriPrefix)
};
try
{
if (!(isLocalhost && PlatformInvokes.EnableTokenPrivilege(ComputerWMIHelper.SE_SHUTDOWN_NAME, ref currentPrivilegeState)) &&
!(!isLocalhost && PlatformInvokes.EnableTokenPrivilege(ComputerWMIHelper.SE_REMOTE_SHUTDOWN_NAME, ref currentPrivilegeState)))
{
string message =
StringUtil.Format(ComputerResources.PrivilegeNotEnabled, computerName,
isLocalhost ? ComputerWMIHelper.SE_SHUTDOWN_NAME : ComputerWMIHelper.SE_REMOTE_SHUTDOWN_NAME);
ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(message), "PrivilegeNotEnabled", ErrorCategory.InvalidOperation, null);
cmdlet.WriteError(errorRecord);
return false;
}
using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(targetMachine, credInUse, authInUse, isLocalhost, cancelToken, cmdlet))
{
var methodParameters = new CimMethodParametersCollection();
int retVal;
methodParameters.Add(CimMethodParameter.Create(
"Flags",
flags[0],
Microsoft.Management.Infrastructure.CimType.SInt32,
CimFlags.None));
methodParameters.Add(CimMethodParameter.Create(
"Reserved",
flags[1],
Microsoft.Management.Infrastructure.CimType.SInt32,
CimFlags.None));
if (!InternalTestHooks.TestStopComputer)
{
CimMethodResult result = null;
if (isLocalhost)
{
// Win32_ComputerSystem is a singleton hence FirstOrDefault() return the only instance returned by EnumerateInstances.
var computerSystem = cimSession.EnumerateInstances(ComputerWMIHelper.CimOperatingSystemNamespace, ComputerWMIHelper.WMI_Class_OperatingSystem).FirstOrDefault();
result = cimSession.InvokeMethod(
ComputerWMIHelper.CimOperatingSystemNamespace,
computerSystem,
ComputerWMIHelper.CimOperatingSystemShutdownMethod,
methodParameters,
operationOptions);
}
else
{
result = cimSession.InvokeMethod(
ComputerWMIHelper.CimOperatingSystemNamespace,
ComputerWMIHelper.WMI_Class_OperatingSystem,
ComputerWMIHelper.CimOperatingSystemShutdownMethod,
methodParameters,
operationOptions);
}
retVal = Convert.ToInt32(result.ReturnValue.Value, CultureInfo.CurrentCulture);
}
else
{
retVal = InternalTestHooks.TestStopComputerResults;
}
if (retVal != 0)
{
var ex = new Win32Exception(retVal);
string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message);
ErrorRecord error = new ErrorRecord(
new InvalidOperationException(errMsg), ErrorFQEID, ErrorCategory.OperationStopped, computerName);
cmdlet.WriteError(error);
}
else
{
isSuccess = true;
}
}
}
catch (CimException ex)
{
string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), ErrorFQEID,
ErrorCategory.OperationStopped, computerName);
cmdlet.WriteError(error);
}
catch (Exception ex)
{
string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message);
ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), ErrorFQEID,
ErrorCategory.OperationStopped, computerName);
cmdlet.WriteError(error);
}
finally
{
// Restore the previous privilege state if something unexpected happened
PlatformInvokes.RestoreTokenPrivilege(
isLocalhost ? ComputerWMIHelper.SE_SHUTDOWN_NAME : ComputerWMIHelper.SE_REMOTE_SHUTDOWN_NAME, ref currentPrivilegeState);
}
return isSuccess;
}
/// <summary>
/// Returns valid computer name or null on failure.
/// </summary>
/// <param name="nameToCheck">Computer name to validate.</param>
/// <param name="shortLocalMachineName"></param>
/// <param name="fullLocalMachineName"></param>
/// <param name="error"></param>
/// <returns>Valid computer name.</returns>
internal static string ValidateComputerName(
string nameToCheck,
string shortLocalMachineName,
string fullLocalMachineName,
ref ErrorRecord error)
{
string validatedComputerName = null;
if (nameToCheck.Equals(".", StringComparison.OrdinalIgnoreCase) ||
nameToCheck.Equals(localhostStr, StringComparison.OrdinalIgnoreCase) ||
nameToCheck.Equals(shortLocalMachineName, StringComparison.OrdinalIgnoreCase) ||
nameToCheck.Equals(fullLocalMachineName, StringComparison.OrdinalIgnoreCase))
{
validatedComputerName = localhostStr;
}
else
{
bool isIPAddress = false;
try
{
IPAddress unused;
isIPAddress = IPAddress.TryParse(nameToCheck, out unused);
}
catch (Exception)
{
}
try
{
string fqcn = Dns.GetHostEntryAsync(nameToCheck).Result.HostName;
if (fqcn.Equals(shortLocalMachineName, StringComparison.OrdinalIgnoreCase) ||
fqcn.Equals(fullLocalMachineName, StringComparison.OrdinalIgnoreCase))
{
// The IPv4 or IPv6 of the local machine is specified
validatedComputerName = localhostStr;
}
else
{
validatedComputerName = nameToCheck;
}
}
catch (Exception e)
{
// If GetHostEntry() throw exception, then the target should not be the local machine
if (!isIPAddress)
{
// Return error if the computer name is not an IP address. Dns.GetHostEntry() may not work on IP addresses.
string errMsg = StringUtil.Format(ComputerResources.CannotResolveComputerName, nameToCheck, e.Message);
error = new ErrorRecord(
new InvalidOperationException(errMsg), "AddressResolutionException",
ErrorCategory.InvalidArgument, nameToCheck);
return null;
}
validatedComputerName = nameToCheck;
}
}
return validatedComputerName;
}
}
#endregion Helper
}
#endif