예제 #1
0
        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);
        }
예제 #2
0
        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;
            }
        }
예제 #3
0
        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();
        }
예제 #4
0
        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;
        }
예제 #5
0
        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;
        }