From e53bc8f09b60419a2e00d9e8b5807306206ccfc6 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Mon, 25 Jan 2021 19:31:02 -0800 Subject: [PATCH 01/62] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 167 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..7b0b1172b --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,167 @@ +# The name of the build that will be seen in mscodehub +name: PowerShellGetv2-Release-$(Build.BuildId) +# how is the build triggered +# since this is a release build, no trigger as it's a manual release +trigger: none + +pr: + branches: + include: + - master + +# variables to set in the build environment +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +# since this build relies on templates, we need access to those +# This needs a service connection in the build to work +# the *name* of the service connection must be the same as the endpoint +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + # this can be any branch of your choosing + ref: master + +# the stages in this build. There are 2 +# the assumption for PowerShellGetv2 is that test is done as part of +# CI so we needn't do it here +stages: +- stage: Build + displayName: Build + pool: + name: Package ES CodeHub Lab E + jobs: + - job: Build_Job + displayName: Build Microsoft.PowerShell.PowerShellGetv2 + # note the variable reference to ESRP. + # this must be created in Project -> Pipelines -> Library -> VariableGroups + # where it describes the link to the SigningServer + variables: + - group: ESRP + steps: + - checkout: self + + # the steps for building the module go here + - pwsh: | + Set-Location "$(Build.SourcesDirectory)" + Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force + Install-Dependencies + Update-ModuleManifestFunctions + Publish-ModuleArtifacts + displayName: Execute build + + # these are setting vso variables which will be persisted between stages + - pwsh: | + $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" + # Set signing src path variable + $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" + $null = New-Item -ItemType Directory -Path $signOutPath + # Set signing out path variable + $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Set path variable for guardian codesign validation + $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Get version and create a variable + $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" + $moduleVersion = $moduleData.ModuleVersion + $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + + displayName: Setup variables for signing + + # checkout the Compliance repository so it can be used to do the actual signing + - checkout: ComplianceRepo + + # this the MS authored step This cert covers MS autored items + # note that the buildOutputPath (where we get the files to sign) + # is the same as the signOutputPath in the previous step + # at the end of this step we will have all the files signed that should be + # signOutPath is the location which contains the files we will use to make the module + - template: EsrpSign.yml@ComplianceRepo + parameters: + # the folder which contains the binaries to sign + buildOutputPath: $(signSrcPath) + # the location to put the signed output + signOutputPath: $(signOutPath) + # the certificate ID to use + certificateId: "CP-230012" + # use minimatch because we need to exclude the NewtonSoft assembly + useMinimatch: true + # the file pattern to use - newtonSoft is excluded + pattern: | + **\*.psd1 + **\*.psm1 + **\*.ps1xml + **\*.mof + + # now create the nupkg which we will use to publish the module + # to the powershell gallery (not part of this yaml) + #- pwsh: | + # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + # publish-module -Path $(signOutPath) + + # ./build -BuildNupkg -signed + 3 displayName: Create nupkg for publishing + + # finally publish the parts of the build which will be used in the next stages + # if it's not published, the subsequent stages will not be able to access it. + # This is the build directory (it contains all of the dll/pdb files) + - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + artifact: build + displayName: publish build directory + + # export the nupkg only which will be used in the release pipeline + #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" + # artifact: nupkg + # displayName: Publish module nupkg + + +# Now on to the compliance stage +- stage: compliance + displayName: Compliance + dependsOn: Build + jobs: + - job: Compliance_Job + pool: + name: Package ES CodeHub Lab E + steps: + - checkout: self + - checkout: ComplianceRepo + - download: current + artifact: build + + # use the templates in the compliance repo + # since script analyzer has modules, we're using the assembly-module-compliance template + # if you don't have assemblies, you should use script-module-compliance template + - template: script-module-compliance.yml@ComplianceRepo + parameters: + # component-governance - the path to sources + sourceScanPath: '$(Build.SourcesDirectory)' + # binskim - this isn't recursive, so you need the path to the assemblies + # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' + # credscan - scan the repo for credentials + # you can suppress some files with this. + # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' + # TermCheck + optionsRulesDBPath: '' + optionsFTPath: '' + # tsa-upload + # the compliance scanning must be uploaded, which you need to request + codeBaseName: 'PSSA_202004' + # selections + APIScan: false # set to false when not using Windows APIs. From 1b4e76d4bb57576f10adc66b40828f585814c816 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Mon, 25 Jan 2021 19:32:40 -0800 Subject: [PATCH 02/62] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines-1.yml | 167 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 azure-pipelines-1.yml diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml new file mode 100644 index 000000000..7b0b1172b --- /dev/null +++ b/azure-pipelines-1.yml @@ -0,0 +1,167 @@ +# The name of the build that will be seen in mscodehub +name: PowerShellGetv2-Release-$(Build.BuildId) +# how is the build triggered +# since this is a release build, no trigger as it's a manual release +trigger: none + +pr: + branches: + include: + - master + +# variables to set in the build environment +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +# since this build relies on templates, we need access to those +# This needs a service connection in the build to work +# the *name* of the service connection must be the same as the endpoint +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + # this can be any branch of your choosing + ref: master + +# the stages in this build. There are 2 +# the assumption for PowerShellGetv2 is that test is done as part of +# CI so we needn't do it here +stages: +- stage: Build + displayName: Build + pool: + name: Package ES CodeHub Lab E + jobs: + - job: Build_Job + displayName: Build Microsoft.PowerShell.PowerShellGetv2 + # note the variable reference to ESRP. + # this must be created in Project -> Pipelines -> Library -> VariableGroups + # where it describes the link to the SigningServer + variables: + - group: ESRP + steps: + - checkout: self + + # the steps for building the module go here + - pwsh: | + Set-Location "$(Build.SourcesDirectory)" + Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force + Install-Dependencies + Update-ModuleManifestFunctions + Publish-ModuleArtifacts + displayName: Execute build + + # these are setting vso variables which will be persisted between stages + - pwsh: | + $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" + # Set signing src path variable + $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" + $null = New-Item -ItemType Directory -Path $signOutPath + # Set signing out path variable + $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Set path variable for guardian codesign validation + $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Get version and create a variable + $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" + $moduleVersion = $moduleData.ModuleVersion + $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + + displayName: Setup variables for signing + + # checkout the Compliance repository so it can be used to do the actual signing + - checkout: ComplianceRepo + + # this the MS authored step This cert covers MS autored items + # note that the buildOutputPath (where we get the files to sign) + # is the same as the signOutputPath in the previous step + # at the end of this step we will have all the files signed that should be + # signOutPath is the location which contains the files we will use to make the module + - template: EsrpSign.yml@ComplianceRepo + parameters: + # the folder which contains the binaries to sign + buildOutputPath: $(signSrcPath) + # the location to put the signed output + signOutputPath: $(signOutPath) + # the certificate ID to use + certificateId: "CP-230012" + # use minimatch because we need to exclude the NewtonSoft assembly + useMinimatch: true + # the file pattern to use - newtonSoft is excluded + pattern: | + **\*.psd1 + **\*.psm1 + **\*.ps1xml + **\*.mof + + # now create the nupkg which we will use to publish the module + # to the powershell gallery (not part of this yaml) + #- pwsh: | + # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + # publish-module -Path $(signOutPath) + + # ./build -BuildNupkg -signed + 3 displayName: Create nupkg for publishing + + # finally publish the parts of the build which will be used in the next stages + # if it's not published, the subsequent stages will not be able to access it. + # This is the build directory (it contains all of the dll/pdb files) + - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + artifact: build + displayName: publish build directory + + # export the nupkg only which will be used in the release pipeline + #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" + # artifact: nupkg + # displayName: Publish module nupkg + + +# Now on to the compliance stage +- stage: compliance + displayName: Compliance + dependsOn: Build + jobs: + - job: Compliance_Job + pool: + name: Package ES CodeHub Lab E + steps: + - checkout: self + - checkout: ComplianceRepo + - download: current + artifact: build + + # use the templates in the compliance repo + # since script analyzer has modules, we're using the assembly-module-compliance template + # if you don't have assemblies, you should use script-module-compliance template + - template: script-module-compliance.yml@ComplianceRepo + parameters: + # component-governance - the path to sources + sourceScanPath: '$(Build.SourcesDirectory)' + # binskim - this isn't recursive, so you need the path to the assemblies + # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' + # credscan - scan the repo for credentials + # you can suppress some files with this. + # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' + # TermCheck + optionsRulesDBPath: '' + optionsFTPath: '' + # tsa-upload + # the compliance scanning must be uploaded, which you need to request + codeBaseName: 'PSSA_202004' + # selections + APIScan: false # set to false when not using Windows APIs. From 0880d95b61df3c8faed8e9d8aac4edc9ee955465 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Mon, 25 Jan 2021 19:33:29 -0800 Subject: [PATCH 03/62] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines-2.yml | 167 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 azure-pipelines-2.yml diff --git a/azure-pipelines-2.yml b/azure-pipelines-2.yml new file mode 100644 index 000000000..6169e1232 --- /dev/null +++ b/azure-pipelines-2.yml @@ -0,0 +1,167 @@ +# The name of the build that will be seen in mscodehub +name: PowerShellGetv2-Release-$(Build.BuildId) +# how is the build triggered +# since this is a release build, no trigger as it's a manual release +trigger: none + +pr: + branches: + include: + - master + +# variables to set in the build environment +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + +# since this build relies on templates, we need access to those +# This needs a service connection in the build to work +# the *name* of the service connection must be the same as the endpoint +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + # this can be any branch of your choosing + ref: master + +# the stages in this build. There are 2 +# the assumption for PowerShellGetv2 is that test is done as part of +# CI so we needn't do it here +stages: +- stage: Build + displayName: Build + pool: + name: Package ES CodeHub Lab E + jobs: + - job: Build_Job + displayName: Build Microsoft.PowerShell.PowerShellGetv2 + # note the variable reference to ESRP. + # this must be created in Project -> Pipelines -> Library -> VariableGroups + # where it describes the link to the SigningServer + variables: + - group: ESRP + steps: + - checkout: self + + # the steps for building the module go here + - pwsh: | + Set-Location "$(Build.SourcesDirectory)" + Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force + Install-Dependencies + Update-ModuleManifestFunctions + Publish-ModuleArtifacts + displayName: Execute build + + # these are setting vso variables which will be persisted between stages + - pwsh: | + $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" + # Set signing src path variable + $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" + $null = New-Item -ItemType Directory -Path $signOutPath + # Set signing out path variable + $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Set path variable for guardian codesign validation + $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + # Get version and create a variable + $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" + $moduleVersion = $moduleData.ModuleVersion + $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + + + displayName: Setup variables for signing + + # checkout the Compliance repository so it can be used to do the actual signing + - checkout: ComplianceRepo + + # this the MS authored step This cert covers MS autored items + # note that the buildOutputPath (where we get the files to sign) + # is the same as the signOutputPath in the previous step + # at the end of this step we will have all the files signed that should be + # signOutPath is the location which contains the files we will use to make the module + - template: EsrpSign.yml@ComplianceRepo + parameters: + # the folder which contains the binaries to sign + buildOutputPath: $(signSrcPath) + # the location to put the signed output + signOutputPath: $(signOutPath) + # the certificate ID to use + certificateId: "CP-230012" + # use minimatch because we need to exclude the NewtonSoft assembly + useMinimatch: true + # the file pattern to use - newtonSoft is excluded + pattern: | + **\*.psd1 + **\*.psm1 + **\*.ps1xml + **\*.mof + + # now create the nupkg which we will use to publish the module + # to the powershell gallery (not part of this yaml) + #- pwsh: | + # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + # publish-module -Path $(signOutPath) + + # ./build -BuildNupkg -signed + # displayName: Create nupkg for publishing + + # finally publish the parts of the build which will be used in the next stages + # if it's not published, the subsequent stages will not be able to access it. + # This is the build directory (it contains all of the dll/pdb files) + - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" + artifact: build + displayName: publish build directory + + # export the nupkg only which will be used in the release pipeline + #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" + # artifact: nupkg + # displayName: Publish module nupkg + + +# Now on to the compliance stage +- stage: compliance + displayName: Compliance + dependsOn: Build + jobs: + - job: Compliance_Job + pool: + name: Package ES CodeHub Lab E + steps: + - checkout: self + - checkout: ComplianceRepo + - download: current + artifact: build + + # use the templates in the compliance repo + # since script analyzer has modules, we're using the assembly-module-compliance template + # if you don't have assemblies, you should use script-module-compliance template + - template: script-module-compliance.yml@ComplianceRepo + parameters: + # component-governance - the path to sources + sourceScanPath: '$(Build.SourcesDirectory)' + # binskim - this isn't recursive, so you need the path to the assemblies + # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' + # credscan - scan the repo for credentials + # you can suppress some files with this. + # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' + # TermCheck + optionsRulesDBPath: '' + optionsFTPath: '' + # tsa-upload + # the compliance scanning must be uploaded, which you need to request + codeBaseName: 'PSSA_202004' + # selections + APIScan: false # set to false when not using Windows APIs. From 976ea4d3f3bbe4ebda3d5cc2db4edc42dbdfc689 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 9 Feb 2021 15:26:52 -0800 Subject: [PATCH 04/62] create new src directory --- {src => srcOld}/PSModule.psm1 | 0 {src => srcOld}/PowerShellGet.psd1 | 0 {src => srcOld}/code/ArgumentCompleter.cs | 0 {src => srcOld}/code/CacheSettings.cs | 0 {src => srcOld}/code/FindHelper.cs | 0 {src => srcOld}/code/FindPSResource.cs | 0 {src => srcOld}/code/GetHelper.cs | 0 {src => srcOld}/code/GetPSResource.cs | 0 {src => srcOld}/code/GetPSResourceRepository.cs | 0 {src => srcOld}/code/InstallHelper.cs | 0 {src => srcOld}/code/InstallPSResource.cs | 0 {src => srcOld}/code/InstallPkgParams.cs | 0 {src => srcOld}/code/LoggerCatalogLeafProcessor.cs | 0 {src => srcOld}/code/NuGetLogger.cs | 0 {src => srcOld}/code/PackageMetadata.cs | 0 {src => srcOld}/code/PackageMetadataAllTables.cs | 0 {src => srcOld}/code/PowerShellGet.csproj | 0 {src => srcOld}/code/PublishPSResource.cs | 0 {src => srcOld}/code/RegisterPSResourceRepository.cs | 0 {src => srcOld}/code/RepositorySettings.cs | 0 {src => srcOld}/code/ResourceSettings.cs | 0 {src => srcOld}/code/SavePSResource.cs | 0 {src => srcOld}/code/SetPSResourceRepository.cs | 0 {src => srcOld}/code/UninstallPSResource.cs | 0 {src => srcOld}/code/UnregisterPSResourceRepository.cs | 0 {src => srcOld}/code/UpdatePSResource.cs | 0 {src => srcOld}/code/Utilities.cs | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename {src => srcOld}/PSModule.psm1 (100%) rename {src => srcOld}/PowerShellGet.psd1 (100%) rename {src => srcOld}/code/ArgumentCompleter.cs (100%) rename {src => srcOld}/code/CacheSettings.cs (100%) rename {src => srcOld}/code/FindHelper.cs (100%) rename {src => srcOld}/code/FindPSResource.cs (100%) rename {src => srcOld}/code/GetHelper.cs (100%) rename {src => srcOld}/code/GetPSResource.cs (100%) rename {src => srcOld}/code/GetPSResourceRepository.cs (100%) rename {src => srcOld}/code/InstallHelper.cs (100%) rename {src => srcOld}/code/InstallPSResource.cs (100%) rename {src => srcOld}/code/InstallPkgParams.cs (100%) rename {src => srcOld}/code/LoggerCatalogLeafProcessor.cs (100%) rename {src => srcOld}/code/NuGetLogger.cs (100%) rename {src => srcOld}/code/PackageMetadata.cs (100%) rename {src => srcOld}/code/PackageMetadataAllTables.cs (100%) rename {src => srcOld}/code/PowerShellGet.csproj (100%) rename {src => srcOld}/code/PublishPSResource.cs (100%) rename {src => srcOld}/code/RegisterPSResourceRepository.cs (100%) rename {src => srcOld}/code/RepositorySettings.cs (100%) rename {src => srcOld}/code/ResourceSettings.cs (100%) rename {src => srcOld}/code/SavePSResource.cs (100%) rename {src => srcOld}/code/SetPSResourceRepository.cs (100%) rename {src => srcOld}/code/UninstallPSResource.cs (100%) rename {src => srcOld}/code/UnregisterPSResourceRepository.cs (100%) rename {src => srcOld}/code/UpdatePSResource.cs (100%) rename {src => srcOld}/code/Utilities.cs (100%) diff --git a/src/PSModule.psm1 b/srcOld/PSModule.psm1 similarity index 100% rename from src/PSModule.psm1 rename to srcOld/PSModule.psm1 diff --git a/src/PowerShellGet.psd1 b/srcOld/PowerShellGet.psd1 similarity index 100% rename from src/PowerShellGet.psd1 rename to srcOld/PowerShellGet.psd1 diff --git a/src/code/ArgumentCompleter.cs b/srcOld/code/ArgumentCompleter.cs similarity index 100% rename from src/code/ArgumentCompleter.cs rename to srcOld/code/ArgumentCompleter.cs diff --git a/src/code/CacheSettings.cs b/srcOld/code/CacheSettings.cs similarity index 100% rename from src/code/CacheSettings.cs rename to srcOld/code/CacheSettings.cs diff --git a/src/code/FindHelper.cs b/srcOld/code/FindHelper.cs similarity index 100% rename from src/code/FindHelper.cs rename to srcOld/code/FindHelper.cs diff --git a/src/code/FindPSResource.cs b/srcOld/code/FindPSResource.cs similarity index 100% rename from src/code/FindPSResource.cs rename to srcOld/code/FindPSResource.cs diff --git a/src/code/GetHelper.cs b/srcOld/code/GetHelper.cs similarity index 100% rename from src/code/GetHelper.cs rename to srcOld/code/GetHelper.cs diff --git a/src/code/GetPSResource.cs b/srcOld/code/GetPSResource.cs similarity index 100% rename from src/code/GetPSResource.cs rename to srcOld/code/GetPSResource.cs diff --git a/src/code/GetPSResourceRepository.cs b/srcOld/code/GetPSResourceRepository.cs similarity index 100% rename from src/code/GetPSResourceRepository.cs rename to srcOld/code/GetPSResourceRepository.cs diff --git a/src/code/InstallHelper.cs b/srcOld/code/InstallHelper.cs similarity index 100% rename from src/code/InstallHelper.cs rename to srcOld/code/InstallHelper.cs diff --git a/src/code/InstallPSResource.cs b/srcOld/code/InstallPSResource.cs similarity index 100% rename from src/code/InstallPSResource.cs rename to srcOld/code/InstallPSResource.cs diff --git a/src/code/InstallPkgParams.cs b/srcOld/code/InstallPkgParams.cs similarity index 100% rename from src/code/InstallPkgParams.cs rename to srcOld/code/InstallPkgParams.cs diff --git a/src/code/LoggerCatalogLeafProcessor.cs b/srcOld/code/LoggerCatalogLeafProcessor.cs similarity index 100% rename from src/code/LoggerCatalogLeafProcessor.cs rename to srcOld/code/LoggerCatalogLeafProcessor.cs diff --git a/src/code/NuGetLogger.cs b/srcOld/code/NuGetLogger.cs similarity index 100% rename from src/code/NuGetLogger.cs rename to srcOld/code/NuGetLogger.cs diff --git a/src/code/PackageMetadata.cs b/srcOld/code/PackageMetadata.cs similarity index 100% rename from src/code/PackageMetadata.cs rename to srcOld/code/PackageMetadata.cs diff --git a/src/code/PackageMetadataAllTables.cs b/srcOld/code/PackageMetadataAllTables.cs similarity index 100% rename from src/code/PackageMetadataAllTables.cs rename to srcOld/code/PackageMetadataAllTables.cs diff --git a/src/code/PowerShellGet.csproj b/srcOld/code/PowerShellGet.csproj similarity index 100% rename from src/code/PowerShellGet.csproj rename to srcOld/code/PowerShellGet.csproj diff --git a/src/code/PublishPSResource.cs b/srcOld/code/PublishPSResource.cs similarity index 100% rename from src/code/PublishPSResource.cs rename to srcOld/code/PublishPSResource.cs diff --git a/src/code/RegisterPSResourceRepository.cs b/srcOld/code/RegisterPSResourceRepository.cs similarity index 100% rename from src/code/RegisterPSResourceRepository.cs rename to srcOld/code/RegisterPSResourceRepository.cs diff --git a/src/code/RepositorySettings.cs b/srcOld/code/RepositorySettings.cs similarity index 100% rename from src/code/RepositorySettings.cs rename to srcOld/code/RepositorySettings.cs diff --git a/src/code/ResourceSettings.cs b/srcOld/code/ResourceSettings.cs similarity index 100% rename from src/code/ResourceSettings.cs rename to srcOld/code/ResourceSettings.cs diff --git a/src/code/SavePSResource.cs b/srcOld/code/SavePSResource.cs similarity index 100% rename from src/code/SavePSResource.cs rename to srcOld/code/SavePSResource.cs diff --git a/src/code/SetPSResourceRepository.cs b/srcOld/code/SetPSResourceRepository.cs similarity index 100% rename from src/code/SetPSResourceRepository.cs rename to srcOld/code/SetPSResourceRepository.cs diff --git a/src/code/UninstallPSResource.cs b/srcOld/code/UninstallPSResource.cs similarity index 100% rename from src/code/UninstallPSResource.cs rename to srcOld/code/UninstallPSResource.cs diff --git a/src/code/UnregisterPSResourceRepository.cs b/srcOld/code/UnregisterPSResourceRepository.cs similarity index 100% rename from src/code/UnregisterPSResourceRepository.cs rename to srcOld/code/UnregisterPSResourceRepository.cs diff --git a/src/code/UpdatePSResource.cs b/srcOld/code/UpdatePSResource.cs similarity index 100% rename from src/code/UpdatePSResource.cs rename to srcOld/code/UpdatePSResource.cs diff --git a/src/code/Utilities.cs b/srcOld/code/Utilities.cs similarity index 100% rename from src/code/Utilities.cs rename to srcOld/code/Utilities.cs From 0976f05753ebf8ce487565937eebd01d22923cf9 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 9 Feb 2021 15:31:30 -0800 Subject: [PATCH 05/62] create new test directory --- {test => testOld}/FindResource.Command.Tests.ps1 | 0 {test => testOld}/FindResource.DSCResource.Tests.ps1 | 0 {test => testOld}/FindResource.Module.Tests.ps1 | 0 {test => testOld}/FindResource.RoleCapability.Tests.ps1 | 0 {test => testOld}/FindResource.Script.Tests.ps1 | 0 {test => testOld}/PSGetTestUtils.psm1 | 0 {test => testOld}/PSRepository.Tests.ps1 | 0 {test => testOld}/PublishResource.Tests.ps1 | 0 {test => testOld}/RequiredResource.Tests.ps1 | 0 {test => testOld}/testRepositories.xml | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename {test => testOld}/FindResource.Command.Tests.ps1 (100%) rename {test => testOld}/FindResource.DSCResource.Tests.ps1 (100%) rename {test => testOld}/FindResource.Module.Tests.ps1 (100%) rename {test => testOld}/FindResource.RoleCapability.Tests.ps1 (100%) rename {test => testOld}/FindResource.Script.Tests.ps1 (100%) rename {test => testOld}/PSGetTestUtils.psm1 (100%) rename {test => testOld}/PSRepository.Tests.ps1 (100%) rename {test => testOld}/PublishResource.Tests.ps1 (100%) rename {test => testOld}/RequiredResource.Tests.ps1 (100%) rename {test => testOld}/testRepositories.xml (100%) diff --git a/test/FindResource.Command.Tests.ps1 b/testOld/FindResource.Command.Tests.ps1 similarity index 100% rename from test/FindResource.Command.Tests.ps1 rename to testOld/FindResource.Command.Tests.ps1 diff --git a/test/FindResource.DSCResource.Tests.ps1 b/testOld/FindResource.DSCResource.Tests.ps1 similarity index 100% rename from test/FindResource.DSCResource.Tests.ps1 rename to testOld/FindResource.DSCResource.Tests.ps1 diff --git a/test/FindResource.Module.Tests.ps1 b/testOld/FindResource.Module.Tests.ps1 similarity index 100% rename from test/FindResource.Module.Tests.ps1 rename to testOld/FindResource.Module.Tests.ps1 diff --git a/test/FindResource.RoleCapability.Tests.ps1 b/testOld/FindResource.RoleCapability.Tests.ps1 similarity index 100% rename from test/FindResource.RoleCapability.Tests.ps1 rename to testOld/FindResource.RoleCapability.Tests.ps1 diff --git a/test/FindResource.Script.Tests.ps1 b/testOld/FindResource.Script.Tests.ps1 similarity index 100% rename from test/FindResource.Script.Tests.ps1 rename to testOld/FindResource.Script.Tests.ps1 diff --git a/test/PSGetTestUtils.psm1 b/testOld/PSGetTestUtils.psm1 similarity index 100% rename from test/PSGetTestUtils.psm1 rename to testOld/PSGetTestUtils.psm1 diff --git a/test/PSRepository.Tests.ps1 b/testOld/PSRepository.Tests.ps1 similarity index 100% rename from test/PSRepository.Tests.ps1 rename to testOld/PSRepository.Tests.ps1 diff --git a/test/PublishResource.Tests.ps1 b/testOld/PublishResource.Tests.ps1 similarity index 100% rename from test/PublishResource.Tests.ps1 rename to testOld/PublishResource.Tests.ps1 diff --git a/test/RequiredResource.Tests.ps1 b/testOld/RequiredResource.Tests.ps1 similarity index 100% rename from test/RequiredResource.Tests.ps1 rename to testOld/RequiredResource.Tests.ps1 diff --git a/test/testRepositories.xml b/testOld/testRepositories.xml similarity index 100% rename from test/testRepositories.xml rename to testOld/testRepositories.xml From 4a1a72f715aac272cb0934bb8099ca6a5b001feb Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 22 Jun 2021 00:07:06 -0700 Subject: [PATCH 06/62] Begin refactoring Install --- src/code/InstallHelper.cs | 881 ++++++++++++++++++++++++++++++++++ src/code/InstallPSResource.cs | 266 ++++++++++ src/code/InstallPkgParams.cs | 17 + src/code/PowerShellGet.csproj | 3 +- src/code/Utils.cs | 58 +++ 5 files changed, 1224 insertions(+), 1 deletion(-) create mode 100644 src/code/InstallHelper.cs create mode 100644 src/code/InstallPSResource.cs create mode 100644 src/code/InstallPkgParams.cs diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs new file mode 100644 index 000000000..26f6557ea --- /dev/null +++ b/src/code/InstallHelper.cs @@ -0,0 +1,881 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using static System.Environment; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading; +using MoreLinq.Extensions; +using Newtonsoft.Json; +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 Microsoft.PowerShell.PowerShellGet.UtilClasses; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// Find helper class + /// + internal class InstallHelper : PSCmdlet + { + private CancellationToken cancellationToken; + private readonly bool update; + private readonly bool save; + private readonly PSCmdlet cmdletPassedIn; + List _pathsToInstallPkg; + VersionRange _versionRange; + bool _prerelease; + string _scope; + bool _acceptLicense; + bool _quiet; + bool _reinstall; + bool _force; + bool _trustRepository; + bool _noClobber; + PSCredential _credential; + string _specifiedPath; + bool _asNupkg; + bool _includeXML; + + public InstallHelper(bool update, bool save, CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) + { + this.update = update; + this.save = save; + this.cancellationToken = cancellationToken; + this.cmdletPassedIn = cmdletPassedIn; + } + + /// 1: Check to see if the pkgs are already installed (ie the pkg is installed and the version satisfies the version range provided via param) + /// 2: If pkg version is NOT installed, continue the installation process + /// 2a: Go through the repositories and see which one has the pkg version availble (ie search call) + /// Use find helper to search for the exact pkg version we want to install + /// 2b: Proceed to find all dependencies for the package availale + /// 3: Once all the pkg versions and dependency pkg versions are found, proceed to install each + public void ProcessInstallParams( + string[] names, + VersionRange versionRange, + bool prerelease, + string[] repository, + string scope, + bool acceptLicense, + bool quiet, + bool reinstall, + bool force, + bool trustRepository, + bool noClobber, + PSCredential credential, + string requiredResourceFile, + string requiredResourceJson, + Hashtable requiredResourceHash, + string specifiedPath, + bool asNupkg, + bool includeXML, + List pathsToInstallPkg) + { + cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; Scope: '{4}'; " + + "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", + string.Join(",", names), versionRange.OriginalString, prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, + scope != null ? scope : string.Empty, acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); + + _versionRange = versionRange; + _prerelease = prerelease; + _scope = scope; + _acceptLicense = acceptLicense; + _quiet = quiet; + _reinstall = reinstall; + _force = force; + _trustRepository = trustRepository; + _noClobber = noClobber; + _credential = credential; + _specifiedPath = specifiedPath; + _asNupkg = asNupkg; + _includeXML = includeXML; + _pathsToInstallPkg = pathsToInstallPkg; + + IEnumerable pkgsAlreadyInstalled = new List(); + + // Check to see if the pkgs are already installed (ie the pkg is installed and the version satisfies the version range provided via param) + // If reinstall is specified, we will skip this check + if (!_reinstall) + { + // Removes all of the names that are already installed from the list of names to search for + names = CheckPkgsInstalled(names); + } + + // If pkg version is NOT installed, continue the installation process + // Go through the repositories and see which one has the pkg version available (ie search call) + ProcessRepositories(names, repository, _trustRepository, _credential); + // Once all the pkg versions and dependency pkg versions are found, proceed to install each + } + + + // done + // Check if any of the pkg versions are already installed + public string[] CheckPkgsInstalled(string[] names) + { + GetHelper getHelper = new GetHelper(cancellationToken, this); + // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) + IEnumerable pkgsAlreadyInstalled = getHelper.ProcessGetParams(names, _versionRange, _pathsToInstallPkg); + + // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names + // In this case we will NOT be checking any dependencies (the assumption is that the dependencies are already available). + if (pkgsAlreadyInstalled.Any()) + { + foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) + { + this.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)); + + // remove this pkg from the list of pkg names install + names.ToList().Remove(pkg.Name); + } + } + + return names; + } + + + // This method calls into the proper repository to search for the pkgs to install + public void ProcessRepositories(string[] packageNames, string[] repository, bool trustRepository, PSCredential credential) + { + var listOfRepositories = RepositorySettings.Read(repository, out string[] _); + // TODO: change name to 'packagesToInstall' + var pkgsLeftToInstall = packageNames.ToList(); + var yesToAll = false; + var noToAll = false; + var repositoryIsNotTrusted = "Untrusted repository"; + var queryInstallUntrustedPackage = "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}' ?"; + + foreach (var repo in listOfRepositories) + { + var sourceTrusted = false; + string repoName = repo.Name; + cmdletPassedIn.WriteDebug(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. + if (repo.Trusted == false && !trustRepository && !_force) + { + cmdletPassedIn.WriteDebug("Checking if untrusted repository should be used"); + + if (!(yesToAll || noToAll)) + { + // Prompt for installation of package from untrusted repository + var message = string.Format(CultureInfo.InvariantCulture, queryInstallUntrustedPackage, repoName); + sourceTrusted = cmdletPassedIn.ShouldContinue(message, repositoryIsNotTrusted, true, ref yesToAll, ref noToAll); + } + } + else { + sourceTrusted = true; + } + + if (sourceTrusted || yesToAll) + { + cmdletPassedIn.WriteDebug("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 + // TODO: make sure to write a test for this scenario + // Search for pkgs + var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter); + + var findHelper = new FindHelper(); + //List pkgsToInstall = FindPkgsToInstall(repoName, repo.Url.AbsoluteUri, credential, isLocalRepo, packageNames, pkgsLeftToInstall); + List pkgsToInstall = beginFindHelper(name: packageNames, Type: null, _version: _versionRange, _prerelease: _prerelease, null, null, repository: repoName, _credential: credential, _includeDependencies: true, writeToConsole: false); + + if (!pkgsToInstall.Any()) + { + // TODO: messaging + return; + } + + // Install pkgs + InstallPackage(pkgsToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); + //pkgsLeftToInstall = returnedPkgsNotInstalled; + } + } + } + + + + + + + + + + + + + + private void InstallPackage(List pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) + { + /*** INSTALL PACKAGES ***/ + foreach (IPackageSearchMetadata p in pkgsToInstall) + { + // Create a temp directory to install to + var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + 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 + dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly; + //TODO: are there Linux accommodations we need to consider here? + + cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Identity.Id)); + + // TODO: move into a private helper function + if (!_quiet && !save) + { + int i = 1; + int j = 1; + /**************************** + * START PACKAGE INSTALLATION -- start progress bar + *****************************/ + // Write-Progress -Activity "Search in Progress" - Status "$i% Complete:" - PercentComplete $i + + int activityId = 0; + string activity = ""; + string statusDescription = ""; + + // If the pkg exists in one of the names passed in, then we wont include it as a dependent package + activityId = 0; + activity = string.Format("Installing {0}...", p.Identity.Id); + statusDescription = string.Format("{0}% Complete:", i++); + + j = 1; + /* + if (packageNames.ToList().Contains(p.Identity.Id)) + { + // If the pkg exists in one of the names passed in, then we wont include it as a dependent package + activityId = 0; + activity = string.Format("Installing {0}...", p.Identity.Id); + statusDescription = string.Format("{0}% Complete:", i++); + + j = 1; + } + else + { + // Child process + // Installing dependent package + activityId = 1; + activity = string.Format("Installing dependent package {0}...", p.Identity.Id); + statusDescription = string.Format("{0}% Complete:", j); + } + */ + var progressRecord = new ProgressRecord(activityId, activity, statusDescription); + cmdletPassedIn.WriteProgress(progressRecord); + } + + // Create PackageIdentity in order to download + var pkgIdentity = new PackageIdentity(p.Identity.Id, p.Identity.Version); + var cacheContext = new SourceCacheContext(); + + if (isLocalRepo) + { + /* Download from a local repository -- this is slightly different process than from a server */ + var localResource = new FindLocalPackagesResourceV2(repoUrl); + var resource = new LocalDownloadResource(repoUrl, localResource); + + // Actually downloading the .nupkg from a local repo + var result = resource.GetDownloadResourceResultAsync( + pkgIdentity, + new PackageDownloadContext(cacheContext), + tempInstallPath, + logger: NullLogger.Instance, + CancellationToken.None).GetAwaiter().GetResult(); + + PackageExtractionContext packageExtractionContext = new PackageExtractionContext( + PackageSaveMode.Nupkg, + PackageExtractionBehavior.XmlDocFileSaveMode, + null, + logger: NullLogger.Instance); + + + if (_asNupkg) // this is Save functinality + { + DirectoryInfo nupkgPath = new DirectoryInfo(((System.IO.FileStream)result.PackageStream).Name); + File.Copy(nupkgPath.FullName, Path.Combine(tempInstallPath, pkgIdentity.Id + pkgIdentity.Version + ".nupkg")); + + continue; + } + else + { + // 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.None); + result.Dispose(); + } + } + else + { + // Set up NuGet API resource for download + PackageSource source = new PackageSource(repoUrl); + if (credential != null) + { + string password = new NetworkCredential(string.Empty, credential.Password).Password; + source.Credentials = PackageSourceCredential.FromUserInput(repoUrl, credential.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(); + try + { + var result = downloadResource.GetDownloadResourceResultAsync( + identity: pkgIdentity, + downloadContext: new PackageDownloadContext(cacheContext), + tempInstallPath, + logger: NullLogger.Instance, + CancellationToken.None).GetAwaiter().GetResult(); + } + catch { } + finally + { + // Need to close the .nupkg + result.Dispose(); + } + + + if (_asNupkg) // Save functionality + { + // Simply move the .nupkg from the temp installation path to the specified path (the path passed in via param value) + var tempPkgIdPath = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToString()); + var tempPkgVersionPath = System.IO.Path.Combine(tempPkgIdPath, p.Identity.Id.ToLower() + "." + p.Identity.Version + ".nupkg"); + var newSavePath = System.IO.Path.Combine(_specifiedPath, p.Identity.Id + "." + p.Identity.Version + ".nupkg"); + + // TODO: path should be preprocessed/resolved + File.Move(tempPkgVersionPath, _specifiedPath); + + //pkgsLeftToInstall.Remove(pkgName); + + continue; + } + } + + cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); + + // Prompt if module requires license acceptance + // Need to read from .psd1 + var newVersion = p.Identity.Version.ToString(); + if (p.Identity.Version.IsPrerelease) + { + newVersion = p.Identity.Version.ToString().Substring(0, p.Identity.Version.ToString().IndexOf('-')); + } + + var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id, newVersion); + var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); + var requireLicenseAcceptance = false; + + // TODO: move into a helper method + // Accept License + if (!save) + { + 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); + + if (rgx.IsMatch(pattern) && !rgx.IsMatch(patternToSkip1) && !rgx.IsMatch(patternToSkip2)) + { + 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.Identity.Id, newVersion); + var LicenseFilePath = Path.Combine(PkgTempInstallPath, "License.txt"); + + if (!File.Exists(LicenseFilePath)) + { + var exMessage = "License.txt not Found. License.txt must be provided when user license acceptance is required."; + var ex = new ArgumentException(exMessage); // System.ArgumentException vs PSArgumentException + var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); + + // TODO: update this to write error + cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + } + + // Otherwise read LicenseFile + string licenseText = System.IO.File.ReadAllText(LicenseFilePath); + var acceptanceLicenseQuery = $"Do you accept the license terms for module '{p.Identity.Id}'."; + var message = licenseText + "`r`n" + acceptanceLicenseQuery; + + var title = "License Acceptance"; + var yesToAll = false; + var noToAll = false; + var shouldContinueResult = ShouldContinue(message, title, true, ref yesToAll, ref noToAll); + + if (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 = $"License Acceptance is required for module '{p.Identity.Id}'. Please specify '-AcceptLicense' to perform this operation."; + var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException + var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); + + // TODO: update to write error + cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + } + } + } + } + + + string dirNameVersion = Path.Combine(tempInstallPath, p.Identity.Id.ToLower(), p.Identity.Version.ToNormalizedString().ToLower()); + + // Delete the extra nupkg related files that are not needed and not part of the module/script + // TODO: consider adding try/catch + DeleteExtraneousFiles(tempInstallPath, p, dirNameVersion); + + + var scriptPath = Path.Combine(dirNameVersion, (p.Identity.Id.ToString() + ".ps1")); + var isScript = File.Exists(scriptPath) ? true : false; + + if (!Directory.Exists(dirNameVersion)) + { + cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", dirNameVersion)); + Directory.CreateDirectory(dirNameVersion); + } + + if (_includeXML) + { + CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); + } + + if (save) + { + //TODO: SavePackage(); + } + else + { + // Copy to proper path + // TODO: resolve this issue; + // todo: create helper function 'findscriptspath... find modules path' + // PICK A PATH TO INSTALL TO + + // TODO: move into helper method (move and delete method) + // PSModules: + /// ./Modules + /// ./Scripts + var installPath = isScript ? psScriptsPath : psModulesPath; + + // Creating the proper installation path depending on whether pkg is a module or script + var newPath = isScript ? installPath + : Path.Combine(installPath, p.Identity.Id.ToString()); + cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); + + // If script, just move the files over, if module, move the version directory over + var tempModuleVersionDir = isScript ? dirNameVersion //Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()) + : Path.Combine(tempInstallPath, p.Identity.Id.ToLower()); + cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); + + if (isScript) + { + // Todo: + // Need to delete old xml files because there can only be 1 per script + var scriptXML = p.Identity.Id + "_InstalledScriptInfo.xml"; + cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)))); + if (File.Exists(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML))) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); + File.Delete(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)); + } + + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML))); + File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)); + + // Need to delete old script file, if that exists + cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(newPath, p.Identity.Id + ".ps1")))); + if (File.Exists(Path.Combine(newPath, p.Identity.Id + ".ps1"))) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); + File.Delete(Path.Combine(newPath, p.Identity.Id + ".ps1")); + } + + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(newPath, p.Identity.Id + ".ps1"))); + File.Move(scriptPath, Path.Combine(newPath, p.Identity.Id + ".ps1")); + } + else + { + // TODO: update variables to be more specific + // If new path does not exist + if (!Directory.Exists(newPath)) + { + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", tempModuleVersionDir, newPath)); + Directory.Move(tempModuleVersionDir, newPath); + } + else + { + tempModuleVersionDir = Path.Combine(tempModuleVersionDir, p.Identity.Version.ToString()); + cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); + + var newVersionPath = Path.Combine(newPath, newVersion); + cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); + + + if (Directory.Exists(newVersionPath)) + { + // Delete the directory path before replacing it with the new module + cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", newVersionPath)); + Directory.Delete(newVersionPath, true); + } + + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", newPath, newVersion)); + Directory.Move(tempModuleVersionDir, Path.Combine(newPath, newVersion)); + + } + } + } + + + + + cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package {0}", p.Identity.Id)); + + // TODO: add finally statement here, consider wrapping in try/catch > should have a debug write, not fail/throw + // Delete the temp directory and all its contents + cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", tempInstallPath)); + if (Directory.Exists(tempInstallPath)) + { + Directory.Delete(tempInstallPath, true); + } + + // TODO: consider some kind of "unable to install messaging" + } + } + + // IGNORE FOR INSTALL + private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, string dirNameVersion, bool isScript, bool isLocalRepo) + { + // I don't believe we should ever be getting to this _asNupkg + if (isScript) + { + var tempScriptPath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToNormalizedString()); + var scriptName = pkgIdentity.Id + ".ps1"; + File.Copy(Path.Combine(tempScriptPath, scriptName), Path.Combine(_specifiedPath, scriptName)); + + if (_includeXML) + { + // else if save and including XML + var scriptXML = p.Identity.Id + "_InstalledScriptInfo.xml"; + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML))); + File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML)); + } + } + else + { + var fullTempInstallpath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); // localRepo ? Path.Combine(tempInstallPath, pkgIdentity.Version.ToString()) : Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); + var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) : Path.Combine(_specifiedPath, pkgIdentity.Id); + + if (isLocalRepo && !Directory.Exists(Path.Combine(_specifiedPath, pkgIdentity.Id))) + { + Directory.CreateDirectory(Path.Combine(_specifiedPath, pkgIdentity.Id)); + } + + if (isLocalRepo) + { + Directory.Move(tempInstallPath, fullPermanentNewPath); + } + else + { + Directory.Move(Path.Combine(tempInstallPath, pkgIdentity.Id), fullPermanentNewPath); + fullPermanentNewPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Version.ToString()); + } + var tempPSGetModuleInfoXML = Path.Combine(Path.Combine(fullPermanentNewPath, pkgIdentity.Id, pkgIdentity.Version.ToString()), "PSGetModuleInfo.xml"); + if (File.Exists(tempPSGetModuleInfoXML)) + { + File.Copy(tempPSGetModuleInfoXML, Path.Combine(fullPermanentNewPath, "PSGetModuleInfo.xml")); + } + + DeleteExtraneousSaveFiles(pkgIdentity, fullPermanentNewPath); + } + } + + + private void CreateMetadataXMLFile(string dirNameVersion, string repoName, IPackageSearchMetadata pkg, bool isScript) + { + // Create PSGetModuleInfo.xml + // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml" + // Modules will have a metadata file: "PSGetModuleInfo.xml" + var metadataXMLPath = isScript ? Path.Combine(dirNameVersion, (pkg.Identity.Id + "_InstalledScriptInfo.xml")) + : Path.Combine(dirNameVersion, "PSGetModuleInfo.xml"); + + + // TODO: this may not be needed anymore, depending on what findHelper returns + // TODO: Can use PSResourceInfo obj to find out if isScript + // first we need to put the pkg into a PSResoourceInfo object, then try to write the xml + // Try to convert the IPackageSearchMetadata into a PSResourceInfo object so that we can then easily create the metadata xml + if (!PSResourceInfo.TryConvert( + metadataToParse: pkg, + psGetInfo: out PSResourceInfo pkgInstalled, + repositoryName: repoName, + type: isScript ? ResourceType.Script : ResourceType.Module, + errorMsg: out string errorMsg)) + { + cmdletPassedIn.WriteError(new ErrorRecord( + new PSInvalidOperationException("Error parsing IPackageSearchMetadata to PSResourceInfo with message: " + errorMsg), + "IPackageSearchMetadataToPSResourceInfoParsingError", + ErrorCategory.InvalidResult, + this)); + return; + } + + // TODO: now need to add the extra properties like 'installation date' and 'installation path' + // Write all metadata into metadataXMLPath + if (!pkgInstalled.TryWrite(metadataXMLPath, out string error)) + { + // TODO: write error + } + + } + + + private void DeleteExtraneousFiles(string tempInstallPath, IPackageSearchMetadata p, string dirNameVersion) + { + // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module + var nupkgSHAToDelete = Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg.sha512").ToLower()); + var nuspecToDelete = Path.Combine(dirNameVersion, (p.Identity.Id + ".nuspec").ToLower()); + var nupkgToDelete = Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg").ToLower()); + + // unforunately have to check if each file exists because it may or may not be there + if (File.Exists(nupkgSHAToDelete)) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgSHAToDelete)); + File.Delete(nupkgSHAToDelete); + } + if (File.Exists(nuspecToDelete)) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nuspecToDelete)); + File.Delete(nuspecToDelete); + } + if (File.Exists(nupkgToDelete)) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgToDelete)); + File.Delete(nupkgToDelete); + } + + // TODO: write debug messaging here + } + + private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullPermanentNewPath) + { + var relsPath = Path.Combine(fullPermanentNewPath, "_rels"); + if (Directory.Exists(relsPath)) + { + Directory.Delete(relsPath, true); + } + + var packagePath = Path.Combine(fullPermanentNewPath, "package"); + if (Directory.Exists(packagePath)) + { + Directory.Delete(packagePath, true); + } + + var pkgIdPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Id); + if (Directory.Exists(pkgIdPath)) + { + Directory.Delete(pkgIdPath, true); + } + + var pkgVersionPath = Path.Combine(Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()), pkgIdentity.Version.ToString()); + if (Directory.Exists(pkgVersionPath)) + { + Directory.Delete(Path.Combine(pkgVersionPath), true); + } + + var contentTypesXMLPath = Path.Combine(fullPermanentNewPath, "[Content_Types].xml"); + if (File.Exists(contentTypesXMLPath)) + { + File.Delete(contentTypesXMLPath); + } + + var nuspecPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Id + ".nuspec"); + if (File.Exists(nuspecPath)) + { + File.Delete(nuspecPath); + } + + var nupkgMetadata = Path.Combine(fullPermanentNewPath, ".nupkg.metadata"); + if (File.Exists(nupkgMetadata)) + { + File.Delete(nupkgMetadata); + } + } + + + + // Finds the exact version that we need to search for or install + // If a user provides a version range we need to make sure version is available + //// search for the pkg in the repository to see if that version is available. + private IPackageSearchMetadata SearchForPkgVersion(string pkgName, PackageMetadataResource pkgMetadataResource, SourceCacheContext srcContext, string repositoryName, VersionRange versionRange, bool prerelease) + { + IPackageSearchMetadata filteredFoundPkgs = null; + + if (versionRange == null) + { + // ensure that the latst version is returned first (the ordering of versions differ + // TODO: proper error handling + try + { + // searchin the repository + filteredFoundPkgs = (pkgMetadataResource.GetMetadataAsync(pkgName, prerelease, false, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult() + .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease) + .FirstOrDefault()); + } + catch + { + // TODO: catch exception here + } + + if (filteredFoundPkgs == null) + { + cmdletPassedIn.WriteVerbose(String.Format("Could not find package '{0}' in repository '{1}'", pkgName, repositoryName)); + + return null; + } + } + else + { + // Search for packages within a version range + // ensure that the latest version is returned first (the ordering of versions differ + filteredFoundPkgs = (pkgMetadataResource.GetMetadataAsync(pkgName, prerelease, false, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult() + .Where(p => versionRange.Satisfies(p.Identity.Version)) + .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease) + .FirstOrDefault()); + } + + return filteredFoundPkgs; + } + + + + // TODO: FindHelper will return dependencies as well + private List FindDependenciesFromSource(IPackageSearchMetadata pkg, PackageMetadataResource pkgMetadataResource, SourceCacheContext srcContext, bool prerelease, bool reinstall, string _path, string repositoryUrl) + { + // Dependency resolver + // This function is recursively called + // Call the findpackages from source helper (potentially generalize this so it's finding packages from source or cache) + List foundDependencies = new List(); + + // 1) Check the dependencies of this pkg + // 2) For each dependency group, search for the appropriate name and version + // A dependency group includes all the dependencies for a particular framework + foreach (var dependencyGroup in pkg.DependencySets) + { + foreach (var pkgDependency in dependencyGroup.Packages) + { + IEnumerable dependencies = null; + // a) Check that the appropriate pkg dependencies exist + // Returns all versions from a single package id. + try + { + dependencies = pkgMetadataResource.GetMetadataAsync(pkgDependency.Id, prerelease, true, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult(); + } + catch + { } + // b) Check if the appropriate verion range exists (if version exists, then add it to the list to return) + VersionRange versionRange = null; + try + { + versionRange = VersionRange.Parse(pkgDependency.VersionRange.OriginalString); + } + catch + { + var exMessage = String.Format("Error parsing version range"); + var ex = new ArgumentException(exMessage); + var ErrorParsingVersionRange = new ErrorRecord(ex, "ErrorParsingVersionRange", ErrorCategory.ParserError, null); + + cmdletPassedIn.ThrowTerminatingError(ErrorParsingVersionRange); + } + + // If no version/version range is specified the we just return the latest version + IPackageSearchMetadata depPkgToReturn = (versionRange == null ? + dependencies.FirstOrDefault() : + dependencies.Where(v => versionRange.Satisfies(v.Identity.Version)).FirstOrDefault()); + + + + + // TODO: figure out paths situation + // If the pkg already exists on the system, don't add it to the list of pkgs that need to be installed + var dirName = save ? cmdletPassedIn.SessionState.Path.GetResolvedPSPathFromPSPath(_path).FirstOrDefault().Path : Path.Combine(psModulesPath, pkgDependency.Id); + var dependencyAlreadyInstalled = false; + + // Check to see if the package dir exists in the path + // If save we only check the -path passed in + if (save || _pathsToInstallPkg.Contains(dirName, StringComparer.OrdinalIgnoreCase)) + { + // Then check to see if the package exists in the path + if (Directory.Exists(dirName)) + { + var pkgDirVersion = (Directory.GetDirectories(dirName)).ToList(); + List pkgVersion = new List(); + foreach (var path in pkgDirVersion) + { + pkgVersion.Add(Path.GetFileName(path)); + } + + // These are all the packages already installed + NuGetVersion ver; + var pkgsAlreadyInstalled = pkgVersion.FindAll(p => NuGetVersion.TryParse(p, out ver) && versionRange.Satisfies(ver)); + + if (pkgsAlreadyInstalled.Any() && !reinstall) + { + // Don't add the pkg to the list of pkgs that need to be installed + dependencyAlreadyInstalled = true; + } + } + } + + if (!dependencyAlreadyInstalled) + { + foundDependencies.Add(depPkgToReturn); + } + + // Recursively search for any dependencies the pkg has + foundDependencies.AddRange(FindDependenciesFromSource(depPkgToReturn, pkgMetadataResource, srcContext, prerelease, reinstall, _path, repositoryUrl)); + } + } + + return foundDependencies; + } + } + + + +} \ No newline at end of file diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs new file mode 100644 index 000000000..c5f525ddb --- /dev/null +++ b/src/code/InstallPSResource.cs @@ -0,0 +1,266 @@ +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Management.Automation; +using System.Threading; +using static System.Environment; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// The Install-PSResource cmdlet installs a resource. + /// It returns nothing. + /// + + [Cmdlet(VerbsLifecycle.Install, "PSResource", DefaultParameterSetName = "NameParameterSet", SupportsShouldProcess = true, HelpUri = "")] + public sealed + class InstallPSResource : PSCmdlet + { + #region parameters + /// + /// Specifies the exact names of resources to install from a repository. + /// A comma-separated list of module names is accepted. The resource name must match the resource name in the repository. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string[] Name { get; set; } + + /// + /// Used for pipeline input. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = InputObjectSet)] + [ValidateNotNullOrEmpty] + public object[] InputObject { get; set; } + + /// + /// Specifies the version or version range of the package to be installed + /// + [Parameter(ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string Version { get; set; } + + /// + /// Specifies to allow installation of prerelease versions + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter Prerelease { get; set; } + + /// + /// Specifies a user account that has rights to find a resource from a specific repository. + /// + [Parameter(ParameterSetName = NameParameterSet)] + // todo: add tab completion (look at get-psresourcerepository at the name parameter) + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + [ValidateNotNullOrEmpty] + public string[] Repository { get; set; } + + /// + /// Specifies a user account that has rights to find a resource from a specific repository. + /// + [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public PSCredential Credential { get; set; } + + /// + /// Specifies to return any dependency packages. + /// Currently only used when name param is specified. + /// + [ValidateSet("CurrentUser", "AllUsers")] + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public string Scope { get; set; } + + /// + /// Overrides warning messages about installation conflicts about existing commands on a computer. + /// Overwrites existing commands that have the same name as commands being installed by a module. AllowClobber and Force can be used together in an Install-Module command. + /// Prevents installing modules that have the same cmdlets as a differently named module already + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter NoClobber { get; set; } + + /// + /// Suppresses being prompted for untrusted sources. + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter TrustRepository { get; set; } + + /// + /// Overwrites a previously installed resource with the same name and version. + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter Reinstall { get; set; } + + /// + /// Suppresses progress information. + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter Quiet { get; set; } + + /// + /// For modules that require a license, AcceptLicense automatically accepts the license agreement during installation. + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter AcceptLicense { get; set; } + + /// + /// + /// + [Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public String RequiredResourceFile { get; set; } + + /// + /// + /// + [Parameter(ParameterSetName = RequiredResourceParameterSet)] + public Object RequiredResource // takes either string (json) or hashtable + { + get { return _requiredResourceHash != null ? (Object)_requiredResourceHash : (Object)_requiredResourceJson; } + + set { + if (value.GetType().Name.Equals("String")) + { + _requiredResourceJson = (String) value; + } + else if (value.GetType().Name.Equals("Hashtable")) + { + _requiredResourceHash = (Hashtable) value; + } + else + { + throw new ParameterBindingException("Object is not a JSON or Hashtable"); + } + } + } + private string _requiredResourceJson; + private Hashtable _requiredResourceHash; + #endregion + + #region members + private const string NameParameterSet = "NameParameterSet"; + private const string InputObjectSet = "InputObjectSet"; + private const string RequiredResourceFileParameterSet = "RequiredResourceFileParameterSet"; + private const string RequiredResourceParameterSet = "RequiredResourceParameterSet"; + List _pathsToInstallPkg; + VersionRange _versionRange; + #endregion + + #region Methods + protected override void BeginProcessing() + { + // validate that if a -Version param is passed in that it can be parsed into a NuGet version range. + // an exact version will be formatted into a version range. + if (ParameterSetName.Equals("NameParameterSet") && Version != null && !Utils.TryParseVersionOrVersionRange(Version, out _versionRange)) + { + var exMessage = "Argument for -Version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + var IncorrectVersionFormat = new ErrorRecord(ex, "IncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(IncorrectVersionFormat); + } + + _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, Scope); + } + + protected override void ProcessRecord() + { + // Define the cancellation token. + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken cancellationToken = source.Token; + + var installHelper = new InstallHelper(update: false, save: false, cancellationToken, this); + + switch (ParameterSetName) + { + case NameParameterSet: + installHelper.ProcessInstallParams(Name, _versionRange, Prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + break; + + // TODO: make sure InputObject types are correct + // TODO: Consider switch statement of object type to clean up a bit + case InputObjectSet: + if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) + { + foreach (PSModuleInfo pkg in InputObject) + { + var prerelease = false; + + if (pkg.PrivateData != null) + { + Hashtable privateData = (Hashtable)pkg.PrivateData; + if (privateData.ContainsKey("PSData")) + { + Hashtable psData = (Hashtable)privateData["PSData"]; + + if (psData.ContainsKey("Prerelease") && !string.IsNullOrEmpty((string)psData["Prerelease"])) + { + prerelease = true; + } + } + } + + // Need to explicitly assign inputObjVersionRange in order to pass to ProcessInstallParams + VersionRange inputObjVersionRange = new VersionRange(); + if (pkg.Version != null && !Utils.TryParseVersionOrVersionRange(pkg.Version.ToString(), out inputObjVersionRange)) + { + var exMessage = "Argument for version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + var InputObjIncorrectVersionFormat = new ErrorRecord(ex, "InputObjIncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InputObjIncorrectVersionFormat); + } + + installHelper.ProcessInstallParams(new[] { pkg.Name }, inputObjVersionRange, prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + } + } + else if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) + { + foreach (PSObject pkg in InputObject) + { + if (pkg != null) + { + var name = (string)pkg.Properties["Name"].Value; + var version = (NuGetVersion)pkg.Properties["Version"].Value; + var prerelease = version.IsPrerelease; + + VersionRange inputObjVersionRange = new VersionRange(); + if (version != null && !Utils.TryParseVersionOrVersionRange(version.ToString(), out inputObjVersionRange)) + { + var exMessage = "Argument for version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + var InputObjIncorrectVersionFormat = new ErrorRecord(ex, "InputObjIncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(InputObjIncorrectVersionFormat); + } + + installHelper.ProcessInstallParams(new[] { name }, inputObjVersionRange, prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + } + } + } + break; + + case RequiredResourceFileParameterSet: + // TODO: throw PSNotImplementedException + WriteDebug("Not yet implemented"); + break; + + case RequiredResourceParameterSet: + // TODO: throw PSNotImplementedException + WriteDebug("Not yet implemented"); + break; + + default: + // TODO: throw some kind of terminating error + // TODO: change to debug assert + WriteDebug("Invalid parameter set"); + break; + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs new file mode 100644 index 000000000..f6591038b --- /dev/null +++ b/src/code/InstallPkgParams.cs @@ -0,0 +1,17 @@ +using System.Management.Automation; + +public class PkgParams +{ + public string Name { get; set; } + public string Version { get; set; } + public string Repository { get; set; } + public PSCredential Credential { get; set; } + public bool AcceptLicense { get; set; } + public bool Prerelease { get; set; } + public string Scope { get; set; } + public bool Quiet { get; set; } + public bool Reinstall { get; set; } + public bool Force { get; set; } + public bool TrustRepository { get; set; } + public bool NoClobber { get; set; } +} \ No newline at end of file diff --git a/src/code/PowerShellGet.csproj b/src/code/PowerShellGet.csproj index 2a1f3e63e..10477f103 100644 --- a/src/code/PowerShellGet.csproj +++ b/src/code/PowerShellGet.csproj @@ -9,13 +9,14 @@ 3.0.0 3.0.0 netstandard2.0;net472; + 7.2 - + diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 2ef46c5de..4ac62bf00 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -166,6 +166,64 @@ public static List GetAllResourcePaths(PSCmdlet psCmdlet) return pathsToSearch; } + // Find all potential installation paths given a scope + public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string scope) + { + List installationPaths = new List(); + var PSVersion6 = new Version(6, 0); + var isCorePS = psCmdlet.Host.Version >= PSVersion6; + string myDocumentsPath; + string programFilesPath; + scope = String.IsNullOrEmpty(scope) ? string.Empty : scope; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string powerShellType = isCorePS ? "PowerShell" : "WindowsPowerShell"; + + myDocumentsPath = Path.Combine(Environment.GetFolderPath(SpecialFolder.MyDocuments), powerShellType); + programFilesPath = Path.Combine(Environment.GetFolderPath(SpecialFolder.ProgramFiles), powerShellType); + } + else + { + // paths are the same for both Linux and MacOS + myDocumentsPath = System.IO.Path.Combine(Environment.GetFolderPath(SpecialFolder.LocalApplicationData), "Powershell"); + programFilesPath = System.IO.Path.Combine("usr", "local", "share", "Powershell"); + } + + + // If no explicit specification, will return PSModulePath, and then CurrentUser paths + // Installation will search for a /Modules or /Scripts directory + // If they are not available within one of the paths in PSModulePath, the CurrentUser path will be used. + if (string.IsNullOrEmpty(scope)) + { + string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); + installationPaths = psModulePath.Split(';').ToList(); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); + } + // If user explicitly specifies AllUsers + if (scope.Equals("AllUsers")) + { + installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Scripts")); + } + // If user explicitly specifies CurrentUser + else if (scope.Equals("CurrentUser")) + { + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); + } + else + { + psCmdlet.WriteDebug(string.Format("Invalid scope provided: '{0}'", scope)); + } + + installationPaths = installationPaths.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + installationPaths.ForEach(dir => psCmdlet.WriteDebug(string.Format("All paths to search: '{0}'", dir))); + + return installationPaths; + } + /// /// Converts an ArrayList of object types to a string array. /// From 792c6fae1816b931029ea050e336ac654bb6b00d Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Wed, 23 Jun 2021 11:50:02 -0700 Subject: [PATCH 07/62] Fix bug in GetHelper class related to debug statement --- src/code/GetHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/code/GetHelper.cs b/src/code/GetHelper.cs index faf50bd95..3995bcf90 100644 --- a/src/code/GetHelper.cs +++ b/src/code/GetHelper.cs @@ -60,7 +60,6 @@ public List FilterPkgPathsByName(string[] names, List dirsToSear p => nameWildCardPattern.IsMatch( System.IO.Path.GetFileNameWithoutExtension((new DirectoryInfo(p).Name))))); } - _cmdletPassedIn.WriteDebug(wildCardDirsToSearch.Any().ToString()); return wildCardDirsToSearch; } From 219e71bbb72a79b93c8d86fc97e8dc1dc86cc89a Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Wed, 23 Jun 2021 11:51:22 -0700 Subject: [PATCH 08/62] Major refactors to Install helper --- src/code/InstallHelper.cs | 901 ++++++++++++++++---------------------- 1 file changed, 380 insertions(+), 521 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 26f6557ea..265120118 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// Find helper class + /// Install helper class /// internal class InstallHelper : PSCmdlet { @@ -58,12 +58,7 @@ public InstallHelper(bool update, bool save, CancellationToken cancellationToken this.cmdletPassedIn = cmdletPassedIn; } - /// 1: Check to see if the pkgs are already installed (ie the pkg is installed and the version satisfies the version range provided via param) - /// 2: If pkg version is NOT installed, continue the installation process - /// 2a: Go through the repositories and see which one has the pkg version availble (ie search call) - /// Use find helper to search for the exact pkg version we want to install - /// 2b: Proceed to find all dependencies for the package availale - /// 3: Once all the pkg versions and dependency pkg versions are found, proceed to install each + // TODO: add passthru public void ProcessInstallParams( string[] names, VersionRange versionRange, @@ -106,53 +101,16 @@ public void ProcessInstallParams( _pathsToInstallPkg = pathsToInstallPkg; IEnumerable pkgsAlreadyInstalled = new List(); - - // Check to see if the pkgs are already installed (ie the pkg is installed and the version satisfies the version range provided via param) - // If reinstall is specified, we will skip this check - if (!_reinstall) - { - // Removes all of the names that are already installed from the list of names to search for - names = CheckPkgsInstalled(names); - } - // If pkg version is NOT installed, continue the installation process - // Go through the repositories and see which one has the pkg version available (ie search call) + // Go through the repositories and see which is the first repository to have the pkg version available ProcessRepositories(names, repository, _trustRepository, _credential); - // Once all the pkg versions and dependency pkg versions are found, proceed to install each - } - - - // done - // Check if any of the pkg versions are already installed - public string[] CheckPkgsInstalled(string[] names) - { - GetHelper getHelper = new GetHelper(cancellationToken, this); - // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) - IEnumerable pkgsAlreadyInstalled = getHelper.ProcessGetParams(names, _versionRange, _pathsToInstallPkg); - - // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names - // In this case we will NOT be checking any dependencies (the assumption is that the dependencies are already available). - if (pkgsAlreadyInstalled.Any()) - { - foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) - { - this.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)); - - // remove this pkg from the list of pkg names install - names.ToList().Remove(pkg.Name); - } - } - - return names; } - - // This method calls into the proper repository to search for the pkgs to install + // This method calls iterates through repositories (by priority order) to search for the pkgs to install public void ProcessRepositories(string[] packageNames, string[] repository, bool trustRepository, PSCredential credential) { var listOfRepositories = RepositorySettings.Read(repository, out string[] _); - // TODO: change name to 'packagesToInstall' - var pkgsLeftToInstall = packageNames.ToList(); + var packagesToInstall = packageNames.ToList(); var yesToAll = false; var noToAll = false; var repositoryIsNotTrusted = "Untrusted repository"; @@ -162,7 +120,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool { var sourceTrusted = false; string repoName = repo.Name; - cmdletPassedIn.WriteDebug(string.Format("Attempting to search for packages in '{0}'", repoName)); + cmdletPassedIn.WriteDebug(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. @@ -187,398 +145,265 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool // If it can't find the pkg in one repository, it'll look for it in the next repo in the list // TODO: make sure to write a test for this scenario - // Search for pkgs var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter); - var findHelper = new FindHelper(); - //List pkgsToInstall = FindPkgsToInstall(repoName, repo.Url.AbsoluteUri, credential, isLocalRepo, packageNames, pkgsLeftToInstall); - List pkgsToInstall = beginFindHelper(name: packageNames, Type: null, _version: _versionRange, _prerelease: _prerelease, null, null, repository: repoName, _credential: credential, _includeDependencies: true, writeToConsole: false); - + var cancellationToken = new CancellationToken(); + var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); + // Finds parent packages and dependencies + IEnumerable pkgsToInstall = findHelper.FindByResourceName( + name: packageNames, + type: ResourceType.None, + version: _versionRange.OriginalString, + prerelease: _prerelease, + tag: null, + repository: new string[] { repoName }, + credential: credential, + includeDependencies: true); + + var test = pkgsToInstall.FirstOrDefault(); + + // Deduplicate any packages + pkgsToInstall.GroupBy( + m => new { m.Name, m.Version }).Select( + group => group.First()).ToList(); + if (!pkgsToInstall.Any()) { - // TODO: messaging + cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); return; } - // Install pkgs + // 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 is specified, we will skip this check + if (!_reinstall) + { + // Removes all of the names that are already installed from the list of names to search for + pkgsToInstall = FilterByInstalledPkgs(pkgsToInstall); + } + + if (!pkgsToInstall.Any()) return; + InstallPackage(pkgsToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); - //pkgsLeftToInstall = returnedPkgsNotInstalled; } } } + // Check if any of the pkg versions are already installed, if they are we'll remove them from the list of packages to install + public IEnumerable FilterByInstalledPkgs(IEnumerable packagesToInstall) + { + List pkgNames = new List(); + foreach (var pkg in packagesToInstall) + { + pkgNames.Add(pkg.Name); + } + GetHelper getHelper = new GetHelper(cancellationToken, this); + // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) + IEnumerable pkgsAlreadyInstalled = getHelper.ProcessGetParams(pkgNames.ToArray(), _versionRange, _pathsToInstallPkg); + // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names + if (pkgsAlreadyInstalled.Any()) + { + foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) + { + this.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)); + // remove this pkg from the list of pkg names install + packagesToInstall.ToList().Remove(pkg); + } + } - - - - - - - - - private void InstallPackage(List pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) + return packagesToInstall; + } + + private void InstallPackage(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) { - /*** INSTALL PACKAGES ***/ - foreach (IPackageSearchMetadata p in pkgsToInstall) + foreach (PSResourceInfo p in pkgsToInstall) { - // Create a temp directory to install to var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - 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 - dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly; - //TODO: are there Linux accommodations we need to consider here? - - cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Identity.Id)); - - // TODO: move into a private helper function - if (!_quiet && !save) + try { - int i = 1; - int j = 1; - /**************************** - * START PACKAGE INSTALLATION -- start progress bar - *****************************/ - // Write-Progress -Activity "Search in Progress" - Status "$i% Complete:" - PercentComplete $i - - int activityId = 0; - string activity = ""; - string statusDescription = ""; - - // If the pkg exists in one of the names passed in, then we wont include it as a dependent package - activityId = 0; - activity = string.Format("Installing {0}...", p.Identity.Id); - statusDescription = string.Format("{0}% Complete:", i++); - - j = 1; - /* - if (packageNames.ToList().Contains(p.Identity.Id)) - { - // If the pkg exists in one of the names passed in, then we wont include it as a dependent package - activityId = 0; - activity = string.Format("Installing {0}...", p.Identity.Id); - statusDescription = string.Format("{0}% Complete:", i++); - - j = 1; - } - else + // 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 = dir.Attributes & ~FileAttributes.ReadOnly; + + cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Name)); + + if (!_quiet && !save) { - // Child process - // Installing dependent package - activityId = 1; - activity = string.Format("Installing dependent package {0}...", p.Identity.Id); - statusDescription = string.Format("{0}% Complete:", j); + // todo: UPDATE PROGRESS BAR + CallProgressBar(p); } - */ - var progressRecord = new ProgressRecord(activityId, activity, statusDescription); - cmdletPassedIn.WriteProgress(progressRecord); - } - - // Create PackageIdentity in order to download - var pkgIdentity = new PackageIdentity(p.Identity.Id, p.Identity.Version); - var cacheContext = new SourceCacheContext(); - if (isLocalRepo) - { - /* Download from a local repository -- this is slightly different process than from a server */ - var localResource = new FindLocalPackagesResourceV2(repoUrl); - var resource = new LocalDownloadResource(repoUrl, localResource); - - // Actually downloading the .nupkg from a local repo - var result = resource.GetDownloadResourceResultAsync( - pkgIdentity, - new PackageDownloadContext(cacheContext), - tempInstallPath, - logger: NullLogger.Instance, - CancellationToken.None).GetAwaiter().GetResult(); - - PackageExtractionContext packageExtractionContext = new PackageExtractionContext( - PackageSaveMode.Nupkg, - PackageExtractionBehavior.XmlDocFileSaveMode, - null, - logger: NullLogger.Instance); - - - if (_asNupkg) // this is Save functinality - { - DirectoryInfo nupkgPath = new DirectoryInfo(((System.IO.FileStream)result.PackageStream).Name); - File.Copy(nupkgPath.FullName, Path.Combine(tempInstallPath, pkgIdentity.Id + pkgIdentity.Version + ".nupkg")); - - continue; - } - else - { - // 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.None); - result.Dispose(); - } - } - else - { - // Set up NuGet API resource for download - PackageSource source = new PackageSource(repoUrl); - if (credential != null) + // Create PackageIdentity in order to download + if (!NuGetVersion.TryParse(p.Version.ToString(), out NuGetVersion pkgVersion)) { - string password = new NetworkCredential(string.Empty, credential.Password).Password; - source.Credentials = PackageSourceCredential.FromUserInput(repoUrl, credential.UserName, password, true, null); + cmdletPassedIn.WriteDebug("Error parsing version into a NuGetVersion"); } - var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider); - SourceRepository repository = new SourceRepository(source, provider); + var pkgIdentity = new PackageIdentity(p.Name, pkgVersion); + var cacheContext = new SourceCacheContext(); - /* Download from a non-local repository -- ie server */ - var downloadResource = repository.GetResourceAsync().GetAwaiter().GetResult(); - try + if (isLocalRepo) { - var result = downloadResource.GetDownloadResourceResultAsync( - identity: pkgIdentity, - downloadContext: new PackageDownloadContext(cacheContext), - tempInstallPath, - logger: NullLogger.Instance, - CancellationToken.None).GetAwaiter().GetResult(); - } - catch { } - finally - { - // Need to close the .nupkg - result.Dispose(); - } + /* Download from a local repository -- this is slightly different process than from a server */ + var localResource = new FindLocalPackagesResourceV2(repoUrl); + var resource = new LocalDownloadResource(repoUrl, 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.None).GetAwaiter().GetResult(); - if (_asNupkg) // Save functionality - { - // Simply move the .nupkg from the temp installation path to the specified path (the path passed in via param value) - var tempPkgIdPath = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToString()); - var tempPkgVersionPath = System.IO.Path.Combine(tempPkgIdPath, p.Identity.Id.ToLower() + "." + p.Identity.Version + ".nupkg"); - var newSavePath = System.IO.Path.Combine(_specifiedPath, p.Identity.Id + "." + p.Identity.Version + ".nupkg"); - // TODO: path should be preprocessed/resolved - File.Move(tempPkgVersionPath, _specifiedPath); - - //pkgsLeftToInstall.Remove(pkgName); + if (_asNupkg) // this is Save functinality + { + DirectoryInfo nupkgPath = new DirectoryInfo(((System.IO.FileStream)result.PackageStream).Name); + File.Copy(nupkgPath.FullName, Path.Combine(tempInstallPath, pkgIdentity.Id + pkgIdentity.Version + ".nupkg")); - continue; + continue; + } + else + { + // 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.None); + result.Dispose(); + } } - } - - cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); - - // Prompt if module requires license acceptance - // Need to read from .psd1 - var newVersion = p.Identity.Version.ToString(); - if (p.Identity.Version.IsPrerelease) - { - newVersion = p.Identity.Version.ToString().Substring(0, p.Identity.Version.ToString().IndexOf('-')); - } - - var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id, newVersion); - var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); - var requireLicenseAcceptance = false; - - // TODO: move into a helper method - // Accept License - if (!save) - { - if (File.Exists(moduleManifest)) + else { - using (StreamReader sr = new StreamReader(moduleManifest)) + // Set up NuGet API resource for download + PackageSource source = new PackageSource(repoUrl); + if (credential != null) { - 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); - - if (rgx.IsMatch(pattern) && !rgx.IsMatch(patternToSkip1) && !rgx.IsMatch(patternToSkip2)) - { - requireLicenseAcceptance = true; - } + string password = new NetworkCredential(string.Empty, credential.Password).Password; + source.Credentials = PackageSourceCredential.FromUserInput(repoUrl, credential.UserName, password, true, null); } + var provider = FactoryExtensionsV3.GetCoreV3(NuGet.Protocol.Core.Types.Repository.Provider); + SourceRepository repository = new SourceRepository(source, provider); - // Licesnse agreement processing - if (requireLicenseAcceptance) + /* Download from a non-local repository -- ie server */ + var downloadResource = repository.GetResourceAsync().GetAwaiter().GetResult(); + DownloadResourceResult result = null; + try { - // If module requires license acceptance and -AcceptLicense is not passed in, display prompt - if (!_acceptLicense) - { - var PkgTempInstallPath = Path.Combine(tempInstallPath, p.Identity.Id, newVersion); - var LicenseFilePath = Path.Combine(PkgTempInstallPath, "License.txt"); - - if (!File.Exists(LicenseFilePath)) - { - var exMessage = "License.txt not Found. License.txt must be provided when user license acceptance is required."; - var ex = new ArgumentException(exMessage); // System.ArgumentException vs PSArgumentException - var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); - - // TODO: update this to write error - cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); - } - - // Otherwise read LicenseFile - string licenseText = System.IO.File.ReadAllText(LicenseFilePath); - var acceptanceLicenseQuery = $"Do you accept the license terms for module '{p.Identity.Id}'."; - var message = licenseText + "`r`n" + acceptanceLicenseQuery; - - var title = "License Acceptance"; - var yesToAll = false; - var noToAll = false; - var shouldContinueResult = ShouldContinue(message, title, true, ref yesToAll, ref noToAll); - - if (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 = $"License Acceptance is required for module '{p.Identity.Id}'. Please specify '-AcceptLicense' to perform this operation."; - var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException - var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); - - // TODO: update to write error - cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); - } + result = downloadResource.GetDownloadResourceResultAsync( + identity: pkgIdentity, + downloadContext: new PackageDownloadContext(cacheContext), + globalPackagesFolder: tempInstallPath, + logger: NullLogger.Instance, + CancellationToken.None).GetAwaiter().GetResult(); + } + catch (Exception e) + { + cmdletPassedIn.WriteDebug(string.Format("Error attempting download: '{0}'", e.Message)); + } + finally + { + // Need to close the .nupkg + if (result != null) result.Dispose(); } - } - } + if (_asNupkg) // Save functionality + { + /* + // Simply move the .nupkg from the temp installation path to the specified path (the path passed in via param value) + var tempPkgIdPath = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToString()); + var tempPkgVersionPath = System.IO.Path.Combine(tempPkgIdPath, p.Identity.Id.ToLower() + "." + p.Identity.Version + ".nupkg"); + var newSavePath = System.IO.Path.Combine(_specifiedPath, p.Identity.Id + "." + p.Identity.Version + ".nupkg"); - string dirNameVersion = Path.Combine(tempInstallPath, p.Identity.Id.ToLower(), p.Identity.Version.ToNormalizedString().ToLower()); + // TODO: path should be preprocessed/resolved + File.Move(tempPkgVersionPath, _specifiedPath); - // Delete the extra nupkg related files that are not needed and not part of the module/script - // TODO: consider adding try/catch - DeleteExtraneousFiles(tempInstallPath, p, dirNameVersion); - + //packagesToInstall.Remove(pkgName); - var scriptPath = Path.Combine(dirNameVersion, (p.Identity.Id.ToString() + ".ps1")); - var isScript = File.Exists(scriptPath) ? true : false; + continue; + */ + } + } - if (!Directory.Exists(dirNameVersion)) - { - cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", dirNameVersion)); - Directory.CreateDirectory(dirNameVersion); - } + cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); - if (_includeXML) - { - CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); - } + // Prompt if module requires license acceptance (need to read info license acceptance info from the module manifest) + var newVersion = p.Version.ToString(); // does system.version include prerelease newVersion = p.Identity.Version.ToString().Substring(0, p.Identity.Version.ToString().IndexOf('-')); + var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id, newVersion); + var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); - if (save) - { - //TODO: SavePackage(); - } - else - { - // Copy to proper path - // TODO: resolve this issue; - // todo: create helper function 'findscriptspath... find modules path' - // PICK A PATH TO INSTALL TO - - // TODO: move into helper method (move and delete method) - // PSModules: - /// ./Modules - /// ./Scripts - var installPath = isScript ? psScriptsPath : psModulesPath; - - // Creating the proper installation path depending on whether pkg is a module or script - var newPath = isScript ? installPath - : Path.Combine(installPath, p.Identity.Id.ToString()); - cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); - - // If script, just move the files over, if module, move the version directory over - var tempModuleVersionDir = isScript ? dirNameVersion //Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()) - : Path.Combine(tempInstallPath, p.Identity.Id.ToLower()); - cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); - - if (isScript) + // Accept License verification + if (!save) { - // Todo: - // Need to delete old xml files because there can only be 1 per script - var scriptXML = p.Identity.Id + "_InstalledScriptInfo.xml"; - cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)))); - if (File.Exists(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML))) - { - cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); - File.Delete(Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)); - } + CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); + } - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML))); - File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(psScriptsPath, "InstalledScriptInfos", scriptXML)); + string dirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), p.Version.ToString()); // p.Identity.Version.ToNormalizedString().ToLower()); - // Need to delete old script file, if that exists - cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(newPath, p.Identity.Id + ".ps1")))); - if (File.Exists(Path.Combine(newPath, p.Identity.Id + ".ps1"))) - { - cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); - File.Delete(Path.Combine(newPath, p.Identity.Id + ".ps1")); - } + // Delete the extra nupkg related files that are not needed and not part of the module/script + DeleteExtraneousFiles(tempInstallPath, p, dirNameVersion); - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(newPath, p.Identity.Id + ".ps1"))); - File.Move(scriptPath, Path.Combine(newPath, p.Identity.Id + ".ps1")); - } - else + if (!Directory.Exists(dirNameVersion)) { - // TODO: update variables to be more specific - // If new path does not exist - if (!Directory.Exists(newPath)) - { - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", tempModuleVersionDir, newPath)); - Directory.Move(tempModuleVersionDir, newPath); - } - else - { - tempModuleVersionDir = Path.Combine(tempModuleVersionDir, p.Identity.Version.ToString()); - cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); - - var newVersionPath = Path.Combine(newPath, newVersion); - cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); - + cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", dirNameVersion)); + Directory.CreateDirectory(dirNameVersion); + } - if (Directory.Exists(newVersionPath)) - { - // Delete the directory path before replacing it with the new module - cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", newVersionPath)); - Directory.Delete(newVersionPath, true); - } + var scriptPath = Path.Combine(dirNameVersion, (p.Name + ".ps1")); + var isScript = File.Exists(scriptPath) ? true : false; - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", newPath, newVersion)); - Directory.Move(tempModuleVersionDir, Path.Combine(newPath, newVersion)); - - } + if (_includeXML) + { + CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); } - } - + if (save) + { + //TODO: SavePackage(); + } + else + { + MoveFilesIntoInstallPath(p, isScript, dirNameVersion, tempInstallPath, newVersion, scriptPath); + } - - cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package {0}", p.Identity.Id)); - // TODO: add finally statement here, consider wrapping in try/catch > should have a debug write, not fail/throw - // Delete the temp directory and all its contents - cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", tempInstallPath)); - if (Directory.Exists(tempInstallPath)) + cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}'", p.Name)); + } + catch { - Directory.Delete(tempInstallPath, true); + cmdletPassedIn.WriteDebug(string.Format("Unable to successfully install package '{0}'")); + } + finally + { + // Delete the temp directory and all its contents + cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", tempInstallPath)); + if (Directory.Exists(tempInstallPath)) + { + Directory.Delete(tempInstallPath, true); + } } - - // TODO: consider some kind of "unable to install messaging" } } + + + // IGNORE FOR INSTALL private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, string dirNameVersion, bool isScript, bool isLocalRepo) { @@ -592,7 +417,7 @@ private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, st if (_includeXML) { // else if save and including XML - var scriptXML = p.Identity.Id + "_InstalledScriptInfo.xml"; + var scriptXML = pkgIdentity.Id + "_InstalledScriptInfo.xml"; cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML))); File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML)); } @@ -600,7 +425,8 @@ private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, st else { var fullTempInstallpath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); // localRepo ? Path.Combine(tempInstallPath, pkgIdentity.Version.ToString()) : Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); - var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) : Path.Combine(_specifiedPath, pkgIdentity.Id); + var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) + : Path.Combine(_specifiedPath, pkgIdentity.Id); if (isLocalRepo && !Directory.Exists(Path.Combine(_specifiedPath, pkgIdentity.Id))) { @@ -626,38 +452,131 @@ private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, st } } - - private void CreateMetadataXMLFile(string dirNameVersion, string repoName, IPackageSearchMetadata pkg, bool isScript) + private void CallProgressBar(PSResourceInfo p) { - // Create PSGetModuleInfo.xml - // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml" - // Modules will have a metadata file: "PSGetModuleInfo.xml" - var metadataXMLPath = isScript ? Path.Combine(dirNameVersion, (pkg.Identity.Id + "_InstalledScriptInfo.xml")) - : Path.Combine(dirNameVersion, "PSGetModuleInfo.xml"); + int i = 1; + //int j = 1; + /**************************** + * START PACKAGE INSTALLATION -- start progress bar + *****************************/ + // Write-Progress -Activity "Search in Progress" - Status "$i% Complete:" - PercentComplete $i + + int activityId = 0; + string activity = ""; + string statusDescription = ""; + + // If the pkg exists in one of the names passed in, then we wont include it as a dependent package + activityId = 0; + activity = string.Format("Installing {0}...", p.Name); + statusDescription = string.Format("{0}% Complete:", i++); + + // j = 1; + /* + if (packageNames.ToList().Contains(p.Identity.Id)) + { + // If the pkg exists in one of the names passed in, then we wont include it as a dependent package + activityId = 0; + activity = string.Format("Installing {0}...", p.Identity.Id); + statusDescription = string.Format("{0}% Complete:", i++); + j = 1; + } + else + { + // Child process + // Installing dependent package + activityId = 1; + activity = string.Format("Installing dependent package {0}...", p.Identity.Id); + statusDescription = string.Format("{0}% Complete:", j); + } + */ + var progressRecord = new ProgressRecord(activityId, activity, statusDescription); + cmdletPassedIn.WriteProgress(progressRecord); + } - // TODO: this may not be needed anymore, depending on what findHelper returns - // TODO: Can use PSResourceInfo obj to find out if isScript - // first we need to put the pkg into a PSResoourceInfo object, then try to write the xml - // Try to convert the IPackageSearchMetadata into a PSResourceInfo object so that we can then easily create the metadata xml - if (!PSResourceInfo.TryConvert( - metadataToParse: pkg, - psGetInfo: out PSResourceInfo pkgInstalled, - repositoryName: repoName, - type: isScript ? ResourceType.Script : ResourceType.Module, - errorMsg: out string errorMsg)) + private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion) + { + var requireLicenseAcceptance = false; + + if (File.Exists(moduleManifest)) { - cmdletPassedIn.WriteError(new ErrorRecord( - new PSInvalidOperationException("Error parsing IPackageSearchMetadata to PSResourceInfo with message: " + errorMsg), - "IPackageSearchMetadataToPSResourceInfoParsingError", - ErrorCategory.InvalidResult, - this)); - return; + 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); + + if (rgx.IsMatch(pattern) && !rgx.IsMatch(patternToSkip1) && !rgx.IsMatch(patternToSkip2)) + { + 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 = "License.txt not Found. License.txt must be provided when user license acceptance is required."; + var ex = new ArgumentException(exMessage); // System.ArgumentException vs PSArgumentException + var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); + + // TODO: update this to write error + cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + } + + // 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 = ShouldContinue(message, title, true, ref yesToAll, ref noToAll); + + if (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 = $"License Acceptance is required for module '{p.Name}'. Please specify '-AcceptLicense' to perform this operation."; + var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException + var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); + + // TODO: update to write error + cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + } + } } + } + private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSResourceInfo pkg, bool isScript) + { + // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml" + // Modules will have the metadata file: "PSGetModuleInfo.xml" + var metadataXMLPath = isScript ? Path.Combine(dirNameVersion, (pkg.Name + "_InstalledScriptInfo.xml")) + : Path.Combine(dirNameVersion, "PSGetModuleInfo.xml"); + + // TODO: now need to add the extra properties like 'installation date' and 'installation path' + // Write all metadata into metadataXMLPath - if (!pkgInstalled.TryWrite(metadataXMLPath, out string error)) + if (!pkg.TryWrite(metadataXMLPath, out string error)) { // TODO: write error } @@ -665,12 +584,12 @@ private void CreateMetadataXMLFile(string dirNameVersion, string repoName, IPack } - private void DeleteExtraneousFiles(string tempInstallPath, IPackageSearchMetadata p, string dirNameVersion) + private void DeleteExtraneousFiles(string tempInstallPath, PSResourceInfo p, string dirNameVersion) { // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module - var nupkgSHAToDelete = Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg.sha512").ToLower()); - var nuspecToDelete = Path.Combine(dirNameVersion, (p.Identity.Id + ".nuspec").ToLower()); - var nupkgToDelete = Path.Combine(dirNameVersion, (p.Identity.ToString() + ".nupkg").ToLower()); + var nupkgSHAToDelete = Path.Combine(dirNameVersion, (p.Name + ".nupkg.sha512").ToLower()); + var nuspecToDelete = Path.Combine(dirNameVersion, (p.Name + ".nuspec").ToLower()); + var nupkgToDelete = Path.Combine(dirNameVersion, (p.Name + ".nupkg").ToLower()); // unforunately have to check if each file exists because it may or may not be there if (File.Exists(nupkgSHAToDelete)) @@ -739,141 +658,81 @@ private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullP - // Finds the exact version that we need to search for or install - // If a user provides a version range we need to make sure version is available - //// search for the pkg in the repository to see if that version is available. - private IPackageSearchMetadata SearchForPkgVersion(string pkgName, PackageMetadataResource pkgMetadataResource, SourceCacheContext srcContext, string repositoryName, VersionRange versionRange, bool prerelease) + private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string scriptPath) { - IPackageSearchMetadata filteredFoundPkgs = null; + // PSModules: + /// ./Modules + /// ./Scripts + /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list + var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) + : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); + + // Creating the proper installation path depending on whether pkg is a module or script + var newPath = isScript ? installPath : Path.Combine(installPath, p.Name); + cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); + + // If script, just move the files over, if module, move the version directory over + var tempModuleVersionDir = isScript ? dirNameVersion //Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()) + : Path.Combine(tempInstallPath, p.Name.ToLower()); + cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); - if (versionRange == null) + if (isScript) { - // ensure that the latst version is returned first (the ordering of versions differ - // TODO: proper error handling - try + // Need to delete old xml files because there can only be 1 per script + var scriptXML = p.Name + "_InstalledScriptInfo.xml"; + cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))); + if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))) { - // searchin the repository - filteredFoundPkgs = (pkgMetadataResource.GetMetadataAsync(pkgName, prerelease, false, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult() - .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease) - .FirstOrDefault()); - } - catch - { - // TODO: catch exception here + cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); + File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); } - if (filteredFoundPkgs == null) - { - cmdletPassedIn.WriteVerbose(String.Format("Could not find package '{0}' in repository '{1}'", pkgName, repositoryName)); + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML))); + File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); - return null; + // Need to delete old script file, if that exists + cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(newPath, p.Name + ".ps1")))); + if (File.Exists(Path.Combine(newPath, p.Name + ".ps1"))) + { + cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); + File.Delete(Path.Combine(newPath, p.Name + ".ps1")); } + + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(newPath, p.Name + ".ps1"))); + File.Move(scriptPath, Path.Combine(newPath, p.Name + ".ps1")); } else { - // Search for packages within a version range - // ensure that the latest version is returned first (the ordering of versions differ - filteredFoundPkgs = (pkgMetadataResource.GetMetadataAsync(pkgName, prerelease, false, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult() - .Where(p => versionRange.Satisfies(p.Identity.Version)) - .OrderByDescending(p => p.Identity.Version, VersionComparer.VersionRelease) - .FirstOrDefault()); - } - - return filteredFoundPkgs; - } - - - - // TODO: FindHelper will return dependencies as well - private List FindDependenciesFromSource(IPackageSearchMetadata pkg, PackageMetadataResource pkgMetadataResource, SourceCacheContext srcContext, bool prerelease, bool reinstall, string _path, string repositoryUrl) - { - // Dependency resolver - // This function is recursively called - // Call the findpackages from source helper (potentially generalize this so it's finding packages from source or cache) - List foundDependencies = new List(); - - // 1) Check the dependencies of this pkg - // 2) For each dependency group, search for the appropriate name and version - // A dependency group includes all the dependencies for a particular framework - foreach (var dependencyGroup in pkg.DependencySets) - { - foreach (var pkgDependency in dependencyGroup.Packages) + // If new path does not exist + if (!Directory.Exists(newPath)) { - IEnumerable dependencies = null; - // a) Check that the appropriate pkg dependencies exist - // Returns all versions from a single package id. - try - { - dependencies = pkgMetadataResource.GetMetadataAsync(pkgDependency.Id, prerelease, true, srcContext, NullLogger.Instance, cancellationToken).GetAwaiter().GetResult(); - } - catch - { } - // b) Check if the appropriate verion range exists (if version exists, then add it to the list to return) - VersionRange versionRange = null; - try - { - versionRange = VersionRange.Parse(pkgDependency.VersionRange.OriginalString); - } - catch - { - var exMessage = String.Format("Error parsing version range"); - var ex = new ArgumentException(exMessage); - var ErrorParsingVersionRange = new ErrorRecord(ex, "ErrorParsingVersionRange", ErrorCategory.ParserError, null); - - cmdletPassedIn.ThrowTerminatingError(ErrorParsingVersionRange); - } - - // If no version/version range is specified the we just return the latest version - IPackageSearchMetadata depPkgToReturn = (versionRange == null ? - dependencies.FirstOrDefault() : - dependencies.Where(v => versionRange.Satisfies(v.Identity.Version)).FirstOrDefault()); - - + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", tempModuleVersionDir, newPath)); + Directory.Move(tempModuleVersionDir, newPath); + } + else + { + tempModuleVersionDir = Path.Combine(tempModuleVersionDir, p.Version.ToString()); + cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); + var newVersionPath = Path.Combine(newPath, newVersion); + cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); - // TODO: figure out paths situation - // If the pkg already exists on the system, don't add it to the list of pkgs that need to be installed - var dirName = save ? cmdletPassedIn.SessionState.Path.GetResolvedPSPathFromPSPath(_path).FirstOrDefault().Path : Path.Combine(psModulesPath, pkgDependency.Id); - var dependencyAlreadyInstalled = false; - // Check to see if the package dir exists in the path - // If save we only check the -path passed in - if (save || _pathsToInstallPkg.Contains(dirName, StringComparer.OrdinalIgnoreCase)) + if (Directory.Exists(newVersionPath)) { - // Then check to see if the package exists in the path - if (Directory.Exists(dirName)) - { - var pkgDirVersion = (Directory.GetDirectories(dirName)).ToList(); - List pkgVersion = new List(); - foreach (var path in pkgDirVersion) - { - pkgVersion.Add(Path.GetFileName(path)); - } - - // These are all the packages already installed - NuGetVersion ver; - var pkgsAlreadyInstalled = pkgVersion.FindAll(p => NuGetVersion.TryParse(p, out ver) && versionRange.Satisfies(ver)); - - if (pkgsAlreadyInstalled.Any() && !reinstall) - { - // Don't add the pkg to the list of pkgs that need to be installed - dependencyAlreadyInstalled = true; - } - } + // Delete the directory path before replacing it with the new module + cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", newVersionPath)); + Directory.Delete(newVersionPath, true); } - if (!dependencyAlreadyInstalled) - { - foundDependencies.Add(depPkgToReturn); - } + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", newPath, newVersion)); + Directory.Move(tempModuleVersionDir, Path.Combine(newPath, newVersion)); - // Recursively search for any dependencies the pkg has - foundDependencies.AddRange(FindDependenciesFromSource(depPkgToReturn, pkgMetadataResource, srcContext, prerelease, reinstall, _path, repositoryUrl)); } } - return foundDependencies; } + } From cae3d5ef7f4b0781b165f18b338be93815fd4d17 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Wed, 23 Jun 2021 11:52:01 -0700 Subject: [PATCH 09/62] Update utils class --- src/code/Utils.cs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 600d7eaf2..fbdde0123 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -101,7 +101,12 @@ public static bool TryParseVersionOrVersionRange( { versionRange = null; - if (version == null) { return false; } + if (version == null) { + versionRange = new VersionRange( + minVersion: NuGetVersion.Parse("0")); + + return true; + } if (version.Trim().Equals("*")) @@ -276,23 +281,6 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string sco return installationPaths; } - - /// - /// Converts an ArrayList of object types to a string array. - /// - public static string[] GetStringArray(ArrayList list) - { - if (list == null) { return null; } - - var strArray = new string[list.Count]; - for (int i=0; i < list.Count; i++) - { - strArray[i] = list[i] as string; - } - - return strArray; - } - #endregion } } From 8d2f910a8f848308094b78df0a3fcf039683a84d Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 29 Jun 2021 15:36:37 -0700 Subject: [PATCH 10/62] incorporate bug fixes --- src/code/InstallHelper.cs | 118 ++++++++++++++++++++++------------ src/code/InstallPSResource.cs | 2 +- src/code/PSResourceInfo.cs | 23 +++++-- src/code/Utils.cs | 10 +-- 4 files changed, 100 insertions(+), 53 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 265120118..e49a190bc 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -82,7 +82,7 @@ public void ProcessInstallParams( { cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; Scope: '{4}'; " + "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", - string.Join(",", names), versionRange.OriginalString, prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, + string.Join(",", names), (_versionRange != null ? _versionRange.OriginalString : string.Empty), prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, scope != null ? scope : string.Empty, acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); _versionRange = versionRange; @@ -110,7 +110,7 @@ public void ProcessInstallParams( public void ProcessRepositories(string[] packageNames, string[] repository, bool trustRepository, PSCredential credential) { var listOfRepositories = RepositorySettings.Read(repository, out string[] _); - var packagesToInstall = packageNames.ToList(); + List packagesToInstall = packageNames.ToList(); var yesToAll = false; var noToAll = false; var repositoryIsNotTrusted = "Untrusted repository"; @@ -118,6 +118,9 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool foreach (var repo in listOfRepositories) { + // If no more packages to install, then return + if (!packagesToInstall.Any()) return; + var sourceTrusted = false; string repoName = repo.Name; cmdletPassedIn.WriteDebug(string.Format("Attempting to search for packages in '{0}'", repoName)); @@ -150,27 +153,28 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var cancellationToken = new CancellationToken(); var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); // Finds parent packages and dependencies - IEnumerable pkgsToInstall = findHelper.FindByResourceName( + IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( name: packageNames, type: ResourceType.None, - version: _versionRange.OriginalString, + version: _versionRange != null ? _versionRange.OriginalString : null, prerelease: _prerelease, tag: null, repository: new string[] { repoName }, credential: credential, includeDependencies: true); - var test = pkgsToInstall.FirstOrDefault(); + //var test = pkgsFromRepoToInstall.FirstOrDefault(); // Deduplicate any packages - pkgsToInstall.GroupBy( + pkgsFromRepoToInstall.GroupBy( m => new { m.Name, m.Version }).Select( group => group.First()).ToList(); - if (!pkgsToInstall.Any()) + if (!pkgsFromRepoToInstall.Any()) { cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); - return; + // Check in the next repository + continue; } // 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) @@ -178,12 +182,20 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool if (!_reinstall) { // Removes all of the names that are already installed from the list of names to search for - pkgsToInstall = FilterByInstalledPkgs(pkgsToInstall); + pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall); } - if (!pkgsToInstall.Any()) return; + if (!pkgsFromRepoToInstall.Any()) continue; + + List pkgsInstalled = InstallPackage(pkgsFromRepoToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); - InstallPackage(pkgsToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); + foreach (string name in pkgsInstalled) + { + if (packagesToInstall.Contains(name)) + { + packagesToInstall.Remove(name); + } + } } } } @@ -216,8 +228,9 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) + private List InstallPackage(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) { + List pkgsSuccessfullyInstalled = new List(); foreach (PSResourceInfo p in pkgsToInstall) { var tempInstallPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); @@ -241,7 +254,13 @@ private void InstallPackage(IEnumerable pkgsToInstall, string re } // Create PackageIdentity in order to download - if (!NuGetVersion.TryParse(p.Version.ToString(), out NuGetVersion pkgVersion)) + string createFullVersion = p.Version.ToString(); + if (p.IsPrerelease) + { + createFullVersion = p.Version.ToString() + "-" + p.PrereleaseLabel; + } + + if (!NuGetVersion.TryParse(createFullVersion, out NuGetVersion pkgVersion)) { cmdletPassedIn.WriteDebug("Error parsing version into a NuGetVersion"); } @@ -344,20 +363,27 @@ private void InstallPackage(IEnumerable pkgsToInstall, string re cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); // Prompt if module requires license acceptance (need to read info license acceptance info from the module manifest) - var newVersion = p.Version.ToString(); // does system.version include prerelease newVersion = p.Identity.Version.ToString().Substring(0, p.Identity.Version.ToString().IndexOf('-')); - var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id, newVersion); - var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); - - // Accept License verification - if (!save) + // pkgIdentity.Version.Version gets the version without metadata or release labels. + string newVersion; + if (pkgIdentity.Version.IsPrerelease) { - CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); + newVersion = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-')); + } + else { + newVersion = pkgIdentity.Version.ToNormalizedString(); } - string dirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), p.Version.ToString()); // p.Identity.Version.ToNormalizedString().ToLower()); + var versionWithoutPrereleaseTag = pkgIdentity.Version.Version.ToString(); + var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); + var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); + + // Accept License verification + if (!save) CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); + + string dirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); // Delete the extra nupkg related files that are not needed and not part of the module/script - DeleteExtraneousFiles(tempInstallPath, p, dirNameVersion); + DeleteExtraneousFiles(tempInstallPath, pkgIdentity, dirNameVersion); if (!Directory.Exists(dirNameVersion)) { @@ -368,10 +394,7 @@ private void InstallPackage(IEnumerable pkgsToInstall, string re var scriptPath = Path.Combine(dirNameVersion, (p.Name + ".ps1")); var isScript = File.Exists(scriptPath) ? true : false; - if (_includeXML) - { - CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); - } + if (_includeXML) CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); if (save) { @@ -379,15 +402,16 @@ private void InstallPackage(IEnumerable pkgsToInstall, string re } else { - MoveFilesIntoInstallPath(p, isScript, dirNameVersion, tempInstallPath, newVersion, scriptPath); + MoveFilesIntoInstallPath(p, isScript, dirNameVersion, tempInstallPath, newVersion, versionWithoutPrereleaseTag, scriptPath); } cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}'", p.Name)); + pkgsSuccessfullyInstalled.Add(p.Name); } - catch + catch (Exception e) { - cmdletPassedIn.WriteDebug(string.Format("Unable to successfully install package '{0}'")); + cmdletPassedIn.WriteDebug(string.Format("Unable to successfully install package '{0}': '{1}'", p.Name, e.Message)); } finally { @@ -399,6 +423,8 @@ private void InstallPackage(IEnumerable pkgsToInstall, string re } } } + + return pkgsSuccessfullyInstalled; } @@ -578,18 +604,23 @@ private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSRes // Write all metadata into metadataXMLPath if (!pkg.TryWrite(metadataXMLPath, out string error)) { - // TODO: write error + var message = string.Format("Error parsing metadata into XML: '{0}'", error); + var ex = new ArgumentException(message); + var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null); + WriteError(ErrorParsingMetadata); } } - private void DeleteExtraneousFiles(string tempInstallPath, PSResourceInfo p, string dirNameVersion) + private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgIdentity, string dirNameVersion) { + /// test this!!!!!!! // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module - var nupkgSHAToDelete = Path.Combine(dirNameVersion, (p.Name + ".nupkg.sha512").ToLower()); - var nuspecToDelete = Path.Combine(dirNameVersion, (p.Name + ".nuspec").ToLower()); - var nupkgToDelete = Path.Combine(dirNameVersion, (p.Name + ".nupkg").ToLower()); + var pkgIdString = pkgIdentity.ToString(); + var nupkgSHAToDelete = Path.Combine(dirNameVersion, (pkgIdString + ".nupkg.sha512").ToLower()); + var nuspecToDelete = Path.Combine(dirNameVersion, (pkgIdentity.Id + ".nuspec").ToLower()); + var nupkgToDelete = Path.Combine(dirNameVersion, (pkgIdString + ".nupkg").ToLower()); // unforunately have to check if each file exists because it may or may not be there if (File.Exists(nupkgSHAToDelete)) @@ -658,7 +689,7 @@ private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullP - private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string scriptPath) + private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string versionWithoutPrereleaseTag, string scriptPath) { // PSModules: /// ./Modules @@ -668,12 +699,13 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); // Creating the proper installation path depending on whether pkg is a module or script - var newPath = isScript ? installPath : Path.Combine(installPath, p.Name); + var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); + var newPath = isScript ? installPath : Path.Combine(installPath, p.Name, newVersion); cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); // If script, just move the files over, if module, move the version directory over var tempModuleVersionDir = isScript ? dirNameVersion //Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()) - : Path.Combine(tempInstallPath, p.Name.ToLower()); + : Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); if (isScript) @@ -704,14 +736,16 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di else { // If new path does not exist - if (!Directory.Exists(newPath)) + if (!Directory.Exists(newPathParent)) { - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", tempModuleVersionDir, newPath)); + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, newPath)); + Directory.CreateDirectory(newPathParent); Directory.Move(tempModuleVersionDir, newPath); } else { - tempModuleVersionDir = Path.Combine(tempModuleVersionDir, p.Version.ToString()); + tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); + var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); var newVersionPath = Path.Combine(newPath, newVersion); @@ -725,8 +759,8 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di Directory.Delete(newVersionPath, true); } - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}", newPath, newVersion)); - Directory.Move(tempModuleVersionDir, Path.Combine(newPath, newVersion)); + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); + Directory.Move(tempModuleVersionDir, finalModuleVersionDir); } } diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index c5f525ddb..2d4f89b46 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -255,7 +255,7 @@ protected override void ProcessRecord() break; default: - // TODO: throw some kind of terminating error + // TODO: throw some kind of terminating error (unrecognized parameter set) // TODO: change to debug assert WriteDebug("Invalid parameter set"); break; diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 286627d73..a6f96c341 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -409,7 +409,8 @@ public static bool TryConvert( Author = ParseMetadataAuthor(metadataToParse), Dependencies = ParseMetadataDependencies(metadataToParse), Description = ParseMetadataDescription(metadataToParse), - IconUri = ParseMetadataIconUri(metadataToParse), + IconUri = ParseMetadataIconUri(metadataToParse), + IsPrerelease = ParseMetadataIsPrerelease(metadataToParse), LicenseUri = ParseMetadataLicenseUri(metadataToParse), Name = ParseMetadataName(metadataToParse), PrereleaseLabel = ParsePrerelease(metadataToParse), @@ -665,6 +666,11 @@ private static string ParseMetadataDescription(IPackageSearchMetadata pkg) private static Uri ParseMetadataIconUri(IPackageSearchMetadata pkg) { return pkg.IconUrl; + } + + private static bool ParseMetadataIsPrerelease(IPackageSearchMetadata pkg) + { + return pkg.Identity.Version.IsPrerelease; } private static Uri ParseMetadataLicenseUri(IPackageSearchMetadata pkg) @@ -771,11 +777,16 @@ private static Version ParseMetadataVersion(IPackageSearchMetadata pkg) private PSObject ConvertToCustomObject() { var additionalMetadata = new PSObject(); - foreach (var item in AdditionalMetadata) - { - additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); - } + // Need to add a null check here due to null ref exception getting thrown + if (AdditionalMetadata != null) + { + foreach (var item in AdditionalMetadata) + { + additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); + } + + } var psObject = new PSObject(); psObject.Properties.Add(new PSNoteProperty(nameof(Name), Name)); psObject.Properties.Add(new PSNoteProperty(nameof(Version), ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel))); @@ -791,7 +802,7 @@ private PSObject ConvertToCustomObject() psObject.Properties.Add(new PSNoteProperty(nameof(ProjectUri), ProjectUri)); psObject.Properties.Add(new PSNoteProperty(nameof(IconUri), IconUri)); psObject.Properties.Add(new PSNoteProperty(nameof(Tags), Tags)); - psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes.ConvertToHashtable())); + psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes != null ? Includes.ConvertToHashtable() : null)); psObject.Properties.Add(new PSNoteProperty(nameof(PowerShellGetFormatVersion), PowerShellGetFormatVersion)); psObject.Properties.Add(new PSNoteProperty(nameof(ReleaseNotes), ReleaseNotes)); psObject.Properties.Add(new PSNoteProperty(nameof(Dependencies), Dependencies)); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index fbdde0123..d530849fd 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -101,12 +101,14 @@ public static bool TryParseVersionOrVersionRange( { versionRange = null; + /* if (version == null) { versionRange = new VersionRange( - minVersion: NuGetVersion.Parse("0")); + minVersion: NuGetVersion.Parse("0"), + originalString: "No version passed in."); return true; - } + }*/ if (version.Trim().Equals("*")) @@ -124,7 +126,7 @@ public static bool TryParseVersionOrVersionRange( maxVersion: nugetVersion, includeMaxVersion: true, floatRange: null, - originalString: null); + originalString: version); return true; } @@ -260,7 +262,7 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string sco installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); } // If user explicitly specifies AllUsers - if (scope.Equals("AllUsers")) + else if (scope.Equals("AllUsers")) { installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Modules")); installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Scripts")); From 67b2c5f116702be4586e9434ed4aef51bb48351e Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 1 Jul 2021 12:55:54 -0700 Subject: [PATCH 11/62] Add install bug fixes and tests --- src/code/InstallHelper.cs | 47 ++++++---- src/code/PSResourceInfo.cs | 2 +- src/code/Utils.cs | 112 ++++++++++++++++++++++- test/InstallPSResource.Tests.ps1 | 147 +++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 test/InstallPSResource.Tests.ps1 diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index e49a190bc..5ef5f4d80 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -330,7 +330,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s downloadContext: new PackageDownloadContext(cacheContext), globalPackagesFolder: tempInstallPath, logger: NullLogger.Instance, - CancellationToken.None).GetAwaiter().GetResult(); + token: CancellationToken.None).GetAwaiter().GetResult(); } catch (Exception e) { @@ -364,37 +364,48 @@ private List InstallPackage(IEnumerable pkgsToInstall, s // Prompt if module requires license acceptance (need to read info license acceptance info from the module manifest) // pkgIdentity.Version.Version gets the version without metadata or release labels. - string newVersion; + + string newVersion = pkgIdentity.Version.ToNormalizedString(); + string modulePath; + string version3digitNoPrerelease = newVersion; if (pkgIdentity.Version.IsPrerelease) { - newVersion = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-')); + modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); + version3digitNoPrerelease = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-')); + } else { - newVersion = pkgIdentity.Version.ToNormalizedString(); + modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); } - - var versionWithoutPrereleaseTag = pkgIdentity.Version.Version.ToString(); - var modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); + + var version4digitNoPrerelease = pkgIdentity.Version.Version.ToString(); var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); + + var parsedMetadataHashtable = Utils.ParseModuleManifest(moduleManifest, this); + + string moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; + + + // Accept License verification if (!save) CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); - string dirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); + string tempDirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); // Delete the extra nupkg related files that are not needed and not part of the module/script - DeleteExtraneousFiles(tempInstallPath, pkgIdentity, dirNameVersion); + DeleteExtraneousFiles(tempInstallPath, pkgIdentity, tempDirNameVersion); - if (!Directory.Exists(dirNameVersion)) + if (!Directory.Exists(tempDirNameVersion)) { - cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", dirNameVersion)); - Directory.CreateDirectory(dirNameVersion); + cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", tempDirNameVersion)); + Directory.CreateDirectory(tempDirNameVersion); } - var scriptPath = Path.Combine(dirNameVersion, (p.Name + ".ps1")); + var scriptPath = Path.Combine(tempDirNameVersion, (p.Name + ".ps1")); var isScript = File.Exists(scriptPath) ? true : false; - if (_includeXML) CreateMetadataXMLFile(dirNameVersion, repoName, p, isScript); + if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, repoName, p, isScript); if (save) { @@ -402,7 +413,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } else { - MoveFilesIntoInstallPath(p, isScript, dirNameVersion, tempInstallPath, newVersion, versionWithoutPrereleaseTag, scriptPath); + MoveFilesIntoInstallPath(p, isScript, tempDirNameVersion, tempInstallPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); } @@ -428,8 +439,6 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } - - // IGNORE FOR INSTALL private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, string dirNameVersion, bool isScript, bool isLocalRepo) { @@ -689,7 +698,7 @@ private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullP - private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string versionWithoutPrereleaseTag, string scriptPath) + private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string moduleManifestVersion, string nupkgVersion, string versionWithoutPrereleaseTag, string scriptPath) { // PSModules: /// ./Modules @@ -700,7 +709,7 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di // Creating the proper installation path depending on whether pkg is a module or script var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); - var newPath = isScript ? installPath : Path.Combine(installPath, p.Name, newVersion); + var newPath = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); // If script, just move the files over, if module, move the version directory over diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 62188b381..0e4025edc 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -805,7 +805,7 @@ private PSObject ConvertToCustomObject() psObject.Properties.Add(new PSNoteProperty(nameof(ProjectUri), ProjectUri)); psObject.Properties.Add(new PSNoteProperty(nameof(IconUri), IconUri)); psObject.Properties.Add(new PSNoteProperty(nameof(Tags), Tags)); - psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes.ConvertToHashtable())); + psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes != null ? Includes.ConvertToHashtable() : null)); psObject.Properties.Add(new PSNoteProperty(nameof(PowerShellGetFormatVersion), PowerShellGetFormatVersion ?? string.Empty)); psObject.Properties.Add(new PSNoteProperty(nameof(ReleaseNotes), ReleaseNotes ?? string.Empty)); psObject.Properties.Add(new PSNoteProperty(nameof(Dependencies), Dependencies)); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 0b4843d8f..84062688e 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { internal static class Utils { - public static void WriteVerboseOnCmdlet( + public static void WriteVerboseOnCmdlet( PSCmdlet cmdlet, string message) { @@ -135,7 +135,7 @@ public static bool TryParseVersionOrVersionRange( maxVersion: nugetVersion, includeMaxVersion: true, floatRange: null, - originalString: null); + originalString: version); return true; } @@ -235,6 +235,114 @@ public static List GetAllResourcePaths(PSCmdlet psCmdlet) return pathsToSearch; } + // Find all potential installation paths given a scope + public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string scope) + { + List installationPaths = new List(); + var PSVersion6 = new Version(6, 0); + var isCorePS = psCmdlet.Host.Version >= PSVersion6; + string myDocumentsPath; + string programFilesPath; + scope = String.IsNullOrEmpty(scope) ? string.Empty : scope; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string powerShellType = isCorePS ? "PowerShell" : "WindowsPowerShell"; + + myDocumentsPath = Path.Combine(Environment.GetFolderPath(SpecialFolder.MyDocuments), powerShellType); + programFilesPath = Path.Combine(Environment.GetFolderPath(SpecialFolder.ProgramFiles), powerShellType); + } + else + { + // paths are the same for both Linux and MacOS + myDocumentsPath = System.IO.Path.Combine(Environment.GetFolderPath(SpecialFolder.LocalApplicationData), "Powershell"); + programFilesPath = System.IO.Path.Combine("usr", "local", "share", "Powershell"); + } + + + // If no explicit specification, will return PSModulePath, and then CurrentUser paths + // Installation will search for a /Modules or /Scripts directory + // If they are not available within one of the paths in PSModulePath, the CurrentUser path will be used. + if (string.IsNullOrEmpty(scope)) + { + string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); + installationPaths = psModulePath.Split(';').ToList(); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); + } + // If user explicitly specifies AllUsers + if (scope.Equals("AllUsers")) + { + installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Scripts")); + } + // If user explicitly specifies CurrentUser + else if (scope.Equals("CurrentUser")) + { + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Modules")); + installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); + } + else + { + psCmdlet.WriteDebug(string.Format("Invalid scope provided: '{0}'", scope)); + } + + installationPaths = installationPaths.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList(); + installationPaths.ForEach(dir => psCmdlet.WriteDebug(string.Format("All paths to search: '{0}'", dir))); + + return installationPaths; + } + + private static string GetResourceNameFromPath(string path) + { + // Resource paths may end in a directory or script file name. + // Directory name is the same as the resource name. + // Script file name is the resource name without the file extension. + // ./Modules/Microsoft.PowerShell.Test-Module : Microsoft.PowerShell.Test-Module + // ./Scripts/Microsoft.PowerShell.Test-Script.ps1 : Microsoft.PowerShell.Test-Script + var resourceName = Path.GetFileName(path); + return Path.GetExtension(resourceName).Equals(".ps1", StringComparison.OrdinalIgnoreCase) + ? Path.GetFileNameWithoutExtension(resourceName) : resourceName; + } + + public static Hashtable ParseModuleManifest(string moduleFileInfo, PSCmdlet cmdletPassedIn) + { + Hashtable parsedMetadataHash = new Hashtable(); + // A script will already have the metadata parsed into the parsedMetadatahash, + // a module will still need the module manifest to be parsed. + if (moduleFileInfo.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) + { + // Parse the module manifest + System.Management.Automation.Language.Token[] tokens; + ParseError[] errors; + var ast = Parser.ParseFile(moduleFileInfo, out tokens, out errors); + + if (errors.Length > 0) + { + var message = String.Format("Could not parse '{0}' as a PowerShell data file.", moduleFileInfo); + var ex = new ArgumentException(message); + var psdataParseError = new ErrorRecord(ex, "psdataParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psdataParseError); + } + else + { + var data = ast.Find(a => a is HashtableAst, false); + if (data != null) + { + parsedMetadataHash = (Hashtable)data.SafeGetValue(); + } + else + { + var message = String.Format("Could not parse as PowerShell data file-- no hashtable root for file '{0}'", moduleFileInfo); + var ex = new ArgumentException(message); + var psdataParseError = new ErrorRecord(ex, "psdataParseError", ErrorCategory.ParserError, null); + cmdletPassedIn.WriteError(psdataParseError); + } + } + } + + return parsedMetadataHash; + } #endregion } } diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 new file mode 100644 index 000000000..756419c26 --- /dev/null +++ b/test/InstallPSResource.Tests.ps1 @@ -0,0 +1,147 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe 'Test Install-PSResource for Module' { + + + BeforeAll{ + $TestGalleryName = Get-PoshTestGalleryName + $PSGalleryName = Get-PSGalleryName + $NuGetGalleryName = Get-NuGetGalleryName + Get-NewPSResourceRepositoryFile + Register-LocalRepos + Get-PSResourceRepository + } + + AfterEach { + Uninstall-PSResource "TestModule" + } + + AfterAll { + Get-RevertPSResourceRepositoryFile + } + + It "Install specific module resource by name" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + } + + It "Install specific script resource by name" { + Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName + $pkg = Get-InstalledPSResource "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + } + + It "Install multiple resources by name" { + $pkgNames = @("TestModule","TestModule99") + Install-PSResource -Name $pkgNames -Repository $TestGalleryName + $pkg = Get-Module $pkgNames -ListAvailable + $pkg.Name | Should -Be $pkgNames + } + + It "Should not install resource given nonexistant name" { + Install-PSResource -Name NonExistantModule -Repository $TestGalleryName + $pkg = Get-Module "NonExistantModule" -ListAvailable + $pkg.Name | Should -BeNullOrEmpty + } + + # Do some version testing, but Find-PSResource should be doing thorough testing + It "Should install resource given name and exact version" { + Install-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.2.0" + } + + + It "Should install resource given name and exact version with bracket syntax" { + Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.2.0" + } + + It "Should install resource given name and exact range inclusive [1.0.0, 1.1.1]" { + Install-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.1.1" + } + + It "Should install resource given name and exact range exclusive (1.0.0, 1.1.1)" { + Install-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.1" + } + + It "Should not install resource with incorrectly formatted version such as " -TestCases @( + @{Version='(1.2.0.0)'; Description="exclusive version (2.10.0.0)"}, + @{Version='[1-2-0-0]'; Description="version formatted with invalid delimiter [1-2-0-0]"} + ) { + param($Version, $Description) + + Install-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName + $res = Get-Module "TestModule" -ListAvailable + $res | Should -BeNullOrEmpty + } + + It "Install resource when given Name, Version '*', should install the latest version" { + $pkg = Install-PSResource -Name "TestModule" -Version "*" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + } + + It "Install resource with latest (including prerelease) version given Prerelease parameter" { + # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version + $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName + $pkg = Get-Module "PSGetTestModule" -ListAvailable + $pkg.Version | Should -Be "2.0.2" + $pkg.PrivateData.PSData.Prerelease | Should -Be "-alpha1" + } + + It "Install resource via InputObject by piping from Find-PSresource" { + Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + } + +<# + It "Install resource from local repository given Repository parameter" { + $publishModuleName = "TestFindModule" + $repoName = "psgettestlocal" + Get-ModuleResourcePublishedToLocalRepoTestDrive $publishModuleName $repoName + + Install-PSResource -Name $publishModuleName -Repository $repoName + $pkg = Get-Module $publishModuleName -ListAvailable + $pkg | Should -Not -BeNullOrEmpty + $pkg.Name | Should -Be $publishModuleName + #$pkg.Repository | Should -Be $repoName + } + + + + + It "Install resource given repository parameter, where resource exists in multiple local repos" { + $moduleName = "test_local_mod" + $repoHigherPriorityRanking = "psgettestlocal" + $repoLowerPriorityRanking = "psgettestlocal2" + + Get-ModuleResourcePublishedToLocalRepoTestDrive $moduleName $repoHigherPriorityRanking + Get-ModuleResourcePublishedToLocalRepoTestDrive $moduleName $repoLowerPriorityRanking + + $res = Find-PSResource -Name $moduleName + $res.Repository | Should -Be $repoHigherPriorityRanking + + $resNonDefault = Find-PSResource -Name $moduleName -Repository $repoLowerPriorityRanking + $resNonDefault.Repository | Should -Be $repoLowerPriorityRanking + } + #> +} From 6302b2d1351e4bcd0db83f8267e4032ac5133a6b Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 1 Jul 2021 15:33:35 -0700 Subject: [PATCH 12/62] Update project to C# 7.2 --- src/code/PowerShellGet.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/code/PowerShellGet.csproj b/src/code/PowerShellGet.csproj index fa274237e..e8e3b26ca 100644 --- a/src/code/PowerShellGet.csproj +++ b/src/code/PowerShellGet.csproj @@ -9,6 +9,7 @@ 3.0.0 3.0.0 netstandard2.0 + 7.2 From e44b9b54593448eb2468a70e5d2b14dbf7d10099 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 1 Jul 2021 15:33:52 -0700 Subject: [PATCH 13/62] Add scope tests --- test/InstallPSResource.Tests.ps1 | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 756419c26..ba8c35dc2 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -30,12 +30,14 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "1.3.0" } + <# It "Install specific script resource by name" { Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "TestModule" -ListAvailable + $pkg = Get-InstalledPSResource "TestModule" $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } +#> It "Install multiple resources by name" { $pkgNames = @("TestModule","TestModule99") @@ -113,6 +115,32 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "1.3.0" } + # Windows only + It "Install resource under CurrentUser scope" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Path.Contains("Documents") | Should -Be $true + + } + + # Windows only + It "Install resource under AllUsers scope" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Path.Contains("Program Files") | Should -Be $true + } + + # Windows only + It "Install resource under no specified scope" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Path.Contains("Documents") | Should -Be $true + } + + <# It "Install resource from local repository given Repository parameter" { $publishModuleName = "TestFindModule" From 0a1edf4a091b6e1ddb4a4ed8199bfb27312b17d7 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 2 Jul 2021 12:10:53 -0700 Subject: [PATCH 14/62] create new src directory --- src/code/InstallHelper.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 5ef5f4d80..a1b4062d1 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -209,16 +209,22 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable pkgsAlreadyInstalled = getHelper.ProcessGetParams(pkgNames.ToArray(), _versionRange, _pathsToInstallPkg); + List pathsToSearch = new List(); + foreach (var path in _pathsToInstallPkg) + { + pathsToSearch.AddRange(Directory.GetDirectories(path)); + } + + IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, pathsToSearch); // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names if (pkgsAlreadyInstalled.Any()) { foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) { - this.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)); + 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)); // remove this pkg from the list of pkg names install packagesToInstall.ToList().Remove(pkg); From 0a1bbc28dd397ca77aa8ec1bf8a0f601c6005cbf Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 7 Jul 2021 12:25:34 -0400 Subject: [PATCH 15/62] made changes to paths created in InstallHelper and use Scope as string --- src/PowerShellGet.psd1 | 6 +- src/code/InstallHelper.cs | 105 +++++++++------ src/code/PSResourceInfo.cs | 7 + src/code/UpdatePSResource.cs | 246 +++++++++++++++++++++++++++++++++++ src/code/Utils.cs | 54 +++++++- 5 files changed, 370 insertions(+), 48 deletions(-) create mode 100644 src/code/UpdatePSResource.cs diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 0ce94ad9a..2ab9abc61 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -14,14 +14,14 @@ 'Find-PSResource', 'Get-InstalledPSResource', 'Get-PSResourceRepository', - # 'Install-PSResource', + 'Install-PSResource', 'Register-PSResourceRepository', # 'Save-PSResource', 'Set-PSResourceRepository', 'Publish-PSResource', 'Uninstall-PSResource', - 'Unregister-PSResourceRepository') - # 'Update-PSResource') + 'Unregister-PSResourceRepository', + 'Update-PSResource') VariablesToExport = 'PSGetPath' AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index a1b4062d1..17e5eff7a 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -45,7 +45,7 @@ internal class InstallHelper : PSCmdlet bool _force; bool _trustRepository; bool _noClobber; - PSCredential _credential; + PSCredential _credential; string _specifiedPath; bool _asNupkg; bool _includeXML; @@ -60,28 +60,28 @@ public InstallHelper(bool update, bool save, CancellationToken cancellationToken // TODO: add passthru public void ProcessInstallParams( - string[] names, - VersionRange versionRange, - bool prerelease, - string[] repository, - string scope, - bool acceptLicense, - bool quiet, - bool reinstall, - bool force, - bool trustRepository, - bool noClobber, - PSCredential credential, - string requiredResourceFile, - string requiredResourceJson, - Hashtable requiredResourceHash, - string specifiedPath, - bool asNupkg, + string[] names, + VersionRange versionRange, + bool prerelease, + string[] repository, + string scope, + bool acceptLicense, + bool quiet, + bool reinstall, + bool force, + bool trustRepository, + bool noClobber, + PSCredential credential, + string requiredResourceFile, + string requiredResourceJson, + Hashtable requiredResourceHash, + string specifiedPath, + bool asNupkg, bool includeXML, List pathsToInstallPkg) { cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; Scope: '{4}'; " + - "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", + "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", string.Join(",", names), (_versionRange != null ? _versionRange.OriginalString : string.Empty), prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, scope != null ? scope : string.Empty, acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); @@ -101,9 +101,9 @@ public void ProcessInstallParams( _pathsToInstallPkg = pathsToInstallPkg; IEnumerable pkgsAlreadyInstalled = new List(); - + // Go through the repositories and see which is the first repository to have the pkg version available - ProcessRepositories(names, repository, _trustRepository, _credential); + ProcessRepositories(names, repository, _trustRepository, _credential); } // This method calls iterates through repositories (by priority order) to search for the pkgs to install @@ -124,7 +124,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var sourceTrusted = false; string repoName = repo.Name; cmdletPassedIn.WriteDebug(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. if (repo.Trusted == false && !trustRepository && !_force) @@ -153,14 +153,14 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var cancellationToken = new CancellationToken(); var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); // Finds parent packages and dependencies - IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( + IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( name: packageNames, type: ResourceType.None, - version: _versionRange != null ? _versionRange.OriginalString : null, + version: _versionRange != null ? _versionRange.OriginalString : null, prerelease: _prerelease, tag: null, - repository: new string[] { repoName }, - credential: credential, + repository: new string[] { repoName }, + credential: credential, includeDependencies: true); //var test = pkgsFromRepoToInstall.FirstOrDefault(); @@ -169,7 +169,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool pkgsFromRepoToInstall.GroupBy( m => new { m.Name, m.Version }).Select( group => group.First()).ToList(); - + if (!pkgsFromRepoToInstall.Any()) { cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); @@ -178,7 +178,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool } // 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 is specified, we will skip this check + // If reinstall is specified, we will skip this check if (!_reinstall) { // Removes all of the names that are already installed from the list of names to search for @@ -216,7 +216,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, pathsToSearch); // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names @@ -233,7 +233,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable InstallPackage(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) { List pkgsSuccessfullyInstalled = new List(); @@ -246,7 +246,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s 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 + // 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 = dir.Attributes & ~FileAttributes.ReadOnly; @@ -383,7 +383,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s else { modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); } - + var version4digitNoPrerelease = pkgIdentity.Version.Version.ToString(); var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); @@ -396,7 +396,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s // Accept License verification if (!save) CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); - + string tempDirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); // Delete the extra nupkg related files that are not needed and not part of the module/script @@ -419,6 +419,9 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } else { + cmdletPassedIn.WriteDebug("ANAM- temp installPath passed in to MoveFilesIntoInstallPath() exists?: " + Directory.Exists(tempInstallPath) + " and is: " + tempInstallPath); + cmdletPassedIn.WriteDebug("ANAM- temp dirNameVersion passed in to MoveFilesIntoInstallPath() exists?: " + Directory.Exists(tempDirNameVersion) + " and is: " + tempDirNameVersion); + MoveFilesIntoInstallPath(p, isScript, tempDirNameVersion, tempInstallPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); } @@ -466,7 +469,7 @@ private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, st else { var fullTempInstallpath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); // localRepo ? Path.Combine(tempInstallPath, pkgIdentity.Version.ToString()) : Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); - var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) + var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) : Path.Combine(_specifiedPath, pkgIdentity.Id); if (isLocalRepo && !Directory.Exists(Path.Combine(_specifiedPath, pkgIdentity.Id))) @@ -613,7 +616,7 @@ private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSRes var metadataXMLPath = isScript ? Path.Combine(dirNameVersion, (pkg.Name + "_InstalledScriptInfo.xml")) : Path.Combine(dirNameVersion, "PSGetModuleInfo.xml"); - + // TODO: now need to add the extra properties like 'installation date' and 'installation path' // Write all metadata into metadataXMLPath @@ -624,9 +627,9 @@ private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSRes var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null); WriteError(ErrorParsingMetadata); } - + } - + private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgIdentity, string dirNameVersion) { @@ -706,13 +709,19 @@ private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullP private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string moduleManifestVersion, string nupkgVersion, string versionWithoutPrereleaseTag, string scriptPath) { - // PSModules: + // MoveFilesIntoInstallPath(p, isScript, tempDirNameVersion, tempInstallPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); + + char[] delimeter = new char[] { ';' }; + cmdletPassedIn.WriteVerbose("paths to install pkg: " + String.Join("\n", _pathsToInstallPkg)); + + // PSModules: /// ./Modules /// ./Scripts /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); + cmdletPassedIn.WriteVerbose(" installPath: " + installPath); // Creating the proper installation path depending on whether pkg is a module or script var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); var newPath = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); @@ -759,11 +768,13 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di } else { - tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); - var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); + // tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); ANAM: this messes up tempModuleVersionDir (src) + // var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); ANAM: this messes up the finalModuleVersionDir (dest) + var finalModuleVersionDir = newPath; cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); - var newVersionPath = Path.Combine(newPath, newVersion); + // var newVersionPath = Path.Combine(newPath, newVersion); ANAM: this messes up the check + var newVersionPath = newPath; cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); @@ -774,6 +785,16 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di Directory.Delete(newVersionPath, true); } + if (!Directory.Exists(tempModuleVersionDir)) + { + cmdletPassedIn.WriteDebug("ANAM: " + tempModuleVersionDir + " does not exist"); + } + + if (!Directory.Exists(finalModuleVersionDir)) + { + cmdletPassedIn.WriteDebug("ANAM: " + finalModuleVersionDir + " does not exist"); + } + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.Move(tempModuleVersionDir, finalModuleVersionDir); @@ -781,7 +802,7 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di } } - + } diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 0e4025edc..3189c6406 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -38,6 +38,13 @@ public enum VersionType MaximumVersion } + public enum ScopeType + { + None, + AllUsers, + CurrentUser + } + #endregion #region VersionInfo diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs new file mode 100644 index 000000000..0cf4635cc --- /dev/null +++ b/src/code/UpdatePSResource.cs @@ -0,0 +1,246 @@ +using System; +using System.Linq; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Dbg = System.Diagnostics.Debug; +using System.Management.Automation; +using System.Threading; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// The Save-PSResource cmdlet combines the Save-Module, Save-Script cmdlets from V2. + /// It saves from a package found from a repository (local or remote) based on the -Name parameter argument. + /// It does not return an object. Other parameters allow the returned results to be further filtered. + /// + + [Cmdlet(VerbsData.Update, + "PSResource", + DefaultParameterSetName = NameParameterSet, + SupportsShouldProcess = true, + HelpUri = "")] + [OutputType(typeof(PSResourceInfo))] + public sealed + class UpdatePSResource : PSCmdlet + { + #region Members + + private const string NameParameterSet = "NameParameterSet"; + private const string InputObjectParameterSet = "InputObjectParameterSet"; + private CancellationTokenSource _source; + private CancellationToken _cancellationToken; + + #endregion + + #region Parameters + + /// + /// Specifies name of a resource or resources to update. + /// Accepts wildcard characters. + /// + [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string[] Name { get; set; } + // create a default string with "*" + + /// + /// Specifies the version the resource is to be updated to. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string Version { get; set; } + + /// + /// When specified, allows updating to a prerelease version. + /// + [Parameter(ParameterSetName = NameParameterSet)] + public SwitchParameter Prerelease { get; set; } + + /// + /// Specifies one or more repository names to update packages from. + /// If not specified, search will include all currently registered repositories in order of highest priority. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string[] Repository { get; set; } + + /// + /// Specifies the scope of the resource to update. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public ScopeType Scope { get; set; } + + /// + /// When specified, supresses being prompted for untrusted sources. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ParameterSetName = InputObjectParameterSet)] + public SwitchParameter TrustRepository { get; set; } + + /// + /// Specifies optional credentials to be used when accessing a private repository. + /// + [Parameter(ParameterSetName = NameParameterSet)] + public PSCredential Credential { get; set; } + + /// + /// Supresses progress information. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ParameterSetName = InputObjectParameterSet)] + public SwitchParameter Quiet { get; set; } + + /// + /// For resources that require a license, AcceptLicense automatically accepts the license agreement during the update. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ParameterSetName = InputObjectParameterSet)] + public SwitchParameter AcceptLicense { get; set; } + + /// + /// When specified, bypasses checks for TrustRepository and AcceptLicense and updates the package. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ParameterSetName = InputObjectParameterSet)] + public SwitchParameter Force { get; set; } + + /// + /// Prevents updating modules that have the same cmdlets as a differently named module already. + /// + [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ParameterSetName = InputObjectParameterSet)] + public SwitchParameter NoClobber { get; set; } + + /// + /// Used to pass in an object via pipeline to update. + /// + [Parameter(ValueFromPipeline = true, ParameterSetName = NameParameterSet)] + public object[] InputObject { get; set; } + + #endregion + + #region Methods + + protected override void BeginProcessing() + { + _source = new CancellationTokenSource(); + _cancellationToken = _source.Token; + } + + protected override void StopProcessing() + { + _source.Cancel(); + } + + protected override void ProcessRecord() + { + Name = Utils.FilterWildcards(Name, out string[] errorMsgs, out bool isContainWildcard); + + foreach (string error in errorMsgs) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException(error), + "ErrorFilteringNamesForUnsupportedWildcards", + ErrorCategory.InvalidArgument, + this)); + } + + if (Name.Length == 1 && String.Equals(Name[0], "*", StringComparison.InvariantCultureIgnoreCase)) + { + WriteVerbose("Name was detected to be (or contain an element equal to): '*', so all packages will be updated"); + } + + // this catches the case where Name wasn't input as null or empty, + // but after filtering out unsupported wildcard names there are no elements left in Name + if (Name.Length == 0) + { + return; + } + + VersionRange versionRange = new VersionRange(); + + // TODO: discuss with Paul + if (Version !=null && !Utils.TryParseVersionOrVersionRange(Version, out versionRange)) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException("Cannot parse Version parameter provided into VersionRange"), + "ErrorParsingVersionParamIntoVersionRange", + ErrorCategory.InvalidArgument, + this)); + versionRange = VersionRange.All; // or should I return here instead? + } + + if (isContainWildcard) + { + // any of the Name entries contains a supported wildcard + // then we need to use GetHelper (Get-InstalledPSResource logic) to find which packages are installed that match + // the wildcard pattern name for each package name with wildcard + + GetHelper getHelper = new GetHelper( + cmdletPassedIn: this); + + // List finalNames = new List(); + // foreach (PSResourceInfo pkg in getHelper.ProcessGetParams( + // name: Name, + // versionRange: versionRange, + // pathsToSearch: Utils.GetAllResourcePaths(this))) + // { + // finalNames.Add(pkg.Name); + // } + Name = getHelper.FilterPkgPaths( + name: Name, + versionRange: versionRange, + pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); + } + WriteVerbose("names after GetHelper.ProcessGetParams: " + String.Join(", ", Name)); + + InstallHelper installHelper = new InstallHelper( + update: true, + save: false, + cancellationToken: _cancellationToken, + cmdletPassedIn: this); + + switch (ParameterSetName) + { + case NameParameterSet: + installHelper.ProcessInstallParams( + names: Name, + versionRange: versionRange, + prerelease: Prerelease, + repository: Repository, + scope: String.Equals(Scope.ToString(), "None", StringComparison.InvariantCultureIgnoreCase) ? "" : Scope.ToString(), + acceptLicense: AcceptLicense, + quiet: Quiet, + reinstall: false, + force: Force, + trustRepository: TrustRepository, + noClobber: NoClobber, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: null, // todo: confirm + asNupkg: false, // todo: confirm + includeXML: true, // todo: confirm! + pathsToInstallPkg: Utils.GetAllInstallationPaths(this, String.Equals(Scope.ToString(), "None", StringComparison.InvariantCultureIgnoreCase) ? "" : Scope.ToString())); + break; + + case InputObjectParameterSet: + // TODO + break; + + default: + // TODO: the case where no name was specified so we update all packages? + Dbg.Assert(false, "Invalid parameter set"); + break; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 84062688e..a868bb409 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { internal static class Utils { - public static void WriteVerboseOnCmdlet( + public static void WriteVerboseOnCmdlet( PSCmdlet cmdlet, string message) { @@ -32,6 +32,54 @@ public static void WriteVerboseOnCmdlet( catch { } } + public static string[] FilterWildcards( + string[] pkgNames, + out string[] errorMsgs, + out bool isContainWildcard) + { + List namesWithSupportedWildcards = new List(); + List errorMsgsList = new List(); + + if (pkgNames == null) + { + isContainWildcard = true; + errorMsgs = errorMsgsList.ToArray(); + return new string[] {"*"}; + } + + isContainWildcard = false; + foreach (string name in pkgNames) + { + if (WildcardPattern.ContainsWildcardCharacters(name)) + { + if (String.Equals(name, "*", StringComparison.InvariantCultureIgnoreCase)) + { + isContainWildcard = true; + errorMsgs = new string[] {}; + return new string[] {"*"}; + } + + if (name.Contains("?") || name.Contains("[")) + { + errorMsgsList.Add(String.Format("-Name with wildcards '?' and '[' are not supported for Find-PSResource so Name entry: {0} will be discarded.", name)); + } + else + { + isContainWildcard = true; + namesWithSupportedWildcards.Add(name); + } + } + else + { + namesWithSupportedWildcards.Add(name); + } + + } + + errorMsgs = errorMsgsList.ToArray(); + return namesWithSupportedWildcards.ToArray(); + } + public static string[] FilterOutWildcardNames( string[] pkgNames, out string[] errorMsgs) @@ -261,7 +309,7 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string sco // If no explicit specification, will return PSModulePath, and then CurrentUser paths - // Installation will search for a /Modules or /Scripts directory + // Installation will search for a /Modules or /Scripts directory // If they are not available within one of the paths in PSModulePath, the CurrentUser path will be used. if (string.IsNullOrEmpty(scope)) { @@ -312,7 +360,7 @@ public static Hashtable ParseModuleManifest(string moduleFileInfo, PSCmdlet cmdl // a module will still need the module manifest to be parsed. if (moduleFileInfo.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase)) { - // Parse the module manifest + // Parse the module manifest System.Management.Automation.Language.Token[] tokens; ParseError[] errors; var ast = Parser.ParseFile(moduleFileInfo, out tokens, out errors); From 2e6081f4072196930566ab6c388db2c8f5e2ef09 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Wed, 7 Jul 2021 10:36:08 -0700 Subject: [PATCH 16/62] Incorporate bug fixes for accept license, scripts, and installation paths --- src/code/InstallHelper.cs | 87 +++++++++++-------- src/code/PSResourceInfo.cs | 172 ++++++++++++++++++++----------------- 2 files changed, 142 insertions(+), 117 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index a1b4062d1..01ffbe7b5 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -153,14 +153,14 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var cancellationToken = new CancellationToken(); var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); // Finds parent packages and dependencies - IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( + IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( name: packageNames, type: ResourceType.None, version: _versionRange != null ? _versionRange.OriginalString : null, prerelease: _prerelease, tag: null, - repository: new string[] { repoName }, - credential: credential, + repository: new string[] { repoName }, + credential: credential, includeDependencies: true); //var test = pkgsFromRepoToInstall.FirstOrDefault(); @@ -385,17 +385,25 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } var version4digitNoPrerelease = pkgIdentity.Version.Version.ToString(); - var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); - + string moduleManifestVersion = string.Empty; + var scriptPath = Path.Combine(modulePath, (p.Name + ".ps1")); + var isScript = File.Exists(scriptPath) ? true : false; - var parsedMetadataHashtable = Utils.ParseModuleManifest(moduleManifest, this); + if (!isScript) + { + var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); - string moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; + var parsedMetadataHashtable = Utils.ParseModuleManifest(moduleManifest, this); + moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; + // Accept License verification + if (!save && !CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion)) + { + continue; + } + } - // Accept License verification - if (!save) CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion); string tempDirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); @@ -408,8 +416,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s Directory.CreateDirectory(tempDirNameVersion); } - var scriptPath = Path.Combine(tempDirNameVersion, (p.Name + ".ps1")); - var isScript = File.Exists(scriptPath) ? true : false; + if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, repoName, p, isScript); @@ -535,9 +542,10 @@ private void CallProgressBar(PSResourceInfo p) cmdletPassedIn.WriteProgress(progressRecord); } - private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion) + private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion) { var requireLicenseAcceptance = false; + var success = true; if (File.Exists(moduleManifest)) { @@ -550,8 +558,9 @@ private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var patternToSkip2 = "\\*\\s*RequireLicenseAcceptance\\s*=\\s*\\$true"; Regex rgx = new Regex(pattern); - - if (rgx.IsMatch(pattern) && !rgx.IsMatch(patternToSkip1) && !rgx.IsMatch(patternToSkip2)) + Regex rgxComment1 = new Regex(patternToSkip1); + Regex rgxComment2 = new Regex(patternToSkip2); + if (rgx.IsMatch(text) && !rgxComment1.IsMatch(text) && !rgxComment2.IsMatch(text)) { requireLicenseAcceptance = true; } @@ -572,8 +581,8 @@ private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var ex = new ArgumentException(exMessage); // System.ArgumentException vs PSArgumentException var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); - // TODO: update this to write error - cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + cmdletPassedIn.WriteError(acceptLicenseError); + success = false; } // Otherwise read LicenseFile @@ -584,9 +593,9 @@ private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var title = "License Acceptance"; var yesToAll = false; var noToAll = false; - var shouldContinueResult = ShouldContinue(message, title, true, ref yesToAll, ref noToAll); + var shouldContinueResult = cmdletPassedIn.ShouldContinue(message, title, true, ref yesToAll, ref noToAll); - if (yesToAll) + if (shouldContinueResult || yesToAll) { _acceptLicense = true; } @@ -599,11 +608,13 @@ private void CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); - // TODO: update to write error - cmdletPassedIn.ThrowTerminatingError(acceptLicenseError); + cmdletPassedIn.WriteError(acceptLicenseError); + success = false; } } } + + return success; } private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSResourceInfo pkg, bool isScript) @@ -715,11 +726,11 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di // Creating the proper installation path depending on whether pkg is a module or script var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); - var newPath = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); - cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", newPath)); + var finalModuleVersionDir = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); // versionWithoutPrereleaseTag + cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", finalModuleVersionDir)); // If script, just move the files over, if module, move the version directory over - var tempModuleVersionDir = isScript ? dirNameVersion //Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToNormalizedString()) + var tempModuleVersionDir = isScript ? dirNameVersion : Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); @@ -738,40 +749,40 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); // Need to delete old script file, if that exists - cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(newPath, p.Name + ".ps1")))); - if (File.Exists(Path.Combine(newPath, p.Name + ".ps1"))) + cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")))); + if (File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))) { cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); - File.Delete(Path.Combine(newPath, p.Name + ".ps1")); + File.Delete(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); } - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(newPath, p.Name + ".ps1"))); - File.Move(scriptPath, Path.Combine(newPath, p.Name + ".ps1")); + cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))); + File.Move(scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); } else { // If new path does not exist if (!Directory.Exists(newPathParent)) { - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, newPath)); + cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.CreateDirectory(newPathParent); - Directory.Move(tempModuleVersionDir, newPath); + Directory.Move(tempModuleVersionDir, finalModuleVersionDir); } else { - tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); - var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); + //tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); + //var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); - var newVersionPath = Path.Combine(newPath, newVersion); - cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); - + //var newVersionPath = Path.Combine(newPath, newVersion); + //cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); - if (Directory.Exists(newVersionPath)) + // At this point if + if (Directory.Exists(finalModuleVersionDir)) { // Delete the directory path before replacing it with the new module - cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", newVersionPath)); - Directory.Delete(newVersionPath, true); + cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", finalModuleVersionDir)); + Directory.Delete(finalModuleVersionDir, true); } cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 0e4025edc..c6af1effd 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -12,7 +12,8 @@ using NuGet.Packaging; using NuGet.Protocol.Core.Types; using NuGet.Versioning; - +using System.Reflection; + namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { #region Enums @@ -525,85 +526,98 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) List dependenciesFound = new List(); if (dependencyInfos == null) { return dependenciesFound.ToArray(); } - + foreach(PSObject dependencyObj in dependencyInfos) - { - if (!(dependencyObj.BaseObject is Hashtable dependencyInfo)) - { - Dbg.Assert(false, "Dependencies BaseObject must be a Hashtable"); - continue; - } - - if (!dependencyInfo.ContainsKey("Name")) - { - Dbg.Assert(false, "Derived dependencies Hashtable must contain a Name key"); - continue; - } - - string dependencyName = (string) dependencyInfo["Name"]; - if (String.IsNullOrEmpty(dependencyName)) - { - Dbg.Assert(false, "Dependency Name must not be null or empty"); - continue; - } - - if (dependencyInfo.ContainsKey("RequiredVersion")) - { - if (!Utils.TryParseVersionOrVersionRange((string) dependencyInfo["RequiredVersion"], out VersionRange dependencyVersion)) - { - dependencyVersion = VersionRange.All; - } - - dependenciesFound.Add(new Dependency(dependencyName, dependencyVersion)); - continue; - } - - if (dependencyInfo.ContainsKey("MinimumVersion") || dependencyInfo.ContainsKey("MaximumVersion")) - { - NuGetVersion minimumVersion = null; - NuGetVersion maximumVersion = null; - bool includeMin = false; - bool includeMax = false; - - if (dependencyInfo.ContainsKey("MinimumVersion") && - !NuGetVersion.TryParse((string) dependencyInfo["MinimumVersion"], out minimumVersion)) - { - VersionRange dependencyAll = VersionRange.All; - dependenciesFound.Add(new Dependency(dependencyName, dependencyAll)); - continue; - } - - if (dependencyInfo.ContainsKey("MaximumVersion") && - !NuGetVersion.TryParse((string) dependencyInfo["MaximumVersion"], out maximumVersion)) - { - VersionRange dependencyAll = VersionRange.All; - dependenciesFound.Add(new Dependency(dependencyName, dependencyAll)); - continue; - } - - if (minimumVersion != null) - { - includeMin = true; - } - - if (maximumVersion != null) - { - includeMax = true; - } - - VersionRange dependencyVersionRange = new VersionRange( - minVersion: minimumVersion, - includeMinVersion: includeMin, - maxVersion: maximumVersion, - includeMaxVersion: includeMax); - - dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRange)); - continue; - } - - // neither Required, Minimum or Maximum Version provided - VersionRange dependencyVersionRangeAll = VersionRange.All; - dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRangeAll)); + { + // can be an array or hashtable + if (dependencyObj.BaseObject is Hashtable dependencyInfo) + { + if (!dependencyInfo.ContainsKey("Name")) + { + Dbg.Assert(false, "Derived dependencies Hashtable must contain a Name key"); + continue; + } + + string dependencyName = (string)dependencyInfo["Name"]; + if (String.IsNullOrEmpty(dependencyName)) + { + Dbg.Assert(false, "Dependency Name must not be null or empty"); + continue; + } + + if (dependencyInfo.ContainsKey("RequiredVersion")) + { + if (!Utils.TryParseVersionOrVersionRange((string)dependencyInfo["RequiredVersion"], out VersionRange dependencyVersion)) + { + dependencyVersion = VersionRange.All; + } + + dependenciesFound.Add(new Dependency(dependencyName, dependencyVersion)); + continue; + } + + if (dependencyInfo.ContainsKey("MinimumVersion") || dependencyInfo.ContainsKey("MaximumVersion")) + { + NuGetVersion minimumVersion = null; + NuGetVersion maximumVersion = null; + bool includeMin = false; + bool includeMax = false; + + if (dependencyInfo.ContainsKey("MinimumVersion") && + !NuGetVersion.TryParse((string)dependencyInfo["MinimumVersion"], out minimumVersion)) + { + VersionRange dependencyAll = VersionRange.All; + dependenciesFound.Add(new Dependency(dependencyName, dependencyAll)); + continue; + } + + if (dependencyInfo.ContainsKey("MaximumVersion") && + !NuGetVersion.TryParse((string)dependencyInfo["MaximumVersion"], out maximumVersion)) + { + VersionRange dependencyAll = VersionRange.All; + dependenciesFound.Add(new Dependency(dependencyName, dependencyAll)); + continue; + } + + if (minimumVersion != null) + { + includeMin = true; + } + + if (maximumVersion != null) + { + includeMax = true; + } + + VersionRange dependencyVersionRange = new VersionRange( + minVersion: minimumVersion, + includeMinVersion: includeMin, + maxVersion: maximumVersion, + includeMaxVersion: includeMax); + + dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRange)); + continue; + } + + // neither Required, Minimum or Maximum Version provided + VersionRange dependencyVersionRangeAll = VersionRange.All; + dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRangeAll)); + } + else if (dependencyObj.Properties["Name"] != null) + { + string name = dependencyObj.Properties["Name"].Value.ToString(); + + string version = string.Empty; + VersionRange versionRange = VersionRange.All; + + if (dependencyObj.Properties["VersionRange"] != null) + { + version = dependencyObj.Properties["VersionRange"].Value.ToString(); + VersionRange.TryParse(version, out versionRange); + } + + dependenciesFound.Add(new Dependency(name, versionRange)); + } } return dependenciesFound.ToArray(); From c2d95d30cfb44aa2fe5e03f627005731163ab297 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Wed, 7 Jul 2021 10:36:55 -0700 Subject: [PATCH 17/62] Add more install tests --- test/InstallPSResource.Tests.ps1 | 133 +++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 6 deletions(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index ba8c35dc2..17f926efa 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -17,6 +17,11 @@ Describe 'Test Install-PSResource for Module' { AfterEach { Uninstall-PSResource "TestModule" + Uninstall-PSResource "TestModule99" + + Uninstall-PSResource "myTestModule" + Uninstall-PSResource "myTestModule2" + uninstall-PSResource "testModuleWithlicense" } AfterAll { @@ -24,20 +29,21 @@ Describe 'Test Install-PSResource for Module' { } It "Install specific module resource by name" { + Get-PSResourceRepository + Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } - <# + It "Install specific script resource by name" { Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "TestModule" - $pkg.Name | Should -Be "TestModule" - $pkg.Version | Should -Be "1.3.0" + $pkg = Get-InstalledPSResource "TestTestScript" + $pkg.Name | Should -Be "TestTestScript" + $pkg.Version | Should -Be "1.3.1.0" } -#> It "Install multiple resources by name" { $pkgNames = @("TestModule","TestModule99") @@ -101,6 +107,17 @@ Describe 'Test Install-PSResource for Module' { } It "Install resource with latest (including prerelease) version given Prerelease parameter" { + # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version + $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName + $pkg = Get-Module "TestModulePrerelease" -ListAvailable + $pkg.Version | Should -Be "0.0.1" + $pkg.PrivateData.PSData.Prerelease | Should -Be "preview" + } + + + + + It "Install a module with a dependency" { # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "PSGetTestModule" -ListAvailable @@ -108,6 +125,7 @@ Describe 'Test Install-PSResource for Module' { $pkg.PrivateData.PSData.Prerelease | Should -Be "-alpha1" } + It "Install resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource $pkg = Get-Module "TestModule" -ListAvailable @@ -129,6 +147,7 @@ Describe 'Test Install-PSResource for Module' { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" + write-host $pkg.Path $pkg.Path.Contains("Program Files") | Should -Be $true } @@ -140,7 +159,109 @@ Describe 'Test Install-PSResource for Module' { $pkg.Path.Contains("Documents") | Should -Be $true } - + + + +<# + It "Should not install resource that is already installed" { + $a = (Get-PSResourceRepository) + write-host $a.Name + write-host $a.Priority + write-host $a.Trusted + + Install-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -WarningVariable WarningVar -warningaction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + } +#> + + + It "Reinstall resource that is already installed with -Reinstall parameter" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Reinstall + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + } + + It "Install resource that requires accept license with -AcceptLicense flag" { + Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName -AcceptLicense + $pkg = Get-InstalledPSResource "testModuleWithlicense" + $pkg.Name | Should -Be "testModuleWithlicense" + $pkg.Version | Should -Be "0.0.1.0" + } + + It "Install resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { + Set-PSResourceRepository PoshTestGallery -Trusted:$false + + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -TrustRepository + + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + + Set-PSResourceRepository PoshTestGallery -Trusted + } + + It "Install resource with cmdlet names from a module already installed (should clobber)" { + Install-PSResource -Name "myTestModule" -Repository $TestGalleryName + $pkg = Get-InstalledPSResource "myTestModule" + $pkg.Name | Should -Be "myTestModule" + $pkg.Version | Should -Be "0.0.3.0" + + Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName + $pkg = Get-InstalledPSResource "myTestModule2" + $pkg.Name | Should -Be "myTestModule2" + $pkg.Version | Should -Be "0.0.1.0" + } + +<# + ############ FAILING + It "Install resource with -NoClobber flag (should not clobber)" { + Install-PSResource -Name "myTestModule" -Repository $TestGalleryName + $pkg = Get-InstalledPSResource "myTestModule" + $pkg.Name | Should -Be "myTestModule" + $pkg.Version | Should -Be "0.0.3.0" + + Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName -NoClobber #-WarningVariable WarningVar -warningaction SilentlyContinue + $WarningVar | Should -Not -BeNullOrEmpty + $pkg = Get-InstalledPSResource "myTestModule2" + $pkg.Name | Should -Be "myTestModule2" + $pkg.Version | Should -Be "0.0.1.0" + } +#> + + ### This needs to be manually tested due to prompt +<# + It "Install resource that requires accept license without -AcceptLicense flag" { + Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName + $pkg = Get-InstalledPSResource "testModuleWithlicense" + $pkg.Name | Should -Be "testModuleWithlicense" + $pkg.Version | Should -Be "0.0.1.0" + } +#> + + ### PoshTestGallery psgettestlocal NuGetGallery PSGallery psgettestlocal2 + ### 10 40 50 50 50 + ### True False True False False + + <# + ### This needs to be manually tested due to prompt + It "Install resource should prompt 'trust repository' if repository is not trusted" { + Set-PSResourceRepository PoshTestGallery -Trusted:$false + + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false + + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + + Set-PSResourceRepository PoshTestGallery -Trusted + } +#> <# It "Install resource from local repository given Repository parameter" { $publishModuleName = "TestFindModule" From db19cb8c9ebdfbca9d8a4e39a4908104d0291a23 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 7 Jul 2021 15:46:59 -0400 Subject: [PATCH 18/62] remove comments used for debugging earlier --- src/code/InstallHelper.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index f19eae294..1d3308ea2 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -426,9 +426,6 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } else { - cmdletPassedIn.WriteDebug("ANAM- temp installPath passed in to MoveFilesIntoInstallPath() exists?: " + Directory.Exists(tempInstallPath) + " and is: " + tempInstallPath); - cmdletPassedIn.WriteDebug("ANAM- temp dirNameVersion passed in to MoveFilesIntoInstallPath() exists?: " + Directory.Exists(tempDirNameVersion) + " and is: " + tempDirNameVersion); - MoveFilesIntoInstallPath(p, isScript, tempDirNameVersion, tempInstallPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); } @@ -794,16 +791,6 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di Directory.Delete(finalModuleVersionDir, true); } - if (!Directory.Exists(tempModuleVersionDir)) - { - cmdletPassedIn.WriteDebug("ANAM: " + tempModuleVersionDir + " does not exist"); - } - - if (!Directory.Exists(finalModuleVersionDir)) - { - cmdletPassedIn.WriteDebug("ANAM: " + finalModuleVersionDir + " does not exist"); - } - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.Move(tempModuleVersionDir, finalModuleVersionDir); From fc7cc7f95fc6a3f7ee6c2e1191f75f3904c913aa Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 8 Jul 2021 01:34:09 -0700 Subject: [PATCH 19/62] Clean up code and tests --- src/code/InstallHelper.cs | 212 +++++++------------------------ test/InstallPSResource.Tests.ps1 | 103 +++------------ 2 files changed, 69 insertions(+), 246 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 01ffbe7b5..049be2d3f 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -3,8 +3,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using static System.Environment; using System.Globalization; using System.IO; using System.Linq; @@ -13,7 +11,6 @@ using System.Text.RegularExpressions; using System.Threading; using MoreLinq.Extensions; -using Newtonsoft.Json; using NuGet.Common; using NuGet.Configuration; using NuGet.Packaging; @@ -49,6 +46,7 @@ internal class InstallHelper : PSCmdlet string _specifiedPath; bool _asNupkg; bool _includeXML; + List _pathsToSearch; public InstallHelper(bool update, bool save, CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) { @@ -58,7 +56,6 @@ public InstallHelper(bool update, bool save, CancellationToken cancellationToken this.cmdletPassedIn = cmdletPassedIn; } - // TODO: add passthru public void ProcessInstallParams( string[] names, VersionRange versionRange, @@ -147,7 +144,6 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool cmdletPassedIn.WriteDebug("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 - // TODO: make sure to write a test for this scenario var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter); var cancellationToken = new CancellationToken(); @@ -163,13 +159,16 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool credential: credential, includeDependencies: true); - //var test = pkgsFromRepoToInstall.FirstOrDefault(); - // Deduplicate any packages - pkgsFromRepoToInstall.GroupBy( + pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy( m => new { m.Name, m.Version }).Select( group => group.First()).ToList(); - + + // Make sure only the latest version of a module gets installed + pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy( + m => new { m.Name }).Select( + group => group.First()).ToList(); + if (!pkgsFromRepoToInstall.Any()) { cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); @@ -178,7 +177,6 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool } // 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 is specified, we will skip this check if (!_reinstall) { // Removes all of the names that are already installed from the list of names to search for @@ -209,15 +207,15 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable(); GetHelper getHelper = new GetHelper(cmdletPassedIn); // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) - List pathsToSearch = new List(); foreach (var path in _pathsToInstallPkg) { - pathsToSearch.AddRange(Directory.GetDirectories(path)); + _pathsToSearch.AddRange(Directory.GetDirectories(path)); } - IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, pathsToSearch); + IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, _pathsToSearch); // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names if (pkgsAlreadyInstalled.Any()) @@ -248,7 +246,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s // 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? + // TODO: are there Linux accommodations we need to consider here? dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly; cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Name)); @@ -286,9 +284,8 @@ private List InstallPackage(IEnumerable pkgsToInstall, s globalPackagesFolder: tempInstallPath, logger: NullLogger.Instance, token: CancellationToken.None).GetAwaiter().GetResult(); - - - if (_asNupkg) // this is Save functinality + + if (_asNupkg) // this is Save functionality { DirectoryInfo nupkgPath = new DirectoryInfo(((System.IO.FileStream)result.PackageStream).Name); File.Copy(nupkgPath.FullName, Path.Combine(tempInstallPath, pkgIdentity.Id + pkgIdentity.Version + ".nupkg")); @@ -372,29 +369,23 @@ private List InstallPackage(IEnumerable pkgsToInstall, s // pkgIdentity.Version.Version gets the version without metadata or release labels. string newVersion = pkgIdentity.Version.ToNormalizedString(); - string modulePath; string version3digitNoPrerelease = newVersion; if (pkgIdentity.Version.IsPrerelease) { - modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); version3digitNoPrerelease = pkgIdentity.Version.ToNormalizedString().Substring(0, pkgIdentity.Version.ToNormalizedString().IndexOf('-')); - } - else { - modulePath = Path.Combine(tempInstallPath, pkgIdentity.Id.ToLower(), newVersion); - } - + + 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(modulePath, (p.Name + ".ps1")); + var scriptPath = Path.Combine(tempDirNameVersion, (p.Name + ".ps1")); var isScript = File.Exists(scriptPath) ? true : false; if (!isScript) { - var moduleManifest = Path.Combine(modulePath, pkgIdentity.Id + ".psd1"); + var moduleManifest = Path.Combine(tempDirNameVersion, pkgIdentity.Id + ".psd1"); var parsedMetadataHashtable = Utils.ParseModuleManifest(moduleManifest, this); - moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; // Accept License verification @@ -403,30 +394,26 @@ private List InstallPackage(IEnumerable pkgsToInstall, s continue; } } - - string tempDirNameVersion = Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); - // Delete the extra nupkg related files that are not needed and not part of the module/script DeleteExtraneousFiles(tempInstallPath, pkgIdentity, tempDirNameVersion); - if (!Directory.Exists(tempDirNameVersion)) - { - cmdletPassedIn.WriteDebug(string.Format("Directory does not exist, creating directory: '{0}'", tempDirNameVersion)); - Directory.CreateDirectory(tempDirNameVersion); - } - - - - if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, repoName, p, isScript); + // PSModules: + /// ./Modules + /// ./Scripts + /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list + var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) + : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); + if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); + if (save) { - //TODO: SavePackage(); + //TODO: SavePackage } else { - MoveFilesIntoInstallPath(p, isScript, tempDirNameVersion, tempInstallPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); + MoveFilesIntoInstallPath(p, isScript, isLocalRepo, tempDirNameVersion, tempInstallPath, installPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); } @@ -450,56 +437,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s return pkgsSuccessfullyInstalled; } - - - // IGNORE FOR INSTALL - private void SavePackage(PackageIdentity pkgIdentity, string tempInstallPath, string dirNameVersion, bool isScript, bool isLocalRepo) - { - // I don't believe we should ever be getting to this _asNupkg - if (isScript) - { - var tempScriptPath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToNormalizedString()); - var scriptName = pkgIdentity.Id + ".ps1"; - File.Copy(Path.Combine(tempScriptPath, scriptName), Path.Combine(_specifiedPath, scriptName)); - - if (_includeXML) - { - // else if save and including XML - var scriptXML = pkgIdentity.Id + "_InstalledScriptInfo.xml"; - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML))); - File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(_specifiedPath, scriptXML)); - } - } - else - { - var fullTempInstallpath = Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); // localRepo ? Path.Combine(tempInstallPath, pkgIdentity.Version.ToString()) : Path.Combine(tempInstallPath, pkgIdentity.Id, pkgIdentity.Version.ToString()); - var fullPermanentNewPath = isLocalRepo ? Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()) - : Path.Combine(_specifiedPath, pkgIdentity.Id); - - if (isLocalRepo && !Directory.Exists(Path.Combine(_specifiedPath, pkgIdentity.Id))) - { - Directory.CreateDirectory(Path.Combine(_specifiedPath, pkgIdentity.Id)); - } - - if (isLocalRepo) - { - Directory.Move(tempInstallPath, fullPermanentNewPath); - } - else - { - Directory.Move(Path.Combine(tempInstallPath, pkgIdentity.Id), fullPermanentNewPath); - fullPermanentNewPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Version.ToString()); - } - var tempPSGetModuleInfoXML = Path.Combine(Path.Combine(fullPermanentNewPath, pkgIdentity.Id, pkgIdentity.Version.ToString()), "PSGetModuleInfo.xml"); - if (File.Exists(tempPSGetModuleInfoXML)) - { - File.Copy(tempPSGetModuleInfoXML, Path.Combine(fullPermanentNewPath, "PSGetModuleInfo.xml")); - } - - DeleteExtraneousSaveFiles(pkgIdentity, fullPermanentNewPath); - } - } - + private void CallProgressBar(PSResourceInfo p) { int i = 1; @@ -578,7 +516,7 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t if (!File.Exists(LicenseFilePath)) { var exMessage = "License.txt not Found. License.txt must be provided when user license acceptance is required."; - var ex = new ArgumentException(exMessage); // System.ArgumentException vs PSArgumentException + var ex = new ArgumentException(exMessage); var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); cmdletPassedIn.WriteError(acceptLicenseError); @@ -605,7 +543,7 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t if (!_acceptLicense) { var message = $"License Acceptance is required for module '{p.Name}'. Please specify '-AcceptLicense' to perform this operation."; - var ex = new ArgumentException(message); // System.ArgumentException vs PSArgumentException + var ex = new ArgumentException(message); var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); cmdletPassedIn.WriteError(acceptLicenseError); @@ -617,15 +555,15 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t return success; } - private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSResourceInfo pkg, bool isScript) + private void CreateMetadataXMLFile(string dirNameVersion, string installPath, string repoName, PSResourceInfo pkg, bool isScript) { // Script will have a metadata file similar to: "TestScript_InstalledScriptInfo.xml" // Modules will have the metadata file: "PSGetModuleInfo.xml" var metadataXMLPath = isScript ? Path.Combine(dirNameVersion, (pkg.Name + "_InstalledScriptInfo.xml")) : Path.Combine(dirNameVersion, "PSGetModuleInfo.xml"); - - // TODO: now need to add the extra properties like 'installation date' and 'installation path' + pkg.InstalledDate = DateTime.Now; + pkg.InstalledLocation = installPath; // Write all metadata into metadataXMLPath if (!pkg.TryWrite(metadataXMLPath, out string error)) @@ -635,20 +573,20 @@ private void CreateMetadataXMLFile(string dirNameVersion, string repoName, PSRes var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null); WriteError(ErrorParsingMetadata); } - } - private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgIdentity, string dirNameVersion) { - /// test this!!!!!!! // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module var pkgIdString = pkgIdentity.ToString(); var nupkgSHAToDelete = Path.Combine(dirNameVersion, (pkgIdString + ".nupkg.sha512").ToLower()); var nuspecToDelete = Path.Combine(dirNameVersion, (pkgIdentity.Id + ".nuspec").ToLower()); var nupkgToDelete = Path.Combine(dirNameVersion, (pkgIdString + ".nupkg").ToLower()); + 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 + // Unforunately have to check if each file exists because it may or may not be there if (File.Exists(nupkgSHAToDelete)) { cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgSHAToDelete)); @@ -664,73 +602,32 @@ private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgId cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgToDelete)); File.Delete(nupkgToDelete); } - - // TODO: write debug messaging here - } - - private void DeleteExtraneousSaveFiles(PackageIdentity pkgIdentity, string fullPermanentNewPath) - { - var relsPath = Path.Combine(fullPermanentNewPath, "_rels"); - if (Directory.Exists(relsPath)) - { - Directory.Delete(relsPath, true); - } - - var packagePath = Path.Combine(fullPermanentNewPath, "package"); - if (Directory.Exists(packagePath)) + if (File.Exists(contentTypesToDelete)) { - Directory.Delete(packagePath, true); + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", contentTypesToDelete)); + File.Delete(contentTypesToDelete); } - - var pkgIdPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Id); - if (Directory.Exists(pkgIdPath)) + if (Directory.Exists(relsDirToDelete)) { - Directory.Delete(pkgIdPath, true); + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", relsDirToDelete)); + Directory.Delete(relsDirToDelete, true); } - - var pkgVersionPath = Path.Combine(Path.Combine(_specifiedPath, pkgIdentity.Id, pkgIdentity.Version.ToString()), pkgIdentity.Version.ToString()); - if (Directory.Exists(pkgVersionPath)) + if (Directory.Exists(packageDirToDelete)) { - Directory.Delete(Path.Combine(pkgVersionPath), true); - } - - var contentTypesXMLPath = Path.Combine(fullPermanentNewPath, "[Content_Types].xml"); - if (File.Exists(contentTypesXMLPath)) - { - File.Delete(contentTypesXMLPath); - } - - var nuspecPath = Path.Combine(fullPermanentNewPath, pkgIdentity.Id + ".nuspec"); - if (File.Exists(nuspecPath)) - { - File.Delete(nuspecPath); - } - - var nupkgMetadata = Path.Combine(fullPermanentNewPath, ".nupkg.metadata"); - if (File.Exists(nupkgMetadata)) - { - File.Delete(nupkgMetadata); + cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", packageDirToDelete)); + Directory.Delete(packageDirToDelete, true); } } - - - private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string dirNameVersion, string tempInstallPath, string newVersion, string moduleManifestVersion, string nupkgVersion, string versionWithoutPrereleaseTag, string scriptPath) + private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLocalRepo, string dirNameVersion, string tempInstallPath, string installPath, string newVersion, string moduleManifestVersion, string nupkgVersion, string versionWithoutPrereleaseTag, string scriptPath) { - // PSModules: - /// ./Modules - /// ./Scripts - /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list - var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) - : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); - // Creating the proper installation path depending on whether pkg is a module or script var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); var finalModuleVersionDir = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); // versionWithoutPrereleaseTag cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", finalModuleVersionDir)); // If script, just move the files over, if module, move the version directory over - var tempModuleVersionDir = isScript ? dirNameVersion + var tempModuleVersionDir = (isScript || isLocalRepo) ? dirNameVersion : Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); @@ -770,13 +667,8 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di } else { - //tempModuleVersionDir = Path.Combine(tempModuleVersionDir, newVersion); - //var finalModuleVersionDir = Path.Combine(newPath, versionWithoutPrereleaseTag); cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); - //var newVersionPath = Path.Combine(newPath, newVersion); - //cmdletPassedIn.WriteDebug(string.Format("Path for module version directory installation is: '{0}'", newVersionPath)); - // At this point if if (Directory.Exists(finalModuleVersionDir)) { @@ -787,14 +679,8 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, string di cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.Move(tempModuleVersionDir, finalModuleVersionDir); - } } - } - } - - - } \ No newline at end of file diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 17f926efa..0f4cd7a29 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -5,23 +5,17 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force Describe 'Test Install-PSResource for Module' { - BeforeAll{ $TestGalleryName = Get-PoshTestGalleryName $PSGalleryName = Get-PSGalleryName $NuGetGalleryName = Get-NuGetGalleryName Get-NewPSResourceRepositoryFile Register-LocalRepos - Get-PSResourceRepository } AfterEach { - Uninstall-PSResource "TestModule" - Uninstall-PSResource "TestModule99" - - Uninstall-PSResource "myTestModule" - Uninstall-PSResource "myTestModule2" - uninstall-PSResource "testModuleWithlicense" + Uninstall-PSResource "TestModule", "TestModule99", "myTestModule", "myTestModule2", "testModulePrerelease", + "testModuleWithlicense","PSGetTestModule", "PSGetTestDependency1", "TestFindModule" } AfterAll { @@ -29,15 +23,12 @@ Describe 'Test Install-PSResource for Module' { } It "Install specific module resource by name" { - Get-PSResourceRepository - Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } - It "Install specific script resource by name" { Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "TestTestScript" @@ -52,6 +43,7 @@ Describe 'Test Install-PSResource for Module' { $pkg.Name | Should -Be $pkgNames } + It "Should not install resource given nonexistant name" { Install-PSResource -Name NonExistantModule -Repository $TestGalleryName $pkg = Get-Module "NonExistantModule" -ListAvailable @@ -66,7 +58,6 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "1.2.0" } - It "Should install resource given name and exact version with bracket syntax" { Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable @@ -107,24 +98,24 @@ Describe 'Test Install-PSResource for Module' { } It "Install resource with latest (including prerelease) version given Prerelease parameter" { - # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "TestModulePrerelease" -ListAvailable + $pkg.Name | Should -Be "TestModulePrerelease" $pkg.Version | Should -Be "0.0.1" $pkg.PrivateData.PSData.Prerelease | Should -Be "preview" } - - - It "Install a module with a dependency" { - # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "PSGetTestModule" -ListAvailable + $pkg.Name | Should -Be "PSGetTestModule" $pkg.Version | Should -Be "2.0.2" $pkg.PrivateData.PSData.Prerelease | Should -Be "-alpha1" - } + $pkg = Get-Module "PSGetTestDependency1" -ListAvailable + $pkg.Name | Should -Be "PSGetTestDependency1" + $pkg.Version | Should -Be "1.0.0" + } It "Install resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource @@ -139,7 +130,6 @@ Describe 'Test Install-PSResource for Module' { $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Path.Contains("Documents") | Should -Be $true - } # Windows only @@ -147,7 +137,6 @@ Describe 'Test Install-PSResource for Module' { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" - write-host $pkg.Path $pkg.Path.Contains("Program Files") | Should -Be $true } @@ -159,24 +148,13 @@ Describe 'Test Install-PSResource for Module' { $pkg.Path.Contains("Documents") | Should -Be $true } - - - -<# It "Should not install resource that is already installed" { - $a = (Get-PSResourceRepository) - write-host $a.Name - write-host $a.Priority - write-host $a.Trusted - Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" Install-PSResource -Name "TestModule" -Repository $TestGalleryName -WarningVariable WarningVar -warningaction SilentlyContinue $WarningVar | Should -Not -BeNullOrEmpty } -#> - It "Reinstall resource that is already installed with -Reinstall parameter" { Install-PSResource -Name "TestModule" -Repository $TestGalleryName @@ -219,38 +197,28 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "0.0.1.0" } -<# - ############ FAILING - It "Install resource with -NoClobber flag (should not clobber)" { - Install-PSResource -Name "myTestModule" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "myTestModule" - $pkg.Name | Should -Be "myTestModule" - $pkg.Version | Should -Be "0.0.3.0" + It "Install resource from local repository given Repository parameter" { + $publishModuleName = "TestFindModule" + $repoName = "psgettestlocal" + Get-ModuleResourcePublishedToLocalRepoTestDrive $publishModuleName $repoName + Set-PSResourceRepository "psgettestlocal" -Trusted:$true - Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName -NoClobber #-WarningVariable WarningVar -warningaction SilentlyContinue - $WarningVar | Should -Not -BeNullOrEmpty - $pkg = Get-InstalledPSResource "myTestModule2" - $pkg.Name | Should -Be "myTestModule2" - $pkg.Version | Should -Be "0.0.1.0" + Install-PSResource -Name $publishModuleName -Repository $repoName + $pkg = Get-Module $publishModuleName -ListAvailable + $pkg | Should -Not -BeNullOrEmpty + $pkg.Name | Should -Be $publishModuleName } -#> - ### This needs to be manually tested due to prompt <# + # This needs to be manually tested due to prompt It "Install resource that requires accept license without -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "testModuleWithlicense" $pkg.Name | Should -Be "testModuleWithlicense" $pkg.Version | Should -Be "0.0.1.0" } -#> - - ### PoshTestGallery psgettestlocal NuGetGallery PSGallery psgettestlocal2 - ### 10 40 50 50 50 - ### True False True False False - <# - ### This needs to be manually tested due to prompt + # This needs to be manually tested due to prompt It "Install resource should prompt 'trust repository' if repository is not trusted" { Set-PSResourceRepository PoshTestGallery -Trusted:$false @@ -262,35 +230,4 @@ Describe 'Test Install-PSResource for Module' { Set-PSResourceRepository PoshTestGallery -Trusted } #> -<# - It "Install resource from local repository given Repository parameter" { - $publishModuleName = "TestFindModule" - $repoName = "psgettestlocal" - Get-ModuleResourcePublishedToLocalRepoTestDrive $publishModuleName $repoName - - Install-PSResource -Name $publishModuleName -Repository $repoName - $pkg = Get-Module $publishModuleName -ListAvailable - $pkg | Should -Not -BeNullOrEmpty - $pkg.Name | Should -Be $publishModuleName - #$pkg.Repository | Should -Be $repoName - } - - - - - It "Install resource given repository parameter, where resource exists in multiple local repos" { - $moduleName = "test_local_mod" - $repoHigherPriorityRanking = "psgettestlocal" - $repoLowerPriorityRanking = "psgettestlocal2" - - Get-ModuleResourcePublishedToLocalRepoTestDrive $moduleName $repoHigherPriorityRanking - Get-ModuleResourcePublishedToLocalRepoTestDrive $moduleName $repoLowerPriorityRanking - - $res = Find-PSResource -Name $moduleName - $res.Repository | Should -Be $repoHigherPriorityRanking - - $resNonDefault = Find-PSResource -Name $moduleName -Repository $repoLowerPriorityRanking - $resNonDefault.Repository | Should -Be $repoLowerPriorityRanking - } - #> } From 8b2260455e4714ef51cc3558c109c239948f99df Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 8 Jul 2021 11:59:33 -0400 Subject: [PATCH 20/62] remove some files accidentally added --- azure-pipelines-1.yml | 167 ----------------------------------- azure-pipelines-2.yml | 167 ----------------------------------- azure-pipelines.yml | 167 ----------------------------------- src/code/UpdatePSResource.cs | 8 +- 4 files changed, 4 insertions(+), 505 deletions(-) delete mode 100644 azure-pipelines-1.yml delete mode 100644 azure-pipelines-2.yml delete mode 100644 azure-pipelines.yml diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml deleted file mode 100644 index 7b0b1172b..000000000 --- a/azure-pipelines-1.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - 3 displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. diff --git a/azure-pipelines-2.yml b/azure-pipelines-2.yml deleted file mode 100644 index 6169e1232..000000000 --- a/azure-pipelines-2.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - # displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 7b0b1172b..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - 3 displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 0cf4635cc..298927742 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -1,10 +1,10 @@ -using System; -using System.Linq; + // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Collections.Generic; +using System; using Dbg = System.Diagnostics.Debug; +using System.Linq; using System.Management.Automation; using System.Threading; using Microsoft.PowerShell.PowerShellGet.UtilClasses; @@ -45,7 +45,7 @@ class UpdatePSResource : PSCmdlet [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] [ValidateNotNullOrEmpty] public string[] Name { get; set; } - // create a default string with "*" + // TODO: create a default string with "*" /// /// Specifies the version the resource is to be updated to. From a8b8962b46027de72acbcbbbfbeb4eebb0174cec Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 8 Jul 2021 09:58:45 -0700 Subject: [PATCH 21/62] Change scope to be a an Enum type --- src/code/InstallHelper.cs | 9 +++------ src/code/InstallPSResource.cs | 2 +- src/code/PSResourceInfo.cs | 15 +++++++++++---- src/code/Utils.cs | 9 ++++----- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 049be2d3f..7e81a9b1b 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -35,7 +35,6 @@ internal class InstallHelper : PSCmdlet List _pathsToInstallPkg; VersionRange _versionRange; bool _prerelease; - string _scope; bool _acceptLicense; bool _quiet; bool _reinstall; @@ -60,8 +59,7 @@ public void ProcessInstallParams( string[] names, VersionRange versionRange, bool prerelease, - string[] repository, - string scope, + string[] repository, bool acceptLicense, bool quiet, bool reinstall, @@ -77,14 +75,13 @@ public void ProcessInstallParams( bool includeXML, List pathsToInstallPkg) { - cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; Scope: '{4}'; " + + cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", string.Join(",", names), (_versionRange != null ? _versionRange.OriginalString : string.Empty), prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, - scope != null ? scope : string.Empty, acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); + acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); _versionRange = versionRange; _prerelease = prerelease; - _scope = scope; _acceptLicense = acceptLicense; _quiet = quiet; _reinstall = reinstall; diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 2d4f89b46..6f1a15426 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -72,7 +72,7 @@ class InstallPSResource : PSCmdlet [ValidateSet("CurrentUser", "AllUsers")] [Parameter(ParameterSetName = NameParameterSet)] //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] - public string Scope { get; set; } + public ScopeType Scope { get; set; } /// /// Overrides warning messages about installation conflicts about existing commands on a computer. diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index c6af1effd..209d67b7e 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -37,12 +37,19 @@ public enum VersionType MinimumVersion, RequiredVersion, MaximumVersion - } - + } + + public enum ScopeType + { + None, + AllUsers, + CurrentUser + } + #endregion - + #region VersionInfo - + public sealed class VersionInfo { public VersionInfo( diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 84062688e..3a5ccf0dc 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -236,14 +236,13 @@ public static List GetAllResourcePaths(PSCmdlet psCmdlet) } // Find all potential installation paths given a scope - public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string scope) + public static List GetAllInstallationPaths(PSCmdlet psCmdlet, ScopeType scope) { List installationPaths = new List(); var PSVersion6 = new Version(6, 0); var isCorePS = psCmdlet.Host.Version >= PSVersion6; string myDocumentsPath; string programFilesPath; - scope = String.IsNullOrEmpty(scope) ? string.Empty : scope; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -263,7 +262,7 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string sco // If no explicit specification, will return PSModulePath, and then CurrentUser paths // Installation will search for a /Modules or /Scripts directory // If they are not available within one of the paths in PSModulePath, the CurrentUser path will be used. - if (string.IsNullOrEmpty(scope)) + if (scope == ScopeType.None) { string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); installationPaths = psModulePath.Split(';').ToList(); @@ -271,13 +270,13 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, string sco installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); } // If user explicitly specifies AllUsers - if (scope.Equals("AllUsers")) + if (scope == ScopeType.AllUsers) { installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Modules")); installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Scripts")); } // If user explicitly specifies CurrentUser - else if (scope.Equals("CurrentUser")) + else if (scope == ScopeType.CurrentUser) { installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Modules")); installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); From e64691f9be75345cb3a168c68e81a7b9ac895ba2 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:38:52 -0700 Subject: [PATCH 22/62] Delete azure-pipelines-1.yml --- azure-pipelines-1.yml | 167 ------------------------------------------ 1 file changed, 167 deletions(-) delete mode 100644 azure-pipelines-1.yml diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml deleted file mode 100644 index 7b0b1172b..000000000 --- a/azure-pipelines-1.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - 3 displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. From ff95a3c1a7cc28ac30294636a643c8a95e9ff7d9 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:39:26 -0700 Subject: [PATCH 23/62] Delete azure-pipelines-2.yml --- azure-pipelines-2.yml | 167 ------------------------------------------ 1 file changed, 167 deletions(-) delete mode 100644 azure-pipelines-2.yml diff --git a/azure-pipelines-2.yml b/azure-pipelines-2.yml deleted file mode 100644 index 6169e1232..000000000 --- a/azure-pipelines-2.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - # displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. From 44b8dfe2580223c87f2b232fa8e930c5bb774d18 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Thu, 8 Jul 2021 13:39:40 -0700 Subject: [PATCH 24/62] Delete azure-pipelines.yml --- azure-pipelines.yml | 167 -------------------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 7b0b1172b..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,167 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PowerShellGetv2-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for PowerShellGetv2 is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: Package ES CodeHub Lab E - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.PowerShellGetv2 - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)" - Import-Module $(Build.SourcesDirectory)/tools/build.psm1 -Force - Install-Dependencies - Update-ModuleManifestFunctions - Publish-ModuleArtifacts - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/dist/PowerShellGet" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2/signed/PowerShellGet" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/dist/PowerShellGet/PowerShellGet.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # this the MS authored step This cert covers MS autored items - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.mof - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - #- pwsh: | - # Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - # publish-module -Path $(signOutPath) - - # ./build -BuildNupkg -signed - 3 displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PowerShellGetv2" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - #- publish: "$(signOutPath)/PowerShellGet.$(moduleVersion).nupkg" - # artifact: nupkg - # displayName: Publish module nupkg - - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: script-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)' - # binskim - this isn't recursive, so you need the path to the assemblies - # AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - # suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. From c0907ba84ce5c89ff119808c88bcba3f4814bed6 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Thu, 8 Jul 2021 14:18:48 -0700 Subject: [PATCH 25/62] Address bugs introduced from changing Scope type to enum --- src/code/InstallHelper.cs | 13 ++++++++++--- src/code/InstallPSResource.cs | 6 +++--- src/code/PSResourceInfo.cs | 4 ++-- src/code/Utils.cs | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 7e81a9b1b..d52b9458f 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -76,9 +76,16 @@ public void ProcessInstallParams( List pathsToInstallPkg) { cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + - "AcceptLicense: '{5}'; Quiet: '{6}'; Reinstall: '{7}'; TrustRepository: '{8}'; NoClobber: '{9}';", - string.Join(",", names), (_versionRange != null ? _versionRange.OriginalString : string.Empty), prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, - acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); + "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';", + string.Join(",", names), + (_versionRange != null ? _versionRange.OriginalString : string.Empty), + prerelease.ToString(), + repository != null ? string.Join(",", repository) : string.Empty, + acceptLicense.ToString(), + quiet.ToString(), + reinstall.ToString(), + trustRepository.ToString(), + noClobber.ToString())); _versionRange = versionRange; _prerelease = prerelease; diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 6f1a15426..c940b35aa 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -180,7 +180,7 @@ protected override void ProcessRecord() switch (ParameterSetName) { case NameParameterSet: - installHelper.ProcessInstallParams(Name, _versionRange, Prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams(Name, _versionRange, Prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); break; // TODO: make sure InputObject types are correct @@ -216,7 +216,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InputObjIncorrectVersionFormat); } - installHelper.ProcessInstallParams(new[] { pkg.Name }, inputObjVersionRange, prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams(new[] { pkg.Name }, inputObjVersionRange, prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); } } else if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) @@ -238,7 +238,7 @@ protected override void ProcessRecord() ThrowTerminatingError(InputObjIncorrectVersionFormat); } - installHelper.ProcessInstallParams(new[] { name }, inputObjVersionRange, prerelease, Repository, Scope, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams(new[] { name }, inputObjVersionRange, prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); } } } diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 209d67b7e..2ef17ef69 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -42,8 +42,8 @@ public enum VersionType public enum ScopeType { None, - AllUsers, - CurrentUser + CurrentUser, + AllUsers } #endregion diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 3a5ccf0dc..b6ff456fb 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -270,7 +270,7 @@ public static List GetAllInstallationPaths(PSCmdlet psCmdlet, ScopeType installationPaths.Add(System.IO.Path.Combine(myDocumentsPath, "Scripts")); } // If user explicitly specifies AllUsers - if (scope == ScopeType.AllUsers) + else if (scope == ScopeType.AllUsers) { installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Modules")); installationPaths.Add(System.IO.Path.Combine(programFilesPath, "Scripts")); From cfcf3519bfe5259f7e65a066f1be7d77aee332b3 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 9 Jul 2021 15:27:07 -0700 Subject: [PATCH 26/62] Incorporate review feedback, clean up code a bit --- src/code/InstallPSResource.cs | 101 ++++++++++++++++++++++++++-------- src/code/InstallPkgParams.cs | 17 ------ src/code/PSRepositoryInfo.cs | 1 - src/code/PowerShellGet.csproj | 7 +-- 4 files changed, 80 insertions(+), 46 deletions(-) delete mode 100644 src/code/InstallPkgParams.cs diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index c940b35aa..77bdee530 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -1,12 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using Microsoft.PowerShell.PowerShellGet.UtilClasses; using NuGet.Versioning; using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Management.Automation; using System.Threading; -using static System.Environment; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -50,11 +50,11 @@ class InstallPSResource : PSCmdlet public SwitchParameter Prerelease { get; set; } /// - /// Specifies a user account that has rights to find a resource from a specific repository. + /// Specifies the repositories from which to search for the resource to be installed. /// [Parameter(ParameterSetName = NameParameterSet)] - // todo: add tab completion (look at get-psresourcerepository at the name parameter) //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + [ArgumentCompleter(typeof(RepositoryNameCompleter))] [ValidateNotNullOrEmpty] public string[] Repository { get; set; } @@ -66,23 +66,13 @@ class InstallPSResource : PSCmdlet public PSCredential Credential { get; set; } /// - /// Specifies to return any dependency packages. - /// Currently only used when name param is specified. + /// Specifies the scope of installation. /// [ValidateSet("CurrentUser", "AllUsers")] [Parameter(ParameterSetName = NameParameterSet)] //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public ScopeType Scope { get; set; } - /// - /// Overrides warning messages about installation conflicts about existing commands on a computer. - /// Overwrites existing commands that have the same name as commands being installed by a module. AllowClobber and Force can be used together in an Install-Module command. - /// Prevents installing modules that have the same cmdlets as a differently named module already - /// - [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] - public SwitchParameter NoClobber { get; set; } - /// /// Suppresses being prompted for untrusted sources. /// @@ -111,14 +101,24 @@ class InstallPSResource : PSCmdlet //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter AcceptLicense { get; set; } + /* + /// + /// Prevents installation conflicts with modules that contain existing commands on a computer. + /// + [Parameter(ParameterSetName = NameParameterSet)] + //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + public SwitchParameter NoClobber { get; set; } + */ + + /* /// - /// /// [Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public String RequiredResourceFile { get; set; } + */ - /// - /// + /* + /// /// [Parameter(ParameterSetName = RequiredResourceParameterSet)] public Object RequiredResource // takes either string (json) or hashtable @@ -142,6 +142,7 @@ class InstallPSResource : PSCmdlet } private string _requiredResourceJson; private Hashtable _requiredResourceHash; + */ #endregion #region members @@ -175,12 +176,30 @@ protected override void ProcessRecord() CancellationTokenSource source = new CancellationTokenSource(); CancellationToken cancellationToken = source.Token; - var installHelper = new InstallHelper(update: false, save: false, cancellationToken, this); + var installHelper = new InstallHelper(update: false, save: false, cancellationToken: cancellationToken, this); switch (ParameterSetName) { case NameParameterSet: - installHelper.ProcessInstallParams(Name, _versionRange, Prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams( + names: Name, + versionRange: _versionRange, + prerelease: Prerelease, + repository: Repository, + acceptLicense: AcceptLicense, + quiet: Quiet, + reinstall: Reinstall, + force: false, + trustRepository: TrustRepository, + noClobber: false, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: null, + asNupkg: false, + includeXML: true, + pathsToInstallPkg: _pathsToInstallPkg); break; // TODO: make sure InputObject types are correct @@ -216,7 +235,25 @@ protected override void ProcessRecord() ThrowTerminatingError(InputObjIncorrectVersionFormat); } - installHelper.ProcessInstallParams(new[] { pkg.Name }, inputObjVersionRange, prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams( + names: new[] { pkg.Name }, + versionRange: inputObjVersionRange, + prerelease: prerelease, + repository: Repository, + acceptLicense: AcceptLicense, + quiet: Quiet, + reinstall: Reinstall, + force: false, + trustRepository: TrustRepository, + noClobber: false, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: null, + asNupkg: false, + includeXML: true, + pathsToInstallPkg: _pathsToInstallPkg); } } else if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) @@ -238,7 +275,25 @@ protected override void ProcessRecord() ThrowTerminatingError(InputObjIncorrectVersionFormat); } - installHelper.ProcessInstallParams(new[] { name }, inputObjVersionRange, prerelease, Repository, AcceptLicense, Quiet, Reinstall, force: false, TrustRepository, NoClobber, Credential, RequiredResourceFile, _requiredResourceJson, _requiredResourceHash, specifiedPath: null, asNupkg: false, includeXML: true, _pathsToInstallPkg); + installHelper.ProcessInstallParams( + names: new[] { name }, + versionRange: inputObjVersionRange, + prerelease: prerelease, + repository: Repository, + acceptLicense: AcceptLicense, + quiet: Quiet, + reinstall: Reinstall, + force: false, + trustRepository: TrustRepository, + noClobber: false, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: null, + asNupkg: false, + includeXML: true, + pathsToInstallPkg: _pathsToInstallPkg); } } } @@ -263,4 +318,4 @@ protected override void ProcessRecord() } #endregion } -} \ No newline at end of file +} diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs deleted file mode 100644 index f6591038b..000000000 --- a/src/code/InstallPkgParams.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Management.Automation; - -public class PkgParams -{ - public string Name { get; set; } - public string Version { get; set; } - public string Repository { get; set; } - public PSCredential Credential { get; set; } - public bool AcceptLicense { get; set; } - public bool Prerelease { get; set; } - public string Scope { get; set; } - public bool Quiet { get; set; } - public bool Reinstall { get; set; } - public bool Force { get; set; } - public bool TrustRepository { get; set; } - public bool NoClobber { get; set; } -} \ No newline at end of file diff --git a/src/code/PSRepositoryInfo.cs b/src/code/PSRepositoryInfo.cs index b1d99eb08..ade75833e 100644 --- a/src/code/PSRepositoryInfo.cs +++ b/src/code/PSRepositoryInfo.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. - using System; using System.Management.Automation; diff --git a/src/code/PowerShellGet.csproj b/src/code/PowerShellGet.csproj index e8e3b26ca..63c5b0bd8 100644 --- a/src/code/PowerShellGet.csproj +++ b/src/code/PowerShellGet.csproj @@ -1,4 +1,4 @@ - + @@ -9,16 +9,14 @@ 3.0.0 3.0.0 netstandard2.0 - 7.2 + 8.0 - - @@ -29,7 +27,6 @@ - From 386f0a9a22a71c1c5d81c891566e4fdf4369177d Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 9 Jul 2021 17:42:45 -0700 Subject: [PATCH 27/62] Remove input object parameter --- src/PowerShellGet.psd1 | 2 +- src/code/InstallPSResource.cs | 117 ++-------------------------------- 2 files changed, 5 insertions(+), 114 deletions(-) diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 0ce94ad9a..ee9bb9f27 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -14,7 +14,7 @@ 'Find-PSResource', 'Get-InstalledPSResource', 'Get-PSResourceRepository', - # 'Install-PSResource', + 'Install-PSResource', 'Register-PSResourceRepository', # 'Save-PSResource', 'Set-PSResourceRepository', diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 77bdee530..a70b8648b 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -28,17 +28,10 @@ class InstallPSResource : PSCmdlet [ValidateNotNullOrEmpty] public string[] Name { get; set; } - /// - /// Used for pipeline input. - /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ParameterSetName = InputObjectSet)] - [ValidateNotNullOrEmpty] - public object[] InputObject { get; set; } - /// /// Specifies the version or version range of the package to be installed /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] [ValidateNotNullOrEmpty] public string Version { get; set; } @@ -147,7 +140,6 @@ class InstallPSResource : PSCmdlet #region members private const string NameParameterSet = "NameParameterSet"; - private const string InputObjectSet = "InputObjectSet"; private const string RequiredResourceFileParameterSet = "RequiredResourceFileParameterSet"; private const string RequiredResourceParameterSet = "RequiredResourceParameterSet"; List _pathsToInstallPkg; @@ -158,7 +150,7 @@ class InstallPSResource : PSCmdlet protected override void BeginProcessing() { // validate that if a -Version param is passed in that it can be parsed into a NuGet version range. - // an exact version will be formatted into a version range. + // An exact version will be formatted into a version range. if (ParameterSetName.Equals("NameParameterSet") && Version != null && !Utils.TryParseVersionOrVersionRange(Version, out _versionRange)) { var exMessage = "Argument for -Version parameter is not in the proper format."; @@ -176,7 +168,7 @@ protected override void ProcessRecord() CancellationTokenSource source = new CancellationTokenSource(); CancellationToken cancellationToken = source.Token; - var installHelper = new InstallHelper(update: false, save: false, cancellationToken: cancellationToken, this); + var installHelper = new InstallHelper(update: false, save: false, cancellationToken: cancellationToken, cmdletPassedIn: this); switch (ParameterSetName) { @@ -201,117 +193,16 @@ protected override void ProcessRecord() includeXML: true, pathsToInstallPkg: _pathsToInstallPkg); break; - - // TODO: make sure InputObject types are correct - // TODO: Consider switch statement of object type to clean up a bit - case InputObjectSet: - if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) - { - foreach (PSModuleInfo pkg in InputObject) - { - var prerelease = false; - - if (pkg.PrivateData != null) - { - Hashtable privateData = (Hashtable)pkg.PrivateData; - if (privateData.ContainsKey("PSData")) - { - Hashtable psData = (Hashtable)privateData["PSData"]; - - if (psData.ContainsKey("Prerelease") && !string.IsNullOrEmpty((string)psData["Prerelease"])) - { - prerelease = true; - } - } - } - - // Need to explicitly assign inputObjVersionRange in order to pass to ProcessInstallParams - VersionRange inputObjVersionRange = new VersionRange(); - if (pkg.Version != null && !Utils.TryParseVersionOrVersionRange(pkg.Version.ToString(), out inputObjVersionRange)) - { - var exMessage = "Argument for version parameter is not in the proper format."; - var ex = new ArgumentException(exMessage); - var InputObjIncorrectVersionFormat = new ErrorRecord(ex, "InputObjIncorrectVersionFormat", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(InputObjIncorrectVersionFormat); - } - - installHelper.ProcessInstallParams( - names: new[] { pkg.Name }, - versionRange: inputObjVersionRange, - prerelease: prerelease, - repository: Repository, - acceptLicense: AcceptLicense, - quiet: Quiet, - reinstall: Reinstall, - force: false, - trustRepository: TrustRepository, - noClobber: false, - credential: Credential, - requiredResourceFile: null, - requiredResourceJson: null, - requiredResourceHash: null, - specifiedPath: null, - asNupkg: false, - includeXML: true, - pathsToInstallPkg: _pathsToInstallPkg); - } - } - else if (InputObject[0].GetType().Name.Equals("PSModuleInfo")) - { - foreach (PSObject pkg in InputObject) - { - if (pkg != null) - { - var name = (string)pkg.Properties["Name"].Value; - var version = (NuGetVersion)pkg.Properties["Version"].Value; - var prerelease = version.IsPrerelease; - - VersionRange inputObjVersionRange = new VersionRange(); - if (version != null && !Utils.TryParseVersionOrVersionRange(version.ToString(), out inputObjVersionRange)) - { - var exMessage = "Argument for version parameter is not in the proper format."; - var ex = new ArgumentException(exMessage); - var InputObjIncorrectVersionFormat = new ErrorRecord(ex, "InputObjIncorrectVersionFormat", ErrorCategory.InvalidArgument, null); - ThrowTerminatingError(InputObjIncorrectVersionFormat); - } - - installHelper.ProcessInstallParams( - names: new[] { name }, - versionRange: inputObjVersionRange, - prerelease: prerelease, - repository: Repository, - acceptLicense: AcceptLicense, - quiet: Quiet, - reinstall: Reinstall, - force: false, - trustRepository: TrustRepository, - noClobber: false, - credential: Credential, - requiredResourceFile: null, - requiredResourceJson: null, - requiredResourceHash: null, - specifiedPath: null, - asNupkg: false, - includeXML: true, - pathsToInstallPkg: _pathsToInstallPkg); - } - } - } - break; - + case RequiredResourceFileParameterSet: - // TODO: throw PSNotImplementedException WriteDebug("Not yet implemented"); break; case RequiredResourceParameterSet: - // TODO: throw PSNotImplementedException WriteDebug("Not yet implemented"); break; default: - // TODO: throw some kind of terminating error (unrecognized parameter set) - // TODO: change to debug assert WriteDebug("Invalid parameter set"); break; } From 4aff5356a15277303b0561d8883d3781ebbc3ffa Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Fri, 9 Jul 2021 18:18:55 -0700 Subject: [PATCH 28/62] Remove string comparison in install helper Co-authored-by: Paul Higinbotham --- src/code/InstallHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index d52b9458f..4a63634d7 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -148,7 +148,8 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool cmdletPassedIn.WriteDebug("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.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter); + var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase); + var cancellationToken = new CancellationToken(); var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); @@ -687,4 +688,4 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLo } } } -} \ No newline at end of file +} From 42c4a39241cf6536d95bcc5c4b5f552b78c72891 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Fri, 9 Jul 2021 18:21:12 -0700 Subject: [PATCH 29/62] Update src/code/PSResourceInfo.cs Co-authored-by: Paul Higinbotham --- src/code/PSResourceInfo.cs | 1562 ++++++++++++++++++------------------ 1 file changed, 781 insertions(+), 781 deletions(-) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 2ef17ef69..a8868f44b 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -1,42 +1,42 @@ -using System.Text.RegularExpressions; -using System.Linq; -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections; -using System.Collections.Generic; -using Dbg = System.Diagnostics.Debug; -using System.Globalization; -using System.Management.Automation; -using NuGet.Packaging; -using NuGet.Protocol.Core.Types; -using NuGet.Versioning; +using System.Text.RegularExpressions; +using System.Linq; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using Dbg = System.Diagnostics.Debug; +using System.Globalization; +using System.Management.Automation; +using NuGet.Packaging; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; using System.Reflection; -namespace Microsoft.PowerShell.PowerShellGet.UtilClasses -{ - #region Enums - - [Flags] - public enum ResourceType - { - // 00001 -> M - // 00100 -> C - // 00101 -> M, C - None = 0x0, - Module = 0x1, - Script = 0x2, - Command = 0x4, - DscResource = 0x8 - } - - public enum VersionType - { - Unknown, - MinimumVersion, - RequiredVersion, - MaximumVersion +namespace Microsoft.PowerShell.PowerShellGet.UtilClasses +{ + #region Enums + + [Flags] + public enum ResourceType + { + // 00001 -> M + // 00100 -> C + // 00101 -> M, C + None = 0x0, + Module = 0x1, + Script = 0x2, + Command = 0x4, + DscResource = 0x8 + } + + public enum VersionType + { + Unknown, + MinimumVersion, + RequiredVersion, + MaximumVersion } public enum ScopeType @@ -46,498 +46,498 @@ public enum ScopeType AllUsers } - #endregion - - #region VersionInfo - - public sealed class VersionInfo - { - public VersionInfo( - VersionType versionType, - Version versionNum) - { - VersionType = versionType; - VersionNum = versionNum; - } - - public VersionType VersionType { get; } - public Version VersionNum { get; } - - public override string ToString() => $"{VersionType}: {VersionNum}"; - } - - #endregion - - #region ResourceIncludes - - public sealed class ResourceIncludes - { - #region Properties - - public string[] Cmdlet { get; } - - public string[] Command { get; } - - public string[] DscResource { get; } - - public string[] Function { get; } - - public string[] RoleCapability { get; } - - public string[] Workflow { get; } - - #endregion - - #region Constructor - - /// - /// Constructor - /// - /// Provided hashtable has form: - /// Key: Cmdlet - /// Value: ArrayList of Cmdlet name strings - /// Key: Command - /// Value: ArrayList of Command name strings - /// Key: DscResource - /// Value: ArrayList of DscResource name strings - /// Key: Function - /// Value: ArrayList of Function name strings - /// Key: RoleCapability (deprecated for PSGetV3) - /// Value: ArrayList of RoleCapability name strings - /// Key: Workflow (deprecated for PSGetV3) - /// Value: ArrayList of Workflow name strings - /// - /// Hashtable of PSGet includes - public ResourceIncludes(Hashtable includes) - { - if (includes == null) { return; } - - Cmdlet = GetHashTableItem(includes, nameof(Cmdlet)); - Command = GetHashTableItem(includes, nameof(Command)); - DscResource = GetHashTableItem(includes, nameof(DscResource)); - Function = GetHashTableItem(includes, nameof(Function)); - RoleCapability = GetHashTableItem(includes, nameof(RoleCapability)); - Workflow = GetHashTableItem(includes, nameof(Workflow)); - } - - #endregion - - #region Public methods - - public Hashtable ConvertToHashtable() - { - var hashtable = new Hashtable - { - { nameof(Cmdlet), Cmdlet }, - { nameof(Command), Command }, - { nameof(DscResource), DscResource }, - { nameof(Function), Function }, - { nameof(RoleCapability), RoleCapability }, - { nameof(Workflow), Workflow } - }; - - return hashtable; - } - - #endregion - - #region Private methods - - private string[] GetHashTableItem( - Hashtable table, - string name) - { - if (table.ContainsKey(name) && - table[name] is PSObject psObjectItem) - { - return Utils.GetStringArray(psObjectItem.BaseObject as ArrayList); - } - - return null; - } - - #endregion - } - - #endregion - - #region Dependency - - public sealed class Dependency - { - #region Properties - - public string Name { get; } - - public VersionRange VersionRange { get; } - - #endregion - - #region Constructor - - /// - /// Constructor - /// - /// - /// Hashtable of PSGet includes - public Dependency(string dependencyName, VersionRange dependencyVersionRange) - { - Name = dependencyName; - VersionRange = dependencyVersionRange; - } - - #endregion - } - - #endregion - - #region PSResourceInfo - - public sealed class PSResourceInfo - { - #region Properties - - public Dictionary AdditionalMetadata { get; set; } - public string Author { get; set; } - public string CompanyName { get; set; } - public string Copyright { get; set; } - public Dependency[] Dependencies { get; set; } - public string Description { get; set; } - public Uri IconUri { get; set; } - public ResourceIncludes Includes { get; set; } - public DateTime? InstalledDate { get; set; } - public string InstalledLocation { get; set; } - public bool IsPrerelease { get; set; } - public Uri LicenseUri { get; set; } - public string Name { get; set; } - public string PackageManagementProvider { get; set; } - public string PowerShellGetFormatVersion { get; set; } - public string PrereleaseLabel { get; set; } - public Uri ProjectUri { get; set; } - public DateTime? PublishedDate { get; set; } - public string ReleaseNotes { get; set; } - public string Repository { get; set; } - public string RepositorySourceLocation { get; set; } - public string[] Tags { get; set; } - public ResourceType Type { get; set; } - public DateTime? UpdatedDate { get; set; } - public Version Version { get; set; } - - #endregion - - #region Private fields - private static readonly char[] Delimeter = {' ', ','}; - - #endregion - - #region Public static methods - - /// - /// Writes the PSGetResourceInfo properties to the specified file path as a - /// PowerShell serialized xml file, maintaining compatibility with - /// PowerShellGet v2 file format. - /// - public bool TryWrite( - string filePath, - out string errorMsg) - { - errorMsg = string.Empty; - - if (string.IsNullOrWhiteSpace(filePath)) - { - errorMsg = "TryWritePSGetInfo: Invalid file path. Filepath cannot be empty or whitespace."; - return false; - } - - try - { - var infoXml = PSSerializer.Serialize( - source: ConvertToCustomObject(), - depth: 5); - - System.IO.File.WriteAllText( - path: filePath, - contents: infoXml); - - return true; - } - catch(Exception ex) - { - errorMsg = string.Format( - CultureInfo.InvariantCulture, - @"TryWritePSGetInfo: Cannot convert and write the PowerShellGet information to file, with error: {0}", - ex.Message); - - return false; - } - } - - /// - /// Reads a PSGet resource xml (PowerShell serialized) file and returns - /// a PSResourceInfo object containing the file contents. - /// - public static bool TryRead( - string filePath, - out PSResourceInfo psGetInfo, - out string errorMsg) - { - psGetInfo = null; - errorMsg = string.Empty; - - if (string.IsNullOrWhiteSpace(filePath)) - { - errorMsg = "TryReadPSGetInfo: Invalid file path. Filepath cannot be empty or whitespace."; - return false; - } - - try - { - // Read and deserialize information xml file. - var psObjectInfo = (PSObject) PSSerializer.Deserialize( - System.IO.File.ReadAllText( - filePath)); - - var additionalMetadata = GetProperty>(nameof(PSResourceInfo.AdditionalMetadata), psObjectInfo); - Version version = GetVersionInfo(psObjectInfo, additionalMetadata, out string prereleaseLabel); - - psGetInfo = new PSResourceInfo - { - AdditionalMetadata = additionalMetadata, - Author = GetStringProperty(nameof(PSResourceInfo.Author), psObjectInfo), - CompanyName = GetStringProperty(nameof(PSResourceInfo.CompanyName), psObjectInfo), - Copyright = GetStringProperty(nameof(PSResourceInfo.Copyright), psObjectInfo), - Dependencies = GetDependencies(GetProperty(nameof(PSResourceInfo.Dependencies), psObjectInfo)), - Description = GetStringProperty(nameof(PSResourceInfo.Description), psObjectInfo), - IconUri = GetProperty(nameof(PSResourceInfo.IconUri), psObjectInfo), - Includes = new ResourceIncludes(GetProperty(nameof(PSResourceInfo.Includes), psObjectInfo)), - InstalledDate = GetProperty(nameof(PSResourceInfo.InstalledDate), psObjectInfo), - InstalledLocation = GetStringProperty(nameof(PSResourceInfo.InstalledLocation), psObjectInfo), - IsPrerelease = GetProperty(nameof(PSResourceInfo.IsPrerelease), psObjectInfo), - LicenseUri = GetProperty(nameof(PSResourceInfo.LicenseUri), psObjectInfo), - Name = GetStringProperty(nameof(PSResourceInfo.Name), psObjectInfo), - PackageManagementProvider = GetStringProperty(nameof(PSResourceInfo.PackageManagementProvider), psObjectInfo), - PowerShellGetFormatVersion = GetStringProperty(nameof(PSResourceInfo.PowerShellGetFormatVersion), psObjectInfo), - PrereleaseLabel = prereleaseLabel, - ProjectUri = GetProperty(nameof(PSResourceInfo.ProjectUri), psObjectInfo), - PublishedDate = GetProperty(nameof(PSResourceInfo.PublishedDate), psObjectInfo), - ReleaseNotes = GetStringProperty(nameof(PSResourceInfo.ReleaseNotes), psObjectInfo), - Repository = GetStringProperty(nameof(PSResourceInfo.Repository), psObjectInfo), - RepositorySourceLocation = GetStringProperty(nameof(PSResourceInfo.RepositorySourceLocation), psObjectInfo), - Tags = Utils.GetStringArray(GetProperty(nameof(PSResourceInfo.Tags), psObjectInfo)), - // try to get the value of PSResourceInfo.Type property, if the value is null use ResourceType.Module as value - // this value will be used in Enum.TryParse. If Enum.TryParse returns false, use ResourceType.Module to set Type instead. - Type = Enum.TryParse( - GetProperty(nameof(PSResourceInfo.Type), psObjectInfo) ?? nameof(ResourceType.Module), - out ResourceType currentReadType) - ? currentReadType : ResourceType.Module, - UpdatedDate = GetProperty(nameof(PSResourceInfo.UpdatedDate), psObjectInfo), - Version = version - }; - - return true; - } - catch(Exception ex) - { - errorMsg = string.Format( - CultureInfo.InvariantCulture, - @"TryReadPSGetInfo: Cannot read the PowerShellGet information file with error: {0}", - ex.Message); - - return false; - } - } - - private static string GetStringProperty( - string name, - PSObject psObjectInfo) - { - return GetProperty(name, psObjectInfo) ?? string.Empty; - } - - private static Version GetVersionInfo( - PSObject psObjectInfo, - Dictionary additionalMetadata, - out string prereleaseLabel) - { - string versionString = GetProperty(nameof(PSResourceInfo.Version), psObjectInfo); - prereleaseLabel = String.Empty; - - if (!String.IsNullOrEmpty(versionString) || - additionalMetadata.TryGetValue("NormalizedVersion", out versionString)) - { - string pkgVersion = versionString; - if (versionString.Contains("-")) - { - string[] versionStringParsed = versionString.Split('-'); - if (versionStringParsed.Length == 1) - { - pkgVersion = versionStringParsed[0]; - } - else - { - // versionStringParsed.Length > 1 (because string contained '-' so couldn't be 0) - pkgVersion = versionStringParsed[0]; - prereleaseLabel = versionStringParsed[1]; - } - } - - if (!Version.TryParse(pkgVersion, out Version parsedVersion)) - { - prereleaseLabel = String.Empty; - return null; - } - else - { - return parsedVersion; - } - } - - prereleaseLabel = String.Empty; - return GetProperty(nameof(PSResourceInfo.Version), psObjectInfo); - } - - - public static bool TryConvert( - IPackageSearchMetadata metadataToParse, - out PSResourceInfo psGetInfo, - string repositoryName, - ResourceType? type, - out string errorMsg) - { - psGetInfo = null; - errorMsg = String.Empty; - - if (metadataToParse == null) - { - errorMsg = "TryConvertPSResourceInfo: Invalid IPackageSearchMetadata object. Object cannot be null."; - return false; - } - - try - { - psGetInfo = new PSResourceInfo - { - // not all of the properties of PSResourceInfo are filled as they are not there in metadata returned for Find-PSResource. - Author = ParseMetadataAuthor(metadataToParse), - Dependencies = ParseMetadataDependencies(metadataToParse), - Description = ParseMetadataDescription(metadataToParse), - IconUri = ParseMetadataIconUri(metadataToParse), - IsPrerelease = ParseMetadataIsPrerelease(metadataToParse), - LicenseUri = ParseMetadataLicenseUri(metadataToParse), - Name = ParseMetadataName(metadataToParse), - PrereleaseLabel = ParsePrerelease(metadataToParse), - ProjectUri = ParseMetadataProjectUri(metadataToParse), - PublishedDate = ParseMetadataPublishedDate(metadataToParse), - Repository = repositoryName, - Tags = ParseMetadataTags(metadataToParse), - Type = ParseMetadataType(metadataToParse, repositoryName, type), - Version = ParseMetadataVersion(metadataToParse) - }; - - return true; - } - catch (Exception ex) - { - errorMsg = string.Format( - CultureInfo.InvariantCulture, - @"TryReadPSGetInfo: Cannot parse PSResourceInfo from IPackageSearchMetadata with error: {0}", - ex.Message); - return false; - } - } - - #endregion - - #region Private static methods - - private static T ConvertToType(PSObject psObject) - { - // We only convert Dictionary types. - if (typeof(T) != typeof(Dictionary)) - { - return default(T); - } - - var dict = new Dictionary(); - foreach (var prop in psObject.Properties) - { - dict.Add(prop.Name, prop.Value.ToString()); - } - - return (T)Convert.ChangeType(dict, typeof(T)); - } - - private static T GetProperty( - string Name, - PSObject psObjectInfo) - { - var val = psObjectInfo.Properties[Name]?.Value; - if (val == null) - { - return default(T); - } - - switch (val) - { - case T valType: - return valType; - - case PSObject valPSObject: - switch (valPSObject.BaseObject) - { - case T valBase: - return valBase; - - case PSCustomObject _: - // A base object of PSCustomObject means this is additional metadata - // and type T should be Dictionary. - return ConvertToType(valPSObject); - - default: - return default(T); - } - - default: - return default(T); - } - } - - private static string GetPrereleaseLabel(Version version) - { - string versionAsString = version.ToString(); - - if (!versionAsString.Contains("-")) - { - // no prerelease label present - return String.Empty; - } - - string[] prereleaseParsed = versionAsString.Split('-'); - if (prereleaseParsed.Length <= 1) - { - return String.Empty; - } - - string prereleaseString = prereleaseParsed[1]; - Regex prereleasePattern = new Regex("^[a-zA-Z0-9]+$"); - if (!prereleasePattern.IsMatch(prereleaseString)) - { - return String.Empty; - } - - return prereleaseString; - } - - private static Dependency[] GetDependencies(ArrayList dependencyInfos) - { - List dependenciesFound = new List(); - if (dependencyInfos == null) { return dependenciesFound.ToArray(); } - - - foreach(PSObject dependencyObj in dependencyInfos) + #endregion + + #region VersionInfo + + public sealed class VersionInfo + { + public VersionInfo( + VersionType versionType, + Version versionNum) + { + VersionType = versionType; + VersionNum = versionNum; + } + + public VersionType VersionType { get; } + public Version VersionNum { get; } + + public override string ToString() => $"{VersionType}: {VersionNum}"; + } + + #endregion + + #region ResourceIncludes + + public sealed class ResourceIncludes + { + #region Properties + + public string[] Cmdlet { get; } + + public string[] Command { get; } + + public string[] DscResource { get; } + + public string[] Function { get; } + + public string[] RoleCapability { get; } + + public string[] Workflow { get; } + + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// Provided hashtable has form: + /// Key: Cmdlet + /// Value: ArrayList of Cmdlet name strings + /// Key: Command + /// Value: ArrayList of Command name strings + /// Key: DscResource + /// Value: ArrayList of DscResource name strings + /// Key: Function + /// Value: ArrayList of Function name strings + /// Key: RoleCapability (deprecated for PSGetV3) + /// Value: ArrayList of RoleCapability name strings + /// Key: Workflow (deprecated for PSGetV3) + /// Value: ArrayList of Workflow name strings + /// + /// Hashtable of PSGet includes + public ResourceIncludes(Hashtable includes) + { + if (includes == null) { return; } + + Cmdlet = GetHashTableItem(includes, nameof(Cmdlet)); + Command = GetHashTableItem(includes, nameof(Command)); + DscResource = GetHashTableItem(includes, nameof(DscResource)); + Function = GetHashTableItem(includes, nameof(Function)); + RoleCapability = GetHashTableItem(includes, nameof(RoleCapability)); + Workflow = GetHashTableItem(includes, nameof(Workflow)); + } + + #endregion + + #region Public methods + + public Hashtable ConvertToHashtable() + { + var hashtable = new Hashtable + { + { nameof(Cmdlet), Cmdlet }, + { nameof(Command), Command }, + { nameof(DscResource), DscResource }, + { nameof(Function), Function }, + { nameof(RoleCapability), RoleCapability }, + { nameof(Workflow), Workflow } + }; + + return hashtable; + } + + #endregion + + #region Private methods + + private string[] GetHashTableItem( + Hashtable table, + string name) + { + if (table.ContainsKey(name) && + table[name] is PSObject psObjectItem) + { + return Utils.GetStringArray(psObjectItem.BaseObject as ArrayList); + } + + return null; + } + + #endregion + } + + #endregion + + #region Dependency + + public sealed class Dependency + { + #region Properties + + public string Name { get; } + + public VersionRange VersionRange { get; } + + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// + /// Hashtable of PSGet includes + public Dependency(string dependencyName, VersionRange dependencyVersionRange) + { + Name = dependencyName; + VersionRange = dependencyVersionRange; + } + + #endregion + } + + #endregion + + #region PSResourceInfo + + public sealed class PSResourceInfo + { + #region Properties + + public Dictionary AdditionalMetadata { get; set; } + public string Author { get; set; } + public string CompanyName { get; set; } + public string Copyright { get; set; } + public Dependency[] Dependencies { get; set; } + public string Description { get; set; } + public Uri IconUri { get; set; } + public ResourceIncludes Includes { get; set; } + public DateTime? InstalledDate { get; set; } + public string InstalledLocation { get; set; } + public bool IsPrerelease { get; set; } + public Uri LicenseUri { get; set; } + public string Name { get; set; } + public string PackageManagementProvider { get; set; } + public string PowerShellGetFormatVersion { get; set; } + public string PrereleaseLabel { get; set; } + public Uri ProjectUri { get; set; } + public DateTime? PublishedDate { get; set; } + public string ReleaseNotes { get; set; } + public string Repository { get; set; } + public string RepositorySourceLocation { get; set; } + public string[] Tags { get; set; } + public ResourceType Type { get; set; } + public DateTime? UpdatedDate { get; set; } + public Version Version { get; set; } + + #endregion + + #region Private fields + private static readonly char[] Delimeter = {' ', ','}; + + #endregion + + #region Public static methods + + /// + /// Writes the PSGetResourceInfo properties to the specified file path as a + /// PowerShell serialized xml file, maintaining compatibility with + /// PowerShellGet v2 file format. + /// + public bool TryWrite( + string filePath, + out string errorMsg) + { + errorMsg = string.Empty; + + if (string.IsNullOrWhiteSpace(filePath)) + { + errorMsg = "TryWritePSGetInfo: Invalid file path. Filepath cannot be empty or whitespace."; + return false; + } + + try + { + var infoXml = PSSerializer.Serialize( + source: ConvertToCustomObject(), + depth: 5); + + System.IO.File.WriteAllText( + path: filePath, + contents: infoXml); + + return true; + } + catch(Exception ex) + { + errorMsg = string.Format( + CultureInfo.InvariantCulture, + @"TryWritePSGetInfo: Cannot convert and write the PowerShellGet information to file, with error: {0}", + ex.Message); + + return false; + } + } + + /// + /// Reads a PSGet resource xml (PowerShell serialized) file and returns + /// a PSResourceInfo object containing the file contents. + /// + public static bool TryRead( + string filePath, + out PSResourceInfo psGetInfo, + out string errorMsg) + { + psGetInfo = null; + errorMsg = string.Empty; + + if (string.IsNullOrWhiteSpace(filePath)) + { + errorMsg = "TryReadPSGetInfo: Invalid file path. Filepath cannot be empty or whitespace."; + return false; + } + + try + { + // Read and deserialize information xml file. + var psObjectInfo = (PSObject) PSSerializer.Deserialize( + System.IO.File.ReadAllText( + filePath)); + + var additionalMetadata = GetProperty>(nameof(PSResourceInfo.AdditionalMetadata), psObjectInfo); + Version version = GetVersionInfo(psObjectInfo, additionalMetadata, out string prereleaseLabel); + + psGetInfo = new PSResourceInfo + { + AdditionalMetadata = additionalMetadata, + Author = GetStringProperty(nameof(PSResourceInfo.Author), psObjectInfo), + CompanyName = GetStringProperty(nameof(PSResourceInfo.CompanyName), psObjectInfo), + Copyright = GetStringProperty(nameof(PSResourceInfo.Copyright), psObjectInfo), + Dependencies = GetDependencies(GetProperty(nameof(PSResourceInfo.Dependencies), psObjectInfo)), + Description = GetStringProperty(nameof(PSResourceInfo.Description), psObjectInfo), + IconUri = GetProperty(nameof(PSResourceInfo.IconUri), psObjectInfo), + Includes = new ResourceIncludes(GetProperty(nameof(PSResourceInfo.Includes), psObjectInfo)), + InstalledDate = GetProperty(nameof(PSResourceInfo.InstalledDate), psObjectInfo), + InstalledLocation = GetStringProperty(nameof(PSResourceInfo.InstalledLocation), psObjectInfo), + IsPrerelease = GetProperty(nameof(PSResourceInfo.IsPrerelease), psObjectInfo), + LicenseUri = GetProperty(nameof(PSResourceInfo.LicenseUri), psObjectInfo), + Name = GetStringProperty(nameof(PSResourceInfo.Name), psObjectInfo), + PackageManagementProvider = GetStringProperty(nameof(PSResourceInfo.PackageManagementProvider), psObjectInfo), + PowerShellGetFormatVersion = GetStringProperty(nameof(PSResourceInfo.PowerShellGetFormatVersion), psObjectInfo), + PrereleaseLabel = prereleaseLabel, + ProjectUri = GetProperty(nameof(PSResourceInfo.ProjectUri), psObjectInfo), + PublishedDate = GetProperty(nameof(PSResourceInfo.PublishedDate), psObjectInfo), + ReleaseNotes = GetStringProperty(nameof(PSResourceInfo.ReleaseNotes), psObjectInfo), + Repository = GetStringProperty(nameof(PSResourceInfo.Repository), psObjectInfo), + RepositorySourceLocation = GetStringProperty(nameof(PSResourceInfo.RepositorySourceLocation), psObjectInfo), + Tags = Utils.GetStringArray(GetProperty(nameof(PSResourceInfo.Tags), psObjectInfo)), + // try to get the value of PSResourceInfo.Type property, if the value is null use ResourceType.Module as value + // this value will be used in Enum.TryParse. If Enum.TryParse returns false, use ResourceType.Module to set Type instead. + Type = Enum.TryParse( + GetProperty(nameof(PSResourceInfo.Type), psObjectInfo) ?? nameof(ResourceType.Module), + out ResourceType currentReadType) + ? currentReadType : ResourceType.Module, + UpdatedDate = GetProperty(nameof(PSResourceInfo.UpdatedDate), psObjectInfo), + Version = version + }; + + return true; + } + catch(Exception ex) + { + errorMsg = string.Format( + CultureInfo.InvariantCulture, + @"TryReadPSGetInfo: Cannot read the PowerShellGet information file with error: {0}", + ex.Message); + + return false; + } + } + + private static string GetStringProperty( + string name, + PSObject psObjectInfo) + { + return GetProperty(name, psObjectInfo) ?? string.Empty; + } + + private static Version GetVersionInfo( + PSObject psObjectInfo, + Dictionary additionalMetadata, + out string prereleaseLabel) + { + string versionString = GetProperty(nameof(PSResourceInfo.Version), psObjectInfo); + prereleaseLabel = String.Empty; + + if (!String.IsNullOrEmpty(versionString) || + additionalMetadata.TryGetValue("NormalizedVersion", out versionString)) + { + string pkgVersion = versionString; + if (versionString.Contains("-")) + { + string[] versionStringParsed = versionString.Split('-'); + if (versionStringParsed.Length == 1) + { + pkgVersion = versionStringParsed[0]; + } + else + { + // versionStringParsed.Length > 1 (because string contained '-' so couldn't be 0) + pkgVersion = versionStringParsed[0]; + prereleaseLabel = versionStringParsed[1]; + } + } + + if (!Version.TryParse(pkgVersion, out Version parsedVersion)) + { + prereleaseLabel = String.Empty; + return null; + } + else + { + return parsedVersion; + } + } + + prereleaseLabel = String.Empty; + return GetProperty(nameof(PSResourceInfo.Version), psObjectInfo); + } + + + public static bool TryConvert( + IPackageSearchMetadata metadataToParse, + out PSResourceInfo psGetInfo, + string repositoryName, + ResourceType? type, + out string errorMsg) + { + psGetInfo = null; + errorMsg = String.Empty; + + if (metadataToParse == null) + { + errorMsg = "TryConvertPSResourceInfo: Invalid IPackageSearchMetadata object. Object cannot be null."; + return false; + } + + try + { + psGetInfo = new PSResourceInfo + { + // not all of the properties of PSResourceInfo are filled as they are not there in metadata returned for Find-PSResource. + Author = ParseMetadataAuthor(metadataToParse), + Dependencies = ParseMetadataDependencies(metadataToParse), + Description = ParseMetadataDescription(metadataToParse), + IconUri = ParseMetadataIconUri(metadataToParse), + IsPrerelease = ParseMetadataIsPrerelease(metadataToParse), + LicenseUri = ParseMetadataLicenseUri(metadataToParse), + Name = ParseMetadataName(metadataToParse), + PrereleaseLabel = ParsePrerelease(metadataToParse), + ProjectUri = ParseMetadataProjectUri(metadataToParse), + PublishedDate = ParseMetadataPublishedDate(metadataToParse), + Repository = repositoryName, + Tags = ParseMetadataTags(metadataToParse), + Type = ParseMetadataType(metadataToParse, repositoryName, type), + Version = ParseMetadataVersion(metadataToParse) + }; + + return true; + } + catch (Exception ex) + { + errorMsg = string.Format( + CultureInfo.InvariantCulture, + @"TryReadPSGetInfo: Cannot parse PSResourceInfo from IPackageSearchMetadata with error: {0}", + ex.Message); + return false; + } + } + + #endregion + + #region Private static methods + + private static T ConvertToType(PSObject psObject) + { + // We only convert Dictionary types. + if (typeof(T) != typeof(Dictionary)) + { + return default(T); + } + + var dict = new Dictionary(); + foreach (var prop in psObject.Properties) + { + dict.Add(prop.Name, prop.Value.ToString()); + } + + return (T)Convert.ChangeType(dict, typeof(T)); + } + + private static T GetProperty( + string Name, + PSObject psObjectInfo) + { + var val = psObjectInfo.Properties[Name]?.Value; + if (val == null) + { + return default(T); + } + + switch (val) + { + case T valType: + return valType; + + case PSObject valPSObject: + switch (valPSObject.BaseObject) + { + case T valBase: + return valBase; + + case PSCustomObject _: + // A base object of PSCustomObject means this is additional metadata + // and type T should be Dictionary. + return ConvertToType(valPSObject); + + default: + return default(T); + } + + default: + return default(T); + } + } + + private static string GetPrereleaseLabel(Version version) + { + string versionAsString = version.ToString(); + + if (!versionAsString.Contains("-")) + { + // no prerelease label present + return String.Empty; + } + + string[] prereleaseParsed = versionAsString.Split('-'); + if (prereleaseParsed.Length <= 1) + { + return String.Empty; + } + + string prereleaseString = prereleaseParsed[1]; + Regex prereleasePattern = new Regex("^[a-zA-Z0-9]+$"); + if (!prereleasePattern.IsMatch(prereleaseString)) + { + return String.Empty; + } + + return prereleaseString; + } + + private static Dependency[] GetDependencies(ArrayList dependencyInfos) + { + List dependenciesFound = new List(); + if (dependencyInfos == null) { return dependenciesFound.ToArray(); } + + + foreach(PSObject dependencyObj in dependencyInfos) { // can be an array or hashtable - if (dependencyObj.BaseObject is Hashtable dependencyInfo) + if (dependencyObj.BaseObject is Hashtable dependencyInfo) { if (!dependencyInfo.ContainsKey("Name")) { @@ -608,7 +608,7 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) // neither Required, Minimum or Maximum Version provided VersionRange dependencyVersionRangeAll = VersionRange.All; - dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRangeAll)); + dependenciesFound.Add(new Dependency(dependencyName, dependencyVersionRangeAll)); } else if (dependencyObj.Properties["Name"] != null) { @@ -625,256 +625,256 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) dependenciesFound.Add(new Dependency(name, versionRange)); } - } - - return dependenciesFound.ToArray(); - } - - private static string ConcatenateVersionWithPrerelease(string version, string prerelease) - { - // if no prerelease, just version suffices - if (String.IsNullOrEmpty(prerelease)) - { - return version; - } - - int numVersionDigits = version.Split('.').Count(); - if (numVersionDigits == 3) - { - // 0.5.3 -> version string , preview4 -> prerelease string , return: 5.3.0-preview4 - return version + "-" + prerelease; - } - - - // number of digits not equivalent to 3 was not supported in V2 - return version; - } - - - #region Parse Metadata private static methods - - private static string ParseMetadataAuthor(IPackageSearchMetadata pkg) - { - return pkg.Authors; - } - - private static Dependency[] ParseMetadataDependencies(IPackageSearchMetadata pkg) - { - List dependencies = new List(); - foreach(var pkgDependencyGroup in pkg.DependencySets) - { - foreach(var pkgDependencyItem in pkgDependencyGroup.Packages) - { - // check if version range is not null. In case we have package with dependency but no version specified - VersionRange depVersionRange; - if (pkgDependencyItem.VersionRange == null) - { - depVersionRange = VersionRange.All; - } - else - { - depVersionRange = pkgDependencyItem.VersionRange; - } - - Dependency currentDependency = new Dependency(pkgDependencyItem.Id, depVersionRange); - dependencies.Add(currentDependency); - } - } - return dependencies.ToArray(); - } - - private static string ParseMetadataDescription(IPackageSearchMetadata pkg) - { - return pkg.Description; - } - - private static Uri ParseMetadataIconUri(IPackageSearchMetadata pkg) - { - return pkg.IconUrl; - } - - private static bool ParseMetadataIsPrerelease(IPackageSearchMetadata pkg) - { - return pkg.Identity.Version.IsPrerelease; - } - - private static Uri ParseMetadataLicenseUri(IPackageSearchMetadata pkg) - { - return pkg.LicenseUrl; - } - - private static string ParseMetadataName(IPackageSearchMetadata pkg) - { - return pkg.Identity?.Id ?? string.Empty; - } - - private static string ParsePrerelease(IPackageSearchMetadata pkg) - { - return pkg.Identity.Version.ReleaseLabels.Count() == 0 ? - String.Empty : - pkg.Identity.Version.ReleaseLabels.FirstOrDefault(); - } - - private static Uri ParseMetadataProjectUri(IPackageSearchMetadata pkg) - { - return pkg.ProjectUrl; - } - - private static DateTime? ParseMetadataPublishedDate(IPackageSearchMetadata pkg) - { - DateTime? publishDate = null; - DateTimeOffset? pkgPublishedDate = pkg.Published; - if (pkgPublishedDate.HasValue) - { - publishDate = pkgPublishedDate.Value.DateTime; - } - return publishDate; - } - - private static string[] ParseMetadataTags(IPackageSearchMetadata pkg) - { - return pkg.Tags.Split(Delimeter, StringSplitOptions.RemoveEmptyEntries); - } - - private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, string repoName, ResourceType? pkgType) - { - // possible type combinations: - // M, C - // M, D - // M - // S - - string[] tags = ParseMetadataTags(pkg); - ResourceType currentPkgType = ResourceType.Module; - - // Check if package came from PSGalleryScripts repo- this indicates that it should have a PSScript tag - // (however some packages that had a wildcard in their name are missing PSScript or PSModule tags) - // but we were able to get the packages by using SearchAsync() with the appropriate Script or Module repository endpoint - // and can check repository endpoint to determine Type. - // Module packages missing tags are accounted for as the default case, and we account for scripts with the following check: - if ((pkgType == null && String.Equals("PSGalleryScripts", repoName, StringComparison.InvariantCultureIgnoreCase)) || - (pkgType != null && pkgType == ResourceType.Script)) - { - // it's a Script resource, so clear default Module tag because a Script resource cannot also be a Module resource - currentPkgType &= ~ResourceType.Module; - currentPkgType |= ResourceType.Script; - } - - // if Name contains wildcard, currently Script and Module tags should be set properly, but need to account for Command and DscResource types too - // if Name does not contain wildcard, GetMetadataAsync() was used, PSGallery only is searched (and pkg will successfully be found - // and returned from there) before PSGalleryScripts can be searched - foreach(string tag in tags) - { - if(String.Equals(tag, "PSScript", StringComparison.InvariantCultureIgnoreCase)) - { - // clear default Module tag, because a Script resource cannot be a Module resource also - currentPkgType &= ~ResourceType.Module; - currentPkgType |= ResourceType.Script; - } - if (tag.StartsWith("PSCommand_")) - { - currentPkgType |= ResourceType.Command; - } - if (String.Equals(tag, "PSIncludes_DscResource", StringComparison.InvariantCultureIgnoreCase)) - { - currentPkgType |= ResourceType.DscResource; - } - } - return currentPkgType; - } - - private static Version ParseMetadataVersion(IPackageSearchMetadata pkg) - { - if (pkg.Identity != null) - { - return pkg.Identity.Version.Version; - } - return null; - } - - #endregion - - #endregion - - #region Private methods - - private PSObject ConvertToCustomObject() - { - var additionalMetadata = new PSObject(); - - // Need to add a null check here due to null ref exception getting thrown - if (AdditionalMetadata != null) - { - foreach (var item in AdditionalMetadata) - { - additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); - } - - } - var psObject = new PSObject(); - psObject.Properties.Add(new PSNoteProperty(nameof(Name), Name ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Version), ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel))); - psObject.Properties.Add(new PSNoteProperty(nameof(Type), Type)); - psObject.Properties.Add(new PSNoteProperty(nameof(Description), Description ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Author), Author ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(CompanyName), CompanyName ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Copyright), Copyright ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(PublishedDate), PublishedDate)); - psObject.Properties.Add(new PSNoteProperty(nameof(InstalledDate), InstalledDate)); - psObject.Properties.Add(new PSNoteProperty(nameof(UpdatedDate), UpdatedDate)); - psObject.Properties.Add(new PSNoteProperty(nameof(LicenseUri), LicenseUri)); - psObject.Properties.Add(new PSNoteProperty(nameof(ProjectUri), ProjectUri)); - psObject.Properties.Add(new PSNoteProperty(nameof(IconUri), IconUri)); - psObject.Properties.Add(new PSNoteProperty(nameof(Tags), Tags)); - psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes != null ? Includes.ConvertToHashtable() : null)); - psObject.Properties.Add(new PSNoteProperty(nameof(PowerShellGetFormatVersion), PowerShellGetFormatVersion ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(ReleaseNotes), ReleaseNotes ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Dependencies), Dependencies)); - psObject.Properties.Add(new PSNoteProperty(nameof(RepositorySourceLocation), RepositorySourceLocation ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Repository), Repository ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(PackageManagementProvider), PackageManagementProvider ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(AdditionalMetadata), additionalMetadata)); - psObject.Properties.Add(new PSNoteProperty(nameof(InstalledLocation), InstalledLocation ?? string.Empty)); - - return psObject; - } - - #endregion - } - - #endregion - - #region Test Hooks - - public static class TestHooks - { - public static PSObject ReadPSGetResourceInfo(string filePath) - { - if (PSResourceInfo.TryRead(filePath, out PSResourceInfo psGetInfo, out string errorMsg)) - { - return PSObject.AsPSObject(psGetInfo); - } - - throw new PSInvalidOperationException(errorMsg); - } - - public static void WritePSGetResourceInfo( - string filePath, - PSObject psObjectGetInfo) - { - if (psObjectGetInfo.BaseObject is PSResourceInfo psGetInfo) - { - if (! psGetInfo.TryWrite(filePath, out string errorMsg)) - { - throw new PSInvalidOperationException(errorMsg); - } - - return; - } - - throw new PSArgumentException("psObjectGetInfo argument is not a PSGetResourceInfo type."); - } - } - - #endregion -} + } + + return dependenciesFound.ToArray(); + } + + private static string ConcatenateVersionWithPrerelease(string version, string prerelease) + { + // if no prerelease, just version suffices + if (String.IsNullOrEmpty(prerelease)) + { + return version; + } + + int numVersionDigits = version.Split('.').Count(); + if (numVersionDigits == 3) + { + // 0.5.3 -> version string , preview4 -> prerelease string , return: 5.3.0-preview4 + return version + "-" + prerelease; + } + + + // number of digits not equivalent to 3 was not supported in V2 + return version; + } + + + #region Parse Metadata private static methods + + private static string ParseMetadataAuthor(IPackageSearchMetadata pkg) + { + return pkg.Authors; + } + + private static Dependency[] ParseMetadataDependencies(IPackageSearchMetadata pkg) + { + List dependencies = new List(); + foreach(var pkgDependencyGroup in pkg.DependencySets) + { + foreach(var pkgDependencyItem in pkgDependencyGroup.Packages) + { + // check if version range is not null. In case we have package with dependency but no version specified + VersionRange depVersionRange; + if (pkgDependencyItem.VersionRange == null) + { + depVersionRange = VersionRange.All; + } + else + { + depVersionRange = pkgDependencyItem.VersionRange; + } + + Dependency currentDependency = new Dependency(pkgDependencyItem.Id, depVersionRange); + dependencies.Add(currentDependency); + } + } + return dependencies.ToArray(); + } + + private static string ParseMetadataDescription(IPackageSearchMetadata pkg) + { + return pkg.Description; + } + + private static Uri ParseMetadataIconUri(IPackageSearchMetadata pkg) + { + return pkg.IconUrl; + } + + private static bool ParseMetadataIsPrerelease(IPackageSearchMetadata pkg) + { + return pkg.Identity?.Version?.IsPrerelease ?? false; + } + + private static Uri ParseMetadataLicenseUri(IPackageSearchMetadata pkg) + { + return pkg.LicenseUrl; + } + + private static string ParseMetadataName(IPackageSearchMetadata pkg) + { + return pkg.Identity?.Id ?? string.Empty; + } + + private static string ParsePrerelease(IPackageSearchMetadata pkg) + { + return pkg.Identity.Version.ReleaseLabels.Count() == 0 ? + String.Empty : + pkg.Identity.Version.ReleaseLabels.FirstOrDefault(); + } + + private static Uri ParseMetadataProjectUri(IPackageSearchMetadata pkg) + { + return pkg.ProjectUrl; + } + + private static DateTime? ParseMetadataPublishedDate(IPackageSearchMetadata pkg) + { + DateTime? publishDate = null; + DateTimeOffset? pkgPublishedDate = pkg.Published; + if (pkgPublishedDate.HasValue) + { + publishDate = pkgPublishedDate.Value.DateTime; + } + return publishDate; + } + + private static string[] ParseMetadataTags(IPackageSearchMetadata pkg) + { + return pkg.Tags.Split(Delimeter, StringSplitOptions.RemoveEmptyEntries); + } + + private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, string repoName, ResourceType? pkgType) + { + // possible type combinations: + // M, C + // M, D + // M + // S + + string[] tags = ParseMetadataTags(pkg); + ResourceType currentPkgType = ResourceType.Module; + + // Check if package came from PSGalleryScripts repo- this indicates that it should have a PSScript tag + // (however some packages that had a wildcard in their name are missing PSScript or PSModule tags) + // but we were able to get the packages by using SearchAsync() with the appropriate Script or Module repository endpoint + // and can check repository endpoint to determine Type. + // Module packages missing tags are accounted for as the default case, and we account for scripts with the following check: + if ((pkgType == null && String.Equals("PSGalleryScripts", repoName, StringComparison.InvariantCultureIgnoreCase)) || + (pkgType != null && pkgType == ResourceType.Script)) + { + // it's a Script resource, so clear default Module tag because a Script resource cannot also be a Module resource + currentPkgType &= ~ResourceType.Module; + currentPkgType |= ResourceType.Script; + } + + // if Name contains wildcard, currently Script and Module tags should be set properly, but need to account for Command and DscResource types too + // if Name does not contain wildcard, GetMetadataAsync() was used, PSGallery only is searched (and pkg will successfully be found + // and returned from there) before PSGalleryScripts can be searched + foreach(string tag in tags) + { + if(String.Equals(tag, "PSScript", StringComparison.InvariantCultureIgnoreCase)) + { + // clear default Module tag, because a Script resource cannot be a Module resource also + currentPkgType &= ~ResourceType.Module; + currentPkgType |= ResourceType.Script; + } + if (tag.StartsWith("PSCommand_")) + { + currentPkgType |= ResourceType.Command; + } + if (String.Equals(tag, "PSIncludes_DscResource", StringComparison.InvariantCultureIgnoreCase)) + { + currentPkgType |= ResourceType.DscResource; + } + } + return currentPkgType; + } + + private static Version ParseMetadataVersion(IPackageSearchMetadata pkg) + { + if (pkg.Identity != null) + { + return pkg.Identity.Version.Version; + } + return null; + } + + #endregion + + #endregion + + #region Private methods + + private PSObject ConvertToCustomObject() + { + var additionalMetadata = new PSObject(); + + // Need to add a null check here due to null ref exception getting thrown + if (AdditionalMetadata != null) + { + foreach (var item in AdditionalMetadata) + { + additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); + } + + } + var psObject = new PSObject(); + psObject.Properties.Add(new PSNoteProperty(nameof(Name), Name ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(Version), ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel))); + psObject.Properties.Add(new PSNoteProperty(nameof(Type), Type)); + psObject.Properties.Add(new PSNoteProperty(nameof(Description), Description ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(Author), Author ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(CompanyName), CompanyName ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(Copyright), Copyright ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(PublishedDate), PublishedDate)); + psObject.Properties.Add(new PSNoteProperty(nameof(InstalledDate), InstalledDate)); + psObject.Properties.Add(new PSNoteProperty(nameof(UpdatedDate), UpdatedDate)); + psObject.Properties.Add(new PSNoteProperty(nameof(LicenseUri), LicenseUri)); + psObject.Properties.Add(new PSNoteProperty(nameof(ProjectUri), ProjectUri)); + psObject.Properties.Add(new PSNoteProperty(nameof(IconUri), IconUri)); + psObject.Properties.Add(new PSNoteProperty(nameof(Tags), Tags)); + psObject.Properties.Add(new PSNoteProperty(nameof(Includes), Includes != null ? Includes.ConvertToHashtable() : null)); + psObject.Properties.Add(new PSNoteProperty(nameof(PowerShellGetFormatVersion), PowerShellGetFormatVersion ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(ReleaseNotes), ReleaseNotes ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(Dependencies), Dependencies)); + psObject.Properties.Add(new PSNoteProperty(nameof(RepositorySourceLocation), RepositorySourceLocation ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(Repository), Repository ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(PackageManagementProvider), PackageManagementProvider ?? string.Empty)); + psObject.Properties.Add(new PSNoteProperty(nameof(AdditionalMetadata), additionalMetadata)); + psObject.Properties.Add(new PSNoteProperty(nameof(InstalledLocation), InstalledLocation ?? string.Empty)); + + return psObject; + } + + #endregion + } + + #endregion + + #region Test Hooks + + public static class TestHooks + { + public static PSObject ReadPSGetResourceInfo(string filePath) + { + if (PSResourceInfo.TryRead(filePath, out PSResourceInfo psGetInfo, out string errorMsg)) + { + return PSObject.AsPSObject(psGetInfo); + } + + throw new PSInvalidOperationException(errorMsg); + } + + public static void WritePSGetResourceInfo( + string filePath, + PSObject psObjectGetInfo) + { + if (psObjectGetInfo.BaseObject is PSResourceInfo psGetInfo) + { + if (! psGetInfo.TryWrite(filePath, out string errorMsg)) + { + throw new PSInvalidOperationException(errorMsg); + } + + return; + } + + throw new PSArgumentException("psObjectGetInfo argument is not a PSGetResourceInfo type."); + } + } + + #endregion +} From f80c3f842a04ddd2678856d544be9eb513ef689c Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 9 Jul 2021 18:22:14 -0700 Subject: [PATCH 30/62] Address some code review suggestions --- src/code/InstallHelper.cs | 111 +++++++++++++++++----------------- src/code/InstallPSResource.cs | 4 +- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index d52b9458f..988b2ee6e 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -28,10 +28,10 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// internal class InstallHelper : PSCmdlet { - private CancellationToken cancellationToken; - private readonly bool update; - private readonly bool save; - private readonly PSCmdlet cmdletPassedIn; + private CancellationToken _cancellationToken; + private readonly bool _updatePkg; + private readonly bool _savePkg; + private readonly PSCmdlet _cmdletPassedIn; List _pathsToInstallPkg; VersionRange _versionRange; bool _prerelease; @@ -47,15 +47,15 @@ internal class InstallHelper : PSCmdlet bool _includeXML; List _pathsToSearch; - public InstallHelper(bool update, bool save, CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) + public InstallHelper(bool updatePkg, bool savePkg, CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) { - this.update = update; - this.save = save; - this.cancellationToken = cancellationToken; - this.cmdletPassedIn = cmdletPassedIn; + this._updatePkg = updatePkg; + this._savePkg = savePkg; + this._cancellationToken = cancellationToken; + this._cmdletPassedIn = cmdletPassedIn; } - public void ProcessInstallParams( + public void InstallPackages( string[] names, VersionRange versionRange, bool prerelease, @@ -75,7 +75,7 @@ public void ProcessInstallParams( bool includeXML, List pathsToInstallPkg) { - cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + + _cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';", string.Join(",", names), (_versionRange != null ? _versionRange.OriginalString : string.Empty), @@ -100,8 +100,6 @@ public void ProcessInstallParams( _asNupkg = asNupkg; _includeXML = includeXML; _pathsToInstallPkg = pathsToInstallPkg; - - IEnumerable pkgsAlreadyInstalled = new List(); // Go through the repositories and see which is the first repository to have the pkg version available ProcessRepositories(names, repository, _trustRepository, _credential); @@ -124,19 +122,19 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var sourceTrusted = false; string repoName = repo.Name; - cmdletPassedIn.WriteDebug(string.Format("Attempting to search for packages in '{0}'", repoName)); + _cmdletPassedIn.WriteDebug(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. if (repo.Trusted == false && !trustRepository && !_force) { - cmdletPassedIn.WriteDebug("Checking if untrusted repository should be used"); + _cmdletPassedIn.WriteDebug("Checking if untrusted repository should be used"); if (!(yesToAll || noToAll)) { // Prompt for installation of package from untrusted repository var message = string.Format(CultureInfo.InvariantCulture, queryInstallUntrustedPackage, repoName); - sourceTrusted = cmdletPassedIn.ShouldContinue(message, repositoryIsNotTrusted, true, ref yesToAll, ref noToAll); + sourceTrusted = _cmdletPassedIn.ShouldContinue(message, repositoryIsNotTrusted, true, ref yesToAll, ref noToAll); } } else { @@ -145,13 +143,12 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool if (sourceTrusted || yesToAll) { - cmdletPassedIn.WriteDebug("Untrusted repository accepted as trusted source."); + _cmdletPassedIn.WriteDebug("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.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter); - var cancellationToken = new CancellationToken(); - var findHelper = new FindHelper(cancellationToken, cmdletPassedIn); + var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn); // Finds parent packages and dependencies IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( name: packageNames, @@ -175,7 +172,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool if (!pkgsFromRepoToInstall.Any()) { - cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); + _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); // Check in the next repository continue; } @@ -212,7 +209,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable(); - GetHelper getHelper = new GetHelper(cmdletPassedIn); + GetHelper getHelper = new GetHelper(_cmdletPassedIn); // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) foreach (var path in _pathsToInstallPkg) { @@ -226,7 +223,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable InstallPackage(IEnumerable pkgsToInstall, s // TODO: are there Linux accommodations we need to consider here? dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly; - cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Name)); + _cmdletPassedIn.WriteVerbose(string.Format("Begin installing package: '{0}'", p.Name)); - if (!_quiet && !save) + if (!_quiet && !_savePkg) { // todo: UPDATE PROGRESS BAR CallProgressBar(p); @@ -270,7 +267,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s if (!NuGetVersion.TryParse(createFullVersion, out NuGetVersion pkgVersion)) { - cmdletPassedIn.WriteDebug("Error parsing version into a NuGetVersion"); + _cmdletPassedIn.WriteDebug("Error parsing version into a NuGetVersion"); } var pkgIdentity = new PackageIdentity(p.Name, pkgVersion); var cacheContext = new SourceCacheContext(); @@ -287,7 +284,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s downloadContext: new PackageDownloadContext(cacheContext), globalPackagesFolder: tempInstallPath, logger: NullLogger.Instance, - token: CancellationToken.None).GetAwaiter().GetResult(); + token: _cancellationToken).GetAwaiter().GetResult(); if (_asNupkg) // this is Save functionality { @@ -311,7 +308,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s packageFiles: result.PackageReader.GetFiles(), extractFile: (new PackageFileExtractor(result.PackageReader.GetFiles(), packageExtractionContext.XmlDocFileSaveMode)).ExtractPackageFile, logger: NullLogger.Instance, - token: CancellationToken.None); + token: _cancellationToken); result.Dispose(); } } @@ -337,11 +334,11 @@ private List InstallPackage(IEnumerable pkgsToInstall, s downloadContext: new PackageDownloadContext(cacheContext), globalPackagesFolder: tempInstallPath, logger: NullLogger.Instance, - token: CancellationToken.None).GetAwaiter().GetResult(); + token: _cancellationToken).GetAwaiter().GetResult(); } catch (Exception e) { - cmdletPassedIn.WriteDebug(string.Format("Error attempting download: '{0}'", e.Message)); + _cmdletPassedIn.WriteDebug(string.Format("Error attempting download: '{0}'", e.Message)); } finally { @@ -367,7 +364,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } } - cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); + _cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); // Prompt if module requires license acceptance (need to read info license acceptance info from the module manifest) // pkgIdentity.Version.Version gets the version without metadata or release labels. @@ -393,7 +390,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s moduleManifestVersion = parsedMetadataHashtable["ModuleVersion"] as string; // Accept License verification - if (!save && !CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion)) + if (!_savePkg && !CallAcceptLicense(p, moduleManifest, tempInstallPath, newVersion)) { continue; } @@ -411,7 +408,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); - if (save) + if (_savePkg) { //TODO: SavePackage } @@ -421,17 +418,17 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } - cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}'", p.Name)); + _cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}'", p.Name)); pkgsSuccessfullyInstalled.Add(p.Name); } catch (Exception e) { - cmdletPassedIn.WriteDebug(string.Format("Unable to successfully install package '{0}': '{1}'", p.Name, e.Message)); + _cmdletPassedIn.WriteDebug(string.Format("Unable to successfully install package '{0}': '{1}'", p.Name, e.Message)); } finally { // Delete the temp directory and all its contents - cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", tempInstallPath)); + _cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", tempInstallPath)); if (Directory.Exists(tempInstallPath)) { Directory.Delete(tempInstallPath, true); @@ -481,7 +478,7 @@ private void CallProgressBar(PSResourceInfo p) } */ var progressRecord = new ProgressRecord(activityId, activity, statusDescription); - cmdletPassedIn.WriteProgress(progressRecord); + _cmdletPassedIn.WriteProgress(progressRecord); } private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string tempInstallPath, string newVersion) @@ -523,7 +520,7 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var ex = new ArgumentException(exMessage); var acceptLicenseError = new ErrorRecord(ex, "LicenseTxtNotFound", ErrorCategory.ObjectNotFound, null); - cmdletPassedIn.WriteError(acceptLicenseError); + _cmdletPassedIn.WriteError(acceptLicenseError); success = false; } @@ -535,7 +532,7 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var title = "License Acceptance"; var yesToAll = false; var noToAll = false; - var shouldContinueResult = cmdletPassedIn.ShouldContinue(message, title, true, ref yesToAll, ref noToAll); + var shouldContinueResult = _cmdletPassedIn.ShouldContinue(message, title, true, ref yesToAll, ref noToAll); if (shouldContinueResult || yesToAll) { @@ -550,7 +547,7 @@ private bool CallAcceptLicense(PSResourceInfo p, string moduleManifest, string t var ex = new ArgumentException(message); var acceptLicenseError = new ErrorRecord(ex, "ForceAcceptLicense", ErrorCategory.InvalidArgument, null); - cmdletPassedIn.WriteError(acceptLicenseError); + _cmdletPassedIn.WriteError(acceptLicenseError); success = false; } } @@ -593,32 +590,32 @@ private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgId // Unforunately have to check if each file exists because it may or may not be there if (File.Exists(nupkgSHAToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgSHAToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgSHAToDelete)); File.Delete(nupkgSHAToDelete); } if (File.Exists(nuspecToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nuspecToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nuspecToDelete)); File.Delete(nuspecToDelete); } if (File.Exists(nupkgToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", nupkgToDelete)); File.Delete(nupkgToDelete); } if (File.Exists(contentTypesToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", contentTypesToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", contentTypesToDelete)); File.Delete(contentTypesToDelete); } if (Directory.Exists(relsDirToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", relsDirToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", relsDirToDelete)); Directory.Delete(relsDirToDelete, true); } if (Directory.Exists(packageDirToDelete)) { - cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", packageDirToDelete)); + _cmdletPassedIn.WriteDebug(string.Format("Deleting '{0}'", packageDirToDelete)); Directory.Delete(packageDirToDelete, true); } } @@ -628,36 +625,36 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLo // Creating the proper installation path depending on whether pkg is a module or script var newPathParent = isScript ? installPath : Path.Combine(installPath, p.Name); var finalModuleVersionDir = isScript ? installPath : Path.Combine(installPath, p.Name, moduleManifestVersion); // versionWithoutPrereleaseTag - cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", finalModuleVersionDir)); + _cmdletPassedIn.WriteDebug(string.Format("Installation path is: '{0}'", finalModuleVersionDir)); // If script, just move the files over, if module, move the version directory over var tempModuleVersionDir = (isScript || isLocalRepo) ? dirNameVersion : Path.Combine(tempInstallPath, p.Name.ToLower(), newVersion); - cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); + _cmdletPassedIn.WriteVerbose(string.Format("Full installation path is: '{0}'", tempModuleVersionDir)); if (isScript) { // Need to delete old xml files because there can only be 1 per script var scriptXML = p.Name + "_InstalledScriptInfo.xml"; - cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))); + _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))); if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))) { - cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); + _cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); } - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML))); + _cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML))); File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); // Need to delete old script file, if that exists - cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")))); + _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")))); if (File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))) { - cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); + _cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); File.Delete(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); } - cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))); + _cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))); File.Move(scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); } else @@ -665,23 +662,23 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLo // If new path does not exist if (!Directory.Exists(newPathParent)) { - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); + _cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.CreateDirectory(newPathParent); Directory.Move(tempModuleVersionDir, finalModuleVersionDir); } else { - cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); + _cmdletPassedIn.WriteDebug(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); // At this point if if (Directory.Exists(finalModuleVersionDir)) { // Delete the directory path before replacing it with the new module - cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", finalModuleVersionDir)); + _cmdletPassedIn.WriteDebug(string.Format("Attempting to delete '{0}'", finalModuleVersionDir)); Directory.Delete(finalModuleVersionDir, true); } - cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); + _cmdletPassedIn.WriteDebug(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); Directory.Move(tempModuleVersionDir, finalModuleVersionDir); } } diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index a70b8648b..80b7da397 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -168,12 +168,12 @@ protected override void ProcessRecord() CancellationTokenSource source = new CancellationTokenSource(); CancellationToken cancellationToken = source.Token; - var installHelper = new InstallHelper(update: false, save: false, cancellationToken: cancellationToken, cmdletPassedIn: this); + var installHelper = new InstallHelper(updatePkg: false, savePkg: false, cancellationToken: cancellationToken, cmdletPassedIn: this); switch (ParameterSetName) { case NameParameterSet: - installHelper.ProcessInstallParams( + installHelper.InstallPackages( names: Name, versionRange: _versionRange, prerelease: Prerelease, From fb9de861216d6fab975975ae3ebb34e022c11fdd Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Sun, 11 Jul 2021 17:41:52 -0700 Subject: [PATCH 31/62] Add comments and incorporate other code review changes --- src/code/InstallHelper.cs | 70 ++++++++++++++++----------------------- src/code/Utils.cs | 8 ++--- 2 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 802c0bec3..f99785991 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -45,7 +45,6 @@ internal class InstallHelper : PSCmdlet string _specifiedPath; bool _asNupkg; bool _includeXML; - List _pathsToSearch; public InstallHelper(bool updatePkg, bool savePkg, CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) { @@ -185,16 +184,16 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall); } - if (!pkgsFromRepoToInstall.Any()) continue; + if (!pkgsFromRepoToInstall.Any()) + { + continue; + } List pkgsInstalled = InstallPackage(pkgsFromRepoToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); foreach (string name in pkgsInstalled) { - if (packagesToInstall.Contains(name)) - { - packagesToInstall.Remove(name); - } + packagesToInstall.Remove(name); } } } @@ -209,7 +208,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable(); + List _pathsToSearch = new List(); GetHelper getHelper = new GetHelper(_cmdletPassedIn); // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) foreach (var path in _pathsToInstallPkg) @@ -294,24 +293,22 @@ private List InstallPackage(IEnumerable pkgsToInstall, s continue; } - else - { - // 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(); - } + + // 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 { @@ -347,21 +344,9 @@ private List InstallPackage(IEnumerable pkgsToInstall, s if (result != null) result.Dispose(); } - if (_asNupkg) // Save functionality + // Save functionality + if (_asNupkg) { - /* - // Simply move the .nupkg from the temp installation path to the specified path (the path passed in via param value) - var tempPkgIdPath = System.IO.Path.Combine(tempInstallPath, p.Identity.Id, p.Identity.Version.ToString()); - var tempPkgVersionPath = System.IO.Path.Combine(tempPkgIdPath, p.Identity.Id.ToLower() + "." + p.Identity.Version + ".nupkg"); - var newSavePath = System.IO.Path.Combine(_specifiedPath, p.Identity.Id + "." + p.Identity.Version + ".nupkg"); - - // TODO: path should be preprocessed/resolved - File.Move(tempPkgVersionPath, _specifiedPath); - - //packagesToInstall.Remove(pkgName); - - continue; - */ } } @@ -407,8 +392,11 @@ private List InstallPackage(IEnumerable pkgsToInstall, s var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); - if (_includeXML) CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); - + if (_includeXML) + { + CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); + } + if (_savePkg) { //TODO: SavePackage diff --git a/src/code/Utils.cs b/src/code/Utils.cs index b6ff456fb..7f46ad2d9 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -16,9 +16,8 @@ namespace Microsoft.PowerShell.PowerShellGet.UtilClasses { internal static class Utils { - public static void WriteVerboseOnCmdlet( - PSCmdlet cmdlet, - string message) + public static void WriteVerboseOnCmdlet( + PSCmdlet cmdlet, string message) { try { @@ -163,6 +162,7 @@ public static string GetInstalledPackageName(string pkgPath) } } + // Returns paths to all installed resources, for example: 'c:\users\johndoe\Documents\PowerShell\Modules\TestModule' public static List GetAllResourcePaths(PSCmdlet psCmdlet) { string psModulePath = Environment.GetEnvironmentVariable("PSModulePath"); @@ -235,7 +235,7 @@ public static List GetAllResourcePaths(PSCmdlet psCmdlet) return pathsToSearch; } - // Find all potential installation paths given a scope + // Returns all potential installation paths given a scope, for example: 'c:\users\johndoe\Documents\PowerShell\Modules' public static List GetAllInstallationPaths(PSCmdlet psCmdlet, ScopeType scope) { List installationPaths = new List(); From c2a5c52537816dbd056af880207bd5239d6ae8d5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Mon, 12 Jul 2021 11:49:29 -0400 Subject: [PATCH 32/62] add tests for Update --- src/code/UpdatePSResource.cs | 18 +-- test/InstallPSResource.Tests.ps1 | 68 ++++---- test/UpdatePSResource.Tests.ps1 | 267 +++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 47 deletions(-) create mode 100644 test/UpdatePSResource.Tests.ps1 diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 298927742..92246deb0 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -164,7 +164,7 @@ protected override void ProcessRecord() VersionRange versionRange = new VersionRange(); - // TODO: discuss with Paul + // TODO: discuss with Paul + then update test for incorrectly formatted version if (Version !=null && !Utils.TryParseVersionOrVersionRange(Version, out versionRange)) { WriteError(new ErrorRecord( @@ -184,20 +184,12 @@ protected override void ProcessRecord() GetHelper getHelper = new GetHelper( cmdletPassedIn: this); - // List finalNames = new List(); - // foreach (PSResourceInfo pkg in getHelper.ProcessGetParams( - // name: Name, - // versionRange: versionRange, - // pathsToSearch: Utils.GetAllResourcePaths(this))) - // { - // finalNames.Add(pkg.Name); - // } Name = getHelper.FilterPkgPaths( name: Name, versionRange: versionRange, pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); } - WriteVerbose("names after GetHelper.ProcessGetParams: " + String.Join(", ", Name)); + WriteVerbose("names after GetHelper.ProcessGetParams: " + String.Join(", ", Name)); // TODO: remove this! InstallHelper installHelper = new InstallHelper( update: true, @@ -224,9 +216,9 @@ protected override void ProcessRecord() requiredResourceFile: null, requiredResourceJson: null, requiredResourceHash: null, - specifiedPath: null, // todo: confirm - asNupkg: false, // todo: confirm - includeXML: true, // todo: confirm! + specifiedPath: null, + asNupkg: false, + includeXML: true, pathsToInstallPkg: Utils.GetAllInstallationPaths(this, String.Equals(Scope.ToString(), "None", StringComparison.InvariantCultureIgnoreCase) ? "" : Scope.ToString())); break; diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 17f926efa..e7b281e6f 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -31,36 +31,36 @@ Describe 'Test Install-PSResource for Module' { It "Install specific module resource by name" { Get-PSResourceRepository - Install-PSResource -Name "TestModule" -Repository $TestGalleryName + Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } - + It "Install specific script resource by name" { - Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName + Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "TestTestScript" - $pkg.Name | Should -Be "TestTestScript" + $pkg.Name | Should -Be "TestTestScript" $pkg.Version | Should -Be "1.3.1.0" } It "Install multiple resources by name" { $pkgNames = @("TestModule","TestModule99") - Install-PSResource -Name $pkgNames -Repository $TestGalleryName + Install-PSResource -Name $pkgNames -Repository $TestGalleryName $pkg = Get-Module $pkgNames -ListAvailable $pkg.Name | Should -Be $pkgNames } It "Should not install resource given nonexistant name" { - Install-PSResource -Name NonExistantModule -Repository $TestGalleryName + Install-PSResource -Name NonExistantModule -Repository $TestGalleryName $pkg = Get-Module "NonExistantModule" -ListAvailable $pkg.Name | Should -BeNullOrEmpty } # Do some version testing, but Find-PSResource should be doing thorough testing It "Should install resource given name and exact version" { - Install-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName + Install-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.2.0" @@ -68,22 +68,22 @@ Describe 'Test Install-PSResource for Module' { It "Should install resource given name and exact version with bracket syntax" { - Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName + Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.2.0" } It "Should install resource given name and exact range inclusive [1.0.0, 1.1.1]" { - Install-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable + Install-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.1.1" } It "Should install resource given name and exact range exclusive (1.0.0, 1.1.1)" { - Install-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable + Install-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.1" } @@ -108,7 +108,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource with latest (including prerelease) version given Prerelease parameter" { # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version - $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName + $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "TestModulePrerelease" -ListAvailable $pkg.Version | Should -Be "0.0.1" $pkg.PrivateData.PSData.Prerelease | Should -Be "preview" @@ -116,10 +116,10 @@ Describe 'Test Install-PSResource for Module' { - + It "Install a module with a dependency" { # test_module resource's latest version is a prerelease version, before that it has a non-prerelease version - $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName + $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "PSGetTestModule" -ListAvailable $pkg.Version | Should -Be "2.0.2" $pkg.PrivateData.PSData.Prerelease | Should -Be "-alpha1" @@ -129,7 +129,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } @@ -137,7 +137,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource under CurrentUser scope" { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" $pkg.Path.Contains("Documents") | Should -Be $true } @@ -146,7 +146,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource under AllUsers scope" { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" write-host $pkg.Path $pkg.Path.Contains("Program Files") | Should -Be $true } @@ -155,7 +155,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource under no specified scope" { Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" $pkg.Path.Contains("Documents") | Should -Be $true } @@ -171,7 +171,7 @@ Describe 'Test Install-PSResource for Module' { Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" Install-PSResource -Name "TestModule" -Repository $TestGalleryName -WarningVariable WarningVar -warningaction SilentlyContinue $WarningVar | Should -Not -BeNullOrEmpty } @@ -192,7 +192,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource that requires accept license with -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName -AcceptLicense $pkg = Get-InstalledPSResource "testModuleWithlicense" - $pkg.Name | Should -Be "testModuleWithlicense" + $pkg.Name | Should -Be "testModuleWithlicense" $pkg.Version | Should -Be "0.0.1.0" } @@ -200,37 +200,37 @@ Describe 'Test Install-PSResource for Module' { Set-PSResourceRepository PoshTestGallery -Trusted:$false Install-PSResource -Name "TestModule" -Repository $TestGalleryName -TrustRepository - + $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" Set-PSResourceRepository PoshTestGallery -Trusted } It "Install resource with cmdlet names from a module already installed (should clobber)" { - Install-PSResource -Name "myTestModule" -Repository $TestGalleryName + Install-PSResource -Name "myTestModule" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "myTestModule" - $pkg.Name | Should -Be "myTestModule" + $pkg.Name | Should -Be "myTestModule" $pkg.Version | Should -Be "0.0.3.0" - Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName + Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "myTestModule2" - $pkg.Name | Should -Be "myTestModule2" + $pkg.Name | Should -Be "myTestModule2" $pkg.Version | Should -Be "0.0.1.0" } <# ############ FAILING It "Install resource with -NoClobber flag (should not clobber)" { - Install-PSResource -Name "myTestModule" -Repository $TestGalleryName + Install-PSResource -Name "myTestModule" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "myTestModule" - $pkg.Name | Should -Be "myTestModule" + $pkg.Name | Should -Be "myTestModule" $pkg.Version | Should -Be "0.0.3.0" Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName -NoClobber #-WarningVariable WarningVar -warningaction SilentlyContinue $WarningVar | Should -Not -BeNullOrEmpty $pkg = Get-InstalledPSResource "myTestModule2" - $pkg.Name | Should -Be "myTestModule2" + $pkg.Name | Should -Be "myTestModule2" $pkg.Version | Should -Be "0.0.1.0" } #> @@ -240,7 +240,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource that requires accept license without -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "testModuleWithlicense" - $pkg.Name | Should -Be "testModuleWithlicense" + $pkg.Name | Should -Be "testModuleWithlicense" $pkg.Version | Should -Be "0.0.1.0" } #> @@ -255,9 +255,9 @@ Describe 'Test Install-PSResource for Module' { Set-PSResourceRepository PoshTestGallery -Trusted:$false Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false - + $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" + $pkg.Name | Should -Be "TestModule" Set-PSResourceRepository PoshTestGallery -Trusted } diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 new file mode 100644 index 000000000..9320f416b --- /dev/null +++ b/test/UpdatePSResource.Tests.ps1 @@ -0,0 +1,267 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe 'Test Install-PSResource for Module' { + + + BeforeAll{ + $TestGalleryName = Get-PoshTestGalleryName + $PSGalleryName = Get-PSGalleryName + $NuGetGalleryName = Get-NuGetGalleryName + Get-NewPSResourceRepositoryFile + Register-LocalRepos + Get-PSResourceRepository + } + + AfterEach { + Uninstall-PSResource "TestModule" + Uninstall-PSResource "TestModule99" + Uninstall-PSResource "TestModuleWithLicense" + Uninstall-PSResource "PSGetTestModule" + + } + + AfterAll { + Get-RevertPSResourceRepositoryFile + } + + It "update resource installed given Name parameter" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + + Update-PSResource -Name "TestModule" -Repository $TestGalleryName + $res = Get-InstalledPSResource -Name "TestModule" + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.0.0.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + It "update resources installed given Name (with wildcard) parameter" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + Install-PSResource -Name "TestModule99" -Version "0.0.4.0" -Repository $TestGalleryName + + Update-PSResource -Name "TestModule*" -Repository $TestGalleryName + $res = Get-InstalledPSResource -Name "TestModule*" + + $inputHashtable = @{TestModule = "1.1.0.0"; TestModule99 = "0.0.4.0"} + $isTestModuleUpdated = $false + $isTestModule99Updated = $false + foreach ($item in $res) + { + if ([System.Version]$item.Version -gt [System.Version]$inputHashtable[$item.Name]) + { + if ($item.Name -like "TestModule") + { + $isTestModuleUpdated = $true + } + elseif ($item.Name -like "TestModule99") + { + $isTestModule99Updated = $true + } + } + } + + $isTestModuleUpdated | Should -BeTrue + $isTestModule99Updated | Should -BeTrue + + } + + # It "update resources installed given Name (with wildcard) and Version (specific -supported? and with wildcard -supported?) " { + # # TODO: determine support! + # # Name (wc) + Version (specific) -> unsupported or update which ones have that version but say/write error if that version doesn't exist for that pkg + # # Name (wc) + Version ("*") -> is perhaps what's supported by default/happening by default + # # NAme (wc) + Version ("1.0.0.*") -> supported? + + # } + + It "update resource installed given Name and Version (specific) parameters" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName + $res = Get-InstalledPSResource -Name "TestModule" + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -eq [System.Version]"1.2.0.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + # It "update resource when given Name and incorrectly formatted version" { + + # } + + It "update resource with latest (including prerelease) version given Prerelease parameter" { + # PSGetTestModule resource's latest version is a prerelease version, before that it has a non-prerelease version + + Install-PSResource -Name "PSGetTestModule" -Version "1.0.2.0" -Repository $TestGalleryName + Update-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName + $res = Get-InstalledPSResource "PSGetTestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.0.2.0") + { + Write-Host $pkg.Version" and prerelease data is:"$pkg.PrivateData.PSData.Prerelease"yep that it" + # $pkg.PrereleaseLabel | Should -Be "-alpha1" (todo: for some reason get-installedpsresource doesn't get this!) + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + # Windows only + It "update resource under CurrentUser scope" { + # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the AllUsers path too + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope CurrentUser + + # TODO: use Get-InstalledPSResource + # $res = Get-InstalledPSResource -Name "TestModule" + # $res.Name | Should -Be "TestModule" + # $res.InstalledLocation.Contains("Documents") | Should -Be $true + # TODO: turn foreach loop bool return into a helper function which takes the list output of get, name and version of a package we wish to search for + $res = Get-Module "TestModule" -ListAvailable + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.Path.Contains("Documents") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + # Windows only + It "update resource under AllUsers scope" { + # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the CurrentUser path too + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope AllUsers + + # $res = Get-InstalledPSResource -Name "TestModule" + # $res.Name | Should -Be "TestModule" + # $res.InstalledLocation.Contains("Program Files") | Should -Be $true + $res = Get-Module "TestModule" -ListAvailable + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.Path.Contains("Program Files") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + # Windows only + It "update resource under no specified scope" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName + + $res = Get-Module "TestModule" -ListAvailable + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.Path.Contains("Documents") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + } + + # TODO: ask Amber if we can publish another version to TestModuleWithLicense + # It "update resource that requires accept license with -AcceptLicense flag" { + # Install-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense + # Update-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense + # $pkg = Get-InstalledPSResource "testModuleWithlicense" + + # $res = Get-Module "TestModule" -ListAvailable + + # $isPkgUpdated = $false + # foreach ($pkg in $res) + # { + # if ([System.Version]$pkg.Version -gt [System.Version]"0.0.1.0") + # { + # $isPkgUpdated = $true + # } + # } + + # $isPkgUpdated | Should -BeTrue + + # # $pkg.Name | Should -Be "testModuleWithlicense" + # # $pkg.Version | Should -Be "0.0.1.0" + # } + + It "Install resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + + Set-PSResourceRepository PoshTestGallery -Trusted:$false + + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -TrustRepository + $res = Get-InstalledPSResource -Name "TestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -BeTrue + Set-PSResourceRepository PoshTestGallery -Trusted + } + + # TODO: slightly confused about NoClobber parameter for Update...if I had already installed + # myTestModule and wanted to update it would NoClobber not allow it to update (bc this new updated version contains the same cmdlets + # as the older installed version)? + # It "update resource with cmdlet names from a module already installed (should clobber)" { + # Install-PSResource -Name "myTestModule" -Repository $TestGalleryName + # $pkg = Get-InstalledPSResource "myTestModule" + # $pkg.Name | Should -Be "myTestModule" + # $pkg.Version | Should -Be "0.0.3.0" + + # Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName + # $pkg = Get-InstalledPSResource "myTestModule2" + # $pkg.Name | Should -Be "myTestModule2" + # $pkg.Version | Should -Be "0.0.1.0" + # } + + # update script resource + # Name + # Version + # Prerelease + # Scope + # AcceptLicense + # TrustRepository + # Credential + # NoClobber + # InputObject + + +} \ No newline at end of file From 2b524654d746e075a5eb94b9e42eb1b2b3c1c0ad Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Mon, 12 Jul 2021 22:41:57 -0700 Subject: [PATCH 33/62] Add save functionality --- src/PowerShellGet.psd1 | 2 +- src/SavePSResource.cs | 182 ++++++++++++++++++++++++++++++ test/PSGetTestUtils.psm1 | 9 +- test/SavePSResource.Tests.ps1 | 203 ++++++++++++++++++++++++++++++++++ 4 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/SavePSResource.cs create mode 100644 test/SavePSResource.Tests.ps1 diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index ee9bb9f27..ae027fb76 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -16,7 +16,7 @@ 'Get-PSResourceRepository', 'Install-PSResource', 'Register-PSResourceRepository', - # 'Save-PSResource', + 'Save-PSResource', 'Set-PSResourceRepository', 'Publish-PSResource', 'Uninstall-PSResource', diff --git a/src/SavePSResource.cs b/src/SavePSResource.cs new file mode 100644 index 000000000..c317c4021 --- /dev/null +++ b/src/SavePSResource.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Threading; +using static System.Environment; + +namespace Microsoft.PowerShell.PowerShellGet.Cmdlets +{ + /// + /// The Save-PSResource cmdlet saves a resource to a machine. + /// It returns nothing. + /// + + [Cmdlet(VerbsData.Save, "PSResource", DefaultParameterSetName = "NameParameterSet", SupportsShouldProcess = true, HelpUri = "")] + public sealed + class SavePSResource : PSCmdlet + { + #region parameters + + /// + /// Specifies the exact names of resources to save from a repository. + /// A comma-separated list of module names is accepted. The resource name must match the resource name in the repository. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string[] Name { get; set; } + + /// + /// Specifies the version or version range of the package to be saved + /// + [Parameter(ParameterSetName = NameParameterSet)] + [ValidateNotNullOrEmpty] + public string Version { get; set; } + + /// + /// Specifies to allow saveing of prerelease versions + /// + [Parameter(ParameterSetName = NameParameterSet)] + public SwitchParameter Prerelease { get; set; } + + /// + /// Specifies the specific repositories to search within. + /// + [Parameter(ParameterSetName = NameParameterSet)] + // todo: add tab completion (look at get-psresourcerepository at the name parameter) + [ValidateNotNullOrEmpty] + public string[] Repository { get; set; } + + /// + /// Specifies a user account that has rights to save a resource from a specific repository. + /// + [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + public PSCredential Credential { get; set; } + + /* + /// + /// Saves as a .nupkg + /// + [Parameter()] + public SwitchParameter AsNupkg { get; set; } + + /// + /// Saves the metadata XML file with the resource + /// + [Parameter()] + public SwitchParameter IncludeXML { get; set; } + */ + + /// + /// The destination where the resource is to be installed. Works for all resource types. + /// + [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "NameParameterSet")] + [ValidateNotNullOrEmpty] + public string Path + { + get + { return _path; } + + set + { + string resolvedPath = string.Empty; + if (!string.IsNullOrEmpty(value)) + { + resolvedPath = SessionState.Path.GetResolvedPSPathFromPSPath(value).First().Path; + } + + // Path where resource is saved must be a directory + if (Directory.Exists(resolvedPath)) + { + _path = resolvedPath; + } + } + } + private string _path; + + /// + /// Suppresses being prompted for untrusted sources. + /// + [Parameter()] + public SwitchParameter TrustRepository { get; set; } + + /// + /// Used for pipeline input. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "InputObjectSet")] + [ValidateNotNullOrEmpty] + public object[] InputObject { set; get; } + #endregion + + #region members + private const string NameParameterSet = "NameParameterSet"; + private const string InputObjectSet = "InputObjectSet"; + VersionRange _versionRange; + #endregion + + #region Methods + protected override void BeginProcessing() + { + // validate that if a -Version param is passed in that it can be parsed into a NuGet version range. + // an exact version will be formatted into a version range. + if (ParameterSetName.Equals("NameParameterSet") && Version != null && !Utils.TryParseVersionOrVersionRange(Version, out _versionRange)) + { + var exMessage = "Argument for -Version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + var IncorrectVersionFormat = new ErrorRecord(ex, "IncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(IncorrectVersionFormat); + } + + // If the user does not specify a path to save to, use the user's current working directory + if (string.IsNullOrWhiteSpace(_path)) + { + _path = SessionState.Path.CurrentLocation.Path; + } + } + + protected override void ProcessRecord() + { + // Define the cancellation token. + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken cancellationToken = source.Token; + + var installHelper = new InstallHelper(updatePkg: false, savePkg: true, cancellationToken, this); + + switch (ParameterSetName) + { + case NameParameterSet: + installHelper.InstallPackages( + names: Name, + versionRange: _versionRange, + prerelease: Prerelease, + repository: Repository, + acceptLicense: true, + quiet: true, + reinstall: true, + force: false, + trustRepository: TrustRepository, + noClobber: false, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: _path, + asNupkg: false, + includeXML: false, + pathsToInstallPkg: new List { _path } ); + break; + + default: + WriteDebug("Invalid parameter set"); + break; + } + } + #endregion + } +} \ No newline at end of file diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index 47ce9bcca..fe5455fe5 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -166,6 +166,13 @@ function Get-RemoveTestDirs { } } +function Create-TemporaryDirectory { + $path = [System.IO.Path]::GetTempPath() + $child = [System.Guid]::NewGuid() + + return New-Item -ItemType Directory -Path (Join-Path $path $child) +} + function Get-NewPSResourceRepositoryFile { # register our own repositories with desired priority $powerShellGetPath = Join-Path -Path ([Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData)) -ChildPath "PowerShellGet" @@ -553,4 +560,4 @@ function CheckForExpectedPSGetInfo $psGetInfo.Type | Should -BeExactly 'Module' $psGetInfo.UpdatedDate.Year | Should -BeExactly 1 $psGetInfo.Version.ToString() | Should -BeExactly '1.0.0' -} +} \ No newline at end of file diff --git a/test/SavePSResource.Tests.ps1 b/test/SavePSResource.Tests.ps1 new file mode 100644 index 000000000..64a619f0a --- /dev/null +++ b/test/SavePSResource.Tests.ps1 @@ -0,0 +1,203 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force + +Describe 'Test Save-PSResource for PSResources' { + + BeforeAll{ + $TestGalleryName = Get-PoshTestGalleryName + $PSGalleryName = Get-PSGalleryName + $NuGetGalleryName = Get-NuGetGalleryName + Get-NewPSResourceRepositoryFile + Register-LocalRepos + + $TempDir = Create-TemporaryDirectory + } + + AfterEach { + # Delete all files and subdirectories in $Tempdir, but keep the directory $Tempdir + try { + Get-ChildItem -Path $TempDir -Force | foreach { $_.Delete($true)} + } + catch { + Get-ChildItem -Path $TempDir -Force | foreach { $_.Delete()} + } + } + + AfterAll { + Get-RevertPSResourceRepositoryFile + } + + It "Save specific module resource by name" { + Save-PSResource -Name "TestModule" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Save specific script resource by name" { + Save-PSResource -Name "TestTestScript" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestTestScript.ps1" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Save multiple resources by name" { + $pkgNames = @("TestModule","TestModule99") + Save-PSResource -Name $pkgNames -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be @("TestModule","TestModule99") + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Should not save resource given nonexistant name" { + Save-PSResource -Name NonExistantModule -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -BeNullOrEmpty + } + + # Do some version testing, but Find-PSResource should be doing thorough testing + It "Should save resource given name and exact version" { + Save-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.2.0" + } + + It "Should save resource given name and exact version with bracket syntax" { + Save-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.2.0" + } + + It "Should save resource given name and exact range inclusive [1.0.0, 1.1.1]" { + Save-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.1.1" + } + + It "Should save resource given name and exact range exclusive (1.0.0, 1.1.1)" { + Save-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.1" + } + + It "Should not save resource with incorrectly formatted version such as " -TestCases @( + @{Version='(1.2.0.0)'; Description="exclusive version (2.10.0.0)"}, + @{Version='[1-2-0-0]'; Description="version formatted with invalid delimiter [1-2-0-0]"} + ) { + param($Version, $Description) + + Save-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName -Path $TempDir + $res = Get-ChildItem $TempDir + $res | Should -BeNullOrEmpty + } + + It "Save resource when given Name, Version '*', should install the latest version" { + Save-PSResource -Name "TestModule" -Version "*" -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.3.0" + } + + It "Save resource with latest (including prerelease) version given Prerelease parameter" { + Save-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModulePrerelease" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "0.0.1" + } + + It "Save a module with a dependency" { + Save-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be @("PSGetTestDependency1","PSGetTestModule") + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Save resource via InputObject by piping from Find-PSresource" { + Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Save-PSResource -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.3.0" + } + + It "Save resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { + Set-PSResourceRepository PoshTestGallery -Trusted:$false + + Save-PSResource -Name "TestModule" -Repository $TestGalleryName -TrustRepository -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + $pkgVersion = Get-ChildItem $pkg + $pkgVersion.Name | Should -Be "1.3.0" + + Set-PSResourceRepository PoshTestGallery -Trusted + } + + It "Save resource from local repository given Repository parameter" { + $publishModuleName = "TestFindModule" + $repoName = "psgettestlocal" + Get-ModuleResourcePublishedToLocalRepoTestDrive $publishModuleName $repoName + Set-PSResourceRepository "psgettestlocal" -Trusted:$true + + Save-PSResource -Name $publishModuleName -Repository $repoName -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be $publishModuleName + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Save specific module resource by name when no repository is specified" { + Set-PSResourceRepository "PoshTestGallery" -Trusted:$True + Set-PSResourceRepository "PSGallery" -Trusted:$True + Set-PSResourceRepository "psgettestlocal2" -Trusted:$True + + Save-PSResource -Name "TestModule" -Path $TempDir + $pkg = Get-ChildItem $TempDir + $pkg.Name | Should -Be "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + } + + It "Save specific module resource by name if no -Path param is specifed" { + Save-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-ChildItem . + + $pkg.Name | Should -Contain "TestModule" + (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 + + # Delete all files and subdirectories in the current , but keep the directory $Tempdir + $pkgDir = Join-Path -Path . -ChildPath "TestModule" + Remove-Item $pkgDir -Recurse -Force + } + +<# + # This needs to be manually tested due to prompt + It "Install resource should prompt 'trust repository' if repository is not trusted" { + Set-PSResourceRepository PoshTestGallery -Trusted:$false + + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false + + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + + Set-PSResourceRepository PoshTestGallery -Trusted + } +#> +} \ No newline at end of file From 869d59638f0ace57b0fde493f451d21bff07c0df Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Mon, 12 Jul 2021 23:55:48 -0700 Subject: [PATCH 34/62] Update InstallHelper with Save changes --- src/code/InstallHelper.cs | 147 +++++++++++++++---------------- src/{ => code}/SavePSResource.cs | 0 2 files changed, 73 insertions(+), 74 deletions(-) rename src/{ => code}/SavePSResource.cs (100%) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index f99785991..41685fb85 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -41,7 +41,7 @@ internal class InstallHelper : PSCmdlet bool _force; bool _trustRepository; bool _noClobber; - PSCredential _credential; + PSCredential _credential; string _specifiedPath; bool _asNupkg; bool _includeXML; @@ -55,34 +55,34 @@ public InstallHelper(bool updatePkg, bool savePkg, CancellationToken cancellatio } public void InstallPackages( - string[] names, - VersionRange versionRange, - bool prerelease, + string[] names, + VersionRange versionRange, + bool prerelease, string[] repository, - bool acceptLicense, - bool quiet, - bool reinstall, - bool force, - bool trustRepository, - bool noClobber, - PSCredential credential, - string requiredResourceFile, - string requiredResourceJson, - Hashtable requiredResourceHash, - string specifiedPath, - bool asNupkg, + bool acceptLicense, + bool quiet, + bool reinstall, + bool force, + bool trustRepository, + bool noClobber, + PSCredential credential, + string requiredResourceFile, + string requiredResourceJson, + Hashtable requiredResourceHash, + string specifiedPath, + bool asNupkg, bool includeXML, List pathsToInstallPkg) { _cmdletPassedIn.WriteDebug(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + - "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';", - string.Join(",", names), - (_versionRange != null ? _versionRange.OriginalString : string.Empty), - prerelease.ToString(), + "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';", + string.Join(",", names), + (_versionRange != null ? _versionRange.OriginalString : string.Empty), + prerelease.ToString(), repository != null ? string.Join(",", repository) : string.Empty, - acceptLicense.ToString(), - quiet.ToString(), - reinstall.ToString(), + acceptLicense.ToString(), + quiet.ToString(), + reinstall.ToString(), trustRepository.ToString(), noClobber.ToString())); @@ -99,9 +99,9 @@ public void InstallPackages( _asNupkg = asNupkg; _includeXML = includeXML; _pathsToInstallPkg = pathsToInstallPkg; - + // Go through the repositories and see which is the first repository to have the pkg version available - ProcessRepositories(names, repository, _trustRepository, _credential); + ProcessRepositories(names, repository, _trustRepository, _credential); } // This method calls iterates through repositories (by priority order) to search for the pkgs to install @@ -122,7 +122,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool var sourceTrusted = false; string repoName = repo.Name; _cmdletPassedIn.WriteDebug(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. if (repo.Trusted == false && !trustRepository && !_force) @@ -136,7 +136,8 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool sourceTrusted = _cmdletPassedIn.ShouldContinue(message, repositoryIsNotTrusted, true, ref yesToAll, ref noToAll); } } - else { + else + { sourceTrusted = true; } @@ -153,7 +154,7 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( name: packageNames, type: ResourceType.None, - version: _versionRange != null ? _versionRange.OriginalString : null, + version: _versionRange != null ? _versionRange.OriginalString : null, prerelease: _prerelease, tag: null, repository: new string[] { repoName }, @@ -215,7 +216,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, _pathsToSearch); // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names @@ -232,7 +233,7 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable InstallPackage(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) { List pkgsSuccessfullyInstalled = new List(); @@ -285,7 +286,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s globalPackagesFolder: tempInstallPath, logger: NullLogger.Instance, token: _cancellationToken).GetAwaiter().GetResult(); - + if (_asNupkg) // this is Save functionality { DirectoryInfo nupkgPath = new DirectoryInfo(((System.IO.FileStream)result.PackageStream).Name); @@ -293,7 +294,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s continue; } - + // Create the package extraction context PackageExtractionContext packageExtractionContext = new PackageExtractionContext( packageSaveMode: PackageSaveMode.Nupkg, @@ -343,11 +344,6 @@ private List InstallPackage(IEnumerable pkgsToInstall, s // Need to close the .nupkg if (result != null) result.Dispose(); } - - // Save functionality - if (_asNupkg) - { - } } _cmdletPassedIn.WriteDebug(string.Format("Successfully able to download package from source to: '{0}'", tempInstallPath)); @@ -381,32 +377,32 @@ private List InstallPackage(IEnumerable pkgsToInstall, s continue; } } - + // Delete the extra nupkg related files that are not needed and not part of the module/script DeleteExtraneousFiles(tempInstallPath, pkgIdentity, tempDirNameVersion); - // PSModules: - /// ./Modules - /// ./Scripts - /// _pathsToInstallPkg is sorted by desirability, Find will pick the pick the first Script or Modules path found in the list - var installPath = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) - : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); - - if (_includeXML) - { - CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); - } - + string installPath; if (_savePkg) { - //TODO: SavePackage + // For save the installation path is what is passed in via -Path + installPath = _pathsToInstallPkg.FirstOrDefault(); } - else - { - MoveFilesIntoInstallPath(p, isScript, isLocalRepo, tempDirNameVersion, tempInstallPath, installPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); + 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 = isScript ? _pathsToInstallPkg.Find(path => path.EndsWith("Scripts", StringComparison.InvariantCultureIgnoreCase)) + : _pathsToInstallPkg.Find(path => path.EndsWith("Modules", StringComparison.InvariantCultureIgnoreCase)); } - + if (_includeXML) + { + CreateMetadataXMLFile(tempDirNameVersion, installPath, repoName, p, isScript); + } + + MoveFilesIntoInstallPath(p, isScript, isLocalRepo, tempDirNameVersion, tempInstallPath, installPath, newVersion, moduleManifestVersion, version3digitNoPrerelease, version4digitNoPrerelease, scriptPath); + _cmdletPassedIn.WriteVerbose(String.Format("Successfully installed package '{0}'", p.Name)); pkgsSuccessfullyInstalled.Add(p.Name); } @@ -427,7 +423,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s return pkgsSuccessfullyInstalled; } - + private void CallProgressBar(PSResourceInfo p) { int i = 1; @@ -446,7 +442,7 @@ private void CallProgressBar(PSResourceInfo p) activity = string.Format("Installing {0}...", p.Name); statusDescription = string.Format("{0}% Complete:", i++); - // j = 1; + // j = 1; /* if (packageNames.ToList().Contains(p.Identity.Id)) { @@ -564,7 +560,7 @@ private void CreateMetadataXMLFile(string dirNameVersion, string installPath, st WriteError(ErrorParsingMetadata); } } - + private void DeleteExtraneousFiles(string tempInstallPath, PackageIdentity pkgIdentity, string dirNameVersion) { // Deleting .nupkg SHA file, .nuspec, and .nupkg after unpacking the module @@ -623,24 +619,27 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLo if (isScript) { - // Need to delete old xml files because there can only be 1 per script - var scriptXML = p.Name + "_InstalledScriptInfo.xml"; - _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))); - if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))) + if (!_savePkg) { - _cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); - File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); - } + // Need to delete old xml files because there can only be 1 per script + var scriptXML = p.Name + "_InstalledScriptInfo.xml"; + _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)))); + if (File.Exists(Path.Combine(installPath, "InstalledScriptInfos", scriptXML))) + { + _cmdletPassedIn.WriteDebug(string.Format("Deleting script metadata XML")); + File.Delete(Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); + } - _cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML))); - File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); + _cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML))); + File.Move(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, "InstalledScriptInfos", scriptXML)); - // Need to delete old script file, if that exists - _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")))); - if (File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))) - { - _cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); - File.Delete(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); + // Need to delete old script file, if that exists + _cmdletPassedIn.WriteDebug(string.Format("Checking if path '{0}' exists: ", File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")))); + if (File.Exists(Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))) + { + _cmdletPassedIn.WriteDebug(string.Format("Deleting script file")); + File.Delete(Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); + } } _cmdletPassedIn.WriteDebug(string.Format("Moving '{0}' to '{1}'", scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1"))); @@ -673,4 +672,4 @@ private void MoveFilesIntoInstallPath(PSResourceInfo p, bool isScript, bool isLo } } } -} +} \ No newline at end of file diff --git a/src/SavePSResource.cs b/src/code/SavePSResource.cs similarity index 100% rename from src/SavePSResource.cs rename to src/code/SavePSResource.cs From ca89e9ae3d185bf489063ef5fed71a459969db45 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 13 Jul 2021 14:51:29 -0400 Subject: [PATCH 35/62] remove NoClobber as Update parameter --- src/code/UpdatePSResource.cs | 21 +++----- test/UpdatePSResource.Tests.ps1 | 85 ++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 92246deb0..864823349 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -44,8 +44,7 @@ class UpdatePSResource : PSCmdlet /// [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] [ValidateNotNullOrEmpty] - public string[] Name { get; set; } - // TODO: create a default string with "*" + public string[] Name { get; set ; } = new string[] {"*"}; /// /// Specifies the version the resource is to be updated to. @@ -109,13 +108,6 @@ class UpdatePSResource : PSCmdlet [Parameter(ParameterSetName = InputObjectParameterSet)] public SwitchParameter Force { get; set; } - /// - /// Prevents updating modules that have the same cmdlets as a differently named module already. - /// - [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] - public SwitchParameter NoClobber { get; set; } - /// /// Used to pass in an object via pipeline to update. /// @@ -164,7 +156,6 @@ protected override void ProcessRecord() VersionRange versionRange = new VersionRange(); - // TODO: discuss with Paul + then update test for incorrectly formatted version if (Version !=null && !Utils.TryParseVersionOrVersionRange(Version, out versionRange)) { WriteError(new ErrorRecord( @@ -172,7 +163,7 @@ protected override void ProcessRecord() "ErrorParsingVersionParamIntoVersionRange", ErrorCategory.InvalidArgument, this)); - versionRange = VersionRange.All; // or should I return here instead? + return; } if (isContainWildcard) @@ -189,7 +180,6 @@ protected override void ProcessRecord() versionRange: versionRange, pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); } - WriteVerbose("names after GetHelper.ProcessGetParams: " + String.Join(", ", Name)); // TODO: remove this! InstallHelper installHelper = new InstallHelper( update: true, @@ -211,7 +201,7 @@ protected override void ProcessRecord() reinstall: false, force: Force, trustRepository: TrustRepository, - noClobber: NoClobber, + noClobber: false, credential: Credential, requiredResourceFile: null, requiredResourceJson: null, @@ -219,7 +209,9 @@ protected override void ProcessRecord() specifiedPath: null, asNupkg: false, includeXML: true, - pathsToInstallPkg: Utils.GetAllInstallationPaths(this, String.Equals(Scope.ToString(), "None", StringComparison.InvariantCultureIgnoreCase) ? "" : Scope.ToString())); + pathsToInstallPkg: Utils.GetAllInstallationPaths( + psCmdlet: this, + scope: String.Equals(Scope.ToString(), "None", StringComparison.InvariantCultureIgnoreCase) ? "" : Scope.ToString())); break; case InputObjectParameterSet: @@ -227,7 +219,6 @@ protected override void ProcessRecord() break; default: - // TODO: the case where no name was specified so we update all packages? Dbg.Assert(false, "Invalid parameter set"); break; } diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 9320f416b..0baa0b36c 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -16,11 +16,7 @@ Describe 'Test Install-PSResource for Module' { } AfterEach { - Uninstall-PSResource "TestModule" - Uninstall-PSResource "TestModule99" - Uninstall-PSResource "TestModuleWithLicense" - Uninstall-PSResource "PSGetTestModule" - + Uninstall-PSResource "TestModule", "TestModule99", "TestModuleWithLicense", "PSGetTestModule" } AfterAll { @@ -32,16 +28,17 @@ Describe 'Test Install-PSResource for Module' { Update-PSResource -Name "TestModule" -Repository $TestGalleryName $res = Get-InstalledPSResource -Name "TestModule" + $isPkgUpdated = $false foreach ($pkg in $res) { - if ([System.Version]$pkg.Version -gt [System.Version]"1.0.0.0") + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { $isPkgUpdated = $true } } - $isPkgUpdated | Should -BeTrue + $isPkgUpdated | Should -Be $true } It "update resources installed given Name (with wildcard) parameter" { @@ -71,17 +68,8 @@ Describe 'Test Install-PSResource for Module' { $isTestModuleUpdated | Should -BeTrue $isTestModule99Updated | Should -BeTrue - } - # It "update resources installed given Name (with wildcard) and Version (specific -supported? and with wildcard -supported?) " { - # # TODO: determine support! - # # Name (wc) + Version (specific) -> unsupported or update which ones have that version but say/write error if that version doesn't exist for that pkg - # # Name (wc) + Version ("*") -> is perhaps what's supported by default/happening by default - # # NAme (wc) + Version ("1.0.0.*") -> supported? - - # } - It "update resource installed given Name and Version (specific) parameters" { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName @@ -99,9 +87,53 @@ Describe 'Test Install-PSResource for Module' { $isPkgUpdated | Should -BeTrue } - # It "update resource when given Name and incorrectly formatted version" { + $testCases2 = @{Version="[2.10.0.0]"; ExpectedVersions=@("2.10.0.0"); Reason="validate version, exact match"}, + @{Version="2.10.0.0"; ExpectedVersions=@("2.10.0.0"); Reason="validate version, exact match without bracket syntax"}, + @{Version="[2.5.0.0, 2.8.0.0]"; ExpectedVersions=@("2.5.0.0", "2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, exact range inclusive"}, + @{Version="(2.5.0.0, 2.8.0.0)"; ExpectedVersions=@("2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0"); Reason="validate version, exact range exclusive"}, + @{Version="(2.9.4.0,)"; ExpectedVersions=@("2.10.0.0", "2.10.1.0", "2.10.2.0"); Reason="validate version, minimum version exclusive"}, + @{Version="[2.9.4.0,)"; ExpectedVersions=@("2.9.4.0", "2.10.0.0", "2.10.1.0", "2.10.2.0"); Reason="validate version, minimum version inclusive"}, + @{Version="(,2.0.0.0)"; ExpectedVersions=@("1.9.0.0"); Reason="validate version, maximum version exclusive"}, + @{Version="(,2.0.0.0]"; ExpectedVersions=@("1.9.0.0", "2.0.0.0"); Reason="validate version, maximum version inclusive"}, + @{Version="[2.5.0.0, 2.8.0.0)"; ExpectedVersions=@("2.5.0.0", "2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, mixed inclusive minimum and exclusive maximum version"} + @{Version="(2.5.0.0, 2.8.0.0]"; ExpectedVersions=@("2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, mixed exclusive minimum and inclusive maximum version"} - # } + It "find resource when given Name to " -TestCases $testCases2{ + param($Version, $ExpectedVersions) + + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + Update-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName + + $res = Get-InstalledPSResource -Name "TestModule" + + foreach ($item in $res) { + $item.Name | Should -Be "TestModule" + $ExpectedVersions | Should -Contain $item.Version + } + } + + $testCases = @( + @{Version='(1.2.0.0)'; Description="exclusive version (2.10.0.0)"}, + @{Version='[1-2-0-0]'; Description="version formatted with invalid delimiter [1-2-0-0]"} + ) + It "Should not update resource with incorrectly formatted version such as " -TestCases $testCases{ + param($Version, $Description) + + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + Update-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName + + $res = Get-InstalledPSResource -Name "TestModule" + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $false + } It "update resource with latest (including prerelease) version given Prerelease parameter" { # PSGetTestModule resource's latest version is a prerelease version, before that it has a non-prerelease version @@ -115,7 +147,7 @@ Describe 'Test Install-PSResource for Module' { { if ([System.Version]$pkg.Version -gt [System.Version]"1.0.2.0") { - Write-Host $pkg.Version" and prerelease data is:"$pkg.PrivateData.PSData.Prerelease"yep that it" + Write-Host $pkg.Version" and prerelease data is:"$pkg.PrivateData.PSData.Prerelease"." # $pkg.PrereleaseLabel | Should -Be "-alpha1" (todo: for some reason get-installedpsresource doesn't get this!) $isPkgUpdated = $true } @@ -237,20 +269,6 @@ Describe 'Test Install-PSResource for Module' { Set-PSResourceRepository PoshTestGallery -Trusted } - # TODO: slightly confused about NoClobber parameter for Update...if I had already installed - # myTestModule and wanted to update it would NoClobber not allow it to update (bc this new updated version contains the same cmdlets - # as the older installed version)? - # It "update resource with cmdlet names from a module already installed (should clobber)" { - # Install-PSResource -Name "myTestModule" -Repository $TestGalleryName - # $pkg = Get-InstalledPSResource "myTestModule" - # $pkg.Name | Should -Be "myTestModule" - # $pkg.Version | Should -Be "0.0.3.0" - - # Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName - # $pkg = Get-InstalledPSResource "myTestModule2" - # $pkg.Name | Should -Be "myTestModule2" - # $pkg.Version | Should -Be "0.0.1.0" - # } # update script resource # Name @@ -260,7 +278,6 @@ Describe 'Test Install-PSResource for Module' { # AcceptLicense # TrustRepository # Credential - # NoClobber # InputObject From 57023cfa5157a45532a4aee49a9b2c78158d3162 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Tue, 13 Jul 2021 12:40:08 -0700 Subject: [PATCH 36/62] Update src/code/InstallPSResource.cs Co-authored-by: Paul Higinbotham --- src/code/InstallPSResource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 80b7da397..7529ce1ea 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -151,7 +151,8 @@ protected override void BeginProcessing() { // validate that if a -Version param is passed in that it can be parsed into a NuGet version range. // An exact version will be formatted into a version range. - if (ParameterSetName.Equals("NameParameterSet") && Version != null && !Utils.TryParseVersionOrVersionRange(Version, out _versionRange)) + if (ParameterSetName.Equals(NameParameterSet) && Version != null && !Utils.TryParseVersionOrVersionRange(Version, out _versionRange)) + { var exMessage = "Argument for -Version parameter is not in the proper format."; var ex = new ArgumentException(exMessage); From 74775281349428ef761dcfedc59843de4117e932 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 13 Jul 2021 13:54:45 -0700 Subject: [PATCH 37/62] Incorporate further code review changes --- src/code/InstallHelper.cs | 24 ++++++++---- src/code/InstallPSResource.cs | 71 +++++++---------------------------- src/code/PSResourceInfo.cs | 5 ++- src/code/SavePSResource.cs | 11 +++--- 4 files changed, 39 insertions(+), 72 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index 41685fb85..1b4acd905 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -161,16 +161,16 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool credential: credential, includeDependencies: true); - // Deduplicate any packages - pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy( - m => new { m.Name, m.Version }).Select( - group => group.First()).ToList(); - - // Make sure only the latest version of a module gets installed + // 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(); - + if (!pkgsFromRepoToInstall.Any()) { _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); @@ -212,6 +212,12 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable _pathsToSearch = new List(); GetHelper getHelper = new GetHelper(_cmdletPassedIn); // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) + // _pathsToSearch will contain all resource package subdirectories within _pathsToInstallPkg path locations + // e.g.: + // ./InstallPackagePath1/PackageA + // ./InstallPackagePath1/PackageB + // ./InstallPackagePath2/PackageC + // ./InstallPackagePath3/PackageD foreach (var path in _pathsToInstallPkg) { _pathsToSearch.AddRange(Directory.GetDirectories(path)); @@ -268,7 +274,8 @@ private List InstallPackage(IEnumerable pkgsToInstall, s if (!NuGetVersion.TryParse(createFullVersion, out NuGetVersion pkgVersion)) { - _cmdletPassedIn.WriteDebug("Error parsing version into a NuGetVersion"); + _cmdletPassedIn.WriteDebug(string.Format("Error parsing package '{0}' version '{1}' into a NuGetVersion", p.Name, p.Version.ToString())); + continue; } var pkgIdentity = new PackageIdentity(p.Name, pkgVersion); var cacheContext = new SourceCacheContext(); @@ -313,6 +320,7 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } else { + /* Download from a non-local repository */ // Set up NuGet API resource for download PackageSource source = new PackageSource(repoUrl); if (credential != null) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 80b7da397..44d24854a 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using NuGet.Versioning; using System; -using System.Collections; using System.Collections.Generic; +using Dbg = System.Diagnostics.Debug; using System.Management.Automation; using System.Threading; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -39,14 +39,12 @@ class InstallPSResource : PSCmdlet /// Specifies to allow installation of prerelease versions /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter Prerelease { get; set; } /// /// Specifies the repositories from which to search for the resource to be installed. /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] [ArgumentCompleter(typeof(RepositoryNameCompleter))] [ValidateNotNullOrEmpty] public string[] Repository { get; set; } @@ -55,7 +53,6 @@ class InstallPSResource : PSCmdlet /// Specifies a user account that has rights to find a resource from a specific repository. /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public PSCredential Credential { get; set; } /// @@ -63,79 +60,31 @@ class InstallPSResource : PSCmdlet /// [ValidateSet("CurrentUser", "AllUsers")] [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public ScopeType Scope { get; set; } /// /// Suppresses being prompted for untrusted sources. /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter TrustRepository { get; set; } /// /// Overwrites a previously installed resource with the same name and version. /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter Reinstall { get; set; } /// /// Suppresses progress information. /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter Quiet { get; set; } /// /// For modules that require a license, AcceptLicense automatically accepts the license agreement during installation. /// [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] public SwitchParameter AcceptLicense { get; set; } - - /* - /// - /// Prevents installation conflicts with modules that contain existing commands on a computer. - /// - [Parameter(ParameterSetName = NameParameterSet)] - //[Parameter(ParameterSetName = RequiredResourceFileParameterSet)] - public SwitchParameter NoClobber { get; set; } - */ - - /* - /// - /// - [Parameter(ParameterSetName = RequiredResourceFileParameterSet)] - public String RequiredResourceFile { get; set; } - */ - - /* - /// - /// - [Parameter(ParameterSetName = RequiredResourceParameterSet)] - public Object RequiredResource // takes either string (json) or hashtable - { - get { return _requiredResourceHash != null ? (Object)_requiredResourceHash : (Object)_requiredResourceJson; } - - set { - if (value.GetType().Name.Equals("String")) - { - _requiredResourceJson = (String) value; - } - else if (value.GetType().Name.Equals("Hashtable")) - { - _requiredResourceHash = (Hashtable) value; - } - else - { - throw new ParameterBindingException("Object is not a JSON or Hashtable"); - } - } - } - private string _requiredResourceJson; - private Hashtable _requiredResourceHash; - */ #endregion #region members @@ -195,15 +144,23 @@ protected override void ProcessRecord() break; case RequiredResourceFileParameterSet: - WriteDebug("Not yet implemented"); + ThrowTerminatingError(new ErrorRecord( + new PSNotImplementedException("RequiredResourceFileParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), + "CommandParameterSetNotImplementedYet", + ErrorCategory.NotImplemented, + this)); break; case RequiredResourceParameterSet: - WriteDebug("Not yet implemented"); + ThrowTerminatingError(new ErrorRecord( + new PSNotImplementedException("RequiredResourceParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), + "CommandParameterSetNotImplementedYet", + ErrorCategory.NotImplemented, + this)); break; default: - WriteDebug("Invalid parameter set"); + Dbg.Assert(false, "Invalid parameter set"); break; } } diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index a8868f44b..061bb7fef 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -536,7 +536,10 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) foreach(PSObject dependencyObj in dependencyInfos) { - // can be an array or hashtable + // The dependency object can be a string or a hashtable + // eg: + // RequiredModules = @('PSGetTestDependency1') + // RequiredModules = @(@{ModuleName='PackageManagement';ModuleVersion='1.0.0.1'}) if (dependencyObj.BaseObject is Hashtable dependencyInfo) { if (!dependencyInfo.ContainsKey("Name")) diff --git a/src/code/SavePSResource.cs b/src/code/SavePSResource.cs index c317c4021..626721530 100644 --- a/src/code/SavePSResource.cs +++ b/src/code/SavePSResource.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using NuGet.Versioning; using System; -using System.Collections; using System.Collections.Generic; +using Dbg = System.Diagnostics.Debug; using System.IO; using System.Linq; using System.Management.Automation; using System.Threading; -using static System.Environment; +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -146,7 +145,7 @@ protected override void ProcessRecord() CancellationTokenSource source = new CancellationTokenSource(); CancellationToken cancellationToken = source.Token; - var installHelper = new InstallHelper(updatePkg: false, savePkg: true, cancellationToken, this); + var installHelper = new InstallHelper(updatePkg: false, savePkg: true, cancellationToken: cancellationToken, cmdletPassedIn: this); switch (ParameterSetName) { @@ -173,7 +172,7 @@ protected override void ProcessRecord() break; default: - WriteDebug("Invalid parameter set"); + Dbg.Assert(false, "Invalid parameter set"); break; } } From c4cc08f2b447edf98f81acbc7fc38a5a2f17cde1 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 13 Jul 2021 17:30:49 -0400 Subject: [PATCH 38/62] add IsPRerelease to AdditionalMetadata --- src/code/PSResourceInfo.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 2ed11dd6b..f3c55b8fb 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -710,9 +710,9 @@ private static string ParseMetadataName(IPackageSearchMetadata pkg) private static string ParsePrerelease(IPackageSearchMetadata pkg) { - return pkg.Identity.Version.ReleaseLabels.Count() == 0 ? - String.Empty : - pkg.Identity.Version.ReleaseLabels.FirstOrDefault(); + return pkg.Identity.Version.ReleaseLabels.Count() > 0 ? + pkg.Identity.Version.ReleaseLabels.FirstOrDefault() : + String.Empty; } private static Uri ParseMetadataProjectUri(IPackageSearchMetadata pkg) @@ -802,15 +802,13 @@ private PSObject ConvertToCustomObject() { var additionalMetadata = new PSObject(); - // Need to add a null check here due to null ref exception getting thrown - if (AdditionalMetadata != null) - { - foreach (var item in AdditionalMetadata) - { - additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); - } + AdditionalMetadata.Add(nameof(IsPrerelease), IsPrerelease.ToString()); + foreach (var item in AdditionalMetadata) + { + additionalMetadata.Properties.Add(new PSNoteProperty(item.Key, item.Value)); } + var psObject = new PSObject(); psObject.Properties.Add(new PSNoteProperty(nameof(Name), Name ?? string.Empty)); psObject.Properties.Add(new PSNoteProperty(nameof(Version), ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel))); From 6ea5a64330ef41f6f9b33a28fbbe4ec1f83e771e Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 14 Jul 2021 12:03:36 -0400 Subject: [PATCH 39/62] resolve prerelease label issue --- src/code/GetHelper.cs | 10 +++++--- src/code/InstallHelper.cs | 4 ++-- src/code/PSResourceInfo.cs | 48 ++++++++++++++++++++++---------------- src/code/Utils.cs | 36 ++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/code/GetHelper.cs b/src/code/GetHelper.cs index 5b2838e51..dace7e778 100644 --- a/src/code/GetHelper.cs +++ b/src/code/GetHelper.cs @@ -111,7 +111,11 @@ public IEnumerable FilterPkgPathsByVersion(VersionRange versionRange, Li // if no version is specified, just delete the latest version Array.Sort(versionsDirs); - yield return (versionsDirs[versionsDirs.Length - 1]); + if (versionsDirs.Length > 0) + { + yield return versionsDirs[versionsDirs.Length - 1]; + } + continue; } @@ -218,7 +222,7 @@ public PSResourceInfo OutputPackageObject(string pkgPath, Dictionary new { m.Name }).Select( group => group.First()).ToList(); - + if (!pkgsFromRepoToInstall.Any()) { _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); @@ -565,7 +565,7 @@ private void CreateMetadataXMLFile(string dirNameVersion, string installPath, st var message = string.Format("Error parsing metadata into XML: '{0}'", error); var ex = new ArgumentException(message); var ErrorParsingMetadata = new ErrorRecord(ex, "ErrorParsingMetadata", ErrorCategory.ParserError, null); - WriteError(ErrorParsingMetadata); + _cmdletPassedIn.WriteError(ErrorParsingMetadata); } } diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index 4d0daf10f..8b38fbfd8 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Linq; // Copyright (c) Microsoft Corporation. All rights reserved. @@ -368,19 +369,24 @@ private static Version GetVersionInfo( string pkgVersion = versionString; if (versionString.Contains("-")) { + // versionString: "1.2.0-alpha1" string[] versionStringParsed = versionString.Split('-'); if (versionStringParsed.Length == 1) { + // versionString: "1.2.0-" (unlikely, at least should not be from our PSResourceInfo.TryWrite()) pkgVersion = versionStringParsed[0]; } else { // versionStringParsed.Length > 1 (because string contained '-' so couldn't be 0) + // versionString: "1.2.0-alpha1" pkgVersion = versionStringParsed[0]; prereleaseLabel = versionStringParsed[1]; } } + // at this point, version is normalized (i.e either "1.2.0" (if part of prerelease) or "1.2.0.0" otherwise) + // parse the pkgVersion parsed out above into a System.Version object if (!Version.TryParse(pkgVersion, out Version parsedVersion)) { prereleaseLabel = String.Empty; @@ -392,6 +398,8 @@ private static Version GetVersionInfo( } } + // version could not be parsed as string, it was written to XML file as a System.Version object + // V3 code briefly did so, I believe so we provide support for it prereleaseLabel = String.Empty; return GetProperty(nameof(PSResourceInfo.Version), psObjectInfo); } @@ -537,8 +545,8 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) foreach(PSObject dependencyObj in dependencyInfos) { // The dependency object can be a string or a hashtable - // eg: - // RequiredModules = @('PSGetTestDependency1') + // eg: + // RequiredModules = @('PSGetTestDependency1') // RequiredModules = @(@{ModuleName='PackageManagement';ModuleVersion='1.0.0.1'}) if (dependencyObj.BaseObject is Hashtable dependencyInfo) { @@ -635,22 +643,7 @@ private static Dependency[] GetDependencies(ArrayList dependencyInfos) private static string ConcatenateVersionWithPrerelease(string version, string prerelease) { - // if no prerelease, just version suffices - if (String.IsNullOrEmpty(prerelease)) - { - return version; - } - - int numVersionDigits = version.Split('.').Count(); - if (numVersionDigits == 3) - { - // 0.5.3 -> version string , preview4 -> prerelease string , return: 5.3.0-preview4 - return version + "-" + prerelease; - } - - - // number of digits not equivalent to 3 was not supported in V2 - return version; + return Utils.GetNormalizedVersionString(version, prerelease); } @@ -803,9 +796,23 @@ private static Version ParseMetadataVersion(IPackageSearchMetadata pkg) private PSObject ConvertToCustomObject() { + string NormalizedVersion = IsPrerelease ? ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel) : Version.ToString(); + var additionalMetadata = new PSObject(); + if (AdditionalMetadata == null) + { + AdditionalMetadata = new Dictionary(); + } - AdditionalMetadata.Add(nameof(IsPrerelease), IsPrerelease.ToString()); + if (!AdditionalMetadata.ContainsKey(nameof(IsPrerelease))) + { + AdditionalMetadata.Add(nameof(IsPrerelease), IsPrerelease.ToString()); + } + + if (!AdditionalMetadata.ContainsKey(nameof(NormalizedVersion))) + { + AdditionalMetadata.Add(nameof(NormalizedVersion), NormalizedVersion); + } foreach (var item in AdditionalMetadata) { @@ -814,7 +821,7 @@ private PSObject ConvertToCustomObject() var psObject = new PSObject(); psObject.Properties.Add(new PSNoteProperty(nameof(Name), Name ?? string.Empty)); - psObject.Properties.Add(new PSNoteProperty(nameof(Version), ConcatenateVersionWithPrerelease(Version.ToString(), PrereleaseLabel))); + psObject.Properties.Add(new PSNoteProperty(nameof(Version), NormalizedVersion)); psObject.Properties.Add(new PSNoteProperty(nameof(Type), Type)); psObject.Properties.Add(new PSNoteProperty(nameof(Description), Description ?? string.Empty)); psObject.Properties.Add(new PSNoteProperty(nameof(Author), Author ?? string.Empty)); @@ -822,6 +829,7 @@ private PSObject ConvertToCustomObject() psObject.Properties.Add(new PSNoteProperty(nameof(Copyright), Copyright ?? string.Empty)); psObject.Properties.Add(new PSNoteProperty(nameof(PublishedDate), PublishedDate)); psObject.Properties.Add(new PSNoteProperty(nameof(InstalledDate), InstalledDate)); + psObject.Properties.Add(new PSNoteProperty(nameof(IsPrerelease), IsPrerelease)); psObject.Properties.Add(new PSNoteProperty(nameof(UpdatedDate), UpdatedDate)); psObject.Properties.Add(new PSNoteProperty(nameof(LicenseUri), LicenseUri)); psObject.Properties.Add(new PSNoteProperty(nameof(ProjectUri), ProjectUri)); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index ff3d35548..c64e5d438 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -1,3 +1,4 @@ +using System.Text; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -31,6 +32,41 @@ public static void WriteVerboseOnCmdlet( catch { } } + public static string GetNormalizedVersionString( + string versionString, + string prerelease + ) + { + // versionString may be like 1.2.0.0 or 1.2.0 + // prerelease may be null or "alpha1" + // possible passed in examples: + // versionString: "1.2.0" prerelease: "alpha1" + // versionString: "1.2.0" prerelease: "" <- doubtful though + // versionString: "1.2.0.0" prerelease: "alpha1" + // versionString: "1.2.0.0" prerelease: "" + + if (String.IsNullOrEmpty(prerelease)) + { + return versionString; + } + + int numVersionDigits = versionString.Split('.').Count(); + + if (numVersionDigits == 3) + { + // versionString: "1.2.0" prerelease: "alpha1" + return versionString + "-" + prerelease; + } + + else if (numVersionDigits == 4) + { + // versionString: "1.2.0.0" prerelease: "alpha1" + return versionString.Substring(0, versionString.LastIndexOf('.')) + "-" + prerelease; + } + + return versionString; + } + public static string[] FilterWildcards( string[] pkgNames, out string[] errorMsgs, From cd9e625c71410621c578c922a2b5f04ec8f6bd35 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 13:10:36 -0400 Subject: [PATCH 40/62] fix merge conflicts in tests --- test/InstallPSResource.Tests.ps1 | 114 ---------------------- test/PSGetTestUtils.psm1 | 29 ------ test/SavePSResource.Tests.ps1 | 162 +------------------------------ 3 files changed, 1 insertion(+), 304 deletions(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 2540596d2..2c1c6eb87 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -14,13 +14,8 @@ Describe 'Test Install-PSResource for Module' { } AfterEach { -<<<<<<< HEAD - Uninstall-PSResource "TestModule", "TestModule99", "myTestModule", "myTestModule2", "testModulePrerelease", - "testModuleWithlicense","PSGetTestModule", "PSGetTestDependency1", "TestFindModule" -======= Uninstall-PSResource "TestModule", "TestModule99", "myTestModule", "myTestModule2", "testModulePrerelease", "testModuleWithlicense","PSGetTestModule", "PSGetTestDependency1", "TestFindModule" -Force -ErrorAction SilentlyContinue ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } AfterAll { @@ -28,96 +23,58 @@ Describe 'Test Install-PSResource for Module' { } It "Install specific module resource by name" { -<<<<<<< HEAD - Install-PSResource -Name "TestModule" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "1.3.0" } It "Install specific script resource by name" { -<<<<<<< HEAD - Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "TestTestScript" - $pkg.Name | Should -Be "TestTestScript" -======= Install-PSResource -Name "TestTestScript" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "TestTestScript" $pkg.Name | Should -Be "TestTestScript" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "1.3.1.0" } It "Install multiple resources by name" { $pkgNames = @("TestModule","TestModule99") -<<<<<<< HEAD - Install-PSResource -Name $pkgNames -Repository $TestGalleryName -======= Install-PSResource -Name $pkgNames -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module $pkgNames -ListAvailable $pkg.Name | Should -Be $pkgNames } It "Should not install resource given nonexistant name" { -<<<<<<< HEAD - Install-PSResource -Name NonExistantModule -Repository $TestGalleryName -======= Install-PSResource -Name NonExistantModule -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module "NonExistantModule" -ListAvailable $pkg.Name | Should -BeNullOrEmpty } # Do some version testing, but Find-PSResource should be doing thorough testing It "Should install resource given name and exact version" { -<<<<<<< HEAD - Install-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName -======= Install-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.2.0" } It "Should install resource given name and exact version with bracket syntax" { -<<<<<<< HEAD - Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName -======= Install-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.2.0" } It "Should install resource given name and exact range inclusive [1.0.0, 1.1.1]" { -<<<<<<< HEAD - Install-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable -======= Install-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.1.1" } It "Should install resource given name and exact range exclusive (1.0.0, 1.1.1)" { -<<<<<<< HEAD - Install-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable -======= Install-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.1" } @@ -141,11 +98,7 @@ Describe 'Test Install-PSResource for Module' { } It "Install resource with latest (including prerelease) version given Prerelease parameter" { -<<<<<<< HEAD - $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName -======= $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module "TestModulePrerelease" -ListAvailable $pkg.Name | Should -Be "TestModulePrerelease" $pkg.Version | Should -Be "0.0.1" @@ -153,11 +106,7 @@ Describe 'Test Install-PSResource for Module' { } It "Install a module with a dependency" { -<<<<<<< HEAD - $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName -======= $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg = Get-Module "PSGetTestModule" -ListAvailable $pkg.Name | Should -Be "PSGetTestModule" $pkg.Version | Should -Be "2.0.2" @@ -171,67 +120,38 @@ Describe 'Test Install-PSResource for Module' { It "Install resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource $pkg = Get-Module "TestModule" -ListAvailable -<<<<<<< HEAD - $pkg.Name | Should -Be "TestModule" -======= $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "1.3.0" } # Windows only -<<<<<<< HEAD - It "Install resource under CurrentUser scope" { - Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= It "Install resource under CurrentUser scope" -Skip:(!$IsWindows) { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Path.Contains("Documents") | Should -Be $true } # Windows only -<<<<<<< HEAD - It "Install resource under AllUsers scope" { - Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= It "Install resource under AllUsers scope" -Skip:(!$IsWindows) { Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Path.Contains("Program Files") | Should -Be $true } # Windows only -<<<<<<< HEAD - It "Install resource under no specified scope" { - Install-PSResource -Name "TestModule" -Repository $TestGalleryName - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= It "Install resource under no specified scope" -Skip:(!$IsWindows) { Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Path.Contains("Documents") | Should -Be $true } It "Should not install resource that is already installed" { Install-PSResource -Name "TestModule" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable -<<<<<<< HEAD - $pkg.Name | Should -Be "TestModule" -======= $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e Install-PSResource -Name "TestModule" -Repository $TestGalleryName -WarningVariable WarningVar -warningaction SilentlyContinue $WarningVar | Should -Not -BeNullOrEmpty } @@ -250,11 +170,7 @@ Describe 'Test Install-PSResource for Module' { It "Install resource that requires accept license with -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName -AcceptLicense $pkg = Get-InstalledPSResource "testModuleWithlicense" -<<<<<<< HEAD - $pkg.Name | Should -Be "testModuleWithlicense" -======= $pkg.Name | Should -Be "testModuleWithlicense" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "0.0.1.0" } @@ -262,30 +178,14 @@ Describe 'Test Install-PSResource for Module' { Set-PSResourceRepository PoshTestGallery -Trusted:$false Install-PSResource -Name "TestModule" -Repository $TestGalleryName -TrustRepository -<<<<<<< HEAD - - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e Set-PSResourceRepository PoshTestGallery -Trusted } It "Install resource with cmdlet names from a module already installed (should clobber)" { -<<<<<<< HEAD - Install-PSResource -Name "myTestModule" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "myTestModule" - $pkg.Name | Should -Be "myTestModule" - $pkg.Version | Should -Be "0.0.3.0" - - Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName - $pkg = Get-InstalledPSResource "myTestModule2" - $pkg.Name | Should -Be "myTestModule2" -======= Install-PSResource -Name "myTestModule" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "myTestModule" $pkg.Name | Should -Be "myTestModule" @@ -294,7 +194,6 @@ Describe 'Test Install-PSResource for Module' { Install-PSResource -Name "myTestModule2" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "myTestModule2" $pkg.Name | Should -Be "myTestModule2" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "0.0.1.0" } @@ -315,29 +214,16 @@ Describe 'Test Install-PSResource for Module' { It "Install resource that requires accept license without -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName $pkg = Get-InstalledPSResource "testModuleWithlicense" -<<<<<<< HEAD - $pkg.Name | Should -Be "testModuleWithlicense" -======= $pkg.Name | Should -Be "testModuleWithlicense" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e $pkg.Version | Should -Be "0.0.1.0" } - # This needs to be manually tested due to prompt It "Install resource should prompt 'trust repository' if repository is not trusted" { Set-PSResourceRepository PoshTestGallery -Trusted:$false - Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false -<<<<<<< HEAD - - $pkg = Get-Module "TestModule" -ListAvailable - $pkg.Name | Should -Be "TestModule" -======= $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e - Set-PSResourceRepository PoshTestGallery -Trusted } #> diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index 97c87f400..d1efbbab9 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -166,13 +166,6 @@ function Get-RemoveTestDirs { } } -function Create-TemporaryDirectory { - $path = [System.IO.Path]::GetTempPath() - $child = [System.Guid]::NewGuid() - - return New-Item -ItemType Directory -Path (Join-Path $path $child) -} - function Get-NewPSResourceRepositoryFile { # register our own repositories with desired priority $powerShellGetPath = Join-Path -Path ([Environment]::GetFolderPath([System.Environment+SpecialFolder]::LocalApplicationData)) -ChildPath "PowerShellGet" @@ -465,40 +458,23 @@ function Create-PSScriptMetadata Process { $PSScriptInfoString = @" - <#PSScriptInfo - .VERSION$(if ($Version) {" $Version"}) - .GUID$(if ($Guid) {" $Guid"}) - .AUTHOR$(if ($Author) {" $Author"}) - .COMPANYNAME$(if ($CompanyName) {" $CompanyName"}) - .COPYRIGHT$(if ($Copyright) {" $Copyright"}) - .DESCRIPTION$(if ($Description) {" $Description"}) - .TAGS$(if ($Tags) {" $Tags"}) - .LICENSEURI$(if ($LicenseUri) {" $LicenseUri"}) - .PROJECTURI$(if ($ProjectUri) {" $ProjectUri"}) - .ICONURI$(if ($IconUri) {" $IconUri"}) - .EXTERNALMODULEDEPENDENCIES$(if ($ExternalModuleDependencies) {" $($ExternalModuleDependencies -join ',')"}) - .REQUIREDSCRIPTS$(if ($RequiredScripts) {" $($RequiredScripts -join ',')"}) - .EXTERNALSCRIPTDEPENDENCIES$(if ($ExternalScriptDependencies) {" $($ExternalScriptDependencies -join ',')"}) - .RELEASENOTES $($ReleaseNotes -join "`r`n") - .PRIVATEDATA$(if ($PrivateData) {" $PrivateData"}) - #> "@ return $PSScriptInfoString @@ -562,10 +538,5 @@ function CheckForExpectedPSGetInfo $psGetInfo.Tags | Should -BeExactly @('PSModule', 'PSEdition_Core') $psGetInfo.Type | Should -BeExactly 'Module' $psGetInfo.UpdatedDate.Year | Should -BeExactly 1 -<<<<<<< HEAD - $psGetInfo.Version.ToString() | Should -BeExactly '1.0.0' -} -======= $psGetInfo.Version | Should -Be "1.1.0" } ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e diff --git a/test/SavePSResource.Tests.ps1 b/test/SavePSResource.Tests.ps1 index 696c2732d..ba8f5281a 100644 --- a/test/SavePSResource.Tests.ps1 +++ b/test/SavePSResource.Tests.ps1 @@ -12,19 +12,6 @@ Describe 'Test Save-PSResource for PSResources' { Get-NewPSResourceRepositoryFile Register-LocalRepos -<<<<<<< HEAD - $TempDir = Create-TemporaryDirectory - } - - AfterEach { - # Delete all files and subdirectories in $Tempdir, but keep the directory $Tempdir - try { - Get-ChildItem -Path $TempDir -Force | foreach { $_.Delete($true)} - } - catch { - Get-ChildItem -Path $TempDir -Force | foreach { $_.Delete()} - } -======= $SaveDir = Join-Path $TestDrive 'SavedResources' New-Item -Item Directory $SaveDir -Force } @@ -32,7 +19,6 @@ Describe 'Test Save-PSResource for PSResources' { AfterEach { # Delte contents of save directory Remove-Item -Path (Join-Path $SaveDir '*') -Recurse -Force -ErrorAction SilentlyContinue ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } AfterAll { @@ -40,19 +26,6 @@ Describe 'Test Save-PSResource for PSResources' { } It "Save specific module resource by name" { -<<<<<<< HEAD - Save-PSResource -Name "TestModule" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - } - - It "Save specific script resource by name" { - Save-PSResource -Name "TestTestScript" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestTestScript.ps1" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 -======= Save-PSResource -Name "TestModule" -Repository $TestGalleryName -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestModule" $pkgDir | Should -Not -BeNullOrEmpty @@ -64,23 +37,10 @@ Describe 'Test Save-PSResource for PSResources' { $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestTestScript.ps1" $pkgDir | Should -Not -BeNullOrEmpty (Get-ChildItem $pkgDir.FullName).Count | Should -Be 1 ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } It "Save multiple resources by name" { $pkgNames = @("TestModule","TestModule99") -<<<<<<< HEAD - Save-PSResource -Name $pkgNames -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be @("TestModule","TestModule99") - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - } - - It "Should not save resource given nonexistant name" { - Save-PSResource -Name NonExistantModule -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -BeNullOrEmpty -======= Save-PSResource -Name $pkgNames -Repository $TestGalleryName -Path $SaveDir $pkgDirs = Get-ChildItem -Path $SaveDir | Where-Object { $_.Name -eq "TestModule" -or $_.Name -eq "TestModule99" } $pkgDirs.Count | Should -Be 2 @@ -92,46 +52,10 @@ Describe 'Test Save-PSResource for PSResources' { Save-PSResource -Name NonExistentModule -Repository $TestGalleryName -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "NonExistentModule" $pkgDir.Name | Should -BeNullOrEmpty ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } # Do some version testing, but Find-PSResource should be doing thorough testing It "Should save resource given name and exact version" { -<<<<<<< HEAD - Save-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.2.0" - } - - It "Should save resource given name and exact version with bracket syntax" { - Save-PSResource -Name "TestModule" -Version "[1.2.0]" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.2.0" - } - - It "Should save resource given name and exact range inclusive [1.0.0, 1.1.1]" { - Save-PSResource -Name "TestModule" -Version "[1.0.0, 1.1.1]" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.1.1" - } - - It "Should save resource given name and exact range exclusive (1.0.0, 1.1.1)" { - Save-PSResource -Name "TestModule" -Version "(1.0.0, 1.1.1)" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.1" -======= Save-PSResource -Name "TestModule" -Version "1.2.0" -Repository $TestGalleryName -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestModule" $pkgDir | Should -Not -BeNullOrEmpty @@ -161,7 +85,6 @@ Describe 'Test Save-PSResource for PSResources' { $pkgDir | Should -Not -BeNullOrEmpty $pkgDirVersion = Get-ChildItem -Path $pkgDir.FullName $pkgDirVersion.Name | Should -Be "1.1" ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } It "Should not save resource with incorrectly formatted version such as " -TestCases @( @@ -170,58 +93,6 @@ Describe 'Test Save-PSResource for PSResources' { ) { param($Version, $Description) -<<<<<<< HEAD - Save-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName -Path $TempDir - $res = Get-ChildItem $TempDir - $res | Should -BeNullOrEmpty - } - - It "Save resource when given Name, Version '*', should install the latest version" { - Save-PSResource -Name "TestModule" -Version "*" -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.3.0" - } - - It "Save resource with latest (including prerelease) version given Prerelease parameter" { - Save-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModulePrerelease" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "0.0.1" - } - - It "Save a module with a dependency" { - Save-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be @("PSGetTestDependency1","PSGetTestModule") - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - } - - It "Save resource via InputObject by piping from Find-PSresource" { - Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Save-PSResource -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.3.0" - } - - It "Save resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { - Set-PSResourceRepository PoshTestGallery -Trusted:$false - - Save-PSResource -Name "TestModule" -Repository $TestGalleryName -TrustRepository -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - $pkgVersion = Get-ChildItem $pkg - $pkgVersion.Name | Should -Be "1.3.0" - - Set-PSResourceRepository PoshTestGallery -Trusted -======= Save-PSResource -Name "TestModule" -Version $Version -Repository $TestGalleryName -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestModule" $pkgDir | Should -BeNullOrEmpty @@ -271,7 +142,6 @@ Describe 'Test Save-PSResource for PSResources' { finally { Set-PSResourceRepository PoshTestGallery -Trusted } ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } It "Save resource from local repository given Repository parameter" { @@ -280,17 +150,10 @@ Describe 'Test Save-PSResource for PSResources' { Get-ModuleResourcePublishedToLocalRepoTestDrive $publishModuleName $repoName Set-PSResourceRepository "psgettestlocal" -Trusted:$true -<<<<<<< HEAD - Save-PSResource -Name $publishModuleName -Repository $repoName -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be $publishModuleName - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 -======= Save-PSResource -Name $publishModuleName -Repository $repoName -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq $publishModuleName $pkgDir | Should -Not -BeNullOrEmpty (Get-ChildItem -Path $pkgDir.FullName).Count | Should -Be 1 ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e } It "Save specific module resource by name when no repository is specified" { @@ -298,25 +161,6 @@ Describe 'Test Save-PSResource for PSResources' { Set-PSResourceRepository "PSGallery" -Trusted:$True Set-PSResourceRepository "psgettestlocal2" -Trusted:$True -<<<<<<< HEAD - Save-PSResource -Name "TestModule" -Path $TempDir - $pkg = Get-ChildItem $TempDir - $pkg.Name | Should -Be "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - } - - It "Save specific module resource by name if no -Path param is specifed" { - Save-PSResource -Name "TestModule" -Repository $TestGalleryName - $pkg = Get-ChildItem . - - $pkg.Name | Should -Contain "TestModule" - (Get-ChildItem $pkg).Count | Should -BeGreaterThan 0 - - # Delete all files and subdirectories in the current , but keep the directory $Tempdir - $pkgDir = Join-Path -Path . -ChildPath "TestModule" - Remove-Item $pkgDir -Recurse -Force - } -======= Save-PSResource -Name "TestModule" -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestModule" $pkgDir | Should -Not -BeNullOrEmpty @@ -330,26 +174,22 @@ Describe 'Test Save-PSResource for PSResources' { $pkgDir = Get-ChildItem -Path . | Where-Object Name -eq "TestModule" $pkgDir | Should -Not -BeNullOrEmpty (Get-ChildItem $pkgDir.FullName).Count | Should -Be 1 - # Delete all files and subdirectories in the current , but keep the directory $SaveDir if (Test-Path -Path $pkgDir.FullName) { Remove-Item -Path $pkgDir.FullName -Recurse -Force -ErrorAction SilentlyContinue } } #> ->>>>>>> b7bb562fa3858b500061aab324d9af4c4a7e375e <# # This needs to be manually tested due to prompt It "Install resource should prompt 'trust repository' if repository is not trusted" { Set-PSResourceRepository PoshTestGallery -Trusted:$false - Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" - Set-PSResourceRepository PoshTestGallery -Trusted } #> -} \ No newline at end of file +} From c607a111b58fe50f03aca6a1194493d65b58dca0 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 13:17:38 -0400 Subject: [PATCH 41/62] pull in Install test from master --- test/InstallPSResource.Tests.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 2c1c6eb87..56238bfef 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -217,13 +217,16 @@ Describe 'Test Install-PSResource for Module' { $pkg.Name | Should -Be "testModuleWithlicense" $pkg.Version | Should -Be "0.0.1.0" } + # This needs to be manually tested due to prompt It "Install resource should prompt 'trust repository' if repository is not trusted" { Set-PSResourceRepository PoshTestGallery -Trusted:$false + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" + Set-PSResourceRepository PoshTestGallery -Trusted } #> From 05fbb0ea41ca29c706f165d954afccb98cdc94ff Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 13:19:23 -0400 Subject: [PATCH 42/62] pull in PSGetTestUtils form master --- test/PSGetTestUtils.psm1 | 17 +++++++++++++++++ test/SavePSResource.Tests.ps1 | 5 ++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index d1efbbab9..f3410ffaf 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -458,23 +458,40 @@ function Create-PSScriptMetadata Process { $PSScriptInfoString = @" + <#PSScriptInfo + .VERSION$(if ($Version) {" $Version"}) + .GUID$(if ($Guid) {" $Guid"}) + .AUTHOR$(if ($Author) {" $Author"}) + .COMPANYNAME$(if ($CompanyName) {" $CompanyName"}) + .COPYRIGHT$(if ($Copyright) {" $Copyright"}) + .DESCRIPTION$(if ($Description) {" $Description"}) + .TAGS$(if ($Tags) {" $Tags"}) + .LICENSEURI$(if ($LicenseUri) {" $LicenseUri"}) + .PROJECTURI$(if ($ProjectUri) {" $ProjectUri"}) + .ICONURI$(if ($IconUri) {" $IconUri"}) + .EXTERNALMODULEDEPENDENCIES$(if ($ExternalModuleDependencies) {" $($ExternalModuleDependencies -join ',')"}) + .REQUIREDSCRIPTS$(if ($RequiredScripts) {" $($RequiredScripts -join ',')"}) + .EXTERNALSCRIPTDEPENDENCIES$(if ($ExternalScriptDependencies) {" $($ExternalScriptDependencies -join ',')"}) + .RELEASENOTES $($ReleaseNotes -join "`r`n") + .PRIVATEDATA$(if ($PrivateData) {" $PrivateData"}) + #> "@ return $PSScriptInfoString diff --git a/test/SavePSResource.Tests.ps1 b/test/SavePSResource.Tests.ps1 index ba8f5281a..6a20641e3 100644 --- a/test/SavePSResource.Tests.ps1 +++ b/test/SavePSResource.Tests.ps1 @@ -174,6 +174,7 @@ Describe 'Test Save-PSResource for PSResources' { $pkgDir = Get-ChildItem -Path . | Where-Object Name -eq "TestModule" $pkgDir | Should -Not -BeNullOrEmpty (Get-ChildItem $pkgDir.FullName).Count | Should -Be 1 + # Delete all files and subdirectories in the current , but keep the directory $SaveDir if (Test-Path -Path $pkgDir.FullName) { Remove-Item -Path $pkgDir.FullName -Recurse -Force -ErrorAction SilentlyContinue @@ -185,11 +186,13 @@ Describe 'Test Save-PSResource for PSResources' { # This needs to be manually tested due to prompt It "Install resource should prompt 'trust repository' if repository is not trusted" { Set-PSResourceRepository PoshTestGallery -Trusted:$false + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -confirm:$false $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" + Set-PSResourceRepository PoshTestGallery -Trusted } #> -} +} \ No newline at end of file From ca35b8c7e63d8f3d8715f3b15627c5fffe9a8330 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 13:24:27 -0400 Subject: [PATCH 43/62] add helper for filtering wildcards that handles * --- src/code/Utils.cs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/code/Utils.cs b/src/code/Utils.cs index b36e80130..37bc44387 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -65,6 +65,53 @@ public static string[] GetStringArray(ArrayList list) return strArray; } + public static string[] FilterWildcards( + string[] pkgNames, + out string[] errorMsgs, + out bool isContainWildcard) + { + List namesWithSupportedWildcards = new List(); + List errorMsgsList = new List(); + + if (pkgNames == null) + { + isContainWildcard = true; + errorMsgs = errorMsgsList.ToArray(); + return new string[] {"*"}; + } + + isContainWildcard = false; + foreach (string name in pkgNames) + { + if (WildcardPattern.ContainsWildcardCharacters(name)) + { + if (String.Equals(name, "*", StringComparison.InvariantCultureIgnoreCase)) + { + isContainWildcard = true; + errorMsgs = new string[] {}; + return new string[] {"*"}; + } + + if (name.Contains("?") || name.Contains("[")) + { + errorMsgsList.Add(String.Format("-Name with wildcards '?' and '[' are not supported for Find-PSResource so Name entry: {0} will be discarded.", name)); + } + else + { + isContainWildcard = true; + namesWithSupportedWildcards.Add(name); + } + } + else + { + namesWithSupportedWildcards.Add(name); + } + + } + + errorMsgs = errorMsgsList.ToArray(); + return namesWithSupportedWildcards.ToArray(); + } public static string[] FilterOutWildcardNames( string[] pkgNames, out string[] errorMsgs) From 2547dd27041ad4dcd05a1feab35ca9128fa9ede1 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 14:14:41 -0400 Subject: [PATCH 44/62] fix failing tests --- test/UpdatePSResource.Tests.ps1 | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 0baa0b36c..75a04da40 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -3,7 +3,7 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force -Describe 'Test Install-PSResource for Module' { +Describe 'Test Update-PSResource for Module' { BeforeAll{ @@ -87,18 +87,18 @@ Describe 'Test Install-PSResource for Module' { $isPkgUpdated | Should -BeTrue } - $testCases2 = @{Version="[2.10.0.0]"; ExpectedVersions=@("2.10.0.0"); Reason="validate version, exact match"}, - @{Version="2.10.0.0"; ExpectedVersions=@("2.10.0.0"); Reason="validate version, exact match without bracket syntax"}, - @{Version="[2.5.0.0, 2.8.0.0]"; ExpectedVersions=@("2.5.0.0", "2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, exact range inclusive"}, - @{Version="(2.5.0.0, 2.8.0.0)"; ExpectedVersions=@("2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0"); Reason="validate version, exact range exclusive"}, - @{Version="(2.9.4.0,)"; ExpectedVersions=@("2.10.0.0", "2.10.1.0", "2.10.2.0"); Reason="validate version, minimum version exclusive"}, - @{Version="[2.9.4.0,)"; ExpectedVersions=@("2.9.4.0", "2.10.0.0", "2.10.1.0", "2.10.2.0"); Reason="validate version, minimum version inclusive"}, - @{Version="(,2.0.0.0)"; ExpectedVersions=@("1.9.0.0"); Reason="validate version, maximum version exclusive"}, - @{Version="(,2.0.0.0]"; ExpectedVersions=@("1.9.0.0", "2.0.0.0"); Reason="validate version, maximum version inclusive"}, - @{Version="[2.5.0.0, 2.8.0.0)"; ExpectedVersions=@("2.5.0.0", "2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, mixed inclusive minimum and exclusive maximum version"} - @{Version="(2.5.0.0, 2.8.0.0]"; ExpectedVersions=@("2.5.1.0", "2.5.2.0", "2.5.3.0", "2.5.4.0", "2.6.0.0", "2.7.0.0", "2.8.0.0"); Reason="validate version, mixed exclusive minimum and inclusive maximum version"} - - It "find resource when given Name to " -TestCases $testCases2{ + $testCases2 = @{Version="[1.3.0.0]"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, exact match"}, + @{Version="1.3.0.0"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, exact match without bracket syntax"}, + @{Version="[1.1.1.0, 1.3.0.0]"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, exact range inclusive"}, + @{Version="(1.1.1.0, 1.3.0.0)"; ExpectedVersions=@("1.1.0.0", "1.2.0.0"); Reason="validate version, exact range exclusive"}, + @{Version="(1.1.1.0,)"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, minimum version exclusive"}, + @{Version="[1.1.1.0,)"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, minimum version inclusive"}, + @{Version="(,1.3.0.0)"; ExpectedVersions=@("1.1.0.0", "1.2.0.0"); Reason="validate version, maximum version exclusive"}, + @{Version="(,1.3.0.0]"; ExpectedVersions=@("1.1.0.0", "1.3.0.0"); Reason="validate version, maximum version inclusive"}, + @{Version="[1.1.1.0, 1.3.0.0)"; ExpectedVersions=@("1.1.0.0", "1.2.0.0"); Reason="validate version, mixed inclusive minimum and exclusive maximum version"} + @{Version="(1.1.1.0, 1.3.0.0]"; ExpectedVersions=@("1.1.0.0", "1.2.0.0"); Reason="validate version, mixed exclusive minimum and inclusive maximum version"} + + It "update resource when given Name to " -TestCases $testCases2{ param($Version, $ExpectedVersions) Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName @@ -248,7 +248,7 @@ Describe 'Test Install-PSResource for Module' { # # $pkg.Version | Should -Be "0.0.1.0" # } - It "Install resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { + It "update resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName Set-PSResourceRepository PoshTestGallery -Trusted:$false From 8b789220dfb92639d339ead071170929864bbced Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 16:52:48 -0400 Subject: [PATCH 45/62] resolve feedback from PR --- src/code/UpdatePSResource.cs | 131 +++++++++++++------------------- src/code/Utils.cs | 9 ++- test/UpdatePSResource.Tests.ps1 | 3 +- 3 files changed, 61 insertions(+), 82 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 2021ba772..b7c28e6fc 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -14,24 +14,20 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { /// - /// The Save-PSResource cmdlet combines the Save-Module, Save-Script cmdlets from V2. - /// It saves from a package found from a repository (local or remote) based on the -Name parameter argument. - /// It does not return an object. Other parameters allow the returned results to be further filtered. + /// The Update-PSResource cmdlet replaces the Update-Module and Update-Script cmdlets from V2. + /// It updates an already installed package based on the -Name parameter argument. + /// It does not return an object. Other parameters allow the package to be updated to be further filtered. /// [Cmdlet(VerbsData.Update, "PSResource", DefaultParameterSetName = NameParameterSet, - SupportsShouldProcess = true, - HelpUri = "")] - [OutputType(typeof(PSResourceInfo))] - public sealed - class UpdatePSResource : PSCmdlet + SupportsShouldProcess = true)] + public sealed class UpdatePSResource : PSCmdlet { #region Members private const string NameParameterSet = "NameParameterSet"; - private const string InputObjectParameterSet = "InputObjectParameterSet"; private CancellationTokenSource _source; private CancellationToken _cancellationToken; private List _pathsToInstallPkg; @@ -73,14 +69,12 @@ class UpdatePSResource : PSCmdlet /// Specifies the scope of the resource to update. /// [Parameter(ParameterSetName = NameParameterSet)] - [ValidateNotNullOrEmpty] public ScopeType Scope { get; set; } /// /// When specified, supresses being prompted for untrusted sources. /// [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] public SwitchParameter TrustRepository { get; set; } /// @@ -93,29 +87,20 @@ class UpdatePSResource : PSCmdlet /// Supresses progress information. /// [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] public SwitchParameter Quiet { get; set; } /// /// For resources that require a license, AcceptLicense automatically accepts the license agreement during the update. /// [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] public SwitchParameter AcceptLicense { get; set; } /// /// When specified, bypasses checks for TrustRepository and AcceptLicense and updates the package. /// [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] public SwitchParameter Force { get; set; } - /// - /// Used to pass in an object via pipeline to update. - /// - [Parameter(ValueFromPipeline = true, ParameterSetName = NameParameterSet)] - public object[] InputObject { get; set; } - #endregion #region Methods @@ -127,15 +112,10 @@ protected override void BeginProcessing() _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, Scope); } - protected override void StopProcessing() - { - _source.Cancel(); - } - - protected override void ProcessRecord() + private string[] ProcessNames(string[] namesToProcess, VersionRange versionRange) { - Name = Utils.FilterWildcards(Name, out string[] errorMsgs, out bool isContainWildcard); - + namesToProcess = Utils.FilterWildcards(namesToProcess, out string[] errorMsgs, out bool nameContainsWildcard); + foreach (string error in errorMsgs) { WriteError(new ErrorRecord( @@ -144,32 +124,20 @@ protected override void ProcessRecord() ErrorCategory.InvalidArgument, this)); } - - if (Name.Length == 1 && String.Equals(Name[0], "*", StringComparison.InvariantCultureIgnoreCase)) - { - WriteVerbose("Name was detected to be (or contain an element equal to): '*', so all packages will be updated"); - } - + // this catches the case where Name wasn't input as null or empty, // but after filtering out unsupported wildcard names there are no elements left in Name if (Name.Length == 0) { - return; + return namesToProcess; } - VersionRange versionRange = new VersionRange(); - - if (Version !=null && !Utils.TryParseVersionOrVersionRange(Version, out versionRange)) + if (String.Equals(namesToProcess[0], "*", StringComparison.InvariantCultureIgnoreCase)) { - WriteError(new ErrorRecord( - new PSInvalidOperationException("Cannot parse Version parameter provided into VersionRange"), - "ErrorParsingVersionParamIntoVersionRange", - ErrorCategory.InvalidArgument, - this)); - return; + WriteVerbose("Name was detected to be (or contain an element equal to): '*', so all packages will be updated"); } - if (isContainWildcard) + if (nameContainsWildcard) { // any of the Name entries contains a supported wildcard // then we need to use GetHelper (Get-InstalledPSResource logic) to find which packages are installed that match @@ -178,50 +146,57 @@ protected override void ProcessRecord() GetHelper getHelper = new GetHelper( cmdletPassedIn: this); - Name = getHelper.FilterPkgPaths( + namesToProcess = getHelper.FilterPkgPaths( name: Name, versionRange: versionRange, pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); } + return namesToProcess; + + } + + protected override void ProcessRecord() + { + // this can handle Version == null and only returns false if the range was incorrectly formatted and couldn't be parsed. + if (!Utils.TryParseVersionOrVersionRange(Version, out VersionRange versionRange)) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException("Cannot parse Version parameter provided into VersionRange"), + "ErrorParsingVersionParamIntoVersionRange", + ErrorCategory.InvalidArgument, + this)); + return; + } + + Name = ProcessNames(Name, versionRange); + InstallHelper installHelper = new InstallHelper( updatePkg: true, savePkg: false, cancellationToken: _cancellationToken, cmdletPassedIn: this); - switch (ParameterSetName) - { - case NameParameterSet: - installHelper.InstallPackages( - names: Name, - versionRange: versionRange, - prerelease: Prerelease, - repository: Repository, - acceptLicense: AcceptLicense, - quiet: Quiet, - reinstall: false, - force: Force, - trustRepository: TrustRepository, - noClobber: false, - credential: Credential, - requiredResourceFile: null, - requiredResourceJson: null, - requiredResourceHash: null, - specifiedPath: null, - asNupkg: false, - includeXML: true, - pathsToInstallPkg: _pathsToInstallPkg); - break; - - case InputObjectParameterSet: - // TODO - break; - - default: - Dbg.Assert(false, "Invalid parameter set"); - break; - } + + installHelper.InstallPackages( + names: Name, + versionRange: versionRange, + prerelease: Prerelease, + repository: Repository, + acceptLicense: AcceptLicense, + quiet: Quiet, + reinstall: false, + force: Force, + trustRepository: TrustRepository, + noClobber: false, + credential: Credential, + requiredResourceFile: null, + requiredResourceJson: null, + requiredResourceHash: null, + specifiedPath: null, + asNupkg: false, + includeXML: true, + pathsToInstallPkg: _pathsToInstallPkg); } #endregion diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 37bc44387..1d8408ba5 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -112,6 +112,7 @@ public static string[] FilterWildcards( errorMsgs = errorMsgsList.ToArray(); return namesWithSupportedWildcards.ToArray(); } + public static string[] FilterOutWildcardNames( string[] pkgNames, out string[] errorMsgs) @@ -190,9 +191,13 @@ public static bool TryParseVersionOrVersionRange( string version, out VersionRange versionRange) { - versionRange = null; + // versionRange = null; - if (version == null) { return false; } + if (version == null) { + versionRange = VersionRange.All; + // return false; + return true; + } if (version.Trim().Equals("*")) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 75a04da40..d721cdc9a 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -147,8 +147,7 @@ Describe 'Test Update-PSResource for Module' { { if ([System.Version]$pkg.Version -gt [System.Version]"1.0.2.0") { - Write-Host $pkg.Version" and prerelease data is:"$pkg.PrivateData.PSData.Prerelease"." - # $pkg.PrereleaseLabel | Should -Be "-alpha1" (todo: for some reason get-installedpsresource doesn't get this!) + $pkg.PrereleaseLabel | Should -Be "alpha1" $isPkgUpdated = $true } } From 7098b2d7a833b7a1160b23558a703385caa91472 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 16:54:26 -0400 Subject: [PATCH 46/62] add code to skip if not windows tests --- test/UpdatePSResource.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index d721cdc9a..25b19aa62 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -156,7 +156,7 @@ Describe 'Test Update-PSResource for Module' { } # Windows only - It "update resource under CurrentUser scope" { + It "update resource under CurrentUser scope" -skip:(!$IsWindows) { # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the AllUsers path too Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope CurrentUser @@ -182,7 +182,7 @@ Describe 'Test Update-PSResource for Module' { } # Windows only - It "update resource under AllUsers scope" { + It "update resource under AllUsers scope" -skip:(!$IsWindows) { # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the CurrentUser path too Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope AllUsers @@ -205,7 +205,7 @@ Describe 'Test Update-PSResource for Module' { } # Windows only - It "update resource under no specified scope" { + It "update resource under no specified scope" -skip:(!$IsWindows) { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName From 2c8e7e9628518c475ae0b5b66e96c69a0032a9c0 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 16:57:10 -0400 Subject: [PATCH 47/62] rename Utils helper method and remove unecessary comments --- src/code/UpdatePSResource.cs | 2 +- src/code/Utils.cs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index b7c28e6fc..323749ed2 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -114,7 +114,7 @@ protected override void BeginProcessing() private string[] ProcessNames(string[] namesToProcess, VersionRange versionRange) { - namesToProcess = Utils.FilterWildcards(namesToProcess, out string[] errorMsgs, out bool nameContainsWildcard); + namesToProcess = Utils.ProcessNameWildcards(namesToProcess, out string[] errorMsgs, out bool nameContainsWildcard); foreach (string error in errorMsgs) { diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 1d8408ba5..7bd444b4c 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -65,7 +65,7 @@ public static string[] GetStringArray(ArrayList list) return strArray; } - public static string[] FilterWildcards( + public static string[] ProcessNameWildcards( string[] pkgNames, out string[] errorMsgs, out bool isContainWildcard) @@ -191,15 +191,11 @@ public static bool TryParseVersionOrVersionRange( string version, out VersionRange versionRange) { - // versionRange = null; - if (version == null) { versionRange = VersionRange.All; - // return false; return true; } - if (version.Trim().Equals("*")) { versionRange = VersionRange.All; From 123e9626aff91e1f2855856b526f07474b6f4d3c Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 20 Jul 2021 18:25:51 -0400 Subject: [PATCH 48/62] add SupportShouldProcess --- src/code/UpdatePSResource.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 323749ed2..6b4f6a9b0 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -1,4 +1,5 @@ +using System.Text; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -62,6 +63,7 @@ public sealed class UpdatePSResource : PSCmdlet /// If not specified, search will include all currently registered repositories in order of highest priority. /// [Parameter(ParameterSetName = NameParameterSet)] + [ArgumentCompleter(typeof(RepositoryNameCompleter))] [ValidateNotNullOrEmpty] public string[] Repository { get; set; } @@ -171,6 +173,12 @@ protected override void ProcessRecord() Name = ProcessNames(Name, versionRange); + if (!ShouldProcess(string.Format("package to update: '{0}'", String.Join(", ", Name)))) + { + WriteVerbose("ShouldProcess was set to false."); + return; + } + InstallHelper installHelper = new InstallHelper( updatePkg: true, savePkg: false, @@ -178,6 +186,7 @@ protected override void ProcessRecord() cmdletPassedIn: this); + installHelper.InstallPackages( names: Name, versionRange: versionRange, From 86e50ce87dfff6f6d42db56dff79cc16e1a24cd0 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 21 Jul 2021 13:01:31 -0400 Subject: [PATCH 49/62] fix tests for updating with Scope, and use Get-InstalledPSResource instead of Get-Module --- test/UpdatePSResource.Tests.ps1 | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 25b19aa62..f8594664f 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -140,7 +140,7 @@ Describe 'Test Update-PSResource for Module' { Install-PSResource -Name "PSGetTestModule" -Version "1.0.2.0" -Repository $TestGalleryName Update-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName - $res = Get-InstalledPSResource "PSGetTestModule" + $res = Get-InstalledPSResource -Name "PSGetTestModule" $isPkgUpdated = $false foreach ($pkg in $res) @@ -158,22 +158,19 @@ Describe 'Test Update-PSResource for Module' { # Windows only It "update resource under CurrentUser scope" -skip:(!$IsWindows) { # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the AllUsers path too + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope CurrentUser - # TODO: use Get-InstalledPSResource - # $res = Get-InstalledPSResource -Name "TestModule" - # $res.Name | Should -Be "TestModule" - # $res.InstalledLocation.Contains("Documents") | Should -Be $true - # TODO: turn foreach loop bool return into a helper function which takes the list output of get, name and version of a package we wish to search for - $res = Get-Module "TestModule" -ListAvailable + $res = Get-InstalledPSResource -Name "TestModule" $isPkgUpdated = $false foreach ($pkg in $res) { if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { - $pkg.Path.Contains("Documents") | Should -Be $true + $pkg.InstalledLocation.Contains("Documents") | Should -Be $true $isPkgUpdated = $true } } @@ -183,20 +180,18 @@ Describe 'Test Update-PSResource for Module' { # Windows only It "update resource under AllUsers scope" -skip:(!$IsWindows) { - # TODO: perhaps also install TestModule with the highest version (the one above 1.2.0.0) to the CurrentUser path too Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName -Scope AllUsers - # $res = Get-InstalledPSResource -Name "TestModule" - # $res.Name | Should -Be "TestModule" - # $res.InstalledLocation.Contains("Program Files") | Should -Be $true - $res = Get-Module "TestModule" -ListAvailable + $res = Get-InstalledPSResource -Name "TestModule" $isPkgUpdated = $false foreach ($pkg in $res) { if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { - $pkg.Path.Contains("Program Files") | Should -Be $true + $pkg.InstalledLocation.Contains("Program Files") | Should -Be $true $isPkgUpdated = $true } } @@ -209,14 +204,14 @@ Describe 'Test Update-PSResource for Module' { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName Update-PSResource -Name "TestModule" -Version "1.2.0.0" -Repository $TestGalleryName - $res = Get-Module "TestModule" -ListAvailable + $res = Get-InstalledPSResource -Name "TestModule" $isPkgUpdated = $false foreach ($pkg in $res) { if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { - $pkg.Path.Contains("Documents") | Should -Be $true + $pkg.InstalledLocation.Contains("Documents") | Should -Be $true $isPkgUpdated = $true } } @@ -230,8 +225,6 @@ Describe 'Test Update-PSResource for Module' { # Update-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense # $pkg = Get-InstalledPSResource "testModuleWithlicense" - # $res = Get-Module "TestModule" -ListAvailable - # $isPkgUpdated = $false # foreach ($pkg in $res) # { From f104e7e787f74518a87d471cf7b7868e539d6b3f Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 27 Jul 2021 13:33:24 -0400 Subject: [PATCH 50/62] revert change to TryParseVersionOrVersionRange --- src/code/UpdatePSResource.cs | 14 +++++++++----- src/code/Utils.cs | 6 ++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 6b4f6a9b0..df879f099 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -1,5 +1,4 @@ -using System.Text; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -8,6 +7,7 @@ using Dbg = System.Diagnostics.Debug; using System.Linq; using System.Management.Automation; +using System.Text; using System.Threading; using Microsoft.PowerShell.PowerShellGet.UtilClasses; using NuGet.Versioning; @@ -160,9 +160,15 @@ private string[] ProcessNames(string[] namesToProcess, VersionRange versionRange protected override void ProcessRecord() { - // this can handle Version == null and only returns false if the range was incorrectly formatted and couldn't be parsed. - if (!Utils.TryParseVersionOrVersionRange(Version, out VersionRange versionRange)) + VersionRange versionRange; + + // handle case where Version == null + if (Version == null) { + versionRange = VersionRange.All; + } + else if (!Utils.TryParseVersionOrVersionRange(Version, out versionRange)) { + // Only returns false if the range was incorrectly formatted and couldn't be parsed. WriteError(new ErrorRecord( new PSInvalidOperationException("Cannot parse Version parameter provided into VersionRange"), "ErrorParsingVersionParamIntoVersionRange", @@ -185,8 +191,6 @@ protected override void ProcessRecord() cancellationToken: _cancellationToken, cmdletPassedIn: this); - - installHelper.InstallPackages( names: Name, versionRange: versionRange, diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 7bd444b4c..048e2d6b4 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -191,10 +191,8 @@ public static bool TryParseVersionOrVersionRange( string version, out VersionRange versionRange) { - if (version == null) { - versionRange = VersionRange.All; - return true; - } + versionRange = null; + if (version == null) { return false; } if (version.Trim().Equals("*")) { From f115d94b9577667250077eb7c5cc8ed97f689fa2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 28 Jul 2021 17:03:29 -0400 Subject: [PATCH 51/62] remove cancellationToken --- src/code/UpdatePSResource.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index df879f099..8b7f2b631 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -29,8 +29,6 @@ public sealed class UpdatePSResource : PSCmdlet #region Members private const string NameParameterSet = "NameParameterSet"; - private CancellationTokenSource _source; - private CancellationToken _cancellationToken; private List _pathsToInstallPkg; #endregion @@ -109,8 +107,6 @@ public sealed class UpdatePSResource : PSCmdlet protected override void BeginProcessing() { - _source = new CancellationTokenSource(); - _cancellationToken = _source.Token; _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, Scope); } @@ -188,7 +184,6 @@ protected override void ProcessRecord() InstallHelper installHelper = new InstallHelper( updatePkg: true, savePkg: false, - cancellationToken: _cancellationToken, cmdletPassedIn: this); installHelper.InstallPackages( From 39e3bd99b595d026303d422d25b93561c1fcb0d8 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 28 Jul 2021 17:12:44 -0400 Subject: [PATCH 52/62] remove PSGalleryName variable not needed --- test/UpdatePSResource.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index f8594664f..5b6fc98f3 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -8,7 +8,6 @@ Describe 'Test Update-PSResource for Module' { BeforeAll{ $TestGalleryName = Get-PoshTestGalleryName - $PSGalleryName = Get-PSGalleryName $NuGetGalleryName = Get-NuGetGalleryName Get-NewPSResourceRepositoryFile Register-LocalRepos From 87bbcf1c3d656dc822bc5dff3e0c136acb9786dc Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 28 Jul 2021 17:35:24 -0400 Subject: [PATCH 53/62] update design and help docs for UpdatePSResource --- Docs/UpdatePSResource.md | 36 +++------------ help/Update-PSResource.md | 77 ++++++++++++++------------------- test/UpdatePSResource.Tests.ps1 | 2 +- 3 files changed, 38 insertions(+), 77 deletions(-) diff --git a/Docs/UpdatePSResource.md b/Docs/UpdatePSResource.md index c22376c9d..dceff9cd7 100644 --- a/Docs/UpdatePSResource.md +++ b/Docs/UpdatePSResource.md @@ -9,16 +9,11 @@ Other parameters allow the returned results to be further filtered. ### NameParameterSet (Default) ``` PowerShell -[[-Name] ] [-Version ] [-Prerelease] [-Scope ] +[[-Name] ] [-Version ] [-Prerelease] [-Scope ] [-Repository ] [-TrustRepository] [-Credential ] [-Quiet] [-AcceptLicense] [-NoClobber] [-WhatIf] [-Confirm] [] ``` -### InputObjectParameterSet -``` PowerShell -[[-InputObject] [-WhatIf] [-Confirm] [] -``` - ## Parameters ### -Name @@ -74,8 +69,8 @@ Parameter Sets: NameParameterSet Specifies the scope of the resource to update. ```yml -Type: string -Parameter Sets: NameParameterSet, RequiredResourceFileParameterSet +Type: Microsoft.PowerShell.PowerShellGet.UtilClasses.ScopeType +Parameter Sets: NameParameterSet AllowedValues: 'CurrentUser','AllUsers' ``` @@ -85,7 +80,7 @@ Suppresses being prompted for untrusted sources. ```yml Type: SwitchParameter -Parameter Sets: NameParameterSet, RequiredResourceFileParameterSet +Parameter Sets: NameParameterSet ``` ### -Credential @@ -115,22 +110,11 @@ Type: SwitchParameter Parameter Sets: (All) ``` -### -NoClobber - -Prevents updating modules that have the same cmdlets as a differently named module already - -```yml -Type: SwitchParameter -Parameter Sets: NameParameterSet -``` - - ### Outputs No output. ## Notes -Input object still needs to be implemented. Should a -PassThru parameter be added? @@ -138,7 +122,7 @@ Should a -PassThru parameter be added? Most update tests can be performed on a local repository. -Some tests should be performed on remote repository (PSGallery) to verify remote operation, but can be limited. +Some tests should be performed on remote repository (PoshTestGallery) to verify remote operation, but can be limited. ### -Name param @@ -149,11 +133,6 @@ Some tests should be performed on remote repository (PSGallery) to verify remote - Errors: Not found (single name, wildcard, multiple name) - Errors: Repository: Invalid name, connection error, etc -### -Type InputObject - -- Validate pipeline input -- Errors: The object passed in is not the correct type - ### -Version param - Validate the resource is updated to the correct version @@ -193,11 +172,6 @@ Some tests should be performed on remote repository (PSGallery) to verify remote - Validate that modules which require license agreements are approved without a prompt -### -NoClobber - -- Validate that resources are not overwritten when flag is passed - - ## Work Items ### Create cmdlet and parameter sets diff --git a/help/Update-PSResource.md b/help/Update-PSResource.md index 70478254c..158815573 100644 --- a/help/Update-PSResource.md +++ b/help/Update-PSResource.md @@ -8,39 +8,45 @@ schema: 2.0.0 # Update-PSResource ## SYNOPSIS -{{ Fill in the Synopsis }} +Updates a package already installed on the user's machine. ## SYNTAX ### NameParameterSet (Default) ``` Update-PSResource [-Name] [-Version ] [-Prerelease] [-Repository ] - [-Scope ] [-TrustRepository] [-Credential ] [-Quiet] [-AcceptLicense] [-NoClobber] - [-WhatIf] [-Confirm] [] -``` - -### RequiredResourceFileParameterSet -``` -Update-PSResource [-Scope ] [-TrustRepository] [-Quiet] [-AcceptLicense] [-WhatIf] [-Confirm] - [] + [-Scope ] [-TrustRepository] [-Credential ] [-Quiet] [-AcceptLicense] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION -{{ Fill in the Description }} +The Update-PSResource cmdlet replaces the Update-Module and Update-Script cmdlets from V2. +It updates an already installed package based on the -Name parameter argument. +It does not return an object. Other parameters allow the package to be updated to be further filtered. ## EXAMPLES ### Example 1 ```powershell -PS C:\> {{ Add example code here }} +PS C:\> Get-InstalledPSResource -Name "TestModule" + Name Version Prerelease Description + ---- ------- ---------- ----------- + TestModule 1.2.0 test + + Update-PSResource -Name "TestModule" + Get-InstalledPSResource -Name "TestModule" + Name Version Prerelease Description + ---- ------- ---------- ----------- + TestModule 1.3.0 test + TestModule 1.2.0 test + ``` -{{ Add example description here }} +In this example, the user already has the TestModule package installed and they update the package. Update-PSResource will install the latest version of the package without deleting the older version installed. ## PARAMETERS ### -AcceptLicense -{{ Fill AcceptLicense Description }} +For resources that require a license, AcceptLicense automatically accepts the license agreement during the update. ```yaml Type: System.Management.Automation.SwitchParameter @@ -55,7 +61,7 @@ Accept wildcard characters: False ``` ### -Credential -{{ Fill Credential Description }} +Specifies optional credentials to be used when accessing a private repository. ```yaml Type: System.Management.Automation.PSCredential @@ -65,12 +71,12 @@ Aliases: Required: False Position: Named Default value: None -Accept pipeline input: True (ByPropertyName) +Accept pipeline input: False Accept wildcard characters: False ``` ### -Name -{{ Fill Name Description }} +Specifies name of a resource or resources to update. ```yaml Type: System.String[] @@ -79,28 +85,13 @@ Aliases: Required: True Position: 0 -Default value: None +Default value: "*" Accept pipeline input: True (ByPropertyName, ByValue) -Accept wildcard characters: False -``` - -### -NoClobber -{{ Fill NoClobber Description }} - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: NameParameterSet -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False +Accept wildcard characters: True ``` ### -Prerelease -{{ Fill Prerelease Description }} +When specified, allows updating to a prerelease version. ```yaml Type: System.Management.Automation.SwitchParameter @@ -115,7 +106,7 @@ Accept wildcard characters: False ``` ### -Quiet -{{ Fill Quiet Description }} +Supresses progress information. ```yaml Type: System.Management.Automation.SwitchParameter @@ -130,7 +121,8 @@ Accept wildcard characters: False ``` ### -Repository -{{ Fill Repository Description }} +Specifies one or more repository names to update packages from. +If not specified, search for packages to update will include all currently registered repositories in order of highest priority. ```yaml Type: System.String[] @@ -145,10 +137,10 @@ Accept wildcard characters: False ``` ### -Scope -{{ Fill Scope Description }} +Specifies the scope of the resource to update. ```yaml -Type: System.String +Type: Microsoft.PowerShell.PowerShellGet.UtilClasses.ScopeType Parameter Sets: (All) Aliases: Accepted values: CurrentUser, AllUsers @@ -161,7 +153,7 @@ Accept wildcard characters: False ``` ### -TrustRepository -{{ Fill TrustRepository Description }} +Specifies optional credentials to be used when accessing a private repository. ```yaml Type: System.Management.Automation.SwitchParameter @@ -176,7 +168,7 @@ Accept wildcard characters: False ``` ### -Version -{{ Fill Version Description }} +Specifies the version the resource is to be updated to. ```yaml Type: System.String @@ -228,15 +220,10 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ### System.String[] -### System.Management.Automation.PSCredential - ## OUTPUTS -### System.Object - ## NOTES ## RELATED LINKS []() - diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 5b6fc98f3..bf95fb467 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -3,7 +3,7 @@ Import-Module "$psscriptroot\PSGetTestUtils.psm1" -Force -Describe 'Test Update-PSResource for Module' { +Describe 'Test Update-PSResource' { BeforeAll{ From b6d7c8d76f0d1f95192a62ab2baefaa056a96b69 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 28 Jul 2021 17:39:00 -0400 Subject: [PATCH 54/62] remove comments from Update tests --- test/UpdatePSResource.Tests.ps1 | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index bf95fb467..599f7a280 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -259,17 +259,4 @@ Describe 'Test Update-PSResource' { $isPkgUpdated | Should -BeTrue Set-PSResourceRepository PoshTestGallery -Trusted } - - - # update script resource - # Name - # Version - # Prerelease - # Scope - # AcceptLicense - # TrustRepository - # Credential - # InputObject - - } \ No newline at end of file From b4aea7d7696d9fa9c164564acb2748c62c1a9cae Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Wed, 28 Jul 2021 18:09:22 -0400 Subject: [PATCH 55/62] remove call to Register-LocalRepo helper as is not needed --- test/UpdatePSResource.Tests.ps1 | 78 ++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 599f7a280..f13345acc 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -10,7 +10,6 @@ Describe 'Test Update-PSResource' { $TestGalleryName = Get-PoshTestGalleryName $NuGetGalleryName = Get-NuGetGalleryName Get-NewPSResourceRepositoryFile - Register-LocalRepos Get-PSResourceRepository } @@ -151,7 +150,7 @@ Describe 'Test Update-PSResource' { } } - $isPkgUpdated | Should -BeTrue + $isPkgUpdated | Should -Be $true } # Windows only @@ -174,7 +173,7 @@ Describe 'Test Update-PSResource' { } } - $isPkgUpdated | Should -BeTrue + $isPkgUpdated | Should -Be $true } # Windows only @@ -195,7 +194,7 @@ Describe 'Test Update-PSResource' { } } - $isPkgUpdated | Should -BeTrue + $isPkgUpdated | Should -Be $true } # Windows only @@ -215,7 +214,76 @@ Describe 'Test Update-PSResource' { } } - $isPkgUpdated | Should -BeTrue + $isPkgUpdated | Should -Be $true + } + + # Unix only + # Expected path should be similar to: '/home/janelane/.local/share/powershell/Modules' + It "Install resource under CurrentUser scope - Unix only" -Skip:(Get-IsWindows) { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + + Update-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser + + $res = Get-InstalledPSResource -Name "TestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.InstalledLocation.Contains("home") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $true + } + + # Unix only + # Expected path should be similar to: '/usr/local/share/powershell/Modules' + It "Install resource under AllUsers scope - Unix only" -Skip:(Get-IsWindows) { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + + Update-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope AllUsers + + $res = Get-InstalledPSResource -Name "TestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.InstalledLocation.Contains("usr") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $true + } + + # Unix only + # Expected path should be similar to: '/home/janelane/.local/share/powershell/Modules' + It "Install resource under no specified scope - Unix only" -Skip:(Get-IsWindows) { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser + + Update-PSResource -Name "TestModule" -Repository $TestGalleryName + + $res = Get-InstalledPSResource -Name "TestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $pkg.InstalledLocation.Contains("home") | Should -Be $true + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $true } # TODO: ask Amber if we can publish another version to TestModuleWithLicense From 63df3a66c7a5650a15d223ee58b65472f5a99a9b Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 09:38:37 -0400 Subject: [PATCH 56/62] add test for AcceptLicense paramter --- test/UpdatePSResource.Tests.ps1 | 36 +++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index f13345acc..3706f67c7 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -286,26 +286,22 @@ Describe 'Test Update-PSResource' { $isPkgUpdated | Should -Be $true } - # TODO: ask Amber if we can publish another version to TestModuleWithLicense - # It "update resource that requires accept license with -AcceptLicense flag" { - # Install-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense - # Update-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense - # $pkg = Get-InstalledPSResource "testModuleWithlicense" - - # $isPkgUpdated = $false - # foreach ($pkg in $res) - # { - # if ([System.Version]$pkg.Version -gt [System.Version]"0.0.1.0") - # { - # $isPkgUpdated = $true - # } - # } - - # $isPkgUpdated | Should -BeTrue - - # # $pkg.Name | Should -Be "testModuleWithlicense" - # # $pkg.Version | Should -Be "0.0.1.0" - # } + It "update resource that requires accept license with -AcceptLicense flag" { + Install-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense + Update-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense + $pkg = Get-InstalledPSResource "testModuleWithlicense" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"0.0.1.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $true + } It "update resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName From 9fd206b761e85723a2b6e59b485406da0c52de13 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 09:49:30 -0400 Subject: [PATCH 57/62] update test with AcceptLicense --- test/UpdatePSResource.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index 3706f67c7..a362ef6ac 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -288,8 +288,8 @@ Describe 'Test Update-PSResource' { It "update resource that requires accept license with -AcceptLicense flag" { Install-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense - Update-PSResource -Name "TestModuleWithLicense" -Version "0.0.1.0" -Repository $TestGalleryName -AcceptLicense - $pkg = Get-InstalledPSResource "testModuleWithlicense" + Update-PSResource -Name "TestModuleWithLicense" -Repository $TestGalleryName -AcceptLicense + $res = Get-InstalledPSResource "TestModuleWithLicense" $isPkgUpdated = $false foreach ($pkg in $res) From 7d088a8d1ee883b61acb0dc82c72c2a0f2408bd5 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 10:08:06 -0400 Subject: [PATCH 58/62] update Install test for AcceptLicense --- test/InstallPSResource.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 33bc966af..3ee94b389 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -189,7 +189,7 @@ Describe 'Test Install-PSResource for Module' { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName -AcceptLicense $pkg = Get-InstalledPSResource "testModuleWithlicense" $pkg.Name | Should -Be "testModuleWithlicense" - $pkg.Version | Should -Be "0.0.1.0" + $pkg.Version | Should -Be "0.0.3.0" } It "Install resource should not prompt 'trust repository' if repository is not trusted but -TrustRepository is used" { From a5fceb5139ec4bb67a606819b3e7ae656eac4570 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 10:38:25 -0400 Subject: [PATCH 59/62] add whatIf test to update --- test/UpdatePSResource.Tests.ps1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index a362ef6ac..f74dd9ad2 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -323,4 +323,22 @@ Describe 'Test Update-PSResource' { $isPkgUpdated | Should -BeTrue Set-PSResourceRepository PoshTestGallery -Trusted } + + It "Update module using -WhatIf, should not update the module" { + Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName + Update-PSResource -Name "TestModule" -WhatIf + + $res = Get-InstalledPSResource -Name "TestModule" + + $isPkgUpdated = $false + foreach ($pkg in $res) + { + if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") + { + $isPkgUpdated = $true + } + } + + $isPkgUpdated | Should -Be $false + } } \ No newline at end of file From b9605f35bf7af8569efe67595891334d9f61f520 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 14:13:51 -0400 Subject: [PATCH 60/62] resolve PR feedback and add skip to Unix AllUsers scope tests --- src/code/UpdatePSResource.cs | 117 +++++++++++++++++--------------- src/code/Utils.cs | 12 ++-- test/UpdatePSResource.Tests.ps1 | 13 ++-- 3 files changed, 75 insertions(+), 67 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 8b7f2b631..f1a5d486d 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -1,4 +1,4 @@ - +using System.Collections.Specialized; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -22,13 +22,10 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets [Cmdlet(VerbsData.Update, "PSResource", - DefaultParameterSetName = NameParameterSet, SupportsShouldProcess = true)] public sealed class UpdatePSResource : PSCmdlet { #region Members - - private const string NameParameterSet = "NameParameterSet"; private List _pathsToInstallPkg; #endregion @@ -39,21 +36,21 @@ public sealed class UpdatePSResource : PSCmdlet /// Specifies name of a resource or resources to update. /// Accepts wildcard characters. /// - [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = NameParameterSet)] + [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] public string[] Name { get; set ; } = new string[] {"*"}; /// /// Specifies the version the resource is to be updated to. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] [ValidateNotNullOrEmpty] public string Version { get; set; } /// /// When specified, allows updating to a prerelease version. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public SwitchParameter Prerelease { get; set; } /// @@ -103,57 +100,13 @@ public sealed class UpdatePSResource : PSCmdlet #endregion - #region Methods + #region Override Methods protected override void BeginProcessing() { _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, Scope); } - private string[] ProcessNames(string[] namesToProcess, VersionRange versionRange) - { - namesToProcess = Utils.ProcessNameWildcards(namesToProcess, out string[] errorMsgs, out bool nameContainsWildcard); - - foreach (string error in errorMsgs) - { - WriteError(new ErrorRecord( - new PSInvalidOperationException(error), - "ErrorFilteringNamesForUnsupportedWildcards", - ErrorCategory.InvalidArgument, - this)); - } - - // this catches the case where Name wasn't input as null or empty, - // but after filtering out unsupported wildcard names there are no elements left in Name - if (Name.Length == 0) - { - return namesToProcess; - } - - if (String.Equals(namesToProcess[0], "*", StringComparison.InvariantCultureIgnoreCase)) - { - WriteVerbose("Name was detected to be (or contain an element equal to): '*', so all packages will be updated"); - } - - if (nameContainsWildcard) - { - // any of the Name entries contains a supported wildcard - // then we need to use GetHelper (Get-InstalledPSResource logic) to find which packages are installed that match - // the wildcard pattern name for each package name with wildcard - - GetHelper getHelper = new GetHelper( - cmdletPassedIn: this); - - namesToProcess = getHelper.FilterPkgPaths( - name: Name, - versionRange: versionRange, - pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); - } - - return namesToProcess; - - } - protected override void ProcessRecord() { VersionRange versionRange; @@ -173,11 +126,16 @@ protected override void ProcessRecord() return; } - Name = ProcessNames(Name, versionRange); + var namesToUpdate = ProcessPackageNameWildCards(Name, versionRange); + + if (namesToUpdate.Length == 0) + { + return; + } if (!ShouldProcess(string.Format("package to update: '{0}'", String.Join(", ", Name)))) { - WriteVerbose("ShouldProcess was set to false."); + WriteVerbose(string.Format("Update is cancelled by user for: {0}", String.Join(", ", Name))); return; } @@ -187,7 +145,7 @@ protected override void ProcessRecord() cmdletPassedIn: this); installHelper.InstallPackages( - names: Name, + names: namesToUpdate, versionRange: versionRange, prerelease: Prerelease, repository: Repository, @@ -208,5 +166,54 @@ protected override void ProcessRecord() } #endregion + #region Private Methods + + /// + /// This method checks all provided package names for wild card characters and removes any name containing invalid characters. + /// If any name contains a single '*' wildcard character then it is returned alone, indicating all packages should be processed. + /// + private string[] ProcessPackageNameWildCards(string[] namesToProcess, VersionRange versionRange) + { + namesToProcess = Utils.ProcessNameWildcards(namesToProcess, out string[] errorMsgs, out bool nameContainsWildcard); + + foreach (string error in errorMsgs) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException(error), + "ErrorFilteringNamesForUnsupportedWildcards", + ErrorCategory.InvalidArgument, + this)); + } + + // this catches the case where namesToProcess wasn't passed in as null or empty, + // but after filtering out unsupported wildcard names there are no elements left in namesToProcess + if (namesToProcess.Length == 0) + { + return namesToProcess; + } + + if (String.Equals(namesToProcess[0], "*", StringComparison.InvariantCultureIgnoreCase)) + { + WriteVerbose("Package names were detected to be (or contain an element equal to): '*', so all packages will be updated"); + } + + if (nameContainsWildcard) + { + // if any of the namesToProcess entries contains a supported wildcard + // then we need to use GetHelper (Get-InstalledPSResource logic) to find which packages are installed that match + // the wildcard pattern name for each package name with wildcard + + GetHelper getHelper = new GetHelper( + cmdletPassedIn: this); + + namesToProcess = getHelper.FilterPkgPaths( + name: namesToProcess, + versionRange: versionRange, + pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); + } + + return namesToProcess; + } + #endregion } } \ No newline at end of file diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 9160f416a..c5155ffc0 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -95,19 +95,17 @@ public static string[] ProcessNameWildcards( if (name.Contains("?") || name.Contains("[")) { - errorMsgsList.Add(String.Format("-Name with wildcards '?' and '[' are not supported for Find-PSResource so Name entry: {0} will be discarded.", name)); - } - else - { - isContainWildcard = true; - namesWithSupportedWildcards.Add(name); + errorMsgsList.Add(String.Format("-Name with wildcards '?' and '[' are not supported for this cmdlet so Name entry: {0} will be discarded.", name)); + continue; } + + isContainWildcard = true; + namesWithSupportedWildcards.Add(name); } else { namesWithSupportedWildcards.Add(name); } - } errorMsgs = errorMsgsList.ToArray(); diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index f74dd9ad2..bf49428ea 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -219,8 +219,9 @@ Describe 'Test Update-PSResource' { # Unix only # Expected path should be similar to: '/home/janelane/.local/share/powershell/Modules' - It "Install resource under CurrentUser scope - Unix only" -Skip:(Get-IsWindows) { - Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + It "Update resource under CurrentUser scope - Unix only" -Skip:(Get-IsWindows) { + # this line is commented out because AllUsers scope requires sudo and that isn't supported in CI yet + # Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser Update-PSResource -Name "TestModule" -Repository $TestGalleryName -Scope CurrentUser @@ -242,7 +243,8 @@ Describe 'Test Update-PSResource' { # Unix only # Expected path should be similar to: '/usr/local/share/powershell/Modules' - It "Install resource under AllUsers scope - Unix only" -Skip:(Get-IsWindows) { + # this test is skipped because it requires sudo to run and has yet to be resolved in CI + It "Update resource under AllUsers scope - Unix only" -Skip:($true) { Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser @@ -265,8 +267,9 @@ Describe 'Test Update-PSResource' { # Unix only # Expected path should be similar to: '/home/janelane/.local/share/powershell/Modules' - It "Install resource under no specified scope - Unix only" -Skip:(Get-IsWindows) { - Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers + It "Update resource under no specified scope - Unix only" -Skip:(Get-IsWindows) { + # this is commented out because it requires sudo to run with AllUsers scope and this hasn't been resolved in CI yet + # Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope AllUsers Install-PSResource -Name "TestModule" -Version "1.1.0.0" -Repository $TestGalleryName -Scope CurrentUser Update-PSResource -Name "TestModule" -Repository $TestGalleryName From 5ffe0dc78bed2195721437b3f5f4c7ec03e1f63d Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 14:25:34 -0400 Subject: [PATCH 61/62] remove references to NameParameterSet --- src/code/UpdatePSResource.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index f1a5d486d..de6bacac7 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -57,7 +57,7 @@ public sealed class UpdatePSResource : PSCmdlet /// Specifies one or more repository names to update packages from. /// If not specified, search will include all currently registered repositories in order of highest priority. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] [ArgumentCompleter(typeof(RepositoryNameCompleter))] [ValidateNotNullOrEmpty] public string[] Repository { get; set; } @@ -65,37 +65,37 @@ public sealed class UpdatePSResource : PSCmdlet /// /// Specifies the scope of the resource to update. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public ScopeType Scope { get; set; } /// /// When specified, supresses being prompted for untrusted sources. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public SwitchParameter TrustRepository { get; set; } /// /// Specifies optional credentials to be used when accessing a private repository. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public PSCredential Credential { get; set; } /// /// Supresses progress information. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public SwitchParameter Quiet { get; set; } /// /// For resources that require a license, AcceptLicense automatically accepts the license agreement during the update. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public SwitchParameter AcceptLicense { get; set; } /// /// When specified, bypasses checks for TrustRepository and AcceptLicense and updates the package. /// - [Parameter(ParameterSetName = NameParameterSet)] + [Parameter] public SwitchParameter Force { get; set; } #endregion From 2994ace09f1e272619a882e987c5c8bd57ac51a6 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Thu, 29 Jul 2021 14:57:44 -0400 Subject: [PATCH 62/62] add fix for failing MacOS tests --- test/UpdatePSResource.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/UpdatePSResource.Tests.ps1 b/test/UpdatePSResource.Tests.ps1 index bf49428ea..12c84574c 100644 --- a/test/UpdatePSResource.Tests.ps1 +++ b/test/UpdatePSResource.Tests.ps1 @@ -233,7 +233,7 @@ Describe 'Test Update-PSResource' { { if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { - $pkg.InstalledLocation.Contains("home") | Should -Be $true + $pkg.InstalledLocation.Contains("$env:HOME/.local") | Should -Be $true $isPkgUpdated = $true } } @@ -281,7 +281,7 @@ Describe 'Test Update-PSResource' { { if ([System.Version]$pkg.Version -gt [System.Version]"1.1.0.0") { - $pkg.InstalledLocation.Contains("home") | Should -Be $true + $pkg.InstalledLocation.Contains("$env:HOME/.local") | Should -Be $true $isPkgUpdated = $true } }