mirror of
https://github.com/actions/runner-images.git
synced 2025-12-11 03:27:05 +00:00
371 lines
13 KiB
PowerShell
371 lines
13 KiB
PowerShell
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 InstallChannel
|
|
The InstallChannelUri 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,
|
|
[String] $InstallChannel = "",
|
|
[Parameter(Mandatory)] [String[]] $RequiredComponents,
|
|
[String] $ExtraArgs = ""
|
|
)
|
|
|
|
|
|
if (-not (Test-IsWin19)) {
|
|
$bootstrapperUrl = "https://aka.ms/vs/${Version}/postGRO-${Channel}/vs_${Edition}.exe"
|
|
} else {
|
|
$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}"
|
|
$installChannelUri = "https://aka.ms/vs/${Version}/${Channel}/${installchannel}/channel"
|
|
|
|
Write-Host "Downloading Bootstrapper ..."
|
|
$bootstrapperFilePath = Invoke-DownloadWithRetry $BootstrapperUrl
|
|
|
|
# Verify that the bootstrapper is signed by Microsoft
|
|
Test-FileSignature -Path $bootstrapperFilePath -ExpectedSubject $(Get-MicrosoftPublisher)
|
|
|
|
try {
|
|
$responseData = @{
|
|
"installChannelUri" = $installChannelUri
|
|
"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
|
|
|
|
$installStartTime = Get-Date
|
|
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
|
|
$installCompleteTime = [math]::Round(($(Get-Date) - $installStartTime).TotalSeconds, 2)
|
|
if ($exitCode -eq 0) {
|
|
Write-Host "Installation successful in $installCompleteTime seconds"
|
|
return $exitCode
|
|
} elseif ($exitCode -eq 3010) {
|
|
Write-Host "Installation successful in $installCompleteTime seconds. Reboot is required."
|
|
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"
|
|
$collectExePath = Invoke-DownloadWithRetry -Url $collectExeUrl
|
|
|
|
# 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_DIR\vslogs.zip" -DestinationPath "$env:TEMP_DIR\vslogs"
|
|
|
|
# Print logs
|
|
$vsLogsPath = "$env:TEMP_DIR\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-VisualStudioInstance {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the Visual Studio instance.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the Visual Studio instance
|
|
using the Get-VSSetupInstance cmdlet.
|
|
It searches for both regular and preview versions
|
|
of Visual Studio and returns the first instance found.
|
|
#>
|
|
|
|
# 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 {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the Visual Studio components.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the Visual Studio components
|
|
by filtering the packages returned by Get-VisualStudioInstance cmdlet.
|
|
It filters the packages based on their type, sorts them by Id and Version,
|
|
and excludes packages with GUID-like Ids.
|
|
#>
|
|
|
|
(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}" }
|
|
}
|
|
|
|
function Get-VsixInfoFromMarketplace {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves information about a Visual Studio extension from the Visual Studio Marketplace.
|
|
|
|
.DESCRIPTION
|
|
The Get-VsixInfoFromMarketplace function retrieves information
|
|
about a Visual Studio extension from the Visual Studio Marketplace.
|
|
It takes the name of the extension as input and returns
|
|
the extension's name, VsixId, filename, and download URI.
|
|
|
|
.PARAMETER Name
|
|
The name of the Visual Studio extension.
|
|
|
|
.PARAMETER MarketplaceUri
|
|
The URI of the Visual Studio Marketplace.
|
|
Default value is "https://marketplace.visualstudio.com/items?itemName=".
|
|
|
|
.EXAMPLE
|
|
Get-VsixInfoFromMarketplace -Name "ProBITools.MicrosoftReportProjectsforVisualStudio2022"
|
|
Retrieves information about the "ProBITools.MicrosoftReportProjectsforVisualStudio2022" extension
|
|
from the Visual Studio Marketplace.
|
|
#>
|
|
|
|
Param
|
|
(
|
|
[Parameter(Mandatory)]
|
|
[Alias("ExtensionMarketPlaceName")]
|
|
[string] $Name,
|
|
[string] $MarketplaceUri = "https://marketplace.visualstudio.com/items?itemName="
|
|
)
|
|
|
|
# Invoke-WebRequest doesn't support retry in PowerShell 5.1
|
|
$webResponse = Invoke-ScriptBlockWithRetry -RetryCount 20 -RetryIntervalSeconds 30 -Command {
|
|
Invoke-WebRequest -Uri "${MarketplaceUri}${Name}" -UseBasicParsing
|
|
}
|
|
|
|
$webResponse -match 'UniqueIdentifierValue":"(?<extensionname>[^"]*)' | Out-Null
|
|
$extensionName = $Matches.extensionname
|
|
|
|
$webResponse -match 'VsixId":"(?<vsixid>[^"]*)' | Out-Null
|
|
$vsixId = $Matches.vsixid
|
|
|
|
$webResponse -match 'AssetUri":"(?<uri>[^"]*)' | Out-Null
|
|
$assetUri = $Matches.uri
|
|
|
|
$webResponse -match 'Microsoft\.VisualStudio\.Services\.Payload\.FileName":"(?<filename>[^"]*)' | Out-Null
|
|
$fileName = $Matches.filename
|
|
|
|
switch ($Name) {
|
|
# ProBITools.MicrosoftReportProjectsforVisualStudio2022 has different URL
|
|
# https://github.com/actions/runner-images/issues/5340
|
|
"ProBITools.MicrosoftReportProjectsforVisualStudio2022" {
|
|
$assetUri = "https://download.microsoft.com/download/1fd275d8-5163-476b-910b-e2f678b3fdbc"
|
|
$fileName = "Microsoft.DataTools.ReportingServices.vsix"
|
|
}
|
|
"ProBITools.MicrosoftAnalysisServicesModelingProjects2022" {
|
|
$assetUri = "https://download.microsoft.com/download/7c91cb5c-1e9c-4df7-a053-d2852e22c658"
|
|
$fileName = "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-VSIXFromFile {
|
|
<#
|
|
.SYNOPSIS
|
|
Installs a Visual Studio Extension (VSIX) from a file.
|
|
|
|
.DESCRIPTION
|
|
This function installs a Visual Studio Extension (VSIX)
|
|
from the specified file path. It uses the VSIXInstaller.exe
|
|
tool provided by Microsoft Visual Studio.
|
|
|
|
.PARAMETER FilePath
|
|
The path to the VSIX file that needs to be installed.
|
|
|
|
.PARAMETER Retries
|
|
The number of retries to attempt if the installation fails. Default is 20.
|
|
|
|
.EXAMPLE
|
|
Install-VSIXFromFile -FilePath "C:\Extensions\MyExtension.vsix" -Retries 10
|
|
Installs the VSIX file located at "C:\Extensions\MyExtension.vsix" with 10 retries in case of failure.
|
|
#>
|
|
Param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[string] $FilePath,
|
|
[int] $Retries = 20
|
|
)
|
|
|
|
Write-Host "Installing VSIX from $FilePath..."
|
|
while ($True) {
|
|
$installerPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service\VSIXInstaller.exe"
|
|
try {
|
|
$process = Start-Process `
|
|
-FilePath $installerPath `
|
|
-ArgumentList @('/quiet', "`"$FilePath`"") `
|
|
-Wait -PassThru
|
|
} catch {
|
|
Write-Host "Failed to start VSIXInstaller.exe with error:"
|
|
$_
|
|
exit 1
|
|
}
|
|
|
|
$exitCode = $process.ExitCode
|
|
|
|
if ($exitCode -eq 0) {
|
|
Write-Host "VSIX installed successfully."
|
|
break
|
|
} elseif ($exitCode -eq 1001) {
|
|
Write-Host "VSIX is already installed."
|
|
break
|
|
}
|
|
|
|
Write-Host "VSIX installation failed with exit code $exitCode."
|
|
|
|
$Retries--
|
|
if ($Retries -eq 0) {
|
|
Write-Host "VSIX installation failed after $Retries retries."
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "Waiting 10 seconds before retrying. Retries left: $Retries"
|
|
Start-Sleep -Seconds 10
|
|
}
|
|
}
|
|
|
|
function Install-VSIXFromUrl {
|
|
<#
|
|
.SYNOPSIS
|
|
Installs a Visual Studio extension (VSIX) from a given URL.
|
|
|
|
.DESCRIPTION
|
|
This function downloads a Visual Studio extension (VSIX)
|
|
from the specified URL and installs it.
|
|
|
|
.PARAMETER Url
|
|
The URL of the VSIX file to download and install.
|
|
|
|
.PARAMETER Retries
|
|
The number of retries to attempt if the download fails. Default is 20.
|
|
|
|
.EXAMPLE
|
|
Install-VSIXFromUrl -Url "https://example.com/extension.vsix" -Retries 10
|
|
Downloads and installs the VSIX file from the specified URL with 10 retries.
|
|
#>
|
|
|
|
Param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[string] $Url,
|
|
[int] $Retries = 20
|
|
)
|
|
|
|
$filePath = Invoke-DownloadWithRetry $Url
|
|
Install-VSIXFromFile -FilePath $filePath -Retries $Retries
|
|
Remove-Item -Force -Confirm:$false $filePath
|
|
}
|
|
|
|
function Get-VSExtensionVersion {
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the version of a Visual Studio extension package.
|
|
|
|
.DESCRIPTION
|
|
This function retrieves the version of a specified Visual Studio extension package.
|
|
It searches for the package in the installed instances of Visual Studio and
|
|
returns the version number.
|
|
|
|
.PARAMETER packageName
|
|
The name of the extension package.
|
|
|
|
.EXAMPLE
|
|
Get-VSExtensionVersion -packageName "MyExtensionPackage"
|
|
Retrieves the version of the extension package named "MyExtensionPackage" for Visual Studio.
|
|
#>
|
|
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
|
|
}
|