From 4b69ef8768bb9f5309fe4fa0df702803b13daf89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bere=C5=BCa=C5=84ski?= Date: Fri, 17 Nov 2017 23:57:51 +0100 Subject: [PATCH] chocolatey-visualstudio.extension: update VS 2017 installer before each operation GitHub-Issue: GH-7 GH-8 GH-26 --- ...t-VSBootstrapperUrlFromChannelManifest.ps1 | 181 ++++++++++++++++++ .../extensions/Install-VSInstaller.ps1 | 13 ++ .../extensions/Install-VisualStudio.ps1 | 2 +- .../Start-VisualStudioModifyOperation.ps1 | 24 ++- 4 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 chocolatey-visualstudio.extension/extensions/Get-VSBootstrapperUrlFromChannelManifest.ps1 diff --git a/chocolatey-visualstudio.extension/extensions/Get-VSBootstrapperUrlFromChannelManifest.ps1 b/chocolatey-visualstudio.extension/extensions/Get-VSBootstrapperUrlFromChannelManifest.ps1 new file mode 100644 index 00000000..85eda34f --- /dev/null +++ b/chocolatey-visualstudio.extension/extensions/Get-VSBootstrapperUrlFromChannelManifest.ps1 @@ -0,0 +1,181 @@ +function Get-VSBootstrapperUrlFromChannelManifest +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] [hashtable] $PackageParameters, + [PSObject] $ProductReference + ) + Write-Debug 'Trying to obtain vs_Setup.exe URL from the channel manifest' + + $manifestUri = $null + # first, see if the caller provided the manifest uri via package parameters + Write-Debug 'Checking if the channel manifest URI has been provided' + if ($PackageParameters.ContainsKey('installChannelUri') -and -not [string]::IsNullOrEmpty($PackageParameters['installChannelUri'])) + { + $manifestUri = $PackageParameters['installChannelUri'] + Write-Debug "Using channel manifest URI from the 'installChannelUri' package parameter: '$manifestUri'" + } + else + { + Write-Debug "Package parameters do not contain 'installChannelUri' or it is empty" + if ($PackageParameters.ContainsKey('channelUri') -and -not [string]::IsNullOrEmpty($PackageParameters['channelUri'])) + { + $manifestUri = $PackageParameters['channelUri'] + Write-Debug "Using channel manifest URI from the 'channelUri' package parameter: '$manifestUri'" + } + else + { + Write-Debug "Package parameters do not contain 'channelUri' or it is empty" + } + } + + if ($manifestUri -eq $null) + { + # second, try to compute the uri from the channel id + Write-Debug 'Checking if the channel id has been provided' + $channelId = $null + if ($PackageParameters.ContainsKey('channelId') -and -not [string]::IsNullOrEmpty($PackageParameters['channelId'])) + { + $channelId = $PackageParameters['channelId'] + Write-Debug "Using channel id from the 'channelId' package parameter: '$channelId'" + } + else + { + Write-Debug "Package parameters do not contain 'channelId' or it is empty" + if ($ProductReference -ne $null) + { + $channelId = $ProductReference.ChannelId + Write-Debug "Using channel id from the provided ProductReference: '$channelId'" + } + else + { + Write-Debug "ProductReference has not been provided; channel id is not known" + } + } + if ($channelId -ne $null) + { + $success = $channelId -match '^VisualStudio\.(?\d+)\.(?\w+)$' # VisualStudio.15.Release + if ($success) + { + $manifestUri = 'https://aka.ms/vs/{0}/{1}/channel' -f $Matches['version'], $Matches['kind'].ToLowerInvariant() + Write-Debug "Using channel manifest URI computed from the channel id: '$manifestUri'" + } + else + { + Write-Debug "Channel id '$channelId' does not match the expected pattern and cannot be used to compute the channel manifest URI" + } + } + } + + if ($manifestUri -eq $null) + { + # finally, fall back to hardcoded + $manifestUri = 'https://aka.ms/vs/15/release/channel' + Write-Debug "Fallback: using hardcoded channel manifest URI: '$manifestUri'" + } + + $chocTempDir = $env:TEMP + $tempDir = Join-Path $chocTempDir 'chocolatey-visualstudio.extension' + if (![System.IO.Directory]::Exists($tempDir)) { [System.IO.Directory]::CreateDirectory($tempDir) | Out-Null } + + $localFileName = '{0}.chman' -f $manifestUri.GetHashCode() + $localFilePath = Join-Path $tempDir $localFileName + + Write-Verbose "Downloading the channel manifest" + $arguments = @{ + packageName = 'channel manifest' + fileFullPath = $localFilePath + url = $manifestUri + url64 = $manifestUri + } + Set-StrictMode -Off + Get-ChocolateyWebFile @arguments | Out-Null + Set-StrictMode -Version 2 + + Write-Verbose "Reading the channel manifest" + $manifestContent = [System.IO.File]::ReadAllText($localFilePath) + + # VS 2017 requires Windows 7 or later, so .NET 3.5 or later is guaranteed, therefore we can use System.Web.Extensions + [System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | Out-Null + $serializer = New-Object -TypeName 'System.Web.Script.Serialization.JavaScriptSerializer' + $manifest = $serializer.DeserializeObject($manifestContent) + + $url = $null + $checksum = $null + $checksumType = $null + if ($manifest -is [Collections.IDictionary] -and $manifest.ContainsKey('channelItems')) + { + $channelItems = $manifest['channelItems'] + if ($channelItems -is [array]) + { + $bootstrapper = $channelItems | Where-Object { $_ -is [Collections.IDictionary] -and $_.ContainsKey('type') -and $_['type'] -eq 'Bootstrapper' } + if (($bootstrapper | Measure-Object).Count -eq 1) + { + if ($bootstrapper -is [Collections.IDictionary] -and $bootstrapper.ContainsKey('payloads')) + { + $payloads = $bootstrapper['payloads'] + if ($payloads -is [array]) + { + if (($payloads | Measure-Object).Count -eq 1) + { + $payload = $payloads[0] + if ($payload -is [Collections.IDictionary] -and $payload.ContainsKey('url')) + { + $url = $payload['url'] + if (-not [string]::IsNullOrEmpty($url) -and $payload.ContainsKey('sha256')) + { + $checksum = $payload['sha256'] + $checksumType = 'sha256' + } + else + { + Write-Debug 'Manifest parsing error: payload url is empty or payload does not contain sha256' + # url will still be returned; it might be useful even without the checksum + } + } + else + { + Write-Debug 'Manifest parsing error: payload is not IDictionary or does not contain url' + } + } + else + { + Write-Debug 'Manifest parsing error: zero or more than one payload objects found' + } + } + else + { + Write-Debug 'Manifest parsing error: payloads is not an array' + } + } + else + { + Write-Debug 'Manifest parsing error: bootstrapper is not IDictionary or does not contain payloads' + } + } + else + { + Write-Debug 'Manifest parsing error: zero or more than one bootstrapper objects found' + } + } + else + { + Write-Debug 'Manifest parsing error: channelItems is not an array' + } + } + else + { + Write-Debug 'Manifest parsing error: manifest is not IDictionary or does not contain channelItems' + } + + if (-not [string]::IsNullOrEmpty($url)) + { + Write-Debug "Bootstrapper url determined from the channel manifest: '$url' (checksum: '$checksum', type: '$checksumType'" + return $url, $checksum, $checksumType + } + else + { + Write-Debug 'The bootstrapper url could not be determined' + return $null + } +} diff --git a/chocolatey-visualstudio.extension/extensions/Install-VSInstaller.ps1 b/chocolatey-visualstudio.extension/extensions/Install-VSInstaller.ps1 index 47237dbc..765f0f16 100644 --- a/chocolatey-visualstudio.extension/extensions/Install-VSInstaller.ps1 +++ b/chocolatey-visualstudio.extension/extensions/Install-VSInstaller.ps1 @@ -4,6 +4,7 @@ function Install-VSInstaller param( [Parameter(Mandatory = $true)] [string] $PackageName, [Parameter(Mandatory = $true)] [hashtable] $PackageParameters, + [PSObject] $ProductReference, [string] $Url, [string] $Checksum, [string] $ChecksumType, @@ -87,6 +88,18 @@ function Install-VSInstaller else { $installerFilePath = $null + if ($Url -eq '') + { + $Url, $Checksum, $ChecksumType = Get-VSBootstrapperUrlFromChannelManifest -PackageParameters $PackageParameters -ProductReference $ProductReference + } + } + + $whitelist = @('offline') + $parametersToRemove = $PackageParameters.Keys | Where-Object { $whitelist -notcontains $_ } + foreach ($parameterToRemove in $parametersToRemove) + { + Write-Debug "Filtering out package parameter not passed to the bootstrapper during VS Installer update: '$parameterToRemove'" + $PackageParameters.Remove($parameterToRemove) } # --update must be last diff --git a/chocolatey-visualstudio.extension/extensions/Install-VisualStudio.ps1 b/chocolatey-visualstudio.extension/extensions/Install-VisualStudio.ps1 index 132a00ab..27388a1e 100644 --- a/chocolatey-visualstudio.extension/extensions/Install-VisualStudio.ps1 +++ b/chocolatey-visualstudio.extension/extensions/Install-VisualStudio.ps1 @@ -69,7 +69,7 @@ Install-ChocolateyPackage { if ($AllowUpdate) { - Start-VisualStudioModifyOperation -PackageName $PackageName -ArgumentList @() -VisualStudioYear $VisualStudioYear -ApplicableProducts @($Product) -OperationTexts @('update', 'updating', 'update') -Operation 'update' + Start-VisualStudioModifyOperation -PackageName $PackageName -ArgumentList @() -VisualStudioYear $VisualStudioYear -ApplicableProducts @($Product) -OperationTexts @('update', 'updating', 'update') -Operation 'update' -PackageParameters $packageParameters -BootstrapperUrl $Url -BootstrapperChecksum $Checksum -BootstrapperChecksumType $ChecksumType -ProductReference $prodRef } else { diff --git a/chocolatey-visualstudio.extension/extensions/Start-VisualStudioModifyOperation.ps1 b/chocolatey-visualstudio.extension/extensions/Start-VisualStudioModifyOperation.ps1 index f23468d9..bea258ad 100644 --- a/chocolatey-visualstudio.extension/extensions/Start-VisualStudioModifyOperation.ps1 +++ b/chocolatey-visualstudio.extension/extensions/Start-VisualStudioModifyOperation.ps1 @@ -9,9 +9,14 @@ [Parameter(Mandatory = $true)] [string[]] $OperationTexts, [ValidateSet('modify', 'uninstall', 'update')] [string] $Operation = 'modify', [string] $InstallerPath, - [version] $RequiredProductVersion + [version] $RequiredProductVersion, + [hashtable] $PackageParameters, + [string] $BootstrapperUrl, + [string] $BootstrapperChecksum, + [string] $BootstrapperChecksumType, + [PSObject] $ProductReference ) - Write-Debug "Running 'Start-VisualStudioModifyOperation' with PackageName:'$PackageName' ArgumentList:'$ArgumentList' VisualStudioYear:'$VisualStudioYear' ApplicableProducts:'$ApplicableProducts' OperationTexts:'$OperationTexts' Operation:'$Operation' InstallerPath:'$InstallerPath' RequiredProductVersion:'$RequiredProductVersion'"; + Write-Debug "Running 'Start-VisualStudioModifyOperation' with PackageName:'$PackageName' ArgumentList:'$ArgumentList' VisualStudioYear:'$VisualStudioYear' ApplicableProducts:'$ApplicableProducts' OperationTexts:'$OperationTexts' Operation:'$Operation' InstallerPath:'$InstallerPath' RequiredProductVersion:'$RequiredProductVersion' BootstrapperUrl:'$BootstrapperUrl' BootstrapperChecksum:'$BootstrapperChecksum' BootstrapperChecksumType:'$BootstrapperChecksumType'"; $frobbed, $frobbing, $frobbage = $OperationTexts @@ -26,7 +31,14 @@ $InstallerPath = $installer.Path } - $packageParameters = Parse-Parameters $env:chocolateyPackageParameters + if ($PackageParameters -eq $null) + { + $PackageParameters = Parse-Parameters $env:chocolateyPackageParameters + } + else + { + $PackageParameters = $PackageParameters.Clone() + } $argumentSetFromArgumentList = @{} for ($i = 0; $i -lt $ArgumentList.Length; $i += 2) @@ -186,6 +198,12 @@ } } + # todo: move this inside the foreach, so that we can take advantage of channelId + if ($Operation -ne 'uninstall') + { + Install-VSInstaller -PackageName $PackageName -PackageParameters $PackageParameters -ProductReference $ProductReference -Url $BootstrapperUrl -Checksum $BootstrapperChecksum -ChecksumType $BootstrapperChecksumType -Force + } + $overallExitCode = 0 foreach ($argumentSet in $argumentSets) {