forked from PowerShell/PowerShell
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModuleIntrinsics.cs
More file actions
1239 lines (1106 loc) · 59.9 KB
/
Copy pathModuleIntrinsics.cs
File metadata and controls
1239 lines (1106 loc) · 59.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections.Generic;
using System.IO;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell.Commands;
using System.Linq;
using System.Threading;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
/// <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");
internal ModuleIntrinsics(ExecutionContext context)
{
_context = context;
// And initialize the module path...
SetModulePath();
}
private readonly ExecutionContext _context;
// Holds the module collection...
internal Dictionary<string, PSModuleInfo> ModuleTable
{
get
{
return _moduleTable;
}
}
private readonly Dictionary<string, PSModuleInfo> _moduleTable = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase);
const int MaxModuleNestingDepth = 10;
internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path)
{
if (++_moduleNestingDepth > MaxModuleNestingDepth)
{
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()
{
--_moduleNestingDepth;
}
internal int ModuleNestingDepth
{
get { return _moduleNestingDepth; }
}
int _moduleNestingDepth;
/// <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">
/// The code to use to create the module. This can be one of ScriptBlock, string
/// 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;
InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptPosition);
// Save the module string
module._definitionExtent = sb.Ast.Extent;
var ast = sb.Ast;
while (ast.Parent != null)
{
ast = ast.Parent;
}
// The variables set in the interpretted case get set by InvokeWithPipe in the compiled case.
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(
useLocalScope: false,
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
dollarUnder: AutomationNull.Value,
input: AutomationNull.Value,
scriptThis: AutomationNull.Value,
outputPipe: outputPipe,
invocationInfo: invocationInfo,
args: arguments ?? Utils.EmptyArray<object>());
}
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; }
return GetModuleCore(new string[] {moduleName}, all, exactMatch);
}
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...
if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||
(!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();
}
internal static bool IsModuleMatchingModuleSpec(PSModuleInfo moduleInfo, ModuleSpecification moduleSpec)
{
if (moduleInfo != null && moduleSpec != null &&
moduleInfo.Name.Equals(moduleSpec.Name, StringComparison.OrdinalIgnoreCase) &&
(!moduleSpec.Guid.HasValue || moduleSpec.Guid.Equals(moduleInfo.Guid)) &&
((moduleSpec.Version == null && moduleSpec.RequiredVersion == null && moduleSpec.MaximumVersion == null)
|| (moduleSpec.RequiredVersion != null && moduleSpec.RequiredVersion.Equals(moduleInfo.Version))
|| (moduleSpec.MaximumVersion == null && moduleSpec.Version != null && moduleSpec.RequiredVersion == null && moduleSpec.Version <= moduleInfo.Version)
|| (moduleSpec.MaximumVersion != null && moduleSpec.Version == null && moduleSpec.RequiredVersion == null && ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion) >= moduleInfo.Version)
|| (moduleSpec.MaximumVersion != null && moduleSpec.Version != null && moduleSpec.RequiredVersion == null && ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion) >= moduleInfo.Version && moduleSpec.Version <= moduleInfo.Version)))
{
return true;
}
return false;
}
internal static Version GetManifestModuleVersion(string manifestPath)
{
if (manifestPath != null &&
manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
{
try
{
var dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestModuleVersionPropertyName);
var versionValue = dataFileSetting["ModuleVersion"];
if (versionValue != null)
{
Version moduleVersion;
if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion))
{
return moduleVersion;
}
}
}
catch (PSInvalidOperationException)
{
}
}
return new Version(0, 0);
}
internal static Guid GetManifestGuid(string manifestPath)
{
if (manifestPath != null &&
manifestPath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))
{
try
{
var dataFileSetting =
PsUtils.GetModuleManifestProperties(
manifestPath,
PsUtils.ManifestGuidPropertyName);
var guidValue = dataFileSetting["GUID"];
if (guidValue != null)
{
Guid guidID;
if (LanguagePrimitives.TryConvertTo(guidValue, out guidID))
{
return guidID;
}
}
}
catch (PSInvalidOperationException)
{
}
}
return new Guid();
}
// 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.
internal static string[] PSModuleProcessableExtensions = new string[] {
StringLiterals.PowerShellDataFileExtension,
StringLiterals.PowerShellScriptFileExtension,
StringLiterals.PowerShellModuleFileExtension,
StringLiterals.PowerShellCmdletizationFileExtension,
StringLiterals.WorkflowFileExtension,
StringLiterals.PowerShellNgenAssemblyExtension,
StringLiterals.DependentWorkflowAssemblyExtension};
// 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.
internal static string[] PSModuleExtensions = new string[] {
StringLiterals.PowerShellDataFileExtension,
StringLiterals.PowerShellModuleFileExtension,
StringLiterals.PowerShellCmdletizationFileExtension,
StringLiterals.WorkflowFileExtension,
StringLiterals.PowerShellNgenAssemblyExtension,
StringLiterals.DependentWorkflowAssemblyExtension};
/// <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;
}
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);
string ext;
if (fileName.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase))
{
ext = StringLiterals.PowerShellNgenAssemblyExtension;
}
else
{
ext = Path.GetExtension(fileName);
}
if (!string.IsNullOrEmpty(ext) && IsPowerShellModuleExtension(ext))
{
return fileName.Substring(0, fileName.Length - ext.Length);
}
else
{
return fileName;
}
}
/// <summary>
/// Gets the personal module path (i.e. C:\Users\lukasza\Documents\WindowsPowerShell\modules)
/// </summary>
/// <returns>personal module path</returns>
internal static string GetPersonalModulePath()
{
string personalModuleRoot = Path.Combine(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
Utils.ProductNameForDirectory),
Utils.ModuleDirectory);
return personalModuleRoot;
}
/// <summary>
/// Gets the default system-wide module path.
/// </summary>
/// <returns>The default system wide module path</returns>
internal static string GetSystemwideModulePath()
{
if (SystemWideModulePath != null)
return SystemWideModulePath;
// There is no runspace config so we use the default string
string shellId = Utils.DefaultPowerShellShellID;
// Now figure out what $PSHOME is.
// This depends on the shellId. If we cannot read the application base
// registry key, set the variable to empty string
string psHome = null;
try
{
psHome = Utils.GetApplicationBase(shellId);
}
catch (System.Security.SecurityException)
{
}
if (!string.IsNullOrEmpty(psHome))
{
// Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed
// This happens because ModuleTable uses Path as the key and CBS installer
// expands the path to include "SysWOW64" (for
// HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase).
// Because of this, the module that is getting loaded during startup (through LocalRunspace)
// is using "SysWow64" in the key. Later, when Import-Module is called, it loads the
// module using ""System32" in the key.
psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\");
Interlocked.CompareExchange(ref SystemWideModulePath, Path.Combine(psHome, Utils.ModuleDirectory), null);
}
return SystemWideModulePath;
}
private static string SystemWideModulePath;
/// <summary>
/// Get the DSC module path.
/// </summary>
/// <returns></returns>
internal static string GetDscModulePath()
{
string dscModulePath = null;
string programFilesPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (!string.IsNullOrEmpty(programFilesPath))
{
dscModulePath = Path.Combine(programFilesPath, Utils.DscModuleDirectory);
}
return dscModulePath;
}
/// <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()
{
string psSystemModulePath = GetSystemwideModulePath();
string dscSystemModulePath = GetDscModulePath();
if (psSystemModulePath != null && dscSystemModulePath != null)
{
return (dscSystemModulePath + ";" + psSystemModulePath);
}
if (psSystemModulePath != null || dscSystemModulePath != null)
{
return (psSystemModulePath ?? dscSystemModulePath);
}
return null;
}
private static string GetExpandedEnvironmentVariable(string name, EnvironmentVariableTarget target)
{
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
string[] substrings = pathToScan.Split(new char[] { ';' }, StringSplitOptions.None); // we want to process empty entries
string goodPathToLookFor = pathToLookFor.Trim().TrimEnd('\\'); // trailing backslashes and white-spaces will mess up equality comparison
foreach (string substring in substrings)
{
string goodSubstring = substring.Trim().TrimEnd('\\'); // trailing backslashes and white-spaces will mess up equality comparison
// 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");
System.Text.StringBuilder result = new System.Text.StringBuilder(basePath);
char[] semicolonSeparator = new char[] { ';' };
if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths
{
foreach (string subPathToAdd in pathToAdd.Split(semicolonSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated)
{
int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly
if (-1 == position) // subPathToAdd not found - add it
{
if (-1 == insertPosition) // append subPathToAdd to the end
{
bool resultHasEndingSemicolon = false;
if (result.Length > 0) resultHasEndingSemicolon = (result[result.Length - 1] == ';');
if (resultHasEndingSemicolon)
result.Append(subPathToAdd);
else
result.Append(";" + subPathToAdd);
}
else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User))
{
result.Insert(insertPosition, subPathToAdd + ";");
}
}
}
}
return result.ToString();
}
/// <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)
{
string programFilesModulePath = GetDscModulePath(); // aka <Program Files> location
string psHomeModulePath = Environment.ExpandEnvironmentVariables(GetSystemwideModulePath()); // $PSHome\Modules location
// 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...
if (hkcuUserModulePath == null) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location
{
currentProcessModulePath = GetPersonalModulePath(); // = SpecialFolder.MyDocuments + Utils.ProductNameForDirectory + Utils.ModuleDirectory
}
else // EVT.User exists -> set to EVT.User
{
currentProcessModulePath = hkcuUserModulePath; // = EVT.User
}
currentProcessModulePath += ';';
if (hklmMachineModulePath == null) // EVT.Machine does Not exist
{
currentProcessModulePath += CombineSystemModulePaths(); // += (DscModulePath + $PSHome\Modules)
}
else
{
currentProcessModulePath += hklmMachineModulePath; // += EVT.Machine
}
}
else // EVT.Process exists
{
// Now handle the case where the environment variable is already set.
// If there is no personal path key, then if the env variable doesn't match the system variable,
// the user modified it somewhere, else prepend the default personel module path
if (hklmMachineModulePath != null) // EVT.Machine exists
{
if (hkcuUserModulePath == null) // EVT.User does Not exist
{
if (!(hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))
{
// before returning, use <presence of Windows module path> heuristic to conditionally add programFilesModulePath
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
#if !CORECLR
// for bug 6678623, if we are running wow64 process (x86 32-bit process on 64-bit (amd64) OS), then ensure that <SpecialFolder.MyDocuments> exists in currentProcessModulePath / return value
if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
{
string userModulePath = GetPersonalModulePath();
currentProcessModulePath = AddToPath(currentProcessModulePath, userModulePath, psHomePosition);
psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath);
}
#endif
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
}
currentProcessModulePath = GetPersonalModulePath() + ';' + hklmMachineModulePath; // <SpecialFolder.MyDocuments> + EVT.Machine + inserted <ProgramFiles> later in this function
}
else // EVT.User exists
{
// PSModulePath is designed to have behaviour like 'Path' var in a sense that EVT.User + EVT.Machine are merged to get final value of PSModulePath
string combined = string.Concat(hkcuUserModulePath, ';', hklmMachineModulePath); // EVT.User + EVT.Machine
if (!((combined).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) ||
(hklmMachineModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase) ||
(hkcuUserModulePath).Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase)))
{
// before returning, use <presence of Windows module path> heuristic to conditionally add programFilesModulePath
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
}
currentProcessModulePath = combined; // = EVT.User + EVT.Machine + inserted <ProgramFiles> later in this function
}
}
else // EVT.Machine does Not exist
{
// If there is no system path key, then if the env variable doesn't match the user variable,
// the user modified it somewhere, otherwise append the default system path
if (hkcuUserModulePath != null) // EVT.User exists
{
if (hkcuUserModulePath.Equals(currentProcessModulePath, StringComparison.OrdinalIgnoreCase))
{
currentProcessModulePath = hkcuUserModulePath + ';' + CombineSystemModulePaths(); // = EVT.User + (DscModulePath + $PSHome\Modules)
}
else
{
// before returning, use <presence of Windows module path> heuristic to conditionally add programFilesModulePath
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
return null;
}
}
else // EVT.User does Not exist
{
// before returning, use <presence of Windows module path> heuristic to conditionally add programFilesModulePath
int psHomePosition = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
if (psHomePosition >= 0) // if $PSHome\Modules IS found - insert <Program Files> location before $PSHome\Modules
{
return AddToPath(currentProcessModulePath, programFilesModulePath, psHomePosition);
} // if $PSHome\Modules NOT found = <scenario 4> = 'PSModulePath has been constrained by a user to create a sand boxed environment without including System Modules'
// Neither key is set so go with what the environment variable is already set to
return null;
}
}
}
// if we reached this point - always add <Program Files> location to EVT.Process
// everything below is the same behaviour as WMF 4 code
int indexOfPSHomeModulePath = PathContainsSubstring(currentProcessModulePath, psHomeModulePath); // index of $PSHome\Modules in currentProcessModulePath
// 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
currentProcessModulePath = AddToPath(currentProcessModulePath, programFilesModulePath, indexOfPSHomeModulePath);
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()
{
string currentModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Process);
return currentModulePath;
}
/// <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 SetModulePath()
{
string currentModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Process);
string systemWideModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.Machine);
string personalModulePath = GetExpandedEnvironmentVariable("PSMODULEPATH", EnvironmentVariableTarget.User);
string newModulePathString = GetModulePath(currentModulePath, systemWideModulePath, personalModulePath);
if(!string.IsNullOrEmpty(newModulePathString))
{
// Set the environment variable...
Environment.SetEnvironmentVariable("PSMODULEPATH", newModulePathString);
}
return newModulePathString;
}
/// <summary>
/// Get the current module path setting.
/// </summary>
/// <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>
/// <returns>The module path as an array of strings</returns>
internal static IEnumerable<string> GetModulePath(bool includeSystemModulePath, ExecutionContext context)
{
string modulePathString = Environment.GetEnvironmentVariable("PSMODULEPATH") ?? SetModulePath();
HashSet<string> processedPathSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!string.IsNullOrWhiteSpace(modulePathString))
{
foreach (string envPath in modulePathString.Split(Utils.Separators.Semicolon, StringSplitOptions.RemoveEmptyEntries))
{
var processedPath = ProcessOneModulePath(context, envPath, processedPathSet);
if (processedPath != null)
yield return processedPath;
}
}
if (includeSystemModulePath)
{
var processedPath = ProcessOneModulePath(context, GetSystemwideModulePath(), processedPathSet);
if (processedPath != null)
yield return processedPath;
}
}
static private string ProcessOneModulePath(ExecutionContext context, string envPath, HashSet<string> processedPathSet)
{
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
// 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")
}
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;
}
static private void SortAndRemoveDuplicates<T>(List<T> input, Func<T, string> keyGetter)
{
Dbg.Assert(input != null, "Caller should verify that input != null");