PowerShell/src/Microsoft.PowerShell.PackageManagement/Cmdlets/CmdletBase.cs
PowerShell Team c748652c34 Copy all mapped files from [SD:725290]
commit 8cec8f150da7583b7af5efbe2853efee0179750c
2016-07-28 23:23:03 -07:00

543 lines
22 KiB
C#

//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
namespace Microsoft.PowerShell.PackageManagement.Cmdlets {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Security;
using System.Threading;
using Microsoft.PackageManagement;
using Microsoft.PackageManagement.Implementation;
using Microsoft.PackageManagement.Internal;
using Microsoft.PackageManagement.Internal.Api;
using Microsoft.PackageManagement.Internal.Implementation;
using Microsoft.PackageManagement.Internal.Utility.Async;
using Microsoft.PackageManagement.Internal.Utility.Extensions;
using Resources;
using Utility;
using Constants = PackageManagement.Constants;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.PackageManagement.Packaging;
using Telemetry.Internal;
using System.Management.Automation.Runspaces;
using System.Net;
using Microsoft.PackageManagement.Internal.Utility.Plugin;
public delegate string GetMessageString(string messageId, string defaultText);
public abstract class CmdletBase : AsyncCmdlet, IHostApi {
private static int _globalCallCount = 1;
private static readonly object _lockObject = new object();
private bool _messageResolverNotResponding = false;
private readonly int _callCount;
private readonly Hashtable _dynamicOptions = new Hashtable();
private string _bootstrapNuGet = "false";
[Parameter]
public SwitchParameter Force;
[Parameter]
public SwitchParameter ForceBootstrap;
private GetMessageString _messageResolver;
protected CmdletBase() {
_callCount = _globalCallCount++;
ShouldLogError = true;
}
protected bool IsRooted(string filePath)
{
return (Path.IsPathRooted(filePath) ||
filePath.StartsWith(@".\", StringComparison.Ordinal) ||
filePath.StartsWith(@"./", StringComparison.Ordinal) ||
filePath.StartsWith(@"..\", StringComparison.Ordinal) ||
filePath.StartsWith(@"../", StringComparison.Ordinal) ||
filePath.StartsWith(@"~/", StringComparison.Ordinal) ||
filePath.StartsWith(@"~\", StringComparison.Ordinal));
}
protected void ValidateVersion(string version) {
if (string.IsNullOrWhiteSpace(version)) {
return;
}
try {
Version.Parse(version);
} catch (Exception ex) {
Error(Constants.Errors.InvalidVersion, version, ex.Message);
}
}
protected bool ShouldLogError {get; set;}
protected bool ShouldSelectAllProviders { get; set; }
protected bool WaitForActivity<T>(IEnumerable<IAsyncEnumerable<T>> enumerables) {
var handles = enumerables.Select(each => each.Ready).ConcatSingleItem(CancellationEvent.Token.WaitHandle).ToArray();
if (handles.Length > 1) {
if (handles.Length <= 64)
{
// less than or equal to 64 handles so we can use waithandle
WaitHandle.WaitAny(handles);
}
else
{
// throw error saying we don't support
Error(Constants.Errors.TooManyPackages, handles.Length);
//// divide handles in chunks of 64 each
//int numberOfTasks = (int)Math.Ceiling(handles.Length / 64.0);
//var cts = new CancellationTokenSource();
//Task[] tasks = new Task[numberOfTasks];
//for (int i = 0; i < numberOfTasks; i += 1)
//{
// // now each task will use the waithandle.waitany since only 64 handles will be assigned to each task
// // so we won't get exception
// tasks[i] = Task.Factory.StartNew(() =>
// {
// if ((handles.Length - i * 64) > 0)
// {
// // either 64 items or whatever left
// int waitHandleArrayLength = Math.Min(64, handles.Length - i * 64);
// WaitHandle[] waithandles = new WaitHandle[waitHandleArrayLength];
// Array.Copy(handles, i * 64, waithandles, 0, waitHandleArrayLength);
// WaitHandle.WaitAny(waithandles);
// }
// }, cts.Token);
//}
//// just need a task to finish
//Task.WaitAny(tasks);
//// cancel the rest
//cts.Cancel();
}
return !IsCanceled;
}
// we are finished when there is only handle to wait on (the cancellation token)
return false;
}
protected abstract IEnumerable<string> ParameterSets {get;}
protected bool IsPackageBySearch {
get {
return ParameterSetName == Constants.ParameterSets.PackageBySearchSet;
}
}
protected bool IsPackageByObject {
get {
return ParameterSetName == Constants.ParameterSets.PackageByInputObjectSet;
}
}
protected bool IsSourceByObject {
get {
return ParameterSetName == Constants.ParameterSets.SourceByInputObjectSet;
}
}
protected internal IPackageManagementService PackageManagementService {
get {
lock (_lockObject) {
if (!IsCanceled && !IsInitialized) {
try {
IsInitialized = PackageManager.Instance.Initialize(this);
} catch (Exception e) {
e.Dump();
}
}
}
return PackageManager.Instance;
}
}
protected int TimeOut {
get {
if (IsInvocation) {
var t = GetDynamicParameterValue<int>("Timeout");
if (t > 0) {
return t;
}
}
return Constants.DefaultTimeout; // in a non-invokcation, always default to one hour
}
}
protected int Responsiveness {
get {
if (IsInvocation) {
var t = GetDynamicParameterValue<int>("Responsiveness");
if (t > 0) {
return t;
}
}
return Constants.DefaultResponsiveness; // in a non-invokcation, always default to one hour
}
}
public GetMessageString MessageResolver {
get {
return _messageResolver ?? (_messageResolver = GetDynamicParameterValue<GetMessageString>("MessageResolver"));
}
}
#region Implementing IHostApi
public Hashtable DynamicOptions {
get {
return _dynamicOptions;
}
}
public virtual IEnumerable<string> Sources {
get {
return null;
}
set {
}
}
public virtual IEnumerable<string> OptionKeys {
get {
return DynamicParameterDictionary.Values.OfType<CustomRuntimeDefinedParameter>().Where(each => each.IsSet).Select(each => each.Name).Concat(MyInvocation.BoundParameters.Keys);
}
}
protected bool GenerateCommonDynamicParameters() {
if (IsInvocation) {
// only generate these parameters if there is an actual call happening.
// this prevents get-help from showing them.
DynamicParameterDictionary.Add("Timeout", new RuntimeDefinedParameter("Timeout", typeof (int), new Collection<Attribute> {
new ParameterAttribute()
}));
DynamicParameterDictionary.Add("Responsiveness", new RuntimeDefinedParameter("Responsiveness", typeof (int), new Collection<Attribute> {
new ParameterAttribute()
}));
DynamicParameterDictionary.Add("MessageResolver", new RuntimeDefinedParameter("MessageResolver", typeof (GetMessageString), new Collection<Attribute> {
new ParameterAttribute()
}));
}
return true;
}
public override string GetMessageString(string messageText, string defaultText) {
messageText = DropMsgPrefix(messageText);
if (string.IsNullOrWhiteSpace(defaultText) || defaultText.IndexOf("MSG:", StringComparison.OrdinalIgnoreCase) == 0) {
defaultText = Messages.ResourceManager.GetString(messageText);
}
string result = null;
//when a user does find-module xjea, psreadline | install-module, the install-module starts executing before find-module completes. This
//is expected. However when the install-module starts, the MessageResolver delegate stops responding. This is a PowerShell thing.
//Once the delegate is blocked, it's blocked for the subsequence log message calls for the current cmdlet, find-module. Note that the
//next cmdlet, install-module has no impact on the delegate blocking issue, meaning all messages are expected to be logged for install-module.
//_messageResolverNotResponding is for avoiding continuesly waiting for unresponsive MessageResolver delegate.
if (MessageResolver != null && !_messageResolverNotResponding) {
// if the consumer has specified a MessageResolver delegate, we need to call it on the main thread
// because powershell won't let us use the default runspace from another thread.
// if we are anywhere but the end of the pipeline, the delegate here would block on stuff later in the pipe
// and since we're blocking *that* based on the the resolution of this, we're better off just skipping
// the message resoluion for things earlier in the pipeline.
_messageResolverNotResponding = !ExecuteOnMainThread(() =>{
result = MessageResolver(messageText, defaultText);
if (string.IsNullOrWhiteSpace(result)) {
result = null;
}
return true;
}).Wait(5000); // you don't get a lot of time to get back to me on this...
}
return result ?? Messages.ResourceManager.GetString(messageText) ?? defaultText;
}
public virtual bool AskPermission(string permission) {
#if DEBUG
Message(Constants.Messages.NotImplemented, permission);
#endif
return true;
}
public virtual IEnumerable<string> GetOptionValues(string key) {
// try to see whether the key is a dynamic parameter first. if we cannot find it then go to bound parameters.
var dynamicValues = DynamicParameterDictionary.Values.OfType<CustomRuntimeDefinedParameter>().Where(each => each.IsSet && each.Name == key).SelectMany(each => each.GetValues(this));
if (dynamicValues.Any())
{
return dynamicValues;
}
if (MyInvocation.BoundParameters.ContainsKey(key)) {
var value = MyInvocation.BoundParameters[key];
if (value is string || value is int) {
return new[] {
MyInvocation.BoundParameters[key].ToString()
};
}
if (value is SwitchParameter) {
return new[] {
((SwitchParameter)MyInvocation.BoundParameters[key]).IsPresent.ToString()
};
}
if (value is string[]) {
return ((string[])value);
}
return new[] {
MyInvocation.BoundParameters[key].ToString()
};
}
return Enumerable.Empty<string>();
}
public virtual IWebProxy WebProxy
{
get
{
return null;
}
}
public virtual string CredentialUsername {
get {
return null;
}
}
public virtual SecureString CredentialPassword {
get {
return null;
}
}
public virtual bool ShouldContinueWithUntrustedPackageSource(string package, string packageSource) {
#if DEBUG
Message(Constants.Messages.NotImplemented, packageSource);
#endif
return true;
}
[SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", Justification = "It's a PowerShell behavior.")]
bool IHostApi.ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll)
{
var shouldContinueResult = base.ShouldContinue(query, caption, true).Result;
if (shouldContinueResult != null)
{
yesToAll = shouldContinueResult.yesToAll;
noToAll = shouldContinueResult.noToAll;
return shouldContinueResult.result;
}
return false;
}
bool IHostApi.ShouldContinue(string query, string caption)
{
return base.ShouldContinue(query, caption).Result;
}
public virtual bool ShouldBootstrapProvider(string requestor, string providerName, string providerVersion, string providerType, string location, string destination) {
try {
if (Force || ForceBootstrap) {
return true;
}
return ShouldContinue(FormatMessageString(Constants.Messages.QueryBootstrap, providerName),
FormatMessageString(Constants.Messages.BootstrapProvider,
!string.IsNullOrWhiteSpace(requestor) ?
FormatMessageString(Constants.Messages.BootstrapProviderProviderRequested, requestor, providerName, providerVersion) :
FormatMessageString(Constants.Messages.BootstrapProviderUserRequested, providerName, providerVersion),
!string.IsNullOrWhiteSpace(providerType) && providerType.Equals(Constants.AssemblyProviderType) ?
FormatMessageString(Constants.Messages.BootstrapManualAssembly, providerName, location, destination) :
FormatMessageString(Constants.Messages.BootstrapManualInstall, providerName, location))).Result;
} catch (Exception e) {
e.Dump();
}
return false;
}
public virtual bool IsInteractive {
get {
return IsInvocation;
}
}
public virtual int CallCount {
get {
return _callCount;
}
}
#endregion
protected override void Init() {
if (!IsInitialized) {
// get the service ( forces initialization )
var x = PackageManagementService;
}
}
protected virtual string BootstrapNuGet
{
get
{
return _bootstrapNuGet;
}
set
{
_bootstrapNuGet = value;
}
}
protected virtual IHostApi PackageManagementHost
{
get
{
IHostApi parent = this;
return new object[] {
new {
GetOptionValues = new Func<string, IEnumerable<string>>(key => {
if (key.EqualsIgnoreCase(Microsoft.PackageManagement.Internal.Constants.BootstrapNuGet)) {
return BootstrapNuGet.SingleItemAsEnumerable();
}
return this.GetOptionValues(key);
}),
},
parent,
}.As<IHostApi>();
}
}
protected IEnumerable<PackageProvider> SelectProviders(string[] names) {
if (names.IsNullOrEmpty()) {
return ShouldSelectAllProviders ? PackageManagementService.SelectProviders(null, PackageManagementHost.SuppressErrorsAndWarnings(IsProcessing))
: PackageManagementService.SelectProviders(null, PackageManagementHost.SuppressErrorsAndWarnings(IsProcessing)).Where(each => !each.Features.ContainsKey(Microsoft.PackageManagement.Internal.Constants.Features.AutomationOnly));
}
// you can manually ask for any provider by name, if it is for automation only.
if (IsInvocation) {
return names.SelectMany(each => PackageManagementService.SelectProviders(each, this));
}
return names.SelectMany(each => PackageManagementService.SelectProviders(each, PackageManagementHost.SuppressErrorsAndWarnings(IsProcessing)));
}
protected IEnumerable<PackageProvider> SelectProviders(string name) {
// you can manually ask for any provider by name, if it is for automation only.
if (IsInvocation) {
var result = PackageManagementService.SelectProviders(name, PackageManagementHost).ToArray();
if ((result.Length == 0) && ShouldLogError) {
Error(Constants.Errors.UnknownProvider, name);
}
return result;
}
var r = PackageManagementService.SelectProviders(name, PackageManagementHost.SuppressErrorsAndWarnings(IsProcessing)).ToArray();
if ((r.Length == 0) && ShouldLogError){
Error(Constants.Errors.UnknownProvider, name);
}
return r;
}
public override bool ConsumeDynamicParameters() {
// pull data from dynamic parameters and place them into the DynamicOptions collection.
foreach (var rdp in DynamicParameterDictionary.Keys.Select(d => DynamicParameterDictionary[d]).Where(rdp => rdp.IsSet)) {
if (rdp.ParameterType == typeof (SwitchParameter)) {
_dynamicOptions[rdp.Name] = true;
} else {
_dynamicOptions[rdp.Name] = rdp.Value;
}
}
return true;
}
#region Event and telemetry stuff
//Calling PowerShell Telemetry APIs
protected void TraceMessage(string message, SoftwareIdentity swidObject) {
TelemetryAPI.TraceMessage(message,
new {
PackageName = swidObject.Name,
PackageVersion = swidObject.Version,
PackageProviderName = swidObject.ProviderName,
Repository = swidObject.Source,
ExuectionStatus = swidObject.Status,
ExecutionTime = DateTime.Today
});
}
protected enum EventTask {
None = 0,
Install = 1,
Uninstall = 2,
Download = 3
}
//We use Microsoft-Windows-PowerShell event source to log PackageManagement events.
//4101... is the eventid of Microsoft-Windows-PowerShell event provider
//You can find the events in Microsoft-Windows-PowerShell event log
protected enum EventId
{
Install = 4101,
Uninstall = 4102,
Save = 4103
}
protected void LogEvent(EventTask task, EventId id, string context, string name, string version, string providerName, string source, string status, string destinationPath)
{
var iis = InitialSessionState.CreateDefault2();
using (Runspace rs = RunspaceFactory.CreateRunspace(iis))
{
using (PowerShell powershell = PowerShell.Create())
{
try
{
rs.Open();
powershell.Runspace = rs;
powershell.AddCommand(Constants.NewWinEvent);
powershell.AddParameter("ProviderName", Constants.PowerShellProviderName);
powershell.AddParameter("Id", id);
powershell.AddParameter("Payload", new object[] {
task.ToString(),
string.Format(CultureInfo.InvariantCulture, "Package={0}, Version={1}, Provider={2}, Source={3}, Status={4}, DestinationPath={5}", name, version, providerName, source, status, destinationPath),
context});
powershell.Invoke();
}
catch (Exception ex)
{
Verbose(ex.Message);
}
}
}
}
#endregion
}
}