CoreePower.Lib.Modules.Management.ps1

if (-not($PSScriptRoot -eq $null -or $PSScriptRoot -eq "")) { 
    . $PSScriptRoot\CoreePower.Lib.Includes.ps1
}

<#
.SYNOPSIS
    Retrieves information about installed PowerShell modules with extended details.
 
.DESCRIPTION
    The `Get-ModulesInfoExtended` cmdlet retrieves information about installed PowerShell modules, including their name, version, author, and description, as well as extended details about their installation location and scope.
    By default, it retrieves information about all modules installed on the local machine, including modules installed in both the `LocalMachine` and `CurrentUser` scopes.
    Note that when multiple versions of a module are installed on a system, the latest version takes precedence, regardless of whether it is installed in the `LocalMachine` or `CurrentUser` scope.
    Therefore, in this context, `LocalMachine` refers to modules installed in both `LocalMachine` and `CurrentUser` scopes.
 
.PARAMETER ModuleNames
    Specifies the names of the PowerShell modules to retrieve information for. You can use the wildcard character (*) to retrieve information about all installed modules.
 
.PARAMETER Scope
    Specifies the scope of the PowerShell modules to retrieve information for. The available values are "LocalMachine" and "CurrentUser".
 
.PARAMETER ExcludeSystemModules
    Indicates whether to exclude modules that are installed in system folders from the results. If specified, modules that are installed in system folders (including both user-installed and system-installed modules) are excluded from the results.
 
.OUTPUTS
    The cmdlet returns an object that includes the following properties additional information for each installed module:
    - BasePath: the base path of the module's installation directory
    - IsMachine: a boolean value indicating whether the module is installed in a machine-wide folder
    - IsUser: a boolean value indicating whether the module is installed in a user-specific folder
    - IsSystem: a boolean value indicating whether the module is installed in a system folder (i.e., a folder within the Windows directory)
 
.EXAMPLE
    PS C:\> Get-ModulesInfoExtended
 
    This command retrieves information about all installed PowerShell modules on the local machine, including extended details about their installation location and scope.
 
.EXAMPLE
    PS C:\> Get-ModulesInfoExtended -ModuleNames MyModule
 
    This command retrieves information about the PowerShell module named "MyModule", including extended details about its installation location and scope.
 
.EXAMPLE
    PS C:\> Get-ModulesInfoExtended -Scope LocalMachine -ExcludeSystemModules $true
 
    This command retrieves information about all installed PowerShell modules, excluding modules that are installed in system folders from the results.
#>

function Get-ModulesInfoExtended {
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    param(
        [string[]] $ModuleNames = @('*'),
        [ModuleScope]$Scope = [ModuleScope]::LocalMachine,
        [bool]$ExcludeSystemModules = $false
    )

    $LocalModulesAll = Get-Module -Name $ModuleNames -ListAvailable  |  Select-Object *,
        @{ Name='BasePath' ; Expression={ $_.ModuleBase.TrimEnd($_.Version.ToString()).TrimEnd('\').TrimEnd($_.Name).TrimEnd('\')  } },
        @{ Name='IsMachine' ; Expression={ ($_.ModuleBase -Like "*$env:ProgramFiles*") -or ($_.ModuleBase -Like "*$env:ProgramW6432*")  } },
        @{ Name='IsUser' ; Expression={ ($_.ModuleBase -Like "*$env:userprofile*") } },
        @{ Name='IsSystem' ; Expression={ ($_.ModuleBase -Like "*$env:SystemRoot*")  } } 

    if ($Scope -eq [ModuleScope]::LocalMachine -and ($ExcludeSystemModules -eq $false))
    {
        return $LocalModulesAll
    }
    elseif ($Scope -eq [ModuleScope]::LocalMachine -and ($ExcludeSystemModules -eq $true)) {
        $LocalAndUser = $LocalModulesAll | Where-Object { $_.IsSystem -eq $false }
        return $LocalAndUser
    }
    elseif ($Scope -eq [ModuleScope]::CurrentUser) {
        $UserModules = $LocalModulesAll | Where-Object { $_.IsUser -eq $true }
        return $UserModules
    }
}

<#
.SYNOPSIS
    Retrieves local modules based on specified criteria.
 
.DESCRIPTION
    The Get-ModulesLocal function retrieves local modules based on the specified module names, scope, and record state.
 
.PARAMETER ModuleNames
    Specifies the names of the modules to retrieve. By default, all modules are included.
 
.PARAMETER Scope
    Specifies the scope from which to retrieve the modules. The available options are: LocalMachine, CurrentUser.
    The default value is LocalMachine.
 
.PARAMETER ExcludeSystemModules
    Determines whether system modules are excluded from the results. By default, system modules are excluded.
 
.PARAMETER ModuleRecordState
    Specifies the record state of the modules to retrieve. The available options are: Latest, Previous.
    The default value is Latest.
 
.OUTPUTS
    Returns the collection of local modules based on the specified criteria.
 
.EXAMPLE
    Get-ModulesLocal -ModuleNames ModuleA, ModuleB -Scope CurrentUser
    Retrieves the local modules with the names 'ModuleA' and 'ModuleB' from the CurrentUser scope.
 
.EXAMPLE
    Get-ModulesLocal -ModuleNames '*' -ExcludeSystemModules $false
    Retrieves all local modules, including system modules.
#>

function Get-ModulesLocal {
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    param(
        [string[]] $ModuleNames = @('*'),
        [ModuleScope]$Scope = [ModuleScope]::LocalMachine,
        [bool]$ExcludeSystemModules = $true,
        [ModuleRecordState]$ModulRecordState = [ModuleRecordState]::Latest
    )

    $ModuleInfo = Get-ModulesInfoExtended -ModuleNames $ModuleNames -Scope $Scope -ExcludeSystemModules $ExcludeSystemModules
    $LocalModulesAll = $ModuleInfo | Sort-Object Name, Version -Descending

    if ($ModulRecordState -eq [ModuleRecordState]::Latest) {
        $LatestLocalModules = $LocalModulesAll | Group-Object Name | ForEach-Object { $_.Group | Select-Object -First 1  }
    }
    elseif ($ModulRecordState -eq [ModuleRecordState]::Previous){
        $LatestLocalModules = $LocalModulesAll | Group-Object Name | ForEach-Object { $_.Group | Select-Object -Skip 1  }
    } else {
        $LatestLocalModules = $LocalModulesAll | Group-Object Name | ForEach-Object { $_.Group }
    }

    return $LatestLocalModules
}

<#
.SYNOPSIS
    Finds and lists updatable PowerShell modules.
 
.DESCRIPTION
    The Get-ModulesUpdatable function takes an array of module names and retrieves their update information from the PSGallery repository.
    It then compares the available versions with locally installed versions and returns a list of modules that have updates available.
 
.PARAMETER ModuleNames
    An array of module names for which to find update information.
 
.EXAMPLE
    Get-ModulesUpdatable -ModuleNames @("ModuleName1", "ModuleName2")
#>

function Get-ModulesUpdatable {
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    param(
        [string[]] $ModuleNames = @('*'),
        [ModuleScope]$Scope = [ModuleScope]::LocalMachine,
        [string[]] $Repositorys = @('All')
    )

    $AllRepositoryNames = (Get-PSRepository | Select-Object -ExpandProperty Name) -as [string[]]

    if ($Repositorys -contains "All") {
        $Repositorys = $AllRepositoryNames
    }

    $LatestLocalModules = Get-ModulesLocal -ModuleNames $ModuleNames -Scope $Scope -ExcludeSystemModules $true -ModulRecordState Latest

    if ($null -eq $LatestLocalModules)
    {
        return
    }

    [string[]]$SeachLocalModulesInPSGallery = $LatestLocalModules | Select-Object -ExpandProperty Name

    $LocalModulesMaxLimitFind = Split-Array -SourceArray $SeachLocalModulesInPSGallery -MaxPartitionSize 63
 
    $LatestRemoteModules = @()

    foreach($Repo in $Repositorys)
    {
        $Block = @()
        foreach($Block in $LocalModulesMaxLimitFind.Partitions)
        {
            $LatestRemoteModules += Find-Module -Name $Block -Repository $Repo -ErrorAction 'silentlycontinue' | Select-Object *
        }
    }
 
    #There can be multiple repos findings , select the last version availible
    $LatestRemoteModules = $LatestRemoteModules | Sort-Object Name, Version -Descending
    $LatestRemoteModules = $LatestRemoteModules | Group-Object Name | ForEach-Object { $_.Group | Select-Object -First 1  }

    #Using wildcards in ModuleNames might cause issues with Find-Module, as it may return a significantly larger number of results.
    $AvailableMatches = $LatestRemoteModules | Where-Object { $_.Name -in $SeachLocalModulesInPSGallery }

    $ModulesToUpdate = $AvailableMatches | Where-Object { $currentUpdate = $_; -not ($LatestLocalModules | Where-Object { $_.Name -eq $currentUpdate.Name -and $_.Version -eq $currentUpdate.Version }) }

    return $ModulesToUpdate
}

<#
.SYNOPSIS
    Removes previous versions of specified modules.
 
.DESCRIPTION
    The Remove-ModulesPrevious function removes previous versions of modules from the specified scope.
 
.PARAMETER ModuleNames
    Specifies the names of the modules to remove. This parameter is mandatory.
 
.PARAMETER Scope
    Specifies the scope from which to remove the modules. The available options are: CurrentUser, LocalMachine.
    The default value is CurrentUser.
#>

function Remove-ModulesPrevious {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "")]
    param(
        [string[]]$ModuleNames = @('*'),
        [ModuleScope]$Scope = [ModuleScope]::CurrentUser
    )

    # Check if the current process can execute in the desired scope
    if (-not(CanExecuteInDesiredScope -Scope $Scope))
    {
        return
    }

    $outdated = Get-ModulesLocal -ModuleNames $ModuleNames -Scope $Scope -ExcludeSystemModules $true -ModulRecordState Previous
 
    foreach ($item in $outdated)
    {
        $DirVers = "$($item.BasePath)\$($item.Name)\$($item.Version)"
        Remove-Item -Recurse -Force -Path $DirVers
        Write-Host "Removed module:" $DirVers
    }
}

<#
.SYNOPSIS
    Removes specified PowerShell modules from the current user's module directory.
 
.DESCRIPTION
    The `Remove-Modules` function removes specified PowerShell modules from the module directory of the current user. It allows for the removal of modules that are no longer needed or outdated.
 
.PARAMETER ModuleNames
    Specifies the names of the PowerShell modules to be removed. Multiple module names can be provided as an array.
 
.PARAMETER Scope
    Specifies the scope of the module removal operation. The available values are "LocalMachine" and "CurrentUser". The default value is "CurrentUser".
 
.NOTES
    - The function requires appropriate permissions to remove modules from the module directory.
    - Removing modules will permanently delete their associated files and directories.
    - The function does not remove modules installed in system folders.
    - It is recommended to use caution when removing modules, as it may affect the functionality of dependent scripts or applications.
 
.EXAMPLE
    PS C:\> Remove-Modules -ModuleNames "Module1", "Module2"
 
    This command removes the PowerShell modules named "Module1" and "Module2" from the current user's module directory.
 
.EXAMPLE
    PS C:\> Remove-Modules -ModuleNames "OutdatedModule" -Scope CurrentUser
 
    This command removes the PowerShell module named "OutdatedModule" from the module directory of the current user.
 
.EXAMPLE
    PS C:\> Remove-Modules -ModuleNames "Module3" -Scope LocalMachine
 
    This command removes the PowerShell module named "Module3" from the module directory of the local machine.
 
.NOTES
    This function internally uses the `Get-ModulesLocal` function to retrieve module information and the `Remove-Item` cmdlet to delete module files and directories.
#>

function Remove-Modules {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "")]
    param(
        [string[]]$ModuleNames,
        [ModuleScope]$Scope = [ModuleScope]::CurrentUser
    )

    # Check if the current process can execute in the desired scope
    if (-not(CanExecuteInDesiredScope -Scope $Scope))
    {
        return
    }
    
    $outdated = Get-ModulesLocal -ModuleNames $ModuleNames -Scope $Scope -ExcludeSystemModules $true -ModulRecordState All
 
    foreach ($item in $outdated)
    {
        $DirVers = "$($item.BasePath)\$($item.Name)\$($item.Version)"
        Remove-Item -Recurse -Force -Path $DirVers
        Write-Host "Removed module:" $DirVers
    }
}

<#
.SYNOPSIS
    Updates specified PowerShell modules to their latest versions.
 
.DESCRIPTION
    The `Update-ModulesLatest` function updates specified PowerShell modules to their latest versions. It retrieves the available updates for the specified modules and installs them, ensuring that the modules are up to date.
 
.PARAMETER ModuleNames
    Specifies the names of the PowerShell modules to be updated. Multiple module names can be provided as an array. By default, all installed modules are considered for updating.
 
.PARAMETER Scope
    Specifies the scope of the module update operation. The available values are "LocalMachine" and "CurrentUser". The default value is "CurrentUser".
 
.PARAMETER SuppressProgressPreference
    Indicates whether to suppress progress preference during the update process. By default, progress preference is not suppressed.
 
.NOTES
    - The function requires appropriate permissions to update modules in the module directory.
    - The function internally uses the `Get-ModulesUpdatable` function to retrieve information about available module updates.
    - The function uses the `Install-Module` cmdlet to install the updates.
    - Use caution when updating modules, as it may affect the functionality of dependent scripts or applications.
 
.EXAMPLE
    PS C:\> Update-ModulesLatest
 
    This command updates all installed PowerShell modules to their latest versions in the current user's module directory.
 
.EXAMPLE
    PS C:\> Update-ModulesLatest -ModuleNames "Module1", "Module2" -Scope LocalMachine
 
    This command updates the PowerShell modules named "Module1" and "Module2" to their latest versions in the module directory of the local machine.
 
.EXAMPLE
    PS C:\> Update-ModulesLatest -ModuleNames "*" -SuppressProgressPreference $true
 
    This command updates all installed PowerShell modules to their latest versions in the current user's module directory, suppressing progress preference during the update process.
 
.NOTES
    - If updates are applied successfully, the function returns `$true`. Otherwise, it returns `$false`.
    - It is recommended to regularly update modules to benefit from bug fixes and new features.
#>

function Update-ModulesLatest {
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    param(
        [string[]] $ModuleNames = @('*'),
        [ModuleScope]$Scope = [ModuleScope]::CurrentUser,
        [bool]$SuppressProgressPreference = $false
    )
    # Check if the current process can execute in the desired scope
    if (-not(CanExecuteInDesiredScope -Scope $Scope))
    {
       # return
    }

    $UpdatableModules = Get-ModulesUpdatable -ModuleNames $ModuleNames
    $UpdatesApplied = $false

    foreach($module in $UpdatableModules)
    {
        #Write-Output "Installing module: $($module.Name) $($module.Version)"

        if ($SuppressProgressPreference)
        {
            $originalProgressPreference = $global:ProgressPreference
            $global:ProgressPreference = 'SilentlyContinue'
        }
        Install-Module -Name $module.Name -RequiredVersion $module.Version -Scope $Scope -Repository $module.Repository -Force -AllowClobber -SkipPublisherCheck | Out-Null
        if ($SuppressProgressPreference)
        {
        
            $global:ProgressPreference = $originalProgressPreference
        }
        $UpdatesApplied = $true
    }
    if ($UpdatesApplied)
    {
        return $true
    } else {
        return $false
    }
}

function Get-CurrentModule {
    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    param()

    if ($null -ne $MyInvocation.MyCommand.Module)
    {
        $module = Get-Module -Name $MyInvocation.MyCommand.Module.Name
        $moduleName = $module.Name
        $moduleVersion = $module.Version
    }
    else {
        $moduleName = $MyInvocation.MyCommand.CommandType
        $moduleVersion = "None"
    }
    return ,$moduleName , $moduleVersion
}

function Test.CoreePower.Lib.Modules.Management {
    param()
    Write-Host "Start Test.CoreePower.Lib.Modules.Management"
    #$result1 = Get-ModulesInfoExtended
    #$result2 = Get-ModulesLocal -ModuleNames @("CoreePower.Lib") -ModulRecordState All
    #$result3 = Get-ModulesUpdatable
    #$result4 = Remove-ModulesPrevious
    #$result5 = Remove-Modules
    #$result6 = Update-ModulesLatest
    Write-Host "End Test.CoreePower.Lib.Modules.Management"
}

if ($Host.Name -match "Visual Studio Code")
{
    Test.CoreePower.Lib.Modules.Management
}