Support work folders, improve agent info scraping

This commit is contained in:
Josh Wittner 2018-07-19 14:56:42 -07:00
Родитель 7c2ea902b2
Коммит a2c747bf62
5 изменённых файлов: 163 добавлений и 114 удалений

Просмотреть файл

@ -39,9 +39,13 @@ function Get-TargetResource {
[System.String]
$AgentDirectory,
[parameter(Mandatory = $false)]
[System.String]
$Work,
[parameter(Mandatory = $true)]
[System.String]
$Account,
$ServerUrl,
[parameter(Mandatory = $true)]
[System.Management.Automation.PSCredential]
@ -58,12 +62,14 @@ function Get-TargetResource {
$PrefixComputerName = $false
)
if( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
if ( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
$returnValue = @{ Name = $Name }
$returnValue = @{ 'Name' = $Name; 'AgentDirectory' = $AgentDirectory }
$agent = Get-VSTSAgent -NameFilter $Name -AgentDirectory $AgentDirectory
if ( $agent ) {
$returnValue['Account'] = $agent.Account
$returnValue['ServerUrl'] = $agent.ServerUrl
$returnValue['Work'] = "$($agent.Work)"
$returnValue['PoolId'] = "$($agent.PoolId)"
$returnValue['Ensure'] = 'Present'
}
else {
@ -106,9 +112,13 @@ function Set-TargetResource {
[System.String]
$AgentDirectory,
[parameter(Mandatory = $false)]
[System.String]
$Work,
[parameter(Mandatory = $true)]
[System.String]
$Account,
$ServerUrl,
[parameter(Mandatory = $true)]
[System.Management.Automation.PSCredential]
@ -127,22 +137,26 @@ function Set-TargetResource {
if ( Test-TargetResource @PSBoundParameters ) { return }
if( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
if ( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
if ( $Ensure -eq 'Present') {
$installArgs = @{
Name = $Name
Pool = $Pool
Account = $Account
PAT = $AccountCredential.Password
AgentDirectory = $AgentDirectory
Replace = $true
'Name' = $Name
'Pool' = $Pool
'ServerUrl' = $ServerUrl
'PAT' = $AccountCredential.Password
'AgentDirectory' = $AgentDirectory
'Replace' = $true
}
if ( $Work ) { $installArgs['Work'] = $Work }
if ( $LogonCredential ) { $installArgs['LogonCredential'] = $LogonCredential }
Install-VSTSAgent @installArgs
}
else {
Uninstall-VSTSAgent -Name $Name -AgentDirectory $AgentDirectory -PAT $AccountCredential.Password
}
}
<#
@ -179,9 +193,13 @@ function Test-TargetResource {
[System.String]
$AgentDirectory,
[parameter(Mandatory = $false)]
[System.String]
$Work,
[parameter(Mandatory = $true)]
[System.String]
$Account,
$ServerUrl,
[parameter(Mandatory = $true)]
[System.Management.Automation.PSCredential]
@ -198,16 +216,37 @@ function Test-TargetResource {
$PrefixComputerName = $false
)
if( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
if ( $PrefixComputerName ) { $Name = Get-PrefixComputerName $Name }
$agent = Get-VSTSAgent -NameFilter $Name -AgentDirectory $AgentDirectory -Verbose
$agent = Get-VSTSAgent -NameFilter $Name -AgentDirectory $AgentDirectory
switch ($Ensure) {
'Present' {
'Present' {
if ( -not $agent ) { return $false }
if ( $agent.Account -ne $Account ) { return $false }
Write-Verbose "Found agent pointed to $($agent.ServerUrl) and working from $($agent.Work)"
if ( $agent.ServerUrl -ne $ServerUrl ) {
Write-Verbose "ServerUrl mismatch: $($agent.ServerUrl) -ne $ServerUrl"
return $false
}
if ( $Work -and $agent.Work -ne $Work ) {
Write-Verbose "Work folder mismatch: $($agent.Work) -ne $Work"
return $false
}
# TODO: Get back to pool name from $agent.PoolId.
Write-Verbose "VSTS Agent is Present"
return $true
}
'Absent' {
if ( $agent ) {
Write-Verbose "Found agent pointed to $($agent.ServerUrl) and working from $($agent.Work)"
return $false
}
Write-Verbose "VSTS Agent is Absent"
return $true
}
'Absent' { return (-not $agent) }
}
}

Просмотреть файл

@ -5,9 +5,10 @@ class xVSTSAgent : OMI_BaseResource
[Key] String Name;
[Write] String Pool;
[Required, EmbeddedInstance("MSFT_Credential")] String AccountCredential;
[Required] String Account;
[Required] String ServerUrl;
[Write, EmbeddedInstance("MSFT_Credential")] String LogonCredential;
[Required] String AgentDirectory;
[Write] String Work;
[Write] Boolean PrefixComputerName;
[Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
};

Просмотреть файл

@ -8,7 +8,7 @@ Configuration Sample_xVSTSAgent {
(
[parameter(Mandatory = $true)]
[System.String]
$Account,
$ServerUrl,
[System.String]
$Name = "$env:COMPUTERNAME",
@ -26,6 +26,9 @@ Configuration Sample_xVSTSAgent {
[System.String]
$AgentDirectory = 'C:\VSTSAgents',
[System.String]
$Work,
[ValidateSet('Present', 'Absent')]
[System.String]
$Ensure = 'Present',
@ -34,17 +37,18 @@ Configuration Sample_xVSTSAgent {
$PrefixComputerName = $false
)
Import-DscResource -ModuleName VSTSAgent
Import-DscResource -ModuleName VSTSAgent -ModuleVersion '2.0'
Node 'localhost' {
xVSTSAgent VSTSAgent {
Name = $Name
Pool = $Pool
Account = $Account
ServerUrl = $ServerUrl
AccountCredential = $AccountCredential
LogonCredential = $LogonCredential
AgentDirectory = $AgentDirectory
Work = $Work
Ensure = $Ensure
PrefixComputerName = $PrefixComputerName
}

Просмотреть файл

@ -11,28 +11,28 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'VSTSAgent.psm1'
RootModule = 'VSTSAgent.psm1'
# Version number of this module.
ModuleVersion = '1.1'
ModuleVersion = '2.0'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = '679b6302-ff19-4e53-a20f-eac2f531b5b6'
GUID = '679b6302-ff19-4e53-a20f-eac2f531b5b6'
# Author of this module
Author = 'Microsoft'
Author = 'Microsoft'
# Company or vendor of this module
CompanyName = 'Microsoft'
CompanyName = 'Microsoft'
# Copyright statement for this module
Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Tools for managing and automating your VSTS Agents.'
Description = 'Tools for managing and automating your VSTS Agents.'
# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = ''
@ -71,7 +71,7 @@
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
FunctionsToExport = @(
'Find-VSTSAgent',
'Install-VSTSAgent',
'Get-VSTSAgent',
@ -81,16 +81,16 @@
)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
VariablesToExport = @()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
DscResourcesToExport = @('xVSTSAgent')
# List of all modules packaged with this module
# ModuleList = @()
@ -99,7 +99,7 @@
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PrivateData = @{
PSData = @{
@ -123,7 +123,7 @@
} # End of PrivateData hashtable
# HelpInfo URI of this module
HelpInfoURI = 'https://github.com/Microsoft/VSTSAgent.PowerShell/blob/master/README.md'
HelpInfoURI = 'https://github.com/Microsoft/VSTSAgent.PowerShell/blob/master/README.md'
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

Просмотреть файл

@ -176,14 +176,18 @@ function Find-VSTSAgent {
The required agent version.
.PARAMETER AgentDirectory
What directory should agents be installed into?
.PARAMETER Work
Work directory where job data is stored. Defaults to _work under the
root of the agent directory. The work directory is owned by a given
agent and should not share between multiple agents.
.PARAMETER Name
What name should the agent use?
.PARAMETER Pool
What pool should the agent be registered into?
.PARAMETER PAT
What personal access token (PAT) should be used to auth with VSTS?
.PARAMETER Account
What account should the agent be registered to? As in "https://$Account.visualstudio.com".
.PARAMETER ServerUrl
What server url should the agent be registered to? Eg. 'https://account.visualstudio.com'
.PARAMETER Replace
Should the new agent replace any existing one on the account?
.PARAMETER LogonCredential
@ -209,6 +213,9 @@ function Install-VSTSAgent {
[parameter(Mandatory = $false)]
[string]$AgentDirectory = [IO.Path]::Combine($env:USERPROFILE, "VSTSAgents"),
[parameter(Mandatory = $false)]
[string]$Work,
[parameter(Mandatory = $false)]
[string]$Name = [System.Environment]::MachineName + "-$(Get-Random)",
@ -219,7 +226,7 @@ function Install-VSTSAgent {
[securestring]$PAT,
[parameter(Mandatory = $true)]
[string]$Account,
[uri]$ServerUrl,
[parameter(Mandatory = $false)]
[switch]$Replace,
@ -239,7 +246,7 @@ function Install-VSTSAgent {
$existing = Get-VSTSAgent -AgentDirectory $AgentDirectory -NameFilter $Name
if ( $existing ) {
if($Replace) {
if ($Replace) {
Uninstall-VSTSAgent -NameFilter $Name -AgentDirectory $AgentDirectory -PAT $PAT -ErrorAction Stop
}
else { throw "Agent $Name already exists in $AgentDirectory" }
@ -284,10 +291,11 @@ function Install-VSTSAgent {
$configPath = Get-ChildItem $configPath -ErrorAction SilentlyContinue
if ( -not $configPath ) { throw "Agent $agentFolder is missing config.cmd" }
[string[]]$configArgs = @('--unattended', '--url', "https://$Account.visualstudio.com", '--auth', `
[string[]]$configArgs = @('--unattended', '--url', "$ServerUrl", '--auth', `
'pat', '--pool', "$Pool", '--agent', "$Name", '--runAsService')
if ( $Replace ) { $configArgs += '--replace' }
if ( $LogonCredential ) { $configArgs += '--windowsLogonAccount', $LogonCredential.UserName }
if ( $Work ) { $configArgs += '--work', $Work }
if ( -not $PSCmdlet.ShouldProcess("$configPath $configArgs", "Start-Process") ) { return }
@ -302,7 +310,7 @@ function Install-VSTSAgent {
$outFile = [io.path]::Combine($agentFolder, "out.log")
$errorFile = [io.path]::Combine($agentFolder, "error.log")
Write-Verbose "Registering $Name to $Pool in $Account"
Write-Verbose "Registering $Name to $Pool at $ServerUrl"
Start-Process $configPath -ArgumentList $configArgs -NoNewWindow -Wait `
-RedirectStandardOutput $outFile -RedirectStandardError $errorFile -ErrorAction Stop
@ -361,13 +369,13 @@ function Uninstall-VSTSAgent {
$token = [System.Net.NetworkCredential]::new($null, $PAT).Password
Get-VSTSAgent @getArgs | ForEach-Object {
if ( -not $PSCmdlet.ShouldProcess("$($_.Name) - $($_.Uri)", "Uninstall")) { return }
if ( -not $PSCmdlet.ShouldProcess("$($_.Name) - $($_.Path)", "Uninstall")) { return }
$configPath = [io.path]::Combine($_.Uri.LocalPath, 'config.cmd')
$configPath = [io.path]::Combine($_.Path.LocalPath, 'config.cmd')
$configArgs = @('remove', '--unattended', '--auth', 'pat', '--token', "$token")
$outFile = [io.path]::Combine($_.Uri.LocalPath, "out.log")
$errorFile = [io.path]::Combine($_.Uri.LocalPath, "error.log")
$outFile = [io.path]::Combine($_.Path.LocalPath, "out.log")
$errorFile = [io.path]::Combine($_.Path.LocalPath, "error.log")
Start-Process $configPath -ArgumentList $configArgs -NoNewWindow -Wait `
-RedirectStandardOutput $outFile -RedirectStandardError $errorFile
@ -377,7 +385,10 @@ function Uninstall-VSTSAgent {
return; # Don't remove the agent folder if something went wrong.
}
Remove-Item $_.Uri.LocalPath -Recurse -Force
Remove-Item $_.Path.LocalPath -Recurse -Force -ErrorAction Continue
if ( $_.Work.IsAbsoluteUri ) {
Remove-Item $_.Work.LocalPath -Recurse -Force -ErrorAction Continue
}
}
}
@ -416,77 +427,63 @@ function Get-VSTSAgent {
[string]$NameFilter = '*'
)
Get-ChildItem $AgentDirectory -Directory -Filter $NameFilter -ErrorAction SilentlyContinue | ForEach-Object {
Get-ChildItem "$AgentDirectory\**\.agent" -Attributes '!D+H,!D' -ErrorAction SilentlyContinue |
ForEach-Object {
Write-Verbose "Found agent at $($_.FullName)"
$configPath = [io.path]::combine($_.FullName, 'config.cmd')
$configPath = Get-ChildItem $configPath -ErrorAction SilentlyContinue
if ( -not $configPath ) {
Write-Warning "Agent $_ is missing config.cmd"
return
}
$agentFullDirectory = $_.Directory.FullName
$agentFullPath = $_.FullName
$version = & $configPath --version
try {
$agent = Get-Content $agentFullPath | ConvertFrom-Json
Write-Verbose "Agent is named $($agent.agentName)"
if ( $NameFilter -and ($agent.agentName -notlike $NameFilter) ) {
Write-Verbose "Skipping agent because $($agent.agentName) is not like $NameFilter"
return
}
if ( $RequiredVersion -and $version -ne $RequiredVersion) { return }
if ( $MinimumVersion -and $version -lt $MinimumVersion) { return }
if ( $MaximumVersion -and $version -gt $MaximumVersion) { return }
$configPath = [io.path]::combine($agentFullDirectory, 'config.cmd')
$configPath = Get-ChildItem $configPath -ErrorAction SilentlyContinue
if ( -not $configPath ) {
Write-Warning "Agent $agentFullDirectory is missing config.cmd"
return
}
$name = $_.Name
$service = Get-Service | ForEach-Object {
if ( $_.Name -match "^vstsagent\.(.+)\.$name$" ) {
[pscustomobject]@{ 'Account' = $Matches[1]; 'Status' = $_.Status }
$version = & $configPath --version
if ( $RequiredVersion -and $version -ne $RequiredVersion) {
Write-Verbose "Skipping agent because $version not match $RequiredVersion"
return
}
if ( $MinimumVersion -and $version -lt $MinimumVersion) {
Write-Verbose "Skipping agent because $version is less than $MinimumVersion"
return
}
if ( $MaximumVersion -and $version -gt $MaximumVersion) {
Write-Verbose "Skipping agent because $version is greater than $MaximumVersion"
return
}
if ( Test-Path "$($_.Directory.FullName)\.service" ) {
$serviceName = Get-Content "$($_.Directory.FullName)\.service"
$service = Get-Service $serviceName
}
[pscustomobject]@{
'Id' = $agent.agentId
'Name' = $agent.agentName
'PoolId' = $agent.poolId
'ServerUrl' = [uri]$agent.serverUrl
'Work' = [uri]$agent.workFolder
'Service' = $service
'Version' = $version
'Path' = [uri]$agentFullDirectory
}
}
[pscustomobject]@{
'Name' = $name
'Version' = $version
'Account' = $service.Account
'Status' = $service.Status
'Uri' = [uri]::new( $configPath.Directory.FullName + [io.path]::DirectorySeparatorChar )
}
catch { Write-Error "Exception processing agent at $agentFullPath\: $_" }
}
}
<#
.SYNOPSIS
Get service for installed agent.
.DESCRIPTION
Get service for installed agent using VSTS agent naming scheme.
.PARAMETER Name
Name of the agent to find the service for.
.PARAMETER Account
Account of the agent to find the service for.
.PARAMETER Status
Filter services to only those whose status matches this.
#>
function Get-VSTSAgentService {
param(
[parameter(ValueFromPipelineByPropertyName, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[parameter(ValueFromPipelineByPropertyName, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Account,
[System.ServiceProcess.ServiceControllerStatus]$Status
)
$serviceName = "vstsagent.$Account.$Name"
$services = Get-Service -Name $serviceName -ErrorAction 'SilentlyContinue'
if ( $services.Count -eq 0 ) {
Write-Error "Agent $($_.Name) has no matching service at $serviceName"
}
if ( $Status ) {
$services = $services | Where-Object { $_.Status -eq $Status }
}
$services
}
<#
.SYNOPSIS
@ -523,9 +520,13 @@ function Start-VSTSAgent {
[string]$NameFilter
)
Get-VSTSAgent @PSBoundParameters | Get-VSTSAgentService -Status Stopped | ForEach-Object {
if ( $PSCmdlet.ShouldProcess($_.Name, "Start-Service") ) {
Start-Service $_
$stoppedAgents = Get-VSTSAgent @PSBoundParameters | Where-Object {
$_.Service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Stopped
}
$stoppedAgents | ForEach-Object {
if ( $PSCmdlet.ShouldProcess($_.Service.Name, "Start-Service") ) {
Start-Service $_.Service
}
}
}
@ -565,9 +566,13 @@ function Stop-VSTSAgent {
[string]$NameFilter
)
Get-VSTSAgent @PSBoundParameters | Get-VSTSAgentService -Status Running | ForEach-Object {
if ( $PSCmdlet.ShouldProcess($_.Name, "Stop-Service") ) {
Stop-Service $_
$runningAgents = Get-VSTSAgent @PSBoundParameters | Where-Object {
$_.Service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running
}
$runningAgents | ForEach-Object {
if ( $PSCmdlet.ShouldProcess($_.Service.Name, "Stop-Service") ) {
Stop-Service $_.Service
}
}
}