// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.PowerShell.PSResourceGet.UtilClasses; using NuGet.Versioning; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Management.Automation; using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; namespace Microsoft.PowerShell.PSResourceGet.Cmdlets { /// /// Install helper class /// internal class InstallHelper { #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 const string ScriptPATHWarning = "The installation path for the script does not currently appear in the {0} path environment variable. To make the script discoverable, add the script installation path, {1}, to the environment PATH variable."; private CancellationToken _cancellationToken; private readonly PSCmdlet _cmdletPassedIn; private List _pathsToInstallPkg; private VersionRange _versionRange; private NuGetVersion _nugetVersion; private VersionType _versionType; private string _versionString; private bool _prerelease; private bool _acceptLicense; private bool _quiet; private bool _reinstall; private bool _force; private bool _trustRepository; private bool _asNupkg; private bool _includeXml; private bool _noClobber; private bool _authenticodeCheck; private bool _savePkg; List _pathsToSearch; List _pkgNamesToInstall; private string _tmpPath; private NetworkCredential _networkCredential; private HashSet _packagesOnMachine; #endregion #region Public Methods public InstallHelper(PSCmdlet cmdletPassedIn, NetworkCredential networkCredential) { CancellationTokenSource source = new(); _cancellationToken = source.Token; _cmdletPassedIn = cmdletPassedIn; _networkCredential = networkCredential; } /// /// This method calls is the starting point for install processes and is called by Install, Update and Save cmdlets. /// public IEnumerable BeginInstallPackages( string[] names, VersionRange versionRange, NuGetVersion nugetVersion, VersionType versionType, string versionString, bool prerelease, string[] repository, bool acceptLicense, bool quiet, bool reinstall, bool force, bool trustRepository, bool noClobber, bool asNupkg, bool includeXml, bool skipDependencyCheck, bool authenticodeCheck, bool savePkg, List pathsToInstallPkg, ScopeType? scope, string tmpPath, HashSet pkgsInstalled) { _cmdletPassedIn.WriteDebug("In InstallHelper::BeginInstallPackages()"); _cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; VersionRange: '{1}'; NuGetVersion: '{2}'; VersionType: '{3}'; Version: '{4}'; Prerelease: '{5}'; Repository: '{6}'; " + "AcceptLicense: '{7}'; Quiet: '{8}'; Reinstall: '{9}'; TrustRepository: '{10}'; NoClobber: '{11}'; AsNupkg: '{12}'; IncludeXml '{13}'; SavePackage '{14}'; TemporaryPath '{15}'; SkipDependencyCheck: '{16}'; " + "AuthenticodeCheck: '{17}'; PathsToInstallPkg: '{18}'; Scope '{19}'", string.Join(",", names), versionRange != null ? (versionRange.OriginalString != null ? versionRange.OriginalString : string.Empty) : string.Empty, nugetVersion != null ? nugetVersion.ToString() : string.Empty, versionType.ToString(), versionString != null ? versionString : 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(), tmpPath ?? string.Empty, skipDependencyCheck, authenticodeCheck, pathsToInstallPkg != null ? string.Join(",", pathsToInstallPkg) : string.Empty, scope?.ToString() ?? string.Empty)); _versionRange = versionRange; _nugetVersion = nugetVersion; _versionType = versionType; _versionString = versionString ?? String.Empty; _prerelease = prerelease; _acceptLicense = acceptLicense || force; _authenticodeCheck = authenticodeCheck; _quiet = quiet; _reinstall = reinstall; _force = force; _trustRepository = trustRepository || force; _noClobber = noClobber; _asNupkg = asNupkg; _includeXml = includeXml; _savePkg = savePkg; _pathsToInstallPkg = pathsToInstallPkg; _tmpPath = tmpPath ?? Path.GetTempPath(); if (_versionRange == VersionRange.All) { _versionType = VersionType.NoVersion; } // Create list of installation paths to search. _pathsToSearch = new List(); _pkgNamesToInstall = names.ToList(); _packagesOnMachine = pkgsInstalled; // _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 List installedPkgs = ProcessRepositories( repository: repository, trustRepository: _trustRepository, skipDependencyCheck: skipDependencyCheck, scope: scope ?? ScopeType.CurrentUser); return installedPkgs; } #endregion #region Private methods /// /// This method calls iterates through repositories (by priority order) to search for the packages to install. /// It calls HTTP or NuGet API based install helper methods, according to repository type. /// private List ProcessRepositories( string[] repository, bool trustRepository, bool skipDependencyCheck, ScopeType scope) { _cmdletPassedIn.WriteDebug("In InstallHelper::ProcessRepositories()"); List allPkgsInstalled = new(); if (repository != null && repository.Length != 0) { // Write error and disregard repository entries containing wildcards. repository = Utils.ProcessNameWildcards(repository, removeWildcardEntries: false, out string[] errorMsgs, out _); foreach (string error in errorMsgs) { _cmdletPassedIn.WriteError(new ErrorRecord( new PSInvalidOperationException(error), "ErrorFilteringNamesForUnsupportedWildcards", ErrorCategory.InvalidArgument, _cmdletPassedIn)); } // If repository entries includes wildcards and non-wildcard names, write terminating error // Ex: -Repository *Gallery, localRepo bool containsWildcard = false; bool containsNonWildcard = false; foreach (string repoName in repository) { if (repoName.Contains("*")) { containsWildcard = true; } else { containsNonWildcard = true; } } if (containsNonWildcard && containsWildcard) { _cmdletPassedIn.ThrowTerminatingError(new ErrorRecord( new PSInvalidOperationException("Repository name with wildcard is not allowed when another repository without wildcard is specified."), "RepositoryNamesWithWildcardsAndNonWildcardUnsupported", ErrorCategory.InvalidArgument, _cmdletPassedIn)); } } // Get repositories to search. List repositoriesToSearch; try { repositoriesToSearch = RepositorySettings.Read(repository, out string[] errorList); if (repositoriesToSearch != null && repositoriesToSearch.Count == 0) { _cmdletPassedIn.ThrowTerminatingError(new ErrorRecord( new PSArgumentException("Cannot resolve -Repository name. Run 'Get-PSResourceRepository' to view all registered repositories."), "RepositoryNameIsNotResolved", ErrorCategory.InvalidArgument, _cmdletPassedIn)); } foreach (string error in errorList) { _cmdletPassedIn.WriteError(new ErrorRecord( new PSInvalidOperationException(error), "ErrorRetrievingSpecifiedRepository", ErrorCategory.InvalidOperation, _cmdletPassedIn)); } } catch (Exception e) { _cmdletPassedIn.ThrowTerminatingError(new ErrorRecord( new PSInvalidOperationException(e.Message), "ErrorLoadingRepositoryStoreFile", ErrorCategory.InvalidArgument, _cmdletPassedIn)); return allPkgsInstalled; } var listOfRepositories = RepositorySettings.Read(repository, out string[] _); var yesToAll = false; var noToAll = false; var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn, _networkCredential); List repositoryNamesToSearch = new(); bool sourceTrusted = false; // Loop through all the repositories provided (in priority order) until there no more packages to install. for (int i = 0; i < listOfRepositories.Count && _pkgNamesToInstall.Count > 0; i++) { PSRepositoryInfo currentRepository = listOfRepositories[i]; bool isAllowed = GroupPolicyRepositoryEnforcement.IsRepositoryAllowed(currentRepository.Uri); if (!isAllowed) { _cmdletPassedIn.WriteError(new ErrorRecord( new PSInvalidOperationException($"Repository '{currentRepository.Name}' is not allowed by Group Policy."), "RepositoryNotAllowedByGroupPolicy", ErrorCategory.PermissionDenied, this)); continue; } string repoName = currentRepository.Name; sourceTrusted = currentRepository.Trusted || trustRepository; // Set network credentials via passed in credentials, AzArtifacts CredentialProvider, or SecretManagement. _networkCredential = currentRepository.SetNetworkCredentials(_networkCredential, _cmdletPassedIn); ServerApiCall currentServer = ServerFactory.GetServer(currentRepository, _cmdletPassedIn, _networkCredential); if (currentServer == null) { // this indicates that PSRepositoryInfo.APIVersion = PSRepositoryInfo.APIVersion.unknown _cmdletPassedIn.WriteError(new ErrorRecord( new PSInvalidOperationException($"Repository '{currentRepository.Name}' is not a known repository type that is supported. Please file an issue for support at https://github.com/PowerShell/PSResourceGet/issues"), "RepositoryApiVersionUnknown", ErrorCategory.InvalidArgument, _cmdletPassedIn)); continue; } ResponseUtil currentResponseUtil = ResponseUtilFactory.GetResponseUtil(currentRepository); bool installDepsForRepo = skipDependencyCheck; // If no more packages to install, then return if (_pkgNamesToInstall.Count == 0) { return allPkgsInstalled; } _cmdletPassedIn.WriteVerbose($"Attempting to search for packages in '{repoName}'"); if (currentRepository.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; } repositoryNamesToSearch.Add(repoName); List installedPkgs = InstallPackages(_pkgNamesToInstall.ToArray(), currentRepository, currentServer, currentResponseUtil, scope, skipDependencyCheck, findHelper); foreach (PSResourceInfo pkg in installedPkgs) { _pkgNamesToInstall.RemoveAll(x => x.Equals(pkg.Name, StringComparison.InvariantCultureIgnoreCase)); } allPkgsInstalled.AddRange(installedPkgs); } if ((!_cmdletPassedIn.MyInvocation.BoundParameters.ContainsKey("WhatIf") || (SwitchParameter)_cmdletPassedIn.MyInvocation.BoundParameters["WhatIf"] == false) && _pkgNamesToInstall.Count > 0) { string repositoryWording = repositoryNamesToSearch.Count > 1 ? "registered repositories" : "repository"; _cmdletPassedIn.WriteError(new ErrorRecord( new ResourceNotFoundException($"Package(s) '{string.Join(", ", _pkgNamesToInstall)}' could not be installed from {repositoryWording} '{String.Join(", ", repositoryNamesToSearch)}'."), "InstallPackageFailure", ErrorCategory.InvalidData, _cmdletPassedIn)); } return allPkgsInstalled; } /// /// Deletes temp directory and is called at end of install process. /// private bool TryDeleteDirectory( string tempInstallPath, out ErrorRecord errorMsg) { errorMsg = null; try { Utils.DeleteDirectory(tempInstallPath); } catch (Exception e) { errorMsg = new ErrorRecord( exception: e, "errorDeletingTempInstallPath", ErrorCategory.InvalidResult, _cmdletPassedIn); return false; } return true; } /// /// Moves file from the temp install path to destination path for install. /// private void MoveFilesIntoInstallPath( PSResourceInfo pkgInfo, bool isModule, bool isLocalRepo, string dirNameVersion, string tempInstallPath, string installPath, string newVersion, string moduleManifestVersion, string scriptPath) { _cmdletPassedIn.WriteDebug("In InstallHelper::MoveFilesIntoInstallPath()"); // 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, newVersion); _cmdletPassedIn.WriteVerbose($"Installation source path is: '{tempModuleVersionDir}'"); _cmdletPassedIn.WriteVerbose($"Installation destination path is: '{finalModuleVersionDir}'"); if (isModule) { // If new path does not exist if (!Directory.Exists(newPathParent)) { _cmdletPassedIn.WriteVerbose($"Attempting to move '{tempModuleVersionDir}' to '{finalModuleVersionDir}'"); Directory.CreateDirectory(newPathParent); Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir); } else { _cmdletPassedIn.WriteVerbose($"Temporary module version directory is: '{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($"Attempting to delete with restore on failure. '{finalModuleVersionDir}'"); Utils.DeleteDirectoryWithRestore(finalModuleVersionDir); } _cmdletPassedIn.WriteVerbose($"Attempting to move '{tempModuleVersionDir}' to '{finalModuleVersionDir}'"); Utils.MoveDirectory(tempModuleVersionDir, finalModuleVersionDir); } } else if (_asNupkg) { foreach (string file in Directory.GetFiles(tempInstallPath)) { string fileName = Path.GetFileName(file); string newFileName = string.Equals(Path.GetExtension(file), ".zip", StringComparison.OrdinalIgnoreCase) ? $"{Path.GetFileNameWithoutExtension(file)}.nupkg" : fileName; Utils.MoveFiles(Path.Combine(tempInstallPath, fileName), Path.Combine(installPath, newFileName)); } } else { string scriptInfoFolderPath = Path.Combine(installPath, "InstalledScriptInfos"); string scriptXML = pkgInfo.Name + "_InstalledScriptInfo.xml"; string scriptXmlFilePath = Path.Combine(scriptInfoFolderPath, scriptXML); if (!_savePkg) { // Need to ensure "InstalledScriptInfos directory exists if (!Directory.Exists(scriptInfoFolderPath)) { _cmdletPassedIn.WriteVerbose($"Created '{scriptInfoFolderPath}' path for scripts"); Directory.CreateDirectory(scriptInfoFolderPath); } // Need to delete old xml files because there can only be 1 per script _cmdletPassedIn.WriteVerbose(string.Format("Checking if path '{0}' exists: '{1}'", scriptXmlFilePath, File.Exists(scriptXmlFilePath))); if (File.Exists(scriptXmlFilePath)) { _cmdletPassedIn.WriteVerbose("Deleting script metadata XML"); File.Delete(Path.Combine(scriptInfoFolderPath, 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("Deleting script file"); File.Delete(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)); } } else { if (_includeXml) { _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, scriptXML))); Utils.MoveFiles(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, scriptXML)); } } _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt))); Utils.MoveFiles(scriptPath, Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)); } } /// /// Iterates through package names passed in and calls method to install each package and their dependencies. /// private List InstallPackages( string[] pkgNamesToInstall, PSRepositoryInfo repository, ServerApiCall currentServer, ResponseUtil currentResponseUtil, ScopeType scope, bool skipDependencyCheck, FindHelper findHelper) { _cmdletPassedIn.WriteDebug("In InstallHelper::InstallPackages()"); List pkgsSuccessfullyInstalled = new(); // Install parent package to the temp directory, // Get the dependencies from the installed package, // Install all dependencies to temp directory. // If a single dependency fails to install, roll back by deleting the temp directory. foreach (var parentPackage in pkgNamesToInstall) { string tempInstallPath = CreateInstallationTempPath(); try { // Hashtable has the key as the package name // and value as a Hashtable of specific package info: // packageName, { version = "", isScript = "", isModule = "", pkg = "", etc. } // Install parent package to the temp directory. Hashtable packagesHash = BeginPackageInstall( searchVersionType: _versionType, specificVersion: _nugetVersion, versionRange: _versionRange, pkgNameToInstall: parentPackage, repository: repository, currentServer: currentServer, currentResponseUtil: currentResponseUtil, tempInstallPath: tempInstallPath, packagesHash: new Hashtable(StringComparer.InvariantCultureIgnoreCase), errRecord: out ErrorRecord errRecord); // At this point parent package is installed to temp path. if (errRecord != null) { if (errRecord.FullyQualifiedErrorId.Equals("PackageNotFound")) { _cmdletPassedIn.WriteVerbose(errRecord.Exception.Message); } else { _cmdletPassedIn.WriteError(errRecord); } continue; } if (packagesHash.Count == 0) { continue; } Hashtable parentPkgInfo = packagesHash[parentPackage] as Hashtable; PSResourceInfo parentPkgObj = parentPkgInfo["psResourceInfoPkg"] as PSResourceInfo; if (!skipDependencyCheck) { // Get the dependencies from the installed package. if (parentPkgObj.Dependencies.Length > 0) { bool depFindFailed = false; foreach (PSResourceInfo depPkg in findHelper.FindDependencyPackages(currentServer, currentResponseUtil, parentPkgObj, repository)) { if (depPkg == null) { depFindFailed = true; continue; } if (String.Equals(depPkg.Name, parentPkgObj.Name, StringComparison.OrdinalIgnoreCase)) { continue; } NuGetVersion depVersion = null; if (depPkg.AdditionalMetadata.ContainsKey("NormalizedVersion")) { if (!NuGetVersion.TryParse(depPkg.AdditionalMetadata["NormalizedVersion"] as string, out depVersion)) { NuGetVersion.TryParse(depPkg.Version.ToString(), out depVersion); } } string depPkgNameVersion = $"{depPkg.Name}{depPkg.Version.ToString()}"; if (_packagesOnMachine.Contains(depPkgNameVersion) && !depPkg.IsPrerelease) { // if a dependency package is already installed, do not install it again. // to determine if the package version is already installed, _packagesOnMachine is used but it only contains name, version info, not version with prerelease info // if the dependency package is found to be prerelease, it is safer to install it (and worse case it reinstalls) _cmdletPassedIn.WriteVerbose($"Dependency '{depPkg.Name}' with version '{depPkg.Version}' is already installed."); continue; } packagesHash = BeginPackageInstall( searchVersionType: VersionType.SpecificVersion, specificVersion: depVersion, versionRange: null, pkgNameToInstall: depPkg.Name, repository: repository, currentServer: currentServer, currentResponseUtil: currentResponseUtil, tempInstallPath: tempInstallPath, packagesHash: packagesHash, errRecord: out ErrorRecord installPkgErrRecord); if (installPkgErrRecord != null) { _cmdletPassedIn.WriteError(installPkgErrRecord); continue; } } if (depFindFailed) { continue; } } } // If -WhatIf is passed in, early out. if (_cmdletPassedIn.MyInvocation.BoundParameters.ContainsKey("WhatIf") && (SwitchParameter)_cmdletPassedIn.MyInvocation.BoundParameters["WhatIf"] == true) { return pkgsSuccessfullyInstalled; } // Parent package and dependencies are now installed to temp directory. // Try to move all package directories from temp directory to final destination. if (!TryMoveInstallContent(tempInstallPath, scope, packagesHash)) { _cmdletPassedIn.WriteError(new ErrorRecord( new InvalidOperationException(), "InstallPackageTryMoveContentFailure", ErrorCategory.InvalidOperation, _cmdletPassedIn)); } else { foreach (string pkgName in packagesHash.Keys) { Hashtable pkgInfo = packagesHash[pkgName] as Hashtable; pkgsSuccessfullyInstalled.Add(pkgInfo["psResourceInfoPkg"] as PSResourceInfo); // Add each pkg to _packagesOnMachine (ie pkgs fully installed on the machine). _packagesOnMachine.Add($"{pkgName}{pkgInfo["pkgVersion"]}"); } } } catch (Exception e) { _cmdletPassedIn.WriteError(new ErrorRecord( e, "InstallPackageFailure", ErrorCategory.InvalidOperation, _cmdletPassedIn)); throw e; } finally { DeleteInstallationTempPath(tempInstallPath); } } return pkgsSuccessfullyInstalled; } /// /// Installs a single package to the temporary path. /// private Hashtable BeginPackageInstall( VersionType searchVersionType, NuGetVersion specificVersion, VersionRange versionRange, string pkgNameToInstall, PSRepositoryInfo repository, ServerApiCall currentServer, ResponseUtil currentResponseUtil, string tempInstallPath, Hashtable packagesHash, out ErrorRecord errRecord) { _cmdletPassedIn.WriteDebug("In InstallHelper::InstallPackage()"); FindResults responses = null; errRecord = null; switch (searchVersionType) { case VersionType.VersionRange: responses = currentServer.FindVersionGlobbing(pkgNameToInstall, versionRange, _prerelease, ResourceType.None, getOnlyLatest: true, out ErrorRecord findVersionGlobbingErrRecord); // Server level globbing API will not populate errRecord for empty response, so must check for empty response and early out if (findVersionGlobbingErrRecord != null || responses.IsFindResultsEmpty()) { errRecord = findVersionGlobbingErrRecord; return packagesHash; } break; case VersionType.SpecificVersion: string nugetVersionString = specificVersion.ToNormalizedString(); // 3.0.17-beta responses = currentServer.FindVersion(pkgNameToInstall, nugetVersionString, ResourceType.None, out ErrorRecord findVersionErrRecord); if (findVersionErrRecord != null) { errRecord = findVersionErrRecord; return packagesHash; } break; default: // VersionType.NoVersion responses = currentServer.FindName(pkgNameToInstall, _prerelease, ResourceType.None, out ErrorRecord findNameErrRecord); if (findNameErrRecord != null) { errRecord = findNameErrRecord; return packagesHash; } break; } PSResourceInfo pkgToInstall = null; foreach (PSResourceResult currentResult in currentResponseUtil.ConvertToPSResourceResult(responses)) { if (currentResult.exception != null && !currentResult.exception.Message.Equals(string.Empty)) { errRecord = new ErrorRecord( currentResult.exception, "FindConvertToPSResourceFailure", ErrorCategory.ObjectNotFound, _cmdletPassedIn); } else if (searchVersionType == VersionType.VersionRange) { // Check to see if version falls within version range PSResourceInfo foundPkg = currentResult.returnedObject; string versionStr = $"{foundPkg.Version}"; if (foundPkg.IsPrerelease) { versionStr += $"-{foundPkg.Prerelease}"; } if (NuGetVersion.TryParse(versionStr, out NuGetVersion version) && _versionRange.Satisfies(version)) { pkgToInstall = foundPkg; break; } } else { pkgToInstall = currentResult.returnedObject; break; } } if (pkgToInstall == null) { return packagesHash; } pkgToInstall.RepositorySourceLocation = repository.Uri.ToString(); pkgToInstall.AdditionalMetadata.TryGetValue("NormalizedVersion", out string pkgVersion); if (pkgVersion == null) { // Not all NuGet providers (e.g. Artifactory, possibly others) send NormalizedVersion in NuGet package responses. // If they don't, we need to manually construct the combined version+prerelease from pkgToInstall.Version and the prerelease string. pkgVersion = pkgToInstall.Version.ToString(); if (!String.IsNullOrEmpty(pkgToInstall.Prerelease)) { pkgVersion += $"-{pkgToInstall.Prerelease}"; } } // For most repositories/providers the server will use the normalized version, which pkgVersion originally reflects // However, for container registries the version must exactly match what was in the artifact manifest and then reflected in PSResourceInfo.Version.ToString() if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.ContainerRegistry) { pkgVersion = String.IsNullOrEmpty(pkgToInstall.Prerelease) ? pkgToInstall.Version.ToString() : $"{pkgToInstall.Version.ToString()}-{pkgToInstall.Prerelease}"; } // Check to see if the pkg is already installed (ie the pkg is installed and the version satisfies the version range provided via param) if (!_reinstall) { string currPkgNameVersion = $"{pkgToInstall.Name}{pkgToInstall.Version}"; if (_packagesOnMachine.Contains(currPkgNameVersion)) { _cmdletPassedIn.WriteWarning($"Resource '{pkgToInstall.Name}' with version '{pkgVersion}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter"); // Remove from tracking list of packages to install. _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase)); return packagesHash; } } if (packagesHash.ContainsKey(pkgToInstall.Name)) { return packagesHash; } Hashtable updatedPackagesHash = packagesHash; // -WhatIf processing. if (_savePkg && !_cmdletPassedIn.ShouldProcess($"Package to save: '{pkgToInstall.Name}', version: '{pkgVersion}'")) { if (!updatedPackagesHash.ContainsKey(pkgToInstall.Name)) { updatedPackagesHash.Add(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase) { { "isModule", "" }, { "isScript", "" }, { "psResourceInfoPkg", pkgToInstall }, { "tempDirNameVersionPath", tempInstallPath }, { "pkgVersion", "" }, { "scriptPath", "" }, { "installPath", "" } }); } } else if (!_cmdletPassedIn.ShouldProcess($"Package to install: '{pkgToInstall.Name}', version: '{pkgVersion}'")) { if (!updatedPackagesHash.ContainsKey(pkgToInstall.Name)) { updatedPackagesHash.Add(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase) { { "isModule", "" }, { "isScript", "" }, { "psResourceInfoPkg", pkgToInstall }, { "tempDirNameVersionPath", tempInstallPath }, { "pkgVersion", "" }, { "scriptPath", "" }, { "installPath", "" } }); } } else { // Download the package. string pkgName = pkgToInstall.Name; Stream responseStream = currentServer.InstallPackage(pkgName, pkgVersion, _prerelease, out ErrorRecord installNameErrRecord); if (installNameErrRecord != null) { errRecord = installNameErrRecord; return packagesHash; } bool installedToTempPathSuccessfully = _asNupkg ? TrySaveNupkgToTempPath(responseStream, tempInstallPath, pkgName, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, out errRecord) : TryInstallToTempPath(responseStream, tempInstallPath, pkgName, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, out errRecord); if (!installedToTempPathSuccessfully) { return packagesHash; } } return updatedPackagesHash; } /// /// Creates a temporary path used for installation before moving package to its final location. /// private string CreateInstallationTempPath() { var tempInstallPath = Path.Combine(_tmpPath, Guid.NewGuid().ToString()); try { 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; } catch (Exception e) { // catch more specific exception first _cmdletPassedIn.ThrowTerminatingError(new ErrorRecord( new ArgumentException($"Temporary folder for installation could not be created or set due to: {e.Message}"), "TempFolderCreationError", ErrorCategory.InvalidOperation, _cmdletPassedIn)); } return tempInstallPath; } /// /// Deletes the temporary path used for intermediary installation. /// private void DeleteInstallationTempPath(string tempInstallPath) { if (Directory.Exists(tempInstallPath)) { // Delete the temp directory and all its contents _cmdletPassedIn.WriteVerbose($"Attempting to delete '{tempInstallPath}'"); if (!TryDeleteDirectory(tempInstallPath, out ErrorRecord errorMsg)) { _cmdletPassedIn.WriteError(errorMsg); } else { _cmdletPassedIn.WriteVerbose($"Successfully deleted '{tempInstallPath}'"); } } } /// /// Attempts to take installed HTTP response content and move it into a temporary install path on the machine. /// private bool TryInstallToTempPath( Stream responseStream, string tempInstallPath, string pkgName, string normalizedPkgVersion, PSResourceInfo pkgToInstall, Hashtable packagesHash, out Hashtable updatedPackagesHash, out ErrorRecord error) { _cmdletPassedIn.WriteDebug("In InstallHelper::TryInstallToTempPath()"); error = null; updatedPackagesHash = packagesHash; try { var pathToFile = Path.Combine(tempInstallPath, $"{pkgName}.{normalizedPkgVersion}.zip"); using var fs = File.Create(pathToFile); responseStream.Seek(0, System.IO.SeekOrigin.Begin); responseStream.CopyTo(fs); fs.Close(); // Expand the zip file var pkgVersion = pkgToInstall.Version.ToString(); var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName, pkgVersion); Directory.CreateDirectory(tempDirNameVersion); if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out error)) { return false; } File.Delete(pathToFile); var moduleManifest = Path.Combine(tempDirNameVersion, pkgName + PSDataFileExt); var scriptPath = Path.Combine(tempDirNameVersion, pkgName + PSScriptFileExt); bool isModule = File.Exists(moduleManifest); bool isScript = File.Exists(scriptPath); if (!isModule && !isScript) { scriptPath = ""; } // TODO: add pkg validation when we figure out consistent/defined way to do so if (_authenticodeCheck && !AuthenticodeSignature.CheckAuthenticodeSignature( pkgName, tempDirNameVersion, _cmdletPassedIn, out error)) { return false; } string installPath = string.Empty; if (isModule) { installPath = _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); if (!File.Exists(moduleManifest)) { error = new ErrorRecord( new ArgumentException("Package '{pkgName}' could not be installed: Module manifest file: {moduleManifest} does not exist. This is not a valid PowerShell module."), "PSDataFileNotExistError", ErrorCategory.ReadError, _cmdletPassedIn); return false; } if (!Utils.TryReadManifestFile( manifestFilePath: moduleManifest, manifestInfo: out Hashtable parsedMetadataHashtable, error: out Exception manifestReadError)) { error = new ErrorRecord( manifestReadError, "ManifestFileReadParseError", ErrorCategory.ReadError, _cmdletPassedIn); return false; } // Accept License verification if (!CallAcceptLicense(pkgToInstall, moduleManifest, tempInstallPath, pkgVersion, out error)) { _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase)); return false; } // If NoClobber is specified, ensure command clobbering does not happen if (_noClobber && DetectClobber(pkgName, parsedMetadataHashtable, out error)) { _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase)); return false; } } else if (isScript) { installPath = _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)); // is script if (!PSScriptFileInfo.TryTestPSScriptFileInfo( scriptFileInfoPath: scriptPath, parsedScript: out PSScriptFileInfo scriptToInstall, out ErrorRecord[] parseScriptFileErrors, out string[] _)) { foreach (ErrorRecord parseError in parseScriptFileErrors) { _cmdletPassedIn.WriteError(parseError); } error = new ErrorRecord( new InvalidOperationException($"PSScriptFile could not be parsed"), "PSScriptParseError", ErrorCategory.ReadError, _cmdletPassedIn); return false; } } else { // This package is not a PowerShell package (eg a resource from the NuGet Gallery). installPath = _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); _cmdletPassedIn.WriteVerbose($"This resource is not a PowerShell package and will be installed to the modules path: {installPath}."); isModule = true; } installPath = _savePkg ? _pathsToInstallPkg.First() : installPath; DeleteExtraneousFiles(pkgName, tempDirNameVersion); if (_includeXml) { if (!CreateMetadataXMLFile(tempDirNameVersion, installPath, pkgToInstall, isModule, out error)) { return false; } } if (!updatedPackagesHash.ContainsKey(pkgName)) { // Add pkg info to hashtable. updatedPackagesHash.Add(pkgName, new Hashtable(StringComparer.InvariantCultureIgnoreCase) { { "isModule", isModule }, { "isScript", isScript }, { "psResourceInfoPkg", pkgToInstall }, { "tempDirNameVersionPath", tempDirNameVersion }, { "pkgVersion", pkgVersion }, { "scriptPath", scriptPath }, { "installPath", installPath } }); } return true; } catch (Exception e) { error = new ErrorRecord( new PSInvalidOperationException( message: $"Unable to successfully install package '{pkgName}': '{e.Message}' to temporary installation path.", innerException: e), "InstallPackageFailed", ErrorCategory.InvalidOperation, _cmdletPassedIn); return false; } } /// /// Attempts to take Http response content and move the .nupkg into a temporary install path on the machine. /// private bool TrySaveNupkgToTempPath( Stream responseStream, string tempInstallPath, string pkgName, string normalizedPkgVersion, PSResourceInfo pkgToInstall, Hashtable packagesHash, out Hashtable updatedPackagesHash, out ErrorRecord error) { _cmdletPassedIn.WriteDebug("In InstallHelper::TrySaveNupkgToTempPath()"); error = null; updatedPackagesHash = packagesHash; try { var pathToFile = Path.Combine(tempInstallPath, $"{pkgName}.{normalizedPkgVersion}.zip"); using var fs = File.Create(pathToFile); responseStream.Seek(0, System.IO.SeekOrigin.Begin); responseStream.CopyTo(fs); fs.Close(); string installPath = _pathsToInstallPkg.First(); if (_includeXml) { if (!CreateMetadataXMLFile(tempInstallPath, installPath, pkgToInstall, isModule: true, out error)) { return false; } } if (!updatedPackagesHash.ContainsKey(pkgName)) { // Add pkg info to hashtable. updatedPackagesHash.Add(pkgName, new Hashtable(StringComparer.InvariantCultureIgnoreCase) { { "isModule", "" }, { "isScript", "" }, { "psResourceInfoPkg", pkgToInstall }, { "tempDirNameVersionPath", tempInstallPath }, { "pkgVersion", "" }, { "scriptPath", "" }, { "installPath", installPath } }); } return true; } catch (Exception e) { error = new ErrorRecord( new PSInvalidOperationException( message: $"Unable to successfully save .nupkg '{pkgName}': '{e.Message}' to temporary installation path.", innerException: e), "SaveNupkgFailed", ErrorCategory.InvalidOperation, _cmdletPassedIn); return false; } } /// /// Extracts files from .nupkg /// Similar functionality as System.IO.Compression.ZipFile.ExtractToDirectory, /// but while ExtractToDirectory cannot overwrite files, this method can. /// private bool TryExtractToDirectory(string zipPath, string extractPath, out ErrorRecord error) { error = null; // Normalize the path extractPath = Path.GetFullPath(extractPath); // Ensures that the last character on the extraction path is the directory separator char. // Without this, a malicious zip file could try to traverse outside of the expected extraction path. if (!extractPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { extractPath += Path.DirectorySeparatorChar; } try { using (ZipArchive archive = ZipFile.OpenRead(zipPath)) { foreach (ZipArchiveEntry entry in archive.Entries.Where(entry => entry.CompressedLength > 0)) { // If a file has one or more parent directories. if (entry.FullName.Contains(Path.DirectorySeparatorChar) || entry.FullName.Contains(Path.AltDirectorySeparatorChar)) { // Create the parent directories if they do not already exist var lastPathSeparatorIdx = entry.FullName.Contains(Path.DirectorySeparatorChar) ? entry.FullName.LastIndexOf(Path.DirectorySeparatorChar) : entry.FullName.LastIndexOf(Path.AltDirectorySeparatorChar); var parentDirs = entry.FullName.Substring(0, lastPathSeparatorIdx); var destinationDirectory = Path.Combine(extractPath, parentDirs); if (!Directory.Exists(destinationDirectory)) { Directory.CreateDirectory(destinationDirectory); } } // Gets the full path to ensure that relative segments are removed. string destinationPath = Path.GetFullPath(Path.Combine(extractPath, entry.FullName)); // Validate that the resolved output path starts with the resolved destination directory. // For example, if a zip file contains a file entry ..\sneaky-file, and the zip file is extracted to the directory c:\output, // then naively combining the paths would result in an output file path of c:\output\..\sneaky-file, which would cause the file to be written to c:\sneaky-file. if (destinationPath.StartsWith(extractPath, StringComparison.Ordinal)) { entry.ExtractToFile(destinationPath, overwrite: true); } } } } catch (Exception e) { error = new ErrorRecord( new Exception($"Error occurred while extracting .nupkg: '{e.Message}'"), "ErrorExtractingNupkg", ErrorCategory.OperationStopped, _cmdletPassedIn); return false; } return true; } /// /// Moves package files/directories from the temp install path into the final install path location. /// private bool TryMoveInstallContent(string tempInstallPath, ScopeType scope, Hashtable packagesHash) { _cmdletPassedIn.WriteDebug("In InstallHelper::TryMoveInstallContent()"); foreach (string pkgName in packagesHash.Keys) { Hashtable pkgInfo = packagesHash[pkgName] as Hashtable; bool isModule = (pkgInfo["isModule"] as bool?) ?? false; bool isScript = (pkgInfo["isScript"] as bool?) ?? false; PSResourceInfo pkgToInstall = pkgInfo["psResourceInfoPkg"] as PSResourceInfo; string tempDirNameVersion = pkgInfo["tempDirNameVersionPath"] as string; string pkgVersion = pkgInfo["pkgVersion"] as string; string scriptPath = pkgInfo["scriptPath"] as string; string installPath = pkgInfo["installPath"] as string; try { MoveFilesIntoInstallPath( pkgToInstall, isModule, isLocalRepo: false, // false for HTTP repo tempDirNameVersion, tempInstallPath, installPath, newVersion: pkgVersion, // would not have prerelease label in this string moduleManifestVersion: pkgVersion, scriptPath); _cmdletPassedIn.WriteVerbose($"Successfully installed package '{pkgName}' to location '{installPath}'"); if (!_savePkg && isScript) { string installPathwithBackSlash = installPath + "\\"; string envPATHVarValue = String.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { envPATHVarValue = Environment.GetEnvironmentVariable("PATH", scope == ScopeType.CurrentUser ? EnvironmentVariableTarget.User : EnvironmentVariableTarget.Machine); } else { // .NET on Unix-based systems does not support per-user and per-machine environment variables, only EnvironmentVariableTarget.Process successfully store an environment variable to the process environment block. envPATHVarValue = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); } if (!String.IsNullOrEmpty(envPATHVarValue) && !envPATHVarValue.Contains(installPath) && !envPATHVarValue.Contains(installPathwithBackSlash)) { _cmdletPassedIn.WriteWarning(String.Format(ScriptPATHWarning, scope, installPath)); } } } catch (Exception e) { _cmdletPassedIn.WriteError(new ErrorRecord( new PSInvalidOperationException( message: $"Unable to successfully install package '{pkgName}': '{e.Message}'", innerException: e), "InstallPackageFailed", ErrorCategory.InvalidOperation, _cmdletPassedIn)); return false; } } return true; } /// /// If the package requires license to be accepted, checks if the user has accepted it. /// private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion, out ErrorRecord error) { _cmdletPassedIn.WriteDebug("In InstallHelper::CallAcceptLicense()"); error = null; var requireLicenseAcceptance = false; if (File.Exists(moduleManifest)) { using (StreamReader sr = new(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(pattern); Regex rgxComment1 = new(patternToSkip1); Regex rgxComment2 = new(patternToSkip2); if (rgx.IsMatch(text) && !rgxComment1.IsMatch(text) && !rgxComment2.IsMatch(text)) { requireLicenseAcceptance = true; } } // License 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); if (!Directory.Exists(PkgTempInstallPath)) { error = new ErrorRecord( new ArgumentException($"Package '{p.Name}' could not be installed: Temporary installation path does not exist."), "TempPathNotFound", ErrorCategory.ObjectNotFound, _cmdletPassedIn); return false; } string[] files = Directory.GetFiles(PkgTempInstallPath); bool foundLicense = false; string LicenseFilePath = string.Empty; foreach (string file in files) { if (string.Equals(Path.GetFileName(file), "License.txt", StringComparison.OrdinalIgnoreCase)) { foundLicense = true; LicenseFilePath = Path.GetFullPath(file); break; } } if (!foundLicense) { error = new ErrorRecord( new ArgumentException($"Package '{p.Name}' could not be installed: License.txt not found. License.txt must be provided when user license acceptance is required."), "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, _cmdletPassedIn); return false; } // Otherwise read LicenseFile string licenseText = 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) { error = new ErrorRecord( new ArgumentException($"Package '{p.Name}' could not be installed: License Acceptance is required for module '{p.Name}'. Please specify '-AcceptLicense' to perform this operation."), "ForceAcceptLicense", ErrorCategory.InvalidArgument, _cmdletPassedIn); return false; } } } return true; } /// /// If the option for no clobber is specified, ensures that commands or cmdlets are not being clobbered. /// private bool DetectClobber(string pkgName, Hashtable parsedMetadataHashtable, out ErrorRecord error) { _cmdletPassedIn.WriteDebug("In InstallHelper::DetectClobber()"); error = null; bool foundClobber = false; // Get installed modules, then get all possible paths // selectPrereleaseOnly is false because even if Prerelease is true we want to include both stable and prerelease, would never select prerelease only. GetHelper getHelper = new(_cmdletPassedIn); IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath( name: new string[] { "*" }, versionRange: VersionRange.All, pathsToSearch: _pathsToSearch, selectPrereleaseOnly: false); List listOfCmdlets = new(); if (parsedMetadataHashtable.ContainsKey("CmdletsToExport")) { if (parsedMetadataHashtable["CmdletsToExport"] is object[] cmdletsToExport) { foreach (var cmdletName in cmdletsToExport) { listOfCmdlets.Add(cmdletName as string); } } } foreach (var pkg in pkgsAlreadyInstalled) { List duplicateCmdlets = new(); List duplicateCmds = new(); // 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.Length != 0) { 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.Count != 0 || duplicateCmds.Count != 0) { duplicateCmdlets.AddRange(duplicateCmds); error = new ErrorRecord( new ArgumentException($"'{pkgName}' package could not be installed with error: The following commands are already available on this system: '{String.Join(", ", duplicateCmdlets)}'. " + $"This module '{pkgName}' may override the existing commands. If you still want to install this module '{pkgName}', remove the -NoClobber parameter."), "CommandAlreadyExists", ErrorCategory.ResourceExists, _cmdletPassedIn); foundClobber = true; return foundClobber; } } return foundClobber; } /// /// Creates metadata XML file for either module or script package. /// private bool CreateMetadataXMLFile(string dirNameVersion, string installPath, PSResourceInfo pkg, bool isModule, out ErrorRecord error) { _cmdletPassedIn.WriteDebug("In InstallHelper::CreateMetadataXMLFile()"); error = null; bool success = true; // 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 writeError)) { error = new ErrorRecord( new ArgumentException($"{pkg.Name} package could not be installed with error: Error parsing metadata into XML: '{writeError}'"), "ErrorParsingMetadata", ErrorCategory.ParserError, _cmdletPassedIn); success = false; } return success; } /// /// Clean up and delete extraneous files found from the package during install. /// private void DeleteExtraneousFiles(string packageName, string dirNameVersion) { _cmdletPassedIn.WriteDebug("In InstallHelper::DeleteExtraneousFiles()"); // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module // since we download as .zip for HTTP calls, we shouldn't have .nupkg* files // var nupkgSHAToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg.sha512"); // var nupkgToDelete = Path.Combine(dirNameVersion, pkgIdString + ".nupkg"); // var nupkgMetadataToDelete = Path.Combine(dirNameVersion, ".nupkg.metadata"); var nuspecToDelete = Path.Combine(dirNameVersion, packageName + ".nuspec"); var contentTypesToDelete = Path.Combine(dirNameVersion, "[Content_Types].xml"); var relsDirToDelete = Path.Combine(dirNameVersion, "_rels"); var packageDirToDelete = Path.Combine(dirNameVersion, "package"); if (File.Exists(nuspecToDelete)) { _cmdletPassedIn.WriteDebug($"Deleting '{nuspecToDelete}'"); File.Delete(nuspecToDelete); } if (File.Exists(contentTypesToDelete)) { _cmdletPassedIn.WriteDebug($"Deleting '{contentTypesToDelete}'"); File.Delete(contentTypesToDelete); } if (Directory.Exists(relsDirToDelete)) { _cmdletPassedIn.WriteDebug($"Deleting '{relsDirToDelete}'"); Utils.DeleteDirectory(relsDirToDelete); } if (Directory.Exists(packageDirToDelete)) { _cmdletPassedIn.WriteDebug($"Deleting '{packageDirToDelete}'"); Utils.DeleteDirectory(packageDirToDelete); } } #endregion } }