Public/Install-PsGalleryModule.ps1
function Install-PsGalleryModule { # .SYNOPSIS # Like install-Module but it manually installs the module when the normal way fails. # .DESCRIPTION # Installs a PowerShell module even on systems that don't have a working PowerShellGet. # But Sometimes you just need to apply a quick fix like this one: # # Unregister-PSRepository -Name PSGallery # Register-PSRepository -Default # if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') { # Set-PSRepository -Name PSGallery -InstallationPolicy Trusted # } # When all that fails, then this function comes in handy. [CmdletBinding()] [OutputType([IO.FileInfo])] param ( [Parameter(Mandatory = $true)] [ValidateScript({ $_ -match '^[a-zA-Z0-9_.-]+$' })] [Alias('Name', 'n')] [string]$moduleName, [Parameter(Mandatory = $false)] [ValidateScript({ ($_ -as 'version') -is [version] -or $_ -eq 'latest' })] [string]$Version = 'latest', [switch]$UpdateOnly, [switch]$Passthru ) Begin { $Get_Install_Path = [scriptblock]::Create({ param([string]$Name, [string]$ReqVersion) $p = [IO.DirectoryInfo][IO.Path]::Combine( $(if (!(Get-Variable -Name IsWindows -ErrorAction Ignore) -or $(Get-Variable IsWindows -ValueOnly)) { $_versionTable = Get-Variable PSVersionTable -ValueOnly $module_folder = if ($_versionTable.ContainsKey('PSEdition') -and $_versionTable.PSEdition -eq 'Core') { 'PowerShell' } else { 'WindowsPowerShell' } Join-Path -Path $([System.Environment]::GetFolderPath('MyDocuments')) -ChildPath $module_folder } else { Split-Path -Path ([System.Management.Automation.Platform]::SelectProductNameForDirectory('USER_MODULES')) -Parent } ), 'Modules' ) if (![string]::IsNullOrWhiteSpace($ReqVersion)) { [IO.Path]::Combine($p.FullName, $Name, $ReqVersion) } else { [IO.Path]::Combine($p.FullName, $Name) } } ) [int]$ret = 0; $response = $null; $downloadUrl = ''; $Module_Path = '' $InstallModule = [scriptblock]::Create({ # There are issues with pester 5.4.1 syntax, so I'll keep using -SkipPublisherCheck. # https://stackoverflow.com/questions/51508982/pester-sample-script-gets-be-is-not-a-valid-should-operator-on-windows-10-wo if ($Version -eq 'latest') { Install-Module -Name $moduleName -SkipPublisherCheck:$($moduleName -eq 'Pester') } else { Install-Module -Name $moduleName -RequiredVersion $Version -SkipPublisherCheck:$($moduleName -eq 'Pester') } } ) $UpdateModule = [scriptblock]::Create({ try { if ($Version -eq 'latest') { Update-Module -Name $moduleName } else { Update-Module -Name $moduleName -RequiredVersion $Version } } catch { if ($ret -lt 1 -and $_.ErrorRecord.Exception.Message -eq "Module '$moduleName' was not installed by using Install-Module, so it cannot be updated.") { Get-Module $moduleName | Remove-Module -Force; $ret++ $UpdateModule.Invoke() } } } ) } Process { # Try Using normal Installation try { if ($PSCmdlet.MyInvocation.BoundParameters['UpdateOnly']) { $UpdateModule.Invoke() } else { $InstallModule.Invoke() } $Module_Path = (Get-LocalModule -Name $moduleName).Psd1 | Split-Path -ErrorAction Stop } catch { $VerboseMsg = 'Normal Installation Failed :' + $_.Exception.Message + "`nUsing Manual Instalation ..." Write-Verbose $VerboseMsg -Verbose # For some reason Install-Module can fail (ex: on Arch). This is a manual workaround when that happens. $version_filter = if ($Version -eq 'latest') { 'IsLatestVersion' } else { "Version eq '$Version'" } $url = "https://www.powershellgallery.com/api/v2/Packages?`$filter=Id eq '$moduleName' and $version_filter" try { $response = Invoke-RestMethod -Uri $url -Method Get -Verbose:$false if ($null -eq $response) { $Error_params = @{ ExceptionName = 'System.InvalidOperationException' ExceptionMessage = "Module '$moduleName' was not found in PSGallery repository." ErrorId = 'CouldNotFindModule' CallerPSCmdlet = $PSCmdlet ErrorCategory = 'InvalidResult' } Write-TerminatingError @Error_params } [ValidateNotNullOrEmpty()][string]$downloadUrl = $response.content.src [ValidateNotNullOrEmpty()][string]$moduleName = $response.properties.Id [ValidateNotNullOrEmpty()][string]$Version = $response.properties.Version $Module_Path = $Get_Install_Path.Invoke($moduleName, $Version) } catch { $Error_params = @{ ExceptionName = 'System.InvalidOperationException' ExceptionMessage = "Failed to find PsGallery release for '$moduleName' version '$Version'. Url used: '$url'. $($_.Exception.Message)" ErrorId = 'RestMethod_Failed' CallerPSCmdlet = $PSCmdlet ErrorCategory = 'OperationStopped' } Write-TerminatingError @Error_params } if (!(Test-Path -Path $Module_Path -PathType Container -ErrorAction Ignore)) { New-Directory -Path $Module_Path } $ModuleNupkg = [IO.Path]::Combine($Module_Path, "$moduleName.nupkg") Write-Host "Download $moduleName.nupkg ... " -NoNewline -ForegroundColor DarkCyan Invoke-WebRequest -Uri $downloadUrl -OutFile $ModuleNupkg -Verbose:$false; if ($IsWindows) { Unblock-File -Path $ModuleNupkg } Expand-Archive $ModuleNupkg -DestinationPath $Module_Path -Verbose:$false -Force $Items_to_CleanUp = [System.Collections.ObjectModel.Collection[System.Object]]::new() @('_rels', 'package', '*Content_Types*.xml', "$ModuleNupkg", "$($moduleName.Tolower()).nuspec" ) | ForEach-Object { [void]$Items_to_CleanUp.Add((Get-Item -Path "$Module_Path/$_" -ErrorAction Ignore)) } $Items_to_CleanUp = $Items_to_CleanUp | Sort-Object -Unique foreach ($Item in $Items_to_CleanUp) { [bool]$Recurse = $Item.Attributes -eq [System.IO.FileAttributes]::Directory Remove-Item -LiteralPath $Item.FullName -Recurse:$Recurse -Force -ErrorAction SilentlyContinue } } } } |