Initialize-Module.ps1
<#PSScriptInfo
.VERSION 1.1 .GUID 0e34e80c-49a0-4579-8113-fd793a44d92c .AUTHOR Jonathan Pitre .TAGS PowerShell Module Automation .RELEASENOTES 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 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-Module. 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. if (-not (Get-Module -Name Microsoft.PowerShell.PSResourceGet -ErrorAction SilentlyContinue)) { Write-Verbose 'Microsoft.PowerShell.PSResourceGet module is not imported.' try { Import-Module -Name Microsoft.PowerShell.PSResourceGet -Force -Scope Global -ErrorAction Stop Write-Verbose 'Successfully imported the Microsoft.PowerShell.PSResourceGet module.' } catch { Write-Warning 'The Microsoft.PowerShell.PSResourceGet module could not be imported. Attempting to install or update it.' try { Install-Module Microsoft.PowerShell.PSResourceGet -Repository PSGallery -Force -Scope AllUsers -AllowClobber -ErrorAction Stop Import-Module -Name Microsoft.PowerShell.PSResourceGet -Force -Scope Global -ErrorAction Stop Write-Verbose 'Successfully installed and imported the Microsoft.PowerShell.PSResourceGet module for AllUsers.' } catch { Write-Warning "Failed to install or import the Microsoft.PowerShell.PSResourceGet module. Error details: $($_.Exception.Message)." } } } else { Write-Verbose 'Microsoft.PowerShell.PSResourceGet module is already imported.' } # Update Microsoft.PowerShell.PSResourceGet if needed $localPSResourceGetVersion = (Get-InstalledModule -Name Microsoft.PowerShell.PSResourceGet -ErrorAction SilentlyContinue).Version if ($localPSResourceGetVersion) { Write-Verbose "Currently installed Microsoft.PowerShell.PSResourceGet version: $localPSResourceGetVersion." try { $onlinePSRessourceGetVersion = (Find-Module -Name Microsoft.PowerShell.PSResourceGet -ErrorAction Stop).Version Write-Verbose "Latest available Microsoft.PowerShell.PSResourceGet version: $onlinePSRessourceGetVersion." Write-Verbose 'Checking for updates to Microsoft.PowerShell.PSResourceGet module online...' if (([version]$onlinePSRessourceGetVersion -gt [version]$localPSResourceGetVersion)) { Write-Verbose "Attempting to update Microsoft.PowerShell.PSResourceGet from $localPSResourceGetVersion to $onlinePSRessourceGetVersion." Install-Module Microsoft.PowerShell.PSResourceGet -Repository PSGallery -Force -Scope AllUsers -ErrorAction Stop Write-Verbose 'Successfully updated the Microsoft.PowerShell.PSResourceGet module.' } else { Write-Verbose 'Microsoft.PowerShell.PSResourceGet module is up to date.' } } catch { Write-Warning "Unable to check for or apply updates to Microsoft.PowerShell.PSResourceGet module. Error details: $($_.Exception.Message). The currently installed version will be used." } } else { Write-Verbose 'Microsoft.PowerShell.PSResourceGet module not found locally, installation attempt should have occurred if not imported.' } } process { foreach ($Module in $Name) { Write-Verbose "Processing module: '$Module'." try { # If module is already imported, skip if (Get-Module -Name $Module -ErrorAction SilentlyContinue) { Write-Host "Module $Module is already installed and imported." -ForegroundColor Yellow continue } # Check if module is available locally $localModule = Get-InstalledModule -Name $Module -ErrorAction SilentlyContinue if ([bool]($localModule)) { $localVersion = $localModule | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version $modulePath = ($localModule | Sort-Object -Property Version -Descending | Select-Object -First 1).InstalledLocation Write-Verbose "Module '$Module' version $localVersion found locally at '$modulePath'." # Module is available locally, check for updates try { Write-Verbose "Checking for updates online for module '$Module'." $onlineModule = Find-Module -Name $Module -ErrorAction Stop Write-Verbose "Latest available version for '$Module' is $($onlineModule.Version)." # Update if online version is newer if ([version]$onlineModule.Version -gt [version]$localVersion) { Write-Verbose "Attempting to update module '$Module' from version $localVersion to $($onlineModule.Version)..." try { Update-Module -Name $Module -Force -ErrorAction Stop # The Remove-Item line is kept as per original logic, though it's unusual. 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 Stop } 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-Module or manual folder removal at '$modulePath'). Attempting a full reinstall. Error details: $($_.Exception.Message)" Uninstall-Module -Name $Module -Force -AllVersions -ErrorAction SilentlyContinue # Best effort uninstall Write-Verbose "Attempting to install module '$Module' after failed update." Install-Module -Name $Module -Force -AllowClobber -Scope AllUsers -ErrorAction Stop Write-Host "Successfully reinstalled module '$Module'." -ForegroundColor Green } } else { Write-Host "Module '$Module' (version $localVersion) 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 $localVersion will be used." } # Import the module Write-Verbose "Attempting to import module '$Module'." Import-Module -Name $Module -Force -Global -DisableNameChecking -ErrorAction Stop Write-Host "Successfully imported module '$Module'." -ForegroundColor Green } 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-Module -Name $Module -ErrorAction Stop # Ensure it exists before install Write-Verbose "Module '$Module' version $($onlineModule.Version) found in PSGallery. Attempting to install..." Install-Module -Name $Module -AllowClobber -Force -Scope AllUsers -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." } Stop-Transcript } } catch { Write-Error "Failed to initialize module '$Module'. Error details: $($_.Exception.Message)" Stop-Transcript # This error means the current module in the loop failed. The loop will continue to the next module. } } } |