// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Management.Automation.Language; using System.Security; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation; using System.Diagnostics.CodeAnalysis; namespace System.Management.Automation { /// /// Holds the state of a Monad Shell session /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is a bridge class between internal classes and a public interface. It requires this much coupling.")] internal sealed partial class SessionStateInternal { #region tracer /// /// An instance of the PSTraceSource class used for trace output /// using "SessionState" as the category. /// [Dbg.TraceSourceAttribute( "SessionState", "SessionState Class")] private static Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("SessionState", "SessionState Class"); #endregion tracer #region Constructor /// /// Constructor for session state object /// /// /// /// The context for the runspace to which this session state object belongs. /// /// /// /// if is null. /// /// internal SessionStateInternal(ExecutionContext context) : this(null, false, context) { } internal SessionStateInternal(SessionStateInternal parent, bool linkToGlobal, ExecutionContext context) { if (context == null) { throw PSTraceSource.NewArgumentNullException("context"); } ExecutionContext = context; // Create the working directory stack. This // is used for the pushd and popd commands _workingLocationStack = new Dictionary>(StringComparer.OrdinalIgnoreCase); // Conservative choice to limit the Set-Location history in order to limit memory impact in case of a regression. const int locationHistoryLimit = 20; _SetLocationHistory = new BoundedStack(locationHistoryLimit); GlobalScope = new SessionStateScope(null); ModuleScope = GlobalScope; _currentScope = GlobalScope; InitializeSessionStateInternalSpecialVariables(false); // Create the push the global scope on as // the starting script scope. That way, if you dot-source a script // that uses variables qualified by script: it works. GlobalScope.ScriptScope = GlobalScope; if (parent != null) { GlobalScope.Parent = parent.GlobalScope; // Copy the drives and providers from the parent... CopyProviders(parent); // During loading of core modules, providers are not populated. // We set the drive information later if (Providers != null && Providers.Count > 0) { CurrentDrive = parent.CurrentDrive; } // Link it to the global scope... if (linkToGlobal) { GlobalScope = parent.GlobalScope; } } else { _currentScope.LocalsTuple = MutableTuple.MakeTuple(Compiler.DottedLocalsTupleType, Compiler.DottedLocalsNameIndexMap); } } /// /// Add any special variables to the session state variable table. This routine /// must be called at construction time or if the variable table is reset. /// internal void InitializeSessionStateInternalSpecialVariables(bool clearVariablesTable) { if (clearVariablesTable) { // Clear the variable table GlobalScope.Variables.Clear(); // Add in the per-scope default variables. GlobalScope.AddSessionStateScopeDefaultVariables(); } // Set variable $Error PSVariable errorvariable = new PSVariable("Error", new ArrayList(), ScopedItemOptions.Constant); GlobalScope.SetVariable(errorvariable.Name, errorvariable, false, false, this, fastPath: true); // Set variable $PSDefaultParameterValues Collection attributes = new Collection(); attributes.Add(new ArgumentTypeConverterAttribute(typeof(System.Management.Automation.DefaultParameterDictionary))); PSVariable psDefaultParameterValuesVariable = new PSVariable(SpecialVariables.PSDefaultParameterValues, new DefaultParameterDictionary(), ScopedItemOptions.None, attributes, RunspaceInit.PSDefaultParameterValuesDescription); GlobalScope.SetVariable(psDefaultParameterValuesVariable.Name, psDefaultParameterValuesVariable, false, false, this, fastPath: true); } #endregion Constructor #region Private data /// /// Provides all the path manipulation and globbing for Monad paths. /// /// internal LocationGlobber Globber { get { return _globberPrivate ?? (_globberPrivate = ExecutionContext.LocationGlobber); } } private LocationGlobber _globberPrivate; /// /// The context of the runspace to which this session state object belongs. /// internal ExecutionContext ExecutionContext { get; } /// /// Returns the public session state facade object for this session state instance. /// internal SessionState PublicSessionState { get { return _publicSessionState ?? (_publicSessionState = new SessionState(this)); } set { _publicSessionState = value; } } private SessionState _publicSessionState; /// /// Gets the engine APIs to access providers /// internal ProviderIntrinsics InvokeProvider { get { return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); } // get } private ProviderIntrinsics _invokeProvider; /// /// The module info object associated with this session state /// internal PSModuleInfo Module { get; set; } = null; // This is used to maintain the order in which modules were imported. // This is used by Get-Command -All to order by last imported internal List ModuleTableKeys = new List(); /// /// The private module table for this session state object... /// internal Dictionary ModuleTable { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Get/set constraints for this execution environment /// internal PSLanguageMode LanguageMode { get { return ExecutionContext.LanguageMode; } set { ExecutionContext.LanguageMode = value; } } /// /// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode /// internal bool UseFullLanguageModeInDebugger { get { return ExecutionContext.UseFullLanguageModeInDebugger; } } /// /// The list of scripts that are allowed to be run. If the name "*" /// is in the list, then all scripts can be run. (This is the default.) /// public List Scripts { get; } = new List(new string[] { "*" }); /// /// See if a script is allowed to be run. /// /// Path to check /// true if script is allowed internal SessionStateEntryVisibility CheckScriptVisibility(string scriptPath) { return checkPathVisibility(Scripts, scriptPath); } /// /// The list of applications that are allowed to be run. If the name "*" /// is in the list, then all applications can be run. (This is the default.) /// public List Applications { get; } = new List(new string[] { "*" }); /// /// List of functions/filters to export from this session state object... /// internal List ExportedCmdlets { get; } = new List(); /// /// Defines the default command visibility for this session state. Binding an InitialSessionState instance /// with private members will set this to Private. /// internal SessionStateEntryVisibility DefaultCommandVisibility = SessionStateEntryVisibility.Public; /// /// Add an new SessionState cmdlet entry to this session state object... /// /// The entry to add internal void AddSessionStateEntry(SessionStateCmdletEntry entry) { AddSessionStateEntry(entry, /*local*/false); } /// /// Add an new SessionState cmdlet entry to this session state object... /// /// The entry to add /// If local, add cmdlet to current scope. Else, add to module scope internal void AddSessionStateEntry(SessionStateCmdletEntry entry, bool local) { ExecutionContext.CommandDiscovery.AddSessionStateCmdletEntryToCache(entry, local); } /// /// Add an new SessionState cmdlet entry to this session state object... /// /// The entry to add internal void AddSessionStateEntry(SessionStateApplicationEntry entry) { this.Applications.Add(entry.Path); } /// /// Add an new SessionState cmdlet entry to this session state object... /// /// The entry to add internal void AddSessionStateEntry(SessionStateScriptEntry entry) { this.Scripts.Add(entry.Path); } /// /// Add the variables that must always be present in a SessionState instance... /// internal void InitializeFixedVariables() { // // BUGBUG // // String resources for aliases are currently associated with Runspace init // // $Host PSVariable v = new PSVariable( SpecialVariables.Host, ExecutionContext.EngineHostInterface, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSHostDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $HOME - indicate where a user's home directory is located in the file system. // -- %USERPROFILE% on windows // -- %HOME% on unix string home = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home) ?? string.Empty; v = new PSVariable(SpecialVariables.Home, home, ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope, RunspaceInit.HOMEDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $ExecutionContext v = new PSVariable(SpecialVariables.ExecutionContext, ExecutionContext.EngineIntrinsics, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.ExecutionContextDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PSVersionTable v = new PSVariable(SpecialVariables.PSVersionTable, PSVersionInfo.GetPSVersionTable(), ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSVersionTableDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PSEdition v = new PSVariable(SpecialVariables.PSEdition, PSVersionInfo.PSEditionValue, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSEditionDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PID Process currentProcess = Process.GetCurrentProcess(); v = new PSVariable( SpecialVariables.PID, currentProcess.Id, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PIDDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PSCulture v = new PSCultureVariable(); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PSUICulture v = new PSUICultureVariable(); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $? v = new QuestionMarkVariable(this.ExecutionContext); this.GlobalScope.SetVariableForce(v, this); // $ShellId - if there is no runspace config, use the default string string shellId = ExecutionContext.ShellID; v = new PSVariable(SpecialVariables.ShellId, shellId, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.MshShellIdDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); // $PSHOME // This depends on the shellId. If we cannot read the application base // registry key, set the variable to empty string string applicationBase = string.Empty; try { applicationBase = Utils.GetApplicationBase(shellId); } catch (SecurityException) { } v = new PSVariable(SpecialVariables.PSHome, applicationBase, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PSHOMEDescription); this.GlobalScope.SetVariable(v.Name, v, false, true, this, CommandOrigin.Internal, fastPath: true); } /// /// Check to see if an application is allowed to be run. /// /// The path to the application to check /// True if application is permitted. internal SessionStateEntryVisibility CheckApplicationVisibility(string applicationPath) { return checkPathVisibility(Applications, applicationPath); } private SessionStateEntryVisibility checkPathVisibility(List list, string path) { if (list == null || list.Count == 0) return SessionStateEntryVisibility.Private; if (String.IsNullOrEmpty(path)) return SessionStateEntryVisibility.Private; if (list.Contains("*")) return SessionStateEntryVisibility.Public; foreach (string p in list) { if (String.Equals(p, path, StringComparison.OrdinalIgnoreCase)) return SessionStateEntryVisibility.Public; if (WildcardPattern.ContainsWildcardCharacters(p)) { WildcardPattern pattern = WildcardPattern.Get(p, WildcardOptions.IgnoreCase); if (pattern.IsMatch(path)) { return SessionStateEntryVisibility.Public; } } } return SessionStateEntryVisibility.Private; } #endregion Private data /// /// Notification for SessionState to do cleanup /// before runspace is closed. /// /// internal void RunspaceClosingNotification() { if (this != ExecutionContext.TopLevelSessionState && Providers.Count > 0) { // Remove all providers at the top level... CmdletProviderContext context = new CmdletProviderContext(this.ExecutionContext); foreach (string providerName in Providers.Keys) { // All errors are ignored. RemoveProvider(providerName, true, context); } } } #region Errors /// /// Constructs a new instance of a ProviderInvocationException /// using the specified data /// /// /// /// The resource ID to use as the format message for the error. /// /// /// /// This is the message template string. /// /// /// /// The provider information used when formatting the error message. /// /// /// /// The path used when formatting the error message. /// /// /// /// The exception that was thrown by the provider. This will be set as /// the ProviderInvocationException's InnerException and the message will /// be used when formatting the error message. /// /// /// /// A new instance of a ProviderInvocationException. /// /// /// /// Wraps in a ProviderInvocationException /// and then throws it. /// /// internal ProviderInvocationException NewProviderInvocationException( string resourceId, string resourceStr, ProviderInfo provider, string path, Exception e) { return NewProviderInvocationException(resourceId, resourceStr, provider, path, e, true); } /// /// Constructs a new instance of a ProviderInvocationException /// using the specified data /// /// /// /// The resource ID to use as the format message for the error. /// /// /// /// This is the message template string. /// /// /// /// The provider information used when formatting the error message. /// /// /// /// The path used when formatting the error message. /// /// /// /// The exception that was thrown by the provider. This will be set as /// the ProviderInvocationException's InnerException and the message will /// be used when formatting the error message. /// /// /// /// If true, the error record from the inner exception will be used if it contains one. /// If false, the error message specified by the resourceId will be used. /// /// /// /// A new instance of a ProviderInvocationException. /// /// /// /// Wraps in a ProviderInvocationException /// and then throws it. /// /// internal ProviderInvocationException NewProviderInvocationException( string resourceId, string resourceStr, ProviderInfo provider, string path, Exception e, bool useInnerExceptionErrorMessage) { // If the innerException was itself thrown by // ProviderBase.ThrowTerminatingError, it is already a // ProviderInvocationException, and we don't want to // re-wrap it. ProviderInvocationException pie = e as ProviderInvocationException; if (pie != null) { pie._providerInfo = provider; return pie; } pie = new ProviderInvocationException(resourceId, resourceStr, provider, path, e, useInnerExceptionErrorMessage); // Log a provider health event MshLog.LogProviderHealthEvent( ExecutionContext, provider.Name, pie, Severity.Warning); return pie; } #endregion Errors } // SessionStateInternal class }