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 analysisSuceeded = hadCmdlets && hadFunctions && hadAliases; if (!analysisSuceeded && !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. analysisSuceeded = !CheckModulesTypesInManifestAgainstExportedCommands(moduleManifestProperties, hadCmdlets, hadFunctions, hadAliases); } if (analysisSuceeded) { 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); }
public static AnalysisCacheData Deserialize(string filename) { using (var stream = File.OpenRead(filename)) { var result = new AnalysisCacheData { LastReadTime = DateTime.Now }; var bytes = new byte[1024]; // Header // "PSMODULECACHE" -> 13 bytes // byte ( 1 byte) -> version ReadHeader(stream, bytes); // int ( 4 bytes) -> count of entries int entries = ReadInt(stream, bytes); if (entries > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); result.Entries = new ConcurrentDictionary<string, ModuleCacheEntry>(/*concurrency*/3, entries, StringComparer.OrdinalIgnoreCase); // entries (?? bytes) -> all entries while (entries > 0) { // DateTime ( 8 bytes) -> last write time for module file var lastWriteTime = new DateTime(ReadLong(stream, bytes)); // int ( 4 bytes) -> path length // string (?? bytes) -> utf8 encoded path var path = ReadString(stream, ref bytes); // int ( 4 bytes) -> count of commands var countItems = ReadInt(stream, bytes); if (countItems > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); var commands = new ConcurrentDictionary<string, CommandTypes>(/*concurrency*/3, countItems, StringComparer.OrdinalIgnoreCase); // commands (?? bytes) -> all commands while (countItems > 0) { // int ( 4 bytes) -> command name length // string (?? bytes) -> utf8 encoded command name var commandName = ReadString(stream, ref bytes); // int ( 4 bytes) -> CommandTypes enum var commandTypes = (CommandTypes)ReadInt(stream, bytes); // Ignore empty entries (possible corruption in the cache or bug?) if (!string.IsNullOrWhiteSpace(commandName)) commands[commandName] = commandTypes; countItems -= 1; } // int ( 4 bytes) -> count of types countItems = ReadInt(stream, bytes); bool typesAnalyzed = countItems != -1; if (!typesAnalyzed) countItems = 0; if (countItems > 20 * 1024) throw new Exception(PossibleCorruptionErrorMessage); var types = new ConcurrentDictionary<string, TypeAttributes>(1, countItems, StringComparer.OrdinalIgnoreCase); // types (?? bytes) -> all types while (countItems > 0) { // int ( 4 bytes) -> type name length // string (?? bytes) -> utf8 encoded type name var typeName = ReadString(stream, ref bytes); // int ( 4 bytes) -> type attributes var typeAttributes = (TypeAttributes)ReadInt(stream, bytes); // Ignore empty entries (possible corruption in the cache or bug?) if (!string.IsNullOrWhiteSpace(typeName)) types[typeName] = typeAttributes; countItems -= 1; } var entry = new ModuleCacheEntry { ModulePath = path, LastWriteTime = lastWriteTime, Commands = commands, TypesAnalyzed = typesAnalyzed, Types = types }; result.Entries[path] = entry; entries -= 1; } if (Environment.GetEnvironmentVariable("PSDisableModuleAnalysisCacheCleanup") == null) { Task.Delay(10000).ContinueWith(_ => result.Cleanup()); } 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 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; }