Public/Import-OriAzBopPsModule.ps1

<#
.SYNOPSIS
    Installs a PowerShell module from an online gallery and returns its dependency tree.
 
.DESCRIPTION
    This cmdlet facilitates the installation of PowerShell modules from an online gallery. It performs the following actions:
 
    1. Verifies if the required module is already loaded with the specified version or higher.
    2. If not, it attempts to import the required version from installed modules.
    3. If the module is still not loaded or missing (i.e., not installed), it proceeds to install it.
    4. Checks prerequisites, such as the PowerShell version, PackageManager version, and others.
    5. Installs any necessary tools as needed.
    6. Registers the required repository if it isn't already registered.
    7. Installs the specified module from the online gallery.
    8. Loads the required version of the module.
     
    After completion, the cmdlet returns the dependency tree of the installed module.
 
.PARAMETER DevOpsAccount
    Specifies the name of the DevOps account. The default value is 'oriflame'.
 
.PARAMETER RegisterPsRepoFeedList
    A list of PowerShell repository feeds to register if needed.
 
.PARAMETER RegisterNugetRepoFeedList
    A list of NuGet repository feeds to register if needed.
 
.PARAMETER Name
    Specifies the name of the module(s) to install from the online gallery. The provided name(s) must match the exact module name(s) in the repository.
 
.PARAMETER Repository
    The repository feed to use for retrieving PowerShell modules. Registers the repository if necessary.
 
.PARAMETER Guid
    Specifies the exact GUID of the module.
 
.PARAMETER MaximumVersion
    Specifies the maximum acceptable version of the module.
 
.PARAMETER RequiredVersion
    Specifies the exact required version of the module. If not provided, the latest version will be installed and loaded.
 
.PARAMETER Version
    Specifies the minimum acceptable version of the module.
 
.PARAMETER Credential
    Credentials to use for accessing the repository, if required.
 
.PARAMETER SkipImport
    Skips the `Import-Module` step when specified.
 
.PARAMETER Prefix
    Adds a prefix to the module name during the `Import-Module` step, if specified.
 
.PARAMETER RegisterViaPSRepository
    Registers the repository using `Register-PSRepository` if specified. Otherwise, it uses `Register-PackageSource`.
 
.PARAMETER SkipInstallCredProvider
    Skips the installation of the Credential Provider if specified.
 
.PARAMETER SkipCriticalPathCheck
    Skips the critical path check if specified.
 
.PARAMETER UseWarningMsgForCriticalPath
    Uses a warning message for the critical path if specified.
 
.PARAMETER SleepInSec
    Specifies the time (in seconds) to wait between checks for module readiness before importing.
 
.PARAMETER MaxRetry
    Specifies the maximum number of retries while waiting for the module to become ready for import.
 
.PARAMETER Proxy
    Specifies a proxy to use when accessing the repository, if required.
 
.PARAMETER ProxyCredential
    Provides credentials to authenticate with the proxy, if required.
 
 
.EXAMPLE
$password = ConvertTo-SecureString 'xbchuuuuhaaaatest' -AsPlainText -Force
$RepositoryCredential = New-Object System.Management.Automation.PSCredential 'feafeafae@mydomain.net',$password
Import-OriAzBopPsModule `
-Name OriAzEncEnvironmentConfiguration `
-RequiredVersion 1.0.48 `
-Credential $RepositoryCredential
 
.EXAMPLE
$password = ConvertTo-SecureString 'xbchuuuuhaaaatest' -AsPlainText -Force
$RepositoryCredential = New-Object System.Management.Automation.PSCredential 'feafeafae@mydomain.net',$password
Import-OriAzBopPsModule `
-Name OriAzEncEnvironmentConfiguration `
-RequiredVersion 1.0.48 `
-Credential $RepositoryCredential `
-Proxy 'http://myproxy:8080'
 
#>

function Import-OriAzBopPsModule {

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = "There's required use the re-execute code.")]
    [CmdLetBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory = $false, HelpMessage = "The name of the dev ops account")]
        [String] $DevOpsAccount = $Script:VstsAccount,

        [Parameter(Mandatory = $true, HelpMessage = "Exact name of the module")]
        [String] $Name,

        [Parameter(Mandatory = $false, HelpMessage = "Powershell Repository feed to register if needed")]
        [String[]] $RegisterPsRepoFeedList = @('PackageManagementFeed'),

        [Parameter(Mandatory = $false, HelpMessage = "Nuget Repository feed to register if needed")]
        [String[]] $RegisterNugetRepoFeedList = @('DeploymentPackages'),

        [Parameter(Mandatory = $false, HelpMessage = "Repository feed to register if needed for getting powershell modules")]
        [String] $Repository = 'PackageManagementFeed',

        [Parameter(Mandatory = $false, HelpMessage = "GUID of the module")]
        [string] $Guid,

        [Parameter(Mandatory = $false, HelpMessage = "Maximum module version")]
        [Version] $MaximumVersion,

        [Parameter(Mandatory = $false, HelpMessage = "Required module version")]
        [Version] $RequiredVersion,

        [Alias("Version")]
        [Parameter(Mandatory = $false, HelpMessage = "Most likely minimum module version")]
        [Version] $MinimumVersion,

        [Parameter(Mandatory = $false, HelpMessage = "Repository Credential if needed")]
        [PSCredential] $Credential = $null,

        [Parameter(Mandatory = $false, HelpMessage = "When is set import-module will be skipped.")]
        [switch] $SkipImport,

        [Parameter(Mandatory = $False, HelpMessage = "When is set the Prefix is used while the Import-Module function as switch parameter")]
        [String] $Prefix,

        [Parameter(Mandatory = $false, HelpMessage = "When is set register via Register-PSRepository, otherwise use Register-PackageSource.")]
        [switch] $RegisterViaPSRepository,

        [Parameter(Mandatory = $false, HelpMessage = "When is set Installation of Credential provider will be skipped.")]
        [switch] $SkipInstallCredProvider,

        [Parameter(Mandatory = $false, HelpMessage = "When is set skip critical path check.")]
        [switch] $SkipCriticalPathCheck,

        [Parameter(Mandatory = $false, HelpMessage = "When is set use warning message for critical path.")]
        [switch] $UseWarningMsgForCriticalPath,

        [Parameter(Mandatory = $false, HelpMessage = "Use it when is required to use proxy while accessing the repository")]
        [Uri] $Proxy = $null,
    
        [Parameter(Mandatory = $false, HelpMessage = "Use it when is required to use proxy with credential")]
        [PSCredential] $ProxyCredential = $null,

        [Parameter(Mandatory = $False, HelpMessage = "Sleep time in sec between test if the module is ready to import.")]
        [int] $SleepInSec = 10,

        [Parameter(Mandatory = $False, HelpMessage = "Max retry")]
        [int] $MaxRetry = 20
    )
    $ErrorActionPreference = 'Stop'
    Write-Verbose -Message ("[ START: {0}:{1} (v.{2}) ]" -f $Local:MyInvocation.MyCommand.Source, $Local:MyInvocation.MyCommand.Name, $Local:MyInvocation.MyCommand.Version)
    foreach ($arg in $PSBoundParameters.GetEnumerator()) {
        if ([string]::IsNullOrEmpty($arg.Value)) {
            Write-Debug -Message ("[null] {0}: {1}" -f $arg.Key, $arg.Value) -ErrorAction SilentlyContinue 
        }
        else {
            Write-Debug -Message ("[{2}] {0}: {1}" -f $arg.Key, $arg.Value, $arg.Value.GetType().Name) -ErrorAction SilentlyContinue 
        }
    }

    # Required module is already imported
    if (-not($SkipImport.IsPresent) -and (Test-GetModule -Name $Name -RequiredVersion $RequiredVersion)) {
        Write-Debug "Module $Name is already imported."
        return
    }

    # Check if the $SkipCriticalPathCheck is set
    if (-not($SkipCriticalPathCheck.IsPresent)) {
        [string] $mutexName = 'Global\OriAzBopCriticalPathMutex'
        [System.Threading.Mutex] $mutex = [System.Threading.Mutex]::new($false, $mutexName)
        [int] $threadID = [System.Threading.Thread]::CurrentThread.ManagedThreadId

        if ($UseWarningMsgForCriticalPath.IsPresent) {
            Write-Warning "[$threadID]: Waiting to enter critical path..."
        }
        else {
            Write-Verbose "[$threadID]: Waiting to enter critical path..."
        }

        try {
            # Try to get the mutex
            $mutex.WaitOne() | Out-Null
            if ($UseWarningMsgForCriticalPath.IsPresent) {
                Write-Warning "[$threadID]: Entered critical path."
            }
            else {
                Write-Verbose "[$threadID]: Entered critical path."
            }

            [PSCustomObject] $DependencyMap = Import-OriAzBopPsModuleWithoutCriticalPath `
                -DevOpsAccount $DevOpsAccount `
                -Name $Name `
                -RegisterPsRepoFeedList $RegisterPsRepoFeedList `
                -RegisterNugetRepoFeedList $RegisterNugetRepoFeedList `
                -Repository $Repository `
                -Guid $Guid `
                -MaximumVersion $MaximumVersion `
                -RequiredVersion $RequiredVersion `
                -MinimumVersion $MinimumVersion `
                -Credential $Credential `
                -SkipImport:$SkipImport `
                -Prefix $Prefix `
                -RegisterViaPSRepository:$RegisterViaPSRepository `
                -SkipInstallCredProvider:$SkipInstallCredProvider `
                -Proxy $Proxy `
                -ProxyCredential $ProxyCredential `
                -SleepInSec $SleepInSec `
                -MaxRetry $MaxRetry `
                -Verbose:$VerbosePreference `
                -Debug:$DebugPreference

            if ($UseWarningMsgForCriticalPath.IsPresent) {
                Write-Warning "[$threadID]: Leaving critical path."
            }
            else {
                Write-Verbose "[$threadID]: Leaving critical path."
            }
        }
        finally {
            # Release of the Mutex
            $mutex.ReleaseMutex()
            $mutex.Dispose()
            if ($UseWarningMsgForCriticalPath.IsPresent) {
                Write-Warning "[$threadID]: Exited critical path."
            }
            else {
                Write-Verbose "[$threadID]: Exited critical path."
            }
        }

    }
    else {
        Write-Verbose "Skip of critical path check."
        [PSCustomObject] $DependencyMap = Import-OriAzBopPsModuleWithoutCriticalPath `
            -DevOpsAccount $DevOpsAccount `
            -Name $Name `
            -RegisterPsRepoFeedList $RegisterPsRepoFeedList `
            -RegisterNugetRepoFeedList $RegisterNugetRepoFeedList `
            -Repository $Repository `
            -Guid $Guid `
            -MaximumVersion $MaximumVersion `
            -RequiredVersion $RequiredVersion `
            -MinimumVersion $MinimumVersion `
            -Credential $Credential `
            -SkipImport:$SkipImport `
            -Prefix $Prefix `
            -RegisterViaPSRepository:$RegisterViaPSRepository `
            -SkipInstallCredProvider:$SkipInstallCredProvider `
            -Proxy $Proxy `
            -ProxyCredential $ProxyCredential `
            -SleepInSec $SleepInSec `
            -MaxRetry $MaxRetry `
            -Verbose:$VerbosePreference `
            -Debug:$DebugPreference
    }


    Write-Verbose -Message ("[ END: {0} ]" -f $Local:MyInvocation.MyCommand.Name)
    return $DependencyMap
}