Private/Invoke-ModuleInstall.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<#
.SYNOPSIS
    Install module with dependent modules in required version
.PARAMETER Name
    Exact name of the module
.PARAMETER Guid
    GUID of the mmodule
.PARAMETER MaximumVersion
    Maximum module version
.PARAMETER RequiredVersion
    Required module version
.PARAMETER Version
    Most likely minimum module version
.PARAMETER Credential
    Credential to repository
.PARAMETER Repository
    Name of the repository
.PARAMETER SkipImport
    When is set import-module will be skipped.
 
#>

function Invoke-ModuleInstall {
    [CmdLetBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Exact name of the module")]
        [string] $Name,
    
        [Parameter(Mandatory = $false, HelpMessage = "GUID of the mmodule")]
        [string] $Guid,
    
        [Parameter(Mandatory = $false, HelpMessage = "Maximum module version")]
        [Version] $MaximumVersion,
    
        [Parameter(Mandatory = $false, HelpMessage = "Required module version")]
        [Version] $RequiredVersion,
    
        [Parameter(Mandatory = $false, HelpMessage = "Most likely minimum module version")]
        [Version] $Version,

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

        [Parameter(Mandatory = $false, HelpMessage = "Repository name")]
        [string] $Repository = $null,

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

    )
    $ErrorActionPreference = 'Stop'
    Write-Verbose '-- begin - Invoke-ModuleInstall --'
    Write-Verbose "Name: $Name"
    Write-Verbose "Guid: $Guid"
    Write-Verbose "MaximumVersion: $MaximumVersion"
    Write-Verbose "RequiredVersion: $RequiredVersion"
    Write-Verbose "Version: $Version"
    Write-Verbose "Repository: $Repository"

    if ($SkipImport.IsPresent) {
        Write-Verbose "SkipImport: True"
    }
    else {
        Write-Verbose "SkipImport: False"
    }

    [Hashtable] $InstallModuleParam = @{ Name = $Name }
    if (![string]::IsNullOrEmpty($Guid)) {
        $InstallModuleParam += @{Guid = $Guid }
    }
    if (![string]::IsNullOrEmpty($MaximumVersion)) {
        $InstallModuleParam += @{MaximumVersion = $MaximumVersion }
    }
    if (![string]::IsNullOrEmpty($RequiredVersion)) {
        # Only when is set RequiredVersion we do need add Prefix into Import-Module paramters
        $InstallModuleParam += @{RequiredVersion = $RequiredVersion }
    }
    if (![string]::IsNullOrEmpty($Version)) {
        $InstallModuleParam += @{Version = $Version }
    }
    if ($null -ne $Credential) {
        $InstallModuleParam += @{Credential = $Credential }
    }
    if ($null -ne $Repository) {
        $InstallModuleParam += @{Repository = $Repository }
    }

    Write-Verbose ($InstallModuleParam | Out-String)
    $FoundModule = Find-ModuleForInstall `
        @InstallModuleParam `
        -Verbose:$VerbosePreference `
        -Debug:$DebugPreference

    if (![String]::IsNullOrEmpty($FoundModule) -and ![String]::IsNullOrEmpty($FoundModule.Repository)) {
        Write-Verbose "Found: [$($FoundModule.Repository)] is not null Version [$($FoundModule.Version)]"
    }
    else {
        Write-Verbose "Module [$Name] not found or Repository is not set."
    }

    # Create hastable for check of installed modules
    $GetInstalledParam = $InstallModuleParam.Clone()
    if ($GetInstalledParam.ContainsKey('Credential')) {
        $GetInstalledParam.Remove('Credential')           
    }
    if ($GetInstalledParam.ContainsKey('Repository')) {
        $GetInstalledParam.Remove('Repository')           
    }

    $ModuleInstalled = Get-InstalledModule @GetInstalledParam -ea SilentlyContinue
    [boolean] $ShouldInstall = $false
    if (-not([string]::IsNullOrEmpty($ModuleInstalled)) `
            -and -not([string]::IsNullOrEmpty($ModuleInstalled.Version))) {
        Write-Verbose "The module $Name is alredy installed in version $($ModuleInstalled.Version)"

        [string] $psd1Path = Join-Path -Path $ModuleInstalled.InstalledLocation -ChildPath ('{0}.psd1' -f $ModuleInstalled.Name)
        [string] $RequiredMd5 = Get-OriAzBopModuleMd5FromTag -Path $psd1Path -Verbose:$VerbosePreference -Debug:$DebugPreference
        Write-Verbose "Expected MD5 in the manifest is [$RequiredMd5]."

        if (-not([string]::IsNullOrEmpty($RequiredMd5)) `
                -and -not(Test-OriAzBopModuleComplete -Path $psd1Path -RequiredMd5 $RequiredMd5 -Verbose:$VerbosePreference -Debug:$DebugPreference)) {
            Write-Verbose "Real MD5 of the module id different than is expected."
            $ShouldInstall = $true
        }

        # Versions should be compared in proper type
        # Note: Original ($ModuleInstalled.Version -lt $FoundModule.Version) is not working
        if (-not([string]::IsNullOrEmpty($FoundModule.Version)) `
                -and ([Version] $ModuleInstalled.Version -lt [Version] $FoundModule.Version)) {
            Write-Verbose "The module $Name is alredy installed in version $($ModuleInstalled.Version), but found acceptable version for upgrade $($FoundModule.Version) "
            $ShouldInstall = $true
        }
        
    }
    else {
        Write-Verbose "The module $Name is NOT installed"
        $ShouldInstall = $true
    }

    if ($ShouldInstall) {

        Write-Verbose "Install-Module param:"
        Write-Verbose (ConvertTo-Json  $InstallModuleParam)

        # Sometimes, specially on servers, the installation process is failing.
        # There has been spotted problems when more processes under the same user is trying to install same module in the same time,
        # but it can ge more general issue.
        # Therefore seems to be usefull to use the retry exeception funtion and collect such errors when simple retry can fix the problem.
        Invoke-OriAzExrExceptionRetry `
            -ListedExceptions @('Access to the path*is denied.', 'Administrator rights are required to install or update.') `
            -ScriptBlockToRun {
            Install-Module @InstallModuleParam `
                -Force `
                -Confirm:$false `
                -Verbose:$VerbosePreference `
                -Debug:$DebugPreference
        } `
            -MaxRetry 30 `
            -SleepTimeInSec 10

        # re-Load after the installation
        $ModuleInstalled = Get-InstalledModule @GetInstalledParam -ea SilentlyContinue 
    }
    else {
        Write-Verbose "Module aready exists"
        Write-Verbose ("Module is already installed in Version: {0} on path: {1}" -f $ModuleInstalled.Version, $ModuleInstalled.InstalledLocation)
    }

    [PSCustomObject] $DependencyMap = Invoke-ResolveDependency `
        -InstalledLocation $ModuleInstalled.InstalledLocation `
        -Credential $Credential `
        -Repository $Repository `
        -SkipImport:$SkipImport `
        -Verbose:$VerbosePreference `
        -Debug:$DebugPreference

    [PSCustomObject] $toReturn = @{
        InstalledLocation = Join-Path -Path $ModuleInstalled.InstalledLocation -ChildPath ('{0}.psd1' -f $ModuleInstalled.Name)
        ModuleFilter      = $GetInstalledParam
        DependencyMap     = $DependencyMap
    }

    Write-Verbose '-- end - Invoke-ModuleInstall --'
    return $toReturn 
}