<powershell>
# Chef Globals and constants
$chefRegion = "us-west-2"
$chefVersion = "14.5.33"
$chefCAURL = "https://opsworks-cm-${chefRegion}-prod-default-assets.s3.amazonaws.com/misc/opsworks-cm-ca-2016-root.pem"

# Chef Paths and constants
$chefConfig = "C:\chef"
$chefBinPath = "C:\opscode\chef\bin"

$chefCA = "C:\Users\Administrator\.chef\trusted_certs\"
$chefCAPem = Join-Path $chefCA "opsworks-cm-ca-2016-root.pem"
$chefAttributesFile = Join-Path $chefConfig "attributes.json"

# Node Configuration
$chefEnvironment = "<%= chef_environment %>"
$chefRunList = "<%= chef_runlist %>"
$chefAttributes = @"
<%= "\n#{JSON.pretty_generate(chef_attributes)}\n" %>
"@
$chefEnvironmentDir = Join-Path $chefConfig 'environments'
$chefEnvironmentFile = Join-Path $chefEnvironmentDir "${chefEnvironment}.json"
$hostnamePrefix = "<%= host_prefix %>"
$cookbookName = $chefRunList.Split(',')[0].Split('::')[0].Replace('recipe[','')
$cookbooksArtifactBucket = "generic-packages-default-uswest2-6d6c0de383565712c723c750188799"
<% if defined?(solo_tarball_version) %>
$cookbooksArtifactKey = [IO.Path]::Combine('chef-solo', $cookbookName, 'cookbooks-<%= solo_tarball_version %>.tar.gz')
<% else %>
$cookbooksArtifactKey = [IO.Path]::Combine('chef-solo', $cookbookName, 'cookbooks-latest.tar.gz')
<% end %>
$cookbooksArtifactLocalPath = Join-Path $chefConfig "cookbooks-latest.tar.gz"
# Helpers
function Get-InstanceId {
    (Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/instance-id).substring(2)
}

function Get-Hostname {
    $availabilityZone = Invoke-RestMethod -Uri http://169.254.169.254/latest/meta-data/placement/availability-zone

    $region = $availabilityZone.substring(0, $availabilityZone.length-1)
    $az = $availabilityZone.substring($availabilityZone.length-1)

    $regions = @{
        "ap-northeast-1" = "nrt";
        "ap-northeast-2" = "icn";
        "ap-south-1" = "bom";
        "ap-southeast-1" = "sin";
        "ap-southeast-2" = "syd";
        "ca-central-1" = "yul";
        "eu-central-1" = "fra";
        "eu-west-1" = "dub";
        "eu-west-2" = "lhr";
        "sa-east-1" = "gru";
        "us-east-1" = "iad";
        "us-east-2" = "cle";
        "us-west-1" = "sjc";
        "us-west-2" = "pdx";
    }

    "${hostnamePrefix}-$($regions[$region])-${az}-$(Get-InstanceId)"
}

# Calculated values
$currentHostname = hostname
$desiredHostname = Get-Hostname

# On boot script
$chefOnBootScript = @'
    sleep 15
    $logFile = Join-Path $env:temp 'chef-onboot.log'
    $hostname = (hostname).ToLower()
    if ( !($hostname -like "win-*") -And !($hostname -like "ip-*") -And !($hostname -like "ec2amaz-*" )) {
        Write-Host "Hostname looks valid, running chef in 5 seconds."
        sleep 5
        C:\opscode\chef\bin\chef-solo-run.ps1 > $logFile 2>&1
    } else {
        Write-Host "Hostname looks invalid, skipping chef run."
        "On Boot Script executed, but hostname was invalid: ${hostname}" | Out-File -Encoding UTF8 -filepath $logFile
    }
'@

# Steps
function Install-Prerequisites {
    #Chef
    . { Invoke-WebRequest -useb https://omnitruck.chef.io/install.ps1 } | Invoke-Expression; install -version $chefVersion -project chef

    # Guarentee a few paths exist
    if (!(Test-Path -Path $chefCA)) { New-Item $chefCA -type Directory }
    if (!(Test-Path -Path $chefConfig)) { New-Item $chefConfig -type Directory }

    # CA
    (New-Object System.Net.WebClient).DownloadFile($chefCAURL, $chefCAPem)
}

function Get-Cookbooks {
    Read-S3Object -BucketName $cookbooksArtifactBucket -Key $cookbooksArtifactKey -File $cookbooksArtifactLocalPath -Region $chefRegion
    Invoke-Expression "${chefBinPath}\tar.exe -xzf ${cookbooksArtifactLocalPath} -C ${chefConfig}"
}

function Write-ChefConfiguration {

    # attributes.json
    # This method is required because ruby's JSON parsing chokes on the UTF8 BOM.
    $encoding = New-Object System.Text.UTF8Encoding($false)
    [IO.File]::WriteAllLines($chefAttributesFile, $chefAttributes, $encoding)

    # environment file
    New-Item -Path $chefEnvironmentDir -ItemType Directory -Force
    [IO.File]::WriteAllLines($chefEnvironmentFile, '{}', $encoding)

    # chef-solo-run.ps1
    $chefCommand = "cd ${chefConfig}; chef-solo.bat -j '${chefAttributesFile}' -o '${chefRunList}'"
    if ($chefEnvironment) { $chefCommand += " -E '${chefEnvironment}'" }
    $fileContent = @"
    cd ${chefConfig}
    ${chefCommand}
"@
    Set-Content -Encoding UTF8 $(Join-Path $chefBinPath 'chef-solo-run.ps1') $fileContent

    # chef-onboot.ps1
    $chefOnBootPath = Join-Path $chefBinPath 'chef-onboot.ps1'
    Set-Content -Encoding UTF8 $chefOnBootPath $chefOnBootScript

    $trigger = New-ScheduledTaskTrigger -AtStartup
    $action = New-ScheduledTaskAction -Execute "c:\windows\system32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-ExecutionPolicy Bypass -Command $chefOnBootPath" -WorkingDirectory $chefBinPath
    $settings = New-ScheduledTaskSettingsSet -DontStopOnIdleEnd -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 5 -RestartInterval 00:01:00
    $principal = New-ScheduledTaskPrincipal -GroupId "NT AUTHORITY\SYSTEM" -RunLevel "Highest"

    $existingTask = Get-ScheduledTask -TaskName "ChefOnBoot" -ErrorAction SilentlyContinue
    if ($existingTask) { Unregister-ScheduledTask -TaskName "ChefOnBoot" -Confirm:$false }
    Register-ScheduledTask "ChefOnBoot" -Action $action -Trigger $trigger -Principal $principal -Settings $settings
}

function Update-Hostname {
    if ($desiredHostname.toLower() -ne $currentHostname.toLower()) {
        Write-Host "Renaming computer and restarting..."
        Rename-Computer -NewName $desiredHostname -Restart -Force
    }
}

Install-Prerequisites
Get-Cookbooks
Write-ChefConfiguration
Update-Hostname
</powershell>
