mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
* Implement location history feature for Set-Location. This makes it possible to go back to the last directory using 'cd -', which is exactly the same syntax as on Linux shells. * Use an existing stack implementation instead of relying on an environment variable and move logic for 'cd -' into the internal class of SessionState.Path.SetLocation Keep the (additional) setting of an environment for Unix system in case of mixed usage of cd and Set-Location to provide consistency. * Use a bounded stack with a limit of 1000 and adapt tests. Remove setting environment variable on Linux as discussed in PR. * Fix test: use get-location explicitly to avoid subtle difference in the path string (e.g. an additional slash at the end) Make syntax to .net call of environment consistent with surrounding code * Move initialization of _workingLocationHistoryStack into constructor of SessionStateInternal and set the limit to 20 as discussed in PR 5051. * Make new BoundedStack class in engine utils internal
564 lines
22 KiB
C#
564 lines
22 KiB
C#
// 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
|
|
{
|
|
/// <summary>
|
|
/// Holds the state of a Monad Shell session
|
|
/// </summary>
|
|
|
|
[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
|
|
|
|
/// <summary>
|
|
/// An instance of the PSTraceSource class used for trace output
|
|
/// using "SessionState" as the category.
|
|
/// </summary>
|
|
[Dbg.TraceSourceAttribute(
|
|
"SessionState",
|
|
"SessionState Class")]
|
|
private static Dbg.PSTraceSource s_tracer =
|
|
Dbg.PSTraceSource.GetTracer("SessionState",
|
|
"SessionState Class");
|
|
|
|
#endregion tracer
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor for session state object
|
|
/// </summary>
|
|
///
|
|
/// <param name="context">
|
|
/// The context for the runspace to which this session state object belongs.
|
|
/// </param>
|
|
///
|
|
/// <exception cref="ArgumentNullException">
|
|
/// if <paramref name="context"/> is null.
|
|
/// </exception>
|
|
///
|
|
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<String, Stack<PathInfo>>(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<PathInfo>(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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<Attribute> attributes = new Collection<Attribute>();
|
|
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
|
|
|
|
/// <summary>
|
|
/// Provides all the path manipulation and globbing for Monad paths.
|
|
/// </summary>
|
|
///
|
|
internal LocationGlobber Globber
|
|
{
|
|
get { return _globberPrivate ?? (_globberPrivate = ExecutionContext.LocationGlobber); }
|
|
}
|
|
private LocationGlobber _globberPrivate;
|
|
|
|
/// <summary>
|
|
/// The context of the runspace to which this session state object belongs.
|
|
/// </summary>
|
|
internal ExecutionContext ExecutionContext { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the public session state facade object for this session state instance.
|
|
/// </summary>
|
|
internal SessionState PublicSessionState
|
|
{
|
|
get { return _publicSessionState ?? (_publicSessionState = new SessionState(this)); }
|
|
set { _publicSessionState = value; }
|
|
}
|
|
private SessionState _publicSessionState;
|
|
|
|
/// <summary>
|
|
/// Gets the engine APIs to access providers
|
|
/// </summary>
|
|
internal ProviderIntrinsics InvokeProvider
|
|
{
|
|
get { return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); } // get
|
|
}
|
|
private ProviderIntrinsics _invokeProvider;
|
|
|
|
/// <summary>
|
|
/// The module info object associated with this session state
|
|
/// </summary>
|
|
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<string> ModuleTableKeys = new List<string>();
|
|
|
|
/// <summary>
|
|
/// The private module table for this session state object...
|
|
/// </summary>
|
|
internal Dictionary<string, PSModuleInfo> ModuleTable { get; } = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Get/set constraints for this execution environment
|
|
/// </summary>
|
|
internal PSLanguageMode LanguageMode
|
|
{
|
|
get
|
|
{
|
|
return ExecutionContext.LanguageMode;
|
|
}
|
|
set
|
|
{
|
|
ExecutionContext.LanguageMode = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true the PowerShell debugger will use FullLanguage mode, otherwise it will use the current language mode
|
|
/// </summary>
|
|
internal bool UseFullLanguageModeInDebugger
|
|
{
|
|
get
|
|
{
|
|
return ExecutionContext.UseFullLanguageModeInDebugger;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.)
|
|
/// </summary>
|
|
public List<string> Scripts { get; } = new List<string>(new string[] { "*" });
|
|
|
|
/// <summary>
|
|
/// See if a script is allowed to be run.
|
|
/// </summary>
|
|
/// <param name="scriptPath">Path to check</param>
|
|
/// <returns>true if script is allowed</returns>
|
|
internal SessionStateEntryVisibility CheckScriptVisibility(string scriptPath)
|
|
{
|
|
return checkPathVisibility(Scripts, scriptPath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.)
|
|
/// </summary>
|
|
public List<string> Applications { get; } = new List<string>(new string[] { "*" });
|
|
|
|
/// <summary>
|
|
/// List of functions/filters to export from this session state object...
|
|
/// </summary>
|
|
internal List<CmdletInfo> ExportedCmdlets { get; } = new List<CmdletInfo>();
|
|
|
|
/// <summary>
|
|
/// Defines the default command visibility for this session state. Binding an InitialSessionState instance
|
|
/// with private members will set this to Private.
|
|
/// </summary>
|
|
internal SessionStateEntryVisibility DefaultCommandVisibility = SessionStateEntryVisibility.Public;
|
|
|
|
/// <summary>
|
|
/// Add an new SessionState cmdlet entry to this session state object...
|
|
/// </summary>
|
|
/// <param name="entry">The entry to add</param>
|
|
internal void AddSessionStateEntry(SessionStateCmdletEntry entry)
|
|
{
|
|
AddSessionStateEntry(entry, /*local*/false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an new SessionState cmdlet entry to this session state object...
|
|
/// </summary>
|
|
/// <param name="entry">The entry to add</param>
|
|
/// <param name="local">If local, add cmdlet to current scope. Else, add to module scope</param>
|
|
internal void AddSessionStateEntry(SessionStateCmdletEntry entry, bool local)
|
|
{
|
|
ExecutionContext.CommandDiscovery.AddSessionStateCmdletEntryToCache(entry, local);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an new SessionState cmdlet entry to this session state object...
|
|
/// </summary>
|
|
/// <param name="entry">The entry to add</param>
|
|
internal void AddSessionStateEntry(SessionStateApplicationEntry entry)
|
|
{
|
|
this.Applications.Add(entry.Path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an new SessionState cmdlet entry to this session state object...
|
|
/// </summary>
|
|
/// <param name="entry">The entry to add</param>
|
|
internal void AddSessionStateEntry(SessionStateScriptEntry entry)
|
|
{
|
|
this.Scripts.Add(entry.Path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the variables that must always be present in a SessionState instance...
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check to see if an application is allowed to be run.
|
|
/// </summary>
|
|
/// <param name="applicationPath">The path to the application to check</param>
|
|
/// <returns>True if application is permitted.</returns>
|
|
internal SessionStateEntryVisibility CheckApplicationVisibility(string applicationPath)
|
|
{
|
|
return checkPathVisibility(Applications, applicationPath);
|
|
}
|
|
|
|
private SessionStateEntryVisibility checkPathVisibility(List<string> 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
|
|
|
|
/// <summary>
|
|
/// Notification for SessionState to do cleanup
|
|
/// before runspace is closed.
|
|
/// </summary>
|
|
///
|
|
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
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of a ProviderInvocationException
|
|
/// using the specified data
|
|
/// </summary>
|
|
///
|
|
/// <param name="resourceId">
|
|
/// The resource ID to use as the format message for the error.
|
|
/// </param>
|
|
///
|
|
/// <param name="resourceStr">
|
|
/// This is the message template string.
|
|
/// </param>
|
|
///
|
|
/// <param name="provider">
|
|
/// The provider information used when formatting the error message.
|
|
/// </param>
|
|
///
|
|
/// <param name="path">
|
|
/// The path used when formatting the error message.
|
|
/// </param>
|
|
///
|
|
/// <param name="e">
|
|
/// 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.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A new instance of a ProviderInvocationException.
|
|
/// </returns>
|
|
///
|
|
/// <exception cref="ProviderInvocationException">
|
|
/// Wraps <paramref name="e"/> in a ProviderInvocationException
|
|
/// and then throws it.
|
|
/// </exception>
|
|
///
|
|
internal ProviderInvocationException NewProviderInvocationException(
|
|
string resourceId,
|
|
string resourceStr,
|
|
ProviderInfo provider,
|
|
string path,
|
|
Exception e)
|
|
{
|
|
return NewProviderInvocationException(resourceId, resourceStr, provider, path, e, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of a ProviderInvocationException
|
|
/// using the specified data
|
|
/// </summary>
|
|
///
|
|
/// <param name="resourceId">
|
|
/// The resource ID to use as the format message for the error.
|
|
/// </param>
|
|
///
|
|
/// <param name="resourceStr">
|
|
/// This is the message template string.
|
|
/// </param>
|
|
///
|
|
/// <param name="provider">
|
|
/// The provider information used when formatting the error message.
|
|
/// </param>
|
|
///
|
|
/// <param name="path">
|
|
/// The path used when formatting the error message.
|
|
/// </param>
|
|
///
|
|
/// <param name="e">
|
|
/// 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.
|
|
/// </param>
|
|
///
|
|
/// <param name="useInnerExceptionErrorMessage">
|
|
/// 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.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A new instance of a ProviderInvocationException.
|
|
/// </returns>
|
|
///
|
|
/// <exception cref="ProviderInvocationException">
|
|
/// Wraps <paramref name="e"/> in a ProviderInvocationException
|
|
/// and then throws it.
|
|
/// </exception>
|
|
///
|
|
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
|
|
}
|