Scripts/Install-Mirantis.ps1 (503 lines of code) (raw):
# The MIT License (MIT)
#
# Copyright (c) 2015 Microsoft Azure
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
<#
.SYNOPSIS
This script checks if Mirantis needs to be installed by downloading and executing the Mirantis installer, after successful installation the machine will be restarted.
More information about the Mirantis installer, see: https://docs.mirantis.com/mcr/20.10/install/mcr-windows.html
.NOTES
v 1.0.4 adding support for docker ce using
https://github.com/microsoft/Windows-Containers/tree/Main/helpful_tools/Install-DockerCE
https://docs.docker.com/desktop/install/windows-install/
https://learn.microsoft.com/en-us/azure/virtual-machines/acu
.PARAMETER dockerVersion
[string] Version of docker to install. Default will be to install latest version.
Format '0.0.0.'
.PARAMETER allowUpgrade
[switch] Allow upgrade of docker. Default is to not upgrade version of docker.
.PARAMETER hypervIsolation
[switch] Install Hyper-V feature / components. Default is to not install Hyper-V feature.
Mirantis install will install container feature.
.PARAMETER installContainerD
[switch] Install containerd. Default is to not install containerd.
containerd is not needed for docker functionality.
.PARAMETER mirantisInstallUrl
[string] Mirantis installation script url. Default is 'https://get.mirantis.com/install.ps1'
.PARAMETER uninstall
[switch] Uninstall docker only. This will not uninstall containerd or Hyper-V feature.
.PARAMETER norestart
[switch] No restart after installation of docker and container feature. By default, after installation, node is restarted.
Use of -norestart is not supported.
.PARAMETER registerEvent
[bool] If true, will write installation summary information to the Application event log. Default is true.
.PARAMETER registerEventSource
[string] Register event source name used to write installation summary information to the Application event log.. Default name is 'CustomScriptExtension'.
.INPUTS
None. You cannot pipe objects to Add-Extension.
.OUTPUTS
Result object from the execution of https://get.mirantis.com/install.ps1.
.EXAMPLE
parameters.json :
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"customScriptExtensionFile": {
"value": "install-mirantis.ps1"
},
"customScriptExtensionFileUri": {
"value": "https://aka.ms/install-mirantis.ps1"
},
template json :
"virtualMachineProfile": {
"extensionProfile": {
"extensions": [
{
"name": "CustomScriptExtension",
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.10",
"autoUpgradeMinorVersion": true,
"settings": {
"fileUris": [
"[parameters('customScriptExtensionFileUri')]"
],
"commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -File .\\', parameters('customScriptExtensionFile'))]"
}
}
}
},
{
"name": "[concat(parameters('vmNodeType0Name'),'_ServiceFabricNode')]",
"properties": {
"provisionAfterExtensions": [
"CustomScriptExtension"
],
"type": "ServiceFabricNode",
.LINK
https://github.com/Azure/Service-Fabric-Troubleshooting-Guides
#>
[cmdletbinding()]
param(
[string]$dockerVersion = '0.0.0.0', # latest
[string]$containerDVersion = '0.0.0.0', # latest
[switch]$allowUpgrade,
[switch]$hypervIsolation,
[switch]$installContainerD,
[string]$mirantisInstallUrl = 'https://get.mirantis.com/install.ps1',
[switch]$dockerCe,
[switch]$uninstall,
[switch]$noRestart,
[switch]$noExceptionOnError,
[bool]$registerEvent = $true,
[string]$registerEventSource = 'CustomScriptExtension',
[switch]$whatIf
)
#$PSModuleAutoLoadingPreference = 2
#$ErrorActionPreference = 'continue'
[Net.ServicePointManager]::Expect100Continue = $true;
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
$eventLogName = 'Application'
$dockerProcessName = 'dockerd'
$dockerServiceName = 'docker'
$transcriptLog = "$psscriptroot\transcript.log"
$defaultDockerExe = 'C:\Program Files\Docker\dockerd.exe'
$nullVersion = '0.0.0.0'
$versionMap = @{}
$mirantisRepo = 'https://repos.mirantis.com'
$dockerCeRepo = 'https://download.docker.com'
$dockerPackageAbsolutePath = 'win/static/stable/x86_64'
$dockerOfflineFile = "$psscriptroot/Docker.zip"
$containerDOfflineFile = "$psscriptroot/Containerd.zip"
$maxEventMessageSize = 16384 #32766 - 1000
$global:currentDockerVersions = @{}
$global:currentContainerDVersions = @{}
$global:downloadUrl = $mirantisRepo
$global:restart = !$noRestart
$global:result = $true
function Main() {
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if (!$isAdmin) {
Write-Error "Restart script as administrator."
return $false
}
Register-Event
Start-Transcript -Path $transcriptLog
$error.Clear()
$installFile = "$psscriptroot\$([IO.Path]::GetFileName($mirantisInstallUrl))"
Write-Host "Installation file:$installFile"
if (!(Test-Path $installFile)) {
Download-File -url $mirantisInstallUrl -outputFile $installFile
}
# temp fix
Add-UseBasicParsing -ScriptFile $installFile
$dockerVersion = Set-DockerVersion -dockerVersion $dockerVersion
$installedVersion = Get-InstalledDockerVersion
# install windows-features
Install-Feature -name 'containers'
if ($hypervIsolation) {
Install-Feature -Name 'hyper-v'
Install-Feature -Name 'rsat-hyper-v-tools'
Install-Feature -Name 'hyper-v-tools'
Install-Feature -Name 'hyper-v-powershell'
}
if ($uninstall -and (Test-DockerIsInstalled)) {
Write-Warning "Uninstalling docker. Uninstall:$uninstall"
Invoke-Script -Script $installFile -Arguments "-Uninstall -verbose 6>&1"
}
elseif ($installedVersion -eq $dockerVersion) {
Write-Host "Docker $installedVersion already installed and is equal to $dockerVersion. Skipping install."
$global:restart = $false
}
elseif ($installedVersion -ge $dockerVersion) {
Write-Host "Docker $installedVersion already installed and is newer than $dockerVersion. Skipping install."
$global:restart = $false
}
elseif ($installedVersion -ne $nullVersion -and ($installedVersion -lt $dockerVersion -and !$allowUpgrade)) {
Write-Host "Docker $installedVersion already installed and is older than $dockerVersion. allowupgrade:$allowUpgrade. skipping install."
$global:restart = $false
}
else {
$error.Clear()
$dockerInstallArgs = @{}
[void]$dockerInstallArgs.Add('dockerVersion', $dockerVersion.ToString())
[void]$dockerInstallArgs.Add('offline', $null)
[void]$dockerInstallArgs.Add('offlinePackagesPath', $psscriptroot)
[void]$dockerInstallArgs.Add('verbose', $null)
if ($global:restart) {
[void]$dockerInstallArgs.Add('noServiceStarts', $null)
}
if ($dockerCe) {
$global:downloadUrl = $dockerCeRepo
}
if (!$installContainerD) {
[void]$dockerInstallArgs.Add('engineOnly', $null)
}
else {
# download containerd outside mirantis script
$containerDVersion = Set-ContainerDVersion -containerDVersion $containerDVersion
$containerDDownloadFile = $global:currentContainerDVersions.Item($containerDVersion)
# containerd only in mirantis repo
Download-File -url "$mirantisRepo/$dockerPackageAbsolutePath/$containerDDownloadFile" -outputFile $containerDOfflineFile
[void]$dockerInstallArgs.Add('containerDVersion', $containerDVersion.ToString())
}
# download docker outside mirantis script
$dockerDownloadFile = $global:currentDockerVersions.Item($dockerVersion)
Download-File -url "$global:downloadUrl/$dockerPackageAbsolutePath/$dockerDownloadFile" -outputFile $dockerOfflineFile
# docker script will always emit errors checking for files even when successful
Write-Host "Installing docker."
$scriptResult = Invoke-Script -script $installFile `
-argumentsTable $dockerInstallArgs `
-checkError $false
$error.Clear()
$finalVersion = Get-InstalledDockerVersion
if ($finalVersion -eq $nullVersion) {
Write-Host "setting `$global:result to false: finalversion:$finalVersion nullversion:$nullversion"
$global:result = $false
}
Write-Host "Install result:$($scriptResult | Format-List * | Out-String)"
Write-Host "Global result:$global:result"
Write-Host "Installed docker version:$finalVersion"
Write-HOst "docker.exe output: $(docker | out-string)"
Write-Host "Restarting OS:$global:restart"
}
Stop-Transcript
$transcript = Get-Content -raw $transcriptLog
Write-Event -data $transcript
if (!$whatIf -and $global:result -and $global:restart) {
# prevent sf extension from trying to install before restart
Start-Process powershell '-c', {
$outvar = $null;
$mutex = [threading.mutex]::new($true, 'Global\ServiceFabricExtensionHandler.A6C37D68-0BDA-4C46-B038-E76418AFC690', [ref]$outvar);
write-host $mutex;
write-host $outvar;
read-host;
}
# return immediately after this call
Restart-Computer -Force
}
if (!$noExceptionOnError -and !$global:result) {
throw [Exception]::new("Exception $($MyInvocation.ScriptName)`n$($transcript)")
}
return $global:result
}
# Adding as most Windows Server images have installed PowerShell 5.1 and without this switch Invoke-WebRequest is using Internet Explorer COM API which is causing issues with PowerShell < 6.0.
function Add-UseBasicParsing($scriptFile) {
$newLine
$updated = $false
$scriptLines = [io.file]::ReadAllLines($scriptFile)
$newScript = [collections.arrayList]::new()
Write-Host "Updating $scriptFile to use -UseBasicParsing for Invoke-WebRequest"
foreach ($line in $scriptLines) {
$newLine = $line
if ([regex]::IsMatch($line, 'Invoke-WebRequest', [text.regularExpressions.regexOptions]::IgnoreCase)) {
Write-Host "Found command $line"
if (![regex]::IsMatch($line, '-UseBasicParsing', [text.regularExpressions.regexOptions]::IgnoreCase)) {
$newLine = [regex]::Replace($line, 'Invoke-WebRequest', 'Invoke-WebRequest -UseBasicParsing', [text.regularExpressions.regexOptions]::IgnoreCase)
Write-Host "Updating command $line to $newLine"
$updated = $true
}
}
[void]$newScript.Add($newLine)
}
if ($updated) {
$newScriptContent = [string]::Join([Environment]::NewLine, $newScript.ToArray())
$tempFile = "$scriptFile.oem"
if ((Test-Path $tempFile)) {
Remove-Item $tempFile -Force
}
Rename-Item $scriptFile -NewName $tempFile -force
Write-Host "Saving new script $scriptFile"
Out-File -InputObject $newScriptContent -FilePath $scriptFile -Force
}
}
function Download-File($url, $outputFile) {
Write-Host "$result = [net.webClient]::new().downloadFile($url, $outputFile)"
[net.webClient]::new().downloadFile($url, $outputFile)
Write-Host "DownloadFile result:$($result | Format-List *)"
if ($error -or !(Test-Path $outputFile)) {
Write-Error "failure downloading file:$($error | out-string)"
$global:result = $false
}
elseif($whatIf) {
[io.file]::delete($outputFile)
}
}
# Get the docker version
function Get-InstalledDockerVersion() {
$installedVersion = [version]::new($nullVersion)
if (Test-IsDockerRunning) {
$path = (Get-Process -Name $dockerProcessName).Path
Write-Host "Docker installed and running: $path"
$dockerInfo = (docker version)
$installedVersion = [version][regex]::Match($dockerInfo, 'Version:\s+?(\d.+?)\s').Groups[1].Value
}
elseif (Test-DockerIsInstalled) {
$path = Get-WmiObject win32_service | Where-Object { $psitem.Name -like $dockerServiceName } | Select-Object PathName
Write-Host "Docker exe path:$path"
$path = [regex]::Match($path.PathName, "`"(.+)`"").Groups[1].Value
Write-Host "Docker exe clean path:$path"
$installedVersion = [version]::new([diagnostics.fileVersionInfo]::GetVersionInfo($path).FileVersion)
Write-Warning "Warning: docker installed but not running: $path"
}
else {
Write-Host "Docker not installed"
}
Write-Host "Installed docker defaultPath:$($defaultDockerExe -ieq $path) path:$path version:$installedVersion"
return $installedVersion
}
# Get Available Versions
function Get-AvailableVersions() {
# install.ps1 using Write-Host to output string data. have to capture with 6>&1
# for docker ce and mirantis compat, query versions outside install.ps1
if ($global:currentDockerVersions.Count -lt 1 -or $global:currentContainerDVersions.Count -lt 1) {
$result = Invoke-WebRequest -Uri "$global:downloadUrl/$dockerPackageAbsolutePath" -UseBasicParsing
$filePattern = '(?<file>(?<filetype>docker|containerd)-(?<major>\d+?)\.(?<minor>\d+?)\.(?<build>\d+?)\.zip)'
$linkMatches = [regex]::matches($result.Links.href, $filePattern, [text.regularExpressions.regexOptions]::IgnoreCase)
foreach ($match in $linkMatches) {
$major = $match.groups['major'].value
$minor = $match.groups['minor'].value
$build = $match.groups['build'].value
$version = [version]::new($major, $minor, $build)
$file = $match.groups['file'].value
$filetype = $match.groups['filetype'].value
if ($filetype -ieq 'docker') {
[void]$global:currentDockerVersions.Add($version, $file)
}
else {
[void]$global:currentContainerDVersions.Add($version, $file)
}
}
}
}
# Get the latest docker version
function Get-LatestVersion([string[]] $versions) {
$latestVersion = [version]::new()
if (!$versions) {
return [version]::new($nullVersion)
}
foreach ($version in $versions) {
try {
$currentVersion = [version]::new($version)
if ($currentVersion -gt $latestVersion) {
$latestVersion = $currentVersion
}
}
catch {
$error.Clear()
continue
}
}
return $latestVersion
}
# Install Windows-Feature if not installed
function Install-Feature([string]$name) {
$feautureResult = $null
$isInstalled = (Get-WindowsFeature -name $name).Installed
Write-Host "Windows feature '$name' installed:$isInstalled"
if (!$isInstalled) {
Write-Host "Installing windows feature '$name'"
$feautureResult = Install-WindowsFeature -Name $name
if (!$feautureResult.Success) {
Write-Error "error installing feature:$($error | out-string)"
$global:result = $false
}
else {
if (!$noRestart) {
$global:restart = $global:restart -or $feautureResult.RestartNeeded -ieq 'yes'
Write-Host "`$global:restart set to $global:restart"
}
}
}
return $feautureResult
}
# Invoke the MCR installer (this will require a reboot)
function Invoke-Script([string]$script, [string] $arguments = $null, [hashtable] $argumentsTable = @{}, [bool]$checkError = $true) {
$scriptResult = $null
if($argumentsTable.Count -gt 0) {
foreach($arg in $argumentsTable.GetEnumerator()) {
$argValue = $null
if($arg.Value) {
$argValue = " '$($arg.Value)'"
}
$arguments += " -$($arg.Key)$argValue"
}
}
Write-Host "Invoke-Expression -Command `"$script $arguments`""
if (!$whatIf) {
$scriptResult = Invoke-Expression -Command "$script $arguments"
}
return $scriptResult
}
# Set version parameter
function Set-Version($version, $currentVersions) {
$setVersion = $version
Write-Host "Current versions: $($currentVersions | out-string)"
$latestVersion = Get-LatestVersion -versions $currentVersions.Keys
Write-Host "Latest version: $latestVersion"
if ($version -eq $nullVersion -or $version -ieq 'latest' -or $allowUpgrade) {
Write-Host "Setting version to latest"
$setVersion = $latestVersion
}
else {
try {
$setVersion = [version]::new($version)
Write-Host "Setting version to $setVersion"
}
catch {
$setVersion = [version]::new($nullVersion)
Write-Warning "Exception setting version to $version`r`n$($error | Out-String)"
}
if ($setVersion -ieq [version]::new($nullVersion)) {
$setVersion = $latestdockerVersion
Write-Host "Setting version to latest version $latestVersion"
}
}
Write-Host "Returning target install version: $setVersion"
return $setVersion
}
# Set containerd version parameter
function Set-ContainerDVersion($containerDVersion) {
Get-AvailableVersions
Write-Host "Requesting containerd target install version: $containerDVersion"
$version = Set-Version -version $containerDVersion -currentVersions $global:currentContainerDVersions
Write-Host "Returning containerd target install version: $version"
return $version
}
# Set docker version parameter
function Set-DockerVersion($dockerVersion) {
Get-AvailableVersions
Write-Host "Requesting docker target install version: $dockerVersion"
$version = Set-Version -version $dockerVersion -currentVersions $global:currentDockerVersions
Write-Host "Returning docker target install version: $version"
return $version
}
# Validate if docker is installed
function Test-DockerIsInstalled() {
$retval = $false
if ((Get-Service -name $dockerServiceName -ErrorAction SilentlyContinue)) {
$retval = $true
}
$error.Clear()
Write-Host "Docker installed:$retval"
return $retval
}
# Check if docker is already running
function Test-IsDockerRunning() {
$retval = $false
if (Get-Process -Name $dockerProcessName -ErrorAction SilentlyContinue) {
if (Invoke-Expression 'Docker version') {
$retval = $true
}
}
Write-Host "Docker running:$retval"
return $retval
}
# Register Windows event source
function Register-Event() {
if ($registerEvent) {
$error.clear()
New-EventLog -LogName $eventLogName -Source $registerEventSource -ErrorAction silentlycontinue
if ($error -and ($error -inotmatch 'source is already registered')) {
$registerEvent = $false
}
else {
$error.clear()
}
}
}
# Trace event
function Write-Event($data, $level = 'Information') {
Write-Host $data
if (!$global:result -or $error -or $level -ieq 'Error') {
$level = 'Error'
$data = "$data`r`nErrors:`r`n$($error | Out-String)"
Write-Error $data
$error.Clear()
}
try {
if ($registerEvent) {
$index = 0
$counter = 1
$totalEvents = [int]($data.Length / $maxEventMessageSize)
while ($index -lt $data.Length) {
$header = "$counter of $totalEvents`n"
$counter++
$dataSize = [math]::Min($data.Length - $index, $maxEventMessageSize)
Write-Verbose "`$data.Substring($index, $dataSize)"
$dataChunk = $header
$dataChunk += $data.Substring($index, $dataSize)
$index += $dataSize
Write-EventLog -LogName $eventLogName `
-Source $registerEventSource `
-Message $dataChunk `
-EventId 1000 `
-EntryType $level
}
}
}
catch {
Write-Host "exception writing event to event log:$($error | out-string)"
$error.Clear()
}
}
Main