File: PSModule-Helpers.ps1

package info (click to toggle)
python-azure 20250603%2Bgit-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 851,724 kB
  • sloc: python: 7,362,925; ansic: 804; javascript: 287; makefile: 195; sh: 145; xml: 109
file content (185 lines) | stat: -rw-r--r-- 6,711 bytes parent folder | download | duplicates (2)
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
$global:CurrentUserModulePath = ""

function Update-PSModulePathForCI() {
  # Information on PSModulePath taken from docs
  # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_psmodulepath

  # Information on Az custom module paths on hosted agents taken from
  # https://github.com/microsoft/azure-pipelines-tasks/blob/c9771bc064cd60f47587c68e5c871b7cd13f0f28/Tasks/AzurePowerShellV5/Utility.ps1

  if ($IsWindows) {
    $hostedAgentModulePath = $env:SystemDrive + "\Modules"
    $moduleSeperator = ";"
  }
  else {
    $hostedAgentModulePath = "/usr/share"
    $moduleSeperator = ":"
  }
  $modulePaths = $env:PSModulePath -split $moduleSeperator

  # Remove any hosted agent paths (needed to remove old default azure/azurerm paths which cause conflicts)
  $modulePaths = $modulePaths.Where({ !$_.StartsWith($hostedAgentModulePath) })

  # Add any "az_" paths from the agent which is the lastest set of azure modules
  $AzModuleCachePath = (Get-ChildItem "$hostedAgentModulePath/az_*" -Attributes Directory) -join $moduleSeperator
  if ($AzModuleCachePath -and $env:PSModulePath -notcontains $AzModuleCachePath) {
    $modulePaths += $AzModuleCachePath
  }

  $env:PSModulePath = $modulePaths -join $moduleSeperator

  # Find the path that is under user home directory
  $homeDirectories = $modulePaths.Where({ $_.StartsWith($home) })
  if ($homeDirectories.Count -gt 0) {
    $global:CurrentUserModulePath = $homeDirectories[0]
    if ($homeDirectories.Count -gt 1) {
      Write-Verbose "Found more then one module path starting with $home so selecting the first one $global:CurrentUserModulePath"
    }

    # In some cases the directory might not exist so we need to create it otherwise caching an empty directory will fail
    if (!(Test-Path $global:CurrentUserModulePath)) {
      New-Item $global:CurrentUserModulePath -ItemType Directory > $null
    }
  }
  else {
    Write-Error "Did not find a module path starting with $home to set up a user module path in $env:PSModulePath"
  }
}

function Get-ModuleRepositories([string]$moduleName) {
  $DefaultPSRepositoryUrl = "https://www.powershellgallery.com/api/v2"
  # List of modules+versions we want to replace with internal feed sources for reliability, security, etc.
  $packageFeedOverrides = @{
    'powershell-yaml' = 'https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-tools/nuget/v2'
  }

  $repoUrls = if ($packageFeedOverrides.Contains("${moduleName}")) {
    @($packageFeedOverrides["${moduleName}"], $DefaultPSRepositoryUrl)
  }
  else {
    @($DefaultPSRepositoryUrl)
  }

  return $repoUrls
}

function moduleIsInstalled([string]$moduleName, [string]$version) {
  if (-not (Test-Path variable:script:InstalledModules)) {
    $script:InstalledModules = @{}
  }

  if ($script:InstalledModules.ContainsKey("${moduleName}")) {
    $modules = $script:InstalledModules["${moduleName}"]
  }
  else {
    $modules = (Get-Module -ListAvailable $moduleName)
    $script:InstalledModules["${moduleName}"] = $modules
  }

  if ($version -as [Version]) {
    $modules = $modules.Where({ [Version]$_.Version -ge [Version]$version })
    if ($modules.Count -gt 0) {
      Write-Verbose "Using module $($modules[0].Name) with version $($modules[0].Version)."
      return $modules[0]
    }
  }
  return $null
}

function installModule([string]$moduleName, [string]$version, $repoUrl) {
  $repo = (Get-PSRepository).Where({ $_.SourceLocation -eq $repoUrl })
  if ($repo.Count -eq 0) {
    Register-PSRepository -Name $repoUrl -SourceLocation $repoUrl -InstallationPolicy Trusted | Out-Null
    $repo = (Get-PSRepository).Where({ $_.SourceLocation -eq $repoUrl })
    if ($repo.Count -eq 0) {
      throw "Failed to register package repository $repoUrl."
    }
  }

  if ($repo.InstallationPolicy -ne "Trusted") {
    Set-PSRepository -Name $repo.Name -InstallationPolicy "Trusted" | Out-Null
  }

  Write-Verbose "Installing module $moduleName with min version $version from $repoUrl"
  # Install under CurrentUser scope so that the end up under $CurrentUserModulePath for caching
  Install-Module $moduleName -MinimumVersion $version -Repository $repo.Name -Scope CurrentUser -Force -WhatIf:$false
  # Ensure module installed
  $modules = (Get-Module -ListAvailable $moduleName)
  if ($version -as [Version]) {
    $modules = $modules.Where({ [Version]$_.Version -ge [Version]$version })
  }
  if ($modules.Count -eq 0) {
    throw "Failed to install module $moduleName with version $version"
  }

  $script:InstalledModules["${moduleName}"] = $modules

  # Unregister repository as it can cause overlap issues with `dotnet tool install`
  # commands using the same devops feed
  Unregister-PSRepository -Name $repoUrl | Out-Null

  return $modules[0]
}

function InstallAndImport-ModuleIfNotInstalled([string]$module, [string]$version) {
  if ($null -eq (moduleIsInstalled $module $version)) {
    Install-ModuleIfNotInstalled -WhatIf:$false $module $version | Import-Module
  } elseif (!(Get-Module -Name $module)) {
    Import-Module $module
  }
}

# Manual test at eng/common-tests/psmodule-helpers/Install-Module-Parallel.ps1
# If we want to use another default repository other then PSGallery we can update the default parameters
function Install-ModuleIfNotInstalled() {
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [string]$moduleName,
    [string]$version,
    [string]$repositoryUrl
  )

  # Check installed modules before after acquiring lock to avoid a big queue
  $module = moduleIsInstalled -moduleName $moduleName -version $version
  if ($module) { return $module }

  try {
    $mutex = New-Object System.Threading.Mutex($false, "Install-ModuleIfNotInstalled")
    $null = $mutex.WaitOne()

    # Check installed modules again after acquiring lock, in case it has been installed
    $module = moduleIsInstalled -moduleName $moduleName -version $version
    if ($module) { return $module }

    $repoUrls = Get-ModuleRepositories $moduleName

    foreach ($url in $repoUrls) {
      try {
        $module = installModule -moduleName $moduleName -version $version -repoUrl $url
      }
      catch {
        if ($url -ne $repoUrls[-1]) {
          Write-Warning "Failed to install powershell module from '$url'. Retrying with fallback repository"
          Write-Warning $_
          continue
        }
        else {
          Write-Warning "Failed to install powershell module from $url"
          throw
        }
      }
      break
    }

    Write-Verbose "Using module '$($module.Name)' with version '$($module.Version)'."
  }
  finally {
    $mutex.ReleaseMutex()
  }

  return $module
}

if ($null -ne $env:SYSTEM_TEAMPROJECTID) {
  Update-PSModulePathForCI
}