Functions/Install-LatestModule.ps1


Function Install-LatestModule {
    <#
    .SYNOPSIS
       This function will install and update module to latest version
 
    .DESCRIPTION
        The function will install and update module to latest version under system context
 
    .PARAMETER Name
        Specify the name of the module
 
    .PARAMETER All
        Will scan for all modules install under C:\Program Files\WindowsPowerShell\Modules and attempt to update them
 
    .PARAMETER Force
        Will reinstall modules no matter if the module is at the latest version
 
    .PARAMETER AllowImport
        After installation of module; it will import it for use within powershell session
 
    .EXAMPLE
        Install-LatestModule -All
        Example will update all modules installed under C:\Program Files\WindowsPowerShell\Modules
 
    .EXAMPLE
        Install-LatestModule -Name AzureAD
        Example will scan for module AzureAD and check its version. it will update if older than PSGallewries latest version
 
    .EXAMPLE
        Install-LatestModule -Name Az.Accounts,Az.Resources,Microsoft.Graph.Intune -Confirm:$false
        Example will scan for 3 modules and check its version. it will update if older than PSGallery's latest version
 
    .EXAMPLE
        Install-LatestModule -Name AzureAD -Force -Confirm:$false
        Example will scan for module AzureAD and check its version. If the version is already up-to-date; Force will reinstall it
 
    .EXAMPLE
        Get-Module -ListAvailable | Install-LatestModule
        Example will scan for all modules on device and install the latest for each if found
    .EXAMPLE
       $Name = 'Az.Resources'
       $Name = 'AzFilesHybrid'
       $Name = 'PSReadLine'
       $Name = 'AzureAD'
    #>

    [CmdletBinding(DefaultParameterSetName = 'NameParameterSet',
        HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=398573',
        SupportsShouldProcess = $true,ConfirmImpact = 'High')]
    Param(
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromPipeline = $true,
            Position = 0,
            ParameterSetName = 'NameParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Alias("ModuleName")]
        [string[]]
        $Name,

        [Parameter(Mandatory = $true,
            Position = 0,
            ParameterSetName = 'AllParameterSet')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $All,

        [Parameter()]
        [switch]
        $Force,

        [Parameter()]
        [bool]
        $AllUsers = $true,

        [Parameter()]
        [switch]
        $AllowImport
    )

    Begin{
        ## Get the name of this function
        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name

        #build log name
        If($DebugPreference){
            [string]$FileName = 'InstallLatestModules_' + ${CmdletName} + '_' + (get-date -Format MM-dd-yyyy) + '.log'
            Start-Transcript -Path $env:TEMP\$FileName -Force -Append | Out-Null
        }

        if (-not $PSBoundParameters.ContainsKey('WhatIf')) {
            $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference')
        }

        Write-Output ("{0} :: Checking for latest installed modules [Press Ctrl+C to cancel]..." -f ${CmdletName})

        #grab just module installed
        $InstalledModules = Get-InstalledModule -ErrorAction SilentlyContinue

        #set variables
        [string]$ModuleName = $null
        $LatestModule = $null
        $ExistingModules = $null
        $RefreshNeeded = $false
        $ModuleInstallStatus = @()


        #set scope for module install
        If($AllUsers -eq $true){$ScopeParam = @{Scope = 'AllUsers'} }Else{$ScopeParam= @{Scope = 'CurrentUser'}}
    }
    Process{
        #create object
        $ModuleDetails = '' | Select ModuleName,InstallStatus,InstallVersion,InstallPath,PriorVersion,InstallMessage,ErrorMsg

        #check if all modules are to be updated, just use one unique name
        If($All){
            $ModuleNames = $InstalledModules.name | Select -Unique
        }Else{
            $ModuleNames = $Name
        }

        #TEST $moduleName = $moduleNames | Select -First 1
        foreach ($ModuleName in $ModuleNames)
        {
            $ModuleDetails.ModuleName = $ModuleName
            Write-Verbose ("{0} :: Searching for Module: {1}" -f ${CmdletName},$ModuleName)

            #$ExistingModules = $InstalledModules | Where-Object Name -eq $ModuleName
            $ExistingModules = Get-Module -Name $ModuleName -ListAvailable

            If($ExistingModules.count -gt 1){
                Write-Verbose ("{0} :: Multiple versions found: {1}" -f ${CmdletName},$ExistingModules.count)
            }ElseIf($ExistingModules){
                Write-Verbose ("{0} :: Found installed version: {1}" -f ${CmdletName},$ExistingModules.Version.ToString())
            }Else{
                Write-Verbose ("{0} :: Module not found" -f ${CmdletName})
            }

            #search for module online and get latest version
            Write-Verbose ("{0} :: Checking for the latest version..." -f ${CmdletName})
            $LatestModule = Find-Module $ModuleName -ErrorAction SilentlyContinue

            #if latest module has been found online, proceed
            If($null -ne $LatestModule)
            {

                #ignore any versions installed, uninstall all and install latest
                Try
                {
                    if($PSCmdlet.ShouldProcess($ModuleName)){
                        
                        If($PSBoundParameters.ContainsKey('Force'))
                        {
                            $StatusMsg = ("Force uninstalling existing module [{0} ({1})] and installing to [{2}]" -f $ModuleName,$ExistingModules.Version.ToString(),$LatestModule.Version)
                            Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                            $ModuleDetails.InstallMessage = $StatusMsg
                            
                            $ExistingModules | Remove-Module -Force -ErrorAction Stop
                            $ExistingModules | Uninstall-Module -Force -ErrorAction Stop
                            Install-Module $ModuleName -RequiredVersion $LatestModule.Version @ScopeParam -Force -SkipPublisherCheck -ErrorAction Stop

                            $ModuleDetails.InstallStatus = 'InstalledToLatest'
                            $RefreshNeeded = $true
                        }
                        Else
                        {                       
                            #if no moduels exist
                            If($null -eq $ExistingModules)
                            {
                                $StatusMsg = ("Module not found [{0}], installing..." -f $ModuleName)
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                $ModuleDetails.InstallMessage = $StatusMsg

                                Install-Module $ModuleName @ScopeParam -Force -SkipPublisherCheck -AllowClobber -ErrorAction Stop

                                $ModuleDetails.InstallStatus = 'NewInstall'
                                $ModuleDetails.InstallVersion = $LatestModule.Version
                            }

                            #check if module is installed for all users or current user
                            ElseIf(($ExistingModules.Path -like "$env:USERPROFILE*") -and ($ExistingModules.Path -notlike "*.vscode*") -and ($AllUsers -eq $true)){
                                
                                $StatusMsg = ("Uninstalling existing module [{0}] from scope [{1}] and installing latest [{2}] in scope [{3}]" -f $ModuleName,'CurrentUser',$LatestModule.Version,'AllUsers')
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                $ModuleDetails.InstallMessage = $StatusMsg
                                
                                $ExistingModules | Remove-Module -Force -ErrorAction Stop
                                $ExistingModules | Uninstall-Module -Force -ErrorAction Stop
                                Install-Module $ModuleName -RequiredVersion $LatestModule.Version @ScopeParam -Force -SkipPublisherCheck -ErrorAction Stop

                                $ModuleDetails.InstallStatus = 'MovedToAllUsers'
                                $RefreshNeeded = $true
                            }

                            #check if module is installed for all users or current user
                            ElseIf(($ExistingModules.Path -notlike "$env:USERPROFILE*") -and ($AllUsers -ne $true)){
                                $StatusMsg = ("Uninstalling existing module [{0}] from scope [{1}] and installing latest [{2}] in scope [{3}]" -f $ModuleName,'AllUsers',$LatestModule.Version,'CurrentUser')
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                $ModuleDetails.InstallMessage = $StatusMsg
                                
                                $ExistingModules | Remove-Module -Force -ErrorAction Stop
                                $ExistingModules | Uninstall-Module -Force -ErrorAction Stop
                                Install-Module $ModuleName -RequiredVersion $LatestModule.Version @ScopeParam -Force -SkipPublisherCheck -ErrorAction Stop

                                $ModuleDetails.InstallVersion = $LatestModule.Version
                                $ModuleDetails.InstallStatus = 'MovedToCurrentUser'
                                $RefreshNeeded = $true
                            }

                            ElseIf($ExistingModules.Version -eq $LatestModule.Version)
                            {
                                $StatusMsg = ("Module [{0}] is at latest version [{1}]!" -f $ModuleName,$ExistingModules.Version)
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                $ModuleDetails.InstallMessage = $StatusMsg
                                
                                $ModuleDetails.InstallStatus = 'UpToDate'
                                $ModuleDetails.InstallVersion = $ExistingModules.Version
                            }

                            #are there multiple of the same module installed?
                            ElseIf( ($ExistingModules | Measure-Object).Count -gt 1)
                            {
                                $ModuleDetails.PriorVersion = $ExistingModules.Version
                                If($LatestModule.Version -in $ExistingModules.Version)
                                {
                                    $StatusMsg = ("Latest Module found [{1}], Cleaning up older [{0}] modules..." -f $ModuleName,$LatestModule.Version.ToString())
                                    Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                    $ModuleDetails.InstallMessage = $StatusMsg

                                    #Check to see if latest module is installed already and uninstall anything older
                                    $ExistingModules | Where-Object Version -NotMatch $LatestModule.Version | Uninstall-Module -Force -ErrorAction Stop
                                    
                                    $ModuleDetails.InstallStatus = 'RemovedOlder'
                                }
                                Else
                                {
                                    $StatusMsg = ("Uninstalling older [{0}] modules and installing the latest module version [{1}]..." -f $ModuleName,$LatestModule.Version.ToString())
                                    Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                    $ModuleDetails.InstallMessage = $StatusMsg

                                    #uninstall all older Modules with that name, then install the latest
                                    Get-Module -FullyQualifiedName $ModuleName -ListAvailable | Uninstall-Module -Force -ErrorAction Stop
                                    Install-Module $ModuleName -RequiredVersion $LatestModule.Version @ScopeParam -AllowClobber -Force -SkipPublisherCheck -ErrorAction Stop
     
                                    $ModuleDetails.InstallStatus = 'Installed'
                                }
                                $ModuleDetails.InstallVersion = $LatestModule.Version
                                $RefreshNeeded = $true
                            }

                            #if only one module exist but not the latest version
                            ElseIf($ExistingModules.Version -ne $LatestModule.Version)
                            {
                                Write-Verbose ("{0} :: Found newer version [{1}]..." -f ${CmdletName},$LatestModule.Version.ToString())
                                #Update module since it was found
                                $StatusMsg = ("Updating Module [{0}] from [{1}] to the latest version [{2}]..." -f $ModuleName,$ExistingModules.Version,$LatestModule.Version)
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)
                                $ModuleDetails.InstallMessage = $StatusMsg

                                Try{
                                    Update-Module $ModuleName -RequiredVersion $LatestModule.Version -Force -ErrorAction Stop    
                                    $ModuleDetails.InstallStatus = 'Updated'
                                }
                                Catch{
                                    #$_.Exception.GetType().FullName
                                    Install-Module $ModuleName -RequiredVersion $LatestModule.Version @ScopeParam -AllowClobber -Force -SkipPublisherCheck -ErrorAction Stop
                                    $ModuleDetails.InstallStatus = 'Installed'
                                }
                                $ModuleDetails.InstallVersion = $LatestModule.Version
                                $RefreshNeeded = $true
                            }
                            Else
                            {
                                $StatusMsg = ("Unable to determine if module [{0}] is at latest version!",$ModuleName)
                                Write-Verbose ("{0} :: {1}" -f ${CmdletName},$StatusMsg)

                                $ModuleDetails.InstallMessage = $StatusMsg
                                $ModuleDetails.InstallStatus = $false
                                Continue
                            }
                        }
                        $ModuleDetails.InstallStatus = $true
                        $ModuleDetails.InstallPath = (Get-Module -Name $ModuleName -ListAvailable).Path

                    }Else{
                        $ModuleDetails.InstallStatus = 'Skipped'
                        $ModuleDetails.InstallVersion = $ExistingModules.Version
                        $ModuleDetails.InstallPath = $ExistingModules.Path
                    }
                    
                }
                Catch
                {
                    Write-Error ("Failed with error: {0}" -f $_.Exception.Message)
                    $ModuleDetails.InstallStatus = 'Failed'
                    $ModuleDetails.InstallPath = $ExistingModules.Path
                    $ModuleDetails.ErrorMsg = $_.Exception.Message
                }
                Finally
                {
                    If($AllowImport){
                        #importing module
                        Write-Verbose ("{0} :: Importing Module [{1}] for use..." -f ${CmdletName},$ModuleName)
                        Import-Module -Name $ModuleName -Force:$force -Verbose:$VerbosePreference
                    }
                }
            }
            Else{
                Write-Verbose ("{0} :: Module [{1}] does not exist, unable to update" -f ${CmdletName},$ModuleName)
                $ModuleDetails.InstallStatus = 'NotFound'
            }

            $ModuleInstallStatus += $ModuleDetails

        } #end of module loop
    }
    End{
        Write-Output ("{0} :: Completed updates on {1} modules" -f ${CmdletName},$ModuleInstallStatus.count)
        If($RefreshNeeded){Write-Host ("A restart of Powershell may be required to refresh module versions.") -ForegroundColor Magenta}
        If($DebugPreference){Stop-Transcript | Out-Null}
        return $ModuleInstallStatus
    }
}

#Export-ModuleMember -Function Install-LatestModule