diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index 2d758216a..17e9e7583 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -1,898 +1,907 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using Microsoft.PowerShell.PowerShellGet.UtilClasses;
-using MoreLinq.Extensions;
-using NuGet.Common;
-using NuGet.Configuration;
-using NuGet.Packaging;
-using NuGet.Packaging.Core;
-using NuGet.Packaging.PackageExtraction;
-using NuGet.Protocol;
-using NuGet.Protocol.Core.Types;
-using NuGet.Versioning;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Management.Automation;
-using System.Net;
-using System.Text.RegularExpressions;
-using System.Threading;
-
-namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
-{
- ///
- /// Install helper class
- ///
- internal class InstallHelper : PSCmdlet
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.Commands;
+using Microsoft.PowerShell.PowerShellGet.UtilClasses;
+using MoreLinq.Extensions;
+using NuGet.Common;
+using NuGet.Configuration;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using NuGet.Packaging.PackageExtraction;
+using NuGet.Protocol;
+using NuGet.Protocol.Core.Types;
+using NuGet.Versioning;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
+{
+ ///
+ /// Install helper class
+ ///
+ internal class InstallHelper : PSCmdlet
{
- #region Members
-
+ #region Members
+
public const string PSDataFileExt = ".psd1";
- public const string PSScriptFileExt = ".ps1";
- private const string MsgRepositoryNotTrusted = "Untrusted repository";
- private const string MsgInstallUntrustedPackage = "You are installing the modules from an untrusted repository. If you trust this repository, change its Trusted value by running the Set-PSResourceRepository cmdlet. Are you sure you want to install the PSresource from '{0}' ?";
-
- private CancellationToken _cancellationToken;
- private readonly PSCmdlet _cmdletPassedIn;
- private List _pathsToInstallPkg;
- private VersionRange _versionRange;
- private bool _prerelease;
- private bool _acceptLicense;
- private bool _quiet;
- private bool _reinstall;
- private bool _force;
- private bool _trustRepository;
- private PSCredential _credential;
- private bool _asNupkg;
- private bool _includeXML;
- private bool _noClobber;
- private bool _savePkg;
- List _pathsToSearch;
- List _pkgNamesToInstall;
-
- #endregion
-
- #region Public methods
-
- public InstallHelper(PSCmdlet cmdletPassedIn)
- {
- CancellationTokenSource source = new CancellationTokenSource();
- _cancellationToken = source.Token;
- _cmdletPassedIn = cmdletPassedIn;
- }
-
- public List InstallPackages(
- string[] names,
- VersionRange versionRange,
- bool prerelease,
- string[] repository,
- bool acceptLicense,
- bool quiet,
- bool reinstall,
- bool force,
- bool trustRepository,
- bool noClobber,
- PSCredential credential,
- bool asNupkg,
- bool includeXML,
- bool skipDependencyCheck,
- bool savePkg,
- List pathsToInstallPkg)
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " +
- "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}'; AsNupkg: '{9}'; IncludeXML '{10}'; SavePackage '{11}'",
- string.Join(",", names),
- versionRange != null ? (versionRange.OriginalString != null ? versionRange.OriginalString : string.Empty) : string.Empty,
- prerelease.ToString(),
- repository != null ? string.Join(",", repository) : string.Empty,
- acceptLicense.ToString(),
- quiet.ToString(),
- reinstall.ToString(),
- trustRepository.ToString(),
- noClobber.ToString(),
- asNupkg.ToString(),
- includeXML.ToString(),
- savePkg.ToString()));
-
- _versionRange = versionRange;
- _prerelease = prerelease;
- _acceptLicense = acceptLicense || force;
- _quiet = quiet;
- _reinstall = reinstall;
- _force = force;
- _trustRepository = trustRepository || force;
- _noClobber = noClobber;
- _credential = credential;
- _asNupkg = asNupkg;
- _includeXML = includeXML;
- _savePkg = savePkg;
- _pathsToInstallPkg = pathsToInstallPkg;
-
- // Create list of installation paths to search.
- _pathsToSearch = new List();
- _pkgNamesToInstall = names.ToList();
-
- // _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
- return ProcessRepositories(
- repository: repository,
- trustRepository: _trustRepository,
- credential: _credential,
- skipDependencyCheck: skipDependencyCheck);
- }
-
- #endregion
-
- #region Private methods
-
- // This method calls iterates through repositories (by priority order) to search for the pkgs to install
- private List ProcessRepositories(
- string[] repository,
- bool trustRepository,
- PSCredential credential,
- bool skipDependencyCheck)
- {
- var listOfRepositories = RepositorySettings.Read(repository, out string[] _);
- var yesToAll = false;
- var noToAll = false;
-
- var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn);
- List allPkgsInstalled = new List();
-
- foreach (var repo in listOfRepositories)
- {
- // If no more packages to install, then return
- if (!_pkgNamesToInstall.Any()) return allPkgsInstalled;
-
- string repoName = repo.Name;
- _cmdletPassedIn.WriteVerbose(string.Format("Attempting to search for packages in '{0}'", repoName));
-
- // Source is only trusted if it's set at the repository level to be trusted, -TrustRepository flag is true, -Force flag is true
- // OR the user issues trust interactively via console.
- var sourceTrusted = true;
- if (repo.Trusted == false && !trustRepository && !_force)
- {
- _cmdletPassedIn.WriteVerbose("Checking if untrusted repository should be used");
-
- if (!(yesToAll || noToAll))
- {
- // Prompt for installation of package from untrusted repository
- var message = string.Format(CultureInfo.InvariantCulture, MsgInstallUntrustedPackage, repoName);
- sourceTrusted = _cmdletPassedIn.ShouldContinue(message, MsgRepositoryNotTrusted, true, ref yesToAll, ref noToAll);
- }
- }
-
- if (!sourceTrusted && !yesToAll)
- {
- continue;
- }
-
- _cmdletPassedIn.WriteVerbose("Untrusted repository accepted as trusted source.");
-
- // If it can't find the pkg in one repository, it'll look for it in the next repo in the list
- var isLocalRepo = repo.Uri.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase);
-
- // Finds parent packages and dependencies
- IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName(
- name: _pkgNamesToInstall.ToArray(),
- type: ResourceType.None,
- version: _versionRange != null ? _versionRange.OriginalString : null,
- prerelease: _prerelease,
- tag: null,
- repository: new string[] { repoName },
- credential: credential,
- includeDependencies: !skipDependencyCheck);
-
- if (!pkgsFromRepoToInstall.Any())
- {
- _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName));
- // Check in the next repository
- continue;
- }
-
- // Select the first package from each name group, which is guaranteed to be the latest version.
- // We should only have one version returned for each package name
- // e.g.:
- // PackageA (version 1.0)
- // PackageB (version 2.0)
- // PackageC (version 1.0)
- pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy(
- m => new { m.Name }).Select(
- group => group.First()).ToList();
-
- // Check to see if the pkgs (including dependencies) are already installed (ie the pkg is installed and the version satisfies the version range provided via param)
- if (!_reinstall)
- {
- pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall);
- }
-
- if (!pkgsFromRepoToInstall.Any())
- {
- continue;
- }
-
- List pkgsInstalled = InstallPackage(
- pkgsFromRepoToInstall,
- repoName,
- repo.Uri.AbsoluteUri,
- repo.CredentialInfo,
- credential,
- isLocalRepo);
-
- foreach (PSResourceInfo pkg in pkgsInstalled)
- {
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- }
-
- allPkgsInstalled.AddRange(pkgsInstalled);
- }
-
- // At this only package names left were those which could not be found in registered repositories
- foreach (string pkgName in _pkgNamesToInstall)
- {
- var message = String.Format("Package '{0}' with requested version range {1} could not be installed as it was not found in any registered repositories",
- pkgName,
- _versionRange.ToString());
- var ex = new ArgumentException(message);
- var ResourceNotFoundError = new ErrorRecord(ex, "ResourceNotFoundError", ErrorCategory.ObjectNotFound, null);
- _cmdletPassedIn.WriteError(ResourceNotFoundError);
- }
-
- return allPkgsInstalled;
- }
-
- // Check if any of the pkg versions are already installed, if they are we'll remove them from the list of packages to install
- private IEnumerable FilterByInstalledPkgs(IEnumerable packages)
- {
- // Create list of installation paths to search.
- List _pathsToSearch = new List();
- // _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));
- }
-
- var filteredPackages = new Dictionary();
- foreach (var pkg in packages)
- {
- filteredPackages.Add(pkg.Name, pkg);
- }
-
- GetHelper getHelper = new GetHelper(_cmdletPassedIn);
- // Get currently installed packages.
- // selectPrereleaseOnly is false because even if Prerelease is true we want to include both stable and prerelease, never select prerelease only.
- IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(
- name: filteredPackages.Keys.ToArray(),
- versionRange: _versionRange,
- pathsToSearch: _pathsToSearch,
- selectPrereleaseOnly: false);
- if (!pkgsAlreadyInstalled.Any())
- {
- return packages;
- }
-
- // Remove from list package versions that are already installed.
- foreach (PSResourceInfo pkg in pkgsAlreadyInstalled)
- {
- _cmdletPassedIn.WriteWarning(
- string.Format("Resource '{0}' with version '{1}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter",
- pkg.Name,
- pkg.Version));
-
- filteredPackages.Remove(pkg.Name);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- }
-
- return filteredPackages.Values.ToArray();
- }
-
- private List InstallPackage(
- IEnumerable pkgsToInstall, // those found to be required to be installed (includes Dependency packages as well)
- string repoName,
- string repoUri,
- PSCredentialInfo repoCredentialInfo,
- PSCredential credential,
- bool isLocalRepo)
- {
- List pkgsSuccessfullyInstalled = new List();
- int totalPkgs = pkgsToInstall.Count();
-
- // Counters for tracking current package out of total
- int totalInstalledPkgCount = 0;
- foreach (PSResourceInfo pkg in pkgsToInstall)
- {
- totalInstalledPkgCount++;
- var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
- try
- {
- // Create a temp directory to install to
- var dir = Directory.CreateDirectory(tempInstallPath); // should check it gets created properly
- // To delete file attributes from the existing ones get the current file attributes first and use AND (&) operator
- // with a mask (bitwise complement of desired attributes combination).
- // TODO: check the attributes and if it's read only then set it
- // attribute may be inherited from the parent
- // TODO: are there Linux accommodations we need to consider here?
- dir.Attributes &= ~FileAttributes.ReadOnly;
-
- _cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", pkg.Name));
-
- if (!_quiet)
- {
- int activityId = 0;
- int percentComplete = ((totalInstalledPkgCount * 100) / totalPkgs);
- string activity = string.Format("Installing {0}...", pkg.Name);
- string statusDescription = string.Format("{0}% Complete", percentComplete);
- _cmdletPassedIn.WriteProgress(
- new ProgressRecord(activityId, activity, statusDescription));
- }
-
- // Create PackageIdentity in order to download
- string createFullVersion = pkg.Version.ToString();
- if (pkg.IsPrerelease)
- {
- createFullVersion = pkg.Version.ToString() + "-" + pkg.Prerelease;
- }
-
- if (!NuGetVersion.TryParse(createFullVersion, out NuGetVersion pkgVersion))
- {
- var message = String.Format("{0} package could not be installed with error: could not parse package '{0}' version '{1} into a NuGetVersion",
- pkg.Name,
- pkg.Version.ToString());
- var ex = new ArgumentException(message);
- var packageIdentityVersionParseError = new ErrorRecord(ex, "psdataFileNotExistError", ErrorCategory.ReadError, null);
- _cmdletPassedIn.WriteError(packageIdentityVersionParseError);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- continue;
- }
-
- var pkgIdentity = new PackageIdentity(pkg.Name, pkgVersion);
- var cacheContext = new SourceCacheContext();
-
- if (isLocalRepo)
- {
- /* Download from a local repository -- this is slightly different process than from a server */
- var localResource = new FindLocalPackagesResourceV2(repoUri);
- var resource = new LocalDownloadResource(repoUri, localResource);
-
- // Actually downloading the .nupkg from a local repo
- var result = resource.GetDownloadResourceResultAsync(
- identity: pkgIdentity,
- downloadContext: new PackageDownloadContext(cacheContext),
- globalPackagesFolder: tempInstallPath,
- logger: NullLogger.Instance,
- token: _cancellationToken).GetAwaiter().GetResult();
-
- // Create the package extraction context
- PackageExtractionContext packageExtractionContext = new PackageExtractionContext(
- packageSaveMode: PackageSaveMode.Nupkg,
- xmlDocFileSaveMode: PackageExtractionBehavior.XmlDocFileSaveMode,
- clientPolicyContext: null,
- logger: NullLogger.Instance);
-
- // Extracting from .nupkg and placing files into tempInstallPath
- result.PackageReader.CopyFiles(
- destination: tempInstallPath,
- packageFiles: result.PackageReader.GetFiles(),
- extractFile: new PackageFileExtractor(
- result.PackageReader.GetFiles(),
- packageExtractionContext.XmlDocFileSaveMode).ExtractPackageFile,
- logger: NullLogger.Instance,
- token: _cancellationToken);
- result.Dispose();
- }
- else
- {
- /* Download from a non-local repository */
- // Set up NuGet API resource for download
- PackageSource source = new PackageSource(repoUri);
-
- // Explicitly passed in Credential takes precedence over repository CredentialInfo
- if (credential != null)
- {
- string password = new NetworkCredential(string.Empty, credential.Password).Password;
- source.Credentials = PackageSourceCredential.FromUserInput(repoUri, credential.UserName, password, true, null);
- }
- else if (repoCredentialInfo != null)
- {
- PSCredential repoCredential = Utils.GetRepositoryCredentialFromSecretManagement(
- repoName,
- repoCredentialInfo,
- _cmdletPassedIn);
-
- string password = new NetworkCredential(string.Empty, repoCredential.Password).Password;
- source.Credentials = PackageSourceCredential.FromUserInput(repoUri, repoCredential.UserName, password, true, null);
- }
- var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);
- SourceRepository repository = new SourceRepository(source, provider);
-
- /* Download from a non-local repository -- ie server */
- var downloadResource = repository.GetResourceAsync().GetAwaiter().GetResult();
- DownloadResourceResult result = null;
- try
- {
- result = downloadResource.GetDownloadResourceResultAsync(
- identity: pkgIdentity,
- downloadContext: new PackageDownloadContext(cacheContext),
- globalPackagesFolder: tempInstallPath,
- logger: NullLogger.Instance,
- token: _cancellationToken).GetAwaiter().GetResult();
- }
- catch (Exception e)
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Error attempting download: '{0}'", e.Message));
- }
- finally
- {
- // Need to close the .nupkg
- if (result != null) result.Dispose();
- }
- }
-
- _cmdletPassedIn.WriteVerbose(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath));
-
- // pkgIdentity.Version.Version gets the version without metadata or release labels.
- string newVersion = pkgIdentity.Version.ToNormalizedString();
- string normalizedVersionNoPrerelease = newVersion;
- if (pkgIdentity.Version.IsPrerelease)
- {
- // eg: 2.0.2
- normalizedVersionNoPrerelease = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-'));
- }
-
- string tempDirNameVersion = isLocalRepo ? tempInstallPath : Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion);
- var version4digitNoPrerelease = pkgIdentity.Version.Version.ToString();
- string moduleManifestVersion = string.Empty;
- var scriptPath = Path.Combine(tempDirNameVersion, pkg.Name + PSScriptFileExt);
- var modulePath = Path.Combine(tempDirNameVersion, pkg.Name + PSDataFileExt);
- // Check if the package is a module or a script
- var isModule = File.Exists(modulePath);
-
- string installPath;
- if (_savePkg)
- {
- // For save the installation path is what is passed in via -Path
- installPath = _pathsToInstallPkg.FirstOrDefault();
-
- // If saving as nupkg simply copy the nupkg and move onto next iteration of loop
- // asNupkg functionality only applies to Save-PSResource
- if (_asNupkg)
- {
- var nupkgFile = pkgIdentity.ToString().ToLower() + ".nupkg";
- File.Copy(Path.Combine(tempDirNameVersion, nupkgFile), Path.Combine(installPath, nupkgFile));
-
- _cmdletPassedIn.WriteVerbose(string.Format("'{0}' moved into file path '{1}'", nupkgFile, installPath));
- pkgsSuccessfullyInstalled.Add(pkg);
-
- continue;
- }
- }
- else
- {
- // PSModules:
- /// ./Modules
- /// ./Scripts
- /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list
- installPath = isModule ? _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase))
- : _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase));
- }
-
- if (isModule)
- {
- var moduleManifest = Path.Combine(tempDirNameVersion, pkgIdentity.Id + PSDataFileExt);
- if (!File.Exists(moduleManifest))
- {
- var message = String.Format("{0} package could not be installed with error: Module manifest file: {1} does not exist. This is not a valid PowerShell module.", pkgIdentity.Id, moduleManifest);
-
- var ex = new ArgumentException(message);
- var psdataFileDoesNotExistError = new ErrorRecord(ex, "psdataFileNotExistError", ErrorCategory.ReadError, null);
- _cmdletPassedIn.WriteError(psdataFileDoesNotExistError);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- continue;
- }
-
- if (!Utils.TryParsePSDataFile(moduleManifest, _cmdletPassedIn, out Hashtable parsedMetadataHashtable))
- {
- // Ran into errors parsing the module manifest file which was found in Utils.ParseModuleManifest() and written.
- continue;
- }
-
- moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string;
-
- // Accept License verification
- if (!_savePkg && !CallAcceptLicense(pkg, moduleManifest, tempInstallPath, newVersion))
- {
- continue;
- }
-
- // If NoClobber is specified, ensure command clobbering does not happen
- if (_noClobber && !DetectClobber(pkg.Name, parsedMetadataHashtable))
- {
- continue;
- }
- }
-
- // Delete the extra nupkg related files that are not needed and not part of the module/script
- DeleteExtraneousFiles(pkgIdentity, tempDirNameVersion);
-
- if (_includeXML)
- {
- CreateMetadataXMLFile(tempDirNameVersion, installPath, pkg, isModule);
- }
-
- MoveFilesIntoInstallPath(
- pkg,
- isModule,
- isLocalRepo,
- tempDirNameVersion,
- tempInstallPath,
- installPath,
- newVersion,
- moduleManifestVersion,
- scriptPath);
-
- _cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}' to location '{1}'", pkg.Name, installPath));
- pkgsSuccessfullyInstalled.Add(pkg);
- }
- catch (Exception e)
- {
- _cmdletPassedIn.WriteError(
- new ErrorRecord(
- new PSInvalidOperationException(
- message: $"Unable to successfully install package '{pkg.Name}': '{e.Message}'",
- innerException: e),
- "InstallPackageFailed",
- ErrorCategory.InvalidOperation,
- _cmdletPassedIn));
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- }
- finally
- {
- // Delete the temp directory and all its contents
- _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete '{0}'", tempInstallPath));
-
- if (Directory.Exists(tempInstallPath))
- {
- if (!TryDeleteDirectory(tempInstallPath, out ErrorRecord errorMsg))
- {
- _cmdletPassedIn.WriteError(errorMsg);
- }
- else
- {
- _cmdletPassedIn.WriteVerbose(String.Format("Successfully deleted '{0}'", tempInstallPath));
- }
- }
- }
- }
-
- return pkgsSuccessfullyInstalled;
- }
-
- private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion)
- {
- var requireLicenseAcceptance = false;
- var success = true;
-
- if (File.Exists(moduleManifest))
- {
- using (StreamReader sr = new StreamReader(moduleManifest))
- {
- var text = sr.ReadToEnd();
-
- var pattern = "RequireLicenseAcceptance\\s*=\\s*\\$true";
- var patternToSkip1 = "#\\s*RequireLicenseAcceptance\\s*=\\s*\\$true";
- var patternToSkip2 = "\\*\\s*RequireLicenseAcceptance\\s*=\\s*\\$true";
-
- Regex rgx = new Regex(pattern);
- Regex rgxComment1 = new Regex(patternToSkip1);
- Regex rgxComment2 = new Regex(patternToSkip2);
- if (rgx.IsMatch(text) && !rgxComment1.IsMatch(text) && !rgxComment2.IsMatch(text))
- {
- requireLicenseAcceptance = true;
- }
- }
-
- // Licesnse agreement processing
- if (requireLicenseAcceptance)
- {
- // If module requires license acceptance and -AcceptLicense is not passed in, display prompt
- if (!_acceptLicense)
- {
- var PkgTempInstallPath = Path.Combine(tempInstallPath, p.Name, newVersion);
- var LicenseFilePath = Path.Combine(PkgTempInstallPath, "License.txt");
-
- if (!File.Exists(LicenseFilePath))
- {
- var exMessage = String.Format("{0} package could not be installed with error: License.txt not found. License.txt must be provided when user license acceptance is required.", p.Name);
- var ex = new ArgumentException(exMessage);
- var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null);
-
- _cmdletPassedIn.WriteError(acceptLicenseError);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase));
- success = false;
- }
-
- // Otherwise read LicenseFile
- string licenseText = System.IO.File.ReadAllText(LicenseFilePath);
- var acceptanceLicenseQuery = $"Do you accept the license terms for module '{p.Name}'.";
- var message = licenseText + "`r`n" + acceptanceLicenseQuery;
-
- var title = "License Acceptance";
- var yesToAll = false;
- var noToAll = false;
- var shouldContinueResult = _cmdletPassedIn.ShouldContinue(message, title, true, ref yesToAll, ref noToAll);
-
- if (shouldContinueResult || yesToAll)
- {
- _acceptLicense = true;
- }
- }
-
- // Check if user agreed to license terms, if they didn't then throw error, otherwise continue to install
- if (!_acceptLicense)
- {
- var message = String.Format("{0} package could not be installed with error: License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation.", p.Name);
- var ex = new ArgumentException(message);
- var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null);
-
- _cmdletPassedIn.WriteError(acceptLicenseError);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase));
- success = false;
- }
- }
- }
-
- return success;
- }
-
- private bool DetectClobber(string pkgName, Hashtable parsedMetadataHashtable)
- {
- // Get installed modules, then get all possible paths
- bool foundClobber = false;
- GetHelper getHelper = new GetHelper(_cmdletPassedIn);
- // selectPrereleaseOnly is false because even if Prerelease is true we want to include both stable and prerelease, never select prerelease only.
- IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(
- name: new string[] { "*" },
- versionRange: VersionRange.All,
- pathsToSearch: _pathsToSearch,
- selectPrereleaseOnly: false);
- // user parsed metadata hash
- List listOfCmdlets = new List();
- foreach (var cmdletName in parsedMetadataHashtable["CmdletsToExport"] as object[])
- {
- listOfCmdlets.Add(cmdletName as string);
-
- }
-
- foreach (var pkg in pkgsAlreadyInstalled)
- {
- List duplicateCmdlets = new List();
- List duplicateCmds = new List();
- // 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(
- "{1} package could not be installed with error: 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);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase));
- foundClobber = true;
-
- return foundClobber;
- }
- }
-
- return foundClobber;
- }
-
- private void CreateMetadataXMLFile(string dirNameVersion, string installPath, PSResourceInfo pkg, bool isModule)
- {
- // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml"
- // Modules will have the metadata file: "PSGetModuleInfo.xml"
- var metadataXMLPath = isModule ? Path.Combine(dirNameVersion, "PSGetModuleInfo.xml")
- : Path.Combine(dirNameVersion, (pkg.Name + "_InstalledScriptInfo.xml"));
-
- pkg.InstalledDate = DateTime.Now;
- pkg.InstalledLocation = installPath;
-
- // Write all metadata into metadataXMLPath
- if (!pkg.TryWrite(metadataXMLPath, out string error))
- {
- var message = string.Format("{0} package could not be installed with error: Error parsing metadata into XML: '{1}'", pkg.Name, error);
- var ex = new ArgumentException(message);
- var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null);
-
- _cmdletPassedIn.WriteError(ErrorParsingMetadata);
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
- }
- }
-
- private void DeleteExtraneousFiles(PackageIdentity pkgIdentity, string dirNameVersion)
- {
- // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module
- var pkgIdString = pkgIdentity.ToString();
- var nupkgSHAToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg.sha512");
- var nuspecToDelete = Path.Combine(dirNameVersion, pkgIdentity.Id + ".nuspec");
- var nupkgToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg");
- var nupkgMetadataToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg.metadata");
- var contentTypesToDelete = Path.Combine(dirNameVersion, "[Content_Types].xml");
- var relsDirToDelete = Path.Combine(dirNameVersion, "_rels");
- var packageDirToDelete = Path.Combine(dirNameVersion, "package");
-
- // Unforunately have to check if each file exists because it may or may not be there
- if (File.Exists(nupkgSHAToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgSHAToDelete));
- File.Delete(nupkgSHAToDelete);
- }
- if (File.Exists(nuspecToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nuspecToDelete));
- File.Delete(nuspecToDelete);
- }
- if (File.Exists(nupkgToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgToDelete));
- File.Delete(nupkgToDelete);
- }
- if (File.Exists(nupkgMetadataToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgMetadataToDelete));
- File.Delete(nupkgMetadataToDelete);
- }
- if (File.Exists(contentTypesToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", contentTypesToDelete));
- File.Delete(contentTypesToDelete);
- }
- if (Directory.Exists(relsDirToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", relsDirToDelete));
- Utils.DeleteDirectory(relsDirToDelete);
- }
- if (Directory.Exists(packageDirToDelete))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", packageDirToDelete));
- Utils.DeleteDirectory(packageDirToDelete);
- }
- }
-
- private bool TryDeleteDirectory(
- string tempInstallPath,
- out ErrorRecord errorMsg)
- {
- errorMsg = null;
-
- try
- {
- Utils.DeleteDirectory(tempInstallPath);
- }
- catch (Exception e)
- {
- var TempDirCouldNotBeDeletedError = new ErrorRecord(e, "errorDeletingTempInstallPath", ErrorCategory.InvalidResult, null);
- errorMsg = TempDirCouldNotBeDeletedError;
- return false;
- }
-
- return true;
- }
-
- private void MoveFilesIntoInstallPath(
- PSResourceInfo pkgInfo,
- bool isModule,
- bool isLocalRepo,
- string dirNameVersion,
- string tempInstallPath,
- string installPath,
- string newVersion,
- string moduleManifestVersion,
- string scriptPath)
- {
- // Creating the proper installation path depending on whether pkg is a module or script
- var newPathParent = isModule ? Path.Combine(installPath, pkgInfo.Name) : installPath;
- var finalModuleVersionDir = isModule ? Path.Combine(installPath, pkgInfo.Name, moduleManifestVersion) : installPath;
-
- // If script, just move the files over, if module, move the version directory over
- var tempModuleVersionDir = (!isModule || isLocalRepo) ? dirNameVersion
- : Path.Combine(tempInstallPath, pkgInfo.Name.ToLower(), newVersion);
-
- _cmdletPassedIn.WriteVerbose(string.Format("Installation source path is: '{0}'", tempModuleVersionDir));
- _cmdletPassedIn.WriteVerbose(string.Format("Installation destination path is: '{0}'", finalModuleVersionDir));
-
- if (isModule)
- {
- // If new path does not exist
- if (!Directory.Exists(newPathParent))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir));
- Directory.CreateDirectory(newPathParent);
- Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir);
- }
- else
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir));
-
- if (Directory.Exists(finalModuleVersionDir))
- {
- // Delete the directory path before replacing it with the new module.
- // If deletion fails (usually due to binary file in use), then attempt restore so that the currently
- // installed module is not corrupted.
- _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete with restore on failure.'{0}'", finalModuleVersionDir));
- Utils.DeleteDirectoryWithRestore(finalModuleVersionDir);
- }
-
- _cmdletPassedIn.WriteVerbose(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir));
- Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir);
- }
- }
- else
- {
- if (!_savePkg)
- {
- // Need to delete old xml files because there can only be 1 per script
- var scriptXML = pkgInfo.Name + "_InstalledScriptInfo.xml";
- _cmdletPassedIn.WriteVerbose(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))));
- if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting script metadata XML"));
- File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML));
- }
-
- _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)));
- Utils.MoveFiles(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML));
-
- // Need to delete old script file, if that exists
- _cmdletPassedIn.WriteVerbose(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt))));
- if (File.Exists(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)))
- {
- _cmdletPassedIn.WriteVerbose(string.Format("Deleting script file"));
- File.Delete(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt));
- }
- }
-
- _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)));
- Utils.MoveFiles(scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt));
- }
- }
-
- #endregion
- }
-}
+ public const string PSScriptFileExt = ".ps1";
+ private const string MsgRepositoryNotTrusted = "Untrusted repository";
+ private const string MsgInstallUntrustedPackage = "You are installing the modules from an untrusted repository. If you trust this repository, change its Trusted value by running the Set-PSResourceRepository cmdlet. Are you sure you want to install the PSresource from '{0}' ?";
+ private CancellationToken _cancellationToken;
+ private readonly PSCmdlet _cmdletPassedIn;
+ private List _pathsToInstallPkg;
+ private VersionRange _versionRange;
+ private bool _prerelease;
+ private bool _acceptLicense;
+ private bool _quiet;
+ private bool _reinstall;
+ private bool _force;
+ private bool _trustRepository;
+ private PSCredential _credential;
+ private bool _asNupkg;
+ private bool _includeXML;
+ private bool _noClobber;
+ private bool _authenticodeCheck;
+ private bool _savePkg;
+ List _pathsToSearch;
+ List _pkgNamesToInstall;
+
+ #endregion
+
+ #region Public methods
+
+ public InstallHelper(PSCmdlet cmdletPassedIn)
+ {
+ CancellationTokenSource source = new CancellationTokenSource();
+ _cancellationToken = source.Token;
+ _cmdletPassedIn = cmdletPassedIn;
+ }
+
+ public List InstallPackages(
+ string[] names,
+ VersionRange versionRange,
+ bool prerelease,
+ string[] repository,
+ bool acceptLicense,
+ bool quiet,
+ bool reinstall,
+ bool force,
+ bool trustRepository,
+ bool noClobber,
+ PSCredential credential,
+ bool asNupkg,
+ bool includeXML,
+ bool skipDependencyCheck,
+ bool authenticodeCheck,
+ bool savePkg,
+ List pathsToInstallPkg)
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " +
+ "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}'; AsNupkg: '{9}'; IncludeXML '{10}'; SavePackage '{11}'",
+ string.Join(",", names),
+ versionRange != null ? (versionRange.OriginalString != null ? versionRange.OriginalString : string.Empty) : string.Empty,
+ prerelease.ToString(),
+ repository != null ? string.Join(",", repository) : string.Empty,
+ acceptLicense.ToString(),
+ quiet.ToString(),
+ reinstall.ToString(),
+ trustRepository.ToString(),
+ noClobber.ToString(),
+ asNupkg.ToString(),
+ includeXML.ToString(),
+ savePkg.ToString()));
+
+ _versionRange = versionRange;
+ _prerelease = prerelease;
+ _acceptLicense = acceptLicense || force;
+ _authenticodeCheck = authenticodeCheck;
+ _quiet = quiet;
+ _reinstall = reinstall;
+ _force = force;
+ _trustRepository = trustRepository || force;
+ _noClobber = noClobber;
+ _credential = credential;
+ _asNupkg = asNupkg;
+ _includeXML = includeXML;
+ _savePkg = savePkg;
+ _pathsToInstallPkg = pathsToInstallPkg;
+
+ // Create list of installation paths to search.
+ _pathsToSearch = new List();
+ _pkgNamesToInstall = names.ToList();
+
+ // _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
+ return ProcessRepositories(
+ repository: repository,
+ trustRepository: _trustRepository,
+ credential: _credential,
+ skipDependencyCheck: skipDependencyCheck);
+ }
+
+ #endregion
+
+ #region Private methods
+
+ // This method calls iterates through repositories (by priority order) to search for the pkgs to install
+ private List ProcessRepositories(
+ string[] repository,
+ bool trustRepository,
+ PSCredential credential,
+ bool skipDependencyCheck)
+ {
+ var listOfRepositories = RepositorySettings.Read(repository, out string[] _);
+ var yesToAll = false;
+ var noToAll = false;
+
+ var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn);
+ List allPkgsInstalled = new List();
+
+ foreach (var repo in listOfRepositories)
+ {
+ // If no more packages to install, then return
+ if (!_pkgNamesToInstall.Any()) return allPkgsInstalled;
+
+ string repoName = repo.Name;
+ _cmdletPassedIn.WriteVerbose(string.Format("Attempting to search for packages in '{0}'", repoName));
+
+ // Source is only trusted if it's set at the repository level to be trusted, -TrustRepository flag is true, -Force flag is true
+ // OR the user issues trust interactively via console.
+ var sourceTrusted = true;
+ if (repo.Trusted == false && !trustRepository && !_force)
+ {
+ _cmdletPassedIn.WriteVerbose("Checking if untrusted repository should be used");
+
+ if (!(yesToAll || noToAll))
+ {
+ // Prompt for installation of package from untrusted repository
+ var message = string.Format(CultureInfo.InvariantCulture, MsgInstallUntrustedPackage, repoName);
+ sourceTrusted = _cmdletPassedIn.ShouldContinue(message, MsgRepositoryNotTrusted, true, ref yesToAll, ref noToAll);
+ }
+ }
+
+ if (!sourceTrusted && !yesToAll)
+ {
+ continue;
+ }
+
+ _cmdletPassedIn.WriteVerbose("Untrusted repository accepted as trusted source.");
+
+ // If it can't find the pkg in one repository, it'll look for it in the next repo in the list
+ var isLocalRepo = repo.Uri.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase);
+
+ // Finds parent packages and dependencies
+ IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName(
+ name: _pkgNamesToInstall.ToArray(),
+ type: ResourceType.None,
+ version: _versionRange != null ? _versionRange.OriginalString : null,
+ prerelease: _prerelease,
+ tag: null,
+ repository: new string[] { repoName },
+ credential: credential,
+ includeDependencies: !skipDependencyCheck);
+
+ if (!pkgsFromRepoToInstall.Any())
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName));
+ // Check in the next repository
+ continue;
+ }
+
+ // Select the first package from each name group, which is guaranteed to be the latest version.
+ // We should only have one version returned for each package name
+ // e.g.:
+ // PackageA (version 1.0)
+ // PackageB (version 2.0)
+ // PackageC (version 1.0)
+ pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy(
+ m => new { m.Name }).Select(
+ group => group.First()).ToList();
+
+ // Check to see if the pkgs (including dependencies) are already installed (ie the pkg is installed and the version satisfies the version range provided via param)
+ if (!_reinstall)
+ {
+ pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall);
+ }
+
+ if (!pkgsFromRepoToInstall.Any())
+ {
+ continue;
+ }
+
+ List pkgsInstalled = InstallPackage(
+ pkgsFromRepoToInstall,
+ repoName,
+ repo.Uri.AbsoluteUri,
+ repo.CredentialInfo,
+ credential,
+ isLocalRepo);
+
+ foreach (PSResourceInfo pkg in pkgsInstalled)
+ {
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ allPkgsInstalled.AddRange(pkgsInstalled);
+ }
+
+ // At this only package names left were those which could not be found in registered repositories
+ foreach (string pkgName in _pkgNamesToInstall)
+ {
+ var message = String.Format("Package '{0}' with requested version range {1} could not be installed as it was not found in any registered repositories",
+ pkgName,
+ _versionRange.ToString());
+ var ex = new ArgumentException(message);
+ var ResourceNotFoundError = new ErrorRecord(ex, "ResourceNotFoundError", ErrorCategory.ObjectNotFound, null);
+ _cmdletPassedIn.WriteError(ResourceNotFoundError);
+ }
+
+ return allPkgsInstalled;
+ }
+
+ // Check if any of the pkg versions are already installed, if they are we'll remove them from the list of packages to install
+ private IEnumerable FilterByInstalledPkgs(IEnumerable packages)
+ {
+ // Create list of installation paths to search.
+ List _pathsToSearch = new List();
+ // _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));
+ }
+
+ var filteredPackages = new Dictionary();
+ foreach (var pkg in packages)
+ {
+ filteredPackages.Add(pkg.Name, pkg);
+ }
+
+ GetHelper getHelper = new GetHelper(_cmdletPassedIn);
+ // Get currently installed packages.
+ // selectPrereleaseOnly is false because even if Prerelease is true we want to include both stable and prerelease, never select prerelease only.
+ IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(
+ name: filteredPackages.Keys.ToArray(),
+ versionRange: _versionRange,
+ pathsToSearch: _pathsToSearch,
+ selectPrereleaseOnly: false);
+ if (!pkgsAlreadyInstalled.Any())
+ {
+ return packages;
+ }
+
+ // Remove from list package versions that are already installed.
+ foreach (PSResourceInfo pkg in pkgsAlreadyInstalled)
+ {
+ _cmdletPassedIn.WriteWarning(
+ string.Format("Resource '{0}' with version '{1}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter",
+ pkg.Name,
+ pkg.Version));
+
+ filteredPackages.Remove(pkg.Name);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ return filteredPackages.Values.ToArray();
+ }
+
+ private List InstallPackage(
+ IEnumerable pkgsToInstall, // those found to be required to be installed (includes Dependency packages as well)
+ string repoName,
+ string repoUri,
+ PSCredentialInfo repoCredentialInfo,
+ PSCredential credential,
+ bool isLocalRepo)
+ {
+ List pkgsSuccessfullyInstalled = new List();
+ int totalPkgs = pkgsToInstall.Count();
+
+ // Counters for tracking current package out of total
+ int totalInstalledPkgCount = 0;
+ foreach (PSResourceInfo pkg in pkgsToInstall)
+ {
+ totalInstalledPkgCount++;
+ var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ try
+ {
+ // Create a temp directory to install to
+ var dir = Directory.CreateDirectory(tempInstallPath); // should check it gets created properly
+ // To delete file attributes from the existing ones get the current file attributes first and use AND (&) operator
+ // with a mask (bitwise complement of desired attributes combination).
+ // TODO: check the attributes and if it's read only then set it
+ // attribute may be inherited from the parent
+ // TODO: are there Linux accommodations we need to consider here?
+ dir.Attributes &= ~FileAttributes.ReadOnly;
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", pkg.Name));
+
+ if (!_quiet)
+ {
+ int activityId = 0;
+ int percentComplete = ((totalInstalledPkgCount * 100) / totalPkgs);
+ string activity = string.Format("Installing {0}...", pkg.Name);
+ string statusDescription = string.Format("{0}% Complete", percentComplete);
+ _cmdletPassedIn.WriteProgress(
+ new ProgressRecord(activityId, activity, statusDescription));
+ }
+
+ // Create PackageIdentity in order to download
+ string createFullVersion = pkg.Version.ToString();
+ if (pkg.IsPrerelease)
+ {
+ createFullVersion = pkg.Version.ToString() + "-" + pkg.Prerelease;
+ }
+
+ if (!NuGetVersion.TryParse(createFullVersion, out NuGetVersion pkgVersion))
+ {
+ var message = String.Format("{0} package could not be installed with error: could not parse package '{0}' version '{1} into a NuGetVersion",
+ pkg.Name,
+ pkg.Version.ToString());
+ var ex = new ArgumentException(message);
+ var packageIdentityVersionParseError = new ErrorRecord(ex, "psdataFileNotExistError", ErrorCategory.ReadError, null);
+ _cmdletPassedIn.WriteError(packageIdentityVersionParseError);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ continue;
+ }
+
+ var pkgIdentity = new PackageIdentity(pkg.Name, pkgVersion);
+ var cacheContext = new SourceCacheContext();
+
+ if (isLocalRepo)
+ {
+ /* Download from a local repository -- this is slightly different process than from a server */
+ var localResource = new FindLocalPackagesResourceV2(repoUri);
+ var resource = new LocalDownloadResource(repoUri, localResource);
+
+ // Actually downloading the .nupkg from a local repo
+ var result = resource.GetDownloadResourceResultAsync(
+ identity: pkgIdentity,
+ downloadContext: new PackageDownloadContext(cacheContext),
+ globalPackagesFolder: tempInstallPath,
+ logger: NullLogger.Instance,
+ token: _cancellationToken).GetAwaiter().GetResult();
+
+ // Create the package extraction context
+ PackageExtractionContext packageExtractionContext = new PackageExtractionContext(
+ packageSaveMode: PackageSaveMode.Nupkg,
+ xmlDocFileSaveMode: PackageExtractionBehavior.XmlDocFileSaveMode,
+ clientPolicyContext: null,
+ logger: NullLogger.Instance);
+
+ // Extracting from .nupkg and placing files into tempInstallPath
+ result.PackageReader.CopyFiles(
+ destination: tempInstallPath,
+ packageFiles: result.PackageReader.GetFiles(),
+ extractFile: new PackageFileExtractor(
+ result.PackageReader.GetFiles(),
+ packageExtractionContext.XmlDocFileSaveMode).ExtractPackageFile,
+ logger: NullLogger.Instance,
+ token: _cancellationToken);
+ result.Dispose();
+ }
+ else
+ {
+ /* Download from a non-local repository */
+ // Set up NuGet API resource for download
+ PackageSource source = new PackageSource(repoUri);
+
+ // Explicitly passed in Credential takes precedence over repository CredentialInfo
+ if (credential != null)
+ {
+ string password = new NetworkCredential(string.Empty, credential.Password).Password;
+ source.Credentials = PackageSourceCredential.FromUserInput(repoUri, credential.UserName, password, true, null);
+ }
+ else if (repoCredentialInfo != null)
+ {
+ PSCredential repoCredential = Utils.GetRepositoryCredentialFromSecretManagement(
+ repoName,
+ repoCredentialInfo,
+ _cmdletPassedIn);
+
+ string password = new NetworkCredential(string.Empty, repoCredential.Password).Password;
+ source.Credentials = PackageSourceCredential.FromUserInput(repoUri, repoCredential.UserName, password, true, null);
+ }
+ var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider);
+ SourceRepository repository = new SourceRepository(source, provider);
+
+ /* Download from a non-local repository -- ie server */
+ var downloadResource = repository.GetResourceAsync().GetAwaiter().GetResult();
+ DownloadResourceResult result = null;
+ try
+ {
+ result = downloadResource.GetDownloadResourceResultAsync(
+ identity: pkgIdentity,
+ downloadContext: new PackageDownloadContext(cacheContext),
+ globalPackagesFolder: tempInstallPath,
+ logger: NullLogger.Instance,
+ token: _cancellationToken).GetAwaiter().GetResult();
+ }
+ catch (Exception e)
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Error attempting download: '{0}'", e.Message));
+ }
+ finally
+ {
+ // Need to close the .nupkg
+ if (result != null) result.Dispose();
+ }
+ }
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath));
+
+ // pkgIdentity.Version.Version gets the version without metadata or release labels.
+ string newVersion = pkgIdentity.Version.ToNormalizedString();
+ string normalizedVersionNoPrerelease = newVersion;
+ if (pkgIdentity.Version.IsPrerelease)
+ {
+ // eg: 2.0.2
+ normalizedVersionNoPrerelease = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-'));
+ }
+
+ string tempDirNameVersion = isLocalRepo ? tempInstallPath : Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion);
+ var version4digitNoPrerelease = pkgIdentity.Version.Version.ToString();
+ string moduleManifestVersion = string.Empty;
+ var scriptPath = Path.Combine(tempDirNameVersion, pkg.Name + PSScriptFileExt);
+ var modulePath = Path.Combine(tempDirNameVersion, pkg.Name + PSDataFileExt);
+ // Check if the package is a module or a script
+ var isModule = File.Exists(modulePath);
+
+ string installPath;
+ if (_savePkg)
+ {
+ // For save the installation path is what is passed in via -Path
+ installPath = _pathsToInstallPkg.FirstOrDefault();
+
+ // If saving as nupkg simply copy the nupkg and move onto next iteration of loop
+ // asNupkg functionality only applies to Save-PSResource
+ if (_asNupkg)
+ {
+ var nupkgFile = pkgIdentity.ToString().ToLower() + ".nupkg";
+ File.Copy(Path.Combine(tempDirNameVersion, nupkgFile), Path.Combine(installPath, nupkgFile));
+
+ _cmdletPassedIn.WriteVerbose(string.Format("'{0}' moved into file path '{1}'", nupkgFile, installPath));
+ pkgsSuccessfullyInstalled.Add(pkg);
+
+ continue;
+ }
+ }
+ else
+ {
+ // PSModules:
+ /// ./Modules
+ /// ./Scripts
+ /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list
+ installPath = isModule ? _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase))
+ : _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase));
+ }
+
+ if (_authenticodeCheck && !AuthenticodeSignature.CheckAuthenticodeSignature(pkg.Name, tempDirNameVersion, _versionRange, _pathsToSearch, installPath, _cmdletPassedIn, out ErrorRecord errorRecord))
+ {
+ ThrowTerminatingError(errorRecord);
+ }
+
+ if (isModule)
+ {
+ var moduleManifest = Path.Combine(tempDirNameVersion, pkgIdentity.Id + PSDataFileExt);
+ if (!File.Exists(moduleManifest))
+ {
+ var message = String.Format("{0} package could not be installed with error: Module manifest file: {1} does not exist. This is not a valid PowerShell module.", pkgIdentity.Id, moduleManifest);
+
+ var ex = new ArgumentException(message);
+ var psdataFileDoesNotExistError = new ErrorRecord(ex, "psdataFileNotExistError", ErrorCategory.ReadError, null);
+ _cmdletPassedIn.WriteError(psdataFileDoesNotExistError);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ continue;
+ }
+
+ if (!Utils.TryParsePSDataFile(moduleManifest, _cmdletPassedIn, out Hashtable parsedMetadataHashtable))
+ {
+ // Ran into errors parsing the module manifest file which was found in Utils.ParseModuleManifest() and written.
+ continue;
+ }
+
+ moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string;
+
+ // Accept License verification
+ if (!_savePkg && !CallAcceptLicense(pkg, moduleManifest, tempInstallPath, newVersion))
+ {
+ continue;
+ }
+
+ // If NoClobber is specified, ensure command clobbering does not happen
+ if (_noClobber && !DetectClobber(pkg.Name, parsedMetadataHashtable))
+ {
+ continue;
+ }
+ }
+
+ // Delete the extra nupkg related files that are not needed and not part of the module/script
+ DeleteExtraneousFiles(pkgIdentity, tempDirNameVersion);
+
+ if (_includeXML)
+ {
+ CreateMetadataXMLFile(tempDirNameVersion, installPath, pkg, isModule);
+ }
+
+ MoveFilesIntoInstallPath(
+ pkg,
+ isModule,
+ isLocalRepo,
+ tempDirNameVersion,
+ tempInstallPath,
+ installPath,
+ newVersion,
+ moduleManifestVersion,
+ scriptPath);
+
+ _cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}' to location '{1}'", pkg.Name, installPath));
+ pkgsSuccessfullyInstalled.Add(pkg);
+ }
+ catch (Exception e)
+ {
+ _cmdletPassedIn.WriteError(
+ new ErrorRecord(
+ new PSInvalidOperationException(
+ message: $"Unable to successfully install package '{pkg.Name}': '{e.Message}'",
+ innerException: e),
+ "InstallPackageFailed",
+ ErrorCategory.InvalidOperation,
+ _cmdletPassedIn));
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ }
+ finally
+ {
+ // Delete the temp directory and all its contents
+ _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete '{0}'", tempInstallPath));
+
+ if (Directory.Exists(tempInstallPath))
+ {
+ if (!TryDeleteDirectory(tempInstallPath, out ErrorRecord errorMsg))
+ {
+ _cmdletPassedIn.WriteError(errorMsg);
+ }
+ else
+ {
+ _cmdletPassedIn.WriteVerbose(String.Format("Successfully deleted '{0}'", tempInstallPath));
+ }
+ }
+ }
+ }
+
+ return pkgsSuccessfullyInstalled;
+ }
+
+ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion)
+ {
+ var requireLicenseAcceptance = false;
+ var success = true;
+
+ if (File.Exists(moduleManifest))
+ {
+ using (StreamReader sr = new StreamReader(moduleManifest))
+ {
+ var text = sr.ReadToEnd();
+
+ var pattern = "RequireLicenseAcceptance\\s*=\\s*\\$true";
+ var patternToSkip1 = "#\\s*RequireLicenseAcceptance\\s*=\\s*\\$true";
+ var patternToSkip2 = "\\*\\s*RequireLicenseAcceptance\\s*=\\s*\\$true";
+
+ Regex rgx = new Regex(pattern);
+ Regex rgxComment1 = new Regex(patternToSkip1);
+ Regex rgxComment2 = new Regex(patternToSkip2);
+ if (rgx.IsMatch(text) && !rgxComment1.IsMatch(text) && !rgxComment2.IsMatch(text))
+ {
+ requireLicenseAcceptance = true;
+ }
+ }
+
+ // Licesnse agreement processing
+ if (requireLicenseAcceptance)
+ {
+ // If module requires license acceptance and -AcceptLicense is not passed in, display prompt
+ if (!_acceptLicense)
+ {
+ var PkgTempInstallPath = Path.Combine(tempInstallPath, p.Name, newVersion);
+ var LicenseFilePath = Path.Combine(PkgTempInstallPath, "License.txt");
+
+ if (!File.Exists(LicenseFilePath))
+ {
+ var exMessage = String.Format("{0} package could not be installed with error: License.txt not found. License.txt must be provided when user license acceptance is required.", p.Name);
+ var ex = new ArgumentException(exMessage);
+ var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null);
+
+ _cmdletPassedIn.WriteError(acceptLicenseError);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase));
+ success = false;
+ }
+
+ // Otherwise read LicenseFile
+ string licenseText = System.IO.File.ReadAllText(LicenseFilePath);
+ var acceptanceLicenseQuery = $"Do you accept the license terms for module '{p.Name}'.";
+ var message = licenseText + "`r`n" + acceptanceLicenseQuery;
+
+ var title = "License Acceptance";
+ var yesToAll = false;
+ var noToAll = false;
+ var shouldContinueResult = _cmdletPassedIn.ShouldContinue(message, title, true, ref yesToAll, ref noToAll);
+
+ if (shouldContinueResult || yesToAll)
+ {
+ _acceptLicense = true;
+ }
+ }
+
+ // Check if user agreed to license terms, if they didn't then throw error, otherwise continue to install
+ if (!_acceptLicense)
+ {
+ var message = String.Format("{0} package could not be installed with error: License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation.", p.Name);
+ var ex = new ArgumentException(message);
+ var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null);
+
+ _cmdletPassedIn.WriteError(acceptLicenseError);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(p.Name, StringComparison.InvariantCultureIgnoreCase));
+ success = false;
+ }
+ }
+ }
+
+ return success;
+ }
+
+ private bool DetectClobber(string pkgName, Hashtable parsedMetadataHashtable)
+ {
+ // Get installed modules, then get all possible paths
+ bool foundClobber = false;
+ GetHelper getHelper = new GetHelper(_cmdletPassedIn);
+ // selectPrereleaseOnly is false because even if Prerelease is true we want to include both stable and prerelease, never select prerelease only.
+ IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath(
+ name: new string[] { "*" },
+ versionRange: VersionRange.All,
+ pathsToSearch: _pathsToSearch,
+ selectPrereleaseOnly: false);
+ // user parsed metadata hash
+ List listOfCmdlets = new List();
+ foreach (var cmdletName in parsedMetadataHashtable["CmdletsToExport"] as object[])
+ {
+ listOfCmdlets.Add(cmdletName as string);
+
+ }
+
+ foreach (var pkg in pkgsAlreadyInstalled)
+ {
+ List duplicateCmdlets = new List();
+ List duplicateCmds = new List();
+ // 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(
+ "{1} package could not be installed with error: 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);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase));
+ foundClobber = true;
+
+ return foundClobber;
+ }
+ }
+
+ return foundClobber;
+ }
+
+ private void CreateMetadataXMLFile(string dirNameVersion, string installPath, PSResourceInfo pkg, bool isModule)
+ {
+ // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml"
+ // Modules will have the metadata file: "PSGetModuleInfo.xml"
+ var metadataXMLPath = isModule ? Path.Combine(dirNameVersion, "PSGetModuleInfo.xml")
+ : Path.Combine(dirNameVersion, (pkg.Name + "_InstalledScriptInfo.xml"));
+
+ pkg.InstalledDate = DateTime.Now;
+ pkg.InstalledLocation = installPath;
+
+ // Write all metadata into metadataXMLPath
+ if (!pkg.TryWrite(metadataXMLPath, out string error))
+ {
+ var message = string.Format("{0} package could not be installed with error: Error parsing metadata into XML: '{1}'", pkg.Name, error);
+ var ex = new ArgumentException(message);
+ var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null);
+
+ _cmdletPassedIn.WriteError(ErrorParsingMetadata);
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase));
+ }
+ }
+
+ private void DeleteExtraneousFiles(PackageIdentity pkgIdentity, string dirNameVersion)
+ {
+ // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module
+ var pkgIdString = pkgIdentity.ToString();
+ var nupkgSHAToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg.sha512");
+ var nuspecToDelete = Path.Combine(dirNameVersion, pkgIdentity.Id + ".nuspec");
+ var nupkgToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg");
+ var nupkgMetadataToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg.metadata");
+ var contentTypesToDelete = Path.Combine(dirNameVersion, "[Content_Types].xml");
+ var relsDirToDelete = Path.Combine(dirNameVersion, "_rels");
+ var packageDirToDelete = Path.Combine(dirNameVersion, "package");
+
+ // Unforunately have to check if each file exists because it may or may not be there
+ if (File.Exists(nupkgSHAToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgSHAToDelete));
+ File.Delete(nupkgSHAToDelete);
+ }
+ if (File.Exists(nuspecToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nuspecToDelete));
+ File.Delete(nuspecToDelete);
+ }
+ if (File.Exists(nupkgToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgToDelete));
+ File.Delete(nupkgToDelete);
+ }
+ if (File.Exists(nupkgMetadataToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", nupkgMetadataToDelete));
+ File.Delete(nupkgMetadataToDelete);
+ }
+ if (File.Exists(contentTypesToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", contentTypesToDelete));
+ File.Delete(contentTypesToDelete);
+ }
+ if (Directory.Exists(relsDirToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", relsDirToDelete));
+ Utils.DeleteDirectory(relsDirToDelete);
+ }
+ if (Directory.Exists(packageDirToDelete))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting '{0}'", packageDirToDelete));
+ Utils.DeleteDirectory(packageDirToDelete);
+ }
+ }
+
+ private bool TryDeleteDirectory(
+ string tempInstallPath,
+ out ErrorRecord errorMsg)
+ {
+ errorMsg = null;
+
+ try
+ {
+ Utils.DeleteDirectory(tempInstallPath);
+ }
+ catch (Exception e)
+ {
+ var TempDirCouldNotBeDeletedError = new ErrorRecord(e, "errorDeletingTempInstallPath", ErrorCategory.InvalidResult, null);
+ errorMsg = TempDirCouldNotBeDeletedError;
+ return false;
+ }
+
+ return true;
+ }
+
+ private void MoveFilesIntoInstallPath(
+ PSResourceInfo pkgInfo,
+ bool isModule,
+ bool isLocalRepo,
+ string dirNameVersion,
+ string tempInstallPath,
+ string installPath,
+ string newVersion,
+ string moduleManifestVersion,
+ string scriptPath)
+ {
+ // Creating the proper installation path depending on whether pkg is a module or script
+ var newPathParent = isModule ? Path.Combine(installPath, pkgInfo.Name) : installPath;
+ var finalModuleVersionDir = isModule ? Path.Combine(installPath, pkgInfo.Name, moduleManifestVersion) : installPath;
+
+ // If script, just move the files over, if module, move the version directory over
+ var tempModuleVersionDir = (!isModule || isLocalRepo) ? dirNameVersion
+ : Path.Combine(tempInstallPath, pkgInfo.Name.ToLower(), newVersion);
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Installation source path is: '{0}'", tempModuleVersionDir));
+ _cmdletPassedIn.WriteVerbose(string.Format("Installation destination path is: '{0}'", finalModuleVersionDir));
+
+ if (isModule)
+ {
+ // If new path does not exist
+ if (!Directory.Exists(newPathParent))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir));
+ Directory.CreateDirectory(newPathParent);
+ Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir);
+ }
+ else
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir));
+
+ if (Directory.Exists(finalModuleVersionDir))
+ {
+ // Delete the directory path before replacing it with the new module.
+ // If deletion fails (usually due to binary file in use), then attempt restore so that the currently
+ // installed module is not corrupted.
+ _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete with restore on failure.'{0}'", finalModuleVersionDir));
+ Utils.DeleteDirectoryWithRestore(finalModuleVersionDir);
+ }
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir));
+ Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir);
+ }
+ }
+ else
+ {
+ if (!_savePkg)
+ {
+ // Need to delete old xml files because there can only be 1 per script
+ var scriptXML = pkgInfo.Name + "_InstalledScriptInfo.xml";
+ _cmdletPassedIn.WriteVerbose(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))));
+ if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting script metadata XML"));
+ File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML));
+ }
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)));
+ Utils.MoveFiles(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML));
+
+ // Need to delete old script file, if that exists
+ _cmdletPassedIn.WriteVerbose(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt))));
+ if (File.Exists(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)))
+ {
+ _cmdletPassedIn.WriteVerbose(string.Format("Deleting script file"));
+ File.Delete(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt));
+ }
+ }
+
+ _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)));
+ Utils.MoveFiles(scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt));
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs
index c08987d26..98c64a346 100644
--- a/src/code/InstallPSResource.cs
+++ b/src/code/InstallPSResource.cs
@@ -103,6 +103,12 @@ class InstallPSResource : PSCmdlet
///
[Parameter]
public SwitchParameter SkipDependencyCheck { get; set; }
+
+ ///
+ /// Check validation for signed and catalog files
+ ///
+ [Parameter]
+ public SwitchParameter AuthenticodeCheck { get; set; }
///
/// Passes the resource installed to the console.
@@ -310,7 +316,7 @@ protected override void ProcessRecord()
{
requiredResourceFileStream = sr.ReadToEnd();
}
-
+
Hashtable pkgsInFile = null;
try
{
@@ -513,6 +519,7 @@ private void ProcessInstallHelper(string[] pkgNames, VersionRange pkgVersion, bo
asNupkg: false,
includeXML: true,
skipDependencyCheck: SkipDependencyCheck,
+ authenticodeCheck: AuthenticodeCheck,
savePkg: false,
pathsToInstallPkg: _pathsToInstallPkg);
diff --git a/src/code/SavePSResource.cs b/src/code/SavePSResource.cs
index a7e17e3ab..73b6d1c19 100644
--- a/src/code/SavePSResource.cs
+++ b/src/code/SavePSResource.cs
@@ -131,6 +131,13 @@ public string Path
[Parameter]
public SwitchParameter SkipDependencyCheck { get; set; }
+ ///
+ /// Check validation for signed and catalog files
+
+ ///
+ [Parameter]
+ public SwitchParameter AuthenticodeCheck { get; set; }
+
///
/// Suppresses progress information.
///
@@ -259,6 +266,7 @@ private void ProcessSaveHelper(string[] pkgNames, bool pkgPrerelease, string[] p
asNupkg: AsNupkg,
includeXML: IncludeXML,
skipDependencyCheck: SkipDependencyCheck,
+ authenticodeCheck: AuthenticodeCheck,
savePkg: true,
pathsToInstallPkg: new List { _path });
diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs
index 79e5e7f02..5ca79d529 100644
--- a/src/code/UpdatePSResource.cs
+++ b/src/code/UpdatePSResource.cs
@@ -111,6 +111,13 @@ public sealed class UpdatePSResource : PSCmdlet
[Parameter]
public SwitchParameter SkipDependencyCheck { get; set; }
+ ///
+ /// Check validation for signed and catalog files
+
+ ///
+ [Parameter]
+ public SwitchParameter AuthenticodeCheck { get; set; }
+
#endregion
#region Override Methods
@@ -178,6 +185,7 @@ protected override void ProcessRecord()
asNupkg: false,
includeXML: true,
skipDependencyCheck: SkipDependencyCheck,
+ authenticodeCheck: AuthenticodeCheck,
savePkg: false,
pathsToInstallPkg: _pathsToInstallPkg);
diff --git a/src/code/Utils.cs b/src/code/Utils.cs
index 07c74fc12..9ab09b3ce 100644
--- a/src/code/Utils.cs
+++ b/src/code/Utils.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using Microsoft.Win32.SafeHandles;
using NuGet.Versioning;
using System;
using System.Collections;
@@ -14,6 +15,7 @@
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using System.Security;
+using System.Security.Cryptography.X509Certificates;
namespace Microsoft.PowerShell.PowerShellGet.UtilClasses
{
@@ -1126,7 +1128,117 @@ public static Collection InvokeScriptWithHost(
}
#endregion Methods
- }
-
+ }
+
+ #endregion
+
+ #region AuthenticodeSignature
+
+ internal static class AuthenticodeSignature
+ {
+ #region Methods
+
+ internal static bool CheckAuthenticodeSignature(string pkgName, string tempDirNameVersion, VersionRange versionRange, List pathsToSearch, string installPath, PSCmdlet cmdletPassedIn, out ErrorRecord errorRecord)
+ {
+ errorRecord = null;
+
+ // Because authenticode and catalog verifications are only applicable on Windows, we allow all packages by default to be installed on unix systems.
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return true;
+ }
+
+ // Check that the catalog file is signed properly
+ string catalogFilePath = Path.Combine(tempDirNameVersion, pkgName + ".cat");
+ if (File.Exists(catalogFilePath))
+ {
+ // Run catalog validation
+ Collection TestFileCatalogResult = new Collection();
+ string moduleBasePath = tempDirNameVersion;
+ try
+ {
+ // By default "Test-FileCatalog will look through all files in the provided directory, -FilesToSkip allows us to ignore specific files
+ TestFileCatalogResult = cmdletPassedIn.InvokeCommand.InvokeScript(
+ script: @"param (
+ [string] $moduleBasePath,
+ [string] $catalogFilePath
+ )
+ $catalogValidation = Test-FileCatalog -Path $moduleBasePath -CatalogFilePath $CatalogFilePath `
+ -FilesToSkip '*.nupkg','*.nuspec', '*.nupkg.metadata', '*.nupkg.sha512' `
+ -Detailed -ErrorAction SilentlyContinue
+
+ if ($catalogValidation.Status.ToString() -eq 'valid' -and $catalogValidation.Signature.Status -eq 'valid') {
+ return $true
+ }
+ else {
+ return $false
+ }
+ ",
+ useNewScope: true,
+ writeToPipeline: System.Management.Automation.Runspaces.PipelineResultTypes.None,
+ input: null,
+ args: new object[] { moduleBasePath, catalogFilePath });
+ }
+ catch (Exception e)
+ {
+ errorRecord = new ErrorRecord(new ArgumentException(e.Message), "TestFileCatalogError", ErrorCategory.InvalidResult, cmdletPassedIn);
+ return false;
+ }
+
+ bool catalogValidation = (TestFileCatalogResult[0] != null) ? (bool)TestFileCatalogResult[0].BaseObject : false;
+ if (!catalogValidation)
+ {
+ var exMessage = String.Format("The catalog file '{0}' is invalid.", pkgName + ".cat");
+ var ex = new ArgumentException(exMessage);
+
+ errorRecord = new ErrorRecord(ex, "TestFileCatalogError", ErrorCategory.InvalidResult, cmdletPassedIn);
+ return false;
+ }
+ }
+
+ Collection authenticodeSignature = new Collection();
+ try
+ {
+ string[] listOfExtensions = { "*.ps1", "*.psd1", "*.psm1", "*.mof", "*.cat", "*.ps1xml" };
+ authenticodeSignature = cmdletPassedIn.InvokeCommand.InvokeScript(
+ script: @"param (
+ [string] $tempDirNameVersion,
+ [string[]] $listOfExtensions
+ )
+ Get-ChildItem $tempDirNameVersion -Recurse -Include $listOfExtensions | Get-AuthenticodeSignature -ErrorAction SilentlyContinue",
+ useNewScope: true,
+ writeToPipeline: System.Management.Automation.Runspaces.PipelineResultTypes.None,
+ input: null,
+ args: new object[] { tempDirNameVersion, listOfExtensions });
+ }
+ catch (Exception e)
+ {
+ errorRecord = new ErrorRecord(new ArgumentException(e.Message), "GetAuthenticodeSignatureError", ErrorCategory.InvalidResult, cmdletPassedIn);
+ return false;
+ }
+
+ // If the authenticode signature is not valid, return false
+ if (authenticodeSignature.Any() && authenticodeSignature[0] != null)
+ {
+ foreach (var sign in authenticodeSignature)
+ {
+ Signature signature = (Signature)sign.BaseObject;
+ if (!signature.Status.Equals(SignatureStatus.Valid))
+ {
+ var exMessage = String.Format("The signature for '{0}' is '{1}.", pkgName, signature.Status.ToString());
+ var ex = new ArgumentException(exMessage);
+ errorRecord = new ErrorRecord(ex, "GetAuthenticodeSignatureError", ErrorCategory.InvalidResult, cmdletPassedIn);
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+
#endregion
}
diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1
index e887265ed..2f82c537e 100644
--- a/test/InstallPSResource.Tests.ps1
+++ b/test/InstallPSResource.Tests.ps1
@@ -12,6 +12,7 @@ Describe 'Test Install-PSResource for Module' {
$testModuleName = "test_module"
$testModuleName2 = "TestModule99"
$testScriptName = "test_script"
+ $PackageManagement = "PackageManagement"
$RequiredResourceJSONFileName = "TestRequiredResourceFile.json"
$RequiredResourcePSD1FileName = "TestRequiredResourceFile.psd1"
Get-NewPSResourceRepositoryFile
@@ -19,7 +20,7 @@ Describe 'Test Install-PSResource for Module' {
}
AfterEach {
- Uninstall-PSResource "test_module", "test_module2", "test_script", "TestModule99", "testModuleWithlicense", "TestFindModule","ClobberTestModule1", "ClobberTestModule2" -SkipDependencyCheck -ErrorAction SilentlyContinue
+ Uninstall-PSResource "test_module", "test_module2", "test_script", "TestModule99", "testModuleWithlicense", "TestFindModule","ClobberTestModule1", "ClobberTestModule2", "PackageManagement" -SkipDependencyCheck -ErrorAction SilentlyContinue
}
AfterAll {
@@ -415,6 +416,56 @@ Describe 'Test Install-PSResource for Module' {
$res3.Name | Should -Be $testModuleName2
$res3.Version | Should -Be "0.0.93.0"
}
+
+ # Install module 1.4.3 (is authenticode signed and has catalog file)
+ # Should install successfully
+ It "Install modules with catalog file using publisher validation" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.3" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource $PackageManagement -Version "1.4.3"
+ $res1.Name | Should -Be $PackageManagement
+ $res1.Version | Should -Be "1.4.3.0"
+ }
+
+ # Install module 1.4.7 (is authenticode signed and has no catalog file)
+ # Should not install successfully
+ It "Install module with no catalog file" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.7" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource $PackageManagement -Version "1.4.7"
+ $res1.Name | Should -Be $PackageManagement
+ $res1.Version | Should -Be "1.4.7.0"
+ }
+
+ # Install module that is not authenticode signed
+ # Should FAIL to install the module
+ It "Install module that is not authenticode signed" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $testModuleName -Version "5.0.0" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.InstallPSResource"
+ }
+ # Install 1.4.4.1 (with incorrect catalog file)
+ # Should FAIL to install the module
+ It "Install module with incorrect catalog file" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.4.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.InstallPSResource"
+ }
+
+ # Install script that is signed
+ # Should install successfully
+ It "Install script that is authenticode signed" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name "Install-VSCode" -Version "1.4.2" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource "Install-VSCode" -Version "1.4.2"
+ $res1.Name | Should -Be "Install-VSCode"
+ $res1.Version | Should -Be "1.4.2.0"
+ }
+
+ # Install script that is not signed
+ # Should throw
+ It "Install script that is not signed" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name "TestTestScript" -Version "1.3.1.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.InstallPSResource"
+ }
}
<# Temporarily commented until -Tag is implemented for this Describe block
diff --git a/test/SavePSResource.Tests.ps1 b/test/SavePSResource.Tests.ps1
index 8160144bc..1c3b612e1 100644
--- a/test/SavePSResource.Tests.ps1
+++ b/test/SavePSResource.Tests.ps1
@@ -12,6 +12,7 @@ Describe 'Test Save-PSResource for PSResources' {
$testModuleName = "test_module"
$testScriptName = "test_script"
$testModuleName2 = "testmodule99"
+ $PackageManagement = "PackageManagement"
Get-NewPSResourceRepositoryFile
Register-LocalRepos
@@ -217,6 +218,61 @@ Describe 'Test Save-PSResource for PSResources' {
$res.Name | Should -Be $testModuleName
$res.Version | Should -Be "1.0.0.0"
}
+
+ # Save module 1.4.3 (is authenticode signed and has catalog file)
+ # Should save successfully
+ It "Save modules with catalog file using publisher validation" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name $PackageManagement -Version "1.4.3" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -Path $SaveDir
+
+ $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq $PackageManagement
+ $pkgDir | Should -Not -BeNullOrEmpty
+ $pkgDirVersion = Get-ChildItem -Path $pkgDir.FullName
+ $pkgDirVersion.Name | Should -Be "1.4.3"
+ }
+
+ # Save module 1.4.7 (is authenticode signed and has NO catalog file)
+ # Should save successfully
+ It "Save module with no catalog file" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name $PackageManagement -Version "1.4.7" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -Path $SaveDir
+
+ $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq $PackageManagement
+ $pkgDir | Should -Not -BeNullOrEmpty
+ $pkgDirVersion = Get-ChildItem -Path $pkgDir.FullName
+ $pkgDirVersion.Name | Should -Be "1.4.7"
+ }
+
+ # Save module that is not authenticode signed
+ # Should FAIL to save the module
+ It "Save module that is not authenticode signed" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name $testModuleName -Version "5.0.0" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -Path $SaveDir -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.SavePSResource"
+ }
+
+ # Save 1.4.4.1 (with incorrect catalog file)
+ # Should FAIL to save the module
+ It "Save module with incorrect catalog file" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name $PackageManagement -Version "1.4.4.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -Path $SaveDir -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.SavePSResource"
+ }
+
+ # Save script that is signed
+ # Should save successfully
+ It "Save script that is authenticode signed" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name "Install-VSCode" -Version "1.4.2" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -Path $SaveDir
+
+ $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "Install-VSCode.ps1"
+ $pkgDir | Should -Not -BeNullOrEmpty
+ $pkgName = Get-ChildItem -Path $pkgDir.FullName
+ $pkgName.Name | Should -Be "Install-VSCode.ps1"
+ }
+
+ # Save script that is not signed
+ # Should throw
+ It "Save script that is not signed" -Skip:(!(Get-IsWindows)) {
+ Save-PSResource -Name "TestTestScript" -Version "1.3.1.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.SavePSResource"
+ }
+
<#
# Tests should not write to module directory
It "Save specific module resource by name if no -Path param is specifed" {
diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1
index 4c3bf082b..0bbbdc2da 100644
--- a/test/UpdatePSResource.Tests.ps1
+++ b/test/UpdatePSResource.Tests.ps1
@@ -13,12 +13,13 @@ Describe 'Test Update-PSResource' {
$testModuleName = "test_module"
$testModuleName2 = "test_module2"
$testModuleName3 = "TestModule99"
+ $PackageManagement = "PackageManagement"
Get-NewPSResourceRepositoryFile
Get-PSResourceRepository
}
AfterEach {
- Uninstall-PSResource "test_module", "TestModule99", "TestModuleWithLicense", "test_module2", "test_script"
+ Uninstall-PSResource "test_module", "TestModule99", "TestModuleWithLicense", "test_module2", "test_script", "PackaeManagement" -Version "*"
}
AfterAll {
@@ -324,4 +325,53 @@ Describe 'Test Update-PSResource' {
$res.Name | Should -Contain $testModuleName
$res.Version | Should -Contain "3.0.0.0"
}
+
+ # Update to module 1.4.3 (is authenticode signed and has catalog file)
+ # Should update successfully
+ It "Update module with catalog file using publisher validation" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.2" -Repository $PSGalleryName -TrustRepository
+ Update-PSResource -Name $PackageManagement -Version "1.4.3" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource $PackageManagement -Version "1.4.3"
+ $res1.Name | Should -Be $PackageManagement
+ $res1.Version | Should -Be "1.4.3.0"
+ }
+
+ # Update to module 1.4.7 (is authenticode signed and has NO catalog file)
+ # Should update successfully
+ It "Install module with no catalog file" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.2" -Repository $PSGalleryName -TrustRepository
+ Update-PSResource -Name $PackageManagement -Version "1.4.7" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource $PackageManagement -Version "1.4.7"
+ $res1.Name | Should -Be $PackageManagement
+ $res1.Version | Should -Be "1.4.7.0"
+ }
+
+ # Update to module 1.4.4.1 (with incorrect catalog file)
+ # Should FAIL to update the module
+ It "Update module with incorrect catalog file" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name $PackageManagement -Version "1.4.2" -Repository $PSGalleryName -TrustRepository
+ Update-PSResource -Name $PackageManagement -Version "1.4.4.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSResource"
+ }
+
+ # Update script that is signed
+ # Should update successfully
+ It "Update script that is authenticode signed" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name "Install-VSCode" -Version "1.4.1" -Repository $PSGalleryName -TrustRepository
+ Update-PSResource -Name "Install-VSCode" -Version "1.4.2" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository
+
+ $res1 = Get-PSResource "Install-VSCode" -Version "1.4.2"
+ $res1.Name | Should -Be "Install-VSCode"
+ $res1.Version | Should -Be "1.4.2.0"
+ }
+
+ # Update script that is not signed
+ # Should throw
+ It "Update script that is not signed" -Skip:(!(Get-IsWindows)) {
+ Install-PSResource -Name "TestTestScript" -Version "1.0" -Repository $PSGalleryName -TrustRepository
+ Update-PSResource -Name "TestTestScript" -Version "1.3.1.1" -AuthenticodeCheck -Repository $PSGalleryName -TrustRepository -ErrorAction SilentlyContinue
+ $Error[0].FullyQualifiedErrorId | Should -be "InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.UpdatePSResource"
+ }
}