mirror of
https://github.com/actions/runner-images-sangeeth.git
synced 2025-12-24 10:28:10 +08:00
[Windows] Implement new directories hierarchy (#8616)
This commit is contained in:
committed by
GitHub
parent
84a7deae24
commit
d1f2c9a3be
65
images/windows/scripts/helpers/ChocoHelpers.ps1
Normal file
65
images/windows/scripts/helpers/ChocoHelpers.ps1
Normal file
@@ -0,0 +1,65 @@
|
||||
function Choco-Install {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $PackageName,
|
||||
[string[]] $ArgumentList,
|
||||
[int] $RetryCount = 5
|
||||
)
|
||||
|
||||
process {
|
||||
$count = 1
|
||||
while($true)
|
||||
{
|
||||
Write-Host "Running [#$count]: choco install $packageName -y $argumentList"
|
||||
choco install $packageName -y @argumentList --no-progress
|
||||
|
||||
$pkg = choco list --localonly $packageName --exact --all --limitoutput
|
||||
if ($pkg) {
|
||||
Write-Host "Package installed: $pkg"
|
||||
break
|
||||
}
|
||||
else {
|
||||
$count++
|
||||
if ($count -ge $retryCount) {
|
||||
Write-Host "Could not install $packageName after $count attempts"
|
||||
exit 1
|
||||
}
|
||||
Start-Sleep -Seconds 30
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Send-RequestToChocolateyPackages {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $FilterQuery,
|
||||
[string] $Url = "https://community.chocolatey.org/api",
|
||||
[int] $ApiVersion = 2
|
||||
)
|
||||
|
||||
$response = Invoke-RestMethod "$Url/v$ApiVersion/Packages()?$filterQuery"
|
||||
|
||||
return $response
|
||||
}
|
||||
|
||||
function Get-LatestChocoPackageVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $PackageName,
|
||||
[Parameter(Mandatory)]
|
||||
[string] $TargetVersion
|
||||
)
|
||||
|
||||
$versionNumbers = $TargetVersion.Split(".")
|
||||
[int]$versionNumbers[-1] += 1
|
||||
$incrementedVersion = $versionNumbers -join "."
|
||||
$filterQuery = "`$filter=(Id eq '$PackageName') and (IsPrerelease eq false) and (Version ge '$TargetVersion') and (Version lt '$incrementedVersion')"
|
||||
$latestVersion = (Send-RequestToChocolateyPackages -FilterQuery $filterQuery).properties.Version |
|
||||
Where-Object {$_ -Like "$TargetVersion.*" -or $_ -eq $TargetVersion} |
|
||||
Sort-Object {[version]$_} |
|
||||
Select-Object -Last 1
|
||||
|
||||
return $latestVersion
|
||||
}
|
||||
107
images/windows/scripts/helpers/ImageHelpers.psd1
Normal file
107
images/windows/scripts/helpers/ImageHelpers.psd1
Normal file
@@ -0,0 +1,107 @@
|
||||
@{
|
||||
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'ImageHelpers.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '0.0.1'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = 'c9334909-16a1-48f1-a94a-c7baf1b961d9'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'Helper functions for creating vsts images'
|
||||
|
||||
# Minimum version of the Windows PowerShell engine required by this module
|
||||
# PowerShellVersion = ''
|
||||
|
||||
# Name of the Windows PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the Windows PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# DotNetFrameworkVersion = ''
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
# CLRVersion = ''
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = '*'
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = '*'
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = '*'
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
# FileList = @()
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
|
||||
PSData = @{
|
||||
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
# Tags = @()
|
||||
|
||||
# A URL to the license for this module.
|
||||
# LicenseUri = ''
|
||||
|
||||
# A URL to the main website for this project.
|
||||
# ProjectUri = ''
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
# ReleaseNotes = ''
|
||||
|
||||
} # End of PSData hashtable
|
||||
|
||||
} # End of PrivateData hashtable
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
|
||||
}
|
||||
|
||||
|
||||
61
images/windows/scripts/helpers/ImageHelpers.psm1
Normal file
61
images/windows/scripts/helpers/ImageHelpers.psm1
Normal file
@@ -0,0 +1,61 @@
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
. $PSScriptRoot\PathHelpers.ps1
|
||||
. $PSScriptRoot\InstallHelpers.ps1
|
||||
. $PSScriptRoot\ChocoHelpers.ps1
|
||||
. $PSScriptRoot\TestsHelpers.ps1
|
||||
. $PSScriptRoot\VisualStudioHelpers.ps1
|
||||
|
||||
Export-ModuleMember -Function @(
|
||||
'Connect-Hive'
|
||||
'Disconnect-Hive'
|
||||
'Test-MachinePath'
|
||||
'Get-MachinePath'
|
||||
'Get-DefaultPath'
|
||||
'Set-MachinePath'
|
||||
'Set-DefaultPath'
|
||||
'Add-MachinePathItem'
|
||||
'Add-DefaultPathItem'
|
||||
'Add-DefaultItem'
|
||||
'Get-SystemVariable'
|
||||
'Get-DefaultVariable'
|
||||
'Set-SystemVariable'
|
||||
'Set-DefaultVariable'
|
||||
'Install-Binary'
|
||||
'Install-VisualStudio'
|
||||
'Get-ToolsetContent'
|
||||
'Get-ToolsetToolFullPath'
|
||||
'Stop-SvcWithErrHandling'
|
||||
'Set-SvcWithErrHandling'
|
||||
'Start-DownloadWithRetry'
|
||||
'Get-VsixExtenstionFromMarketplace'
|
||||
'Install-VsixExtension'
|
||||
'Get-VSExtensionVersion'
|
||||
'Get-WinVersion'
|
||||
'Test-IsWin22'
|
||||
'Test-IsWin19'
|
||||
'Choco-Install'
|
||||
'Send-RequestToCocolateyPackages'
|
||||
'Get-LatestChocoPackageVersion'
|
||||
'Get-GitHubPackageDownloadUrl'
|
||||
'Extract-7Zip'
|
||||
'Get-CommandResult'
|
||||
'Get-WhichTool'
|
||||
'Get-EnvironmentVariable'
|
||||
'Invoke-PesterTests'
|
||||
'Invoke-SBWithRetry'
|
||||
'Get-VsCatalogJsonPath'
|
||||
'Install-AndroidSDKPackages'
|
||||
'Get-AndroidPackages'
|
||||
'Get-AndroidPackagesByName'
|
||||
'Get-AndroidPackagesByVersion'
|
||||
'Get-VisualStudioInstance'
|
||||
'Get-VisualStudioComponents'
|
||||
'Get-WindowsUpdatesHistory'
|
||||
'New-ItemPath'
|
||||
'Get-ModuleVersionAsJob'
|
||||
'Use-ChecksumComparison'
|
||||
'Get-HashFromGitHubReleaseBody'
|
||||
'Test-FileSignature'
|
||||
)
|
||||
736
images/windows/scripts/helpers/InstallHelpers.ps1
Normal file
736
images/windows/scripts/helpers/InstallHelpers.ps1
Normal file
@@ -0,0 +1,736 @@
|
||||
function Install-Binary
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A helper function to install executables.
|
||||
|
||||
.DESCRIPTION
|
||||
Download and install .exe or .msi binaries from specified URL.
|
||||
|
||||
.PARAMETER Url
|
||||
The URL from which the binary will be downloaded. Required parameter.
|
||||
|
||||
.PARAMETER Name
|
||||
The Name with which binary will be downloaded. Required parameter.
|
||||
|
||||
.PARAMETER ArgumentList
|
||||
The list of arguments that will be passed to the installer. Required for .exe binaries.
|
||||
|
||||
.EXAMPLE
|
||||
Install-Binary -Url "https://go.microsoft.com/fwlink/p/?linkid=2083338" -Name "winsdksetup.exe" -ArgumentList ("/features", "+", "/quiet") -ExpectedSignature "XXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
#>
|
||||
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory, ParameterSetName="Url")]
|
||||
[String] $Url,
|
||||
[Parameter(Mandatory, ParameterSetName="Url")]
|
||||
[String] $Name,
|
||||
[Parameter(Mandatory, ParameterSetName="LocalPath")]
|
||||
[String] $FilePath,
|
||||
[String[]] $ArgumentList,
|
||||
[String[]] $ExpectedSignature
|
||||
)
|
||||
|
||||
if ($PSCmdlet.ParameterSetName -eq "LocalPath")
|
||||
{
|
||||
$name = Split-Path -Path $FilePath -Leaf
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Downloading $Name..."
|
||||
$filePath = Start-DownloadWithRetry -Url $Url -Name $Name
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('ExpectedSignature'))
|
||||
{
|
||||
if ($ExpectedSignature)
|
||||
{
|
||||
Test-FileSignature -FilePath $filePath -ExpectedThumbprint $ExpectedSignature
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "ExpectedSignature parameter is specified, but no signature is provided."
|
||||
}
|
||||
}
|
||||
|
||||
# MSI binaries should be installed via msiexec.exe
|
||||
$fileExtension = ([System.IO.Path]::GetExtension($Name)).Replace(".", "")
|
||||
if ($fileExtension -eq "msi")
|
||||
{
|
||||
if (-not $ArgumentList)
|
||||
{
|
||||
$ArgumentList = ('/i', $filePath, '/QN', '/norestart')
|
||||
}
|
||||
$filePath = "msiexec.exe"
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$installStartTime = Get-Date
|
||||
Write-Host "Starting Install $Name..."
|
||||
$process = Start-Process -FilePath $filePath -ArgumentList $ArgumentList -Wait -PassThru
|
||||
$exitCode = $process.ExitCode
|
||||
$installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2)
|
||||
if ($exitCode -eq 0 -or $exitCode -eq 3010)
|
||||
{
|
||||
Write-Host "Installation successful in $installCompleteTime seconds"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Non zero exit code returned by the installation process: $exitCode"
|
||||
Write-Host "Total time elapsed: $installCompleteTime seconds"
|
||||
exit $exitCode
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
$installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2)
|
||||
Write-Host "Failed to install the $fileExtension ${Name}: $($_.Exception.Message)"
|
||||
Write-Host "Installation failed after $installCompleteTime seconds"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-SvcWithErrHandling {
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Function for stopping the Windows Service with error handling
|
||||
|
||||
.PARAMETER ServiceName
|
||||
The name of stopping service
|
||||
|
||||
.PARAMETER StopOnError
|
||||
Switch for stopping the script and exit from PowerShell if one service is absent
|
||||
#>
|
||||
Param (
|
||||
[Parameter(Mandatory, ValueFromPipeLine = $true)]
|
||||
[string] $ServiceName,
|
||||
[switch] $StopOnError
|
||||
)
|
||||
|
||||
Process {
|
||||
$service = Get-Service $ServiceName -ErrorAction SilentlyContinue
|
||||
if (-not $service) {
|
||||
Write-Warning "[!] Service [$ServiceName] is not found"
|
||||
if ($StopOnError) {
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "Try to stop service [$ServiceName]"
|
||||
try {
|
||||
Stop-Service -Name $ServiceName -Force
|
||||
$service.WaitForStatus("Stopped", "00:01:00")
|
||||
Write-Host "Service [$ServiceName] has been stopped successfuly"
|
||||
} catch {
|
||||
Write-Error "[!] Failed to stop service [$ServiceName] with error:"
|
||||
$_ | Out-String | Write-Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Set-SvcWithErrHandling {
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Function for setting the Windows Service parameter with error handling
|
||||
|
||||
.PARAMETER ServiceName
|
||||
The name of stopping service
|
||||
|
||||
.PARAMETER Arguments
|
||||
Hashtable for service arguments
|
||||
|
||||
.PARAMETER StopOnError
|
||||
Switch for stopping the script and exit from PowerShell if one service is absent
|
||||
#>
|
||||
|
||||
Param (
|
||||
[Parameter(Mandatory, ValueFromPipeLine = $true)]
|
||||
[string] $ServiceName,
|
||||
[Parameter(Mandatory)]
|
||||
[hashtable] $Arguments,
|
||||
[switch] $StopOnError
|
||||
)
|
||||
|
||||
Process {
|
||||
$service = Get-Service $ServiceName -ErrorAction SilentlyContinue
|
||||
if (-not $service) {
|
||||
Write-Warning "[!] Service [$ServiceName] is not found"
|
||||
if ($StopOnError) {
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Set-Service $serviceName @Arguments
|
||||
} catch {
|
||||
Write-Error "[!] Failed to set service [$ServiceName] arguments with error:"
|
||||
$_ | Out-String | Write-Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Start-DownloadWithRetry
|
||||
{
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $Url,
|
||||
[string] $Name,
|
||||
[string] $DownloadPath = "${env:Temp}",
|
||||
[int] $Retries = 20
|
||||
)
|
||||
|
||||
if ([String]::IsNullOrEmpty($Name)) {
|
||||
$Name = [IO.Path]::GetFileName($Url)
|
||||
}
|
||||
|
||||
$filePath = Join-Path -Path $DownloadPath -ChildPath $Name
|
||||
$downloadStartTime = Get-Date
|
||||
|
||||
# Default retry logic for the package.
|
||||
while ($Retries -gt 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
$downloadAttemptStartTime = Get-Date
|
||||
Write-Host "Downloading package from: $Url to path $filePath ."
|
||||
(New-Object System.Net.WebClient).DownloadFile($Url, $filePath)
|
||||
break
|
||||
}
|
||||
catch
|
||||
{
|
||||
$failTime = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2)
|
||||
$attemptTime = [math]::Round(($(Get-Date) - $downloadAttemptStartTime).TotalSeconds, 2)
|
||||
Write-Host "There is an error encounterd after $attemptTime seconds during package downloading:`n $_"
|
||||
$Retries--
|
||||
|
||||
if ($Retries -eq 0)
|
||||
{
|
||||
Write-Host "File can't be downloaded. Please try later or check that file exists by url: $Url"
|
||||
Write-Host "Total time elapsed $failTime"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Waiting 30 seconds before retrying. Retries left: $Retries"
|
||||
Start-Sleep -Seconds 30
|
||||
}
|
||||
}
|
||||
|
||||
$downloadCompleteTime = [math]::Round(($(Get-Date) - $downloadStartTime).TotalSeconds, 2)
|
||||
Write-Host "Package downloaded successfully in $downloadCompleteTime seconds"
|
||||
return $filePath
|
||||
}
|
||||
|
||||
function Get-VsixExtenstionFromMarketplace {
|
||||
Param
|
||||
(
|
||||
[string] $ExtensionMarketPlaceName,
|
||||
[string] $MarketplaceUri = "https://marketplace.visualstudio.com/items?itemName="
|
||||
)
|
||||
|
||||
$extensionUri = $MarketplaceUri + $ExtensionMarketPlaceName
|
||||
$request = Invoke-SBWithRetry -Command { Invoke-WebRequest -Uri $extensionUri -UseBasicParsing } -RetryCount 20 -RetryIntervalSeconds 30
|
||||
$request -match 'UniqueIdentifierValue":"(?<extensionname>[^"]*)' | Out-Null
|
||||
$extensionName = $Matches.extensionname
|
||||
$request -match 'VsixId":"(?<vsixid>[^"]*)' | Out-Null
|
||||
$vsixId = $Matches.vsixid
|
||||
$request -match 'AssetUri":"(?<uri>[^"]*)' | Out-Null
|
||||
$assetUri = $Matches.uri
|
||||
$request -match 'Microsoft\.VisualStudio\.Services\.Payload\.FileName":"(?<filename>[^"]*)' | Out-Null
|
||||
$fileName = $Matches.filename
|
||||
$downloadUri = $assetUri + "/" + $fileName
|
||||
# ProBITools.MicrosoftReportProjectsforVisualStudio2022 has different URL https://github.com/actions/runner-images/issues/5340
|
||||
switch ($ExtensionMarketPlaceName) {
|
||||
"ProBITools.MicrosoftReportProjectsforVisualStudio2022" {
|
||||
$fileName = "Microsoft.DataTools.ReportingServices.vsix"
|
||||
$downloadUri = "https://download.microsoft.com/download/b/b/5/bb57be7e-ae72-4fc0-b528-d0ec224997bd/Microsoft.DataTools.ReportingServices.vsix"
|
||||
}
|
||||
"ProBITools.MicrosoftAnalysisServicesModelingProjects2022" {
|
||||
$fileName = "Microsoft.DataTools.AnalysisServices.vsix"
|
||||
$downloadUri = "https://download.microsoft.com/download/c/8/9/c896a7f2-d0fd-45ac-90e6-ff61f67523cb/Microsoft.DataTools.AnalysisServices.vsix"
|
||||
}
|
||||
# Starting from version 4.1 SqlServerIntegrationServicesProjects extension is distributed as exe file
|
||||
"SSIS.SqlServerIntegrationServicesProjects" {
|
||||
$fileName = "Microsoft.DataTools.IntegrationServices.exe"
|
||||
$downloadUri = $assetUri + "/" + $fileName
|
||||
}
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
"ExtensionName" = $extensionName
|
||||
"VsixId" = $vsixId
|
||||
"FileName" = $fileName
|
||||
"DownloadUri" = $downloadUri
|
||||
}
|
||||
}
|
||||
|
||||
function Install-VsixExtension
|
||||
{
|
||||
Param
|
||||
(
|
||||
[string] $Url,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $Name,
|
||||
[string] $FilePath,
|
||||
[int] $Retries = 20,
|
||||
[switch] $InstallOnly
|
||||
)
|
||||
|
||||
if (-not $InstallOnly)
|
||||
{
|
||||
$FilePath = Start-DownloadWithRetry -Url $Url -Name $Name
|
||||
}
|
||||
|
||||
$argumentList = ('/quiet', "`"$FilePath`"")
|
||||
|
||||
do
|
||||
{
|
||||
Write-Host "Starting Install $Name..."
|
||||
try
|
||||
{
|
||||
$installPath = ${env:ProgramFiles(x86)}
|
||||
|
||||
# There are 2 types of packages at the moment - exe and vsix
|
||||
if ($Name -match "vsix")
|
||||
{
|
||||
$process = Start-Process -FilePath "${installPath}\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service\VSIXInstaller.exe" -ArgumentList $argumentList -Wait -PassThru
|
||||
}
|
||||
else
|
||||
{
|
||||
$process = Start-Process -FilePath ${env:Temp}\$Name /Q -Wait -PassThru
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host "There is an error during $Name installation"
|
||||
$_
|
||||
exit 1
|
||||
}
|
||||
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
if ($exitCode -eq 0 -or $exitCode -eq 1001) # 1001 means the extension is already installed
|
||||
{
|
||||
Write-Host "$Name installed successfully"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "Unsuccessful exit code returned by the installation process: $exitCode."
|
||||
$Retries--
|
||||
if ($Retries -eq 0) {
|
||||
Write-Host "The $Name couldn't be installed after 20 attempts."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host "Waiting 10 seconds before retrying. Retries left: $Retries"
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
}
|
||||
} until ($exitCode -eq 0 -or $exitCode -eq 1001 -or $Retries -eq 0 )
|
||||
|
||||
#Cleanup downloaded installation files
|
||||
if (-not $InstallOnly)
|
||||
{
|
||||
Remove-Item -Force -Confirm:$false $FilePath
|
||||
}
|
||||
}
|
||||
|
||||
function Get-VSExtensionVersion
|
||||
{
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $packageName
|
||||
)
|
||||
|
||||
$instanceFolders = Get-ChildItem -Path "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances"
|
||||
if ($instanceFolders -is [array])
|
||||
{
|
||||
Write-Host ($instanceFolders | Out-String)
|
||||
Write-Host ($instanceFolders | Get-ChildItem | Out-String)
|
||||
Write-Host "More than one instance installed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$stateContent = Get-Content -Path (Join-Path $instanceFolders.FullName '\state.packages.json')
|
||||
$state = $stateContent | ConvertFrom-Json
|
||||
$packageVersion = ($state.packages | Where-Object { $_.id -eq $packageName }).version
|
||||
|
||||
if (-not $packageVersion)
|
||||
{
|
||||
Write-Host "Installed package $packageName for Visual Studio was not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
return $packageVersion
|
||||
}
|
||||
|
||||
function Get-ToolsetContent
|
||||
{
|
||||
$toolsetPath = Join-Path "C:\\image" "toolset.json"
|
||||
$toolsetJson = Get-Content -Path $toolsetPath -Raw
|
||||
ConvertFrom-Json -InputObject $toolsetJson
|
||||
}
|
||||
|
||||
function Get-ToolcacheToolDirectory {
|
||||
Param ([string] $ToolName)
|
||||
$toolcacheRootPath = Resolve-Path $env:AGENT_TOOLSDIRECTORY
|
||||
return Join-Path $toolcacheRootPath $ToolName
|
||||
}
|
||||
|
||||
function Get-ToolsetToolFullPath
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Function that return full path to specified toolset tool.
|
||||
|
||||
.PARAMETER Name
|
||||
The name of required tool.
|
||||
|
||||
.PARAMETER Version
|
||||
The version of required tool.
|
||||
|
||||
.PARAMETER Arch
|
||||
The architecture of required tool.
|
||||
#>
|
||||
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $Name,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string] $Version,
|
||||
[string] $Arch = "x64"
|
||||
)
|
||||
|
||||
$toolPath = Get-ToolcacheToolDirectory -ToolName $Name
|
||||
|
||||
# Add wildcard if missing
|
||||
if ($Version.Split(".").Length -lt 3) {
|
||||
$Version += ".*"
|
||||
}
|
||||
|
||||
$versionPath = Join-Path $toolPath $Version
|
||||
|
||||
# Take latest installed version in case if toolset version contains wildcards
|
||||
$foundVersion = Get-Item $versionPath `
|
||||
| Sort-Object -Property {[version]$_.name} -Descending `
|
||||
| Select-Object -First 1
|
||||
|
||||
if (-not $foundVersion) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return Join-Path $foundVersion $Arch
|
||||
}
|
||||
|
||||
function Get-WinVersion
|
||||
{
|
||||
(Get-CimInstance -ClassName Win32_OperatingSystem).Caption
|
||||
}
|
||||
|
||||
function Test-IsWin22
|
||||
{
|
||||
(Get-WinVersion) -match "2022"
|
||||
}
|
||||
|
||||
function Test-IsWin19
|
||||
{
|
||||
(Get-WinVersion) -match "2019"
|
||||
}
|
||||
|
||||
function Extract-7Zip {
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Path,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$DestinationPath,
|
||||
[ValidateSet("x", "e")]
|
||||
[char]$ExtractMethod = "x"
|
||||
)
|
||||
|
||||
Write-Host "Expand archive '$PATH' to '$DestinationPath' directory"
|
||||
7z.exe $ExtractMethod "$Path" -o"$DestinationPath" -y | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0)
|
||||
{
|
||||
Write-Host "There is an error during expanding '$Path' to '$DestinationPath' directory"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Install-AndroidSDKPackages {
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$AndroidSDKManagerPath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$AndroidSDKRootPath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[AllowEmptyCollection()]
|
||||
[string[]]$AndroidPackages,
|
||||
[string] $PrefixPackageName
|
||||
)
|
||||
|
||||
foreach ($package in $AndroidPackages) {
|
||||
& $AndroidSDKManagerPath --sdk_root=$AndroidSDKRootPath "$PrefixPackageName$package"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AndroidPackages {
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$AndroidSDKManagerPath
|
||||
)
|
||||
|
||||
$packagesListFile = "C:\Android\android-sdk\packages-list.txt"
|
||||
|
||||
if (-Not (Test-Path -Path $packagesListFile -PathType Leaf)) {
|
||||
(cmd /c "$AndroidSDKManagerPath --list --verbose 2>&1") |
|
||||
Where-Object { $_ -Match "^[^\s]" } |
|
||||
Where-Object { $_ -NotMatch "^(Loading |Info: Parsing |---|\[=+|Installed |Available )" } |
|
||||
Where-Object { $_ -NotMatch "^[^;]*$" } |
|
||||
Out-File -FilePath $packagesListFile
|
||||
}
|
||||
|
||||
return Get-Content $packagesListFile
|
||||
}
|
||||
|
||||
function Get-AndroidPackagesByName {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string[]]$AndroidPackages,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$PrefixPackageName
|
||||
)
|
||||
|
||||
return $AndroidPackages | Where-Object { "$_".StartsWith($PrefixPackageName) }
|
||||
}
|
||||
|
||||
function Get-AndroidPackagesByVersion {
|
||||
Param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string[]]$AndroidPackages,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$PrefixPackageName,
|
||||
[object]$MinimumVersion,
|
||||
[char]$Delimiter,
|
||||
[int]$Index = 0
|
||||
)
|
||||
|
||||
$Type = $MinimumVersion.GetType()
|
||||
$packagesByName = Get-AndroidPackagesByName -AndroidPackages $AndroidPackages -PrefixPackageName $PrefixPackageName
|
||||
$packagesByVersion = $packagesByName | Where-Object { ($_.Split($Delimiter)[$Index] -as $Type) -ge $MinimumVersion }
|
||||
return $packagesByVersion | Sort-Object -Unique
|
||||
}
|
||||
|
||||
function Get-WindowsUpdatesHistory {
|
||||
$allEvents = @{}
|
||||
# 19 - Installation Successful: Windows successfully installed the following update
|
||||
# 20 - Installation Failure: Windows failed to install the following update with error
|
||||
# 43 - Installation Started: Windows has started installing the following update
|
||||
$filter = @{
|
||||
LogName = "System"
|
||||
Id = 19, 20, 43
|
||||
ProviderName = "Microsoft-Windows-WindowsUpdateClient"
|
||||
}
|
||||
$events = Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue | Sort-Object Id
|
||||
|
||||
foreach ( $event in $events ) {
|
||||
switch ( $event.Id ) {
|
||||
19 {
|
||||
$status = "Successful"
|
||||
$title = $event.Properties[0].Value
|
||||
$allEvents[$title] = ""
|
||||
break
|
||||
}
|
||||
20 {
|
||||
$status = "Failure"
|
||||
$title = $event.Properties[1].Value
|
||||
$allEvents[$title] = ""
|
||||
break
|
||||
}
|
||||
43 {
|
||||
$status = "InProgress"
|
||||
$title = $event.Properties[0].Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ( $status -eq "InProgress" -and $allEvents.ContainsKey($title) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
Status = $status
|
||||
Title = $title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-SBWithRetry {
|
||||
param (
|
||||
[scriptblock] $Command,
|
||||
[int] $RetryCount = 10,
|
||||
[int] $RetryIntervalSeconds = 5
|
||||
)
|
||||
|
||||
while ($RetryCount -gt 0) {
|
||||
try {
|
||||
& $Command
|
||||
return
|
||||
}
|
||||
catch {
|
||||
Write-Host "There is an error encountered:`n $_"
|
||||
$RetryCount--
|
||||
|
||||
if ($RetryCount -eq 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Waiting $RetryIntervalSeconds seconds before retrying. Retries left: $RetryCount"
|
||||
Start-Sleep -Seconds $RetryIntervalSeconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-GitHubPackageDownloadUrl {
|
||||
param (
|
||||
[string]$RepoOwner,
|
||||
[string]$RepoName,
|
||||
[string]$BinaryName,
|
||||
[string]$Version,
|
||||
[string]$UrlFilter,
|
||||
[boolean]$IsPrerelease = $false,
|
||||
[boolean]$LatestReleaseOnly = $true,
|
||||
[int]$SearchInCount = 100
|
||||
)
|
||||
|
||||
if ($Version -eq "latest") {
|
||||
$Version = "*"
|
||||
}
|
||||
|
||||
$json = Invoke-RestMethod -Uri "https://api.github.com/repos/${RepoOwner}/${RepoName}/releases?per_page=${SearchInCount}"
|
||||
$tags = $json.Where{ $_.prerelease -eq $IsPrerelease -and $_.assets }.tag_name
|
||||
$availableVersions = $tags |
|
||||
Select-String -Pattern "\d+.\d+.\d+" |
|
||||
ForEach-Object { $_.Matches.Value } |
|
||||
Where-Object { $_ -like "$Version.*" -or $_ -eq $Version } |
|
||||
Sort-Object -Descending { [version]$_ }
|
||||
|
||||
if (-not $availableVersions) {
|
||||
throw "Failed to get available versions from ${RepoOwner}/${RepoName} releases"
|
||||
}
|
||||
|
||||
if ($LatestReleaseOnly) {
|
||||
$latestVersion = $availableVersions | Select-Object -First 1
|
||||
$urlFilterReplaced = $UrlFilter -replace "{BinaryName}", $BinaryName -replace "{Version}", $latestVersion
|
||||
$downloadUrl = $json.assets.browser_download_url -like $urlFilterReplaced
|
||||
} else {
|
||||
foreach ($version in $availableVersions) {
|
||||
$urlFilterReplaced = $UrlFilter -replace "{BinaryName}", $BinaryName -replace "{Version}", $version
|
||||
$downloadUrl = $json.assets.browser_download_url -like $urlFilterReplaced
|
||||
|
||||
if ($downloadUrl) {
|
||||
Write-Host "Found download url for ${RepoOwner}/${RepoName} ${BinaryName} ${version}"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $downloadUrl) {
|
||||
throw "Failed to get download url for ${RepoOwner}/${RepoName} ${BinaryName}"
|
||||
}
|
||||
|
||||
return $downloadUrl
|
||||
}
|
||||
|
||||
function Use-ChecksumComparison {
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$LocalFileHash,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$DistributorFileHash
|
||||
)
|
||||
|
||||
Write-Verbose "Performing checksum verification"
|
||||
|
||||
if ($LocalFileHash -ne $DistributorFileHash) {
|
||||
throw "Checksum verification failed. Expected hash: $DistributorFileHash; Actual hash: $LocalFileHash."
|
||||
} else {
|
||||
Write-Verbose "Checksum verification passed"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-HashFromGitHubReleaseBody {
|
||||
param (
|
||||
[string]$RepoOwner,
|
||||
[string]$RepoName,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FileName,
|
||||
[string]$Url,
|
||||
[string]$Version = "latest",
|
||||
[boolean]$IsPrerelease = $false,
|
||||
[int]$SearchInCount = 100,
|
||||
[string]$Delimiter = '|',
|
||||
[int]$WordNumber = 1
|
||||
)
|
||||
|
||||
if ($Url) {
|
||||
$releaseUrl = $Url
|
||||
} else {
|
||||
if ($Version -eq "latest") {
|
||||
$releaseUrl = "https://api.github.com/repos/${RepoOwner}/${RepoName}/releases/latest"
|
||||
} else {
|
||||
$json = Invoke-RestMethod -Uri "https://api.github.com/repos/${RepoOwner}/${RepoName}/releases?per_page=${SearchInCount}"
|
||||
$tags = $json.Where{ $_.prerelease -eq $IsPrerelease }.tag_name
|
||||
$tag = $tags -match $Version
|
||||
if (-not $tag) {
|
||||
throw "Failed to get a tag name for version $Version."
|
||||
}
|
||||
$releaseUrl = "https://api.github.com/repos/${RepoOwner}/${RepoName}/releases/tag/$tag"
|
||||
}
|
||||
}
|
||||
$body = (Invoke-RestMethod -Uri $releaseUrl).body -replace('`', "") -join "`n"
|
||||
$matchingLine = $body.Split("`n") | Where-Object { $_ -like "*$FileName*" }
|
||||
if ([string]::IsNullOrEmpty($matchingLine)) {
|
||||
throw "File name '$FileName' not found in release body."
|
||||
}
|
||||
$result = $matchingLine.Split($Delimiter)[$WordNumber] -replace "[^a-zA-Z0-9]", ""
|
||||
if ([string]::IsNullOrEmpty($result)) {
|
||||
throw "Empty result. Check Split method parameters (delimiter and/or word number) for the matching line."
|
||||
}
|
||||
return $result
|
||||
}
|
||||
function Test-FileSignature {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FilePath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string[]]$ExpectedThumbprint
|
||||
)
|
||||
|
||||
$signature = Get-AuthenticodeSignature $FilePath
|
||||
|
||||
if ($signature.Status -ne "Valid") {
|
||||
throw "Signature status is not valid. Status: $($signature.Status)"
|
||||
}
|
||||
|
||||
foreach ($thumbprint in $ExpectedThumbprint) {
|
||||
if ($signature.SignerCertificate.Thumbprint.Contains($thumbprint)) {
|
||||
Write-Output "Signature for $FilePath is valid"
|
||||
$signatureMatched = $true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ($signatureMatched) {
|
||||
Write-Output "Signature for $FilePath is valid"
|
||||
}
|
||||
else {
|
||||
throw "Signature thumbprint do not match expected."
|
||||
}
|
||||
}
|
||||
165
images/windows/scripts/helpers/PathHelpers.ps1
Normal file
165
images/windows/scripts/helpers/PathHelpers.ps1
Normal file
@@ -0,0 +1,165 @@
|
||||
function Connect-Hive {
|
||||
param(
|
||||
[string]$FileName = "C:\Users\Default\NTUSER.DAT",
|
||||
[string]$SubKey = "HKLM\DEFAULT"
|
||||
)
|
||||
|
||||
Write-Host "Loading the file $FileName to the Key $SubKey"
|
||||
if (Test-Path $SubKey.Replace("\",":")) {
|
||||
return
|
||||
}
|
||||
|
||||
$result = reg load $SubKey $FileName *>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Failed to load hive: $result"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Disconnect-Hive {
|
||||
param(
|
||||
[string]$SubKey = "HKLM\DEFAULT"
|
||||
)
|
||||
|
||||
Write-Host "Unloading the hive $SubKey"
|
||||
if (-not (Test-Path $SubKey.Replace("\",":"))) {
|
||||
return
|
||||
}
|
||||
|
||||
$result = reg unload $SubKey *>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Failed to unload hive: $result"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SystemVariable {
|
||||
param(
|
||||
[string]$SystemVariable
|
||||
)
|
||||
|
||||
[System.Environment]::GetEnvironmentVariable($SystemVariable, "Machine")
|
||||
}
|
||||
|
||||
function Get-DefaultVariable {
|
||||
param(
|
||||
[string]$DefaultVariable,
|
||||
[string]$Name = "DEFAULT\Environment",
|
||||
[bool]$Writable = $false
|
||||
)
|
||||
|
||||
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($Name, $Writable)
|
||||
$key.GetValue($DefaultVariable, "", "DoNotExpandEnvironmentNames")
|
||||
$key.Handle.Close()
|
||||
[System.GC]::Collect()
|
||||
}
|
||||
|
||||
function Set-SystemVariable {
|
||||
param(
|
||||
[string]$SystemVariable,
|
||||
[string]$Value
|
||||
)
|
||||
|
||||
[System.Environment]::SetEnvironmentVariable($SystemVariable, $Value, "Machine")
|
||||
Get-SystemVariable $SystemVariable
|
||||
}
|
||||
|
||||
function Set-DefaultVariable {
|
||||
param(
|
||||
[string]$DefaultVariable,
|
||||
[string]$Value,
|
||||
[string]$Name = "DEFAULT\Environment",
|
||||
[string]$Kind = "ExpandString",
|
||||
[bool]$Writable = $true
|
||||
)
|
||||
|
||||
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($Name, $Writable)
|
||||
$key.SetValue($DefaultVariable, $Value, $Kind)
|
||||
Get-DefaultVariable -DefaultVariable $DefaultVariable -Name $Name
|
||||
$key.Handle.Close()
|
||||
[System.GC]::Collect()
|
||||
}
|
||||
|
||||
function Get-MachinePath {
|
||||
Get-SystemVariable PATH
|
||||
}
|
||||
|
||||
function Get-DefaultPath {
|
||||
Get-DefaultVariable Path
|
||||
}
|
||||
|
||||
function Set-MachinePath {
|
||||
param(
|
||||
[string]$NewPath
|
||||
)
|
||||
|
||||
Set-SystemVariable PATH $NewPath
|
||||
}
|
||||
|
||||
function Set-DefaultPath {
|
||||
param(
|
||||
[string]$NewPath
|
||||
)
|
||||
|
||||
Set-DefaultVariable PATH $NewPath
|
||||
}
|
||||
|
||||
function Test-MachinePath {
|
||||
param(
|
||||
[string]$PathItem
|
||||
)
|
||||
|
||||
$pathItems = (Get-MachinePath).Split(';')
|
||||
$pathItems.Contains($PathItem)
|
||||
}
|
||||
|
||||
function Add-MachinePathItem {
|
||||
param(
|
||||
[string]$PathItem
|
||||
)
|
||||
|
||||
$currentPath = Get-MachinePath
|
||||
$newPath = $PathItem + ';' + $currentPath
|
||||
Set-MachinePath -NewPath $newPath
|
||||
}
|
||||
|
||||
function Add-DefaultPathItem {
|
||||
param(
|
||||
[string]$PathItem
|
||||
)
|
||||
|
||||
Connect-Hive
|
||||
$currentPath = Get-DefaultPath
|
||||
$newPath = $PathItem + ';' + $currentPath
|
||||
Set-DefaultPath -NewPath $newPath
|
||||
Disconnect-Hive
|
||||
}
|
||||
|
||||
function Add-DefaultItem {
|
||||
param(
|
||||
[string]$DefaultVariable,
|
||||
[string]$Value,
|
||||
[string]$Name = "DEFAULT\Environment",
|
||||
[string]$Kind = "ExpandString",
|
||||
[bool]$Writable = $true
|
||||
)
|
||||
|
||||
Connect-Hive
|
||||
$regPath = Join-Path "HKLM:\" $Name
|
||||
if (-not (Test-Path $Name)) {
|
||||
Write-Host "Creating $regPath key"
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
Set-DefaultVariable -DefaultVariable $DefaultVariable -Value $Value -Name $Name -Kind $Kind -Writable $Writable
|
||||
Disconnect-Hive
|
||||
}
|
||||
|
||||
function New-ItemPath {
|
||||
param (
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -Path $Path -Force -ErrorAction Ignore | Out-Null
|
||||
}
|
||||
}
|
||||
200
images/windows/scripts/helpers/TestsHelpers.ps1
Normal file
200
images/windows/scripts/helpers/TestsHelpers.ps1
Normal file
@@ -0,0 +1,200 @@
|
||||
function Get-CommandResult {
|
||||
Param (
|
||||
[Parameter(Mandatory)][string] $Command
|
||||
)
|
||||
# CMD trick to suppress and show error output because some commands write to stderr (for example, "python --version")
|
||||
[string[]]$output = & $env:comspec /c "$Command 2>&1"
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
return @{
|
||||
Output = $output
|
||||
ExitCode = $exitCode
|
||||
}
|
||||
}
|
||||
|
||||
# Gets path to the tool, analogue of 'which tool'
|
||||
function Get-WhichTool($tool) {
|
||||
return (Get-Command $tool).Path
|
||||
}
|
||||
|
||||
# Gets value of environment variable by the name
|
||||
function Get-EnvironmentVariable($variable) {
|
||||
return [System.Environment]::GetEnvironmentVariable($variable, "Machine")
|
||||
}
|
||||
|
||||
# Update environment variables without reboot
|
||||
function Update-Environment {
|
||||
$variables = [Environment]::GetEnvironmentVariables("Machine")
|
||||
$variables.Keys | ForEach-Object {
|
||||
$key = $_
|
||||
$value = $variables[$key]
|
||||
Set-Item -Path "env:$key" -Value $value
|
||||
}
|
||||
# We need to refresh PATH the latest one because it could include other variables "%M2_HOME%/bin"
|
||||
$env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine")
|
||||
}
|
||||
|
||||
# Run Pester tests for specific tool
|
||||
function Invoke-PesterTests {
|
||||
Param(
|
||||
[Parameter(Mandatory)][string] $TestFile,
|
||||
[string] $TestName
|
||||
)
|
||||
|
||||
$testPath = "C:\image\tests\${TestFile}.Tests.ps1"
|
||||
if (-not (Test-Path $testPath)) {
|
||||
throw "Unable to find test file '$TestFile' on '$testPath'."
|
||||
}
|
||||
|
||||
$configuration = [PesterConfiguration] @{
|
||||
Run = @{ Path = $testPath; PassThru = $true }
|
||||
Output = @{ Verbosity = "Detailed"; RenderMode = "Plaintext" }
|
||||
}
|
||||
if ($TestName) {
|
||||
$configuration.Filter.FullName = $TestName
|
||||
}
|
||||
if ($TestFile -eq "*") {
|
||||
$configuration.TestResult.Enabled = $true
|
||||
$configuration.TestResult.OutputPath = "C:\image\tests\testResults.xml"
|
||||
}
|
||||
|
||||
# Update environment variables without reboot
|
||||
Update-Environment
|
||||
|
||||
# Switch ErrorActionPreference to Stop temporary to make sure that tests will on silent errors too
|
||||
$backupErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Stop"
|
||||
$results = Invoke-Pester -Configuration $configuration
|
||||
$ErrorActionPreference = $backupErrorActionPreference
|
||||
|
||||
# Fail in case if no tests are run
|
||||
if (-not ($results -and ($results.FailedCount -eq 0) -and ($results.PassedCount -gt 0))) {
|
||||
$results
|
||||
throw "Test run has failed"
|
||||
}
|
||||
}
|
||||
|
||||
# Pester Assert to check exit code of command
|
||||
function ShouldReturnZeroExitCode {
|
||||
Param(
|
||||
[String] $ActualValue,
|
||||
[switch] $Negate,
|
||||
[string] $Because
|
||||
)
|
||||
|
||||
$result = Get-CommandResult $ActualValue
|
||||
|
||||
[bool]$succeeded = $result.ExitCode -eq 0
|
||||
if ($Negate) { $succeeded = -not $succeeded }
|
||||
|
||||
if (-not $succeeded)
|
||||
{
|
||||
$commandOutputIndent = " " * 4
|
||||
$commandOutput = ($result.Output | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n"
|
||||
$failureMessage = "Command '${ActualValue}' has finished with exit code ${actualExitCode}`n${commandOutput}"
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
# Pester Assert to check exit code of command with given parameter, the assertion performed up to 3 checks (without '-', with 1 and 2 '-') until succeeded
|
||||
function ShouldReturnZeroExitCodeWithParam {
|
||||
param (
|
||||
[Parameter(Mandatory)] [string] $ActualValue,
|
||||
[switch] $Negate,
|
||||
[string] $CallParameter = "version",
|
||||
[string] $CallerSessionState
|
||||
)
|
||||
|
||||
$delimiterCharacter = ""
|
||||
|
||||
while ($delimiterCharacter.Length -le 2)
|
||||
{
|
||||
$callParameterWithDelimiter = $delimiterCharacter + $CallParameter
|
||||
$commandToCheck = "$ActualValue $callParameterWithDelimiter"
|
||||
[bool]$succeeded = (ShouldReturnZeroExitCode -ActualValue $commandToCheck).Succeeded
|
||||
|
||||
if ($succeeded)
|
||||
{
|
||||
break
|
||||
}
|
||||
$delimiterCharacter += '-'
|
||||
}
|
||||
if ($Negate) { $succeeded = -not $succeeded }
|
||||
|
||||
if (-not $succeeded)
|
||||
{
|
||||
$failureMessage = "Tool '$ActualValue' has not returned 0 exit code for any of these flags: '$CallParameter' or '-$CallParameter' or '--$CallParameter'"
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
# Pester Assert to match output of command
|
||||
function ShouldMatchCommandOutput {
|
||||
Param(
|
||||
[String] $ActualValue,
|
||||
[String] $RegularExpression,
|
||||
[switch] $Negate
|
||||
)
|
||||
|
||||
$output = (Get-CommandResult $ActualValue).Output | Out-String
|
||||
[bool] $succeeded = $output -cmatch $RegularExpression
|
||||
|
||||
if ($Negate) {
|
||||
$succeeded = -not $succeeded
|
||||
}
|
||||
|
||||
$failureMessage = ''
|
||||
|
||||
if (-not $succeeded) {
|
||||
if ($Negate) {
|
||||
$failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to not match '$output', but it did match."
|
||||
}
|
||||
else {
|
||||
$failureMessage = "Expected regular expression '$RegularExpression' for '$ActualValue' command to match '$output', but it did not match."
|
||||
}
|
||||
}
|
||||
|
||||
return [PSCustomObject] @{
|
||||
Succeeded = $succeeded
|
||||
FailureMessage = $failureMessage
|
||||
}
|
||||
}
|
||||
|
||||
If (Get-Command -Name Add-ShouldOperator -ErrorAction SilentlyContinue) {
|
||||
Add-ShouldOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode}
|
||||
Add-ShouldOperator -Name ReturnZeroExitCodeWithParam -InternalName ShouldReturnZeroExitCodeWithParam -Test ${function:ShouldReturnZeroExitCodeWithParam}
|
||||
Add-ShouldOperator -Name MatchCommandOutput -InternalName ShouldMatchCommandOutput -Test ${function:ShouldMatchCommandOutput}
|
||||
}
|
||||
|
||||
Function Get-ModuleVersionAsJob {
|
||||
Param (
|
||||
[Parameter(Mandatory)]
|
||||
[String] $modulePath,
|
||||
[Parameter(Mandatory)]
|
||||
[String] $moduleName
|
||||
)
|
||||
# Script block to run commands in separate PowerShell environment
|
||||
$testJob = Start-Job -ScriptBlock {
|
||||
param (
|
||||
$modulePath,
|
||||
$moduleName
|
||||
)
|
||||
# Disable warning messages to prevent additional warnings about Az and Azurerm modules in the same session
|
||||
$WarningPreference = "SilentlyContinue"
|
||||
$env:PsModulePath = "$modulePath;$env:PsModulePath"
|
||||
Import-Module -Name $moduleName
|
||||
(Get-Module -Name $moduleName).Version.ToString()
|
||||
|
||||
} -ArgumentList $modulePath, $moduleName
|
||||
|
||||
$testJob | Wait-Job | Receive-Job | Out-File -FilePath "${env:TEMP}\module-version.txt"
|
||||
Remove-Job $testJob
|
||||
}
|
||||
131
images/windows/scripts/helpers/VisualStudioHelpers.ps1
Normal file
131
images/windows/scripts/helpers/VisualStudioHelpers.ps1
Normal file
@@ -0,0 +1,131 @@
|
||||
Function Install-VisualStudio {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A helper function to install Visual Studio.
|
||||
|
||||
.DESCRIPTION
|
||||
Prepare system environment, and install Visual Studio bootstrapper with selected workloads.
|
||||
|
||||
.PARAMETER Version
|
||||
The version of Visual Studio that will be installed. Required parameter.
|
||||
|
||||
.PARAMETER Edition
|
||||
The edition of Visual Studio that will be installed. Required parameter.
|
||||
|
||||
.PARAMETER Channel
|
||||
The channel of Visual Studio that will be installed. Required parameter.
|
||||
|
||||
.PARAMETER RequiredComponents
|
||||
The list of required components. Required parameter.
|
||||
|
||||
.PARAMETER ExtraArgs
|
||||
The extra arguments to pass to the bootstrapper. Optional parameter.
|
||||
#>
|
||||
|
||||
Param
|
||||
(
|
||||
[Parameter(Mandatory)] [String] $Version,
|
||||
[Parameter(Mandatory)] [String] $Edition,
|
||||
[Parameter(Mandatory)] [String] $Channel,
|
||||
[Parameter(Mandatory)] [String[]] $RequiredComponents,
|
||||
[String] $ExtraArgs = "",
|
||||
[Parameter(Mandatory)] [String] $SignatureThumbprint
|
||||
)
|
||||
|
||||
$bootstrapperUrl = "https://aka.ms/vs/${Version}/${Channel}/vs_${Edition}.exe"
|
||||
$channelUri = "https://aka.ms/vs/${Version}/${Channel}/channel"
|
||||
$channelId = "VisualStudio.${Version}.Release"
|
||||
$productId = "Microsoft.VisualStudio.Product.${Edition}"
|
||||
|
||||
Write-Host "Downloading Bootstrapper ..."
|
||||
$BootstrapperName = [IO.Path]::GetFileName($BootstrapperUrl)
|
||||
$bootstrapperFilePath = Start-DownloadWithRetry -Url $BootstrapperUrl -Name $BootstrapperName
|
||||
|
||||
# Verify that the bootstrapper is signed by Microsoft
|
||||
Test-FileSignature -FilePath $bootstrapperFilePath -ExpectedThumbprint $SignatureThumbprint
|
||||
|
||||
try {
|
||||
Write-Host "Enable short name support on Windows needed for Xamarin Android AOT, defaults appear to have been changed in Azure VMs"
|
||||
$shortNameEnableProcess = Start-Process -FilePath fsutil.exe -ArgumentList ('8dot3name', 'set', '0') -Wait -PassThru
|
||||
|
||||
$shortNameEnableExitCode = $shortNameEnableProcess.ExitCode
|
||||
if ($shortNameEnableExitCode -ne 0) {
|
||||
Write-Host "Enabling short name support on Windows failed. This needs to be enabled prior to VS 2017 install for Xamarin Andriod AOT to work."
|
||||
exit $shortNameEnableExitCode
|
||||
}
|
||||
|
||||
$responseData = @{
|
||||
"channelUri" = $channelUri
|
||||
"channelId" = $channelId
|
||||
"productId" = $productId
|
||||
"arch" = "x64"
|
||||
"add" = $RequiredComponents | ForEach-Object { "$_;includeRecommended" }
|
||||
}
|
||||
|
||||
# Create json file with response data
|
||||
$responseDataPath = "$env:TEMP\vs_install_response.json"
|
||||
$responseData | ConvertTo-Json | Out-File -FilePath $responseDataPath
|
||||
|
||||
Write-Host "Starting Install ..."
|
||||
$bootstrapperArgumentList = ('/c', $bootstrapperFilePath, '--in', $responseDataPath, $ExtraArgs, '--quiet', '--norestart', '--wait', '--nocache' )
|
||||
Write-Host "Bootstrapper arguments: $bootstrapperArgumentList"
|
||||
$process = Start-Process -FilePath cmd.exe -ArgumentList $bootstrapperArgumentList -Wait -PassThru
|
||||
|
||||
$exitCode = $process.ExitCode
|
||||
if ($exitCode -eq 0 -or $exitCode -eq 3010) {
|
||||
Write-Host "Installation successful"
|
||||
return $exitCode
|
||||
} else {
|
||||
Write-Host "Non zero exit code returned by the installation process : $exitCode"
|
||||
|
||||
# Try to download tool to collect logs
|
||||
$collectExeUrl = "https://aka.ms/vscollect.exe"
|
||||
$collectExeName = [IO.Path]::GetFileName($collectExeUrl)
|
||||
$collectExePath = Start-DownloadWithRetry -Url $collectExeUrl -Name $collectExeName
|
||||
|
||||
# Collect installation logs using the collect.exe tool and check if it is successful
|
||||
& "$collectExePath"
|
||||
if ($LastExitCode -ne 0) {
|
||||
Write-Host "Failed to collect logs using collect.exe tool. Exit code : $LastExitCode"
|
||||
exit $exitCode
|
||||
}
|
||||
|
||||
# Expand the zip file
|
||||
Expand-Archive -Path "$env:TEMP\vslogs.zip" -DestinationPath "$env:TEMP\vslogs"
|
||||
|
||||
# Print logs
|
||||
$vsLogsPath = "$env:TEMP\vslogs"
|
||||
$vsLogs = Get-ChildItem -Path $vsLogsPath -Recurse | Where-Object { -not $_.PSIsContainer } | Select-Object -ExpandProperty FullName
|
||||
foreach ($log in $vsLogs) {
|
||||
Write-Host "============================"
|
||||
Write-Host "== Log file : $log "
|
||||
Write-Host "============================"
|
||||
Get-Content -Path $log -ErrorAction Continue
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Host "Failed to install Visual Studio; $($_.Exception.Message)"
|
||||
exit -1
|
||||
}
|
||||
}
|
||||
|
||||
function Get-VsCatalogJsonPath {
|
||||
$instanceFolder = Get-Item "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances\*" | Select-Object -First 1
|
||||
return Join-Path $instanceFolder.FullName "catalog.json"
|
||||
}
|
||||
|
||||
function Get-VisualStudioInstance {
|
||||
# Use -Prerelease and -All flags to make sure that Preview versions of VS are found correctly
|
||||
$vsInstance = Get-VSSetupInstance -Prerelease -All | Where-Object { $_.DisplayName -match "Visual Studio" } | Select-Object -First 1
|
||||
$vsInstance | Select-VSSetupInstance -Product *
|
||||
}
|
||||
|
||||
function Get-VisualStudioComponents {
|
||||
(Get-VisualStudioInstance).Packages | Where-Object type -in 'Component', 'Workload' |
|
||||
Sort-Object Id, Version | Select-Object @{n = 'Package'; e = {$_.Id}}, Version |
|
||||
Where-Object { $_.Package -notmatch "[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}" }
|
||||
}
|
||||
13
images/windows/scripts/helpers/test/ImageHelpers.Tests.ps1
Normal file
13
images/windows/scripts/helpers/test/ImageHelpers.Tests.ps1
Normal file
@@ -0,0 +1,13 @@
|
||||
$ModuleManifestName = 'ImageHelpers.psd1'
|
||||
$ModuleManifestPath = "$PSScriptRoot\..\$ModuleManifestName"
|
||||
|
||||
|
||||
|
||||
Describe 'Module Manifest Tests' {
|
||||
It 'Passes Test-ModuleManifest' {
|
||||
Test-ModuleManifest -Path $ModuleManifestPath | Should Not BeNullOrEmpty
|
||||
$? | Should Be $true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
images/windows/scripts/helpers/test/PathHelpers.Tests.ps1
Normal file
34
images/windows/scripts/helpers/test/PathHelpers.Tests.ps1
Normal file
@@ -0,0 +1,34 @@
|
||||
. $PSScriptRoot\..\PathHelpers.ps1
|
||||
|
||||
Describe 'Test-MachinePath Tests' {
|
||||
Mock Get-MachinePath {return "C:\foo;C:\bar"}
|
||||
It 'Path contains item' {
|
||||
Test-MachinePath -PathItem "C:\foo" | Should Be $true
|
||||
}
|
||||
It 'Path does not containe item' {
|
||||
Test-MachinePath -PathItem "C:\baz" | Should Be $false
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Set-MachinePath Tests' {
|
||||
Mock Get-MachinePath {return "C:\foo;C:\bar"}
|
||||
Mock Set-ItemProperty {return}
|
||||
It 'Set-MachinePath should return new path' {
|
||||
Set-MachinePath -NewPath "C:\baz" | Should Be "C:\baz"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Add-MachinePathItem Tests"{
|
||||
Mock Get-MachinePath {return "C:\foo;C:\bar"}
|
||||
Mock Set-ItemProperty {return}
|
||||
It 'Add-MachinePathItem should return complete path' {
|
||||
Add-MachinePathItem -PathItem 'C:\baz' | Should Be 'C:\baz;C:\foo;C:\bar'
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Set-SystemVariable Tests' {
|
||||
Mock Set-ItemProperty {return}
|
||||
It 'Set-SystemVariable should return new path' {
|
||||
Set-SystemVariable -SystemVariable "NewPathVar" -Value "C:\baz" | Should Be "C:\baz"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user