Skip to content
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