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
}