This repository has been archived by the owner on Jan 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 560
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add extension for Windows patching at deployment (#3704)
- Loading branch information
1 parent
997e9e3
commit ecc9b5b
Showing
5 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Windows Patching Extension | ||
|
||
This extension will install Windows Server patches, including prerelease hotfixes. It's useful for the following cases: | ||
|
||
1. Microsoft support has provided a pre-release hotfix for your testing | ||
2. Installing additional Windows update packages (MSU) that are not yet included in the default Windows Server with Containers VM on the Azure Marketplace. | ||
|
||
## Configuration | ||
|
||
|Name |Required| Acceptable Value | | ||
|-------------------|--------|----------------------| | ||
|name |yes | windows-patches | | ||
|version |yes | v1 | | ||
|rootURL |optional| `https://raw.githubusercontent.com/Azure/acs-engine/master/` or any repo with the same extensions/... directory structure | | ||
|extensionParameters|yes | comma-delimited list of URIs enclosed with ' such as `'https://privateupdates.domain.ext/Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe', 'https://privateupdates.domain.ext/Windows10.0-KB123456-x64-InstallForTestingPurposesOnly.exe'` | | ||
|
||
## Example | ||
|
||
```json | ||
... | ||
"agentPoolProfiles": [ | ||
{ | ||
"name": "windowspool1", | ||
"extensions": [ | ||
{ | ||
"name": "windows-patches" | ||
} | ||
] | ||
} | ||
], | ||
... | ||
"extensionProfiles": [ | ||
{ | ||
"name": "windows-patches", | ||
"version": "v1", | ||
"rootURL": "https://raw.githubusercontent.com/Azure/acs-engine/master/", | ||
"extensionParameters": "'https://mypatches.blob.core.windows.net/hotfix3692/Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe?sp=r&st=2018-08-17T00:25:01Z&se=2018-09-17T08:25:01Z&spr=https&sv=2017-11-09&sig=0000000000%3D&sr=b', 'http://download.windowsupdate.com/c/msdownload/update/software/secu/2018/08/windows10.0-kb4343909-x64_f931af6d56797388715fe3b0d97569af7aebdae6.msu'" | ||
} | ||
] | ||
... | ||
``` | ||
|
||
## Supported Orchestrators | ||
|
||
This has been tested with Kubernetes clusters, and does not depend on any specific version. | ||
|
||
## Selecting Patches | ||
|
||
### Cumulative Updates | ||
|
||
If you would like to include a cumulative update as part of your deployment that isn't in the Windows Server with Containers image, then follow these steps. | ||
|
||
1. Browse to [Windows 10 Update History](https://support.microsoft.com/en-us/help/4099479), and follow the link to the right version (1709 or 1803) in the left. This page should be titled "Windows 10 and Windows Server update history" because the links also lead you to Windows Server updates. | ||
2. Next, look for the latest patch in the lower left such as ["August 14, 2018—KB4343909 (OS Build 17134.228)"](https://support.microsoft.com/en-us/help/4343909), and click that link. | ||
3. Scroll down to "How to get this update", and click on the ["Microsoft Update Catalog"](http://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB4343909) link. | ||
4. Find the row for `(year)-(month) Cumulative Update for Windows Server 2016 ((1709 or 1803)) for x64-based Systems (KB######)`, and click the "Download" button. | ||
5. This will pop up a new window with a hyperlink such as `windows10.0-kb4343909-x64_f931af6d56797388715fe3b0d97569af7aebdae6.msu`. Copy that link. | ||
6. Include that link in the `extensionParameters` as shown in the [Example](#Example) | ||
|
||
### Supplied by Microsoft support | ||
|
||
Once you have downloaded a private hotfix from Microsoft support, it needs to be put in an Azure-accessible location. The easiest way to do this is to create an [Azure Blob Storage](https://docs.microsoft.com/en-us/azure/storage/common/storage-create-storage-account#blob-storage-accounts) account. Once uploaded, you can create a private link with a key to access it that will work with this extension. | ||
|
||
1. If you haven't already, install the [Azure CLI](https://docs.microsoft.com/cli/azure/get-started-with-az-cli2), and run `az login` to log in to Azure | ||
2. Copy the sample below for either bash (Linux, Mac, or WSL), or PowerShell (Windows), and modify the variables at the top. | ||
|
||
|
||
#### Using az cli and bash to upload the patch | ||
|
||
```bash | ||
#!/bin/bash | ||
export resource_group=patchgroup | ||
export storage_location=westus2 | ||
export storage_account_name=privatepatches | ||
export container_name=hotfix | ||
export blob_name=Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe | ||
export file_to_upload=Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe | ||
|
||
echo "Creating the group..." | ||
az group create --location $storage_location --resource-group $resource_group | ||
|
||
echo "Creating the storage account..." | ||
az storage account create --location $storage_location --name $storage_account_name --resource-group $resource_group --sku Standard_LRS | ||
|
||
echo "Getting the connection string..." | ||
export AZURE_STORAGE_CONNECTION_STRING="`az storage account show-connection-string --name $storage_account_name --resource-group $resource_group`" | ||
|
||
echo "Creating the container..." | ||
az storage container create --name $container_name | ||
|
||
echo "Uploading the file..." | ||
az storage blob upload --container-name $container_name --file $file_to_upload --name $blob_name | ||
|
||
echo "Getting a read-only SAS token, good for 30 days..." | ||
export EXPIRY=`date +"%Y-%m-%dT%H:%M:%SZ" -d '30 days'` | ||
export TEMPORARY_SAS=`az storage blob generate-sas --container-name $container_name --name $blob_name --permissions r --expiry $EXPIRY` | ||
|
||
echo "Getting a URL to the file..." | ||
export ABSURL=`az storage blob url --container-name $container_name --name $blob_name --sas-token $TEMPORARY_SAS` | ||
|
||
echo "Full URL including access token:" | ||
echo "$ABSURL?$TEMPORARY_SAS" | sed "s/\"//g" | ||
``` | ||
|
||
#### Using az cli and PowerShell to upload the patch | ||
|
||
```powershell | ||
$resource_group="patchgroup" | ||
$storage_location="westus2" | ||
$storage_account_name="privatepatches" | ||
$container_name="hotfix" | ||
$blob_name="Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe" | ||
$file_to_upload="Windows10.0-KB999999-x64-InstallForTestingPurposesOnly.exe" | ||
Write-Host "Creating the group..." | ||
az group create --location $storage_location --resource-group $resource_group | ||
Write-Host "Creating the storage account..." | ||
az storage account create --location $storage_location --name $storage_account_name --resource-group $resource_group --sku Standard_LRS | ||
Write-Host "Getting the connection string..." | ||
$ENV:AZURE_STORAGE_CONNECTION_STRING = az storage account show-connection-string --name $storage_account_name --resource-group $resource_group | ||
Write-Host "Creating the container..." | ||
az storage container create --name $container_name | ||
Write-Host "Uploading the file..." | ||
az storage blob upload --container-name $container_name --file $file_to_upload --name $blob_name | ||
Write-Host "Getting a read-only SAS token, good for 30 days..." | ||
$EXPIRY = ([DateTime]::Now + [timespan]::FromDays(30)).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") | ||
$TEMPORARY_SAS = az storage blob generate-sas --container-name $container_name --name $blob_name --permissions r --expiry $EXPIRY | ||
Write-Host "Getting a URL to the file..." | ||
$ABSURL = az storage blob url --container-name $container_name --name $blob_name --sas-token $TEMPORARY_SAS | ||
Write-Host "Full URL including access token:" | ||
$full_url = "$($ABSURL)?$($TEMPORARY_SAS)".Replace("""","") | ||
$full_url | Write-Host | ||
$full_url | Set-Clipboard | ||
``` | ||
|
||
The last line of the script will output a URL, and also put it on the Windows clipboard. Copy it into `extensionParameters` as shown in the sample above. Do not share this URL, keep it private. | ||
|
||
## Troubleshooting | ||
|
||
Extension execution output is logged to files found under the following directory on the target virtual machine. | ||
|
||
```powershell | ||
C:\WindowsAzure\Logs\Plugins\Microsoft.Compute.CustomScriptExtension | ||
``` | ||
|
||
The specified files are downloaded into the following directory on the target virtual machine. | ||
|
||
```powershell | ||
C:\Packages\Plugins\Microsoft.Compute.CustomScriptExtension\1.*\Downloads\<n> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
|
||
# Return codes: | ||
# 0 - success | ||
# 1 - install failure | ||
# 2 - download failure | ||
# 3 - unrecognized patch extension | ||
|
||
param( | ||
[string[]] $URIs | ||
) | ||
|
||
function DownloadFile([string] $URI, [string] $fullName) | ||
{ | ||
try { | ||
Write-Host "Downloading $URI" | ||
Invoke-WebRequest -UseBasicParsing $URI -OutFile $fullName | ||
} catch { | ||
Write-Error $_ | ||
exit 2 | ||
} | ||
} | ||
|
||
|
||
$URIs | ForEach-Object { | ||
Write-Host "Processing $_" | ||
$uri = $_ | ||
$pathOnly = $uri | ||
if ($pathOnly.Contains("?")) | ||
{ | ||
$pathOnly = $pathOnly.Split("?")[0] | ||
} | ||
$fileName = Split-Path $pathOnly -Leaf | ||
$ext = [io.path]::GetExtension($fileName) | ||
$fullName = [io.path]::Combine($env:TEMP, $fileName) | ||
switch ($ext) { | ||
".exe" { | ||
Start-Process -FilePath bcdedit.exe -ArgumentList "/set {current} testsigning on" -Wait | ||
DownloadFile -URI $uri -fullName $fullName | ||
Write-Host "Starting $fullName" | ||
$proc = Start-Process -Passthru -FilePath "$fullName" -ArgumentList "/q /norestart" | ||
Wait-Process -InputObject $proc | ||
switch ($proc.ExitCode) | ||
{ | ||
0 { | ||
Write-Host "Finished running $fullName" | ||
} | ||
3010 { | ||
Write-Host "Finished running $fullName. Reboot required to finish patching." | ||
} | ||
Default { | ||
Write-Error "Error running $fullName, exitcode $($proc.ExitCode)" | ||
exit 1 | ||
} | ||
} | ||
} | ||
".msu" { | ||
DownloadFile -URI $uri -fullName $fullName | ||
Write-Host "Installing $localPath" | ||
$proc = Start-Process -Passthru -FilePath wusa.exe -ArgumentList "$fullName /quiet /norestart" | ||
Wait-Process -InputObject $proc | ||
switch ($proc.ExitCode) | ||
{ | ||
0 { | ||
Write-Host "Finished running $fullName" | ||
} | ||
3010 { | ||
Write-Host "Finished running $fullName. Reboot required to finish patching." | ||
} | ||
Default { | ||
Write-Error "Error running $fullName, exitcode $($proc.ExitCode)" | ||
exit 1 | ||
} | ||
} | ||
} | ||
Default { | ||
Write-Error "This script extension doesn't know how to install $ext files" | ||
exit 3 | ||
} | ||
} | ||
} | ||
|
||
# No failures, schedule reboot now | ||
|
||
schtasks /create /TN RebootAfterPatch /RU SYSTEM /TR "shutdown.exe /r /t 0 /d 2:17" /SC ONCE /ST $(([System.DateTime]::Now + [timespan]::FromMinutes(5)).ToString("HH:mm")) /V1 /Z | ||
exit 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
["Kubernetes"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "[concat(EXTENSION_TARGET_VM_NAME_PREFIX, copyIndex(EXTENSION_LOOP_OFFSET), 'windows-patches')]", | ||
"type": "Microsoft.Resources/deployments", | ||
"apiVersion": "[variables('apiVersionLinkDefault')]", | ||
"dependsOn": [ | ||
"[concat('Microsoft.Compute/virtualMachines/', EXTENSION_TARGET_VM_NAME_PREFIX, copyIndex(EXTENSION_LOOP_OFFSET), '/extensions/cse', '-EXTENSION_TARGET_VM_TYPE-', copyIndex(EXTENSION_LOOP_OFFSET))]" | ||
], | ||
"copy": { | ||
"count": "EXTENSION_LOOP_COUNT", | ||
"name": "windows-patchesExtensionLoop" | ||
}, | ||
"properties": { | ||
"mode": "Incremental", | ||
"templateLink": { | ||
"uri": "EXTENSION_URL_REPLACEextensions/windows-patches/v1/template.json", | ||
"contentVersion": "1.0.0.0" | ||
}, | ||
"parameters": { | ||
"artifactsLocation": { | ||
"value": "EXTENSION_URL_REPLACE" | ||
}, | ||
"apiVersionCompute": { | ||
"value": "[variables('apiVersionDefault')]" | ||
}, | ||
"targetVMName": { | ||
"value": "[concat(EXTENSION_TARGET_VM_NAME_PREFIX, copyIndex(EXTENSION_LOOP_OFFSET))]" | ||
}, | ||
"targetVMType": { | ||
"value": "EXTENSION_TARGET_VM_TYPE" | ||
}, | ||
"extensionParameters": { | ||
"value": "EXTENSION_PARAMETERS_REPLACE" | ||
}, | ||
"vmIndex":{ | ||
"value": "[copyIndex(EXTENSION_LOOP_OFFSET)]" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
{ | ||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", | ||
"contentVersion": "1.0.0.0", | ||
"parameters": { | ||
"artifactsLocation": { | ||
"type": "string", | ||
"minLength": 1, | ||
"metadata": { | ||
"description": "Artifacts Location - URL" | ||
} | ||
}, | ||
"apiVersionCompute": { | ||
"type": "string", | ||
"minLength": 1, | ||
"metadata": { | ||
"description": "Compute API Version" | ||
} | ||
}, | ||
"targetVMName":{ | ||
"type": "string", | ||
"minLength": 1, | ||
"metadata": { | ||
"description": "Name of the vm to run the extension on" | ||
} | ||
}, | ||
"targetVMType":{ | ||
"type": "string", | ||
"minLength": 1, | ||
"metadata": { | ||
"description": "Type of the vm to run the extension: master or agent " | ||
} | ||
}, | ||
"extensionParameters": { | ||
"type": "securestring", | ||
"minLength": 0, | ||
"metadata": { | ||
"description": "Custom Parameter for Extension - comma delimited list of URIs" | ||
} | ||
}, | ||
"vmIndex": { | ||
"type": "int", | ||
"metadata": { | ||
"description": "index in the pool of the current agent, used so that we can get the extension name right" | ||
} | ||
} | ||
}, | ||
"variables": { }, | ||
"resources": [ | ||
{ | ||
"apiVersion": "[parameters('apiVersionCompute')]", | ||
"dependsOn": [], | ||
"location": "[resourceGroup().location]", | ||
"type": "Microsoft.Compute/virtualMachines/extensions", | ||
"name": "[concat(parameters('targetVMName'),'/cse', '-', parameters('targetVMType'), '-', parameters('vmIndex'))]", | ||
"properties": { | ||
"publisher": "Microsoft.Compute", | ||
"type": "CustomScriptExtension", | ||
"typeHandlerVersion": "1.8", | ||
"autoUpgradeMinorVersion": true, | ||
"settings": { | ||
"fileUris": [ | ||
"[concat(parameters('artifactsLocation'), 'extensions/windows-patches/v1/installPatches.ps1')]" | ||
] | ||
}, | ||
"protectedSettings": { | ||
"commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass \"& ./installPatches.ps1 -URIs ', parameters('extensionParameters'), '\"')]" | ||
} | ||
} | ||
} | ||
], | ||
"outputs": { } | ||
} |