Public/Install-ModuleFromLocalRepo.ps1
function Install-ModuleFromLocalRepo { <# .SYNOPSIS Install PowerShell modules from local PowerShell repositories. .DESCRIPTION This function installs modules from registered local PowerShell repositories. By default, it searches all registered repositories with local file paths. Can also install from a specific folder path containing NuPkg files. .PARAMETER RepositoryName Name of a specific registered PowerShell repository to search. If not specified, searches all registered repositories with local file system paths. .PARAMETER Path Path to a folder containing NuPkg files. Use this to install directly from a folder without requiring repository registration. .PARAMETER Force Force reinstallation even if the module is already installed. .PARAMETER PassThru Return information about the installed module. .PARAMETER PackageName Specify a specific package name to install without showing the menu. .EXAMPLE Install-ModuleFromLocalRepo Searches all registered local repositories and presents a selection menu. .EXAMPLE Install-ModuleFromLocalRepo -RepositoryName "LocalNuGetRepo" Installs from the specific registered repository. .EXAMPLE Install-ModuleFromLocalRepo -Path "C:\MyPackages" -Force Installs directly from a folder path, bypassing repository registration. .EXAMPLE Install-ModuleFromLocalRepo -PackageName "MyModule" -RepositoryName "LocalNuGetRepo" Directly installs MyModule from the specified repository. #> [CmdletBinding(DefaultParameterSetName = 'Repository', SupportsShouldProcess)] param( [Parameter(ParameterSetName = 'Repository')] [string]$RepositoryName, [Parameter(ParameterSetName = 'Path', Position = 0)] [ValidateScript({ if (-not (Test-Path -Path $_ -PathType Container)) { throw "Path '$_' does not exist or is not a directory." } $true })] [string]$Path, [Parameter()] [switch]$Force, [Parameter()] [switch]$PassThru, [Parameter()] [string]$PackageName ) try { # Determine installation source if ($PSCmdlet.ParameterSetName -eq 'Repository') { # Use registered PowerShell repositories if ($RepositoryName) { # Install from specific repository $repositories = Get-PSRepository -Name $RepositoryName -ErrorAction SilentlyContinue if (-not $repositories) { Write-Error "Repository '$RepositoryName' not found. Use Get-PSRepository to list available repositories." return } } else { # Find all local file-based repositories $repositories = Get-PSRepository | Where-Object { $_.SourceLocation -match '^[A-Za-z]:\\' -or $_.SourceLocation -match '^\\\\' } if (-not $repositories) { Write-Warning "No local file-based repositories found. Use New-LocalRepository to create one, or specify -Path to install from a folder." return } } Write-Host "Searching registered repositories..." -ForegroundColor Cyan foreach ($repo in $repositories) { Write-Host " - $($repo.Name): $($repo.SourceLocation)" -ForegroundColor Gray } # Install using PowerShell's native module installation if ($PackageName) { $repoNames = $repositories.Name -join ', ' Write-Host "Installing $PackageName from repository: $repoNames" -ForegroundColor Yellow $installParams = @{ Name = $PackageName Repository = $repositories.Name Scope = 'CurrentUser' Force = $Force.IsPresent } if ($PSCmdlet.ShouldProcess($PackageName, "Install module from repository")) { try { # Suppress the misleading "No match was found" error that occurs even when installation succeeds $ErrorActionPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' Install-Module @installParams -ErrorAction SilentlyContinue -WarningAction SilentlyContinue # Reset error preferences $ErrorActionPreference = 'Continue' $WarningPreference = 'Continue' # Verify installation success by checking if module is available $installedModule = Get-Module -Name $PackageName -ListAvailable | Select-Object -First 1 if ($installedModule) { Write-Host "Successfully installed $PackageName" -ForegroundColor Green if ($PassThru) { return $installedModule } } else { Write-Error "Failed to install $PackageName - module not found after installation attempt" } } catch { # Only show actual critical errors if ($_.Exception.Message -notmatch "No match was found") { Write-Error "Failed to install $PackageName`: $($_.Exception.Message)" } else { # Check if module was installed despite the error $installedModule = Get-Module -Name $PackageName -ListAvailable | Select-Object -First 1 if ($installedModule) { Write-Host "Successfully installed $PackageName" -ForegroundColor Green if ($PassThru) { return $installedModule } } else { Write-Error "Failed to install $PackageName" } } } } } else { # List available modules from repositories Write-Host "Finding available modules..." -ForegroundColor Cyan $availableModules = @() foreach ($repo in $repositories) { try { $modules = Find-Module -Repository $repo.Name -ErrorAction SilentlyContinue foreach ($module in $modules) { $availableModules += [PSCustomObject]@{ Name = $module.Name Version = $module.Version Repository = $repo.Name Description = $module.Description } } } catch { Write-Verbose "Could not find modules in repository $($repo.Name): $($_.Exception.Message)" } } if ($availableModules) { Write-Host "Available modules:" -ForegroundColor Green for ($i = 0; $i -lt $availableModules.Count; $i++) { $module = $availableModules[$i] Write-Host " [$($i + 1)] $($module.Name) v$($module.Version) [$($module.Repository)]" -ForegroundColor White } $choice = Read-Host "`nEnter the number of the module to install (or 'q' to quit)" if ($choice -eq 'q') { Write-Host "Installation cancelled." -ForegroundColor Yellow return } if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le $availableModules.Count) { $selectedModule = $availableModules[[int]$choice - 1] if ($PSCmdlet.ShouldProcess($selectedModule.Name, "Install module from repository")) { try { # Suppress misleading errors $ErrorActionPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' Install-Module -Name $selectedModule.Name -Repository $selectedModule.Repository -Scope CurrentUser -Force:$Force.IsPresent -ErrorAction SilentlyContinue -WarningAction SilentlyContinue # Reset preferences $ErrorActionPreference = 'Continue' $WarningPreference = 'Continue' # Verify installation $installedModule = Get-Module -Name $selectedModule.Name -ListAvailable | Select-Object -First 1 if ($installedModule) { Write-Host "Successfully installed $($selectedModule.Name) v$($selectedModule.Version)" -ForegroundColor Green if ($PassThru) { return $installedModule } } else { Write-Error "Failed to install $($selectedModule.Name) - module not found after installation" } } catch { if ($_.Exception.Message -notmatch "No match was found") { Write-Error "Failed to install $($selectedModule.Name): $($_.Exception.Message)" } else { # Check if it installed despite the error $installedModule = Get-Module -Name $selectedModule.Name -ListAvailable | Select-Object -First 1 if ($installedModule) { Write-Host "Successfully installed $($selectedModule.Name) v$($selectedModule.Version)" -ForegroundColor Green if ($PassThru) { return $installedModule } } else { Write-Error "Failed to install $($selectedModule.Name)" } } } } } else { Write-Warning "Invalid selection. Installation cancelled." } } else { Write-Warning "No modules found in the specified repositories." } } } else { # Use legacy folder-based installation Write-Host "Scanning for NuPkg files in: $Path" -ForegroundColor Cyan # Find all .nupkg files $nupkgFiles = Get-ChildItem -Path $Path -Filter "*.nupkg" -File if (-not $nupkgFiles) { Write-Warning "No NuPkg files found in: $Path" return } Write-Host "Found $($nupkgFiles.Count) NuPkg file(s)" -ForegroundColor Gray # List the files found for debugging Write-Verbose "NuPkg files found:" foreach ($file in $nupkgFiles) { Write-Verbose " - $($file.Name) ($($file.FullName))" } # Parse package information $packages = @{} foreach ($file in $nupkgFiles) { Write-Verbose "Processing file: $($file.Name)" $pkgInfo = Get-NuPkgInfo -FilePath $file.FullName if ($pkgInfo) { $packageName = $pkgInfo.Name Write-Verbose "Parsed package: $packageName v$($pkgInfo.VersionString)" # Keep only the newest version of each package if (-not $packages.ContainsKey($packageName) -or $pkgInfo.Version -gt $packages[$packageName].Version) { Write-Verbose "Adding/updating package: $packageName v$($pkgInfo.VersionString)" $packages[$packageName] = $pkgInfo } else { Write-Verbose "Skipping older version: $packageName v$($pkgInfo.VersionString) (current: v$($packages[$packageName].VersionString))" } } else { Write-Verbose "Failed to parse package info for: $($file.Name)" } } if ($packages.Count -eq 0) { Write-Warning "No valid NuPkg files found (could not parse package information)" return } Write-Verbose "Parsed $($packages.Count) unique packages:" foreach ($pkg in $packages.Values) { Write-Verbose " - $($pkg.Name) v$($pkg.VersionString) ($($pkg.Size) MB)" } # Select package $selectedPackage = $null if ($PackageName) { # Direct package selection if ($packages.ContainsKey($PackageName)) { $selectedPackage = $packages[$PackageName] Write-Host "Selected package: $($selectedPackage.Name) v$($selectedPackage.VersionString)" -ForegroundColor Green } else { Write-Error "Package '$PackageName' not found in repository. Available packages: $($packages.Keys -join ', ')" return } } else { # Interactive selection menu Write-Host "`nAvailable packages (newest versions):" -ForegroundColor Green Write-Host "=====================================" -ForegroundColor Green $packageList = @($packages.Values | Sort-Object Name) Write-Verbose "Package list contains $($packageList.Count) items" for ($i = 0; $i -lt $packageList.Count; $i++) { $pkg = $packageList[$i] Write-Verbose "Displaying package $($i+1): $($pkg.Name) v$($pkg.VersionString) ($($pkg.Size) MB)" Write-Host " [$($i + 1)] $($pkg.Name) v$($pkg.VersionString) ($($pkg.Size) MB)" -ForegroundColor White } Write-Host " [0] Exit" -ForegroundColor Gray Write-Host "" # Get user selection do { $selection = Read-Host "Select package to install (1-$($packageList.Count), 0 to exit)" if ($selection -eq "0") { Write-Host "Installation cancelled." -ForegroundColor Yellow return } $selectionInt = $null $isValidNumber = [int]::TryParse($selection, [ref]$selectionInt) if ($isValidNumber -and $selectionInt -ge 1 -and $selectionInt -le $packageList.Count) { $selectedPackage = $packageList[$selectionInt - 1] break } else { Write-Host "Invalid selection. Please enter a number between 1 and $($packageList.Count), or 0 to exit." -ForegroundColor Red } } while ($true) } # Confirm installation $userModulesPath = Get-UserModulePath Write-Host "`nSelected package: $($selectedPackage.Name) v$($selectedPackage.VersionString)" -ForegroundColor Green Write-Host "Installation location: $userModulesPath" -ForegroundColor Gray if ($PSCmdlet.ShouldProcess($selectedPackage.Name, "Install PowerShell module to current user scope")) { $installResult = Install-NuPkgModule -PackageInfo $selectedPackage -DestinationPath $userModulesPath -Force:$Force if ($installResult) { Write-Host "`n[SUCCESS] Module installation completed!" -ForegroundColor Green # Show how to use the module Write-Host "`nTo use the module, run:" -ForegroundColor Cyan Write-Host " Import-Module $($selectedPackage.Name) -Force" -ForegroundColor White Write-Host " Get-Command -Module $($selectedPackage.Name)" -ForegroundColor White # Return module information if requested if ($PassThru) { return Get-Module -Name $selectedPackage.Name -ListAvailable | Where-Object Version -eq $selectedPackage.VersionString } } else { Write-Error "Module installation failed." } } } # End of legacy folder-based installation } catch { Write-Error "Script execution failed: $($_.Exception.Message)" Write-Debug $_.ScriptStackTrace } } |