1010using System . Management . Automation . Language ;
1111using System . Text ;
1212using System . Threading ;
13-
1413using Microsoft . PowerShell . Commands ;
15- using Microsoft . Win32 ;
1614
1715using Dbg = System . Management . Automation . Diagnostics ;
1816
@@ -39,18 +37,22 @@ public class ModuleIntrinsics
3937 private static readonly string s_windowsPowerShellPSHomeModulePath =
4038 Path . Combine ( System . Environment . SystemDirectory , "WindowsPowerShell" , "v1.0" , "Modules" ) ;
4139
40+ static ModuleIntrinsics ( )
41+ {
42+ // Initialize the module path.
43+ SetModulePath ( ) ;
44+ }
45+
4246 internal ModuleIntrinsics ( ExecutionContext context )
4347 {
4448 _context = context ;
45-
46- // And initialize the module path...
47- SetModulePath ( ) ;
49+ ModuleTable = new Dictionary < string , PSModuleInfo > ( StringComparer . OrdinalIgnoreCase ) ;
4850 }
4951
5052 private readonly ExecutionContext _context ;
5153
5254 // Holds the module collection...
53- internal Dictionary < string , PSModuleInfo > ModuleTable { get ; } = new Dictionary < string , PSModuleInfo > ( StringComparer . OrdinalIgnoreCase ) ;
55+ internal Dictionary < string , PSModuleInfo > ModuleTable { get ; }
5456
5557 private const int MaxModuleNestingDepth = 10 ;
5658
@@ -176,11 +178,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object
176178
177179 sb . SessionState = ss ;
178180 }
179- else
181+ else if ( moduleCode is string sbText )
180182 {
181- var sbText = moduleCode as string ;
182- if ( sbText != null )
183- sb = ScriptBlock . Create ( _context , sbText ) ;
183+ sb = ScriptBlock . Create ( _context , sbText ) ;
184184 }
185185 }
186186
@@ -1082,91 +1082,80 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa
10821082 return result ;
10831083 }
10841084
1085- /// <summary>
1086- /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%)
1087- /// </summary>
1088- /// <param name="pathToScan">'Combined path' string to analyze; can not be null.</param>
1089- /// <param name="pathToLookFor">Path to search for; can not be another 'combined path' (semicolon-separated); can not be null.</param>
1090- /// <returns>Index of pathToLookFor in pathToScan; -1 if not found.</returns>
1091- private static int PathContainsSubstring ( string pathToScan , string pathToLookFor )
1092- {
1093- // we don't support if any of the args are null - parent function should ensure this; empty values are ok
1094- Diagnostics . Assert ( pathToScan != null , "pathToScan should not be null according to contract of the function" ) ;
1095- Diagnostics . Assert ( pathToLookFor != null , "pathToLookFor should not be null according to contract of the function" ) ;
1096-
1097- int pos = 0 ; // position of the current substring in pathToScan
1098- string [ ] substrings = pathToScan . Split ( Path . PathSeparator , StringSplitOptions . None ) ; // we want to process empty entries
1099- string goodPathToLookFor = pathToLookFor . Trim ( ) . TrimEnd ( Path . DirectorySeparatorChar ) ; // trailing backslashes and white-spaces will mess up equality comparison
1100- foreach ( string substring in substrings )
1101- {
1102- string goodSubstring = substring . Trim ( ) . TrimEnd ( Path . DirectorySeparatorChar ) ; // trailing backslashes and white-spaces will mess up equality comparison
1103-
1104- // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains')
1105- // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" }
1106-
1107- if ( string . Equals ( goodSubstring , goodPathToLookFor , StringComparison . OrdinalIgnoreCase ) )
1108- {
1109- return pos ; // match found - return index of it in the 'pathToScan' string
1110- }
1111- else
1112- {
1113- pos += substring . Length + 1 ; // '1' is for trailing semicolon
1114- }
1115- }
1116- // if we are here, that means a match was not found
1117- return - 1 ;
1118- }
1119-
11201085 /// <summary>
11211086 /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there.
11221087 /// </summary>
11231088 /// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param>
1124- /// <param name="pathToAdd">Collection of individual paths to add.</param>
1089+ /// <param name="pathToAdd">An individual path to add, or multiple paths separated by the path separator character .</param>
11251090 /// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param>
11261091 /// <returns>Result string.</returns>
1127- private static string AddToPath ( string basePath , string pathToAdd , int insertPosition )
1092+ private static string UpdatePath ( string basePath , string pathToAdd , ref int insertPosition )
11281093 {
11291094 // we don't support if any of the args are null - parent function should ensure this; empty values are ok
1130- Diagnostics . Assert ( basePath != null , "basePath should not be null according to contract of the function" ) ;
1131- Diagnostics . Assert ( pathToAdd != null , "pathToAdd should not be null according to contract of the function" ) ;
1095+ Dbg . Assert ( basePath != null , "basePath should not be null according to contract of the function" ) ;
1096+ Dbg . Assert ( pathToAdd != null , "pathToAdd should not be null according to contract of the function" ) ;
1097+
1098+ // The 'pathToAdd' could be a 'combined path' (path-separator-separated).
1099+ string [ ] newPaths = pathToAdd . Split (
1100+ Path . PathSeparator ,
1101+ StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
11321102
1133- StringBuilder result = new StringBuilder ( basePath ) ;
1103+ if ( newPaths . Length is 0 )
1104+ {
1105+ // The 'pathToAdd' doesn't really contain any paths to add.
1106+ return basePath ;
1107+ }
1108+
1109+ var result = new StringBuilder ( basePath , capacity : basePath . Length + pathToAdd . Length + newPaths . Length ) ;
1110+ var addedPaths = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
1111+ string [ ] initialPaths = basePath . Split (
1112+ Path . PathSeparator ,
1113+ StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
1114+
1115+ foreach ( string p in initialPaths )
1116+ {
1117+ // Remove the trailing directory separators.
1118+ // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'.
1119+ addedPaths . Add ( Path . TrimEndingDirectorySeparator ( p ) ) ;
1120+ }
11341121
1135- if ( ! string . IsNullOrEmpty ( pathToAdd ) ) // we don't want to append empty paths
1122+ foreach ( string subPathToAdd in newPaths )
11361123 {
1137- foreach ( string subPathToAdd in pathToAdd . Split ( Path . PathSeparator , StringSplitOptions . RemoveEmptyEntries ) ) // in case pathToAdd is a 'combined path' (semicolon-separated)
1124+ // Remove the trailing directory separators.
1125+ // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'.
1126+ string normalizedPath = Path . TrimEndingDirectorySeparator ( subPathToAdd ) ;
1127+ if ( addedPaths . Contains ( normalizedPath ) )
11381128 {
1139- int position = PathContainsSubstring ( result . ToString ( ) , subPathToAdd ) ; // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly
1140- if ( position == - 1 ) // subPathToAdd not found - add it
1141- {
1142- if ( insertPosition == - 1 || insertPosition > basePath . Length ) // append subPathToAdd to the end
1143- {
1144- bool endsWithPathSeparator = false ;
1145- if ( result . Length > 0 )
1146- {
1147- endsWithPathSeparator = ( result [ result . Length - 1 ] == Path . PathSeparator ) ;
1148- }
1129+ // The normalized sub path was already added - skip it.
1130+ continue ;
1131+ }
11491132
1150- if ( endsWithPathSeparator )
1151- {
1152- result . Append ( subPathToAdd ) ;
1153- }
1154- else
1155- {
1156- result . Append ( Path . PathSeparator + subPathToAdd ) ;
1157- }
1158- }
1159- else if ( insertPosition > result . Length )
1160- {
1161- // handle case where path is a singleton with no path separator already
1162- result . Append ( Path . PathSeparator ) . Append ( subPathToAdd ) ;
1163- }
1164- else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User))
1165- {
1166- result . Insert ( insertPosition , subPathToAdd + Path . PathSeparator ) ;
1167- }
1133+ // The normalized sub path was not found - add it.
1134+ if ( insertPosition is - 1 || insertPosition >= result . Length )
1135+ {
1136+ // Append the normalized sub path to the end.
1137+ if ( result . Length > 0 && result [ ^ 1 ] != Path . PathSeparator )
1138+ {
1139+ result . Append ( Path . PathSeparator ) ;
11681140 }
1141+
1142+ result . Append ( normalizedPath ) ;
1143+ // Next insertion should happen at the end.
1144+ insertPosition = result . Length ;
1145+ }
1146+ else
1147+ {
1148+ // Insert at the requested location.
1149+ // This is used by the user-specific module path, the shared module path (<Program Files> location), and the PSHome module path.
1150+ string strToInsert = normalizedPath + Path . PathSeparator ;
1151+ result . Insert ( insertPosition , strToInsert ) ;
1152+
1153+ // Next insertion should happen after the just inserted string.
1154+ insertPosition += strToInsert . Length ;
11691155 }
1156+
1157+ // Add it to the set.
1158+ addedPaths . Add ( normalizedPath ) ;
11701159 }
11711160
11721161 return result . ToString ( ) ;
@@ -1218,7 +1207,7 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM
12181207 string psHomeModulePath = GetPSHomeModulePath ( ) ; // $PSHome\Modules location
12191208
12201209 // If the variable isn't set, then set it to the default value
1221- if ( currentProcessModulePath == null ) // EVT.Process does Not exist - really corner case
1210+ if ( string . IsNullOrEmpty ( currentProcessModulePath ) ) // EVT.Process does Not exist - really corner case
12221211 {
12231212 // Handle the default case...
12241213 if ( string . IsNullOrEmpty ( hkcuUserModulePath ) ) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location
@@ -1270,21 +1259,6 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM
12701259 return currentProcessModulePath ;
12711260 }
12721261
1273- private static string UpdatePath ( string path , string pathToAdd , ref int insertIndex )
1274- {
1275- if ( ! string . IsNullOrEmpty ( pathToAdd ) )
1276- {
1277- path = AddToPath ( path , pathToAdd , insertIndex ) ;
1278- insertIndex = path . IndexOf ( Path . PathSeparator , PathContainsSubstring ( path , pathToAdd ) ) ;
1279- if ( insertIndex != - 1 )
1280- {
1281- // advance past the path separator
1282- insertIndex ++ ;
1283- }
1284- }
1285- return path ;
1286- }
1287-
12881262 /// <summary>
12891263 /// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these
12901264 /// strings go through the provider, we need to escape any wildcards before passing them
@@ -1358,11 +1332,16 @@ private static string SetModulePath()
13581332 {
13591333 string currentModulePath = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Process ) ;
13601334#if ! UNIX
1361- // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete
1362- // otherwise , the user modified it and we should use the process one
1335+ // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete.
1336+ // Otherwise , the user modified it and we should use the process one.
13631337 if ( string . CompareOrdinal ( GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . User ) , currentModulePath ) == 0 )
13641338 {
1365- currentModulePath = currentModulePath + Path . PathSeparator + GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Machine ) ;
1339+ string machineScopeValue = GetExpandedEnvironmentVariable ( Constants . PSModulePathEnvVar , EnvironmentVariableTarget . Machine ) ;
1340+ currentModulePath = string . IsNullOrEmpty ( currentModulePath )
1341+ ? machineScopeValue
1342+ : string . IsNullOrEmpty ( machineScopeValue )
1343+ ? currentModulePath
1344+ : string . Concat ( currentModulePath , Path . PathSeparator , machineScopeValue ) ;
13661345 }
13671346#endif
13681347 string allUsersModulePath = PowerShellConfig . Instance . GetModulePath ( ConfigScope . AllUsers ) ;
0 commit comments