CommonLibrary.psm1 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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"
  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. if (Test-Path -IsValid -Path $Uri) {
  123. Write-Verbose "'$Uri' is a file path, copying file to '$Path'"
  124. Copy-Item -Path $Uri -Destination $Path
  125. return $?
  126. }
  127. else {
  128. Write-Verbose "Downloading $Uri"
  129. while($Attempt -Lt $DownloadRetries)
  130. {
  131. try {
  132. Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path
  133. Write-Verbose "Downloaded to '$Path'"
  134. return $True
  135. }
  136. catch {
  137. $Attempt++
  138. if ($Attempt -Lt $DownloadRetries) {
  139. $AttemptsLeft = $DownloadRetries - $Attempt
  140. Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds"
  141. Start-Sleep -Seconds $RetryWaitTimeInSeconds
  142. }
  143. else {
  144. Write-Error $_
  145. Write-Error $_.Exception
  146. }
  147. }
  148. }
  149. }
  150. return $False
  151. }
  152. <#
  153. .SYNOPSIS
  154. Generate a shim for a native tool
  155. .DESCRIPTION
  156. Creates a wrapper script (shim) that passes arguments forward to native tool assembly
  157. .PARAMETER ShimName
  158. The name of the shim
  159. .PARAMETER ShimDirectory
  160. The directory where shims are stored
  161. .PARAMETER ToolFilePath
  162. Path to file that shim forwards to
  163. .PARAMETER Force
  164. Replace shim if already present. Default = False
  165. .NOTES
  166. Returns $True if generating shim succeeds, $False otherwise
  167. #>
  168. function New-ScriptShim {
  169. [CmdletBinding(PositionalBinding=$false)]
  170. Param (
  171. [Parameter(Mandatory=$True)]
  172. [string] $ShimName,
  173. [Parameter(Mandatory=$True)]
  174. [string] $ShimDirectory,
  175. [Parameter(Mandatory=$True)]
  176. [string] $ToolFilePath,
  177. [Parameter(Mandatory=$True)]
  178. [string] $BaseUri,
  179. [switch] $Force
  180. )
  181. try {
  182. Write-Verbose "Generating '$ShimName' shim"
  183. if (-Not (Test-Path $ToolFilePath)){
  184. Write-Error "Specified tool file path '$ToolFilePath' does not exist"
  185. return $False
  186. }
  187. # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs
  188. # Many of the checks for installed programs expect a .exe extension for Windows tools, rather
  189. # than a .bat or .cmd file.
  190. # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer
  191. if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) {
  192. $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" `
  193. -InstallDirectory $ShimDirectory\WinShimmer `
  194. -Force:$Force `
  195. -DownloadRetries 2 `
  196. -RetryWaitTimeInSeconds 5 `
  197. -Verbose:$Verbose
  198. }
  199. if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) {
  200. Write-Host "$ShimName.exe already exists; replacing..."
  201. Remove-Item (Join-Path $ShimDirectory "$ShimName.exe")
  202. }
  203. & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory
  204. return $True
  205. }
  206. catch {
  207. Write-Host $_
  208. Write-Host $_.Exception
  209. return $False
  210. }
  211. }
  212. <#
  213. .SYNOPSIS
  214. Returns the machine architecture of the host machine
  215. .NOTES
  216. Returns 'x64' on 64 bit machines
  217. Returns 'x86' on 32 bit machines
  218. #>
  219. function Get-MachineArchitecture {
  220. $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE
  221. $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432
  222. if($ProcessorArchitecture -Eq "X86")
  223. {
  224. if(($ProcessorArchitectureW6432 -Eq "") -Or
  225. ($ProcessorArchitectureW6432 -Eq "X86")) {
  226. return "x86"
  227. }
  228. $ProcessorArchitecture = $ProcessorArchitectureW6432
  229. }
  230. if (($ProcessorArchitecture -Eq "AMD64") -Or
  231. ($ProcessorArchitecture -Eq "IA64") -Or
  232. ($ProcessorArchitecture -Eq "ARM64")) {
  233. return "x64"
  234. }
  235. return "x86"
  236. }
  237. <#
  238. .SYNOPSIS
  239. Get the name of a temporary folder under the native install directory
  240. #>
  241. function Get-TempDirectory {
  242. return Join-Path (Get-NativeInstallDirectory) "temp/"
  243. }
  244. function Get-TempPathFilename {
  245. [CmdletBinding(PositionalBinding=$false)]
  246. Param (
  247. [Parameter(Mandatory=$True)]
  248. [string] $Path
  249. )
  250. $TempDir = CommonLibrary\Get-TempDirectory
  251. $TempFilename = Split-Path $Path -leaf
  252. $TempPath = Join-Path $TempDir $TempFilename
  253. return $TempPath
  254. }
  255. <#
  256. .SYNOPSIS
  257. Returns the base directory to use for native tool installation
  258. .NOTES
  259. Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable
  260. is set, or otherwise returns an install directory under the %USERPROFILE%
  261. #>
  262. function Get-NativeInstallDirectory {
  263. $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY
  264. if (!$InstallDir) {
  265. $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/"
  266. }
  267. return $InstallDir
  268. }
  269. <#
  270. .SYNOPSIS
  271. Unzip an archive
  272. .DESCRIPTION
  273. Powershell module to unzip an archive to a specified directory
  274. .PARAMETER ZipPath (Required)
  275. Path to archive to unzip
  276. .PARAMETER OutputDirectory (Required)
  277. Output directory for archive contents
  278. .PARAMETER Force
  279. Overwrite output directory contents if they already exist
  280. .NOTES
  281. - Returns True and does not perform an extraction if output directory already exists but Overwrite is not True.
  282. - Returns True if unzip operation is successful
  283. - Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory
  284. - Returns False if unable to extract zip archive
  285. #>
  286. function Expand-Zip {
  287. [CmdletBinding(PositionalBinding=$false)]
  288. Param (
  289. [Parameter(Mandatory=$True)]
  290. [string] $ZipPath,
  291. [Parameter(Mandatory=$True)]
  292. [string] $OutputDirectory,
  293. [switch] $Force
  294. )
  295. Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'"
  296. try {
  297. if ((Test-Path $OutputDirectory) -And (-Not $Force)) {
  298. Write-Host "Directory '$OutputDirectory' already exists, skipping extract"
  299. return $True
  300. }
  301. if (Test-Path $OutputDirectory) {
  302. Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory"
  303. Remove-Item $OutputDirectory -Force -Recurse
  304. if ($? -Eq $False) {
  305. Write-Error "Unable to remove '$OutputDirectory'"
  306. return $False
  307. }
  308. }
  309. if (-Not (Test-Path $OutputDirectory)) {
  310. New-Item -path $OutputDirectory -Force -itemType "Directory" | Out-Null
  311. }
  312. Add-Type -assembly "system.io.compression.filesystem"
  313. [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$OutputDirectory")
  314. if ($? -Eq $False) {
  315. Write-Error "Unable to extract '$ZipPath'"
  316. return $False
  317. }
  318. }
  319. catch {
  320. Write-Host $_
  321. Write-Host $_.Exception
  322. return $False
  323. }
  324. return $True
  325. }
  326. export-modulemember -function DownloadAndExtract
  327. export-modulemember -function Expand-Zip
  328. export-modulemember -function Get-File
  329. export-modulemember -function Get-MachineArchitecture
  330. export-modulemember -function Get-NativeInstallDirectory
  331. export-modulemember -function Get-TempDirectory
  332. export-modulemember -function Get-TempPathFilename
  333. export-modulemember -function New-ScriptShim