Initialize-Module.ps1
<#PSScriptInfo
.VERSION 1.3 .GUID 0e34e80c-49a0-4579-8113-fd793a44d92c .AUTHOR Jonathan Pitre .TAGS PowerShell Module Automation .RELEASENOTES 1.3 - 2025-06-20 - Replaced Install-Module, Update-Module, and Get-Module commands with Install-PSResource, Update-PSResource, and Get-InstalledPSResource commands for improved performance and modern PowerShell Gallery interaction. - Enhanced module management efficiency by leveraging the Microsoft.PowerShell.PSResourceGet module's optimized cmdlets. - Fixed an error with the transcript file not being created on a fresh reboot. 1.2 - 2025-06-17 - Added error action silently continue to the Import-Module commands for Microsoft.PowerShell.PSResourceGet to avoid errors when the module is not installed. 1.1 - 2025-06-16 Added log file and transcript. Added log path and file parameters. 1.0 - 2025-05-30 Initial release. #> <# .SYNOPSIS Installs (or updates) and imports PowerShell modules from the PowerShell Gallery. .DESCRIPTION This function ensures that specified PowerShell modules are installed, up-to-date, and imported for use. It handles necessary prerequisites for PowerShell Gallery interaction, including: - Setting the security protocol to TLS 1.2. - Ensuring the NuGet package provider is available. - Installing/updating and importing the 'Microsoft.PowerShell.PSResourceGet' module for modern gallery operations. For each module specified by the -Name parameter, the function will: 1. Attempt to install or update it to the latest version from the PowerShell Gallery. Modules are typically installed for AllUsers. 2. Import the module into the current session. This automates the common tasks required to make PowerShell modules ready for use. .PARAMETER Name The name of the module or an array of module names to initialize. These modules will be installed/updated from the PowerShell Gallery and then imported. This parameter is mandatory. .PARAMETER LogPath The path where the log file will be created. Defaults to "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs". .PARAMETER LogFile The name of the log file. Defaults to 'Initialize-Module.log'. .EXAMPLE Initialize-Module -Name 'Pester' # Ensures the Pester module is installed (or updated to the latest version) and imported. .EXAMPLE Initialize-Module -Name 'Module1', 'Module2' # Initializes both 'Module1' and 'Module2', ensuring they are installed/updated and imported. .EXAMPLE Initialize-Module -Name 'PSReadLine' -LogPath 'C:\Logs' -LogFile 'ModuleInit.log' # Initializes the PSReadLine module with custom logging location. .NOTES - Requires an active internet connection to access the PowerShell Gallery. - Administrative privileges are generally required to install modules for 'AllUsers' and to install package providers or the 'Microsoft.PowerShell.PSResourceGet' module itself. - The function relies on 'Microsoft.PowerShell.PSResourceGet' for robust interaction with the PowerShell Gallery. - All operations are logged to the specified log file for troubleshooting purposes. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [System.String[]]$Name, [Parameter(Mandatory = $false)] [string]$LogPath = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs", [Parameter(Mandatory = $false)] [string]$LogFile = 'Initialize-Module.log' ) begin { $ProgressPreference = 'SilentlyContinue' # Create log directory if it doesn't exist if (-not (Test-Path -Path $LogPath)) { New-Item -Path $LogPath -ItemType Directory -Force | Out-Null } # Start transcript Start-Transcript -Path (Join-Path -Path $LogPath -ChildPath $LogFile) -Force -Append -Verbose Write-Host 'Starting module initialization process...' -ForegroundColor Cyan # Ensure TLS 1.2 is enabled for compatibility with newer repositories if (-not ([Net.ServicePointManager]::SecurityProtocol.HasFlag([Net.SecurityProtocolType]::Tls12))) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Verbose 'Set security protocol to TLS 1.2.' } else { Write-Verbose 'TLS 1.2 is already enabled in the current security protocol settings.' } # Try to import the built-in PackageManagement module first try { Import-Module -Name PackageManagement -Force -Scope Global -ErrorAction Stop Write-Verbose 'Successfully imported the PackageManagement module.' } catch { Write-Warning 'Failed to import the PackageManagement module directly. Proceeding to ensure NuGet provider and Microsoft.PowerShell.PSResourceGet module are available.' } # Ensure NuGet package provider is installed. # This is a prerequisite for interacting with PSGallery, especially for Install-PSResource. if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { Write-Verbose 'NuGet package provider not found. Attempting to install.' try { $null = Install-PackageProvider -Name NuGet -Force -Scope AllUsers -ErrorAction Stop Write-Verbose 'Successfully installed the NuGet package provider.' } catch { Write-Warning "Failed to install the NuGet package provider. Error details: $($_.Exception.Message)." } } else { Write-Verbose 'NuGet package provider is already available.' } # Add the PowerShell Gallery as trusted repository try { # Check if PSGallery is registered $PSGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue if ($PSGallery) { Write-Verbose 'The PSGallery repository is already registered.' # Check if PSGallery is trusted if ($PSGallery.InstallationPolicy -ne 'Trusted') { Write-Verbose 'Setting the PSGallery repository installation policy to Trusted.' Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -ErrorAction Stop Write-Verbose 'Successfully set the PSGallery repository installation policy to Trusted.' } else { Write-Verbose 'The PSGallery repository installation policy is already Trusted.' } } else { Write-Verbose 'PSGallery repository not found. Attempting to register and set it to Trusted.' Register-PSRepository -Default -ErrorAction Stop Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -ErrorAction Stop Write-Verbose 'Successfully registered the PSGallery repository and set its installation policy to Trusted.' } } catch { Write-Error "Failed to configure the PSGallery repository (register or set trust). Error details: $($_.Exception.Message)" } # Attempt to import Microsoft.PowerShell.PSResourceGet, which is the newer module for PSGallery. $PSResourceGetModule = 'Microsoft.PowerShell.PSResourceGet' $PSResourceGet = Get-Module -Name $PSResourceGetModule -ListAvailable -ErrorAction SilentlyContinue if ([bool]($PSResourceGet)) { Write-Verbose "$PSResourceGetModule module is available locally." # Get the current version of the $PSResourceGetModule module $currentVersion = (Get-Module -Name $PSResourceGetModule -ListAvailable -ErrorAction SilentlyContinue).Version # Update $PSResourceGetModule module if needed if ([bool]($currentVersion)) { Write-Verbose "Currently installed $PSResourceGetModule version: $currentVersion." try { $latestVersion = (Find-PSResource -Name $PSResourceGetModule -Repository PSGallery -ErrorAction SilentlyContinue).Version Write-Verbose "Latest available $PSResourceGetModule version: $latestVersion." Write-Verbose "Checking for updates to $PSResourceGetModule module online..." if (([version]$latestVersion -gt [version]$currentVersion)) { Write-Verbose "Attempting to update $PSResourceGetModule from $currentVersion to $latestVersion." Update-PSResource -Name $PSResourceGetModule -Repository PSGallery -Scope AllUsers -TrustRepository -Quiet -AcceptLicense -Force -ErrorAction SilentlyContinue Write-Verbose "Successfully updated the $PSResourceGetModule module." } else { Write-Verbose "$PSResourceGetModule module is up to date." } } catch { Write-Warning "Unable to check for or apply updates to $PSResourceGetModule module. Error details: $($_.Exception.Message). The currently installed version will be used." } } try { Import-Module -Name $PSResourceGetModule -Force -Scope Global -ErrorAction Stop Write-Verbose "Successfully imported the $PSResourceGetModule module." } catch { Write-Error "Failed to import the $PSResourceGetModule module. Error details: $($_.Exception.Message)." } } else { try { Write-Warning "The $PSResourceGetModule module is not installed. Attempting to install it..." Install-Module -Name $PSResourceGetModule -Repository PSGallery -Force -Scope AllUsers -AllowClobber -ErrorAction Stop Import-Module -Name $PSResourceGetModule -Force -Scope Global -ErrorAction Stop Write-Verbose "Successfully installed and imported the $PSResourceGetModule module for AllUsers." } catch { Write-Warning "Failed to install and import the $PSResourceGetModule module. Error details: $($_.Exception.Message)." } } } process { foreach ($Module in $Name) { Write-Verbose "Processing module: '$Module'." try { # Check if module is already imported and available locally in one operation $localModule = Get-Module -Name $Module -ListAvailable -ErrorAction SilentlyContinue if ([bool]($localModule)) { Write-Host "Module $Module is already installed and imported." -ForegroundColor Yellow $currentVersion = $localModule | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version $modulePath = ($localModule).ModuleBase Write-Verbose "Module '$Module' version $currentVersion found locally at '$modulePath'." # Module is available locally, check for updates try { Write-Verbose "Checking for updates online for module '$Module'." $onlineVersion = (Find-PSResource -Name $Module -Repository PSGallery -ErrorAction Stop).Version Write-Verbose "Latest available version for '$Module' is $onlineVersion." # Update if online version is newer if ([version]$onlineVersion -gt [version]$currentVersion) { Write-Verbose "Attempting to update module '$Module' from version $currentVersion to $onlineVersion..." try { Update-PSResource -Name $Module -Repository PSGallery -Scope AllUsers -TrustRepository -Quiet -AcceptLicense -Force -ErrorAction SilentlyContinue if (Test-Path -Path $modulePath) { # Check if path still exists before attempting removal Write-Verbose "Attempting to remove old module files from '$modulePath' after update." Remove-Item -Path $modulePath -Force -Recurse -ErrorAction SilentlyContinue } Write-Host "Successfully updated module '$Module' to version $($onlineModule.Version)." -ForegroundColor Green } catch { Write-Warning "An error occurred during the update process for module '$Module' (Update-PSResource or manual folder removal at '$modulePath'). Attempting a full reinstall. Error details: $($_.Exception.Message)" Uninstall-PSResource -Name $Module -Scope AllUsers -ErrorAction SilentlyContinue # Best effort uninstall Write-Verbose "Attempting to install module '$Module' after failed update." Install-PSResource -Name $Module -AcceptLicense -Scope AllUsers -TrustRepository -Repository PSGallery -Quiet -ErrorAction Stop Write-Host "Successfully reinstalled module '$Module'." -ForegroundColor Green } } else { Write-Host "Module '$Module' (version $currentVersion) is already up to date." -ForegroundColor Yellow } } catch { Write-Warning "Unable to check for updates online for module '$Module'. Error details: $($_.Exception.Message). The locally installed version $currentVersion will be used." } # Import the module try { Write-Verbose "Attempting to import module '$Module'." Import-Module -Name $Module -Force -Global -DisableNameChecking -ErrorAction Stop Write-Host "Successfully imported module '$Module'." -ForegroundColor Green } catch { Write-Error "Failed to import module '$Module'. Error details: $($_.Exception.Message)" } } else { # Module is not available locally, try to install from gallery Write-Verbose "Module '$Module' not found locally." try { Write-Verbose "Searching for module '$Module' in the PSGallery repository." $onlineModule = Find-PSResource -Name $Module -ErrorAction Stop # Ensure it exists before install Write-Verbose "Module '$Module' version $($onlineModule.Version) found in PSGallery. Attempting to install..." Install-PSResource -Name $Module -AcceptLicense -Scope AllUsers -TrustRepository -Repository PSGallery -Quiet -ErrorAction Stop Write-Verbose "Attempting to import module '$Module' after installation." Import-Module -Name $Module -Force -Global -DisableNameChecking -ErrorAction Stop Write-Host "Successfully installed and imported module $Module." -ForegroundColor Green } catch { Write-Error "Failed to find or install module '$Module' from the PSGallery repository. Error details: $($_.Exception.Message)" throw "Module '$Module' is not installed locally and could not be found in or installed from the PSGallery repository. Initialization cannot proceed for this module." } } } catch { Write-Error "Failed to initialize module '$Module'. Error details: $($_.Exception.Message)" # This error means the current module in the loop failed. The loop will continue to the next module. } } } end { # Stop transcript Stop-Transcript } |