private static bool GetModuleEntryFromCache(string modulePath, out DateTime lastWriteTime, out ModuleCacheEntry moduleCacheEntry) { try { lastWriteTime = new FileInfo(modulePath).LastWriteTime; } catch (Exception e) { ModuleIntrinsics.Tracer.WriteLine("Exception checking LastWriteTime on module {0}: {1}", modulePath, e.Message); lastWriteTime = DateTime.MinValue; } if (s_cacheData.Entries.TryGetValue(modulePath, out moduleCacheEntry)) { if (lastWriteTime == moduleCacheEntry.LastWriteTime) { return(true); } ModuleIntrinsics.Tracer.WriteLine("{0}: cache entry out of date, cached on {1}, last updated on {2}", modulePath, moduleCacheEntry.LastWriteTime, lastWriteTime); s_cacheData.Entries.TryRemove(modulePath, out moduleCacheEntry); } moduleCacheEntry = null; return(false); }
private static ConcurrentDictionary <string, CommandTypes> AnalyzeScriptModule(string modulePath, ExecutionContext context, DateTime lastWriteTime) { var scriptAnalysis = ScriptAnalysis.Analyze(modulePath, context); if (scriptAnalysis == null) { return(null); } List <WildcardPattern> scriptAnalysisPatterns = new List <WildcardPattern>(); foreach (string discoveredCommandFilter in scriptAnalysis.DiscoveredCommandFilters) { scriptAnalysisPatterns.Add(new WildcardPattern(discoveredCommandFilter)); } var result = new ConcurrentDictionary <string, CommandTypes>(3, scriptAnalysis.DiscoveredExports.Count + scriptAnalysis.DiscoveredAliases.Count, StringComparer.OrdinalIgnoreCase); // Add any directly discovered exports foreach (var command in scriptAnalysis.DiscoveredExports) { if (SessionStateUtilities.MatchesAnyWildcardPattern(command, scriptAnalysisPatterns, true)) { if (command.IndexOfAny(InvalidCommandNameCharacters) < 0) { result[command] = CommandTypes.Function; } } } // Add the discovered aliases foreach (var pair in scriptAnalysis.DiscoveredAliases) { var commandName = pair.Key; // These are already filtered if (commandName.IndexOfAny(InvalidCommandNameCharacters) < 0) { result.AddOrUpdate(commandName, CommandTypes.Alias, (_, existingCommandType) => existingCommandType | CommandTypes.Alias); } } // Add any files in PsScriptRoot if it added itself to the path if (scriptAnalysis.AddsSelfToPath) { string baseDirectory = Path.GetDirectoryName(modulePath); try { foreach (string item in Directory.GetFiles(baseDirectory, "*.ps1")) { var command = Path.GetFileNameWithoutExtension(item); result.AddOrUpdate(command, CommandTypes.ExternalScript, (_, existingCommandType) => existingCommandType | CommandTypes.ExternalScript); } } catch (UnauthorizedAccessException) { // Consume this exception here } } var exportedClasses = new ConcurrentDictionary <string, TypeAttributes>( /*concurrency*/ 1, scriptAnalysis.DiscoveredClasses.Count, StringComparer.OrdinalIgnoreCase); foreach (var exportedClass in scriptAnalysis.DiscoveredClasses) { exportedClasses[exportedClass.Name] = exportedClass.TypeAttributes; } var moduleCacheEntry = new ModuleCacheEntry { ModulePath = modulePath, LastWriteTime = lastWriteTime, Commands = result, TypesAnalyzed = true, Types = exportedClasses }; s_cacheData.Entries[modulePath] = moduleCacheEntry; return(result); }
internal static void CacheModuleExports(PSModuleInfo module, ExecutionContext context) { ModuleIntrinsics.Tracer.WriteLine("Requested caching for {0}", module.Name); DateTime lastWriteTime; ModuleCacheEntry moduleCacheEntry; GetModuleEntryFromCache(module.Path, out lastWriteTime, out moduleCacheEntry); var realExportedCommands = module.ExportedCommands; var realExportedClasses = module.GetExportedTypeDefinitions(); ConcurrentDictionary <string, CommandTypes> exportedCommands; ConcurrentDictionary <string, TypeAttributes> exportedClasses; // First see if the existing module info is sufficient. GetModuleEntryFromCache does LastWriteTime // verification, so this will also return nothing if the cache is out of date or corrupt. if (moduleCacheEntry != null) { bool needToUpdate = false; // We need to iterate and check as exportedCommands will have more item as it can have aliases as well. exportedCommands = moduleCacheEntry.Commands; foreach (var pair in realExportedCommands) { var commandName = pair.Key; var realCommandType = pair.Value.CommandType; CommandTypes commandType; if (!exportedCommands.TryGetValue(commandName, out commandType) || commandType != realCommandType) { needToUpdate = true; break; } } exportedClasses = moduleCacheEntry.Types; foreach (var pair in realExportedClasses) { var className = pair.Key; var realTypeAttributes = pair.Value.TypeAttributes; TypeAttributes typeAttributes; if (!exportedClasses.TryGetValue(className, out typeAttributes) || typeAttributes != realTypeAttributes) { needToUpdate = true; break; } } // Update or not, we've analyzed commands and types now. moduleCacheEntry.TypesAnalyzed = true; if (!needToUpdate) { ModuleIntrinsics.Tracer.WriteLine("Existing cached info up-to-date. Skipping."); return; } exportedCommands.Clear(); exportedClasses.Clear(); } else { exportedCommands = new ConcurrentDictionary <string, CommandTypes>(3, realExportedCommands.Count, StringComparer.OrdinalIgnoreCase); exportedClasses = new ConcurrentDictionary <string, TypeAttributes>(1, realExportedClasses.Count, StringComparer.OrdinalIgnoreCase); moduleCacheEntry = new ModuleCacheEntry { ModulePath = module.Path, LastWriteTime = lastWriteTime, Commands = exportedCommands, TypesAnalyzed = true, Types = exportedClasses }; moduleCacheEntry = s_cacheData.Entries.GetOrAdd(module.Path, moduleCacheEntry); } // We need to update the cache foreach (var exportedCommand in realExportedCommands.Values) { ModuleIntrinsics.Tracer.WriteLine("Caching command: {0}", exportedCommand.Name); exportedCommands.GetOrAdd(exportedCommand.Name, exportedCommand.CommandType); } foreach (var pair in realExportedClasses) { var className = pair.Key; ModuleIntrinsics.Tracer.WriteLine("Caching command: {0}", className); moduleCacheEntry.Types.AddOrUpdate(className, pair.Value.TypeAttributes, (k, t) => t); } s_cacheData.QueueSerialization(); }
private static ConcurrentDictionary <string, CommandTypes> AnalyzeManifestModule(string modulePath, ExecutionContext context, DateTime lastWriteTime, bool etwEnabled) { ConcurrentDictionary <string, CommandTypes> result = null; try { var moduleManifestProperties = PsUtils.GetModuleManifestProperties(modulePath, PsUtils.FastModuleManifestAnalysisPropertyNames); if (moduleManifestProperties != null) { Version version; if (ModuleUtils.IsModuleInVersionSubdirectory(modulePath, out version)) { var versionInManifest = LanguagePrimitives.ConvertTo <Version>(moduleManifestProperties["ModuleVersion"]); if (version != versionInManifest) { ModuleIntrinsics.Tracer.WriteLine("ModuleVersion in manifest does not match versioned module directory, skipping module: {0}", modulePath); return(null); } } result = new ConcurrentDictionary <string, CommandTypes>(3, moduleManifestProperties.Count, StringComparer.OrdinalIgnoreCase); var sawWildcard = false; var hadCmdlets = AddPsd1EntryToResult(result, moduleManifestProperties["CmdletsToExport"], CommandTypes.Cmdlet, ref sawWildcard); var hadFunctions = AddPsd1EntryToResult(result, moduleManifestProperties["FunctionsToExport"], CommandTypes.Function, ref sawWildcard); var hadAliases = AddPsd1EntryToResult(result, moduleManifestProperties["AliasesToExport"], CommandTypes.Alias, ref sawWildcard); var analysisSucceeded = hadCmdlets && hadFunctions && hadAliases; if (!analysisSucceeded && !sawWildcard && (hadCmdlets || hadFunctions)) { // If we're missing CmdletsToExport, that might still be OK, but only if we have a script module. // Likewise, if we're missing FunctionsToExport, that might be OK, but only if we have a binary module. analysisSucceeded = !CheckModulesTypesInManifestAgainstExportedCommands(moduleManifestProperties, hadCmdlets, hadFunctions, hadAliases); } if (analysisSucceeded) { var moduleCacheEntry = new ModuleCacheEntry { ModulePath = modulePath, LastWriteTime = lastWriteTime, Commands = result, TypesAnalyzed = false, Types = new ConcurrentDictionary <string, TypeAttributes>(1, 8, StringComparer.OrdinalIgnoreCase) }; s_cacheData.Entries[modulePath] = moduleCacheEntry; } else { result = null; } } } catch (Exception e) { if (etwEnabled) { CommandDiscoveryEventSource.Log.ModuleManifestAnalysisException(modulePath, e.Message); } // Ignore the errors, proceed with the usual module analysis ModuleIntrinsics.Tracer.WriteLine("Exception on fast-path analysis of module {0}", modulePath); } if (etwEnabled) { CommandDiscoveryEventSource.Log.ModuleManifestAnalysisResult(modulePath, result != null); } return(result ?? AnalyzeTheOldWay(modulePath, context, lastWriteTime)); }