Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take module version into account for -SaveDscDependency switch #1094

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 62 additions & 80 deletions Engine/Generic/ModuleDependencyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,6 @@ private string GetTempModulePath(string symLinkPath)
return line;
}

private void SaveModule(PSObject module)
{
ThrowIfNull(module, "module");

// TODO validate module
using (var ps = System.Management.Automation.PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Save-Module")
.AddParameter("Path", tempModulePath)
.AddParameter("InputObject", module);
ps.Invoke();
}
}

private void SetupPSModulePath()
{
oldPSModulePath = Environment.GetEnvironmentVariable("PSModulePath");
Expand Down Expand Up @@ -295,51 +280,17 @@ public ModuleDependencyHandler(

}

/// <summary>
/// Encapsulates Find-Module
/// </summary>
/// <param name="moduleName">Name of the module</param>
/// <returns>A PSObject if it finds the modules otherwise returns null</returns>
public PSObject FindModule(string moduleName)
{
ThrowIfNull(moduleName, "moduleName");
moduleName = moduleName.ToLower();
if (modulesFound.ContainsKey(moduleName))
{
return modulesFound[moduleName];
}
Collection<PSObject> modules = null;
using (var ps = System.Management.Automation.PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Find-Module", true)
.AddParameter("Name", moduleName)
.AddParameter("Repository", moduleRepository);
modules = ps.Invoke<PSObject>();
}
if (modules == null)
{
return null;
}
var module = modules.FirstOrDefault();
if (module == null )
{
return null;
}
modulesFound.Add(moduleName, module);
return module;
}

/// <summary>
/// SaveModule version that doesn't throw
/// </summary>
/// <param name="moduleName">Name of the module</param>
/// <param name="moduleVersion">(Optional) version of the module</param>
/// <returns>True if it can save a module otherwise false.</returns>
public bool TrySaveModule(string moduleName)
public bool TrySaveModule(string moduleName, Version moduleVersion)
{
try
{
SaveModule(moduleName);
SaveModule(moduleName, moduleVersion);
return true;
}
catch
Expand All @@ -353,7 +304,8 @@ public bool TrySaveModule(string moduleName)
/// Encapsulates Save-Module cmdlet
/// </summary>
/// <param name="moduleName">Name of the module</param>
public void SaveModule(string moduleName)
/// <param name="moduleVersion">(Optional) version of the module</param>
public void SaveModule(string moduleName, Version moduleVersion)
{
ThrowIfNull(moduleName, "moduleName");
if (IsModulePresentInTempModulePath(moduleName))
Expand All @@ -368,6 +320,10 @@ public void SaveModule(string moduleName)
.AddParameter("Name", moduleName)
.AddParameter("Repository", moduleRepository)
.AddParameter("Force");
if (moduleVersion != null)
{
ps.AddParameter("RequiredVersion", moduleVersion);
}
ps.Invoke();
}
}
Expand All @@ -376,18 +332,25 @@ public void SaveModule(string moduleName)
/// Encapsulates Get-Module to check the availability of the module on the system
/// </summary>
/// <param name="moduleName"></param>
/// <param name="moduleVersion"></param>
/// <returns>True indicating the presence of the module, otherwise false</returns>
public bool IsModuleAvailable(string moduleName)
public bool IsModuleAvailable(string moduleName, Version moduleVersion)
{
ThrowIfNull(moduleName, "moduleName");
IEnumerable<PSModuleInfo> availableModules;
using (var ps = System.Management.Automation.PowerShell.Create())
{
ps.Runspace = runspace;
availableModules = ps.AddCommand("Get-Module")
ps.AddCommand("Get-Module")
.AddParameter("Name", moduleName)
.AddParameter("ListAvailable")
.Invoke<PSModuleInfo>();
.AddParameter("ListAvailable");
if (moduleVersion != null)
{
ps.AddCommand("Where-Object")
.AddParameter("Filterscript", ScriptBlock.Create($"$_.Version -eq '{moduleVersion}'"));
}
availableModules = ps.Invoke<PSModuleInfo>();

}
return availableModules != null ? availableModules.Any() : false;
}
Expand All @@ -405,25 +368,26 @@ public bool IsModuleAvailable(string moduleName)
/// </summary>
/// <param name="error"></param>
/// <param name="ast"></param>
/// <param name="moduleVersion"></param>
/// <returns>An enumeration over the module names that are not available</returns>
public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast)
public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast, out Version moduleVersion)
{
ThrowIfNull(error, "error");
ThrowIfNull(ast, "ast");
var moduleNames = ModuleDependencyHandler.GetModuleNameFromErrorExtent(error, ast);
var moduleNames = ModuleDependencyHandler.GetModuleNameFromErrorExtent(error, ast, out moduleVersion);
if (moduleNames == null)
{
return null;
}
var unavailableModules = new List<string>();
foreach (var moduleName in moduleNames)
{
if (!IsModuleAvailable(moduleName))
if (!IsModuleAvailable(moduleName, moduleVersion))
{
unavailableModules.Add(moduleName);
}
}
//return moduleNames.Where(x => !IsModuleAvailable(x));

return unavailableModules;
}

Expand All @@ -438,9 +402,11 @@ public IEnumerable<string> GetUnavailableModuleNameFromErrorExtent(ParseError er
/// </summary>
/// <param name="error">Parse error</param>
/// <param name="ast">AST of the script that contians the parse error</param>
/// <param name="moduleVersion">Specifc version of the required module</param>
/// <returns>The name of the module that caused the parser to throw the error. Returns null if it cannot extract the module name.</returns>
public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast)
public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error, ScriptBlockAst ast, out Version moduleVersion)
{
moduleVersion = null;
ThrowIfNull(error, "error");
ThrowIfNull(ast, "ast");
var statement = ast.Find(x => x.Extent.Equals(error.Extent), true);
Expand All @@ -452,46 +418,61 @@ public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error,
// check if the command name is import-dscmodule
// right now we handle only the following forms
// 1. Import-DSCResourceModule -ModuleName somemodule
// 2. Import-DSCResourceModule -ModuleName somemodule1,somemodule2
if (dynamicKywdAst.CommandElements.Count < 3)
{
return null;
}

// 2. Import-DSCResourceModule -ModuleName somemodule1 -ModuleVersion major.minor.patch.build
// 3. Import-DSCResourceModule -ModuleName somemodule1,somemodule2
var dscKeywordAst = dynamicKywdAst.CommandElements[0] as StringConstantExpressionAst;
if (dscKeywordAst == null || !dscKeywordAst.Value.Equals("Import-DscResource", StringComparison.OrdinalIgnoreCase))
{
return null;
}

// find a parameter named modulename
int k;
for (k = 1; k < dynamicKywdAst.CommandElements.Count; k++)
int positionOfModuleNameParamter = 0;
int positionOfModuleVersionParameter = 0;
for (int i = 1; i < dynamicKywdAst.CommandElements.Count; i++)
{
var paramAst = dynamicKywdAst.CommandElements[k] as CommandParameterAst;
var paramAst = dynamicKywdAst.CommandElements[i] as CommandParameterAst;
// TODO match the initial letters only
if (paramAst == null || !paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
if (paramAst != null && paramAst.ParameterName.Equals("ModuleName", StringComparison.OrdinalIgnoreCase))
{
if (i == dynamicKywdAst.CommandElements.Count)
{
// command was Save-DscDependency ... -ModuleName -> module name missing
return null;
}
positionOfModuleNameParamter = i + 1;
continue;
}

if (paramAst != null && paramAst.ParameterName.Equals("ModuleVersion", StringComparison.OrdinalIgnoreCase))
{
if (i == dynamicKywdAst.CommandElements.Count)
{
// command was Save-DscDependency ... -ModuleVersion -> module version missing
return null;
}
positionOfModuleVersionParameter = i + 1;
continue;
}
break;
}

if (k == dynamicKywdAst.CommandElements.Count)
{
// cannot find modulename
return null;
}
var modules = new List<string>();

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

// import-dscresource -ModuleName module1
var paramValStrConstExprAst = paramValAst as StringConstantExpressionAst;
if (paramValStrConstExprAst != null)
{
modules.Add(paramValStrConstExprAst.Value);

// import-dscresource -ModuleName module1 -ModuleVersion major.minor.patch.build
var versionParameterAst = dynamicKywdAst.CommandElements[positionOfModuleVersionParameter] as StringConstantExpressionAst;
if (versionParameterAst != null)
{
Version.TryParse(versionParameterAst.Value, out Version version); // ignore return value since a module version of null means no version
moduleVersion = version;
}
return modules;
}

Expand All @@ -513,6 +494,7 @@ public static IEnumerable<string> GetModuleNameFromErrorExtent(ParseError error,
}
return modules;
}

return null;
}

Expand Down
4 changes: 2 additions & 2 deletions Engine/ScriptAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1804,7 +1804,7 @@ private bool TrySaveModules(ParseError[] errors, ScriptBlockAst scriptAst)
}
foreach (var error in errors.Where(IsModuleNotFoundError))
{
var moduleNames = moduleHandler.GetUnavailableModuleNameFromErrorExtent(error, scriptAst);
var moduleNames = moduleHandler.GetUnavailableModuleNameFromErrorExtent(error, scriptAst, out Version moduleVersion);
if (moduleNames == null)
{
continue;
Expand All @@ -1815,7 +1815,7 @@ private bool TrySaveModules(ParseError[] errors, ScriptBlockAst scriptAst)
String.Format(
"Saving module {0} from PSGallery",
moduleName));
var moduleSaved = moduleHandler.TrySaveModule(moduleName);
var moduleSaved = moduleHandler.TrySaveModule(moduleName, moduleVersion);
if (!moduleSaved)
{
this.outputWriter.WriteVerbose(
Expand Down
22 changes: 19 additions & 3 deletions Tests/Engine/ModuleDependencyHandler.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,26 @@ Describe "Resolve DSC Resource Dependency" {
$tokens = $null
$parseError = $null
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
}

It "Extracts 1 module name with version" -skip:$skipTest {
$sb = @"
{Configuration SomeConfiguration
{
Import-DscResource -ModuleName SomeDscModule1 -ModuleVersion 1.2.3.4
}}
"@
$tokens = $null
$parseError = $null
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
$moduleVersion = $null
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$moduleVersion).ToArray()
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
$moduleVersion | Should -Be ([version]'1.2.3.4')
}

It "Extracts more than 1 module names" -skip:$skipTest {
$sb = @"
{Configuration SomeConfiguration
Expand All @@ -99,7 +115,7 @@ Describe "Resolve DSC Resource Dependency" {
$tokens = $null
$parseError = $null
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
$resultModuleNames[1] | Should -Be 'SomeDscModule2'
$resultModuleNames[2] | Should -Be 'SomeDscModule3'
Expand All @@ -116,7 +132,7 @@ Describe "Resolve DSC Resource Dependency" {
$tokens = $null
$parseError = $null
$ast = [System.Management.Automation.Language.Parser]::ParseInput($sb, [ref]$tokens, [ref]$parseError)
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast).ToArray()
$resultModuleNames = $moduleHandlerType::GetModuleNameFromErrorExtent($parseError[0], $ast, [ref]$null).ToArray()
$resultModuleNames[0] | Should -Be 'SomeDscModule1'
}
}
Expand Down