Skip to content

Add -NoClobber functionality to Install-PSResource #509

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

Merged
merged 11 commits into from
Nov 5, 2021
85 changes: 79 additions & 6 deletions src/code/InstallHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ internal class InstallHelper : PSCmdlet
private string _specifiedPath;
private bool _asNupkg;
private bool _includeXML;
private bool _noClobber;
List<string> _pathsToSearch;

#endregion

Expand All @@ -72,25 +74,24 @@ public void InstallPackages(
bool reinstall,
bool force,
bool trustRepository,
bool noClobber,
PSCredential credential,
string requiredResourceFile,
string requiredResourceJson,
Hashtable requiredResourceHash,
string specifiedPath,
bool asNupkg,
bool includeXML,
List<string> pathsToInstallPkg)
{
_cmdletPassedIn.WriteVerbose(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " +
"AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}';",
"AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';",
string.Join(",", names),
(versionRange != null ? versionRange.OriginalString : string.Empty),
prerelease.ToString(),
repository != null ? string.Join(",", repository) : string.Empty,
acceptLicense.ToString(),
quiet.ToString(),
reinstall.ToString(),
trustRepository.ToString()));
trustRepository.ToString(),
noClobber.ToString()));

_versionRange = versionRange;
_prerelease = prerelease;
Expand All @@ -99,12 +100,28 @@ public void InstallPackages(
_reinstall = reinstall;
_force = force;
_trustRepository = trustRepository;
_noClobber = noClobber;
_credential = credential;
_specifiedPath = specifiedPath;
_asNupkg = asNupkg;
_includeXML = includeXML;
_pathsToInstallPkg = pathsToInstallPkg;

// Create list of installation paths to search.
_pathsToSearch = new List<string>();

// _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable)
// _pathsToSearch will contain all resource package subdirectories within _pathsToInstallPkg path locations
// e.g.:
// ./InstallPackagePath1/PackageA
// ./InstallPackagePath1/PackageB
// ./InstallPackagePath2/PackageC
// ./InstallPackagePath3/PackageD
foreach (var path in _pathsToInstallPkg)
{
_pathsToSearch.AddRange(Utils.GetSubDirectories(path));
}

// Go through the repositories and see which is the first repository to have the pkg version available
ProcessRepositories(names, repository, _trustRepository, _credential);
}
Expand Down Expand Up @@ -209,7 +226,6 @@ private IEnumerable<PSResourceInfo> FilterByInstalledPkgs(IEnumerable<PSResource
{
// Create list of installation paths to search.
List<string> _pathsToSearch = new List<string>();
GetHelper getHelper = new GetHelper(_cmdletPassedIn);
// _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable)
// _pathsToSearch will contain all resource package subdirectories within _pathsToInstallPkg path locations
// e.g.:
Expand All @@ -228,6 +244,7 @@ private IEnumerable<PSResourceInfo> FilterByInstalledPkgs(IEnumerable<PSResource
filteredPackages.Add(pkg.Name, pkg);
}

GetHelper getHelper = new GetHelper(_cmdletPassedIn);
// Get currently installed packages.
IEnumerable<PSResourceInfo> pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(
name: filteredPackages.Keys.ToArray(),
Expand Down Expand Up @@ -410,6 +427,12 @@ private List<string> InstallPackage(IEnumerable<PSResourceInfo> pkgsToInstall, s
{
continue;
}

// If NoClobber is specified, ensure command clobbering does not happen
if (_noClobber && !DetectClobber(p.Name, tempDirNameVersion, parsedMetadataHashtable))
{
continue;
}
}

// Delete the extra nupkg related files that are not needed and not part of the module/script
Expand Down Expand Up @@ -548,6 +571,56 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t
return success;
}

private bool DetectClobber(string pkgName, string tempDirNameVersion, Hashtable parsedMetadataHashtable)
{
// Get installed modules, then get all possible paths
bool foundClobber = false;
GetHelper getHelper = new GetHelper(_cmdletPassedIn);
IEnumerable<PSResourceInfo> pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(new string[] { "*" }, VersionRange.All, _pathsToSearch);
// user parsed metadata hash
List<string> listOfCmdlets = new List<string>();
foreach (var cmdletName in parsedMetadataHashtable["CmdletsToExport"] as object[])
{
listOfCmdlets.Add(cmdletName as string);

}

foreach (var pkg in pkgsAlreadyInstalled)
{
List<string> duplicateCmdlets = new List<string>();
List<string> duplicateCmds = new List<string>();
// See if any of the cmdlets or commands in the pkg we're trying to install exist within a package that's already installed
if (pkg.Includes.Cmdlet != null && pkg.Includes.Cmdlet.Any())
{
duplicateCmdlets = listOfCmdlets.Where(cmdlet => pkg.Includes.Cmdlet.Contains(cmdlet)).ToList();

}
if (pkg.Includes.Command != null && pkg.Includes.Command.Any())
{
duplicateCmds = listOfCmdlets.Where(commands => pkg.Includes.Command.Contains(commands, StringComparer.InvariantCultureIgnoreCase)).ToList();
}
if (duplicateCmdlets.Any() || duplicateCmds.Any())
{

duplicateCmdlets.AddRange(duplicateCmds);

var errMessage = string.Format(
"The following commands are already available on this system: '{0}'. This module '{1}' may override the existing commands. If you still want to install this module '{1}', remove the -NoClobber parameter.",
String.Join(", ", duplicateCmdlets), pkgName);

var ex = new ArgumentException(errMessage);
var noClobberError = new ErrorRecord(ex, "CommandAlreadyExists", ErrorCategory.ResourceExists, null);

_cmdletPassedIn.WriteError(noClobberError);
foundClobber = true;

return foundClobber;
}
}

return foundClobber;
}

private void CreateMetadataXMLFile(string dirNameVersion, string installPath, string repoName, PSResourceInfo pkg, bool isModule)
{
// Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml"
Expand Down
14 changes: 9 additions & 5 deletions src/code/InstallPSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class InstallPSResource : PSCmdlet
[Parameter(ParameterSetName = NameParameterSet)]
[Parameter(ParameterSetName = InputObjectParameterSet)]
public SwitchParameter TrustRepository { get; set; }

/// <summary>
/// Overwrites a previously installed resource with the same name and version.
/// </summary>
Expand All @@ -92,6 +92,12 @@ class InstallPSResource : PSCmdlet
[Parameter(ParameterSetName = InputObjectParameterSet)]
public SwitchParameter AcceptLicense { get; set; }

/// <summary>
/// Prevents installing a package that contains cmdlets that already exist on the machine.
/// </summary>
[Parameter(ParameterSetName = NameParameterSet)]
public SwitchParameter NoClobber { get; set; }

/// <summary>
/// Used for pipeline input.
/// </summary>
Expand Down Expand Up @@ -235,11 +241,9 @@ private void ProcessInstallHelper(InstallHelper installHelper, string[] pkgNames
reinstall: Reinstall,
force: false,
trustRepository: TrustRepository,
noClobber: NoClobber,
credential: Credential,
requiredResourceFile: null,
requiredResourceJson: null,
requiredResourceHash: null,
specifiedPath: null,
specifiedPath: null,
asNupkg: false,
includeXML: true,
pathsToInstallPkg: _pathsToInstallPkg);
Expand Down
4 changes: 1 addition & 3 deletions src/code/SavePSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,7 @@ private void ProcessSaveHelper(InstallHelper installHelper, string[] pkgNames, b
force: false,
trustRepository: TrustRepository,
credential: Credential,
requiredResourceFile: null,
requiredResourceJson: null,
requiredResourceHash: null,
noClobber: false,
specifiedPath: _path,
asNupkg: false,
includeXML: false,
Expand Down
4 changes: 1 addition & 3 deletions src/code/UpdatePSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,7 @@ protected override void ProcessRecord()
force: Force,
trustRepository: TrustRepository,
credential: Credential,
requiredResourceFile: null,
requiredResourceJson: null,
requiredResourceHash: null,
noClobber: false,
specifiedPath: null,
asNupkg: false,
includeXML: true,
Expand Down
11 changes: 10 additions & 1 deletion test/InstallPSResource.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,16 @@ Describe 'Test Install-PSResource for Module' {
$res.Path.Contains("Modules") | Should -Be $true
}

It "Install PSResourceInfo object piped in" {
It "Install module using -NoClobber, should throw clobber error and not install the module" {
Install-PSResource -Name "ClobberTestModule1" -Repository $TestGalleryName

$res = Get-Module "ClobberTestModule1" -ListAvailable
$res.Name | Should -Be "ClobberTestModule1"

Install-PSResource -Name "ClobberTestModule2" -Repository $TestGalleryName -NoClobber -ErrorAction SilentlyContinue
$Error[0].FullyQualifiedErrorId | Should -be "CommandAlreadyExists,Microsoft.PowerShell.PowerShellGet.Cmdlets.InstallPSResource"
}
It "Install PSResourceInfo object piped in" {
Find-PSResource -Name $testModuleName -Version "1.1.0.0" -Repository $TestGalleryName | Install-PSResource
$res = Get-InstalledPSResource -Name $testModuleName
$res.Name | Should -Be $testModuleName
Expand Down