Artifacts/windows-azuredevops-multiple-agents/run.ps1 (176 lines of code) (raw):

# Downloads the Azure DevOps Pipelines Agent and installs specified instances on the new machine # under C:\agents\ and registers with the Azure DevOps Pipelines agent pool [CmdletBinding()] Param ( [Parameter()] [string]$Account, [Parameter()] [String]$PersonalAccessToken, [Parameter()] [string]$AgentName, [Parameter()] [string]$AgentInstallLocation, [Parameter()] [string]$AgentNamePrefix, [Parameter()] [string]$PoolName, [Parameter()] [int] $AgentCount, [Parameter()] [bool] $Overwrite ) ################################################################################################### # # PowerShell configurations # # NOTE: Because the $ErrorActionPreference is "Stop", this script will stop on first failure. # This is necessary to ensure we capture errors inside the try-catch-finally block. $ErrorActionPreference = "Stop" # Ensure we force use of TLS 1.2 for all downloads. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Ensure we set the working directory to that of the script. Push-Location $PSScriptRoot ################################################################################################### # # Handle all errors in this script. # trap { # NOTE: This trap will handle all errors. There should be no need to use a catch below in this # script, unless you want to ignore a specific error. $message = $error[0].Exception.Message if ($message) { Write-Host -Object "ERROR: $message" -ForegroundColor Red } # IMPORTANT NOTE: Throwing a terminating error (using $ErrorActionPreference = "Stop") still # returns exit code zero from the PowerShell script when using -File. The workaround is to # NOT use -File when calling this script and leverage the try-catch-finally block and return # a non-zero exit code from the catch block. exit -1 } # # Test the last exit code correctly. # function Test-LastExitCode { param ( [string] $Message = '' ) # Whenever we execute commands such as '& somecommand' or via Invoke-Expression, we should always # check the exit code, so we can decide to stop processing at that time, by throwing an exception. $exitCode = $LASTEXITCODE if ($exitCode -and $exitCode -ne 0) { if ($Message) { if (-not $Message.EndsWith('.')) { $Message += '.' } $Message += ' ' } $Message += "Last command exited with error code $exitCode" throw $Message } Write-Output "Completed with exit code: $exitCode" } ################################################################################################### # # Main execution block. # try { Write-Output "Entering powershell task" Write-Output "Current folder: $PSScriptRoot" Write-Output "Validating parameters..." if ([string]::IsNullOrWhiteSpace($Account)) { throw "Account parameter is required." } if ([string]::IsNullOrWhiteSpace($PersonalAccessToken)) { throw "PersonalAccessToken parameter is required." } if ([string]::IsNullOrWhiteSpace($PoolName)) { throw "PoolName parameter is required." } if ([string]::IsNullOrWhiteSpace($AgentName)) { $AgentName = $env:COMPUTERNAME } if (-not [string]::IsNullOrWhiteSpace($AgentNamePrefix)) { $AgentName = ("{0}-{1}" -f $AgentNamePrefix, $AgentName) } if ([string]::IsNullOrWhiteSpace($AgentInstallLocation)) { $AgentInstallLocation = "c:\agents"; } #Create a temporary directory where to download from Azure DevOps the agent package (vsts-agent.zip) and then launch the configuration. $agentTempFolderName = Join-Path $env:temp ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Force -Path $agentTempFolderName Write-Output "Temporary Agent download folder: $agentTempFolderName" $serverUrl = "https://dev.azure.com/$Account" Write-Output "Server URL: $serverUrl" $retryCount = 3 $retries = 1 Write-Output "Downloading Agent install files" do { try { Write-Output "Fetching download URL for latest Azure DevOps agent..." $vstsAgentUrl = "$serverUrl/_apis/distributedtask/packages/agent/win7-x64?`$top=1&api-version=3.0" $basicAuth = (":{0}" -f $PersonalAccessToken) $basicAuth = [System.Text.Encoding]::UTF8.GetBytes($basicAuth) $basicAuth = [System.Convert]::ToBase64String($basicAuth) $headers = @{ Authorization = ("Basic {0}" -f $basicAuth) } [array] $agentList = Invoke-WebRequest -Uri $vstsAgentUrl -Headers $headers -Method Get -ContentType application/json -UseBasicParsing | ConvertFrom-Json $agent = $agentList.value[0] Write-Output "Agent will be downloaded to: '$agentTempFolderName'" $agentPackagePath = "$agentTempFolderName\agent.zip" if (Test-Path -Path $agentPackagePath) { Write-Output "Directory $agentTempFolderName is not empty...Removing all contents..." Remove-Item "$agentTempFolderName/*" -Force -Recurse } $downloadUrl = $agent.downloadUrl; Write-Output "Downloading agent from: $downloadUrl" Invoke-WebRequest -Uri $agent.downloadUrl -Headers $headers -Method Get -OutFile "$agentPackagePath" -UseBasicParsing | Out-Null Write-Output "Downloaded agent successfully on attempt $retries" break } catch { $exceptionText = ($_ | Out-String).Trim() Write-Output "Exception occured downloading agent: $exceptionText in try number $retries" $retries++ Start-Sleep -Seconds 30 } } while ($retries -le $retryCount) for ($i = 1; $i -lt $AgentCount + 1; $i++) { $Agent = ($AgentName + "-" + $i) # Construct the agent folder under the main (hardcoded) C: drive. $agentInstallationPath = Join-Path $AgentInstallLocation $Agent #Test if the directory already exist, which probably means agent also exists if (-not $Overwrite -and (Test-Path $agentInstallationPath)) { Write-Output "Directory $agentInstallationPath not empty..Overwrite is set to 'false', skipping..." continue; } # Create the directory for this agent. New-Item -ItemType Directory -Force -Path $agentInstallationPath # Set the current directory to the agent dedicated one previously created. Push-Location -Path $agentInstallationPath Write-Output "Extracting the zip file for the agent" $destShellFolder = (new-object -com shell.application).namespace("$agentInstallationPath") $destShellFolder.CopyHere((new-object -com shell.application).namespace("$agentTempFolderName\agent.zip").Items(), 16) # Removing the ZoneIdentifier from files downloaded from the internet so the plugins can be loaded # Don't recurse down _work or _diag, those files are not blocked and cause the process to take much longer Write-Output "Unblocking files" Get-ChildItem -Recurse -Path $agentInstallationPath | Unblock-File | out-null # Retrieve the path to the config.cmd file. $agentConfigPath = [System.IO.Path]::Combine($agentInstallationPath, 'config.cmd') Write-Output "Agent Location = $agentConfigPath" if (![System.IO.File]::Exists($agentConfigPath)) { throw "File not found: $agentConfigPath" } # Call the agent with the configure command and all the options (this creates the settings file) without prompting # the user or blocking the cmd execution Write-Output "Configuring agent '$($Agent)'" .\config.cmd --unattended --url $serverUrl --auth PAT --token $PersonalAccessToken --pool $PoolName --agent $Agent --runasservice Test-LastExitCode Pop-Location } Write-Output "Exiting InstallVSTSAgent.ps1" } finally { Pop-Location }