/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
///
/// Used to enumerate the commands on the system that match the specified
/// command name
///
internal class CommandSearcher : IEnumerable, IEnumerator
{
///
/// Constructs a command searching enumerator that resolves the location
/// to a command using a standard algorithm.
///
///
///
/// The name of the command to look for.
///
///
///
/// Determines which types of commands glob resolution of the name will take place on.
///
///
///
/// The types of commands to look for.
///
///
///
/// The execution context for this engine instance...
///
///
///
/// If is null.
///
///
///
/// If is null or empty.
///
///
internal CommandSearcher(
string commandName,
SearchResolutionOptions options,
CommandTypes commandTypes,
ExecutionContext context)
{
Diagnostics.Assert(context != null, "caller to verify context is not null");
Diagnostics.Assert(!string.IsNullOrEmpty(commandName), "caller to verify commandName is valid");
_commandName = commandName;
_context = context;
_commandResolutionOptions = options;
_commandTypes = commandTypes;
// Initialize the enumerators
this.Reset();
}
///
/// Gets an instance of a command enumerator
///
///
///
/// An instance of this class as IEnumerator.
///
///
IEnumerator IEnumerable.GetEnumerator()
{
return this;
} // GetEnumerator
IEnumerator IEnumerable.GetEnumerator()
{
return this;
} // GetEnumerator
///
/// Moves the enumerator to the next command match. Public for IEnumerable
///
///
///
/// true if there was another command that matches, false otherwise.
///
///
public bool MoveNext()
{
_currentMatch = null;
if (_currentState == SearchState.SearchingAliases)
{
_currentMatch = SearchForAliases();
// Why don't we check IsVisible on other scoped items?
if (_currentMatch != null && SessionState.IsVisible(_commandOrigin, _currentMatch))
{
return true;
}
// Make sure Current doesn't return an alias that isn't visible
_currentMatch = null;
// Advance the state
_currentState = SearchState.SearchingFunctions;
}
if (_currentState == SearchState.SearchingFunctions)
{
_currentMatch = SearchForFunctions();
// Return the alias info only if it is visible. If not, then skip to the next
// stage of command resolution...
if (_currentMatch != null)
{
return true;
}
// Advance the state
_currentState = SearchState.SearchingCmdlets;
}
if (_currentState == SearchState.SearchingCmdlets)
{
_currentMatch = SearchForCmdlets();
if (_currentMatch != null)
{
return true;
}
// Advance the state
_currentState = SearchState.SearchingBuiltinScripts;
}
if (_currentState == SearchState.SearchingBuiltinScripts)
{
_currentMatch = SearchForBuiltinScripts();
if (_currentMatch != null)
{
return true;
}
// Advance the state
_currentState = SearchState.StartSearchingForExternalCommands;
}
if (_currentState == SearchState.StartSearchingForExternalCommands)
{
if ((_commandTypes & (CommandTypes.Application | CommandTypes.ExternalScript)) == 0)
{
// Since we are not requiring any path lookup in this search, just return false now
// because all the remaining searches do path lookup.
return false;
}
// For security reasons, if the command is coming from outside the runspace and it looks like a path,
// we want to pre-check that path before doing any probing of the network or drives
if (_commandOrigin == CommandOrigin.Runspace && _commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) >= 0)
{
bool allowed = false;
// Ok - it looks like it might be a path, so we're going to check to see if the command is prefixed
// by any of the allowed paths. If so, then we allow the search to proceed...
// If either the Applications or Script lists contain just '*' the command is allowed
// at this point.
if ((_context.EngineSessionState.Applications.Count == 1 &&
_context.EngineSessionState.Applications[0].Equals("*", StringComparison.OrdinalIgnoreCase)) ||
(_context.EngineSessionState.Scripts.Count == 1 &&
_context.EngineSessionState.Scripts[0].Equals("*", StringComparison.OrdinalIgnoreCase)))
{
allowed = true;
}
else
{
// Ok see it it's in the applications list
foreach (string path in _context.EngineSessionState.Applications)
{
if (checkPath(path, _commandName))
{
allowed = true;
break;
}
}
// If it wasn't in the applications list, see it's in the script list
if (!allowed)
{
foreach (string path in _context.EngineSessionState.Scripts)
{
if (checkPath(path, _commandName))
{
allowed = true;
break;
}
}
}
}
if (!allowed)
{
return false;
}
}
// Advance the state
_currentState = SearchState.PowerShellPathResolution;
_currentMatch = ProcessBuiltinScriptState();
if (_currentMatch != null)
{
// Set the current state to QualifiedFileSystemPath since
// we want to skip the qualified file system path search
// in the case where we found a PowerShell qualified path.
_currentState = SearchState.QualifiedFileSystemPath;
return true;
}
} // SearchState.Reset
if (_currentState == SearchState.PowerShellPathResolution)
{
_currentState = SearchState.QualifiedFileSystemPath;
_currentMatch = ProcessPathResolutionState();
if (_currentMatch != null)
{
return true;
}
} // SearchState.PowerShellPathResolution
// Search using CommandPathSearch
if (_currentState == SearchState.QualifiedFileSystemPath ||
_currentState == SearchState.PathSearch)
{
_currentMatch = ProcessQualifiedFileSystemState();
if (_currentMatch != null)
{
return true;
}
} // SearchState.QualifiedFileSystemPath || SearchState.PathSearch
if (_currentState == SearchState.PathSearch)
{
_currentState = SearchState.PowerShellRelativePath;
_currentMatch = ProcessPathSearchState();
if (_currentMatch != null)
{
return true;
}
}
return false;
} // MoveNext
private CommandInfo SearchForAliases()
{
CommandInfo currentMatch = null;
if (_context.EngineSessionState != null &&
(_commandTypes & CommandTypes.Alias) != 0)
{
currentMatch = GetNextAlias();
}
return currentMatch;
}
private CommandInfo SearchForFunctions()
{
CommandInfo currentMatch = null;
if (_context.EngineSessionState != null &&
(_commandTypes & (CommandTypes.Function | CommandTypes.Filter | CommandTypes.Workflow | CommandTypes.Configuration)) != 0)
{
currentMatch = GetNextFunction();
}
return currentMatch;
}
private CommandInfo SearchForCmdlets()
{
CommandInfo currentMatch = null;
if ((_commandTypes & CommandTypes.Cmdlet) != 0)
{
currentMatch = GetNextCmdlet();
}
return currentMatch;
}
private CommandInfo SearchForBuiltinScripts()
{
CommandInfo currentMatch = null;
if ((_commandTypes & CommandTypes.Script) != 0)
{
currentMatch = GetNextBuiltinScript();
}
return currentMatch;
}
private CommandInfo ProcessBuiltinScriptState()
{
CommandInfo currentMatch = null;
// Check to see if the path is qualified
if (_context.EngineSessionState != null &&
_context.EngineSessionState.ProviderCount > 0 &&
IsQualifiedPSPath(_commandName))
{
currentMatch = GetNextFromPath();
}
return currentMatch;
}
private CommandInfo ProcessPathResolutionState()
{
CommandInfo currentMatch = null;
try
{
// Check to see if the path is a file system path that
// is rooted. If so that is the next match
if (Path.IsPathRooted(_commandName) &&
File.Exists(_commandName))
{
try
{
currentMatch = GetInfoFromPath(_commandName);
}
catch (FileLoadException)
{
}
catch (FormatException)
{
}
catch (MetadataException)
{
}
}
}
catch (ArgumentException)
{
// If the path contains illegal characters that
// weren't caught by the other APIs, IsPathRooted
// will throw an exception.
// For example, looking for a command called
// `abcdef
// The `a will be translated into the beep control character
// which is not a legal file system character, though
// Path.InvalidPathChars does not contain it as an invalid
// character.
}
return currentMatch;
}
private CommandInfo ProcessQualifiedFileSystemState()
{
try
{
setupPathSearcher();
}
catch (ArgumentException)
{
_currentState = SearchState.NoMoreMatches;
throw;
}
catch (PathTooLongException)
{
_currentState = SearchState.NoMoreMatches;
throw;
}
CommandInfo currentMatch = null;
_currentState = SearchState.PathSearch;
if (_canDoPathLookup)
{
try
{
while (currentMatch == null && _pathSearcher.MoveNext())
{
currentMatch = GetInfoFromPath(((IEnumerator)_pathSearcher).Current);
}
}
catch (InvalidOperationException)
{
// The enumerator may throw if there are no more matches
}
}
return currentMatch;
}
private CommandInfo ProcessPathSearchState()
{
CommandInfo currentMatch = null;
string path = DoPowerShellRelativePathLookup();
if (!String.IsNullOrEmpty(path))
{
currentMatch = GetInfoFromPath(path);
}
return currentMatch;
}
///
/// Gets the CommandInfo representing the current command match.
///
///
///
///
/// The enumerator is positioned before the first element of
/// the collection or after the last element.
///
///
CommandInfo IEnumerator.Current
{
get
{
if ((_currentState == SearchState.SearchingAliases && _currentMatch == null) ||
_currentState == SearchState.NoMoreMatches ||
_currentMatch == null)
{
throw PSTraceSource.NewInvalidOperationException();
}
return _currentMatch;
}
} // Current
object IEnumerator.Current
{
get
{
return ((IEnumerator)this).Current;
}
}
///
/// Required by the IEnumerator generic interface.
/// Resets the searcher.
///
public void Dispose()
{
if (_pathSearcher != null)
{
_pathSearcher.Dispose();
_pathSearcher = null;
}
Reset();
GC.SuppressFinalize(this);
}
#region private members
///
/// Gets the next command info using the command name as a path
///
///
///
/// A CommandInfo for the next command if it exists as a path, or null otherwise.
///
///
private CommandInfo GetNextFromPath()
{
CommandInfo result = null;
do // false loop
{
CommandDiscovery.discoveryTracer.WriteLine(
"The name appears to be a qualified path: {0}",
_commandName);
CommandDiscovery.discoveryTracer.WriteLine(
"Trying to resolve the path as an PSPath");
// Find the match if it is.
Collection resolvedPaths = new Collection();
try
{
Provider.CmdletProvider providerInstance;
ProviderInfo provider;
resolvedPaths =
_context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(_commandName, false, out provider, out providerInstance);
}
catch (ItemNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path could not be found: {0}",
_commandName);
}
catch (DriveNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A drive could not be found for the path: {0}",
_commandName);
}
catch (ProviderNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A provider could not be found for the path: {0}",
_commandName);
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path specified a home directory, but the provider home directory was not set. {0}",
_commandName);
}
catch (ProviderInvocationException providerException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' encountered an error: {1}",
_commandName,
providerException.Message);
}
catch (PSNotSupportedException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' does not implement ContainerCmdletProvider",
_commandName);
}
if (resolvedPaths.Count > 1)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path resolved to more than one result so this path cannot be used.");
break;
}
// If the path was resolved, and it exists
if (resolvedPaths.Count == 1 &&
File.Exists(resolvedPaths[0]))
{
string path = resolvedPaths[0];
CommandDiscovery.discoveryTracer.WriteLine(
"Path resolved to: {0}",
path);
result = GetInfoFromPath(path);
}
} while (false);
return result;
}
private static bool checkPath(string path, string commandName)
{
return path.StartsWith(commandName, StringComparison.OrdinalIgnoreCase);
}
///
/// Gets the appropriate CommandInfo instance given the specified path.
///
///
///
/// The path to create the CommandInfo for.
///
///
///
/// An instance of the appropriate CommandInfo derivative given the specified path.
///
///
///
/// The refers to a cmdlet, or cmdletprovider
/// and it could not be loaded as an XML document.
///
///
///
/// The refers to a cmdlet, or cmdletprovider
/// that does not adhere to the appropriate file format for its extension.
///
///
///
/// If refers to a cmdlet file that
/// contains invalid metadata.
///
///
private CommandInfo GetInfoFromPath(string path)
{
CommandInfo result = null;
do // false loop
{
if (!Utils.NativeFileExists(path))
{
CommandDiscovery.discoveryTracer.TraceError("The path does not exist: {0}", path);
break;
}
// Now create the appropriate CommandInfo using the extension
string extension = null;
try
{
extension = Path.GetExtension(path);
}
catch (ArgumentException)
{
// If the path contains illegal characters that
// weren't caught by the other APIs, GetExtension
// will throw an exception.
// For example, looking for a command called
// `abcdef
// The `a will be translated into the beep control character
// which is not a legal file system character.
}
if (extension == null)
{
result = null;
break;
}
if (String.Equals(extension, StringLiterals.PowerShellScriptFileExtension, StringComparison.OrdinalIgnoreCase))
{
if ((_commandTypes & CommandTypes.ExternalScript) != 0)
{
string scriptName = Path.GetFileName(path);
CommandDiscovery.discoveryTracer.WriteLine(
"Command Found: path ({0}) is a script with name: {1}",
path,
scriptName);
// The path is to a PowerShell script
result = new ExternalScriptInfo(scriptName, path, _context);
break;
}
break;
}
if ((_commandTypes & CommandTypes.Application) != 0)
{
// Anything else is treated like an application
string appName = Path.GetFileName(path);
CommandDiscovery.discoveryTracer.WriteLine(
"Command Found: path ({0}) is an application with name: {1}",
path,
appName);
result = new ApplicationInfo(appName, path, _context);
break;
}
} while (false);
// Verify that this script is not untrusted, if we aren't constrained.
if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))
{
result = null;
}
return result;
} // GetNextFromPath
///
/// Gets the next matching alias
///
///
///
/// A CommandInfo representing the next matching alias if found, otherwise null.
///
///
private CommandInfo GetNextAlias()
{
CommandInfo result = null;
if ((_commandResolutionOptions & SearchResolutionOptions.ResolveAliasPatterns) != 0)
{
if (_matchingAlias == null)
{
// Generate the enumerator of matching alias names
Collection matchingAliases = new Collection();
WildcardPattern aliasMatcher =
WildcardPattern.Get(
_commandName,
WildcardOptions.IgnoreCase);
foreach (KeyValuePair aliasEntry in _context.EngineSessionState.GetAliasTable())
{
if (aliasMatcher.IsMatch(aliasEntry.Key))
{
matchingAliases.Add(aliasEntry.Value);
}
}
// Process alias from modules
AliasInfo c = GetAliasFromModules(_commandName);
if (c != null)
{
matchingAliases.Add(c);
}
_matchingAlias = matchingAliases.GetEnumerator();
}
if (!_matchingAlias.MoveNext())
{
// Advance the state
_currentState = SearchState.SearchingFunctions;
_matchingAlias = null;
}
else
{
result = _matchingAlias.Current;
}
}
else
{
// Advance the state
_currentState = SearchState.SearchingFunctions;
result = _context.EngineSessionState.GetAlias(_commandName) ?? GetAliasFromModules(_commandName);
}
// Verify that this alias was not created by an untrusted constrained language,
// if we aren't constrained.
if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))
{
result = null;
}
if (result != null)
{
CommandDiscovery.discoveryTracer.WriteLine(
"Alias found: {0} {1}",
result.Name,
result.Definition);
}
return result;
} // GetNextAlias
///
/// Gets the next matching function
///
///
///
/// A CommandInfo representing the next matching function if found, otherwise null.
///
///
private CommandInfo GetNextFunction()
{
CommandInfo result = null;
if ((_commandResolutionOptions & SearchResolutionOptions.ResolveFunctionPatterns) != 0)
{
if (_matchingFunctionEnumerator == null)
{
Collection matchingFunction = new Collection();
// Generate the enumerator of matching function names
WildcardPattern functionMatcher =
WildcardPattern.Get(
_commandName,
WildcardOptions.IgnoreCase);
foreach (DictionaryEntry functionEntry in _context.EngineSessionState.GetFunctionTable())
{
if (functionMatcher.IsMatch((string)functionEntry.Key))
{
matchingFunction.Add((CommandInfo)functionEntry.Value);
}
}
// Process functions from modules
CommandInfo c = GetFunctionFromModules(_commandName);
if (c != null)
{
matchingFunction.Add(c);
}
_matchingFunctionEnumerator = matchingFunction.GetEnumerator();
}
if (!_matchingFunctionEnumerator.MoveNext())
{
// Advance the state
_currentState = SearchState.SearchingCmdlets;
_matchingFunctionEnumerator = null;
}
else
{
result = _matchingFunctionEnumerator.Current;
}
}
else
{
// Advance the state
_currentState = SearchState.SearchingCmdlets;
result = GetFunction(_commandName);
}
// Verify that this function was not created by an untrusted constrained language,
// if we aren't constrained.
if (ShouldSkipCommandResolutionForConstrainedLanguage(result, _context))
{
result = null;
}
return result;
}
// Don't return commands to the user if that might result in:
// - Trusted commands calling untrusted functions that the user has overridden
// - Debug prompts calling internal functions that are likely to have code injection
private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo result, ExecutionContext executionContext)
{
if (result == null)
{
return false;
}
// Don't return untrusted commands to trusted functions
if ((result.DefiningLanguageMode == PSLanguageMode.ConstrainedLanguage) &&
(executionContext.LanguageMode == PSLanguageMode.FullLanguage))
{
return true;
}
// Don't allow invocation of trusted functions from debug breakpoints.
// They were probably defined within a trusted script, and could be
// susceptible to injection attacks. However, we do allow execution
// of functions defined in the global scope (i.e.: "more",) as those
// are intended to be exposed explicitly to users.
if ((result is FunctionInfo) &&
(executionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage) &&
(result.DefiningLanguageMode == PSLanguageMode.FullLanguage) &&
(executionContext.Debugger != null) &&
(executionContext.Debugger.InBreakpoint) &&
(!(executionContext.TopLevelSessionState.GetFunctionTableAtScope("GLOBAL").ContainsKey(result.Name))))
{
return true;
}
return false;
}
private AliasInfo GetAliasFromModules(string command)
{
AliasInfo result = null;
if (command.IndexOf('\\') > 0)
{
// See if it's a module qualified alias...
PSSnapinQualifiedName qualifiedName = PSSnapinQualifiedName.GetInstance(command);
if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName))
{
PSModuleInfo module = GetImportedModuleByName(qualifiedName.PSSnapInName);
if (module != null)
{
module.ExportedAliases.TryGetValue(qualifiedName.ShortName, out result);
}
}
}
return result;
}
private CommandInfo GetFunctionFromModules(string command)
{
FunctionInfo result = null;
if (command.IndexOf('\\') > 0)
{
// See if it's a module qualified function call...
PSSnapinQualifiedName qualifiedName = PSSnapinQualifiedName.GetInstance(command);
if (qualifiedName != null && !string.IsNullOrEmpty(qualifiedName.PSSnapInName))
{
PSModuleInfo module = GetImportedModuleByName(qualifiedName.PSSnapInName);
if (module != null)
{
module.ExportedFunctions.TryGetValue(qualifiedName.ShortName, out result);
}
}
}
return result;
}
private PSModuleInfo GetImportedModuleByName(string moduleName)
{
PSModuleInfo module = null;
List modules = _context.Modules.GetModules(new string[] { moduleName }, false);
if (modules != null && modules.Count > 0)
{
foreach (PSModuleInfo m in modules)
{
if (_context.previousModuleImported.ContainsKey(m.Name) && ((string)_context.previousModuleImported[m.Name] == m.Path))
{
module = m;
break;
}
}
if (module == null)
{
module = modules[0];
}
}
return module;
}
///
/// Gets the FunctionInfo or FilterInfo for the specified function name.
///
///
///
/// The name of the function/filter to retrieve.
///
///
///
/// A FunctionInfo if the function name exists and is a function, a FilterInfo if
/// the filter name exists and is a filter, or null otherwise.
///
///
private CommandInfo GetFunction(string function)
{
CommandInfo result = _context.EngineSessionState.GetFunction(function);
if (result != null)
{
if (result is FilterInfo)
{
CommandDiscovery.discoveryTracer.WriteLine(
"Filter found: {0}",
function);
}
else if (result is ConfigurationInfo)
{
CommandDiscovery.discoveryTracer.WriteLine(
"Configuration found: {0}",
function);
}
else
{
CommandDiscovery.discoveryTracer.WriteLine(
"Function found: {0} {1}",
function);
}
}
else
{
result = GetFunctionFromModules(function);
}
return result;
} // GetFunction
///
/// Gets the next cmdlet from the collection of matching cmdlets.
/// If the collection doesn't exist yet it is created and the
/// enumerator is moved to the first item in the collection.
///
///
///
/// A CmdletInfo for the next matching Cmdlet or null if there are
/// no more matches.
///
///
private CmdletInfo GetNextCmdlet()
{
CmdletInfo result = null;
if (_matchingCmdlet == null)
{
if ((_commandResolutionOptions & SearchResolutionOptions.CommandNameIsPattern) != 0)
{
Collection matchingCmdletInfo = new Collection();
PSSnapinQualifiedName PSSnapinQualifiedCommandName =
PSSnapinQualifiedName.GetInstance(_commandName);
if (PSSnapinQualifiedCommandName == null)
{
return null;
}
WildcardPattern cmdletMatcher =
WildcardPattern.Get(
PSSnapinQualifiedCommandName.ShortName,
WildcardOptions.IgnoreCase);
SessionStateInternal ss = _context.EngineSessionState;
foreach (List cmdletList in ss.GetCmdletTable().Values)
{
foreach (CmdletInfo cmdlet in cmdletList)
{
if (cmdletMatcher.IsMatch(cmdlet.Name))
{
if (string.IsNullOrEmpty(PSSnapinQualifiedCommandName.PSSnapInName) ||
(PSSnapinQualifiedCommandName.PSSnapInName.Equals(
cmdlet.ModuleName, StringComparison.OrdinalIgnoreCase)))
{
// If PSSnapin is specified, make sure they match
matchingCmdletInfo.Add(cmdlet);
}
}
}
}
_matchingCmdlet = matchingCmdletInfo.GetEnumerator();
}
else
{
_matchingCmdlet = _context.CommandDiscovery.GetCmdletInfo(_commandName,
(_commandResolutionOptions & SearchResolutionOptions.SearchAllScopes) != 0);
}
}
if (!_matchingCmdlet.MoveNext())
{
// Advance the state
_currentState = SearchState.SearchingBuiltinScripts;
_matchingCmdlet = null;
}
else
{
result = _matchingCmdlet.Current;
}
return traceResult(result);
}
private IEnumerator _matchingCmdlet;
private static CmdletInfo traceResult(CmdletInfo result)
{
if (result != null)
{
CommandDiscovery.discoveryTracer.WriteLine(
"Cmdlet found: {0} {1}",
result.Name,
result.ImplementingType);
}
return result;
}
///
/// Gets the next builtin script from the collection of matching scripts.
/// If the collection doesn't exist yet it is created and the
/// enumerator is moved to the first item in the collection.
///
///
///
/// A ScriptInfo for the next matching script or null if there are
/// no more matches.
///
///
private ScriptInfo GetNextBuiltinScript()
{
ScriptInfo result = null;
if ((_commandResolutionOptions & SearchResolutionOptions.CommandNameIsPattern) != 0)
{
if (_matchingScript == null)
{
// Generate the enumerator of matching script names
Collection matchingScripts =
new Collection();
WildcardPattern scriptMatcher =
WildcardPattern.Get(
_commandName,
WildcardOptions.IgnoreCase);
WildcardPattern scriptExtensionMatcher =
WildcardPattern.Get(
_commandName + StringLiterals.PowerShellScriptFileExtension,
WildcardOptions.IgnoreCase);
// Get the script cache enumerator. This acquires the cache lock,
// so be sure to dispose.
foreach (string scriptName in _context.CommandDiscovery.ScriptCache.Keys)
{
if (scriptMatcher.IsMatch(scriptName) ||
scriptExtensionMatcher.IsMatch(scriptName))
{
matchingScripts.Add(scriptName);
}
}
_matchingScript = matchingScripts.GetEnumerator();
}
if (!_matchingScript.MoveNext())
{
// Advance the state
_currentState = SearchState.StartSearchingForExternalCommands;
_matchingScript = null;
}
else
{
result = _context.CommandDiscovery.GetScriptInfo(_matchingScript.Current);
}
}
else
{
// Advance the state
_currentState = SearchState.StartSearchingForExternalCommands;
result = _context.CommandDiscovery.GetScriptInfo(_commandName) ??
_context.CommandDiscovery.GetScriptInfo(_commandName + StringLiterals.PowerShellScriptFileExtension);
}
if (result != null)
{
CommandDiscovery.discoveryTracer.WriteLine(
"Script found: {0}",
result.Name);
}
return result;
}
private IEnumerator _matchingScript;
private string DoPowerShellRelativePathLookup()
{
string result = null;
if (_context.EngineSessionState != null &&
_context.EngineSessionState.ProviderCount > 0)
{
// NTRAID#Windows OS Bugs-1009294-2004/02/04-JeffJon
// This is really slow. Maybe since we are only allowing FS paths right
// now we should use the file system APIs to verify the existence of the file.
// Since the path to the command was not found using the PATH variable,
// maybe it is relative to the current location. Try resolving the
// path.
// Relative Path: ".\command.exe"
// Home Path: "~\command.exe"
// Drive Relative Path: "\Users\User\AppData\Local\Temp\command.exe"
if (_commandName[0] == '.' || _commandName[0] == '~' || _commandName[0] == '\\')
{
using (CommandDiscovery.discoveryTracer.TraceScope(
"{0} appears to be a relative path. Trying to resolve relative path",
_commandName))
{
result = ResolvePSPath(_commandName);
}
}
}
return result;
} // DoPowerShellRelativePathLookup
///
/// Resolves the given path as an PSPath and ensures that it was resolved
/// by the FileSystemProvider
///
///
///
/// The path to resolve.
///
///
///
/// The path that was resolved. Null if the path couldn't be resolved or was
/// not resolved by the FileSystemProvider.
///
///
private string ResolvePSPath(string path)
{
string result = null;
try
{
ProviderInfo provider = null;
string resolvedPath = null;
if (WildcardPattern.ContainsWildcardCharacters(path))
{
// Let PowerShell resolve relative path with wildcards.
Provider.CmdletProvider providerInstance;
Collection resolvedPaths = _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(
path,
false,
out provider,
out providerInstance);
if (resolvedPaths.Count == 0)
{
resolvedPath = null;
CommandDiscovery.discoveryTracer.TraceError(
"The relative path with wildcard did not resolve to valid path. {0}",
path);
}
else if (resolvedPaths.Count > 1)
{
resolvedPath = null;
CommandDiscovery.discoveryTracer.TraceError(
"The relative path with wildcard resolved to mutiple paths. {0}",
path);
}
else
{
resolvedPath = resolvedPaths[0];
}
}
// Revert to previous path resolver if wildcards produces no results.
if ((resolvedPath == null) || (provider == null))
{
resolvedPath = _context.LocationGlobber.GetProviderPath(path, out provider);
}
// Verify the path was resolved to a file system path
if (provider.NameEquals(_context.ProviderNames.FileSystem))
{
result = resolvedPath;
CommandDiscovery.discoveryTracer.WriteLine(
"The relative path was resolved to: {0}",
result);
}
else
{
// The path was not to the file system
CommandDiscovery.discoveryTracer.TraceError(
"The relative path was not a file system path. {0}",
path);
}
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The home path was not specified for the provider. {0}",
path);
}
catch (ProviderInvocationException providerInvocationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"While resolving the path, \"{0}\", an error was encountered by the provider: {1}",
path,
providerInvocationException.Message);
}
catch (ItemNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path does not exist: {0}",
path);
}
catch (DriveNotFoundException driveNotFound)
{
CommandDiscovery.discoveryTracer.TraceError(
"The drive does not exist: {0}",
driveNotFound.ItemName);
}
return result;
} // ResolvePSPath
///
/// Creates a collection of patterns used to find the command
///
///
///
/// The name of the command to search for.
///
/// get names for command discovery
///
/// A collection of the patterns used to find the command.
/// The patterns are as follows:
/// 1. [commandName].cmdlet
/// 2. [commandName].ps1
/// 3..x
/// foreach (extension in PATHEXT)
/// [commandName].[extension]
/// x+1. [commandName]
///
///
///
/// If contains one or more of the
/// invalid characters defined in InvalidPathChars.
///
internal Collection ConstructSearchPatternsFromName(string name, bool commandDiscovery = false)
{
Dbg.Assert(
!String.IsNullOrEmpty(name),
"Caller should verify name");
Collection result = new Collection();
// First check to see if the commandName has an extension, if so
// look for that first
bool commandNameAddedFirst = false;
if (!String.IsNullOrEmpty(Path.GetExtension(name)))
{
result.Add(name);
commandNameAddedFirst = true;
}
// Add the extensions for script, module and data files in that order...
if ((_commandTypes & CommandTypes.ExternalScript) != 0)
{
result.Add(name + StringLiterals.PowerShellScriptFileExtension);
if (!commandDiscovery)
{
// psd1 and psm1 are not executable, so don't add them
result.Add(name + StringLiterals.PowerShellModuleFileExtension);
result.Add(name + StringLiterals.PowerShellDataFileExtension);
}
}
if ((_commandTypes & CommandTypes.Application) != 0)
{
// Now add each extension from the PATHEXT environment variable
foreach (string extension in CommandDiscovery.PathExtensions)
{
result.Add(name + extension);
}
}
// Now add the commandName by itself if it wasn't added as the first
// pattern
if (!commandNameAddedFirst)
{
result.Add(name);
}
return result;
} // ConstructSearchPatternsFromName
///
/// Determines if the given command name is a qualified PowerShell path.
///
///
///
/// The name of the command.
///
///
///
/// True if the command name is either a provider-qualified or PowerShell drive-qualified
/// path. False otherwise.
///
///
private static bool IsQualifiedPSPath(string commandName)
{
Dbg.Assert(
!String.IsNullOrEmpty(commandName),
"The caller should have verified the commandName");
bool result =
LocationGlobber.IsAbsolutePath(commandName) ||
LocationGlobber.IsProviderQualifiedPath(commandName) ||
LocationGlobber.IsHomePath(commandName) ||
LocationGlobber.IsProviderDirectPath(commandName);
return result;
} // IsQualifiedPSPath
private enum CanDoPathLookupResult
{
Yes,
PathIsRooted,
WildcardCharacters,
DirectorySeparator,
IllegalCharacters
}
///
/// Determines if the command name has any path special
/// characters which would require resolution. If so,
/// path lookup will not succeed.
///
///
///
/// The command name (or possible path) to look for the special characters.
///
///
///
/// True if the command name does not contain any special
/// characters. False otherwise.
///
///
private static CanDoPathLookupResult CanDoPathLookup(string possiblePath)
{
CanDoPathLookupResult result = CanDoPathLookupResult.Yes;
do // false loop
{
// If the command name contains any wildcard characters
// we can't do the path lookup
if (WildcardPattern.ContainsWildcardCharacters(possiblePath))
{
result = CanDoPathLookupResult.WildcardCharacters;
break;
}
try
{
if (Path.IsPathRooted(possiblePath))
{
result = CanDoPathLookupResult.PathIsRooted;
break;
}
}
catch (ArgumentException)
{
result = CanDoPathLookupResult.IllegalCharacters;
break;
}
// If the command contains any path separators, we can't
// do the path lookup
if (possiblePath.IndexOfAny(Utils.Separators.Directory) != -1)
{
result = CanDoPathLookupResult.DirectorySeparator;
break;
}
// If the command contains any invalid path characters, we can't
// do the path lookup
if (possiblePath.IndexOfAny(Path.GetInvalidPathChars()) != -1)
{
result = CanDoPathLookupResult.IllegalCharacters;
break;
}
} while (false);
return result;
} // CanDoPathLookup
///
/// The command name to search for
///
private string _commandName;
///
/// Determines which command types will be globbed.
///
private SearchResolutionOptions _commandResolutionOptions;
///
/// Determines which types of commands to look for.
///
private CommandTypes _commandTypes = CommandTypes.All;
///
/// The enumerator that uses the Path to
/// search for commands.
///
private CommandPathSearch _pathSearcher;
///
/// Thge execution context instance for the current engine...
///
private ExecutionContext _context;
///
/// A routine to initialize the path searcher...
///
///
///
/// If the commandName used to construct this object
/// contains one or more of the invalid characters defined
/// in InvalidPathChars.
///
///
private void setupPathSearcher()
{
// If it's already set up, just return...
if (_pathSearcher != null)
{
return;
}
// We are never going to look for non-executable commands in CommandSearcher.
// Even though file types like .DOC, .LOG,.TXT, etc. can be opened / invoked, users think of these as files, not applications.
// So I don't think we should show applications with the additional extensions at all.
// Applications should only include files whose extensions are in the PATHEXT list and these would only be returned with the All parameter.
if ((_commandResolutionOptions & SearchResolutionOptions.CommandNameIsPattern) != 0)
{
_canDoPathLookup = true;
_canDoPathLookupResult = CanDoPathLookupResult.Yes;
_pathSearcher =
new CommandPathSearch(
_commandName,
_context.CommandDiscovery.GetLookupDirectoryPaths(),
_context,
acceptableCommandNames: null);
}
else
{
_canDoPathLookupResult = CanDoPathLookup(_commandName);
if (_canDoPathLookupResult == CanDoPathLookupResult.Yes)
{
_canDoPathLookup = true;
_commandName = _commandName.TrimEnd(Utils.Separators.PathSearchTrimEnd);
_pathSearcher =
new CommandPathSearch(
_commandName,
_context.CommandDiscovery.GetLookupDirectoryPaths(),
_context,
ConstructSearchPatternsFromName(_commandName, commandDiscovery: true));
}
else if (_canDoPathLookupResult == CanDoPathLookupResult.PathIsRooted)
{
_canDoPathLookup = true;
string directory = Path.GetDirectoryName(_commandName);
var directoryCollection = new[] { directory };
CommandDiscovery.discoveryTracer.WriteLine(
"The path is rooted, so only doing the lookup in the specified directory: {0}",
directory);
string fileName = Path.GetFileName(_commandName);
if (!String.IsNullOrEmpty(fileName))
{
fileName = fileName.TrimEnd(Utils.Separators.PathSearchTrimEnd);
_pathSearcher =
new CommandPathSearch(
fileName,
directoryCollection,
_context,
ConstructSearchPatternsFromName(fileName, commandDiscovery: true));
}
else
{
_canDoPathLookup = false;
}
}
else if (_canDoPathLookupResult == CanDoPathLookupResult.DirectorySeparator)
{
_canDoPathLookup = true;
// We must try to resolve the path as an PSPath or else we can't do
// path lookup for relative paths.
string directory = Path.GetDirectoryName(_commandName);
directory = ResolvePSPath(directory);
CommandDiscovery.discoveryTracer.WriteLine(
"The path is relative, so only doing the lookup in the specified directory: {0}",
directory);
if (directory == null)
{
_canDoPathLookup = false;
}
else
{
var directoryCollection = new[] { directory };
string fileName = Path.GetFileName(_commandName);
if (!String.IsNullOrEmpty(fileName))
{
fileName = fileName.TrimEnd(Utils.Separators.PathSearchTrimEnd);
_pathSearcher =
new CommandPathSearch(
fileName,
directoryCollection,
_context,
ConstructSearchPatternsFromName(fileName, commandDiscovery: true));
}
else
{
_canDoPathLookup = false;
}
}
}
}
}
///
/// Resets the enumerator to before the first command match, public for IEnumerable
///
public void Reset()
{
// If this is a command coming from outside the runspace and there are no
// permitted scripts or applications,
// remove them from the set of things to search for...
if (_commandOrigin == CommandOrigin.Runspace)
{
if (_context.EngineSessionState.Applications.Count == 0)
_commandTypes &= ~CommandTypes.Application;
if (_context.EngineSessionState.Scripts.Count == 0)
_commandTypes &= ~CommandTypes.ExternalScript;
}
if (_pathSearcher != null)
{
_pathSearcher.Reset();
}
_currentMatch = null;
_currentState = SearchState.SearchingAliases;
_matchingAlias = null;
_matchingCmdlet = null;
_matchingScript = null;
} // Reset
internal CommandOrigin CommandOrigin
{
get { return _commandOrigin; }
set { _commandOrigin = value; }
}
private CommandOrigin _commandOrigin = CommandOrigin.Internal;
///
/// An enumerator of the matching aliases
///
private IEnumerator _matchingAlias;
///
/// An enumerator of the matching functions
///
private IEnumerator _matchingFunctionEnumerator;
///
/// The CommandInfo that references the command that matches the pattern.
///
private CommandInfo _currentMatch;
private bool _canDoPathLookup;
private CanDoPathLookupResult _canDoPathLookupResult = CanDoPathLookupResult.Yes;
///
/// The current state of the enumerator
///
private SearchState _currentState = SearchState.SearchingAliases;
private enum SearchState
{
// the searcher has been reset or has not been advanced since being created.
SearchingAliases,
// the searcher has finished alias resolution and is now searching for functions.
SearchingFunctions,
// the searcher has finished function resolution and is now searching for cmdlets
SearchingCmdlets,
// the search has finished cmdlet resolution and is now searching for builtin scripts
SearchingBuiltinScripts,
// the search has finished builtin script resolution and is now searching for external commands
StartSearchingForExternalCommands,
// the searcher has moved to
PowerShellPathResolution,
// the searcher has moved to a qualified file system path
QualifiedFileSystemPath,
// the searcher has moved to using a CommandPathSearch object
// for resolution
PathSearch,
// the searcher has moved to using a CommandPathSearch object
// with get prepended to the command name for resolution
GetPathSearch,
// the searcher has moved to resolving the command as a
// relative PowerShell path
PowerShellRelativePath,
// No more matches can be found
NoMoreMatches,
} // SearchState
#endregion private members
} // CommandSearcher
///
/// Determines which types of commands should be globbed using the specified
/// pattern. Any flag that is not specified will only match if exact.
///
[Flags]
internal enum SearchResolutionOptions
{
None = 0x0,
ResolveAliasPatterns = 0x01,
ResolveFunctionPatterns = 0x02,
CommandNameIsPattern = 0x04,
SearchAllScopes = 0x08,
}
}