Skip to content
Open
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
23 changes: 16 additions & 7 deletions src/code/FindHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,8 @@ private IEnumerable<PSResourceInfo> SearchByNames(ServerApiCall currentServer, R
foreach (PSResourceInfo currentPkg in parentPkgs)
{
_cmdletPassedIn.WriteDebug($"Finding dependency packages for '{currentPkg.Name}'");
foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository))
string[] emptyExternalModuleDependencies = new string[0];
foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, emptyExternalModuleDependencies, repository))
{
yield return pkgDep;
}
Expand Down Expand Up @@ -1100,6 +1101,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(
ServerApiCall currentServer,
ResponseUtil currentResponseUtil,
PSResourceInfo currentPkg,
string[] externalModuleDependencies,
PSRepositoryInfo repository)
{
if (currentPkg.Dependencies.Length > 0)
Expand All @@ -1108,6 +1110,13 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(
{
PSResourceInfo depPkg = null;

if (externalModuleDependencies.Contains(dep.Name, StringComparer.OrdinalIgnoreCase))
{
_cmdletPassedIn.WriteVerbose($"Dependency '{dep.Name}' is listed as an external module dependency, skipping search/install for this dependency.");
continue;
}

string[] emptyExternalModuleDependencies = new string[0];
if (dep.VersionRange.Equals(VersionRange.All))
{
FindResults responses = currentServer.FindName(dep.Name, includePrerelease: true, _type, out ErrorRecord errRecord);
Expand Down Expand Up @@ -1153,7 +1162,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(

if (!_packagesFound.ContainsKey(depPkg.Name))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand All @@ -1164,7 +1173,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(
// _packagesFound has depPkg.name in it, but the version is not the same
if (!pkgVersions.Contains(FormatPkgVersionString(depPkg)))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand Down Expand Up @@ -1217,7 +1226,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(

if (!_packagesFound.ContainsKey(depPkg.Name))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand All @@ -1228,7 +1237,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(
// _packagesFound has depPkg.name in it, but the version is not the same
if (!pkgVersions.Contains(FormatPkgVersionString(depPkg)))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand Down Expand Up @@ -1299,7 +1308,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(

if (!_packagesFound.ContainsKey(depPkg.Name))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand All @@ -1310,7 +1319,7 @@ internal IEnumerable<PSResourceInfo> FindDependencyPackages(
// _packagesFound has depPkg.name in it, but the version is not the same
if (!pkgVersions.Contains(FormatPkgVersionString(depPkg)))
{
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository))
foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, emptyExternalModuleDependencies, repository))
{
yield return depRes;
}
Expand Down
137 changes: 131 additions & 6 deletions src/code/InstallHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,14 +558,15 @@ private List<PSResourceInfo> InstallPackages(

Hashtable parentPkgInfo = packagesHash[parentPackage] as Hashtable;
PSResourceInfo parentPkgObj = parentPkgInfo["psResourceInfoPkg"] as PSResourceInfo;
string[] externalModuleDependencies = parentPkgInfo["externalModuleDependencies"] as string[];

if (!skipDependencyCheck)
{
// Get the dependencies from the installed package.
if (parentPkgObj.Dependencies.Length > 0)
{
bool depFindFailed = false;
foreach (PSResourceInfo depPkg in findHelper.FindDependencyPackages(currentServer, currentResponseUtil, parentPkgObj, repository))
foreach (PSResourceInfo depPkg in findHelper.FindDependencyPackages(currentServer, currentResponseUtil, parentPkgObj, externalModuleDependencies, repository))
{
if (depPkg == null)
{
Expand Down Expand Up @@ -824,7 +825,8 @@ private Hashtable BeginPackageInstall(
{ "tempDirNameVersionPath", tempInstallPath },
{ "pkgVersion", "" },
{ "scriptPath", "" },
{ "installPath", "" }
{ "installPath", "" },
{ "externalModuleDependencies", Utils.EmptyStrArray }
});
}
}
Expand All @@ -840,7 +842,8 @@ private Hashtable BeginPackageInstall(
{ "tempDirNameVersionPath", tempInstallPath },
{ "pkgVersion", "" },
{ "scriptPath", "" },
{ "installPath", "" }
{ "installPath", "" },
{ "externalModuleDependencies", Utils.EmptyStrArray }
});
}
}
Expand Down Expand Up @@ -933,6 +936,7 @@ private bool TryInstallToTempPath(
_cmdletPassedIn.WriteDebug("In InstallHelper::TryInstallToTempPath()");
error = null;
updatedPackagesHash = packagesHash;
string[] externalModuleDependencies = Utils.EmptyStrArray;
try
{
var pathToFile = Path.Combine(tempInstallPath, $"{pkgName}.{normalizedPkgVersion}.zip");
Expand Down Expand Up @@ -1004,6 +1008,11 @@ private bool TryInstallToTempPath(
return false;
}

if (!RetrieveExternalModuleDependenciesForModule(pkgName, parsedMetadataHashtable, out externalModuleDependencies, out error))
{
return false;
}

// Accept License verification
if (!CallAcceptLicense(pkgToInstall, moduleManifest, tempInstallPath, pkgVersion, out error))
{
Expand All @@ -1022,7 +1031,6 @@ private bool TryInstallToTempPath(
{
installPath = _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase));

// is script
if (!PSScriptFileInfo.TryTestPSScriptFileInfo(
scriptFileInfoPath: scriptPath,
parsedScript: out PSScriptFileInfo scriptToInstall,
Expand All @@ -1042,6 +1050,8 @@ private bool TryInstallToTempPath(

return false;
}

externalModuleDependencies = scriptToInstall.ScriptMetadataComment.ExternalModuleDependencies;
}
else
{
Expand Down Expand Up @@ -1075,7 +1085,8 @@ private bool TryInstallToTempPath(
{ "tempDirNameVersionPath", tempDirNameVersion },
{ "pkgVersion", pkgVersion },
{ "scriptPath", scriptPath },
{ "installPath", installPath }
{ "installPath", installPath },
{ "externalModuleDependencies", externalModuleDependencies }
});
}

Expand Down Expand Up @@ -1111,6 +1122,7 @@ private bool TrySaveNupkgToTempPath(
_cmdletPassedIn.WriteDebug("In InstallHelper::TrySaveNupkgToTempPath()");
error = null;
updatedPackagesHash = packagesHash;
string[] externalModuleDependencies = Utils.EmptyStrArray;

try
{
Expand All @@ -1120,6 +1132,85 @@ private bool TrySaveNupkgToTempPath(
responseStream.CopyTo(fs);
fs.Close();

var pkgVersion = pkgToInstall.Version.ToString();
var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName, pkgVersion);
Directory.CreateDirectory(tempDirNameVersion);

if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out error))
{
return false;
}

var moduleManifest = Path.Combine(tempDirNameVersion, pkgName + PSDataFileExt);
var scriptPath = Path.Combine(tempDirNameVersion, pkgName + PSScriptFileExt);

bool isModule = File.Exists(moduleManifest);
bool isScript = File.Exists(scriptPath);

if (!isModule && !isScript)
{
scriptPath = "";
}

if (isModule)
{
if (!File.Exists(moduleManifest))
{
error = new ErrorRecord(
new ArgumentException("Package '{pkgName}' could not be installed: Module manifest file: {moduleManifest} does not exist. This is not a valid PowerShell module."),
"PSDataFileNotExistError",
ErrorCategory.ReadError,
_cmdletPassedIn);

return false;
}

if (!Utils.TryReadManifestFile(
manifestFilePath: moduleManifest,
manifestInfo: out Hashtable parsedMetadataHashtable,
error: out Exception manifestReadError))
{
error = new ErrorRecord(
manifestReadError,
"ManifestFileReadParseError",
ErrorCategory.ReadError,
_cmdletPassedIn);

return false;
}

if (!RetrieveExternalModuleDependenciesForModule(pkgName, parsedMetadataHashtable, out externalModuleDependencies, out error))
{
return false;
}
}
else if(isScript)
{
if (!PSScriptFileInfo.TryTestPSScriptFileInfo(
scriptFileInfoPath: scriptPath,
parsedScript: out PSScriptFileInfo scriptToInstall,
out ErrorRecord[] parseScriptFileErrors,
out string[] _))
{
foreach (ErrorRecord parseError in parseScriptFileErrors)
{
_cmdletPassedIn.WriteError(parseError);
}

error = new ErrorRecord(
new InvalidOperationException($"PSScriptFile could not be parsed"),
"PSScriptParseError",
ErrorCategory.ReadError,
_cmdletPassedIn);

return false;
}

externalModuleDependencies = scriptToInstall.ScriptMetadataComment.ExternalModuleDependencies;
}

DeleteExtraneousFiles(pkgName, tempDirNameVersion);

string installPath = _pathsToInstallPkg.First();
if (_includeXml)
{
Expand All @@ -1140,7 +1231,8 @@ private bool TrySaveNupkgToTempPath(
{ "tempDirNameVersionPath", tempInstallPath },
{ "pkgVersion", "" },
{ "scriptPath", "" },
{ "installPath", installPath }
{ "installPath", installPath },
{ "externalModuleDependencies", externalModuleDependencies }
});
}

Expand Down Expand Up @@ -1531,6 +1623,39 @@ private void DeleteExtraneousFiles(string packageName, string dirNameVersion)
}
}

private bool RetrieveExternalModuleDependenciesForModule(string pkgName, Hashtable moduleMetadata, out string[] externalModuleDependencies, out ErrorRecord error)
{
error = null;
externalModuleDependencies = Utils.EmptyStrArray;
List<string> externalModuleDependenciesForPkg = new List<string>();

Hashtable privateData = moduleMetadata.ContainsKey("PrivateData") ? moduleMetadata["PrivateData"] as Hashtable : new Hashtable(StringComparer.InvariantCultureIgnoreCase);
Hashtable psData = privateData.ContainsKey("PSData") ? privateData["PSData"] as Hashtable : new Hashtable(StringComparer.InvariantCultureIgnoreCase);
object[] externalModDepObjects = psData.ContainsKey("ExternalModuleDependencies") ? psData["ExternalModuleDependencies"] as object[] : new object[0];
if (externalModDepObjects != null)
{
foreach (var dep in externalModDepObjects)
{
string dependencyName = dep as string;
if (dependencyName.Contains("="))
{
error = new ErrorRecord(
new ArgumentException($"Package '{pkgName}' could not be installed: ExternalModuleDependencies should only contain module names, not other metadata. Invalid entry: '{dependencyName}'"),
"ExternalModuleDependencyInvalidEntry",
ErrorCategory.ReadError,
_cmdletPassedIn);

return false;
}

externalModuleDependenciesForPkg.Add(dependencyName);
}
}

externalModuleDependencies = externalModuleDependenciesForPkg.ToArray();
return true;
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,15 @@ Describe 'Test Install-PSResource for V2 Server scenarios' -tags 'CI' {
$depRes = Get-InstalledPSResource $depPkgName1, $depPkgName2
$depRes.Name | Should -Contain $depPkgName1
$depRes.Name | Should -Contain $depPkgName2
}
}

It "Install resource and dependency, while skipping dependency that is listed as external module dependency" {
$testParentModule = "test_module_ext_dep"
$requiredDependency = "test_module10"
$res = Install-PSresource -Name "test_module_ext_dep" -Repository $PSGalleryName -TrustRepository -PassThru
$res.Name | Should -Contain $testParentModule
$res.Name | Should -Contain $requiredDependency
}
}

Describe 'Test Install-PSResource for V2 Server scenarios' -tags 'ManualValidationOnly' {
Expand Down
16 changes: 16 additions & 0 deletions test/SavePSResourceTests/SavePSResourceV2.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,20 @@ Describe 'Test HTTP Save-PSResource for V2 Server Protocol' -tags 'CI' {
$err.Count | Should -BeGreaterThan 0
$err[0].FullyQualifiedErrorId | Should -BeExactly 'ErrorFilteringNamesForUnsupportedWildcards,Microsoft.PowerShell.PSResourceGet.Cmdlets.SavePSResource'
}

It "Save resource and dependency, while skipping dependency that is listed as external module dependency" {
$testParentModule = "test_module_ext_dep"
$requiredDependency = "test_module10"
$res = Save-PSresource -Name "test_module_ext_dep" -Repository $PSGalleryName -Path $SaveDir -TrustRepository -PassThru
$res.Name | Should -Contain $testParentModule
$res.Name | Should -Contain $requiredDependency
}

It "Save resource and dependency, as .nupkg, while skipping dependency that is listed as external module dependency" {
$testParentModule = "test_module_ext_dep"
$requiredDependency = "test_module10"
$res = Save-PSresource -Name "test_module_ext_dep" -Repository $PSGalleryName -AsNupkg -Path $SaveDir -TrustRepository -PassThru
$res.Name | Should -Contain $testParentModule
$res.Name | Should -Contain $requiredDependency
}
}
Loading