2018-02-13 09:23:53 -08:00
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
2016-03-30 14:20:52 -07:00
2018-06-26 12:37:43 -07:00
using System.Collections ;
2016-03-30 14:20:52 -07:00
using System.Collections.Generic ;
using System.IO ;
2017-12-21 14:27:49 -08:00
using System.Management.Automation.Configuration ;
2016-03-30 14:20:52 -07:00
using System.Management.Automation.Internal ;
using System.Management.Automation.Language ;
using Microsoft.PowerShell.Commands ;
using System.Linq ;
2016-08-10 10:37:42 -07:00
using System.Text ;
2016-03-30 14:20:52 -07:00
using System.Threading ;
using Dbg = System . Management . Automation . Diagnostics ;
namespace System.Management.Automation
{
2017-03-15 12:04:28 -07:00
internal static class Constants
{
public const string PSModulePathEnvVar = "PSModulePath" ;
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Encapsulates the basic module operations for a PowerShell engine instance...
/// </summary>
public class ModuleIntrinsics
{
/// <summary>
/// Tracer for module analysis
/// </summary>
[TraceSource("Modules", "Module loading and analysis")]
internal static PSTraceSource Tracer = PSTraceSource . GetTracer ( "Modules" , "Module loading and analysis" );
2018-07-16 16:58:40 -07:00
// The %WINDIR%\System32\WindowsPowerShell\v1.0\Modules module path,
// to load forward compatible Windows PowerShell modules from
private static readonly string s_windowsPowerShellPSHomeModulePath =
Path . Combine ( System . Environment . SystemDirectory , "WindowsPowerShell" , "v1.0" , "Modules" );
2016-03-30 14:20:52 -07:00
internal ModuleIntrinsics ( ExecutionContext context )
{
_context = context ;
// And initialize the module path...
SetModulePath ();
}
private readonly ExecutionContext _context ;
// Holds the module collection...
2016-08-02 13:29:09 -07:00
internal Dictionary < string , PSModuleInfo > ModuleTable { get ; } = new Dictionary < string , PSModuleInfo >( StringComparer . OrdinalIgnoreCase );
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
private const int MaxModuleNestingDepth = 10 ;
2016-03-30 14:20:52 -07:00
2018-10-30 09:55:39 -07:00
/// <summary>
/// Gets and sets boolean that indicates when an implicit remoting module is loaded.
/// </summary>
internal bool IsImplicitRemotingModuleLoaded
{
get ;
set ;
}
2016-03-30 14:20:52 -07:00
internal void IncrementModuleNestingDepth ( PSCmdlet cmdlet , string path )
{
2016-08-02 13:29:09 -07:00
if (++ ModuleNestingDepth > MaxModuleNestingDepth )
2016-03-30 14:20:52 -07:00
{
string message = StringUtil . Format ( Modules . ModuleTooDeeplyNested , path , MaxModuleNestingDepth );
InvalidOperationException ioe = new InvalidOperationException ( message );
ErrorRecord er = new ErrorRecord ( ioe , "Modules_ModuleTooDeeplyNested" ,
ErrorCategory . InvalidOperation , path );
// NOTE: this call will throw
cmdlet . ThrowTerminatingError ( er );
}
}
internal void DecrementModuleNestingCount ()
{
2016-08-02 13:29:09 -07:00
-- ModuleNestingDepth ;
2016-03-30 14:20:52 -07:00
}
2016-08-02 13:29:09 -07:00
internal int ModuleNestingDepth { get ; private set ; }
2016-03-30 14:20:52 -07:00
/// <summary>
/// Create a new module object from a scriptblock specifying the path to set for the module
/// </summary>
/// <param name="name">The name of the module</param>
/// <param name="path">The path where the module is rooted</param>
/// <param name="scriptBlock">
/// ScriptBlock that is executed to initialize the module...
/// </param>
/// <param name="arguments">
/// The arguments to pass to the scriptblock used to initialize the module
/// </param>
/// <param name="ss">The session state instance to use for this module - may be null</param>
/// <param name="results">The results produced from evaluating the scriptblock</param>
/// <returns>The newly created module info object</returns>
internal PSModuleInfo CreateModule ( string name , string path , ScriptBlock scriptBlock , SessionState ss , out List < object > results , params object [] arguments )
{
return CreateModuleImplementation ( name , path , scriptBlock , null , ss , null , out results , arguments );
}
/// <summary>
/// Create a new module object from a ScriptInfo object
/// </summary>
/// <param name="path">The path where the module is rooted</param>
/// <param name="scriptInfo">The script info to use to create the module</param>
/// <param name="scriptPosition">The position for the command that loaded this module</param>
/// <param name="arguments">Optional arguments to pass to the script while executing</param>
/// <param name="ss">The session state instance to use for this module - may be null</param>
/// <param name="privateData">The private data to use for this module - may be null</param>
/// <returns>The constructed module object</returns>
internal PSModuleInfo CreateModule ( string path , ExternalScriptInfo scriptInfo , IScriptExtent scriptPosition , SessionState ss , object privateData , params object [] arguments )
{
List < object > result ;
return CreateModuleImplementation ( ModuleIntrinsics . GetModuleName ( path ), path , scriptInfo , scriptPosition , ss , privateData , out result , arguments );
}
/// <summary>
/// Create a new module object from code specifying the path to set for the module
/// </summary>
/// <param name="name">The name of the module</param>
/// <param name="path">The path to use for the module root</param>
/// <param name="moduleCode">
2017-01-16 13:31:14 -08:00
/// The code to use to create the module. This can be one of ScriptBlock, string
2016-03-30 14:20:52 -07:00
/// or ExternalScriptInfo
/// </param>
/// <param name="arguments">
/// Arguments to pass to the module scriptblock during evaluation.
/// </param>
/// <param name="result">
/// The results of the evaluation of the scriptblock.
/// </param>
/// <param name="scriptPosition">
/// The position of the caller of this function so you can tell where the call
/// to Import-Module (or whatever) occurred. This can be null.
/// </param>
/// <param name="ss">The session state instance to use for this module - may be null</param>
/// <param name="privateData">The private data to use for this module - may be null</param>
/// <returns>The created module</returns>
private PSModuleInfo CreateModuleImplementation ( string name , string path , object moduleCode , IScriptExtent scriptPosition , SessionState ss , object privateData , out List < object > result , params object [] arguments )
{
ScriptBlock sb ;
// By default the top-level scope in a session state object is the global scope for the instance.
// For modules, we need to set its global scope to be another scope object and, chain the top
// level scope for this sessionstate instance to be the parent. The top level scope for this ss is the
// script scope for the ss.
// Allocate the session state instance for this module.
if ( ss == null )
{
ss = new SessionState ( _context , true , true );
}
// Now set up the module's session state to be the current session state
SessionStateInternal oldSessionState = _context . EngineSessionState ;
PSModuleInfo module = new PSModuleInfo ( name , path , _context , ss );
ss . Internal . Module = module ;
module . PrivateData = privateData ;
bool setExitCode = false ;
int exitCode = 0 ;
try
{
_context . EngineSessionState = ss . Internal ;
// Build the scriptblock at this point so the references to the module
// context are correct...
ExternalScriptInfo scriptInfo = moduleCode as ExternalScriptInfo ;
if ( scriptInfo != null )
{
sb = scriptInfo . ScriptBlock ;
_context . Debugger . RegisterScriptFile ( scriptInfo );
}
else
{
sb = moduleCode as ScriptBlock ;
if ( sb != null )
{
PSLanguageMode ? moduleLanguageMode = sb . LanguageMode ;
sb = sb . Clone ();
sb . LanguageMode = moduleLanguageMode ;
sb . SessionState = ss ;
}
else
{
var sbText = moduleCode as string ;
if ( sbText != null )
sb = ScriptBlock . Create ( _context , sbText );
}
}
if ( sb == null )
throw PSTraceSource . NewInvalidOperationException ();
sb . SessionStateInternal = ss . Internal ;
2018-11-13 16:16:29 -08:00
module . LanguageMode = sb . LanguageMode ;
2016-03-30 14:20:52 -07:00
InvocationInfo invocationInfo = new InvocationInfo ( scriptInfo , scriptPosition );
2017-01-16 13:31:14 -08:00
// Save the module string
2016-03-30 14:20:52 -07:00
module . _definitionExtent = sb . Ast . Extent ;
var ast = sb . Ast ;
while ( ast . Parent != null )
{
ast = ast . Parent ;
}
2016-07-29 13:02:49 -07:00
2016-08-26 16:46:03 -04:00
// The variables set in the interpreted case get set by InvokeWithPipe in the compiled case.
2016-03-30 14:20:52 -07:00
Diagnostics . Assert ( _context . SessionState . Internal . CurrentScope . LocalsTuple == null ,
"No locals tuple should have been created yet." );
List < object > resultList = new List < object >();
try
{
Pipe outputPipe = new Pipe ( resultList );
// And run the scriptblock...
sb . InvokeWithPipe (
2016-07-29 13:02:49 -07:00
useLocalScope : false ,
2016-03-30 14:20:52 -07:00
errorHandlingBehavior : ScriptBlock . ErrorHandlingBehavior . WriteToCurrentErrorPipe ,
2016-07-29 13:02:49 -07:00
dollarUnder : AutomationNull . Value ,
input : AutomationNull . Value ,
scriptThis : AutomationNull . Value ,
outputPipe : outputPipe ,
invocationInfo : invocationInfo ,
args : arguments ?? Utils . EmptyArray < object >());
2016-03-30 14:20:52 -07:00
}
catch ( ExitException ee )
{
exitCode = ( int ) ee . Argument ;
setExitCode = true ;
}
result = resultList ;
}
finally
{
_context . EngineSessionState = oldSessionState ;
}
if ( setExitCode )
{
_context . SetVariable ( SpecialVariables . LastExitCodeVarPath , exitCode );
}
module . ImplementingAssembly = sb . AssemblyDefiningPSTypes ;
// We force re-population of ExportedTypeDefinitions, now with the actual RuntimeTypes, created above.
module . CreateExportedTypeDefinitions ( sb . Ast as ScriptBlockAst );
return module ;
}
/// <summary>
/// Allocate a new dynamic module then return a new scriptblock
/// bound to the module instance.
/// </summary>
/// <param name="context">Context to use to create bounded script.</param>
/// <param name="sb">The scriptblock to bind</param>
/// <param name="linkToGlobal">Whether it should be linked to the global session state or not</param>
/// <returns>A new scriptblock</returns>
internal ScriptBlock CreateBoundScriptBlock ( ExecutionContext context , ScriptBlock sb , bool linkToGlobal )
{
PSModuleInfo module = new PSModuleInfo ( context , linkToGlobal );
return module . NewBoundScriptBlock ( sb , context );
}
internal List < PSModuleInfo > GetModules ( string [] patterns , bool all )
{
return GetModuleCore ( patterns , all , false );
}
internal List < PSModuleInfo > GetExactMatchModules ( string moduleName , bool all , bool exactMatch )
{
if ( moduleName == null ) { moduleName = String . Empty ; }
2016-07-29 13:02:49 -07:00
return GetModuleCore ( new string [] { moduleName }, all , exactMatch );
2016-03-30 14:20:52 -07:00
}
private List < PSModuleInfo > GetModuleCore ( string [] patterns , bool all , bool exactMatch )
{
string targetModuleName = null ;
List < WildcardPattern > wcpList = new List < WildcardPattern >();
if ( exactMatch )
{
Dbg . Assert ( patterns . Length == 1 , "The 'patterns' should only contain one element when it is for an exact match" );
targetModuleName = patterns [ 0 ];
}
else
{
if ( patterns == null )
{
patterns = new string [] { "*" };
}
foreach ( string pattern in patterns )
{
wcpList . Add ( WildcardPattern . Get ( pattern , WildcardOptions . IgnoreCase ));
}
}
List < PSModuleInfo > modulesMatched = new List < PSModuleInfo >();
if ( all )
{
foreach ( PSModuleInfo module in ModuleTable . Values )
{
// See if this is the requested module...
2016-07-29 13:02:49 -07:00
if (( exactMatch && module . Name . Equals ( targetModuleName , StringComparison . OrdinalIgnoreCase )) ||
2016-03-30 14:20:52 -07:00
(! exactMatch && SessionStateUtilities . MatchesAnyWildcardPattern ( module . Name , wcpList , false )))
{
modulesMatched . Add ( module );
}
}
}
else
{
// Create a joint list of local and global modules. Only report a module once.
// Local modules are reported before global modules...
Dictionary < string , bool > found = new Dictionary < string , bool >( StringComparer . OrdinalIgnoreCase );
foreach ( var pair in _context . EngineSessionState . ModuleTable )
{
string path = pair . Key ;
PSModuleInfo module = pair . Value ;
// See if this is the requested module...
if (( exactMatch && module . Name . Equals ( targetModuleName , StringComparison . OrdinalIgnoreCase )) ||
(! exactMatch && SessionStateUtilities . MatchesAnyWildcardPattern ( module . Name , wcpList , false )))
{
modulesMatched . Add ( module );
found [ path ] = true ;
}
}
if ( _context . EngineSessionState != _context . TopLevelSessionState )
{
foreach ( var pair in _context . TopLevelSessionState . ModuleTable )
{
string path = pair . Key ;
if (! found . ContainsKey ( path ))
{
PSModuleInfo module = pair . Value ;
// See if this is the requested module...
if (( exactMatch && module . Name . Equals ( targetModuleName , StringComparison . OrdinalIgnoreCase )) ||
(! exactMatch && SessionStateUtilities . MatchesAnyWildcardPattern ( module . Name , wcpList , false )))
{
modulesMatched . Add ( module );
}
}
}
}
}
return modulesMatched . OrderBy ( m => m . Name ). ToList ();
}
internal List < PSModuleInfo > GetModules ( ModuleSpecification [] fullyQualifiedName , bool all )
{
List < PSModuleInfo > modulesMatched = new List < PSModuleInfo >();
if ( all )
{
foreach ( var moduleSpec in fullyQualifiedName )
{
foreach ( PSModuleInfo module in ModuleTable . Values )
{
// See if this is the requested module...
if ( IsModuleMatchingModuleSpec ( module , moduleSpec ))
{
modulesMatched . Add ( module );
}
}
}
}
else
{
foreach ( var moduleSpec in fullyQualifiedName )
{
// Create a joint list of local and global modules. Only report a module once.
// Local modules are reported before global modules...
Dictionary < string , bool > found = new Dictionary < string , bool >( StringComparer . OrdinalIgnoreCase );
foreach ( var pair in _context . EngineSessionState . ModuleTable )
{
string path = pair . Key ;
PSModuleInfo module = pair . Value ;
// See if this is the requested module...
if ( IsModuleMatchingModuleSpec ( module , moduleSpec ))
{
modulesMatched . Add ( module );
found [ path ] = true ;
}
}
if ( _context . EngineSessionState != _context . TopLevelSessionState )
{
foreach ( var pair in _context . TopLevelSessionState . ModuleTable )
{
string path = pair . Key ;
if (! found . ContainsKey ( path ))
{
PSModuleInfo module = pair . Value ;
// See if this is the requested module...
if ( IsModuleMatchingModuleSpec ( module , moduleSpec ))
{
modulesMatched . Add ( module );
}
}
}
}
}
}
return modulesMatched . OrderBy ( m => m . Name ). ToList ();
}
2018-11-01 13:41:51 -07:00
/// <summary>
/// Check if a given module info object matches a given module specification.
/// </summary>
/// <param name="moduleInfo">The module info object to check.</param>
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
2016-03-30 14:20:52 -07:00
internal static bool IsModuleMatchingModuleSpec ( PSModuleInfo moduleInfo , ModuleSpecification moduleSpec )
{
2018-11-01 13:41:51 -07:00
return IsModuleMatchingModuleSpec ( out ModuleMatchFailure matchFailureReason , moduleInfo , moduleSpec );
}
/// <summary>
/// Check if a given module info object matches a given module specification.
/// </summary>
/// <param name="matchFailureReason">The constraint that caused the match failure, if any.</param>
/// <param name="moduleInfo">The module info object to check.</param>
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
internal static bool IsModuleMatchingModuleSpec ( out ModuleMatchFailure matchFailureReason , PSModuleInfo moduleInfo , ModuleSpecification moduleSpec )
{
if ( moduleSpec == null )
2016-03-30 14:20:52 -07:00
{
2018-11-01 13:41:51 -07:00
matchFailureReason = ModuleMatchFailure . NullModuleSpecification ;
return false ;
2016-03-30 14:20:52 -07:00
}
2018-11-01 13:41:51 -07:00
return IsModuleMatchingConstraints (
out matchFailureReason ,
moduleInfo ,
moduleSpec . Name ,
moduleSpec . Guid ,
moduleSpec . RequiredVersion ,
moduleSpec . Version ,
moduleSpec . MaximumVersion == null ? null : ModuleCmdletBase . GetMaximumVersion ( moduleSpec . MaximumVersion ));
}
/// <summary>
/// Check if a given module info object matches the given constraints.
/// Constraints given as null are ignored.
/// </summary>
/// <param name="moduleInfo">The module info object to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="name">The name or normalized absolute path of the expected module.</param>
2018-11-01 13:41:51 -07:00
/// <param name="guid">The guid of the expected module.</param>
/// <param name="requiredVersion">The required version of the expected module.</param>
/// <param name="minimumVersion">The minimum required version of the expected module.</param>
/// <param name="maximumVersion">The maximum required version of the expected module.</param>
/// <returns>True if the module info object matches all given constraints, false otherwise.</returns>
internal static bool IsModuleMatchingConstraints (
PSModuleInfo moduleInfo ,
string name = null ,
Guid ? guid = null ,
Version requiredVersion = null ,
Version minimumVersion = null ,
Version maximumVersion = null )
{
return IsModuleMatchingConstraints (
out ModuleMatchFailure matchFailureReason ,
moduleInfo ,
name ,
guid ,
requiredVersion ,
minimumVersion ,
maximumVersion );
}
/// <summary>
/// Check if a given module info object matches the given constraints.
/// Constraints given as null are ignored.
/// </summary>
/// <param name="matchFailureReason">The reason for the module constraint match failing.</param>
/// <param name="moduleInfo">The module info object to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="name">The name or normalized absolute path of the expected module.</param>
2018-11-01 13:41:51 -07:00
/// <param name="guid">The guid of the expected module.</param>
/// <param name="requiredVersion">The required version of the expected module.</param>
/// <param name="minimumVersion">The minimum required version of the expected module.</param>
/// <param name="maximumVersion">The maximum required version of the expected module.</param>
/// <returns>True if the module info object matches all given constraints, false otherwise.</returns>
internal static bool IsModuleMatchingConstraints (
out ModuleMatchFailure matchFailureReason ,
PSModuleInfo moduleInfo ,
string name ,
Guid ? guid ,
Version requiredVersion ,
Version minimumVersion ,
Version maximumVersion )
{
// Define that a null module does not meet any constraints
if ( moduleInfo == null )
{
matchFailureReason = ModuleMatchFailure . NullModule ;
return false ;
}
return AreModuleFieldsMatchingConstraints (
out matchFailureReason ,
moduleInfo . Name ,
2018-11-13 14:56:07 -08:00
moduleInfo . Path ,
2018-11-01 13:41:51 -07:00
moduleInfo . Guid ,
moduleInfo . Version ,
name ,
guid ,
requiredVersion ,
minimumVersion ,
maximumVersion
);
}
/// <summary>
/// Check that given module fields meet any given constraints.
/// </summary>
/// <param name="moduleName">The name of the module to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="modulePath">The path of the module to check.</param>
2018-11-01 13:41:51 -07:00
/// <param name="moduleGuid">The GUID of the module to check.</param>
/// <param name="moduleVersion">The version of the module to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>
2018-11-01 13:41:51 -07:00
/// <param name="requiredGuid">The GUID the module must have, if any.</param>
/// <param name="requiredVersion">The exact version the module must have, if any.</param>
/// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>
/// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>
/// <returns>True if the module parameters match all given constraints, false otherwise.</returns>
internal static bool AreModuleFieldsMatchingConstraints (
string moduleName = null ,
2018-11-13 14:56:07 -08:00
string modulePath = null ,
2018-11-01 13:41:51 -07:00
Guid ? moduleGuid = null ,
Version moduleVersion = null ,
string requiredName = null ,
Guid ? requiredGuid = null ,
Version requiredVersion = null ,
Version minimumRequiredVersion = null ,
Version maximumRequiredVersion = null )
{
return AreModuleFieldsMatchingConstraints (
out ModuleMatchFailure matchFailureReason ,
moduleName ,
2018-11-13 14:56:07 -08:00
modulePath ,
2018-11-01 13:41:51 -07:00
moduleGuid ,
moduleVersion ,
requiredName ,
requiredGuid ,
requiredVersion ,
minimumRequiredVersion ,
maximumRequiredVersion );
}
/// <summary>
/// Check that given module fields meet any given constraints.
/// </summary>
/// <param name="matchFailureReason">The reason the match failed, if any.</param>
/// <param name="moduleName">The name of the module to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="modulePath">The path of the module to check.</param>
2018-11-01 13:41:51 -07:00
/// <param name="moduleGuid">The GUID of the module to check.</param>
/// <param name="moduleVersion">The version of the module to check.</param>
2018-11-13 14:56:07 -08:00
/// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>
2018-11-01 13:41:51 -07:00
/// <param name="requiredGuid">The GUID the module must have, if any.</param>
/// <param name="requiredVersion">The exact version the module must have, if any.</param>
/// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>
/// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>
/// <returns>True if the module parameters match all given constraints, false otherwise.</returns>
internal static bool AreModuleFieldsMatchingConstraints (
out ModuleMatchFailure matchFailureReason ,
string moduleName ,
2018-11-13 14:56:07 -08:00
string modulePath ,
2018-11-01 13:41:51 -07:00
Guid ? moduleGuid ,
Version moduleVersion ,
string requiredName ,
Guid ? requiredGuid ,
Version requiredVersion ,
Version minimumRequiredVersion ,
Version maximumRequiredVersion )
{
2018-11-13 14:56:07 -08:00
// If a name is required, check that it matches.
// A required module name may also be an absolute path, so check it against the given module's path as well.
if ( requiredName != null
&& ! requiredName . Equals ( moduleName , StringComparison . OrdinalIgnoreCase )
&& ! MatchesModulePath ( modulePath , requiredName ))
2018-11-01 13:41:51 -07:00
{
matchFailureReason = ModuleMatchFailure . Name ;
return false ;
}
// If a GUID is required, check it matches
if ( requiredGuid != null && ! requiredGuid . Equals ( moduleGuid ))
{
matchFailureReason = ModuleMatchFailure . Guid ;
return false ;
}
// Check the versions
return IsVersionMatchingConstraints ( out matchFailureReason , moduleVersion , requiredVersion , minimumRequiredVersion , maximumRequiredVersion );
}
/// <summary>
/// Check that a given module version matches the required or minimum/maximum version constraints.
/// Null constraints are not checked.
/// </summary>
/// <param name="version">The module version to check. Must not be null</param>
/// <param name="requiredVersion">The version that the given version must be, if not null.</param>
/// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>
/// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>
/// <returns>
/// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.
/// </returns>
internal static bool IsVersionMatchingConstraints (
Version version ,
Version requiredVersion = null ,
Version minimumVersion = null ,
Version maximumVersion = null )
{
return IsVersionMatchingConstraints ( out ModuleMatchFailure matchFailureReason , version , requiredVersion , minimumVersion , maximumVersion );
}
/// <summary>
/// Check that a given module version matches the required or minimum/maximum version constraints.
/// Null constraints are not checked.
/// </summary>
/// <param name="matchFailureReason">The reason why the match failed.</param>
/// <param name="version">The module version to check. Must not be null</param>
/// <param name="requiredVersion">The version that the given version must be, if not null.</param>
/// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>
/// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>
/// <returns>
/// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.
/// </returns>
internal static bool IsVersionMatchingConstraints (
out ModuleMatchFailure matchFailureReason ,
Version version ,
Version requiredVersion = null ,
Version minimumVersion = null ,
Version maximumVersion = null )
{
Dbg . Assert ( version != null , $"Caller to verify that {nameof(version)} is not null" );
// If a RequiredVersion is given it overrides other version settings
if ( requiredVersion != null )
{
matchFailureReason = ModuleMatchFailure . RequiredVersion ;
return requiredVersion . Equals ( version );
}
// Check the version is at least the minimum version
if ( minimumVersion != null && version < minimumVersion )
{
matchFailureReason = ModuleMatchFailure . MinimumVersion ;
return false ;
}
// Check the version is at most the maximum version
if ( maximumVersion != null && version > maximumVersion )
{
matchFailureReason = ModuleMatchFailure . MaximumVersion ;
return false ;
}
matchFailureReason = ModuleMatchFailure . None ;
return true ;
2016-03-30 14:20:52 -07:00
}
2018-11-13 14:56:07 -08:00
/// <summary>
/// Checks whether a given module path is the same as
/// a required path.
/// </summary>
/// <param name="modulePath">The path of the module whose path to check. This must be the path to the module file (.psd1, .psm1, .dll, etc).</param>
/// <param name="requiredPath">The path of the required module. This may be the module directory path or the file path. Only normalized absolute paths will work for this.</param>
/// <returns>True if the module path matches the required path, false otherwise.</returns>
internal static bool MatchesModulePath ( string modulePath , string requiredPath )
{
Dbg . Assert ( requiredPath != null , $"Caller to verify that {nameof(requiredPath)} is not null" );
if ( modulePath == null )
{
return false ;
}
#if UNIX
StringComparison strcmp = StringComparison . Ordinal ;
#else
StringComparison strcmp = StringComparison . OrdinalIgnoreCase ;
#endif
// We must check modulePath (e.g. /path/to/module/module.psd1) against several possibilities:
// 1. "/path/to/module" - Module dir path
// 2. "/path/to/module/module.psd1" - Module root file path
// 3. "/path/to/module/2.1/module.psd1" - Versioned module path
// If the required module just matches the module path (case 1), we are done
if ( modulePath . Equals ( requiredPath , strcmp ))
{
return true ;
}
// At this point we are looking for the module directory (case 2 or 3).
// We can some allocations here if module path doesn't sit under the required path
// (the required path may still refer to some nested module though)
if (! modulePath . StartsWith ( requiredPath , strcmp ))
{
return false ;
}
string moduleDirPath = Path . GetDirectoryName ( modulePath );
// The module itself may be in a versioned directory (case 3)
if ( Version . TryParse ( Path . GetFileName ( moduleDirPath ), out Version unused ))
{
moduleDirPath = Path . GetDirectoryName ( moduleDirPath );
}
return moduleDirPath . Equals ( requiredPath , strcmp );
}
/// <summary>
/// Takes the name of a module as used in a module specification
/// and either returns it as a simple name (if it was a simple name)
/// or a fully qualified, PowerShell-resolved path.
/// </summary>
/// <param name="moduleName">The name or path of the module from the specification.</param>
/// <param name="basePath">The path to base relative paths off.</param>
/// <param name="executionContext">The current execution context.</param>
/// <returns>
/// The simple module name if the given one was simple,
/// otherwise a fully resolved, absolute path to the module.
/// </returns>
/// <remarks>
/// 2018-11-09 rjmholt:
/// There are several, possibly inconsistent, path handling mechanisms
/// in the module cmdlets. After looking through all of them and seeing
/// they all make some assumptions about their caller I wrote this method.
/// Hopefully we can find a standard path resolution API to settle on.
/// </remarks>
internal static string NormalizeModuleName (
string moduleName ,
string basePath ,
ExecutionContext executionContext )
{
if ( moduleName == null )
{
return null ;
}
// Check whether the module is a path -- if not, it is a simple name and we just return it.
if (! IsModuleNamePath ( moduleName ))
{
return moduleName ;
}
// Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows
moduleName = moduleName . Replace ( StringLiterals . AlternatePathSeparator , StringLiterals . DefaultPathSeparator );
// Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true
if (! Path . IsPathRooted ( moduleName ))
{
moduleName = Path . Join ( basePath , moduleName );
}
// Use the PowerShell filesystem provider to fully resolve the path
// If there is a problem, null could be returned -- so default back to the pre-normalized path
string normalizedPath = ModuleCmdletBase . GetResolvedPath ( moduleName , executionContext )?. TrimEnd ( StringLiterals . DefaultPathSeparator );
// ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed.
// If it does, we return the fully qualified path generated before.
return normalizedPath ?? Path . GetFullPath ( moduleName );
}
/// <summary>
/// Check if a given module name is a path to a module rather than a simple name.
/// </summary>
/// <param name="moduleName">The module name to check.</param>
/// <returns>True if the module name is a path, false otherwise.</returns>
internal static bool IsModuleNamePath ( string moduleName )
{
return moduleName . Contains ( StringLiterals . DefaultPathSeparator )
|| moduleName . Contains ( StringLiterals . AlternatePathSeparator )
|| moduleName . Equals ( ".." )
|| moduleName . Equals ( "." );
}
2016-03-30 14:20:52 -07:00
internal static Version GetManifestModuleVersion ( string manifestPath )
{
2018-06-26 12:37:43 -07:00
try
2016-03-30 14:20:52 -07:00
{
2018-06-26 12:37:43 -07:00
Hashtable dataFileSetting =
PsUtils . GetModuleManifestProperties (
manifestPath ,
PsUtils . ManifestModuleVersionPropertyName );
2016-03-30 14:20:52 -07:00
2018-06-26 12:37:43 -07:00
object versionValue = dataFileSetting [ "ModuleVersion" ];
if ( versionValue != null )
{
Version moduleVersion ;
if ( LanguagePrimitives . TryConvertTo ( versionValue , out moduleVersion ))
2016-03-30 14:20:52 -07:00
{
2018-06-26 12:37:43 -07:00
return moduleVersion ;
2016-03-30 14:20:52 -07:00
}
}
}
2018-06-26 12:37:43 -07:00
catch ( PSInvalidOperationException ) { }
2016-03-30 14:20:52 -07:00
return new Version ( 0 , 0 );
}
internal static Guid GetManifestGuid ( string manifestPath )
{
2018-06-26 12:37:43 -07:00
try
2016-03-30 14:20:52 -07:00
{
2018-06-26 12:37:43 -07:00
Hashtable dataFileSetting =
PsUtils . GetModuleManifestProperties (
manifestPath ,
PsUtils . ManifestGuidPropertyName );
2016-03-30 14:20:52 -07:00
2018-06-26 12:37:43 -07:00
object guidValue = dataFileSetting [ "GUID" ];
if ( guidValue != null )
{
Guid guidID ;
if ( LanguagePrimitives . TryConvertTo ( guidValue , out guidID ))
2016-03-30 14:20:52 -07:00
{
2018-06-26 12:37:43 -07:00
return guidID ;
2016-03-30 14:20:52 -07:00
}
}
}
2018-06-26 12:37:43 -07:00
catch ( PSInvalidOperationException ) { }
2016-03-30 14:20:52 -07:00
return new Guid ();
}
2018-06-26 12:37:43 -07:00
internal static ExperimentalFeature [] GetExperimentalFeature ( string manifestPath )
{
try
{
Hashtable dataFileSetting =
PsUtils . GetModuleManifestProperties (
manifestPath ,
PsUtils . ManifestPrivateDataPropertyName );
object privateData = dataFileSetting [ "PrivateData" ];
if ( privateData is Hashtable hashData && hashData [ "PSData" ] is Hashtable psData )
{
object expFeatureValue = psData [ "ExperimentalFeatures" ];
if ( expFeatureValue != null &&
LanguagePrimitives . TryConvertTo ( expFeatureValue , out Hashtable [] features ) &&
features . Length > 0 )
{
string moduleName = ModuleIntrinsics . GetModuleName ( manifestPath );
var expFeatureList = new List < ExperimentalFeature >();
foreach ( Hashtable feature in features )
{
string featureName = feature [ "Name" ] as string ;
if ( String . IsNullOrEmpty ( featureName )) { continue ; }
if ( ExperimentalFeature . IsModuleFeatureName ( featureName , moduleName ))
{
string featureDescription = feature [ "Description" ] as string ;
expFeatureList . Add ( new ExperimentalFeature ( featureName , featureDescription , manifestPath ,
2018-07-09 17:14:34 -07:00
ExperimentalFeature . IsEnabled ( featureName )));
2018-06-26 12:37:43 -07:00
}
}
return expFeatureList . ToArray ();
}
}
}
catch ( PSInvalidOperationException ) { }
return Utils . EmptyArray < ExperimentalFeature >();
}
2016-04-28 13:41:25 -07:00
// The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded.
2016-03-30 14:20:52 -07:00
internal static string [] PSModuleProcessableExtensions = new string [] {
StringLiterals . PowerShellDataFileExtension ,
StringLiterals . PowerShellScriptFileExtension ,
StringLiterals . PowerShellModuleFileExtension ,
StringLiterals . PowerShellCmdletizationFileExtension ,
StringLiterals . WorkflowFileExtension ,
2016-04-28 13:41:25 -07:00
StringLiterals . PowerShellNgenAssemblyExtension ,
2017-09-11 08:55:13 -07:00
StringLiterals . PowerShellILAssemblyExtension };
2016-03-30 14:20:52 -07:00
2016-04-28 13:41:25 -07:00
// A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded.
2016-03-30 14:20:52 -07:00
internal static string [] PSModuleExtensions = new string [] {
StringLiterals . PowerShellDataFileExtension ,
StringLiterals . PowerShellModuleFileExtension ,
StringLiterals . PowerShellCmdletizationFileExtension ,
StringLiterals . WorkflowFileExtension ,
2016-04-28 13:41:25 -07:00
StringLiterals . PowerShellNgenAssemblyExtension ,
2017-09-11 08:55:13 -07:00
StringLiterals . PowerShellILAssemblyExtension };
2016-03-30 14:20:52 -07:00
/// <summary>
/// Returns true if the extension is one of the module extensions...
/// </summary>
/// <param name="extension">The extension to check</param>
/// <returns>True if it was a module extension...</returns>
internal static bool IsPowerShellModuleExtension ( string extension )
{
foreach ( string ext in PSModuleProcessableExtensions )
{
if ( extension . Equals ( ext , StringComparison . OrdinalIgnoreCase ))
return true ;
}
2018-08-14 05:03:27 +05:00
2016-03-30 14:20:52 -07:00
return false ;
}
/// <summary>
/// Gets the module name from module path.
/// </summary>
/// <param name="path">The path to the module</param>
/// <returns>The module name</returns>
internal static string GetModuleName ( string path )
{
string fileName = path == null ? string . Empty : Path . GetFileName ( path );
2016-04-28 13:41:25 -07:00
string ext ;
if ( fileName . EndsWith ( StringLiterals . PowerShellNgenAssemblyExtension , StringComparison . OrdinalIgnoreCase ))
{
ext = StringLiterals . PowerShellNgenAssemblyExtension ;
}
else
{
ext = Path . GetExtension ( fileName );
}
2016-03-30 14:20:52 -07:00
if (! string . IsNullOrEmpty ( ext ) && IsPowerShellModuleExtension ( ext ))
{
return fileName . Substring ( 0 , fileName . Length - ext . Length );
}
else
{
return fileName ;
}
}
/// <summary>
2016-04-06 12:01:07 -07:00
/// Gets the personal module path
2016-03-30 14:20:52 -07:00
/// </summary>
/// <returns>personal module path</returns>
internal static string GetPersonalModulePath ()
{
2016-08-10 10:37:42 -07:00
#if UNIX
2016-08-11 14:22:23 -07:00
return Platform . SelectProductNameForDirectory ( Platform . XDG_Type . USER_MODULES );
2016-08-10 10:37:42 -07:00
#else
return Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . MyDocuments ), Utils . ModuleDirectory );
#endif
2016-03-30 14:20:52 -07:00
}
/// <summary>
2016-08-30 17:14:28 -07:00
/// Gets the PSHome module path, as known as the "system wide module path" in windows powershell.
2016-03-30 14:20:52 -07:00
/// </summary>
2016-08-30 17:14:28 -07:00
/// <returns>The PSHome module path</returns>
internal static string GetPSHomeModulePath ()
2016-03-30 14:20:52 -07:00
{
2016-08-30 17:14:28 -07:00
if ( s_psHomeModulePath != null )
2018-08-14 05:03:27 +05:00
{
2016-08-30 17:14:28 -07:00
return s_psHomeModulePath ;
2018-08-14 05:03:27 +05:00
}
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
try
{
2017-06-21 02:08:02 +04:00
string psHome = Utils . DefaultPowerShellAppBase ;
2016-08-10 10:37:42 -07:00
if (! string . IsNullOrEmpty ( psHome ))
2016-04-06 12:01:07 -07:00
{
2016-08-10 10:37:42 -07:00
// Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed
2017-01-16 13:31:14 -08:00
// This happens because ModuleTable uses Path as the key and CBS installer
// expands the path to include "SysWOW64" (for
2016-08-10 10:37:42 -07:00
// HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase).
// Because of this, the module that is getting loaded during startup (through LocalRunspace)
2017-01-16 13:31:14 -08:00
// is using "SysWow64" in the key. Later, when Import-Module is called, it loads the
2016-08-10 10:37:42 -07:00
// module using ""System32" in the key.
#if ! UNIX
2016-04-06 12:01:07 -07:00
psHome = psHome . ToLowerInvariant (). Replace ( "\\syswow64\\" , "\\system32\\" );
2016-08-10 10:37:42 -07:00
#endif
2016-08-30 17:14:28 -07:00
Interlocked . CompareExchange ( ref s_psHomeModulePath , Path . Combine ( psHome , "Modules" ), null );
2016-04-06 12:01:07 -07:00
}
2016-03-30 14:20:52 -07:00
}
2018-08-14 05:03:27 +05:00
catch ( System . Security . SecurityException )
{
}
2016-03-30 14:20:52 -07:00
2016-08-30 17:14:28 -07:00
return s_psHomeModulePath ;
2016-03-30 14:20:52 -07:00
}
2016-08-30 17:14:28 -07:00
private static string s_psHomeModulePath ;
2016-03-30 14:20:52 -07:00
/// <summary>
2016-08-30 17:14:28 -07:00
/// Get the module path that is shared among different users.
/// It's known as "Program Files" module path in windows powershell.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <returns></returns>
2017-07-11 11:23:43 -07:00
private static string GetSharedModulePath ()
2016-03-30 14:20:52 -07:00
{
2016-08-10 10:37:42 -07:00
#if UNIX
2016-08-11 14:22:23 -07:00
return Platform . SelectProductNameForDirectory ( Platform . XDG_Type . SHARED_MODULES );
2016-08-10 10:37:42 -07:00
#else
2017-07-11 11:23:43 -07:00
string sharedModulePath = Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles );
2017-07-31 23:50:15 +04:00
2017-07-11 11:23:43 -07:00
if (! string . IsNullOrEmpty ( sharedModulePath ))
2016-03-30 14:20:52 -07:00
{
2017-07-11 11:23:43 -07:00
sharedModulePath = Path . Combine ( sharedModulePath , Utils . ModuleDirectory );
2016-03-30 14:20:52 -07:00
}
2018-08-14 05:03:27 +05:00
2016-08-30 17:14:28 -07:00
return sharedModulePath ;
2016-08-10 10:37:42 -07:00
#endif
2016-03-30 14:20:52 -07:00
}
2018-07-16 16:58:40 -07:00
#if ! UNIX
/// <summary>
/// Get the path to the Windows PowerShell module directory under the
/// System32 directory on Windows (the Windows PowerShell $PSHOME).
/// </summary>
/// <returns>The path of the Windows PowerShell system module directory.</returns>
internal static string GetWindowsPowerShellPSHomeModulePath ()
{
if (! String . IsNullOrEmpty ( InternalTestHooks . TestWindowsPowerShellPSHomeLocation ))
{
return InternalTestHooks . TestWindowsPowerShellPSHomeLocation ;
}
return s_windowsPowerShellPSHomeModulePath ;
}
#endif
2016-03-30 14:20:52 -07:00
/// <summary>
/// Combine the PS system-wide module path and the DSC module path
/// to get the system module paths.
/// </summary>
/// <returns></returns>
private static string CombineSystemModulePaths ()
{
2016-08-30 17:14:28 -07:00
string psHomeModulePath = GetPSHomeModulePath ();
string sharedModulePath = GetSharedModulePath ();
2016-03-30 14:20:52 -07:00
2016-08-30 17:14:28 -07:00
bool isPSHomePathNullOrEmpty = string . IsNullOrEmpty ( psHomeModulePath );
bool isSharedPathNullOrEmpty = string . IsNullOrEmpty ( sharedModulePath );
2016-08-10 10:37:42 -07:00
2016-08-30 17:14:28 -07:00
if (! isPSHomePathNullOrEmpty && ! isSharedPathNullOrEmpty )
2016-03-30 14:20:52 -07:00
{
2016-08-30 17:14:28 -07:00
return ( sharedModulePath + Path . PathSeparator + psHomeModulePath );
2016-03-30 14:20:52 -07:00
}
2016-08-30 17:14:28 -07:00
if (! isPSHomePathNullOrEmpty || ! isSharedPathNullOrEmpty )
2016-03-30 14:20:52 -07:00
{
2016-08-30 17:14:28 -07:00
return isPSHomePathNullOrEmpty ? sharedModulePath : psHomeModulePath ;
2016-03-30 14:20:52 -07:00
}
return null ;
}
2016-08-04 13:15:08 -07:00
internal static string GetExpandedEnvironmentVariable ( string name , EnvironmentVariableTarget target )
2016-03-30 14:20:52 -07:00
{
string result = Environment . GetEnvironmentVariable ( name , target );
if (! string . IsNullOrEmpty ( result ))
{
result = Environment . ExpandEnvironmentVariables ( result );
}
return result ;
}
/// <summary>
/// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%)
/// </summary>
/// <param name="pathToScan">'Combined path' string to analyze; can not be null.</param>
/// <param name="pathToLookFor">Path to search for; can not be another 'combined path' (semicolon-separated); can not be null.</param>
/// <returns>Index of pathToLookFor in pathToScan; -1 if not found.</returns>
private static int PathContainsSubstring ( string pathToScan , string pathToLookFor )
{
// we don't support if any of the args are null - parent function should ensure this; empty values are ok
Diagnostics . Assert ( pathToScan != null , "pathToScan should not be null according to contract of the function" );
Diagnostics . Assert ( pathToLookFor != null , "pathToLookFor should not be null according to contract of the function" );
int pos = 0 ; // position of the current substring in pathToScan
2016-08-10 10:37:42 -07:00
string [] substrings = pathToScan . Split ( Utils . Separators . PathSeparator , StringSplitOptions . None ); // we want to process empty entries
string goodPathToLookFor = pathToLookFor . Trim (). TrimEnd ( Path . DirectorySeparatorChar ); // trailing backslashes and white-spaces will mess up equality comparison
2016-03-30 14:20:52 -07:00
foreach ( string substring in substrings )
{
2016-08-10 10:37:42 -07:00
string goodSubstring = substring . Trim (). TrimEnd ( Path . DirectorySeparatorChar ); // trailing backslashes and white-spaces will mess up equality comparison
2016-03-30 14:20:52 -07:00
// We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains')
// because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" }
if ( string . Equals ( goodSubstring , goodPathToLookFor , StringComparison . OrdinalIgnoreCase ))
{
return pos ; // match found - return index of it in the 'pathToScan' string
}
else
{
pos += substring . Length + 1 ; // '1' is for trailing semicolon
}
}
// if we are here, that means a match was not found
return - 1 ;
}
/// <summary>
/// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there.
/// </summary>
/// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param>
/// <param name="pathToAdd">Collection of individual paths to add.</param>
/// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param>
/// <returns>Result string.</returns>
private static string AddToPath ( string basePath , string pathToAdd , int insertPosition )
{
// we don't support if any of the args are null - parent function should ensure this; empty values are ok
Diagnostics . Assert ( basePath != null , "basePath should not be null according to contract of the function" );
Diagnostics . Assert ( pathToAdd != null , "pathToAdd should not be null according to contract of the function" );
2016-08-10 10:37:42 -07:00
StringBuilder result = new StringBuilder ( basePath );
2017-01-16 13:31:14 -08:00
2016-03-30 14:20:52 -07:00
if (! string . IsNullOrEmpty ( pathToAdd )) // we don't want to append empty paths
{
2016-08-10 10:37:42 -07:00
foreach ( string subPathToAdd in pathToAdd . Split ( Utils . Separators . PathSeparator , StringSplitOptions . RemoveEmptyEntries )) // in case pathToAdd is a 'combined path' (semicolon-separated)
2016-03-30 14:20:52 -07:00
{
int position = PathContainsSubstring ( result . ToString (), subPathToAdd ); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly
2018-07-02 11:00:27 -07:00
if ( position == - 1 ) // subPathToAdd not found - add it
2016-03-30 14:20:52 -07:00
{
2018-07-02 11:00:27 -07:00
if ( insertPosition == - 1 ) // append subPathToAdd to the end
2016-03-30 14:20:52 -07:00
{
2016-08-10 10:37:42 -07:00
bool endsWithPathSeparator = false ;
if ( result . Length > 0 ) endsWithPathSeparator = ( result [ result . Length - 1 ] == Path . PathSeparator );
2016-03-30 14:20:52 -07:00
2016-08-10 10:37:42 -07:00
if ( endsWithPathSeparator )
2016-03-30 14:20:52 -07:00
result . Append ( subPathToAdd );
else
2016-08-10 10:37:42 -07:00
result . Append ( Path . PathSeparator + subPathToAdd );
2016-03-30 14:20:52 -07:00
}
else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User))
{
2016-08-10 10:37:42 -07:00
result . Insert ( insertPosition , subPathToAdd + Path . PathSeparator );
2016-03-30 14:20:52 -07:00
}
}
}
}
return result . ToString ();
}
2017-01-16 13:31:14 -08:00
2016-08-11 17:07:03 -07:00
/// <summary>
/// Check if the current powershell is likely running in following scenarios:
2018-07-02 11:00:27 -07:00
/// - PSCore started on windows [machine-wide env:PSModulePath will influence]
/// - PSCore started from full ps
/// - PSCore started from inbox nano/iot ps
2016-08-11 17:07:03 -07:00
/// If it's likely one of them, then we need to clear the current process module path.
/// </summary>
2018-07-02 11:00:27 -07:00
private static bool NeedToClearProcessModulePath ( string currentProcessModulePath , string personalModulePath , string sharedModulePath )
2016-08-10 10:37:42 -07:00
{
#if UNIX
return false ;
#else
2017-07-11 11:23:43 -07:00
Dbg . Assert (! string . IsNullOrEmpty ( personalModulePath ), "caller makes sure personalModulePath not null or empty" );
Dbg . Assert ( sharedModulePath != null , "caller makes sure sharedModulePath is not null" );
2016-08-10 10:37:42 -07:00
2016-08-11 17:07:03 -07:00
const string winSxSModuleDirectory = @"PowerShell\Modules" ;
2016-08-10 10:37:42 -07:00
const string winLegacyModuleDirectory = @"WindowsPowerShell\Modules" ;
2018-07-02 11:00:27 -07:00
// The machine-wide and user-wide environment variables are only meaningful for full ps,
// so if the current process module path contains any of them, it's likely that the sxs
// ps was started directly on windows, or from full ps. The same goes for the legacy personal
// and shared module paths.
string hklmModulePath = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Machine );
string hkcuModulePath = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . User );
string legacyPersonalModulePath = personalModulePath . Replace ( winSxSModuleDirectory , winLegacyModuleDirectory );
string legacyProgramFilesModulePath = sharedModulePath . Replace ( winSxSModuleDirectory , winLegacyModuleDirectory );
2016-08-10 10:37:42 -07:00
2018-07-02 11:00:27 -07:00
return (! string . IsNullOrEmpty ( hklmModulePath ) && currentProcessModulePath . IndexOf ( hklmModulePath , StringComparison . OrdinalIgnoreCase ) != - 1 ) ||
(! string . IsNullOrEmpty ( hkcuModulePath ) && currentProcessModulePath . IndexOf ( hkcuModulePath , StringComparison . OrdinalIgnoreCase ) != - 1 ) ||
currentProcessModulePath . IndexOf ( legacyPersonalModulePath , StringComparison . OrdinalIgnoreCase ) != - 1 ||
currentProcessModulePath . IndexOf ( legacyProgramFilesModulePath , StringComparison . OrdinalIgnoreCase ) != - 1 ;
2016-08-10 10:37:42 -07:00
#endif
}
2016-08-11 17:07:03 -07:00
/// <summary>
/// When sxs ps instance B got started from sxs ps instance A, A's pshome module path might
/// show up in current process module path. It doesn't make sense for B to load modules from
/// A's pshome module path, so remove it in such case.
/// </summary>
2016-09-13 17:20:40 -07:00
private static string RemoveSxSPsHomeModulePath ( string currentProcessModulePath , string personalModulePath , string sharedModulePath , string psHomeModulePath )
2016-08-10 10:37:42 -07:00
{
#if UNIX
2017-10-17 17:25:11 -07:00
const string powershellExeName = "pwsh" ;
2016-08-10 10:37:42 -07:00
#else
2017-10-17 17:25:11 -07:00
const string powershellExeName = "pwsh.exe" ;
2016-08-10 10:37:42 -07:00
#endif
2017-10-17 17:25:11 -07:00
const string powershellDepsName = "pwsh.deps.json" ;
2016-08-10 10:37:42 -07:00
StringBuilder modulePathString = new StringBuilder ( currentProcessModulePath . Length );
char [] invalidPathChars = Path . GetInvalidPathChars ();
foreach ( var path in currentProcessModulePath . Split ( Utils . Separators . PathSeparator , StringSplitOptions . RemoveEmptyEntries ))
{
string trimedPath = path . Trim (). TrimEnd ( Path . DirectorySeparatorChar );
2016-09-13 17:20:40 -07:00
if ( trimedPath . IndexOfAny ( invalidPathChars ) != - 1 || ! Path . IsPathRooted ( trimedPath ))
2016-08-10 10:37:42 -07:00
{
2016-09-13 17:20:40 -07:00
// Path contains invalid characters or it's not an absolute path. Ignore it.
2016-08-10 10:37:42 -07:00
continue ;
}
2017-01-16 13:31:14 -08:00
if (! trimedPath . Equals ( personalModulePath , StringComparison . OrdinalIgnoreCase ) &&
2016-09-13 17:20:40 -07:00
! trimedPath . Equals ( sharedModulePath , StringComparison . OrdinalIgnoreCase ) &&
! trimedPath . Equals ( psHomeModulePath , StringComparison . OrdinalIgnoreCase ) &&
trimedPath . EndsWith ( "Modules" , StringComparison . OrdinalIgnoreCase ))
2016-08-10 10:37:42 -07:00
{
2016-09-13 17:20:40 -07:00
string parentDir = Path . GetDirectoryName ( trimedPath );
string psExePath = Path . Combine ( parentDir , powershellExeName );
2017-10-17 17:25:11 -07:00
string psDepsPath = Path . Combine ( parentDir , powershellDepsName );
2017-12-06 12:37:41 -08:00
if (( File . Exists ( psExePath ) && File . Exists ( psDepsPath )))
2016-09-13 17:20:40 -07:00
{
// Path is a PSHome module path from a different powershell core instance. Ignore it.
continue ;
}
2016-08-10 10:37:42 -07:00
}
if ( modulePathString . Length > 0 )
{
modulePathString . Append ( Path . PathSeparator );
}
modulePathString . Append ( trimedPath );
}
return modulePathString . ToString ();
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate. Note - because these
/// strings go through the provider, we need to escape any wildcards before passing them
/// along.
/// </summary>
public static string GetModulePath ( string currentProcessModulePath , string hklmMachineModulePath , string hkcuUserModulePath )
{
2016-08-10 10:37:42 -07:00
string personalModulePath = GetPersonalModulePath ();
2016-08-30 17:14:28 -07:00
string sharedModulePath = GetSharedModulePath (); // aka <Program Files> location
string psHomeModulePath = GetPSHomeModulePath (); // $PSHome\Modules location
2017-07-31 23:50:15 +04:00
2017-01-16 13:31:14 -08:00
if (! string . IsNullOrEmpty ( currentProcessModulePath ) &&
2018-07-02 11:00:27 -07:00
NeedToClearProcessModulePath ( currentProcessModulePath , personalModulePath , sharedModulePath ))
2016-08-10 10:37:42 -07:00
{
// Clear the current process module path in the following cases
2018-07-02 11:00:27 -07:00
// - start PSCore on windows [machine-wide env:PSModulePath will influence]
// - start PSCore from full ps
// - start PSCore from inbox nano/iot ps
2016-08-10 10:37:42 -07:00
currentProcessModulePath = null ;
}
2016-03-30 14:20:52 -07:00
// If the variable isn't set, then set it to the default value
if ( currentProcessModulePath == null ) // EVT.Process does Not exist - really corner case
{
// Handle the default case...
2016-08-10 10:37:42 -07:00
if ( string . IsNullOrEmpty ( hkcuUserModulePath )) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location
2016-03-30 14:20:52 -07:00
{
2016-08-10 10:37:42 -07:00
currentProcessModulePath = personalModulePath ; // = SpecialFolder.MyDocuments + Utils.ProductNameForDirectory + Utils.ModuleDirectory
2016-03-30 14:20:52 -07:00
}
else // EVT.User exists -> set to EVT.User
{
currentProcessModulePath = hkcuUserModulePath ; // = EVT.User
}
2016-08-10 10:37:42 -07:00
currentProcessModulePath += Path . PathSeparator ;
if ( string . IsNullOrEmpty ( hklmMachineModulePath )) // EVT.Machine does Not exist
2016-03-30 14:20:52 -07:00
{
2016-08-30 17:14:28 -07:00
currentProcessModulePath += CombineSystemModulePaths (); // += (SharedModulePath + $PSHome\Modules)
2016-03-30 14:20:52 -07:00
}
else
{
currentProcessModulePath += hklmMachineModulePath ; // += EVT.Machine
}
}
2016-08-10 10:37:42 -07:00
// EVT.Process exists
// Now handle the case where the environment variable is already set.
2018-07-02 11:00:27 -07:00
else
2016-03-30 14:20:52 -07:00
{
2016-08-11 17:07:03 -07:00
// When SxS PS instance A starts SxS PS instance B, A's PSHome module path might be inherited by B. We need to remove that path from B
2016-09-13 17:20:40 -07:00
currentProcessModulePath = RemoveSxSPsHomeModulePath ( currentProcessModulePath , personalModulePath , sharedModulePath , psHomeModulePath );
2016-03-30 14:20:52 -07:00
2016-08-10 10:37:42 -07:00
string personalModulePathToUse = string . IsNullOrEmpty ( hkcuUserModulePath ) ? personalModulePath : hkcuUserModulePath ;
string systemModulePathToUse = string . IsNullOrEmpty ( hklmMachineModulePath ) ? psHomeModulePath : hklmMachineModulePath ;
2016-04-06 12:01:07 -07:00
2016-08-10 10:37:42 -07:00
currentProcessModulePath = AddToPath ( currentProcessModulePath , personalModulePathToUse , 0 );
2018-09-10 10:58:38 -07:00
int insertIndex = - 1 ;
#if ! UNIX
string windowsPowerShellModulePath = GetWindowsPowerShellPSHomeModulePath ();
// If the Windows PowerShell Module path is already present, insert the system module path
// ($PSHOME/Modules) before it.
insertIndex = PathContainsSubstring ( currentProcessModulePath , windowsPowerShellModulePath );
2018-12-05 12:20:58 -08:00
#endif
2018-09-10 10:58:38 -07:00
currentProcessModulePath = AddToPath ( currentProcessModulePath , systemModulePathToUse , insertIndex );
2016-08-10 10:37:42 -07:00
}
2016-03-30 14:20:52 -07:00
// if we reached this point - always add <Program Files> location to EVT.Process
// everything below is the same behaviour as WMF 4 code
2018-07-02 11:00:27 -07:00
// index of $PSHome\Modules in currentProcessModulePath
int indexOfPSHomeModulePath = PathContainsSubstring ( currentProcessModulePath , psHomeModulePath );
2018-12-05 12:20:58 -08:00
2016-03-30 14:20:52 -07:00
// if $PSHome\Modules not found (psHomePosition == -1) - append <Program Files> location to the end;
// if $PSHome\Modules IS found (psHomePosition >= 0) - insert <Program Files> location before $PSHome\Modules
2016-08-30 17:14:28 -07:00
currentProcessModulePath = AddToPath ( currentProcessModulePath , sharedModulePath , indexOfPSHomeModulePath );
2016-03-30 14:20:52 -07:00
return currentProcessModulePath ;
}
/// <summary>
/// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these
/// strings go through the provider, we need to escape any wildcards before passing them
/// along.
/// </summary>
internal static string GetModulePath ()
{
2017-03-15 12:04:28 -07:00
string currentModulePath = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Process );
2016-03-30 14:20:52 -07:00
return currentModulePath ;
}
2018-07-02 11:00:27 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these
/// strings go through the provider, we need to escape any wildcards before passing them
/// along.
/// </summary>
2018-07-02 11:00:27 -07:00
private static string SetModulePath ()
2016-03-30 14:20:52 -07:00
{
2017-03-15 12:04:28 -07:00
string currentModulePath = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Process );
2018-12-05 12:20:58 -08:00
string allUsersModulePath = PowerShellConfig . Instance . GetModulePath ( ConfigScope . AllUsers );
2018-01-03 12:38:38 -08:00
string personalModulePath = PowerShellConfig . Instance . GetModulePath ( ConfigScope . CurrentUser );
2016-03-30 14:20:52 -07:00
2018-12-05 12:20:58 -08:00
string newModulePathString = GetModulePath ( currentModulePath , allUsersModulePath , personalModulePath );
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
if (! string . IsNullOrEmpty ( newModulePathString ))
2016-04-06 12:01:07 -07:00
{
2018-07-16 16:58:40 -07:00
#if ! UNIX
// If on Windows, we want to add the System32 Windows PowerShell module directory
// so that Windows modules are discoverable
2018-08-03 11:22:30 -07:00
string windowsPowerShellModulePath = GetWindowsPowerShellPSHomeModulePath ();
if (! newModulePathString . Contains ( windowsPowerShellModulePath , StringComparison . OrdinalIgnoreCase ))
{
newModulePathString += Path . PathSeparator + windowsPowerShellModulePath ;
}
2018-07-16 16:58:40 -07:00
#endif
2016-04-06 12:01:07 -07:00
// Set the environment variable...
2017-03-15 12:04:28 -07:00
Environment . SetEnvironmentVariable ( Constants . PSModulePathEnvVar , newModulePathString );
2016-04-06 12:01:07 -07:00
}
2016-03-30 14:20:52 -07:00
return newModulePathString ;
}
/// <summary>
/// Get the current module path setting.
/// </summary>
2016-04-28 13:41:25 -07:00
/// <param name="includeSystemModulePath">
/// Include The system wide module path ($PSHOME\Modules) even if it's not in PSModulePath.
/// In V3-V5, we prepended this path during module auto-discovery which incorrectly preferred
/// $PSHOME\Modules over user installed modules that might have a command that overrides
/// a product-supplied command.
/// For 5.1, we append $PSHOME\Modules in this case to avoid the rare case where PSModulePath
/// does not contain the path, but a script depends on previous behavior.
/// Note that appending is still a potential breaking change, but necessary to update in-box
/// modules long term - e.g. when open sourcing a module and installing from the gallery.
/// </param>
/// <param name="context"></param>
2016-03-30 14:20:52 -07:00
/// <returns>The module path as an array of strings</returns>
2016-04-28 13:41:25 -07:00
internal static IEnumerable < string > GetModulePath ( bool includeSystemModulePath , ExecutionContext context )
2016-03-30 14:20:52 -07:00
{
2017-03-15 12:04:28 -07:00
string modulePathString = Environment . GetEnvironmentVariable ( Constants . PSModulePathEnvVar ) ?? SetModulePath ();
2016-03-30 14:20:52 -07:00
HashSet < string > processedPathSet = new HashSet < string >( StringComparer . OrdinalIgnoreCase );
2016-04-28 13:41:25 -07:00
if (! string . IsNullOrWhiteSpace ( modulePathString ))
2016-03-30 14:20:52 -07:00
{
2016-08-10 10:37:42 -07:00
foreach ( string envPath in modulePathString . Split ( Utils . Separators . PathSeparator , StringSplitOptions . RemoveEmptyEntries ))
2016-07-29 13:02:49 -07:00
{
var processedPath = ProcessOneModulePath ( context , envPath , processedPathSet );
if ( processedPath != null )
yield return processedPath ;
}
2016-08-10 10:37:42 -07:00
}
2016-03-30 14:20:52 -07:00
2016-04-28 13:41:25 -07:00
if ( includeSystemModulePath )
{
2016-08-30 17:14:28 -07:00
var processedPath = ProcessOneModulePath ( context , GetPSHomeModulePath (), processedPathSet );
2016-04-28 13:41:25 -07:00
if ( processedPath != null )
yield return processedPath ;
}
}
2016-08-02 19:00:50 -07:00
private static string ProcessOneModulePath ( ExecutionContext context , string envPath , HashSet < string > processedPathSet )
2016-03-30 14:20:52 -07:00
{
string trimmedenvPath = envPath . Trim ();
bool isUnc = Utils . PathIsUnc ( trimmedenvPath );
if (! isUnc )
{
// if the path start with "filesystem::", remove it so we can test for URI and
// also Directory.Exists (if the file system provider isn't actually loaded.)
if ( trimmedenvPath . StartsWith ( "filesystem::" , StringComparison . OrdinalIgnoreCase ))
{
trimmedenvPath = trimmedenvPath . Remove ( 0 , 12 /*"filesystem::".Length*/ );
}
isUnc = Utils . PathIsUnc ( trimmedenvPath );
}
// If we have an unc, just return the value as resolving the path is expensive.
if ( isUnc )
{
return trimmedenvPath ;
}
// We prefer using the file system provider to resolve paths so callers can avoid processing
// duplicates, e.g. the following are all the same:
// a\b
// a\.\b
// a\b\
// But if the file system provider isn't loaded, we will just check if the directory exists.
if ( context . EngineSessionState . IsProviderLoaded ( context . ProviderNames . FileSystem ))
{
ProviderInfo provider = null ;
IEnumerable < string > resolvedPaths = null ;
try
{
resolvedPaths = context . SessionState . Path . GetResolvedProviderPathFromPSPath (
WildcardPattern . Escape ( trimmedenvPath ), out provider );
}
catch ( ItemNotFoundException )
{
// silently skip directories that are not found
}
catch ( DriveNotFoundException )
{
// silently skip drives that are not found
}
catch ( NotSupportedException )
{
// silently skip invalid path
2016-04-06 12:01:07 -07:00
// NotSupportedException is thrown if path contains a colon (":") that is not part of a
// volume identifier (for example, "c:\" is Supported but not "c:\temp\Z:\invalidPath")
2016-03-30 14:20:52 -07:00
}
if ( provider != null && resolvedPaths != null && provider . NameEquals ( context . ProviderNames . FileSystem ))
{
var result = resolvedPaths . FirstOrDefault ();
if ( processedPathSet . Add ( result ))
{
return result ;
}
}
}
else if ( Directory . Exists ( trimmedenvPath ))
{
return trimmedenvPath ;
}
return null ;
}
2018-11-13 16:16:29 -08:00
/// <summary>
/// Removes all functions not belonging to the parent module.
/// </summary>
/// <param name="module">Parent module</param>
static internal void RemoveNestedModuleFunctions ( PSModuleInfo module )
{
var input = module . SessionState ?. Internal ?. ExportedFunctions ;
if (( input == null ) || ( input . Count == 0 ))
{ return ; }
List < FunctionInfo > output = new List < FunctionInfo >( input . Count );
foreach ( var fnInfo in input )
{
if ( module . Name . Equals ( fnInfo . ModuleName , StringComparison . OrdinalIgnoreCase ))
{
output . Add ( fnInfo );
}
}
input . Clear ();
input . AddRange ( output );
}
2016-08-02 19:00:50 -07:00
private static void SortAndRemoveDuplicates < T >( List < T > input , Func < T , string > keyGetter )
2016-03-30 14:20:52 -07:00
{
Dbg . Assert ( input != null , "Caller should verify that input != null" );
input . Sort (
2016-07-29 13:02:49 -07:00
delegate ( T x , T y )
2016-03-30 14:20:52 -07:00
{
string kx = keyGetter ( x );
string ky = keyGetter ( y );
return string . Compare ( kx , ky , StringComparison . OrdinalIgnoreCase );
}
);
bool firstItem = true ;
string previousKey = null ;
List < T > output = new List < T >( input . Count );
foreach ( T item in input )
{
string currentKey = keyGetter ( item );
if (( firstItem ) || ! currentKey . Equals ( previousKey , StringComparison . OrdinalIgnoreCase ))
{
output . Add ( item );
}
previousKey = currentKey ;
firstItem = false ;
}
input . Clear ();
input . AddRange ( output );
}
/// <summary>
/// Mark stuff to be exported from the current environment using the various patterns
/// </summary>
/// <param name="cmdlet">The cmdlet calling this method</param>
/// <param name="sessionState">The session state instance to do the exports on</param>
/// <param name="functionPatterns">Patterns describing the functions to export</param>
/// <param name="cmdletPatterns">Patterns describing the cmdlets to export</param>
/// <param name="aliasPatterns">Patterns describing the aliases to export</param>
/// <param name="variablePatterns">Patterns describing the variables to export</param>
2018-08-14 05:03:27 +05:00
/// <param name="doNotExportCmdlets">List of Cmdlets that will not be exported, even if they match in cmdletPatterns.</param>
internal static void ExportModuleMembers (
PSCmdlet cmdlet ,
SessionStateInternal sessionState ,
List < WildcardPattern > functionPatterns ,
List < WildcardPattern > cmdletPatterns ,
List < WildcardPattern > aliasPatterns ,
List < WildcardPattern > variablePatterns ,
List < string > doNotExportCmdlets )
2016-03-30 14:20:52 -07:00
{
// If this cmdlet is called, then mark that the export list should be used for exporting
// module members...
sessionState . UseExportList = true ;
if ( functionPatterns != null )
{
2018-11-13 16:16:29 -08:00
sessionState . FunctionsExported = true ;
if ( PatternContainsWildcard ( functionPatterns ))
{
sessionState . FunctionsExportedWithWildcard = true ;
}
2016-03-30 14:20:52 -07:00
IDictionary < string , FunctionInfo > ft = sessionState . ModuleScope . FunctionTable ;
foreach ( KeyValuePair < string , FunctionInfo > entry in ft )
{
// Skip AllScope functions
if (( entry . Value . Options & ScopedItemOptions . AllScope ) != 0 )
{
continue ;
}
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( entry . Key , functionPatterns , false ))
{
2018-04-27 23:02:15 -07:00
sessionState . ExportedFunctions . Add ( entry . Value );
string message = StringUtil . Format ( Modules . ExportingFunction , entry . Key );
2016-03-30 14:20:52 -07:00
cmdlet . WriteVerbose ( message );
}
}
2016-07-29 13:02:49 -07:00
SortAndRemoveDuplicates ( sessionState . ExportedFunctions , delegate ( FunctionInfo ci ) { return ci . Name ; });
2016-03-30 14:20:52 -07:00
}
if ( cmdletPatterns != null )
{
IDictionary < string , List < CmdletInfo >> ft = sessionState . ModuleScope . CmdletTable ;
// Subset the existing cmdlet exports if there are any. This will be the case
// if we're using ModuleToProcess to import a binary module which has nested modules.
if ( sessionState . Module . CompiledExports . Count > 0 )
{
CmdletInfo [] copy = sessionState . Module . CompiledExports . ToArray ();
sessionState . Module . CompiledExports . Clear ();
foreach ( CmdletInfo element in copy )
{
if ( doNotExportCmdlets == null
|| ! doNotExportCmdlets . Exists ( cmdletName => string . Equals ( element . FullName , cmdletName , StringComparison . OrdinalIgnoreCase )))
{
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( element . Name , cmdletPatterns , false ))
{
string message = StringUtil . Format ( Modules . ExportingCmdlet , element . Name );
cmdlet . WriteVerbose ( message );
// Copy the cmdlet info, changing the module association to be the current module...
CmdletInfo exportedCmdlet = new CmdletInfo ( element . Name , element . ImplementingType ,
2016-07-29 13:02:49 -07:00
element . HelpFile , null , element . Context )
{ Module = sessionState . Module };
2016-03-30 14:20:52 -07:00
Dbg . Assert ( sessionState . Module != null , "sessionState.Module should not be null by the time we're exporting cmdlets" );
sessionState . Module . CompiledExports . Add ( exportedCmdlet );
}
}
}
}
// And copy in any cmdlets imported from the nested modules...
foreach ( KeyValuePair < string , List < CmdletInfo >> entry in ft )
{
CmdletInfo cmdletToImport = entry . Value [ 0 ];
if ( doNotExportCmdlets == null
|| ! doNotExportCmdlets . Exists ( cmdletName => string . Equals ( cmdletToImport . FullName , cmdletName , StringComparison . OrdinalIgnoreCase )))
{
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( entry . Key , cmdletPatterns , false ))
{
string message = StringUtil . Format ( Modules . ExportingCmdlet , entry . Key );
cmdlet . WriteVerbose ( message );
// Copy the cmdlet info, changing the module association to be the current module...
CmdletInfo exportedCmdlet = new CmdletInfo ( cmdletToImport . Name , cmdletToImport . ImplementingType ,
2016-07-29 13:02:49 -07:00
cmdletToImport . HelpFile , null , cmdletToImport . Context )
{ Module = sessionState . Module };
2016-03-30 14:20:52 -07:00
Dbg . Assert ( sessionState . Module != null , "sessionState.Module should not be null by the time we're exporting cmdlets" );
sessionState . Module . CompiledExports . Add ( exportedCmdlet );
}
}
}
2016-07-29 13:02:49 -07:00
SortAndRemoveDuplicates ( sessionState . Module . CompiledExports , delegate ( CmdletInfo ci ) { return ci . Name ; });
2016-03-30 14:20:52 -07:00
}
if ( variablePatterns != null )
{
IDictionary < string , PSVariable > vt = sessionState . ModuleScope . Variables ;
foreach ( KeyValuePair < string , PSVariable > entry in vt )
{
// The magic variables are always private as are all-scope variables...
if ( entry . Value . IsAllScope || Array . IndexOf ( PSModuleInfo . _builtinVariables , entry . Key ) != - 1 )
{
continue ;
}
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( entry . Key , variablePatterns , false ))
{
string message = StringUtil . Format ( Modules . ExportingVariable , entry . Key );
cmdlet . WriteVerbose ( message );
sessionState . ExportedVariables . Add ( entry . Value );
}
}
2016-07-29 13:02:49 -07:00
SortAndRemoveDuplicates ( sessionState . ExportedVariables , delegate ( PSVariable v ) { return v . Name ; });
2016-03-30 14:20:52 -07:00
}
if ( aliasPatterns != null )
{
IEnumerable < AliasInfo > mai = sessionState . ModuleScope . AliasTable ;
// Subset the existing alias exports if there are any. This will be the case
// if we're using ModuleToProcess to import a binary module which has nested modules.
if ( sessionState . Module . CompiledAliasExports . Count > 0 )
{
AliasInfo [] copy = sessionState . Module . CompiledAliasExports . ToArray ();
foreach ( var element in copy )
{
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( element . Name , aliasPatterns , false ))
{
string message = StringUtil . Format ( Modules . ExportingAlias , element . Name );
cmdlet . WriteVerbose ( message );
sessionState . ExportedAliases . Add ( NewAliasInfo ( element , sessionState ));
}
}
}
foreach ( AliasInfo entry in mai )
{
// Skip allscope items...
if (( entry . Options & ScopedItemOptions . AllScope ) != 0 )
{
continue ;
}
if ( SessionStateUtilities . MatchesAnyWildcardPattern ( entry . Name , aliasPatterns , false ))
{
string message = StringUtil . Format ( Modules . ExportingAlias , entry . Name );
cmdlet . WriteVerbose ( message );
sessionState . ExportedAliases . Add ( NewAliasInfo ( entry , sessionState ));
}
}
2016-07-29 13:02:49 -07:00
SortAndRemoveDuplicates ( sessionState . ExportedAliases , delegate ( AliasInfo ci ) { return ci . Name ; });
2016-03-30 14:20:52 -07:00
}
}
2018-11-13 16:16:29 -08:00
/// <summary>
/// Checks pattern list for wildcard characters.
/// </summary>
/// <param name="list">Pattern list</param>
/// <returns>True if pattern contains '*'</returns>
internal static bool PatternContainsWildcard ( List < WildcardPattern > list )
{
if ( list != null )
{
foreach ( var item in list )
{
if ( WildcardPattern . ContainsWildcardCharacters ( item . Pattern ))
{
return true ;
}
}
}
return false ;
}
2016-08-02 19:00:50 -07:00
private static AliasInfo NewAliasInfo ( AliasInfo alias , SessionStateInternal sessionState )
2016-03-30 14:20:52 -07:00
{
Dbg . Assert ( alias != null , "alias should not be null" );
Dbg . Assert ( sessionState != null , "sessionState should not be null" );
Dbg . Assert ( sessionState . Module != null , "sessionState.Module should not be null by the time we're exporting aliases" );
// Copy the alias info, changing the module association to be the current module...
var aliasCopy = new AliasInfo ( alias . Name , alias . Definition , alias . Context , alias . Options )
{
Module = sessionState . Module
};
return aliasCopy ;
}
} // ModuleIntrinsics
2018-11-01 13:41:51 -07:00
/// <summary>
/// Enumeration of reasons for a failure to match a module by constraints.
/// </summary>
internal enum ModuleMatchFailure
{
/// <summary>Match did not fail.</summary>
None ,
/// <summary>Match failed because the module was null.</summary>
NullModule ,
/// <summary>Module name did not match.</summary>
Name ,
/// <summary>Module GUID did not match.</summary>
Guid ,
/// <summary>Module version did not match the required version.</summary>
RequiredVersion ,
/// <summary>Module version was lower than the minimum version.</summary>
MinimumVersion ,
/// <summary>Module version was greater than the maximum version.</summary>
MaximumVersion ,
/// <summary>The module specifcation passed in was null.</summary>
NullModuleSpecification ,
}
2016-03-30 14:20:52 -07:00
/// <summary>
/// Used by Modules/Snapins to provide a hook to the engine for startup initialization
/// w.r.t compiled assembly loading.
/// </summary>
public interface IModuleAssemblyInitializer
{
/// <summary>
/// Gets called when assembly is loaded.
/// </summary>
void OnImport ();
}
/// <summary>
2016-08-26 16:46:03 -04:00
/// Used by modules to provide a hook to the engine for cleanup on removal
2016-03-30 14:20:52 -07:00
/// w.r.t. compiled assembly being removed.
/// </summary>
public interface IModuleAssemblyCleanup
{
/// <summary>
/// Gets called when the binary module is unloaded.
/// </summary>
void OnRemove ( PSModuleInfo psModuleInfo );
}
} // System.Management.Automation