Artifacts/windows-ServiceFabricSDK/InstallServiceFabricSDKAndTools.ps1 (365 lines of code) (raw):
[CmdletBinding()]
param(
[string] $VSVersion
)
###################################################################################################
#
# PowerShell configurations
#
# NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure.
# This is necessary to ensure we capture errors inside the try-catch-finally block.
$ErrorActionPreference = "Stop"
# Hide any progress bars, due to downloads and installs of remote components.
$ProgressPreference = "SilentlyContinue"
# Ensure we force use of TLS 1.2 for all downloads.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Ensure we set the working directory to that of the script.
Push-Location $PSScriptRoot
# Discard any collected errors from a previous execution.
$Error.Clear()
# Configure strict debugging.
Set-PSDebug -Strict
###################################################################################################
#
# Handle all errors in this script.
#
trap
{
# NOTE: This trap will handle all errors. There should be no need to use a catch below in this
# script, unless you want to ignore a specific error.
$message = $Error[0].Exception.Message
if ($message)
{
Write-Host -Object "`nERROR: $message" -ForegroundColor Red
}
Write-Host "`nThe artifact failed to apply.`n"
# IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still
# returns exit code zero from the PowerShell script when using -File. The workaround is to
# NOT use -File when calling this script and leverage the try-catch-finally block and return
# a non-zero exit code from the catch block.
exit -1
}
###################################################################################################
#
# Functions used in this script.
#
function Get-VSVersionNumber
{
[CmdletBinding()]
param(
[string] $VSVersion
)
switch ($VSVersion)
{
'Visual Studio 2015' { return 14 }
'Visual Studio 2017' { return 15 }
default { throw "Unsupported Visual Studio version specified: $VSVersion" }
}
}
function Get-VSSetupInstances
{
$null = @(
if (-not (Get-Module -ListAvailable -Name VSSetup))
{
Write-Host "Updating NuGet provider to a version higher than 2.8.5.201."
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Write-Host "Installing PowerShellGet module."
Install-Module -Name PowerShellGet -Force
Write-Host "Installing VSSetup module."
Install-Module -Name VSSetup -Force
}
# Make sure the VSSetup module is loaded.
Import-Module -Name VSSetup
)
# Get VS installation information.
return Get-VSSetupInstance
}
function Test-PowerShellVersion
{
[CmdletBinding()]
param(
[double] $Version
)
$currentVersion = [double] "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
if ($currentVersion -lt $Version)
{
throw "The current version of PowerShell is $currentVersion. Prior to running this artifact, ensure you have PowerShell version $Version or higher installed."
}
}
function Test-VSVersion
{
[CmdletBinding()]
param(
[string] $VSVersion
)
$foundDesiredVSVersion = $false
$vsVersionNumber = Get-VSVersionNumber -VSVersion $VSVersion
switch ($vsVersionNumber)
{
14
{
$foundDesiredVSVersion = Test-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\$vsVersionNumber.0"
}
default
{
# Get VS installation information.
$vsInstances = Get-VSSetupInstances
# See if any major version of installed products match the desired VS version.
$vsInstances | % {
Write-Host "Found version '$($_.InstallationVersion)' of Visual Studio installed at $($_.InstallationPath)."
if ($_.InstallationVersion.Major -eq $vsVersionNumber)
{
$foundDesiredVSVersion = $true
break;
}
}
}
}
if (-not $foundDesiredVSVersion)
{
throw "Unable to find specified version: $VSVersion. It must be installed before proceeding."
}
}
function Invoke-Process
{
[CmdletBinding()]
param (
[string] $FileName = $(throw 'The FileName must be provided'),
[string] $Arguments = '',
[Array] $ValidExitCodes = @()
)
Write-Host "Running command '$FileName $Arguments'"
# Prepare specifics for starting the process that will install the component.
$startInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
Arguments = $Arguments
CreateNoWindow = $true
ErrorDialog = $false
FileName = $FileName
RedirectStandardError = $true
RedirectStandardInput = $true
RedirectStandardOutput = $true
UseShellExecute = $false
Verb = 'runas'
WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
WorkingDirectory = $PSScriptRoot
}
# Initialize a new process.
$process = New-Object System.Diagnostics.Process
try
{
# Configure the process so we can capture all its output.
$process.EnableRaisingEvents = $true
# Hook into the standard output and error stream events
$errEvent = Register-ObjectEvent -SourceIdentifier OnErrorDataReceived $process "ErrorDataReceived" `
`
{
param
(
[System.Object] $sender,
[System.Diagnostics.DataReceivedEventArgs] $e
)
foreach ($s in $e.Data) { if ($s) { Write-Host $err $s -ForegroundColor Red } }
}
$outEvent = Register-ObjectEvent -SourceIdentifier OnOutputDataReceived $process "OutputDataReceived" `
`
{
param
(
[System.Object] $sender,
[System.Diagnostics.DataReceivedEventArgs] $e
)
foreach ($s in $e.Data) { if ($s -and $s.Trim('. ').Length -gt 0) { Write-Host $s } }
}
$process.StartInfo = $startInfo;
# Attempt to start the process.
if ($process.Start())
{
# Read from all redirected streams before waiting to prevent deadlock.
$process.BeginErrorReadLine()
$process.BeginOutputReadLine()
# Wait for the application to exit for no more than 5 minutes.
$process.WaitForExit(300000) | Out-Null
}
# Ensure we extract an exit code, if not from the process itself.
$exitCode = $process.ExitCode
# Determine if process requires a reboot.
if ($exitCode -eq 3010)
{
Write-Host 'The recent changes indicate a reboot is necessary. Please reboot at your earliest convenience.'
}
elseif ($ValidExitCodes.Contains($exitCode))
{
Write-Host "$FileName exited with expected valid exit code: $exitCode"
# Override to ensure the overall script doesn't fail.
$LASTEXITCODE = 0
}
# Determine if process failed to execute.
elseif ($exitCode -gt 0)
{
# Throwing an exception at this point will stop any subsequent
# attempts for deployment.
throw "$FileName exited with code: $exitCode"
}
}
finally
{
# Free all resources associated to the process.
$process.Close();
# Remove any previous event handlers.
Unregister-Event OnErrorDataReceived -Force | Out-Null
Unregister-Event OnOutputDataReceived -Force | Out-Null
}
}
function Install-WebPlatformInstaller
{
if (Test-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebPlatformInstaller")
{
Write-Host 'Web Platform Installer already installed'
}
else
{
# Get MSI to install Web Platform Installer URL.
if ($ENV:PROCESSOR_ARCHITECTURE -eq 'AMD64')
{
$wpiPackage = "http://download.microsoft.com/download/C/F/F/CFF3A0B8-99D4-41A2-AE1A-496C08BEB904/WebPlatformInstaller_amd64_en-US.msi"
}
else
{
$wpiPackage = "http://download.microsoft.com/download/C/F/F/CFF3A0B8-99D4-41A2-AE1A-496C08BEB904/WebPlatformInstaller_x86_en-US.msi"
}
Write-Host "Installing Web Platform Installer"
Invoke-Process -FileName "$env:windir\system32\msiexec.exe" -Arguments "/quiet /norestart /package $wpiPackage"
}
}
function Get-WebPlatformInstaller
{
$wpiInfo = (ls "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebPlatformInstaller")[-1].Name
return Join-Path (Get-ItemProperty -Path "Registry::$wpiInfo" -Name 'InstallPath').InstallPath 'webpicmd.exe'
}
function Get-VSInsaller
{
$software = 'HKEY_LOCAL_MACHINE\SOFTWARE'
$uninstall = 'Microsoft\Windows\CurrentVersion\Uninstall'
$winUninstall = "$software\$uninstall"
$wowUninstall = "$software\WOW6432Node\$uninstall"
$installerGuid = '{6F320B93-EE3C-4826-85E0-ADF79F8D4C61}'
$installerExe = 'vs_installer.exe'
$paths = (
"Registry::$winUninstall\$installerGuid",
"Registry::$wowUninstall\$installerGuid",
"Registry::$winUninstall\*",
"Registry::$wowUninstall\*"
)
[string] $vsInstallerExePath = $null
foreach ($path in $paths)
{
$vsInstallerExePath = (Get-ItemProperty -Path "$path" | Where-Object { $_.ModifyPath -match "$installerExe" } | Select-Object -First 1).InstallLocation.Trim('"')
if ($vsInstallerExePath)
{
break
}
}
if (-not $vsInstallerExePath)
{
throw 'Unable to find the Visual Studio Installer. Please verify its installation.'
}
return (Join-Path $vsInstallerExePath $installerExe)
}
function Get-ServiceFabricSdkProductId
{
[CmdletBinding()]
param(
[int] $VSVersionNumber
)
# Get correct Service Fabric SDK Product ID for version of VS that is installed.
switch ($VSVersionNumber){
14 { return 'MicrosoftAzure-ServiceFabric-VS2015' } #tools and sdk
default { return 'MicrosoftAzure-ServiceFabric-CoreSDK' } #core sdk only
}
}
function Install-ServiceFabricSdk
{
[CmdletBinding()]
param(
[int] $VSVersionNumber,
[string] $LogPath = $(Join-Path $env:Temp 'ServiceFabricSDK.log')
)
$wpiExe = Get-WebPlatformInstaller
$productId = Get-ServiceFabricSdkProductId -VSVersionNumber $VSVersionNumber
Invoke-Process -FileName "$wpiExe" -Arguments "/Offline /Products:$productId /Path:$($env:Temp)\OfflineCache"
Invoke-Process -FileName "$wpiExe" -Arguments "/Install /Products:$productId /AcceptEula /SuppressReboot /SuppressPostFinish /Log:$LogPath /xml:$($env:Temp)\OfflineCache\feeds\latest\webproductlist.xml"
}
function Enable-ServiceFabricTools
{
[CmdletBinding()]
param(
[int] $VSVersionNumber
)
$vsBootstrapperExe = Join-Path $env:Temp "vsbootstrap.exe"
$vsBootstrapperUrl = "https://aka.ms/vs/$VSVersionNumber/release/vs_enterprise.exe"
Write-Host "Downloading Visual Studio bootstrapper from $vsBootstrapperUrl"
try
{
(New-Object System.Net.WebClient).DownloadFile($vsBootstrapperUrl, $vsBootstrapperExe)
}
catch [System.Management.Automation.MethodInvocationException]
{
throw "Unable to find Visual Studio bootstapper at $vsBootstrapperUrl"
}
if (-not (Test-Path $vsBootstrapperExe))
{
throw "Visual Studio bootstrapper was not successfully downloaded to $vsBootstrapperExe"
}
Write-Host 'Getting Visual Studio Installer executable path'
$vsInstallExe = Get-VSInsaller
Write-Host "Visual Studio Installer is at $vsInstallExe"
# Get VS installation information.
$vsInstances = Get-VSSetupInstances
# Add Service Fabric component to all Visual Studio instances.
$vsInstances | % {
if ($_.InstallationVersion.Major -eq $VSVersionNumber)
{
# We must do an update to restore the channel used for modifying the Visual Studio Instance.
Write-Host 'Updating Visual Studio instance'
Invoke-Process -FileName "$vsBootstrapperExe" -Arguments "update --installPath `"$($_.InstallationPath)`" --quiet --wait" -ValidExitCodes 1
# Modify the Visual Studio instance with Service Fabric SDK components.
Write-Host "Enabling Service Fabric Tools component for installation $($_.InstallationPath)"
Invoke-Process -FileName "$vsInstallExe" -Arguments "modify --installPath `"$($_.InstallationPath)`" --add Microsoft.VisualStudio.Workload.Azure --add Microsoft.VisualStudio.Component.Azure.ServiceFabric.Tools --quiet --norestart --wait" -ValidExitCodes 1
}
}
}
###################################################################################################
#
# Main execution block.
#
try
{
Write-Host "Starting installation of Service Fabric SDK and Tools for $VSVersion."
# For this process, we want to be able to execute downloaded scripts.
Write-Host 'Configuring PowerShell session.'
Test-PowerShellVersion -Version 5.1
Set-ExecutionPolicy Bypass -Scope Process -Force | Out-Null
# Change the "Local AppData" path to a location where the process can write, or the relevant
# VS installer components will fail to complete.
reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d "$($env:Temp)\AppData\Local" /f | Out-Null
Write-Host "Validating version specified: $VSVersion."
Test-VSVersion -VSVersion $VSVersion
Write-Host "Fetching $VSVersion details."
$vsVersionNumber = Get-VSVersionNumber -VSVersion $VSVersion
Write-Host 'Preparing Web Platform Installer.'
Install-WebPlatformInstaller
Write-Host 'Installing Service Fabric SDK.'
Install-ServiceFabricSdk -VSVersionNumber $vsVersionNumber
if ($vsVersionNumber -ge 15)
{
Write-Host 'Enabling Service Fabric Tools.'
Enable-ServiceFabricTools -VSVersionNumber $vsVersionNumber
}
Write-Host "`nThe artifact was applied successfully.`n"
}
finally
{
# Restore system to state prior to execution of this script.
reg add "hku\.default\software\microsoft\windows\currentversion\explorer\user shell folders" /v "Local AppData" /t REG_EXPAND_SZ /d %%USERPROFILE%%\AppData\Local /f | Out-Null
Pop-Location
}