Skip to content

Commit 888139b

Browse files
authored
Take module version into account for -SaveDscDependency switch (#1094)
* Take module version into account for SaveDscDependency and remove old dead code * fix tests and add test case
1 parent 1f855ac commit 888139b

File tree

3 files changed

+83
-85
lines changed

3 files changed

+83
-85
lines changed

Engine/Generic/ModuleDependencyHandler.cs

Lines changed: 62 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -212,21 +212,6 @@ private string GetTempModulePath(string symLinkPath)
212212
return line;
213213
}
214214

215-
private void SaveModule(PSObject module)
216-
{
217-
ThrowIfNull(module, "module");
218-
219-
// TODO validate module
220-
using (var ps = System.Management.Automation.PowerShell.Create())
221-
{
222-
ps.Runspace = runspace;
223-
ps.AddCommand("Save-Module")
224-
.AddParameter("Path", tempModulePath)
225-
.AddParameter("InputObject", module);
226-
ps.Invoke();
227-
}
228-
}
229-
230215
private void SetupPSModulePath()
231216
{
232217
oldPSModulePath = Environment.GetEnvironmentVariable("PSModulePath");
@@ -295,51 +280,17 @@ public ModuleDependencyHandler(
295280

296281
}
297282

298-
/// <summary>
299-
/// Encapsulates Find-Module
300-
/// </summary>
301-
/// <param name="moduleName">Name of the module</param>
302-
/// <returns>A PSObject if it finds the modules otherwise returns null</returns>
303-
public PSObject FindModule(string moduleName)
304-
{
305-
ThrowIfNull(moduleName, "moduleName");
306-
moduleName = moduleName.ToLower();
307-
if (modulesFound.ContainsKey(moduleName))
308-
{
309-
return modulesFound[moduleName];
310-
}
311-
Collection<PSObject> modules = null;
312-
using (var ps = System.Management.Automation.PowerShell.Create())
313-
{
314-
ps.Runspace = runspace;
315-
ps.AddCommand("Find-Module", true)
316-
.AddParameter("Name", moduleName)
317-
.AddParameter("Repository", moduleRepository);
318-
modules = ps.Invoke<PSObject>();
319-
}
320-
if (modules == null)
321-
{
322-
return null;
323-
}
324-
var module = modules.FirstOrDefault();
325-
if (module == null )
326-
{
327-
return null;
328-
}
329-
modulesFound.Add(moduleName, module);
330-
return module;
331-
}
332-
333283
/// <summary>
334284
/// SaveModule version that doesn't throw
335285
/// </summary>
336286
/// <param name="moduleName">Name of the module</param>
287+
/// <param name="moduleVersion">(Optional) version of the module</param>
337288
/// <returns>True if it can save a module otherwise false.</returns>
338-
public bool TrySaveModule(string moduleName)
289+
public bool TrySaveModule(string moduleName, Version moduleVersion)
339290
{
340291
try
341292
{
342-
SaveModule(moduleName);
293+
SaveModule(moduleName, moduleVersion);
343294
return true;
344295
}
345296
catch
@@ -353,7 +304,8 @@ public bool TrySaveModule(string moduleName)
353304
/// Encapsulates Save-Module cmdlet
354305
/// </summary>
355306
/// <param name="moduleName">Name of the module</param>
356-
public void SaveModule(string moduleName)
307+
/// <param name="moduleVersion">(Optional) version of the module</param>
308+
public void SaveModule(string moduleName, Version moduleVersion)
357309
{
358310
ThrowIfNull(moduleName, "moduleName");
359311
if (IsModulePresentInTempModulePath(moduleName))
@@ -368,6 +320,10 @@ public void SaveModule(string moduleName)
368320
.AddParameter("Name", moduleName)
369321
.AddParameter("Repository", moduleRepository)
370322
.AddParameter("Force");
323+
if (moduleVersion != null)
324+
{
325+
ps.AddParameter("RequiredVersion", moduleVersion);
326+
}
371327
ps.Invoke();
372328
}
373329
}
@@ -376,18 +332,25 @@ public void SaveModule(string moduleName)
376332
/// Encapsulates Get-Module to check the availability of the module on the system
377333
/// </summary>
378334
/// <param name="moduleName"></param>
335+
/// <param name="moduleVersion"></param>
379336
/// <returns>True indicating the presence of the module, otherwise false</returns>
380-
public bool IsModuleAvailable(string moduleName)
337+
public bool IsModuleAvailable(string moduleName, Version moduleVersion)
381338
{
382339
ThrowIfNull(moduleName, "moduleName");
383340
IEnumerable<PSModuleInfo> availableModules;
384341
using (var ps = System.Management.Automation.PowerShell.Create())
385342
{
386343
ps.Runspace = runspace;
387-
availableModules = ps.AddCommand("Get-Module")
344+
ps.AddCommand("Get-Module")
388345
.AddParameter("Name", moduleName)
389-
.AddParameter("ListAvailable")
390-
.Invoke<PSModuleInfo>();
346+
.AddParameter("ListAvailable");
347+
if (moduleVersion != null)
348+
{
349+
ps.AddCommand("Where-Object")
350+
.AddParameter("Filterscript", ScriptBlock.Create($"$_.Version -eq '{moduleVersion}'"));
351+
}
352+
availableModules = ps.Invoke<PSModuleInfo>();
353+
391354
}
392355
return availableModules != null ? availableModules.Any() : false;
393356
}
@@ -405,25 +368,26 @@ public bool IsModuleAvailable(string moduleName)
405368
/// </summary>
406369
/// <param name="error"></param>
407370
/// <param name="ast"></param>
371+
/// <param name="moduleVersion"></param>
408372
/// <returns>An enumeration over the module names that are not available</returns>
409-
public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast)
373+
public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast, out Version moduleVersion)
410374
{
411375
ThrowIfNull(error, "error");
412376
ThrowIfNull(ast, "ast");
413-
var moduleNames = ModuleDependencyHandler.GetModuleNameFromErrorExtent(error, ast);
377+
var moduleNames = ModuleDependencyHandler.GetModuleNameFromErrorExtent(error, ast, out moduleVersion);
414378
if (moduleNames == null)
415379
{
416380
return null;
417381
}
418382
var unavailableModules = new List<string>();
419383
foreach (var moduleName in moduleNames)
420384
{
421-
if (!IsModuleAvailable(moduleName))
385+
if (!IsModuleAvailable(moduleName, moduleVersion))
422386
{
423387
unavailableModules.Add(moduleName);
424388
}
425389
}
426-
//return moduleNames.Where(x => !IsModuleAvailable(x));
390+
427391
return unavailableModules;
428392
}
429393

@@ -438,9 +402,11 @@ public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError er
438402
/// </summary>
439403
/// <param name="error">Parse error</param>
440404
/// <param name="ast">AST of the script that contians the parse error</param>
405+
/// <param name="moduleVersion">Specifc version of the required module</param>
441406
/// <returns>The name of the module that caused the parser to throw the error. Returns null if it cannot extract the module name.</returns>
442-
public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast)
407+
public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast, out Version moduleVersion)
443408
{
409+
moduleVersion = null;
444410
ThrowIfNull(error, "error");
445411
ThrowIfNull(ast, "ast");
446412
var statement = ast.Find(x => x.Extent.Equals(error.Extent), true);
@@ -452,46 +418,61 @@ public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error,
452418
// check if the command name is import-dscmodule
453419
// right now we handle only the following forms
454420
// 1. Import-DSCResourceModule -ModuleName somemodule
455-
// 2. Import-DSCResourceModule -ModuleName somemodule1,somemodule2
456-
if (dynamicKywdAst.CommandElements.Count < 3)
457-
{
458-
return null;
459-
}
460-
421+
// 2. Import-DSCResourceModule -ModuleName somemodule1 -ModuleVersion major.minor.patch.build
422+
// 3. Import-DSCResourceModule -ModuleName somemodule1,somemodule2
461423
var dscKeywordAst = dynamicKywdAst.CommandElements[0] as StringConstantExpressionAst;
462424
if (dscKeywordAst == null || !dscKeywordAst.Value.Equals("Import-DscResource", StringComparison.OrdinalIgnoreCase))
463425
{
464426
return null;
465427
}
466428

467429
// find a parameter named modulename
468-
int k;
469-
for (k = 1; k < dynamicKywdAst.CommandElements.Count; k++)
430+
int positionOfModuleNameParamter = 0;
431+
int positionOfModuleVersionParameter = 0;
432+
for (int i = 1; i < dynamicKywdAst.CommandElements.Count; i++)
470433
{
471-
var paramAst = dynamicKywdAst.CommandElements[k] as CommandParameterAst;
434+
var paramAst = dynamicKywdAst.CommandElements[i] as CommandParameterAst;
472435
// TODO match the initial letters only
473-
if (paramAst == null || !paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
436+
if (paramAst != null && paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
437+
{
438+
if (i == dynamicKywdAst.CommandElements.Count)
439+
{
440+
// command was Save-DscDependency ... -ModuleName -> module name missing
441+
return null;
442+
}
443+
positionOfModuleNameParamter = i + 1;
444+
continue;
445+
}
446+
447+
if (paramAst != null && paramAst.ParameterName.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase))
474448
{
449+
if (i == dynamicKywdAst.CommandElements.Count)
450+
{
451+
// command was Save-DscDependency ... -ModuleVersion -> module version missing
452+
return null;
453+
}
454+
positionOfModuleVersionParameter = i + 1;
475455
continue;
476456
}
477-
break;
478457
}
479458

480-
if (k == dynamicKywdAst.CommandElements.Count)
481-
{
482-
// cannot find modulename
483-
return null;
484-
}
485459
var modules = new List<string>();
486460

487-
// k < count - 1, because only -ModuleName throws parse error and hence not possible
488-
var paramValAst = dynamicKywdAst.CommandElements[++k];
461+
var paramValAst = dynamicKywdAst.CommandElements[positionOfModuleNameParamter];
489462

490463
// import-dscresource -ModuleName module1
491464
var paramValStrConstExprAst = paramValAst as StringConstantExpressionAst;
492465
if (paramValStrConstExprAst != null)
493466
{
494467
modules.Add(paramValStrConstExprAst.Value);
468+
469+
// import-dscresource -ModuleName module1 -ModuleVersion major.minor.patch.build
470+
var versionParameterAst = dynamicKywdAst.CommandElements[positionOfModuleVersionParameter] as StringConstantExpressionAst;
471+
if (versionParameterAst != null)
472+
{
473+
Version.TryParse(versionParameterAst.Value, out Version version); // ignore return value since a module version of null means no version
474+
moduleVersion = version;
475+
}
495476
return modules;
496477
}
497478

@@ -513,6 +494,7 @@ public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error,
513494
}
514495
return modules;
515496
}
497+
516498
return null;
517499
}
518500

Engine/ScriptAnalyzer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,7 +1804,7 @@ private bool TrySaveModules(ParseError[] errors, ScriptBlockAst scriptAst)
18041804
}
18051805
foreach (var error in errors.Where(IsModuleNotFoundError))
18061806
{
1807-
var moduleNames = moduleHandler.GetUnavailableModuleNameFromErrorExtent(error, scriptAst);
1807+
var moduleNames = moduleHandler.GetUnavailableModuleNameFromErrorExtent(error, scriptAst, out Version moduleVersion);
18081808
if (moduleNames == null)
18091809
{
18101810
continue;
@@ -1815,7 +1815,7 @@ private bool TrySaveModules(ParseError[] errors, ScriptBlockAst scriptAst)
18151815
String.Format(
18161816
"Saving module {0} from PSGallery",
18171817
moduleName));
1818-
var moduleSaved = moduleHandler.TrySaveModule(moduleName);
1818+
var moduleSaved = moduleHandler.TrySaveModule(moduleName, moduleVersion);
18191819
if (!moduleSaved)
18201820
{
18211821
this.outputWriter.WriteVerbose(

Tests/Engine/ModuleDependencyHandler.tests.ps1

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,26 @@ Describe "Resolve DSC Resource Dependency" {
8585
$tokens = $null
8686
$parseError = $null
8787
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
88-
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
88+
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
8989
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
9090
}
9191

92+
It "Extracts 1 module name with version" -skip:$skipTest {
93+
$sb = @"
94+
{Configuration SomeConfiguration
95+
{
96+
Import-DscResource -ModuleName SomeDscModule1 -ModuleVersion 1.2.3.4
97+
}}
98+
"@
99+
$tokens = $null
100+
$parseError = $null
101+
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
102+
$moduleVersion = $null
103+
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$moduleVersion).ToArray()
104+
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
105+
$moduleVersion | Should -Be ([version]'1.2.3.4')
106+
}
107+
92108
It "Extracts more than 1 module names" -skip:$skipTest {
93109
$sb = @"
94110
{Configuration SomeConfiguration
@@ -99,7 +115,7 @@ Describe "Resolve DSC Resource Dependency" {
99115
$tokens = $null
100116
$parseError = $null
101117
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
102-
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
118+
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
103119
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
104120
$resultModuleNames[1] | Should -Be 'SomeDscModule2'
105121
$resultModuleNames[2] | Should -Be 'SomeDscModule3'
@@ -116,7 +132,7 @@ Describe "Resolve DSC Resource Dependency" {
116132
$tokens = $null
117133
$parseError = $null
118134
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
119-
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
135+
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
120136
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
121137
}
122138
}

0 commit comments

Comments
 (0)