From de67b037b63d1470e66a2f296611698c97eccd61 Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Fri, 12 Nov 2021 10:36:43 -0600 Subject: [PATCH 1/5] Adding support for PSGallery Scripts. --- PSDepend/PSDependMap.psd1 | 6 + PSDepend/PSDependScripts/PSGalleryScript.ps1 | 315 +++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 PSDepend/PSDependScripts/PSGalleryScript.ps1 diff --git a/PSDepend/PSDependMap.psd1 b/PSDepend/PSDependMap.psd1 index d150c72..fee24e9 100644 --- a/PSDepend/PSDependMap.psd1 +++ b/PSDepend/PSDependMap.psd1 @@ -78,6 +78,12 @@ Supports = 'windows', 'core', 'macos', 'linux' } + PSGalleryScript = @{ + Script= 'PSGalleryScript.ps1' + Description = 'Install a PowerShell script from the PowerShell Gallery' + Supports = 'windows', 'core', 'macos', 'linux' + } + PSGalleryNuget = @{ Script = 'PSGalleryNuget.ps1' Description = 'Install a PowerShell module from the PowerShell Gallery without the PowerShellGet dependency' diff --git a/PSDepend/PSDependScripts/PSGalleryScript.ps1 b/PSDepend/PSDependScripts/PSGalleryScript.ps1 new file mode 100644 index 0000000..73fd5ec --- /dev/null +++ b/PSDepend/PSDependScripts/PSGalleryScript.ps1 @@ -0,0 +1,315 @@ +<# + .SYNOPSIS + Installs a module from a PowerShell repository like the PowerShell Gallery. + + .DESCRIPTION + Installs a module from a PowerShell repository like the PowerShell Gallery. + + Relevant Dependency metadata: + Name: The name for this module + Version: Used to identify existing installs meeting this criteria, and as RequiredVersion for installation. Defaults to 'latest' + Target: Used as 'Scope' for Install-Module. If this is a path, we use Save-Module with this path. Defaults to 'AllUsers' + AddToPath: If target is used as a path, prepend that path to ENV:PSModulePath + Credential: The username and password used to authenticate against the private repository + + If you don't have the Nuget package provider, we install it for you + + .PARAMETER Repository + PSRepository to download from. Defaults to PSGallery + + .PARAMETER AcceptLicense + Accepts the license agreement during installation. + + .PARAMETER AllowPrerelease + If specified, allow for prerelease. + + If specified along with version 'latest', a prerelease will be selected if it is the latest version + + Sorting assumes you name prereleases appropriately (i.e. alpha < beta < gamma) + + .PARAMETER Import + If specified, import the module in the global scope + + Deprecated. Moving to PSDependAction + + .PARAMETER PSDependAction + Test, Install, or Import the module. Defaults to Install + + Test: Return true or false on whether the dependency is in place + Install: Install the dependency + Import: Import the dependency + + .PARAMETER NoPathUpdate + If specified, don't update the path. Defaults to false. + + + .EXAMPLE + @{ + BuildHelpers = 'latest' + PSDeploy = '' + InvokeBuild = '3.2.1' + } + + # From the PSGallery repository (PowerShellGallery.com)... + # Install the latest BuildHelpers and PSDeploy ('latest' and '' both evaluate to latest) + # Install version 3.2.1 + + .EXAMPLE + @{ + BuildHelpers = @{ + Target = 'C:\Build' + } + } + + # Install the latest BuildHelpers module from PSGallery to C:\Build (i.e. C:\Build\BuildHelpers will be the module folder) + # No version is specified - we assume latest in this case. + + .EXAMPLE + @{ + BuildHelpers = @{ + Parameters @{ + Repository = 'PSPrivateGallery' + SkipPublisherCheck = $true + } + } + } + + # Install the latest BuildHelpers module from a custom gallery* that you registered, and bypass the catalog signing check + # No version is specified - we assume latest in this case. + + # * Perhaps you use this https://github.com/PowerShell/PSPrivateGallery, or Artifactory, ProGet, etc. + + .EXAMPLE + @{ + 'vmware.powercli' = @{ + Parameters = @{ + AllowPrerelease = $True + } + } + } + # Install the latest version of PowerCLI, allowing for prerelease +#> +[cmdletbinding()] +param( + [PSTypeName('PSDepend.Dependency')] + [psobject[]]$Dependency, + + [AllowNull()] + [string]$Repository = 'PSGallery', # From Parameters... + + [bool]$AcceptLicense, + + [bool]$AllowPrerelease, + + [bool]$NoPathUpdate, + + [switch]$Import, + + [ValidateSet('Test', 'Install', 'Import')] + [string[]]$PSDependAction = @('Install') +) + +# Extract data from Dependency + $DependencyName = $Dependency.DependencyName + $Name = $Dependency.Name + if(-not $Name) + { + $Name = $DependencyName + } + + $Version = $Dependency.Version + if(-not $Version) + { + $Version = 'latest' + } + + # We use target as a proxy for Scope + if(-not $Dependency.Target) + { + $Scope = 'AllUsers' + } + else + { + $Scope = $Dependency.Target + } + + $Credential = $Dependency.Credential + + if('AllUsers', 'CurrentUser' -notcontains $Scope) + { + $command = 'save' + } + else + { + $command = 'install' + } + +if(-not (Get-PackageProvider -Name Nuget)) +{ + # Grab nuget bits. + $null = Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null +} + +Write-Verbose -Message "Getting dependency [$name] from PowerShell repository [$Repository]" + +# Validate that $target has been setup as a valid PowerShell repository, +# but allow to rely on all PS repos registered. +if($Repository) { + $validRepo = Get-PSRepository -Name $Repository -Verbose:$false -ErrorAction SilentlyContinue + if (-not $validRepo) { + Write-Error "[$Repository] has not been setup as a valid PowerShell repository." + return + } +} + +$params = @{ + Name = $Name + NoPathUpdate = $NoPathUpdate + Verbose = $VerbosePreference + Force = $True +} + +if($PSBoundParameters.ContainsKey('AllowPrerelease')){ + $params.Add('AllowPrerelease', $AllowPrerelease) +} + +if($PSBoundParameters.ContainsKey('AcceptLicense')){ + $params.Add('AcceptLicense', $AcceptLicense) +} + +if($Repository) { + $params.Add('Repository',$Repository) +} + +if($Version -and $Version -ne 'latest') +{ + $Params.add('RequiredVersion', $Version) +} + +if($Credential) +{ + $Params.add('Credential', $Credential) +} + +# This code works for both install and save scenarios. +if($command -eq 'Save') +{ + $ModuleName = Join-Path $Scope $Name +} +elseif ($Command -eq 'Install') +{ + $ModuleName = $Name +} + +# Only use "SkipPublisherCheck" (and other) parameter if "Install-Script" supports it +$availableParameters = (Get-Command "Install-Script").Parameters +$tempParams = $Params.Clone() +foreach($thisParameter in $Params.Keys) +{ + if(-Not ($availableParameters.ContainsKey($thisParameter))) + { + Write-Verbose -Message "Removing parameter [$thisParameter] from [Install-Script] as it is not available" + $tempParams.Remove($thisParameter) + } +} +$Params = $tempParams.Clone() + +# @matthewjdegarmo doesn't think this is needed for scripts +# Add-ToPsModulePathIfRequired -Dependency $Dependency -Action $PSDependAction + +$Existing = $null +$Existing = Get-Module -ListAvailable -Name $ModuleName -ErrorAction SilentlyContinue + +if($Existing) +{ + Write-Verbose "Found existing script [$Name]" + # Thanks to Brandon Padgett! + $ExistingVersion = $Existing | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum + $FindScriptParams = @{Name = $Name} + if($Repository) { + $FindScriptParams.Add('Repository', $Repository) + } + if($Credential) + { + $FindScriptParams.Add('Credential', $Credential) + } + if($AllowPrerelease) + { + $FindScriptParams.Add('AllowPrerelease', $AllowPrerelease) + } + + # Version string, and equal to current + if($Version -and $Version -ne 'latest' -and $Version -eq $ExistingVersion) + { + Write-Verbose "You have the requested version [$Version] of [$Name]" + # Conditional import + Import-PSDependModule -Name $ModuleName -Action $PSDependAction -Version $ExistingVersion + + if($PSDependAction -contains 'Test') + { + return $true + } + return $null + } + + $GalleryVersion = Find-Module @FindScriptParams | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum + [System.Version]$parsedVersion = $null + [System.Management.Automation.SemanticVersion]$parsedSemanticVersion = $null + [System.Management.Automation.SemanticVersion]$parsedTempSemanticVersion = $null + $isGalleryVersionLessEquals = if ( + [System.Management.Automation.SemanticVersion]::TryParse($ExistingVersion, [ref]$parsedSemanticVersion) -and + [System.Management.Automation.SemanticVersion]::TryParse($GalleryVersion, [ref]$parsedTempSemanticVersion) + ) { + $GalleryVersion -le $parsedSemanticVersion + } + elseif ([System.Version]::TryParse($ExistingVersion, [ref]$parsedVersion)) { + $GalleryVersion -le $parsedVersion + } + + # latest, and we have latest + if( $Version -and ($Version -eq 'latest' -or $Version -eq '') -and $isGalleryVersionLessEquals) + { + Write-Verbose "You have the latest version of [$Name], with installed version [$ExistingVersion] and PSGallery version [$GalleryVersion]" + # Conditional import + # @matthewjdegarmo doesn't think this is needed for scripts + # Import-PSDependModule -Name $ModuleName -Action $PSDependAction -Version $ExistingVersion + + if($PSDependAction -contains 'Test') + { + return $True + } + return $null + } + Write-Verbose "Continuing to install [$Name]: Requested version [$version], existing version [$ExistingVersion]" +} + +#No dependency found, return false if we're testing alone... +if( $PSDependAction -contains 'Test' -and $PSDependAction.count -eq 1) +{ + return $False +} + +if($PSDependAction -contains 'Install') +{ + if('AllUsers', 'CurrentUser' -contains $Scope) + { + Write-Verbose "Installing [$Name] with scope [$Scope]" + Install-Script @params -Scope $Scope + } + else + { + Write-Verbose "Saving [$Name] with path [$Scope]" + # @matthewjdegarmo doesn't think this is needed for scripts + # Write-Verbose "Creating directory path to [$Scope]" + # if(-not (Test-Path $Scope -ErrorAction SilentlyContinue)) + # { + # $Null = New-Item -ItemType Directory -Path $Scope -Force -ErrorAction SilentlyContinue + # } + Save-Script @params -Path $Scope + } +} + +# Conditional import +# @matthewjdegarmo doesn't think this is needed for scripts +# $importVs = $params['RequiredVersion'] +# Import-PSDependModule -Name $ModuleName -Action $PSDependAction -Version $importVs From d7fd1565b16b9ac629172cece47775cdcbfbf8ce Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Fri, 12 Nov 2021 11:20:10 -0600 Subject: [PATCH 2/5] Cleaning up documentation. --- PSDepend/PSDependScripts/PSGalleryScript.ps1 | 48 ++++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/PSDepend/PSDependScripts/PSGalleryScript.ps1 b/PSDepend/PSDependScripts/PSGalleryScript.ps1 index 73fd5ec..b05aa8e 100644 --- a/PSDepend/PSDependScripts/PSGalleryScript.ps1 +++ b/PSDepend/PSDependScripts/PSGalleryScript.ps1 @@ -1,15 +1,14 @@ <# .SYNOPSIS - Installs a module from a PowerShell repository like the PowerShell Gallery. + Installs a script from a PowerShell repository like the PowerShell Gallery. .DESCRIPTION - Installs a module from a PowerShell repository like the PowerShell Gallery. + Installs a script from a PowerShell repository like the PowerShell Gallery. Relevant Dependency metadata: - Name: The name for this module + Name: The name for this script Version: Used to identify existing installs meeting this criteria, and as RequiredVersion for installation. Defaults to 'latest' - Target: Used as 'Scope' for Install-Module. If this is a path, we use Save-Module with this path. Defaults to 'AllUsers' - AddToPath: If target is used as a path, prepend that path to ENV:PSModulePath + Target: Used as 'Scope' for Install-Script. If this is a path, we use Save-Script with this path. Defaults to 'AllUsers' Credential: The username and password used to authenticate against the private repository If you don't have the Nuget package provider, we install it for you @@ -27,13 +26,8 @@ Sorting assumes you name prereleases appropriately (i.e. alpha < beta < gamma) - .PARAMETER Import - If specified, import the module in the global scope - - Deprecated. Moving to PSDependAction - .PARAMETER PSDependAction - Test, Install, or Import the module. Defaults to Install + Test, Install, or Import the script. Defaults to Install Test: Return true or false on whether the dependency is in place Install: Install the dependency @@ -45,49 +39,52 @@ .EXAMPLE @{ - BuildHelpers = 'latest' - PSDeploy = '' - InvokeBuild = '3.2.1' + 'Get-CurrentUser' = @{ + Version = 'latest' + Parameters = @{ + NoPathUpdate = $true + } + 'Get-RemoteProgram' = '' + 'Update-Windows' = '1.1.0' } # From the PSGallery repository (PowerShellGallery.com)... - # Install the latest BuildHelpers and PSDeploy ('latest' and '' both evaluate to latest) - # Install version 3.2.1 + # Install the latest Get-CurrentUser and Get-RemoteProgram scripts ('latest' and '' both evaluate to latest) + # Install version 1.1.0 of Update-Windows script .EXAMPLE @{ - BuildHelpers = @{ - Target = 'C:\Build' + 'Get-CurrentUser' = @{ + Target = 'C:\test' } } - # Install the latest BuildHelpers module from PSGallery to C:\Build (i.e. C:\Build\BuildHelpers will be the module folder) + # Install the latest Get-CurrentUser script from PSGallery to C:\test (i.e. C:\test\Get-CurrentUser.ps1 will be the script) # No version is specified - we assume latest in this case. .EXAMPLE @{ - BuildHelpers = @{ + 'Get-CurrentUser' = @{ Parameters @{ Repository = 'PSPrivateGallery' - SkipPublisherCheck = $true } } } - # Install the latest BuildHelpers module from a custom gallery* that you registered, and bypass the catalog signing check + # Install the latest Get-CurrentUser script from a custom gallery* that you registered # No version is specified - we assume latest in this case. # * Perhaps you use this https://github.com/PowerShell/PSPrivateGallery, or Artifactory, ProGet, etc. .EXAMPLE @{ - 'vmware.powercli' = @{ + 'Get-CurrentUser' = @{ Parameters = @{ - AllowPrerelease = $True + NoPathUpdate = $true } } } - # Install the latest version of PowerCLI, allowing for prerelease + # Install the latest version of Get-CurrentUser script, disabling the path update. #> [cmdletbinding()] param( @@ -195,6 +192,7 @@ if($Credential) if($command -eq 'Save') { $ModuleName = Join-Path $Scope $Name + $Params.Remove('NoPathUpdate') } elseif ($Command -eq 'Install') { From aaaffaeaa981a75353657e5ee5afd8533dccc8ac Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Fri, 12 Nov 2021 12:16:16 -0600 Subject: [PATCH 3/5] Fixing module command to script. --- PSDepend/PSDependScripts/PSGalleryScript.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSDepend/PSDependScripts/PSGalleryScript.ps1 b/PSDepend/PSDependScripts/PSGalleryScript.ps1 index b05aa8e..a1ba531 100644 --- a/PSDepend/PSDependScripts/PSGalleryScript.ps1 +++ b/PSDepend/PSDependScripts/PSGalleryScript.ps1 @@ -250,7 +250,7 @@ if($Existing) return $null } - $GalleryVersion = Find-Module @FindScriptParams | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum + $GalleryVersion = Find-Script @FindScriptParams | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum [System.Version]$parsedVersion = $null [System.Management.Automation.SemanticVersion]$parsedSemanticVersion = $null [System.Management.Automation.SemanticVersion]$parsedTempSemanticVersion = $null From 36127ab870d0fcaf862eb6e72e9fb45bf466da6c Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Fri, 12 Nov 2021 13:03:34 -0600 Subject: [PATCH 4/5] Adding support for psgallery script dependencies. --- PSDepend/PSDependScripts/PSGalleryScript.ps1 | 21 ++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/PSDepend/PSDependScripts/PSGalleryScript.ps1 b/PSDepend/PSDependScripts/PSGalleryScript.ps1 index a1ba531..6f8a70c 100644 --- a/PSDepend/PSDependScripts/PSGalleryScript.ps1 +++ b/PSDepend/PSDependScripts/PSGalleryScript.ps1 @@ -100,6 +100,8 @@ param( [bool]$NoPathUpdate, + [bool]$IncludeDependencies, + [switch]$Import, [ValidateSet('Test', 'Install', 'Import')] @@ -241,7 +243,8 @@ if($Existing) { Write-Verbose "You have the requested version [$Version] of [$Name]" # Conditional import - Import-PSDependModule -Name $ModuleName -Action $PSDependAction -Version $ExistingVersion + # @matthewjdegarmo doesn't think this is needed for scripts + # Import-PSDependModule -Name $ModuleName -Action $PSDependAction -Version $ExistingVersion if($PSDependAction -contains 'Test') { @@ -292,7 +295,21 @@ if($PSDependAction -contains 'Install') if('AllUsers', 'CurrentUser' -contains $Scope) { Write-Verbose "Installing [$Name] with scope [$Scope]" - Install-Script @params -Scope $Scope + #Account for [-IncludeDependencies] on Find-Script. This is dependency mapping outside of the PSDepend file. + if($IncludeDependencies) + { + $FindScriptParams.Add('IncludeDependencies', $IncludeDependencies) + $FindScriptParams.Add('RequiredVersion', $Version) + + # Need to remove Name and RequiredVersion, that info is coming across the Pipeline. + $InstallParams = $params.clone() + $InstallParams.Remove('Name') + $InstallParams.Remove('RequiredVersion') + + Find-Script @FindScriptParams | Install-Script @InstallParams -Scope $Scope + } Else { + Install-Script @params -Scope $Scope + } } else { From a6d5497042ece2516099a35b1177c80a0c76ad0f Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Fri, 12 Nov 2021 13:04:17 -0600 Subject: [PATCH 5/5] Removing import dead parameter. --- PSDepend/PSDependScripts/PSGalleryScript.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/PSDepend/PSDependScripts/PSGalleryScript.ps1 b/PSDepend/PSDependScripts/PSGalleryScript.ps1 index 6f8a70c..5cb7f9d 100644 --- a/PSDepend/PSDependScripts/PSGalleryScript.ps1 +++ b/PSDepend/PSDependScripts/PSGalleryScript.ps1 @@ -102,8 +102,6 @@ param( [bool]$IncludeDependencies, - [switch]$Import, - [ValidateSet('Test', 'Install', 'Import')] [string[]]$PSDependAction = @('Install') )