2a08d5246e73cb7b85f88cf03eab019eff98af94
[platform/upstream/coreclr.git] / eng / common / native / CommonLibrary.psm1
1 <#
2 .SYNOPSIS
3 Helper module to install an archive to a directory
4
5 .DESCRIPTION
6 Helper module to download and extract an archive to a specified directory
7
8 .PARAMETER Uri
9 Uri of artifact to download
10
11 .PARAMETER InstallDirectory
12 Directory to extract artifact contents to
13
14 .PARAMETER Force
15 Force download / extraction if file or contents already exist. Default = False
16
17 .PARAMETER DownloadRetries
18 Total number of retry attempts. Default = 5
19
20 .PARAMETER RetryWaitTimeInSeconds
21 Wait time between retry attempts in seconds. Default = 30
22
23 .NOTES
24 Returns False if download or extraction fail, True otherwise
25 #>
26 function DownloadAndExtract {
27   [CmdletBinding(PositionalBinding=$false)]
28   Param (
29     [Parameter(Mandatory=$True)]
30     [string] $Uri,
31     [Parameter(Mandatory=$True)]
32     [string] $InstallDirectory,
33     [switch] $Force = $False,
34     [int] $DownloadRetries = 5,
35     [int] $RetryWaitTimeInSeconds = 30
36   )
37   # Define verbose switch if undefined
38   $Verbose = $VerbosePreference -Eq "Continue"
39
40   $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri
41
42   # Download native tool
43   $DownloadStatus = CommonLibrary\Get-File -Uri $Uri `
44                                            -Path $TempToolPath `
45                                            -DownloadRetries $DownloadRetries `
46                                            -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds `
47                                            -Force:$Force `
48                                            -Verbose:$Verbose
49
50   if ($DownloadStatus -Eq $False) {
51     Write-Error "Download failed"
52     return $False
53   }
54
55   # Extract native tool
56   $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath `
57                                           -OutputDirectory $InstallDirectory `
58                                           -Force:$Force `
59                                           -Verbose:$Verbose
60
61   if ($UnzipStatus -Eq $False) {
62     # Retry Download one more time with Force=true
63     $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri `
64                                              -Path $TempToolPath `
65                                              -DownloadRetries 1 `
66                                              -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds `
67                                              -Force:$True `
68                                              -Verbose:$Verbose
69
70     if ($DownloadRetryStatus -Eq $False) {
71       Write-Error "Last attempt of download failed as well"
72       return $False
73     }
74
75     # Retry unzip again one more time with Force=true
76     $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath `
77                                             -OutputDirectory $InstallDirectory `
78                                             -Force:$True `
79                                             -Verbose:$Verbose
80     if ($UnzipRetryStatus -Eq $False)
81     {
82       Write-Error "Last attempt of unzip failed as well"
83       # Clean up partial zips and extracts
84       if (Test-Path $TempToolPath) {
85         Remove-Item $TempToolPath -Force
86       }
87       if (Test-Path $InstallDirectory) {
88         Remove-Item $InstallDirectory -Force -Recurse
89       }
90       return $False
91     }
92   }
93
94   return $True
95 }
96
97 <#
98 .SYNOPSIS
99 Download a file, retry on failure
100
101 .DESCRIPTION
102 Download specified file and retry if attempt fails
103
104 .PARAMETER Uri
105 Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded
106
107 .PARAMETER Path
108 Path to download or copy uri file to
109
110 .PARAMETER Force
111 Overwrite existing file if present. Default = False
112
113 .PARAMETER DownloadRetries
114 Total number of retry attempts. Default = 5
115
116 .PARAMETER RetryWaitTimeInSeconds
117 Wait time between retry attempts in seconds Default = 30
118
119 #>
120 function Get-File {
121   [CmdletBinding(PositionalBinding=$false)]
122   Param (
123     [Parameter(Mandatory=$True)]
124     [string] $Uri,
125     [Parameter(Mandatory=$True)]
126     [string] $Path,
127     [int] $DownloadRetries = 5,
128     [int] $RetryWaitTimeInSeconds = 30,
129     [switch] $Force = $False
130   )
131   $Attempt = 0
132
133   if ($Force) {
134     if (Test-Path $Path) {
135       Remove-Item $Path -Force
136     }
137   }
138   if (Test-Path $Path) {
139     Write-Host "File '$Path' already exists, skipping download"
140     return $True
141   }
142
143   $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent
144   if (-Not (Test-Path $DownloadDirectory)) {
145     New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null
146   }
147
148   if (Test-Path -IsValid -Path $Uri) {
149     Write-Verbose "'$Uri' is a file path, copying file to '$Path'"
150     Copy-Item -Path $Uri -Destination $Path
151     return $?
152   }
153   else {
154     Write-Verbose "Downloading $Uri"
155     while($Attempt -Lt $DownloadRetries)
156     {
157       try {
158         Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path
159         Write-Verbose "Downloaded to '$Path'"
160         return $True
161       }
162       catch {
163         $Attempt++
164         if ($Attempt -Lt $DownloadRetries) {
165           $AttemptsLeft = $DownloadRetries - $Attempt
166           Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds"
167           Start-Sleep -Seconds $RetryWaitTimeInSeconds
168         }
169         else {
170           Write-Error $_
171           Write-Error $_.Exception
172         }
173       }
174     }
175   }
176
177   return $False
178 }
179
180 <#
181 .SYNOPSIS
182 Generate a shim for a native tool
183
184 .DESCRIPTION
185 Creates a wrapper script (shim) that passes arguments forward to native tool assembly
186
187 .PARAMETER ShimName
188 The name of the shim
189
190 .PARAMETER ShimDirectory
191 The directory where shims are stored
192
193 .PARAMETER ToolFilePath
194 Path to file that shim forwards to
195
196 .PARAMETER Force
197 Replace shim if already present.  Default = False
198
199 .NOTES
200 Returns $True if generating shim succeeds, $False otherwise
201 #>
202 function New-ScriptShim {
203   [CmdletBinding(PositionalBinding=$false)]
204   Param (
205     [Parameter(Mandatory=$True)]
206     [string] $ShimName,
207     [Parameter(Mandatory=$True)]
208     [string] $ShimDirectory,
209     [Parameter(Mandatory=$True)]
210     [string] $ToolFilePath,
211     [Parameter(Mandatory=$True)]
212     [string] $BaseUri,
213     [switch] $Force
214   )
215   try {
216     Write-Verbose "Generating '$ShimName' shim"
217
218     if (-Not (Test-Path $ToolFilePath)){
219       Write-Error "Specified tool file path '$ToolFilePath' does not exist"
220       return $False
221     }
222
223     # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs
224     # Many of the checks for installed programs expect a .exe extension for Windows tools, rather
225     # than a .bat or .cmd file.
226     # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer
227     if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) {
228       $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" `
229                                           -InstallDirectory $ShimDirectory\WinShimmer `
230                                           -Force:$Force `
231                                           -DownloadRetries 2 `
232                                           -RetryWaitTimeInSeconds 5 `
233                                           -Verbose:$Verbose
234     }
235
236     if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) {
237       Write-Host "$ShimName.exe already exists; replacing..."
238       Remove-Item (Join-Path $ShimDirectory "$ShimName.exe")
239     }
240
241     & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory
242     return $True
243   }
244   catch {
245     Write-Host $_
246     Write-Host $_.Exception
247     return $False
248   }
249 }
250
251 <#
252 .SYNOPSIS
253 Returns the machine architecture of the host machine
254
255 .NOTES
256 Returns 'x64' on 64 bit machines
257  Returns 'x86' on 32 bit machines
258 #>
259 function Get-MachineArchitecture {
260   $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE
261   $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432
262   if($ProcessorArchitecture -Eq "X86")
263   {
264     if(($ProcessorArchitectureW6432 -Eq "") -Or
265        ($ProcessorArchitectureW6432 -Eq "X86")) {
266         return "x86"
267     }
268     $ProcessorArchitecture = $ProcessorArchitectureW6432
269   }
270   if (($ProcessorArchitecture -Eq "AMD64") -Or
271       ($ProcessorArchitecture -Eq "IA64") -Or
272       ($ProcessorArchitecture -Eq "ARM64")) {
273     return "x64"
274   }
275   return "x86"
276 }
277
278 <#
279 .SYNOPSIS
280 Get the name of a temporary folder under the native install directory
281 #>
282 function Get-TempDirectory {
283   return Join-Path (Get-NativeInstallDirectory) "temp/"
284 }
285
286 function Get-TempPathFilename {
287   [CmdletBinding(PositionalBinding=$false)]
288   Param (
289     [Parameter(Mandatory=$True)]
290     [string] $Path
291   )
292   $TempDir = CommonLibrary\Get-TempDirectory
293   $TempFilename = Split-Path $Path -leaf
294   $TempPath = Join-Path $TempDir $TempFilename
295   return $TempPath
296 }
297
298 <#
299 .SYNOPSIS
300 Returns the base directory to use for native tool installation
301
302 .NOTES
303 Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable
304 is set, or otherwise returns an install directory under the %USERPROFILE%
305 #>
306 function Get-NativeInstallDirectory {
307   $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY
308   if (!$InstallDir) {
309     $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/"
310   }
311   return $InstallDir
312 }
313
314 <#
315 .SYNOPSIS
316 Unzip an archive
317
318 .DESCRIPTION
319 Powershell module to unzip an archive to a specified directory
320
321 .PARAMETER ZipPath (Required)
322 Path to archive to unzip
323
324 .PARAMETER OutputDirectory (Required)
325 Output directory for archive contents
326
327 .PARAMETER Force
328 Overwrite output directory contents if they already exist
329
330 .NOTES
331 - Returns True and does not perform an extraction if output directory already exists but Overwrite is not True.
332 - Returns True if unzip operation is successful
333 - Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory
334 - Returns False if unable to extract zip archive
335 #>
336 function Expand-Zip {
337   [CmdletBinding(PositionalBinding=$false)]
338   Param (
339     [Parameter(Mandatory=$True)]
340     [string] $ZipPath,
341     [Parameter(Mandatory=$True)]
342     [string] $OutputDirectory,
343     [switch] $Force
344   )
345
346   Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'"
347   try {
348     if ((Test-Path $OutputDirectory) -And (-Not $Force)) {
349       Write-Host "Directory '$OutputDirectory' already exists, skipping extract"
350       return $True
351     }
352     if (Test-Path $OutputDirectory) {
353       Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory"
354       Remove-Item $OutputDirectory -Force -Recurse
355       if ($? -Eq $False) {
356         Write-Error "Unable to remove '$OutputDirectory'"
357         return $False
358       }
359     }
360     if (-Not (Test-Path $OutputDirectory)) {
361       New-Item -path $OutputDirectory -Force -itemType "Directory" | Out-Null
362     }
363
364     Add-Type -assembly "system.io.compression.filesystem"
365     [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$OutputDirectory")
366     if ($? -Eq $False) {
367       Write-Error "Unable to extract '$ZipPath'"
368       return $False
369     }
370   }
371   catch {
372     Write-Host $_
373     Write-Host $_.Exception
374
375     return $False
376   }
377   return $True
378 }
379
380 export-modulemember -function DownloadAndExtract
381 export-modulemember -function Expand-Zip
382 export-modulemember -function Get-File
383 export-modulemember -function Get-MachineArchitecture
384 export-modulemember -function Get-NativeInstallDirectory
385 export-modulemember -function Get-TempDirectory
386 export-modulemember -function Get-TempPathFilename
387 export-modulemember -function New-ScriptShim