private static JsonCaseInsensitiveStringDictionary <TValue> StringDictionaryUnion <TValue>( JsonCaseInsensitiveStringDictionary <TValue> thisStringDict, JsonCaseInsensitiveStringDictionary <TValue> thatStringDict, Func <TValue, TValue, object> valueUnionizer = null) where TValue : ICloneable { if (thatStringDict == null) { return(thisStringDict); } if (thisStringDict == null) { return((JsonCaseInsensitiveStringDictionary <TValue>)thatStringDict.Clone()); } foreach (KeyValuePair <string, TValue> item in thatStringDict) { if (!thisStringDict.ContainsKey(item.Key)) { thisStringDict.Add(item.Key, item.Value); continue; } if (valueUnionizer != null) { thisStringDict[item.Key] = (TValue)valueUnionizer(thisStringDict[item.Key], item.Value); } } return(thisStringDict); }
/// <summary> /// Collate an AssemblyData object for a single assembly. /// </summary> /// <param name="asm">the assembly to collect data on.</param> /// <returns>A pair of the name and data of the given assembly.</returns> public static KeyValuePair <string, AssemblyData> AssembleAssembly(Assembly asm) { AssemblyName asmName = asm.GetName(); var asmNameData = new AssemblyNameData() { Name = asmName.Name, Version = asmName.Version, Culture = string.IsNullOrEmpty(asmName.CultureName) ? null : asmName.CultureName, PublicKeyToken = asmName.GetPublicKeyToken(), }; Type[] types = asm.GetTypes(); JsonCaseInsensitiveStringDictionary <JsonCaseInsensitiveStringDictionary <TypeData> > namespacedTypes = null; if (types.Any()) { namespacedTypes = new JsonCaseInsensitiveStringDictionary <JsonCaseInsensitiveStringDictionary <TypeData> >(); foreach (Type type in asm.GetTypes()) { if (!type.IsPublic) { continue; } // Some types don't have a namespace, but we still want to file them string typeNamespace = type.Namespace ?? ""; if (!namespacedTypes.ContainsKey(typeNamespace)) { namespacedTypes.Add(typeNamespace, new JsonCaseInsensitiveStringDictionary <TypeData>()); } TypeData typeData = AssembleType(type); namespacedTypes[typeNamespace][type.Name] = typeData; } } var asmData = new AssemblyData() { AssemblyName = asmNameData, Types = namespacedTypes }; return(new KeyValuePair <string, AssemblyData>(asmName.Name, asmData)); }
/// <summary> /// Collate type data from assemblies into an AvailableTypeData object. /// </summary> /// <param name="assemblies">the assemblies to collate data from.</param> /// <param name="typeAccelerators">lookup table of PowerShell type accelerators.</param> /// <returns>an object describing all the available types from the given assemblies.</returns> public static AvailableTypeData AssembleAvailableTypes( IEnumerable <Assembly> assemblies, IDictionary <string, Type> typeAccelerators, out IEnumerable <CompatibilityAnalysisException> errors) { var errAcc = new List <CompatibilityAnalysisException>(); var typeAcceleratorDict = new JsonCaseInsensitiveStringDictionary <TypeAcceleratorData>(typeAccelerators.Count); foreach (KeyValuePair <string, Type> typeAccelerator in typeAccelerators) { var ta = new TypeAcceleratorData() { Assembly = typeAccelerator.Value.Assembly.GetName().Name, Type = typeAccelerator.Value.FullName }; typeAcceleratorDict.Add(typeAccelerator.Key, ta); } var asms = new JsonCaseInsensitiveStringDictionary <AssemblyData>(); foreach (Assembly asm in assemblies) { // Don't want to include this module or assembly in the output if (Assembly.GetCallingAssembly() == asm) { continue; } try { KeyValuePair <string, AssemblyData> asmData = AssembleAssembly(asm); asms.Add(asmData.Key, asmData.Value); } catch (ReflectionTypeLoadException e) { errAcc.Add(new CompatibilityAnalysisException($"Failed to load assembly '{asm.GetName().FullName}'", e)); } } errors = errAcc; return(new AvailableTypeData() { TypeAccelerators = typeAcceleratorDict, Assemblies = asms }); }
/// <summary> /// Assembly module data objects into a lookup table. /// </summary> /// <param name="modules">An enumeration of module data objects to assemble</param> /// <returns>A case-insensitive dictionary of versioned module data objects.</returns> public JsonCaseInsensitiveStringDictionary <JsonDictionary <Version, ModuleData> > AssembleModulesData( IEnumerable <Tuple <string, Version, ModuleData> > modules) { var moduleDict = new JsonCaseInsensitiveStringDictionary <JsonDictionary <Version, ModuleData> >(); foreach (Tuple <string, Version, ModuleData> module in modules) { if (moduleDict.TryGetValue(module.Item1, out JsonDictionary <Version, ModuleData> versionDict)) { versionDict.Add(module.Item2, module.Item3); continue; } var newVersionDict = new JsonDictionary <Version, ModuleData>(); newVersionDict.Add(module.Item2, module.Item3); moduleDict.Add(module.Item1, newVersionDict); } return(moduleDict); }
/// <summary> /// Collate type data from assemblies into an AvailableTypeData object. /// </summary> /// <param name="assemblies">the assemblies to collate data from.</param> /// <param name="typeAccelerators">lookup table of PowerShell type accelerators.</param> /// <returns>an object describing all the available types from the given assemblies.</returns> public AvailableTypeData AssembleAvailableTypes( IEnumerable <Assembly> assemblies, IReadOnlyDictionary <string, Type> typeAccelerators, out IEnumerable <CompatibilityAnalysisException> errors) { var errAcc = new List <CompatibilityAnalysisException>(); var typeAcceleratorDict = new JsonCaseInsensitiveStringDictionary <TypeAcceleratorData>(typeAccelerators.Count); foreach (KeyValuePair <string, Type> typeAccelerator in typeAccelerators) { var ta = new TypeAcceleratorData() { Assembly = typeAccelerator.Value.Assembly.GetName().Name, Type = typeAccelerator.Value.FullName }; typeAcceleratorDict.Add(typeAccelerator.Key, ta); } var asms = new JsonDictionary <string, AssemblyData>(); foreach (Assembly asm in assemblies) { // Skip over this if (asm == s_executingAssembly || asm.IsDynamic || string.IsNullOrEmpty(asm.Location)) { continue; } if (_excludedAssemblyPathPrefixes != null && IsAssemblyPathExcluded(asm.Location)) { continue; } try { // First check whether an assembly with this name already exists // Only replace it if the current one is newer AssemblyName asmName = asm.GetName(); if (asms.TryGetValue(asmName.Name, out AssemblyData currentAssemblyData) && asmName.Version < currentAssemblyData.AssemblyName.Version) { continue; } KeyValuePair <string, AssemblyData> asmData = AssembleAssembly(asm); try { asms.Add(asmData.Key, asmData.Value); } catch (ArgumentException e) { // We don't have a way in the schema for two assemblies with the same name, so we just keep the first // This is not really valid and we should update the schema to subkey the version errAcc.Add(new CompatibilityAnalysisException($"Found duplicate assemblies with name {asmData.Key}. Kept the first one.", e)); } } catch (Exception e) when(e is ReflectionTypeLoadException || e is FileNotFoundException) { errAcc.Add(new CompatibilityAnalysisException($"Failed to load assembly '{asm.GetName().FullName}'", e)); } } errors = errAcc; return(new AvailableTypeData() { TypeAccelerators = typeAcceleratorDict, Assemblies = asms }); }
/// <summary> /// Get a module data object for the Microsoft.PowerShell.Core pseudo-module. /// The version is given as the PowerShell version. /// </summary> /// <returns>The name, version and data of the core pseudo-module.</returns> public Tuple <string, Version, ModuleData> GetCoreModuleData() { var moduleData = new ModuleData(); IEnumerable <CommandInfo> coreCommands = _pwsh.AddCommand(GcmInfo) .AddParameter("Module", CORE_MODULE_NAME) .InvokeAndClear <CommandInfo>(); var cmdletData = new JsonCaseInsensitiveStringDictionary <CmdletData>(); var functionData = new JsonCaseInsensitiveStringDictionary <FunctionData>(); foreach (CommandInfo command in coreCommands) { switch (command) { case CmdletInfo cmdlet: try { cmdletData.Add(cmdlet.Name, GetSingleCmdletData(cmdlet)); } catch (RuntimeException) { // If we can't load the cmdlet, we just move on } continue; case FunctionInfo function: try { functionData.Add(function.Name, GetSingleFunctionData(function)); } catch (RuntimeException) { // Some functions have problems loading, // which likely means PowerShell wouldn't be able to run them } continue; default: throw new CompatibilityAnalysisException($"Command {command.Name} in core module is of unsupported type {command.CommandType}"); } } moduleData.Cmdlets = cmdletData; moduleData.Functions = functionData; // Get default variables and core aliases out of a fresh runspace using (SMA.PowerShell freshPwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace)) { Collection <PSObject> varsAndAliases = freshPwsh.AddCommand("Get-ChildItem") .AddParameter("Path", "variable:,alias:") .InvokeAndClear(); var variables = new List <string>(); var aliases = new JsonCaseInsensitiveStringDictionary <string>(); foreach (PSObject returnedObject in varsAndAliases) { switch (returnedObject.BaseObject) { case PSVariable variable: variables.Add(variable.Name); continue; case AliasInfo alias: aliases.Add(alias.Name, GetSingleAliasData(alias)); continue; // Skip over other objects we get back, since there's no reason to throw } } moduleData.Variables = variables.ToArray(); moduleData.Aliases = aliases; } Version psVersion = _psVersion.PreReleaseLabel != null ? new Version(_psVersion.Major, _psVersion.Minor, _psVersion.Build) : (Version)_psVersion; return(new Tuple <string, Version, ModuleData>(CORE_MODULE_NAME, psVersion, moduleData)); }
/// <summary> /// Get a module data object for the Microsoft.PowerShell.Core pseudo-module. /// The version is given as the PowerShell version. /// </summary> /// <returns>The name, version and data of the core pseudo-module.</returns> public Tuple <string, Version, ModuleData> GetCoreModuleData() { var moduleData = new ModuleData(); IEnumerable <CommandInfo> coreCommands = _pwsh.AddCommand(GcmInfo) .AddParameter("Type", CommandTypes.Alias | CommandTypes.Cmdlet | CommandTypes.Function) .InvokeAndClear <CommandInfo>() .Where(commandInfo => string.IsNullOrEmpty(commandInfo.ModuleName) || CORE_MODULE_NAME.Equals(commandInfo.ModuleName, StringComparison.OrdinalIgnoreCase)); var cmdletData = new JsonCaseInsensitiveStringDictionary <CmdletData>(); var functionData = new JsonCaseInsensitiveStringDictionary <FunctionData>(); var aliases = new JsonCaseInsensitiveStringDictionary <string>(); var aliasesToRequest = new List <string>(); foreach (CommandInfo command in coreCommands) { switch (command) { case CmdletInfo cmdlet: try { cmdletData.Add(cmdlet.Name, GetSingleCmdletData(cmdlet)); } catch (RuntimeException) { // If we can't load the cmdlet, we just move on } continue; case FunctionInfo function: try { functionData.Add(function.Name, GetSingleFunctionData(function)); } catch (RuntimeException) { // Some functions have problems loading, // which likely means PowerShell wouldn't be able to run them } continue; case AliasInfo alias: try { // Some aliases won't resolve unless specified specifically if (alias.Definition == null) { aliasesToRequest.Add(alias.Name); continue; } aliases.Add(alias.Name, alias.Definition); } catch (RuntimeException) { // Ignore aliases that have trouble loading } continue; default: throw new CompatibilityAnalysisException($"Command {command.Name} in core module is of unsupported type {command.CommandType}"); } } moduleData.Cmdlets = cmdletData; moduleData.Functions = functionData; if (aliasesToRequest != null && aliasesToRequest.Count > 0) { IEnumerable <AliasInfo> resolvedAliases = _pwsh.AddCommand(GcmInfo) .AddParameter("Name", aliasesToRequest) .InvokeAndClear <AliasInfo>(); foreach (AliasInfo resolvedAlias in resolvedAliases) { if (resolvedAlias?.Definition == null) { continue; } aliases[resolvedAlias.Name] = resolvedAlias.Definition; } } // Get default variables and core aliases out of a fresh runspace using (SMA.PowerShell freshPwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace)) { Collection <PSObject> varsAndAliases = freshPwsh.AddCommand("Get-ChildItem") .AddParameter("Path", "variable:") .InvokeAndClear(); var variables = new List <string>(); foreach (PSObject returnedObject in varsAndAliases) { switch (returnedObject.BaseObject) { case PSVariable variable: variables.Add(variable.Name); continue; // Skip over other objects we get back, since there's no reason to throw } } moduleData.Variables = variables.ToArray(); moduleData.Aliases = aliases; } Version psVersion = _psVersion.PreReleaseLabel != null ? new Version(_psVersion.Major, _psVersion.Minor, _psVersion.Build) : (Version)_psVersion; return(new Tuple <string, Version, ModuleData>(CORE_MODULE_NAME, psVersion, moduleData)); }