tests/build/Initialize-VstsAgentOnWindowsServerCoreContainer.ps1
################################################################################################################## # Script Disclaimer ################################################################################################################## # This script is not supported under any Microsoft standard support program or service. # This script is provided AS IS without warranty of any kind. # Microsoft disclaims all implied warranties including, without limitation, any implied warranties of # merchantability or of fitness for a particular purpose. The entire risk arising out of the use or # performance of this script and documentation remains with you. In no event shall Microsoft, its authors, # or anyone else involved in the creation, production, or delivery of this script be liable for any damages # whatsoever (including, without limitation, damages for loss of business profits, business interruption, # loss of business information, or other pecuniary loss) arising out of the use of or inability to use # this script or documentation, even if Microsoft has been advised of the possibility of such damages. <# .SYNOPSIS This script creates ACI container instance(s) which run the Azure DevOps (formerly VSTS) Agent, alongside with the requested PowerShell modules, json2hcl and Terraform to enable Azure resource build automation from Azure DevOps CI/CD pipelines. .DESCRIPTION This script is used as a wrapper of the container configuration script ("Install-VstsAgentOnWindowsServerCoreContainer.ps1") to create ACI container instance(s) which run the Azure DevOps Agent, alongside with the requested PowerShell modules, json2hcl and Terraform to enable Azure resource build automation from Azure DevOps CI/CD pipelines. It copies the container configuration script to a publicly available storage container of the requested Storage Account, it creates a new Resource Group (if one doesn't exist with the provided name), removes any pre-existing ACI containers with the same name, within the same Resource Group, then creates new ACI container instance(s) based on the provided names and invokes the container configuration script inside the container(s). This script is designed and tested to be run from Azure Cloud Shell. Prerequisites: - Azure Subscription, with an existing Storage Account - You need to have **admin rights** : - to create a storage container within the already existing Storage Account *- OR -* you need to have a storage container which has its public access type configured to the type of "Blob", - to create a new Resource Group *- OR -* an existing Resource Group for the Azure Container Instances, - to create resources in the selected Resource Group. - Azure DevOps account with the requested Agent Pool has to exist. - Permission in the Azure DevOps account to add Agents to the chosen Agent Pool. - A PAT token. .PARAMETER SubscriptionName Name of the Subscription. .PARAMETER ResourceGroupName Name of the Resource Group. .PARAMETER ContainerName Name of the ACI container(s). .PARAMETER ReplaceExistingContainer Switch to replace existing container(s) with the same name(s) provided. .PARAMETER MemoryInGB Amount of memory in GBs. .PARAMETER Cpu Number of CPU cores. .PARAMETER Location Region of the Azure resources. .PARAMETER StorageAccountName Name of the Storage Account to upload the script file to, which is then invoke by this wrapper. .PARAMETER StorageContainerName Name of the storage container to upload the script file to be invoked by this wrapper. .PARAMETER ScriptPublicUrl Publicly available URL of the internal script file to be invoked by this wrapper. This can be used optionally, instead of defining the Storage Account name. You need to make the internal script available on this URL as a prerequisite. .PARAMETER ScriptFileName Name of the script file to invoke by this wrapper. .PARAMETER VSTSAccountName Name of the Azure Devops account - formerly Visual Studio Team Services (VSTS) account, e.g. https://<Azure DevOps Account Name>.visualstudio.com - OR - https://dev.azure.com/<Azure DevOps Account Name>/ .PARAMETER PATToken PPAT token generated by the user who is configuring the container to be used by Azure Devops. .PARAMETER PoolName Name of the Agent pool. It defaults to the "Default" pool when not defined. .PARAMETER RequiredPowerShellModules List of the required PowerShell modules, e.g. Az, AzureAD, Pester .PARAMETER ContainerImage Fully qualified name of the container image, optionally including tags. .PARAMETER AcrPassword Access password to the Azure Container Registry (ACR) .PARAMETER InstallAzureCli Switch to define whether or not you want to install the Azure CLI on your container. .PARAMETER InstallPowerShellCore Switch to define whether or not you want to install Azure PowerShell Core on your container. .PARAMETER UseChocolatey Switch to define whether or not Chocolatey should be used to install the supported components .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region>" -StorageAccountName "<storage account name>" -VSTSAccountName "<azure devops account name>" -PATToken "<PAT token>" This uploads the container configuration script to the default "publicvstsscript" storage container of the requested Storage Account and then creates 2 Azure Container Instances, with the default settings (Default Agent Pool 1 GB RAM, 1 CPU core, PowerShell modules installed: "Az", "AzureAD", "Pester"). .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region>" -VSTSAccountName "<azure devops account name>" -PATToken "<PAT token>" This downloads the container configuration script directly from its default location on GitHub, and then creates 2 Azure Container Instances, with the default settings (Default Agent Pool 1 GB RAM, 1 CPU core, PowerShell modules installed: "Az", "AzureAD", "Pester"). .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region>" -VSTSAccountName "<azure devops account name>" -PATToken "<PAT token>" -ScriptPublicUrl "<public URL of the internal config script>" This downloads the container configuration script directly from the provided location (this can be anything, e.g. GitHub, a public Storage Account, or any publicly available URL), and then creates 2 Azure Container Instances, with the default settings (Default Agent Pool 1 GB RAM, 1 CPU core, PowerShell modules installed: "Az", "AzureAD", "Pester"). .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region>" -StorageAccountName "<storage account name>" -StorageContainerName "publicvstsscript" -MemoryInGB 1 -Cpu 1 -ScriptFileName "Install-VstsAgentOnWindowsServerCoreContainer.ps1" -VSTSAccountName "<Azure DevOps Account name>" -PATToken "<PAT token>" -PoolName "<Azure DevOps Agent Pool name>" -RequiredPowerShellModules "Az", "AzureAD", "Pester" This installs 2 Azure Container Instances with all the possible values manually defined. .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region 2>" -StorageAccountName "<storage account name>" -VSTSAccountName "<azure devops account name>" -PATToken "<PAT token>" -PoolName "<agent pool name>" -ReplaceExistingContainer This removes any existing ACI containers with the provided names, then creates new ones with the requested values. .EXAMPLE .\Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 -SubscriptionName "<subscription name>" -ResourceGroupName "<resource group name>" -ContainerName "<container 1 name>", "<container 2 name>" -Location "<azure region>" -StorageAccountName "<storage account name>" -VSTSAccountName "<azure devops account name>" -PATToken "<PAT token>" -ContainerImage <myacr.azurecr.io/myrepo/myimage:v1> -AcrPassword <ACR password> This uploads the container configuration script to the default "publicvstsscript" storage container of the requested Storage Account and then creates 2 Azure Container Instances, based on the custom image provided, with the default settings (Default Agent Pool 1 GB RAM, 1 CPU core, PowerShell modules installed: "Az", "AzureAD", "Pester"). .INPUTS <none> .OUTPUTS <none> .NOTES Version: 1.0 Author: Mate Barabas Creation Date: 2018-08-29 #> param( [Parameter(Mandatory = $true, HelpMessage = "Name of the Subscription.")] [ValidateNotNullOrEmpty()] [string]$SubscriptionName, [Parameter(Mandatory = $true, HelpMessage = "Name of the Resource Group.")] [ValidateNotNullOrEmpty()] [string]$ResourceGroupName, [Parameter(Mandatory = $false, HelpMessage = "Name of the ACI container(s).")] [ValidateNotNullOrEmpty()] [array]$ContainerName, [Parameter(Mandatory = $false, HelpMessage = "Switch to replace existing container(s) with the same name(s) provided.")] [ValidateNotNullOrEmpty()] [switch]$ReplaceExistingContainer, [Parameter(Mandatory = $false, HelpMessage = "Amount of memory in GBs.")] [ValidateNotNullOrEmpty()] [int]$MemoryInGB = 1, [Parameter(Mandatory = $false, HelpMessage = "Number of CPU cores.")] [ValidateNotNullOrEmpty()] [int]$Cpu = 1, [Parameter(Mandatory = $true, HelpMessage = "Region of the Azure resources.")] [ValidateNotNullOrEmpty()] [string]$Location, [Parameter(Mandatory = $false, HelpMessage = "Name of the Storage Account to upload the script file to, which is then invoke by this wrapper.")] [ValidateNotNullOrEmpty()] [string]$StorageAccountName, [Parameter(Mandatory = $false, HelpMessage = "Name of the Storage Account Resource Group")] [ValidateNotNullOrEmpty()] [string]$StorageAccountResourceGroupName, [Parameter(Mandatory = $false, HelpMessage = "Name of the storage container to upload the scriptfile to be invoked by this wrapper.")] [ValidateNotNullOrEmpty()] [string]$StorageContainerName = "publicvstsscript", [Parameter(Mandatory = $false, HelpMessage = "Publicly available URL of the internal script file to be invoked by this wrapper. This can be used optionally, instead of defining the Storage Account name. You need to make the internal script available on this URL as a prerequisite.")] [ValidateNotNullOrEmpty()] [string]$ScriptPublicUrl = "https://raw.githubusercontent.com/matebarabas/azure/master/scripts/AzureDevOpsAgentOnACI/Install-VstsAgentOnWindowsServerCoreContainer.ps1", [Parameter(Mandatory = $false, HelpMessage = "Name of the script file to invoke by this wrapper.")] [ValidateNotNullOrEmpty()] [string]$ScriptFileName = "Install-VstsAgentOnWindowsServerCoreContainer.ps1", [Parameter(Mandatory = $false, HelpMessage = "Path to the file - if script not running in same directory")] [ValidateNotNullOrEmpty()] [string]$ScriptFilePath = ".\", [Parameter(Mandatory = $true, HelpMessage = "Name of the Visual Studio Team Services Account (VSTS), e.g. https://<VSTSAccountName>.visualstudio.com")] [ValidateNotNullOrEmpty()] [string]$VSTSAccountName, [Parameter(Mandatory = $true, HelpMessage = "PAT token generated by the user who is configuring the container to be used by VSTS.")] [ValidateNotNullOrEmpty()] [string]$PATToken, [Parameter(Mandatory = $false, HelpMessage = "Name of the Agent pool. It defaults to the ""Default"" pool when not defined.")] [ValidateNotNullOrEmpty()] [string]$PoolName = "Default", [Parameter(Mandatory = $false, HelpMessage = "List of the required PowerShell modules, e.g. Az, AzureAD, Pester")] [ValidateNotNullOrEmpty()] [array]$RequiredPowerShellModules = @("Az", "AzureAD", "Pester"), [Parameter(Mandatory = $false, HelpMessage = "Access password to the Azure Container Registry (ACR)")] [ValidateNotNullOrEmpty()] [string]$AcrPassword, [Parameter(Mandatory = $false, HelpMessage = "Fully qualified name of the container image, optionally including tags.")] [ValidateNotNullOrEmpty()] [string]$ContainerImage, # e.g. "microsoft/dotnet-framework:4.7.2-runtime-20190212-windowsservercore-ltsc2016" or "microsoft/windowsservercore:10.0.14393.2791" or "mcr.microsoft.com/windows/servercore:ltsc2016" [Parameter(Mandatory = $false, HelpMessage = "Switch to define whether or not you want to install the Azure CLI on your container.")] [ValidateNotNullOrEmpty()] [bool]$InstallAzureCli = $false, [Parameter(Mandatory = $false, HelpMessage = "Switch to define whether or not you want to install Azure PowerShell Core on your container.")] [ValidateNotNullOrEmpty()] [bool]$InstallPowerShellCore = $false, [Parameter(Mandatory = $false, HelpMessage = "Switch to define whether or not Chocolatey should be used to install the supported components")] [ValidateNotNullOrEmpty()] [bool]$UseChocolatey = $false ) #region Functions function Set-AzureContext { param ( [Parameter(Mandatory = $false)][string]$SubscriptionName ) # Select the desired Subscription based on the Subscription name provided if ($SubscriptionName) { $Subscription = (Get-AzureRmSubscription | Where-Object { $_.Name -eq $SubscriptionName }) if (-not $Subscription) { Write-Error "There's no Subscription available with the provided name." return } else { $SubscriptionId = $Subscription.Id Select-AzureRmSubscription -SubscriptionId $SubscriptionId | Out-Null Write-Output "The following subscription was selected: ""$SubscriptionName""" } } # If no Subscription name was provided select the active Subscription based on the existing context else { $SubscriptionName = (Get-AzureRmContext).Subscription.Name $Subscription = (Get-AzureRmSubscription | Where-Object { $_.Name -eq $SubscriptionName }) Write-Output "The following subscription was selected: ""$SubscriptionName""" } if ($Subscription.Count -gt 1) { Write-Error "You have more then 1 Subscription with the same name. Exiting..." return } } if(-not $StorageAccountResourceGroupName){ $StorageAccountResourceGroupName = $ResourceGroupName } function Copy-ScriptToStorageAccount { param ( [Parameter(Mandatory = $true)][string]$StorageAccountName, [Parameter(Mandatory = $true)][string]$StorageAccountResourceGroupName, [Parameter(Mandatory = $false)][string]$StorageContainerName = "publicvstsscript", [Parameter(Mandatory = $false)][string]$ScriptFileName = "Install-VstsAgentOnWindowsServerCoreContainer.ps1" ) # Check if the Install-VstsAgentOnWindowsServerCoreContainer.ps1 script exists within the same folder if (-not (Get-Item -Path "$ScriptFilePath\$ScriptFileName" -ErrorAction SilentlyContinue)) { Write-Error "The script to be uploaded to the Storage Account ($ScriptFilePath\$ScriptFileName) does not exist in the same folder. Make sure that it is copied to the same folder along with the Initialize-VstsAgentOnWindowsServerCoreContainer.ps1 script. Exiting..." break } if(-not (Get-AzureRmStorageAccount -ResourceGroupName $StorageAccountResourceGroupName -Name $StorageAccountName -ErrorAction SilentlyContinue)){ New-AzureRmStorageAccount -ResourceGroupName $StorageAccountResourceGroupName -Name $StorageAccountName -Location "West Europe" -SkuName "Standard_GRS" } # Getting Storage Account Key Write-Output "Getting Storage Account Key..." $StorageKey = Get-AzureRmStorageAccountKey -ResourceGroupName $StorageAccountResourceGroupName -Name $StorageAccountName # Setting storage context Write-Output "Setting storage context..." $ctx = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $StorageKey[0].Value # Checking if the container exists, creating if it doesn't $StorageContainer = Get-AzureStorageContainer -Context $ctx -Name $StorageContainerName -ErrorAction SilentlyContinue if (-not $StorageContainer) { Write-Output "Storage container ""$StorageContainerName"" does not exist in the ""$StorageAccountName"" Storage Account. Creating storage container with public access..." New-AzureStorageContainer -Context $ctx -Name $StorageContainerName -Permission Blob | Out-Null Write-Output "Wait 10 seconds..." Start-Sleep -Seconds 10 } else { Write-Output "Storage container ""$StorageContainerName"" exists in the ""$StorageAccountName"" Storage Account." $PublicAccess = (Get-AzureStorageContainerAcl -Name $StorageContainerName -Context $ctx).PublicAccess if ($PublicAccess -ne "Blob") { Write-Output "Public Access was configured to $PublicAccess. Resetting it to ""Blob"" (public access)." Set-AzureStorageContainerAcl -Name $StorageContainerName -Permission Blob Write-Output "Wait 10 seconds..." Start-Sleep -Seconds 10 } } # Check if container creation was successful $StorageContainer = Get-AzureStorageContainer -Context $ctx -Name $StorageContainerName -ErrorAction SilentlyContinue if (-not $StorageContainer) { Write-Error "Storage container ($StorageContainerName) could not be created in the $StorageAccountName Storage Account. Exiting..." break } # Uploading CSV file Write-Output "Uploading the Install-VstsAgentOnWindowsServerCoreContainer.ps1 script to the Storage Account..." Set-AzureStorageBlobContent -Container $StorageContainerName -File "$ScriptFilePath\$ScriptFileName" -Blob $ScriptFileName -context $ctx -Force | Out-Null # Checking success $Blob = Get-AzureStorageBlob -Context $ctx -Container $StorageContainerName -Blob $ScriptFileName -ErrorAction SilentlyContinue if ($Blob) { Write-Output "The script file ($ScriptFileName) was successfully uploaded to the ""$StorageContainerName"" container of the ""$StorageAccountName"" Storage Account in the ""$StorageAccountResourceGroupName"" Resource Group." } else { Write-Error "The script file ($ScriptFileName) could not be uploaded to the ""$StorageContainerName"" container of the ""$StorageAccountName"" Storage Account in the ""$StorageAccountResourceGroupName"" Resource Group. Exiting..." break } } function New-Container { param ( [Parameter(Mandatory = $false)][string]$AcrPassword, [Parameter(Mandatory = $true)][string]$ContainerImage ) # Create & Install containers if ($StorageAccountName) #if you want to upload the internal script to a Storage Account { $ScriptURL = "https://$StorageAccountName.blob.core.windows.net/$StorageContainerName/$ScriptFileName" } else # if you want to use the internal script from a public location (e.g. GitHub) { $ScriptURL = $ScriptPublicUrl } foreach ($Name in $ContainerName) { $CanCreateContainerWithProvidedName = $true $ExistingContainer = Get-AzureRmContainerGroup -ResourceGroupName $ResourceGroupName -Name $Name -ErrorAction SilentlyContinue if ($ExistingContainer) { Write-Warning "ACI container with the requested name ($Name) already exists in the given Resource Group ($ResourceGroupName)." $CanCreateContainerWithProvidedName = $false if ($ReplaceExistingContainer.IsPresent) { Write-Warning "Deleting the existing container instance ($Name)..." Remove-AzureRmContainerGroup -ResourceGroupName $ResourceGroupName -Name $Name -Confirm:$false Write-Warning "Unregistering existing container instance ($Name) from Agent pool ($PoolName)..." Remove-AzureDevOpsAgentFromPool -PatToken $PatToken -AzureDevOpsAccountName $VSTSAccountName -AgentPoolName $PoolName -ContainerName $ContainerName $ContainerDeletetionWasRequired = $true $CanCreateContainerWithProvidedName = $true Write-Output "Waiting 10 seconds..." Start-Sleep -Seconds 10 } else { Write-Output "Existing container with the name of $Name is being kept." } } if ($CanCreateContainerWithProvidedName) { $CreateAtLeastOneContainer = $true Write-Output "Creating ACI container ($Name) with the image of $ContainerImage..." Write-Output "Instantiating a container can take a few minutes, depending on the image size and whether or not the container image is cached in the ACI platform." if ($RequiredPowerShellModules.Count -gt 1) { $RequiredPowerShellModules = $RequiredPowerShellModules -join "," } if ($AcrPassword) { $SecPasswd = ConvertTo-SecureString $AcrPassword -AsPlainText -Force $RegistryName = $ContainerImage.Split(".")[0] $MyCred = New-Object System.Management.Automation.PSCredential ($RegistryName, $SecPasswd) if ($PSVersionTable.PSEdition -eq "Core") { # The AZ CLI has to be used, as the required -Command parameter is not available in the core version of the related AzureRM PowerShell module in Azure Cloud Shell. # When running in Cloud Shell, login is not required (has already happened) if ($null -ne $env:ACC_CLOUD) { az container create --resource-group $ResourceGroupName --name $Name --image $ContainerImage --registry-password $AcrPassword --location $Location --os-type Windows --cpu $Cpu --memory $MemoryInGB --restart-policy Always --command-line "powershell Start-Sleep -Seconds 20; Invoke-WebRequest -Uri $ScriptURL -OutFile $ScriptFileName -UseBasicParsing; & .\\$ScriptFileName -VSTSAccountName $VSTSAccountName -PATToken $PATToken -AgentNamePrefix $Name -PoolName $PoolName -InstallPowerShellCore $InstallPowerShellCore -InstallAzureCli $InstallAzureCli -UseChocolatey $UseChocolatey -RequiredPowerShellModules $RequiredPowerShellModules" --subscription $SubscriptionName --output none } } elseif ($PSVersionTable.PSEdition -eq "Desktop") { # Alternative option, using AzureRM PowerShell commandlet (this doesn't work in Azure Cloud Shell, as the -Command parameter is not available in the core version of this cmdlet) New-AzureRmContainerGroup -ResourceGroupName $ResourceGroupName ` -Name $Name ` -Image $ContainerImage ` -Location $Location ` -OsType Windows ` -Cpu $Cpu ` -MemoryInGB $MemoryInGB ` -RestartPolicy Always ` -RegistryCredential $MyCred ` -Command "powershell Start-Sleep -Seconds 20; Invoke-WebRequest -Uri $ScriptURL -OutFile $ScriptFileName -UseBasicParsing; & .\$ScriptFileName -VSTSAccountName $VSTSAccountName -PATToken $PATToken -AgentNamePrefix $Name -PoolName $PoolName -RequiredPowerShellModules $RequiredPowerShellModules -InstallAzureCli $InstallAzureCli -UseChocolatey $UseChocolatey -InstallPowerShellCore $InstallPowerShellCore" | Out-null } else { Write-Error "PowerShell version could not be defined. Exiting..." return } } else { if ($PSVersionTable.PSEdition -eq "Core") { # The AZ CLI has to be used, as the required -Command parameter is note available in the core version of the related AzureRM PowerShell module in Azure Cloud Shell. # When running in Cloud Shell, login is not required (has already happened) if ($null -ne $env:ACC_CLOUD) { az container create --resource-group $ResourceGroupName --name $Name --image $ContainerImage --location $Location --os-type Windows --cpu $Cpu --memory $MemoryInGB --restart-policy Always --command-line "powershell Start-Sleep -Seconds 20; Invoke-WebRequest -Uri $ScriptURL -OutFile $ScriptFileName -UseBasicParsing; & .\\$ScriptFileName -VSTSAccountName $VSTSAccountName -PATToken $PATToken -AgentNamePrefix $Name -PoolName $PoolName -RequiredPowerShellModules $RequiredPowerShellModules -InstallAzureCli $InstallAzureCli -UseChocolatey $UseChocolatey -InstallPowerShellCore $InstallPowerShellCore" --subscription $SubscriptionName --output none } } elseif ($PSVersionTable.PSEdition -eq "Desktop") { # Alternative option, using AzureRM PowerShell commandlet (this doesn't work in Azure Cloud Shell, as the -Command parameter is not available in the core version of this cmdlet) New-AzureRmContainerGroup -ResourceGroupName $ResourceGroupName ` -Name $Name ` -Image $ContainerImage ` -Location $Location ` -OsType Windows ` -Cpu $Cpu ` -MemoryInGB $MemoryInGB ` -RestartPolicy Always ` -Command "powershell Start-Sleep -Seconds 20; Invoke-WebRequest -Uri $ScriptURL -OutFile $ScriptFileName -UseBasicParsing; & .\$ScriptFileName -VSTSAccountName $VSTSAccountName -PATToken $PATToken -AgentNamePrefix $Name -PoolName $PoolName -RequiredPowerShellModules $RequiredPowerShellModules -InstallAzureCli $InstallAzureCli -UseChocolatey $UseChocolatey -InstallPowerShellCore $InstallPowerShellCore" | Out-null } else { Write-Error "PowerShell version could not be defined. Exiting..." return } } } else { Write-Warning "ACI container could not be created, as there's another container instance with the same name in the same Resource Group. If you want to remove the existing intance first, run the script again by using the -ReplaceExistingContainer switch." } } if ($CreateAtLeastOneContainer) { Write-Output "ACI container creation tasks have been submitted. When using a cached image, it usually takes about 10 minutes to fully provision a container." Write-Output "New ACI container(s) are being built..." # Periodically check if all containers have been configured $ConfiguredContainers = @() $NotificationTracker = @{ } while ($ConfiguredContainers.Count -ne $ContainerName.Count) { Start-Sleep -Seconds 10 foreach ($Name in $ContainerName) { if ($ConfiguredContainers -notcontains $Name) { $LogEntries = Get-AzureRmContainerInstanceLog -ResourceGroupName $ResourceGroupName -ContainerGroupName $Name -ErrorAction SilentlyContinue $RestartCount = (Get-AzureRmContainerGroup -ResourceGroupName $ResourceGroupName -Name $Name).Containers.restartcount # Show restart counts, avoid repeating the same entry if ($RestartCount -gt 0) { $Key = "$Name-$RestartCount" if ($NotificationTracker[$Key] -ne "MessageAlreadyShown") { Write-Output "The creation of the ACI container ""$Name"" was restarted $RestartCount time(s). Note that a few retries are acceptable, however each iteration increases the overall creation time." $NotificationTracker.Add($Key, "MessageAlreadyShown") } } # Trigger on success if ($LogEntries) { # Check if the last line of the log is "Container successfully configured." $Success = $LogEntries.Split("`n")[-4] -eq "Container successfully configured." # Do NOT change this value, as the wrapper script is triggered based on this. if ($Success) { $ConfiguredContainers += $Name Write-Output "ACI container ""$Name"" successfully configured" } } } } } # Print results if ($ConfiguredContainers.Count -eq $ContainerName.Count) { Write-Output "All requested containers have been successfully deployed and configured." Write-Output "Check if VSTS agents have been registered under the requested Agent Pool on the VSTS portal." $TimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date)) Write-Output "Finished at $(Get-Date)" Write-Output "It took $($TimeSpan.Minutes) minutes and $($TimeSpan.Seconds) seconds to initialize the requested containers." if ($ContainerDeletetionWasRequired) { Write-Warning "One or more containers have been deleted. " } } } else { Write-Output "No ACI containers have been created, as there were conflicts with existing intances and the -ReplaceExistingContainer was not used." } } function Get-LatestCachedImageVersion { # This function returns the name of the latest version of the # microsoft/windowsservercore image that is cached in the ACI platform # This is needed, because Microsoft no longer uses the ":latest" tag on their images # See more details here: https://techcommunity.microsoft.com/t5/Containers/Removing-the-latest-Tag-An-Update-on-MCR/ba-p/393045 # Authenticate to invoke Azure REST API $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile $currentAzureContext = Get-AzureRmContext $profileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($azProfile) $token = $profileClient.AcquireAccessToken($currentAzureContext.Tenant.TenantId) $accessToken = $token.AccessToken $RestCall = @{ Method = "Get" Uri = "https://management.azure.com/subscriptions/$($currentAzureContext.Subscription)/providers/Microsoft.ContainerInstance/locations/$Location/cachedImages?api-version=2018-10-01" Headers = @{ Authorization = "Bearer " + $AccessToken } } $result = Invoke-RestMethod @RestCall $LatestCachedImage = (($result.value | Where-Object { $_.image -like "microsoft/*dotnet*windowsservercore*" } | Sort-Object image)[-1]).image return $LatestCachedImage } function Remove-AzureDevOpsAgentFromPool { Param( [string]$PatToken, #Personal access token [string]$AzureDevOpsAccountName, #Azure DevOps account name [string]$AgentPoolName, #Azure DevOps Agent pool name [array]$ContainerName #Azure DevOps Agent pool name ) $base64AuthInfo = [System.Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PatToken)")) $Header = @{Authorization = ("Basic $base64AuthInfo") } # Get Agent Pool Write-Output "Getting Agent Pool ($AgentPoolName)..." $uri = "https://dev.azure.com/$AzureDevOpsAccountName/_apis/distributedtask/pools" $result = Invoke-RestMethod -Uri $uri -Method GET -ContentType "application/json" -Headers $Header $AgentPool = $result.value | Where-Object { $_.Name -eq "$AgentPoolName" } $AgentPoolId = $AgentPool.id if (-not $AgentPoolId) { Write-Error "The Agent Pool ($AgentPoolName) doesn't exist!" } else { # Get Agents Write-Output "Getting Agents from Pool..." $uri = "https://dev.azure.com/$AzureDevOpsAccountName/_apis/distributedtask/pools/$AgentPoolId/agents?includeCapabilities=false&includeAssignedRequest=true" $result = Invoke-RestMethod -Uri $uri -Method GET -ContentType "application/json" -Headers $Header $Agents = $result.value if (-not $Agents) { Write-Output "There are no Agents in this Agent Pool" } else { Write-Output "The following agents were found:" foreach ($Agent in $Agents) { $AgentName = $Agent.name $AgentStatus = $Agent.status Write-Output "$AgentName ($AgentStatus)" } # Delete Agent(s) from Pool foreach ($Name in $ContainerName) { Write-Output "Attempting to remove any Agent(s) that belonged to this ACI container: $Name..." foreach ($Agent in $Agents) { $AgentName = $Agent.name if ($AgentName -match "^$Name-") { # Delete Agent $AgentIds = $Agent.id foreach ($AgentId in $AgentIds) { Write-Output "Deleting Agent ($AgentName) - (Agent ID: $AgentId)..." $uri = "https://dev.azure.com/$AzureDevOpsAccountName/_apis/distributedtask/pools/$AgentPoolId/agents/$($AgentId)?api-version=5.0" $result = Invoke-RestMethod -Uri $uri -Method DELETE -ContentType "application/json" -Headers $Header # Check success Write-Output "Checking if the Agent ($AgentName) - (Agent ID: $AgentId) is still there..." $uri = "https://dev.azure.com/$AzureDevOpsAccountName/_apis/distributedtask/pools/$AgentPoolId/agents?includeCapabilities=false&includeAssignedRequest=true" $result = Invoke-RestMethod -Uri $uri -Method GET -ContentType "application/json" -Headers $Header $AgentStillThere = $result.value.id -contains $AgentId if ($AgentStillThere) { Write-Warning "Agent ($AgentName) could not be deleted. Don't forget to clean your Agent pool in Azure Devops (remove any agents that were created in a previous iteration and are now offline)!" } else { Write-Output "Agent ($AgentName) successfully deleted." } } } } } } } } #endregion #region Main # Report start time $StartDate = Get-Date Write-Output "Started at $StartDate..." # Login to Azure and select Subscription Set-AzureContext -SubscriptionName $SubscriptionName if ($StorageAccountName) { # Upload the configuration script to a Storage Account Copy-ScriptToStorageAccount -StorageAccountResourceGroupName $ResourceGroupName -StorageAccountName $StorageAccountName -StorageContainerName $StorageContainerName -ScriptFileName $ScriptFileName } Write-Output "Ready For creating Resource Group for containers" <# # Create Resource Group for containers if (-not (Get-AzureRmResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue)) { Write-Output "Resource Group ""$ResourceGroupName"" does not exist for ACI containers. Creating..." New-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location | Out-Null } #> Write-Output "Ready For creating containers" # Create containers if (-not $ContainerImage) { $ContainerImage = Get-LatestCachedImageVersion Write-Output "Container Image = $ContainerImage" } New-Container -ContainerImage $ContainerImage #endregion # SIG # Begin signature block # MIINEAYJKoZIhvcNAQcCoIINATCCDP0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUag+tQogpqd0EKtkbuuzLmtQU # xrSgggpSMIIFGjCCBAKgAwIBAgIQAsF1KHTVwoQxhSrYoGRpyjANBgkqhkiG9w0B # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz # c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTE3MDUwOTAwMDAwMFoXDTIwMDUx # MzEyMDAwMFowVzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMQ8wDQYD # VQQHEwZWaWVubmExETAPBgNVBAoTCGRiYXRvb2xzMREwDwYDVQQDEwhkYmF0b29s # czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI8ng7JxnekL0AO4qQgt # Kr6p3q3SNOPh+SUZH+SyY8EA2I3wR7BMoT7rnZNolTwGjUXn7bRC6vISWg16N202 # 1RBWdTGW2rVPBVLF4HA46jle4hcpEVquXdj3yGYa99ko1w2FOWzLjKvtLqj4tzOh # K7wa/Gbmv0Si/FU6oOmctzYMI0QXtEG7lR1HsJT5kywwmgcjyuiN28iBIhT6man0 # Ib6xKDv40PblKq5c9AFVldXUGVeBJbLhcEAA1nSPSLGdc7j4J2SulGISYY7ocuX3 # tkv01te72Mv2KkqqpfkLEAQjXgtM0hlgwuc8/A4if+I0YtboCMkVQuwBpbR9/6ys # Z+sCAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl6nEDwGD5LfZldQ5Y # MB0GA1UdDgQWBBRcxSkFqeA3vvHU0aq2mVpFRSOdmjAOBgNVHQ8BAf8EBAMCB4Aw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOgMYYvaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwNaAzoDGGL2h0 # dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMEwG # A1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 # LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcwAoZC # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJ # RENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD # ggEBANuBGTbzCRhgG0Th09J0m/qDqohWMx6ZOFKhMoKl8f/l6IwyDrkG48JBkWOA # QYXNAzvp3Ro7aGCNJKRAOcIjNKYef/PFRfFQvMe07nQIj78G8x0q44ZpOVCp9uVj # sLmIvsmF1dcYhOWs9BOG/Zp9augJUtlYpo4JW+iuZHCqjhKzIc74rEEiZd0hSm8M # asshvBUSB9e8do/7RhaKezvlciDaFBQvg5s0fICsEhULBRhoyVOiUKUcemprPiTD # xh3buBLuN0bBayjWmOMlkG1Z6i8DUvWlPGz9jiBT3ONBqxXfghXLL6n8PhfppBhn # daPQO8+SqF5rqrlyBPmRRaTz2GQwggUwMIIEGKADAgECAhAECRgbX9W7ZnVTQ7Vv # lVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp # Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0Rp # Z2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAwMDBaFw0yODEw # MjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNI # QTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE9X/lqJ3bMtdx # 6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvspJ8fTeyOU5JEj # lpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWuHEqHCN8M9eJN # YBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel05iv+bMt+dDk2 # DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4PwaLoLFH3c7y9 # hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHNMIIByTASBgNV # HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF # BQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig # NoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v # dENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAo # BggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgB # hv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYDVR0jBBgwFoAU # Reuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAD7sDVoks/Mi # 0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh9tGSdQ9RtG6l # jlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6RFfu6r7VRwo0k # riTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEMj7uo+MUSaJ/P # QMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutmQ9qzsIzV6Q3d # 9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUukpHqaGxEMrJm # oecYpJpkUe8xggIoMIICJAIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM # RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQD # EyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBAhACwXUo # dNXChDGFKtigZGnKMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQodToNMwK9wGq1pq5gEafK/pkI # 6TANBgkqhkiG9w0BAQEFAASCAQA1cWGaoOtYK7Aj0+voFm8NBPFXUyBfFFFMBUBK # np8giAMSvASbzXSUPR/hae6LR/UzZFdXqH3ZhAbA//BItAjM5EC8fhbZMHGzqXmx # IxU7YxzuXJvrLd7yVjLRRhu+kclMVGSGELBFIfEAm8nMtrhWAKGhXPlzFTJtwOiD # qREulLuvtEBb/LBk/mT/vw8499Jzj9fE5oXYkqcJiodToaagGEpe2fZC03s8rbm9 # kl3n3H39/j7y4PwJHt0BEhtT4koNVod5d3Jx42hpJIN77vLCmJ3DtVhckArrkftc # UPdWInKb/H47AJCuP3+chRgiGmEGdlnVVM13FJfzIkHxDxpA # SIG # End signature block |