Skip to content

Instantly share code, notes, and snippets.

@aidansteele
Created September 4, 2017 00:21
Show Gist options
  • Select an option

  • Save aidansteele/4ff4995ebbb8174714090b3e2251fc00 to your computer and use it in GitHub Desktop.

Select an option

Save aidansteele/4ff4995ebbb8174714090b3e2251fc00 to your computer and use it in GitHub Desktop.

Revisions

  1. aidansteele created this gist Sep 4, 2017.
    477 changes: 477 additions & 0 deletions automation.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,477 @@
    ---
    schemaVersion: '0.3'
    description: Updates a Microsoft Windows AMI. By default it will install all Windows
    updates, Amazon software, and Amazon drivers. It will then sysprep and create a
    new AMI. Supports Windows Server 2008 R2 and greater.
    assumeRole: "{{ AutomationAssumeRole }}"
    parameters:
    SourceAmiId:
    type: String
    description: "(Required) The source Amazon Machine Image ID."
    IamInstanceProfileName:
    type: String
    description: "(Required) The name of the role that enables Systems Manager to
    manage the instance."
    default: ManagedInstanceProfile
    AutomationAssumeRole:
    type: String
    description: "(Required) The ARN of the role that allows Automation to perform
    the actions on your behalf."
    default: arn:aws:iam::{{global:ACCOUNT_ID}}:role/AutomationServiceRole
    TargetAmiName:
    type: String
    description: "(Optional) The name of the new AMI that will be created. Default
    is a system-generated string including the source AMI id, and the creation time
    and date."
    default: UpdateWindowsAmi_from_{{SourceAmiId}}_on_{{global:DATE_TIME}}
    InstanceType:
    type: String
    description: "(Optional) Type of instance to launch as the workspace host. Instance
    types vary by region. Default is t2.medium."
    default: t2.medium
    IncludeKbs:
    type: String
    description: "(Optional) Specify one or more Microsoft Knowledge Base (KB) article
    IDs to include. You can install multiple IDs using comma-separated values. Valid
    formats: KB9876543 or 9876543."
    default: ''
    ExcludeKbs:
    type: String
    description: "(Optional) Specify one or more Microsoft Knowledge Base (KB) article
    IDs to exclude. You can exclude multiple IDs using comma-separated values. Valid
    formats: KB9876543 or 9876543."
    default: ''
    Categories:
    type: String
    description: "(Optional) Specify one or more update categories. You can filter
    categories using comma-separated values. Options: Application, Connectors, CriticalUpdates,
    DefinitionUpdates, DeveloperKits, Drivers, FeaturePacks, Guidance, Microsoft,
    SecurityUpdates, ServicePacks, Tools, UpdateRollups, Updates. Valid formats
    include a single entry, for example: CriticalUpdates. Or you can specify a comma
    separated list: CriticalUpdates,SecurityUpdates. NOTE: There cannot be any spaces
    around the commas."
    default: ''
    SeverityLevels:
    type: String
    description: "(Optional) Specify one or more MSRC severity levels associated with
    an update. You can filter severity levels using comma-separated values. By default
    patches for all security levels are selected. If value supplied, the update
    list is filtered by those values. Options: Critical, Important, Low, Moderate
    or Unspecified. Valid formats include a single entry, for example: Critical.
    Or, you can specify a comma separated list: Critical,Important,Low."
    default: ''
    PublishedDaysOld:
    type: String
    default: ''
    description: "(Optional) Specify the amount of days old the updates must be from
    the published date. For example, if 10 is specified, any updates that were
    found during the Windows Update search that have been published 10 or more days
    ago will be returned."
    PublishedDateAfter:
    type: String
    default: ''
    description: "(Optional) Specify the date that the updates should be published
    after. For example, if 01/01/2017 is specified, any updates that were found
    during the Windows Update search that have been published on or after 01/01/2017
    will be returned."
    PublishedDateBefore:
    type: String
    default: ''
    description: "(Optional) Specify the date that the updates should be published
    before. For example, if 01/01/2017 is specified, any updates that were found
    during the Windows Update search that have been published on or before 01/01/2017
    will be returned."
    PreUpdateScript:
    type: String
    description: "(Optional) A script provided as a string. It will execute prior
    to installing OS updates."
    default: ''
    PostUpdateScript:
    type: String
    description: "(Optional) A script provided as a string. It will execute after
    installing OS updates."
    default: ''
    mainSteps:
    - name: LaunchInstance
    action: aws:runInstances
    timeoutSeconds: 1800
    maxAttempts: 3
    onFailure: Abort
    inputs:
    ImageId: "{{ SourceAmiId }}"
    InstanceType: "{{ InstanceType }}"
    MinInstanceCount: 1
    MaxInstanceCount: 1
    IamInstanceProfileName: "{{ IamInstanceProfileName }}"
    - name: OSCompatibilityCheck
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 7200
    inputs:
    DocumentName: AWS-RunPowerShellScript
    InstanceIds:
    - "{{LaunchInstance.InstanceIds}}"
    Parameters:
    executionTimeout: '7200'
    commands:
    [System.Version]$osversion = [System.Environment]::OSVersion.Version
    if(($osversion.Major -eq 6 -and $osversion.Minor -ge 1) -or ($osversion.Major -ge 10)) {
    Write-Host 'This OS is supported for use with this automation document.'
    } else {
    Write-Host 'This OS is not supported for use with this automation document.'
    exit -1
    }
    - name: RunPreUpdateScript
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 1800
    inputs:
    DocumentName: AWS-RunPowerShellScript
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    Parameters:
    commands: "{{ PreUpdateScript }}"
    - name: UpdateEC2Config
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 7200
    inputs:
    DocumentName: AWS-RunPowerShellScript
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    Parameters:
    commands:
    $moduleName = 'AWSUpdateWindowsInstance'
    $zipFilename = 'AWSUpdateWindowsInstance.zip'
    $tempPath = $env:TEMP
    $moduleDirectory = Join-Path $tempPath -ChildPath $moduleName
    $moduleZipFilePath = Join-Path $tempPath -ChildPath $zipFilename
    $moduleManifestPath = Join-Path $moduleDirectory -ChildPath ('{0}.psd1' -f $moduleName)
    $id = '{{automation:EXECUTION_ID}}'

    $regionUrl = 'http://169.254.169.254/latest/dynamic/instance-identity/document/region'
    $metadata = (New-Object Net.WebClient).DownloadString($regionUrl)
    $region = (ConvertFrom-JSON $metadata).region

    function Main {
    Clear-WindowsUpdateModule
    Get-WindowsUpdateModule
    Expand-WindowsUpdateModule
    if ([Environment]::OSVersion.Version.Major -ge 10) {
    Invoke-UpdateEC2Launch
    } else {
    Invoke-UpdateEC2Config
    }
    }

    function Clear-WindowsUpdateModule {
    try {
    if (Test-Path $moduleDirectory) {
    Remove-Item $moduleDirectory -Force -Recurse
    }
    if (Test-Path $moduleZipFilePath) {
    Remove-Item $moduleZipFilePath -Force
    }
    } catch {
    Write-Host "Cleaning Windows update module resulted in error: $($_)"
    }
    }

    function Get-WindowsUpdateModule {
    try {
    if ($region.StartsWith('cn-')) {
    $s3Location = 'https://s3.{0}.amazonaws.com.cn/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } elseif($region.StartsWith('us-gov-')) {
    $s3Location = 'https://s3-fips-{0}.amazonaws.com/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } elseif($region -eq 'us-east-1') {
    $s3Location = 'https://s3.amazonaws.com/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } else {
    $s3Location = 'https://aws-windows-downloads-{0}.s3.amazonaws.com/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    }

    $moduleS3Location = $s3Location -f $region, $zipFilename
    $moduleLocalPath = Join-Path $tempPath -ChildPath $zipFilename
    (New-Object Net.WebClient).DownloadFile($moduleS3Location, $moduleLocalPath)

    $fileStream = New-Object System.IO.FileStream($moduleLocalPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $sha256 = [System.Security.Cryptography.HashAlgorithm]::Create('System.Security.Cryptography.SHA256CryptoServiceProvider')
    $currentHash = [System.BitConverter]::ToString($sha256.ComputeHash($fileStream), 0).Replace('-', '').ToLowerInvariant()
    $sha256.Dispose()
    $fileStream.Dispose()

    if ($currentHash -ne 'B32D397AAA312E5EB0B2E0E0BC7146FB716AED3867A73FA650E0F222DF1079AE')
    {
    Write-Host 'The SHA hash of the module does not match the expected value.'
    Exit -1
    }
    } catch {
    Write-Host ('Error encountered while getting the module: {0}.' -f $_.Exception.Message)
    Exit -1
    }
    }

    function Expand-WindowsUpdateModule {
    try {
    [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null
    $zip = [System.IO.Compression.ZipFile]::OpenRead($moduleZipFilePath)
    foreach ($item in $zip.Entries) {
    $extractPath = Join-Path $tempPath -ChildPath $item.FullName
    if ($item.Length -eq 0) {
    if (-not (Test-Path $extractPath)) {
    New-Item $extractPath -ItemType Directory | Out-Null
    }
    } else {
    $parentPath = Split-Path $extractPath
    if (-not (Test-Path $parentPath)) {
    New-Item $parentPath -ItemType Directory | Out-Null
    }
    [System.IO.Compression.ZipFileExtensions]::ExtractToFile($item, $extractPath, $true)
    }
    }
    } catch {
    Write-Host ('Error encountered when extracting module file: {0}.' -f $_.Exception.Message)
    Exit -1
    } finally {
    $zip.Dispose()
    }
    }

    function Invoke-UpdateEC2Config {
    Import-Module $moduleManifestPath
    $command = "Install-AwsUwiEC2Config -Region $region"
    if($id) { $command += " -Id $($id)"}
    Invoke-Expression $command
    }

    function Invoke-UpdateEC2Launch {
    Import-Module $moduleManifestPath
    $command = 'Install-AwsUwiEC2Launch'
    if($id) { $command += " -Id $($id)" }
    Invoke-Expression $command
    }

    Main
    - name: UpdateSSMAgent
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 600
    inputs:
    DocumentName: AWS-UpdateSSMAgent
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    - name: UpdateAWSPVDriver
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 600
    inputs:
    DocumentName: AWS-ConfigureAWSPackage
    InstanceIds:
    - "{{LaunchInstance.InstanceIds}}"
    Parameters:
    name: AWSPVDriver
    action: Install
    - name: UpdateAWSEnaNetworkDriver
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 600
    inputs:
    DocumentName: AWS-ConfigureAWSPackage
    InstanceIds:
    - "{{LaunchInstance.InstanceIds}}"
    Parameters:
    name: AwsEnaNetworkDriver
    action: Install
    - name: InstallWindowsUpdates
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 14400
    inputs:
    DocumentName: AWS-InstallWindowsUpdates
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    Parameters:
    Action: Install
    IncludeKbs: "{{ IncludeKbs }}"
    ExcludeKbs: "{{ ExcludeKbs }}"
    Categories: "{{ Categories }}"
    SeverityLevels: "{{ SeverityLevels }}"
    PublishedDaysOld: "{{ PublishedDaysOld }}"
    PublishedDateAfter: "{{ PublishedDateAfter }}"
    PublishedDateBefore: "{{ PublishedDateBefore }}"
    - name: RunPostUpdateScript
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 1800
    inputs:
    DocumentName: AWS-RunPowerShellScript
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    Parameters:
    commands: "{{ PostUpdateScript }}"
    - name: RunSysprepGeneralize
    action: aws:runCommand
    maxAttempts: 3
    onFailure: Abort
    timeoutSeconds: 7200
    inputs:
    DocumentName: AWS-RunPowerShellScript
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    Parameters:
    commands: |
    $moduleName = 'AWSUpdateWindowsInstance'
    $zipFilename = 'AWSUpdateWindowsInstance.zip'
    $tempPath = $env:TEMP
    $moduleDirectory = Join-Path $tempPath -ChildPath $moduleName
    $moduleZipFilePath = Join-Path $tempPath -ChildPath $zipFilename
    $moduleManifestPath = Join-Path $moduleDirectory -ChildPath ('{0}.psd1' -f $moduleName)
    $id = '{{automation:EXECUTION_ID}}'
    function Main {
    Test-PreCondition
    Clear-WindowsUpdateModule
    Get-WindowsUpdateModule
    Expand-WindowsUpdateModule
    Invoke-RunSysprep
    }
    function Test-PreCondition {
    $osversion = [Environment]::OSVersion.Version
    if ($osversion.Major -le 5) {
    Write-Host 'This document is not supported on Windows Server 2003 or earlier.'
    Exit -1
    }
    if ($osversion.Version -ge '10.0') {
    $sku = (Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU
    if ($sku -eq 143 -or $sku -eq 144) {
    Write-Host 'This document is not supported on Windows 2016 Nano Server.'
    Exit -1
    }
    }
    $ssmAgentService = Get-ItemProperty 'HKLM:SYSTEM\\CurrentControlSet\\Services\\AmazonSSMAgent\\' -ErrorAction SilentlyContinue
    if (-not $ssmAgentService -or $ssmAgentService.Version -lt '2.0.533.0')
    {
    Write-Host 'This document is not supported with SSM Agent version less than 2.0.533.0.'
    exit -1
    }
    }
    function Clear-WindowsUpdateModule {
    try {
    if (Test-Path $moduleDirectory) {
    Remove-Item $moduleDirectory -Force -Recurse
    }
    if (Test-Path $moduleZipFilePath) {
    Remove-Item $moduleZipFilePath -Force
    }
    } catch {
    Write-Host "Cleaning Windows update module resulted in error: $($_)"
    }
    }
    function Get-WindowsUpdateModule {
    try {
    $region = $env:AWS_SSM_REGION_NAME
    if ($region.StartsWith('cn-')) {
    $s3Location = 'https://s3.{0}.amazonaws.com.cn/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } elseif($region.StartsWith('us-gov-')) {
    $s3Location = 'https://s3-fips-{0}.amazonaws.com/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } elseif($region -eq 'us-east-1') {
    $s3Location = 'https://s3.amazonaws.com/aws-windows-downloads-{0}/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    } else {
    $s3Location = 'https://aws-windows-downloads-{0}.s3.amazonaws.com/PSModules/AWSUpdateWindowsInstance/Latest/{1}'
    }
    $moduleS3Location = $s3Location -f $region, $zipFilename
    $moduleLocalPath = Join-Path $tempPath -ChildPath $zipFilename
    (New-Object Net.WebClient).DownloadFile($moduleS3Location, $moduleLocalPath)
    $fileStream = New-Object System.IO.FileStream($moduleLocalPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $sha256 = [System.Security.Cryptography.HashAlgorithm]::Create('System.Security.Cryptography.SHA256CryptoServiceProvider')
    $currentHash = [System.BitConverter]::ToString($sha256.ComputeHash($fileStream), 0).Replace('-', '').ToLowerInvariant()
    $sha256.Dispose()
    $fileStream.Dispose()
    if ($currentHash -ne 'B32D397AAA312E5EB0B2E0E0BC7146FB716AED3867A73FA650E0F222DF1079AE')
    {
    Write-Host 'The SHA hash of the module does not match the expected value.'
    Exit -1
    }
    } catch {
    Write-Host ('Error encountered while getting the module: {0}.' -f $_.Exception.Message)
    Exit -1
    }
    }
    function Expand-WindowsUpdateModule {
    try {
    [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') | Out-Null
    $zip = [System.IO.Compression.ZipFile]::OpenRead($moduleZipFilePath)
    foreach ($item in $zip.Entries) {
    $extractPath = Join-Path $tempPath -ChildPath $item.FullName
    if ($item.Length -eq 0) {
    if (-not (Test-Path $extractPath)) {
    New-Item $extractPath -ItemType Directory | Out-Null
    }
    } else {
    $parentPath = Split-Path $extractPath
    if (-not (Test-Path $parentPath)) {
    New-Item $parentPath -ItemType Directory | Out-Null
    }
    [System.IO.Compression.ZipFileExtensions]::ExtractToFile($item, $extractPath, $true)
    }
    }
    } catch {
    Write-Host ('Error encountered when extracting module file: {0}.' -f $_.Exception.Message)
    Exit -1
    } finally {
    $zip.Dispose()
    }
    }
    function Invoke-RunSysprep {
    Import-Module $moduleManifestPath
    $command = 'Start-AwsUwiSysprep'
    if($id) { $command += " -Id $($id)"}
    Invoke-Expression $command
    }
    Main
    - name: StopInstance
    action: aws:changeInstanceState
    maxAttempts: 3
    timeoutSeconds: 7200
    onFailure: Abort
    inputs:
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    CheckStateOnly: false
    DesiredState: stopped
    - name: CreateImage
    action: aws:createImage
    maxAttempts: 3
    onFailure: Abort
    inputs:
    InstanceId: "{{ LaunchInstance.InstanceIds }}"
    ImageName: "{{ TargetAmiName }}"
    NoReboot: true
    ImageDescription: Test CreateImage Description
    - name: TerminateInstance
    action: aws:changeInstanceState
    maxAttempts: 3
    onFailure: Abort
    inputs:
    InstanceIds:
    - "{{ LaunchInstance.InstanceIds }}"
    DesiredState: terminated
    outputs:
    - CreateImage.ImageId