CommonLibrary.psm1 11 KB

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