/********************************************************************++ 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, } }