CommonLibrary.psm1 12 KB

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