AzdoAPITools.psm1
function Convert-AzDoAPIToolsYamlObjectToYAMLFile { [CmdletBinding()] param ( # Parameter help description [Parameter(Mandatory=$true)] [Object] $InputObject, # Parameter help description [Parameter(Mandatory=$true)] [String] $outputpath, # Parameter help description [Parameter(Mandatory=$true)] [String] $Outputfilename ) process { if (!(Test-Path $outputpath)) { if(Get-Confirmation "$outputpath not detected. Do you want to create it"){ New-Item -path $OutputPath -ItemType 'Directory' | Out-Null } } $InputObject | ConvertTo-Yaml | Out-File "$outputpath\$Outputfilename" -encoding utf8 } } function Get-AzDoAPIToolsDefinitionAsYAMLPrepped { param ( # Parameter help description [Parameter(mandatory=$true)] [Array] $DefinitionsToConvert, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Profilename [Parameter(mandatory=$false)] [string] $profilename, # OutputPath [Parameter(mandatory=$false)] [string] $OutputPath, # switch for expanding nested taskgroups [Parameter(mandatory=$false)] [switch] $ExpandNestedTaskGroups, # Switch to output file instead of retruning an object [Parameter(mandatory=$false)] [switch] $Outputasfile ) Import-Module powershell-yaml foreach ($Definition in $DefinitionsToConvert) { $yamlTemplate = New-Object PSObject $yamlarray = @() if ($definition.value.buildNumberFormat) { $name = @{'name' = $definition.value.buildNumberFormat} }else { $name = @{'name' = '$(buildid)'} } $inputs = Get-AzDoAPIToolsDefinitionVariablesAsYAMLPrepped -InputDefinitions $Definition $triggers = Get-AzDoAPIToolsDefinitionTriggersAsYAMLPrepped -InputDefinitions $Definition $schedules = Get-AzDoAPIToolsDefinitionSchedulesAsYAMLPrepped -InputDefinitions $Definition $pool = Get-AzDoAPIToolsAgentPool -PoolURL $Definition.value.queue._links.self.href -agentidentifier $Definition.value.process.target.agentSpecification.identifier $pooltoadd = @{} $pooltoadd.add('pool',$pool) $steps = Get-AzDoAPIToolsDefinitionStepsAsYAMLPrepped -InputDefinitions $Definition -projectname $projectname -profilename $profilename -ExpandNestedTaskGroups:$ExpandNestedTaskGroups.IsPresent if ($name) { $yamlarray += $name } if ($pooltoadd) { $yamlarray += $pooltoadd } if ($inputs) { $yamlarray += $inputs } if ($triggers) { $yamlarray += $triggers } if ($schedules) { $yamlarray += $schedules } if ($steps) { $yamlarray += $steps } foreach ($yamlobject in $yamlarray) { $yamlobject.getEnumerator() | ForEach-Object{ $yamlTemplate | Add-Member -NotePropertyName $_.name -NotePropertyValue $_.value } } if ($outputasfile.IsPresent) { if (!$outputpath) { Write-Error "You have used the -Outputfile switch without mentioning OutputPath" }else{ Convert-AzDoAPIToolsYamlObjectToYAMLFile -InputObject $yamlTemplate -outputpath $OutputPath -Outputfilename "$($Definition.name).yml" } }else { return $yamlTemplate } } } function Get-AzDoAPIToolsDefinitionSchedulesAsYAMLPrepped { [CmdletBinding()] param ( # InputObjects [Parameter(Mandatory=$true)] [Array] $InputDefinitions ) process { foreach ($definition in $InputDefinitions) { $definition = $definition.value if ($definition.triggers) { $citriggers = $definition.triggers | Where-Object {$_.triggerType -eq 'schedule'} if ($citriggers.schedules) { $schedules = @() foreach ($schedule in $citriggers.schedules) { $yamlschedule = [ordered]@{} $cron = Get-CronFromSchedule -InputSchedules $schedule $yamlschedule.add('cron', $cron) if($schedule.branchFilters){ $branchtriggers = Get-DefinitionInputIncludeExclude -inputs $schedule.branchFilters if ($branchtriggers.count -ge 1) { $yamlschedule.add('branches', $branchtriggers) } } if ($schedule.scheduleOnlyWithChanges -eq $false) { $yamlschedule.add('always','true') } $schedules += $yamlschedule } if ($schedules.Count -ge 1) { $returnedtriggerobject = @{ 'schedules' = $schedules } } } } return $returnedtriggerobject } } } function Get-AzDoAPIToolsDefinitionsTaskGroupsByID { param ( # Nameslist [Parameter(mandatory=$true)] [String] $ID, # Nameslist [Parameter(mandatory=$false)] [int] $TGVersion, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Profilename [Parameter(mandatory=$false)] [string] $profilename, # type [Parameter(mandatory=$true)] [ValidateSet('BuildDefinition','ReleaseDefinition','TaskGroup')] [string] $ApiType, # also return drafts [Parameter(mandatory=$false)] [switch] $includeTGdrafts, # also return previews [Parameter(mandatory=$false)] [switch] $includeTGpreview, # Return all versions [Parameter(mandatory=$false)] [switch] $AllTGVersions ) $return = @() switch ($apitype) { BuildDefinition { $definitions = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'build' -resource 'definitions' -profilename $profilename -version '5.1' -id $id $definitions | ForEach-Object{ $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', $($_.url)) $hash.Add('type', $apitype) $hash.add('value',$_) $return += [PSCustomObject]$hash } } ReleaseDefinition { $definitions = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'release' -resource 'definitions' -profilename $profilename -version '5.1-preview' -id $id $filtereddefinitions = $definitions.value | Where-Object {$_.name -in $NamesList} $filtereddefinitions | ForEach-Object{ $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', $($_.url)) $hash.Add('type', $apitype) $hash.Add('value',$_) $return += [PSCustomObject]$hash } } TaskGroup { $taskgroups = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'distributedtask' -resource 'taskgroups' -profilename $profilename -version '5.1-preview' -id $id $filteredtaskgroups = $taskgroups.value $filteredproperties = $filteredtaskgroups $filteredproperties | ForEach-Object { $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', (Get-AzdoAPIURL -projectname $Projectname -area 'distributedtask' -resource 'taskgroups' -profilename $profilename -version '5.1-preview' -id $id)) $hash.Add('type', $apitype) $hash.Add('version', $($_.version.major)) if($_.version.istest){ $hash.Add('Draft','True') }else{ $hash.Add('Draft','False') } if ($_.preview) { $hash.Add('Preview','True') }else{ $hash.Add('Preview','False') } if (!$TGVersion) { $hash.Add('highestversion',(Get-HighestTaskGroupVersion -TaskGroupObject $filteredproperties -Taskgroupid $_.id -includeTGPreview:$includeTGpreview.IsPresent)) } $hash.Add('value',$_) $return += [PSCustomObject]$hash } if ($TGVersion) { $return = $return | Where-Object {$_.version -eq $TGVersion} if (($return | Measure-Object | Select-Object -ExpandProperty count) -ne 1) { Write-Error "version supplied $TGVersion not found for Task Group ID $ID" throw; } }else{ if (-not $includeTGdrafts.IsPresent) { $return = $return | Where-Object {$_.Draft -eq $False} } if (-not $includeTGpreview.IsPresent) { $return = $return | Where-Object {$_.Preview -eq $False} } if (-not $AllTGVersions.IsPresent) { $return = $return | Where-Object {$_.version -eq $_.highestversion} } } } Default {Write-Error "unaccepted type found. Accepted values are BuildDefintion, ReleaseDefinition, TaskGroup"} } return $return } function Get-AzDoAPIToolsDefinitionsTaskGroupsByNamesList { param ( # Nameslist [Parameter(mandatory=$true)] [array] $NamesList, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Profilename [Parameter(mandatory=$false)] [string] $profilename, # type [Parameter(mandatory=$true)] [ValidateSet('BuildDefinition','ReleaseDefinition','TaskGroup')] [string] $ApiType, # also return drafts [Parameter(mandatory=$false)] [switch] $includeTGdrafts, # also return previews [Parameter(mandatory=$false)] [switch] $includeTGpreview, # Return all versions [Parameter(mandatory=$false)] [switch] $AllTGVersions ) $return = @() switch ($apitype) { BuildDefinition { $definitions = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'build' -resource 'definitions' -profilename $profilename -version '5.1-preview' $filtereddefinitions = $definitions.value | Where-Object {$_.name -in $NamesList} $filtereddefinitions | ForEach-Object{ $filtereddefinitiondetails = Use-AzDoAPI -url $_.url -method 'Get' -projectname $Projectname -profilename $profilename -version '5.1-preview' # $filtereddefinitiondetails $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', $($_.url)) $hash.Add('type', $apitype) $hash.Add('value',$filtereddefinitiondetails) $return += [PSCustomObject]$hash } } ReleaseDefinition { $definitions = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'release' -resource 'definitions' -profilename $profilename -version '5.1-preview' $filtereddefinitions = $definitions.value | Where-Object {$_.name -in $NamesList} $filtereddefinitions | ForEach-Object{ $filtereddefinitiondetails = Use-AzDoAPI -url $_.url -method 'Get' -projectname $Projectname -profilename $profilename -version '5.1-preview' $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', $($_.url)) $hash.Add('type', $apitype) $hash.Add('value',$filtereddefinitiondetails) $return += [PSCustomObject]$hash } } TaskGroup { $taskgroups = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'distributedtask' -resource 'taskgroups' -profilename $profilename -version '5.1-preview' $filteredtaskgroups = $taskgroups.value | Where-Object {$_.name -in $NamesList} $filteredproperties = $filteredtaskgroups $filteredproperties | ForEach-Object { $hash = @{} $hash.Add('id', $($_.id)) $hash.Add('name', $($_.name)) $hash.Add('url', (Get-AzdoAPIURL -projectname $Projectname -area 'distributedtask' -resource 'taskgroups' -profilename $profilename -version '5.1-preview' -id $_.id)) $hash.Add('type', $apitype) $hash.Add('version', $($_.version.major)) if($_.version.istest){ $hash.Add('Draft','True') }else{ $hash.Add('Draft','False') } if ($_.preview) { $hash.Add('Preview','True') }else{ $hash.Add('Preview','False') } $hash.Add('highestversion',(Get-HighestTaskGroupVersion -TaskGroupObject $filteredproperties -Taskgroupid $_.id -includeTGPreview:$includeTGpreview.IsPresent)) $hash.Add('value',$_) $return += [PSCustomObject]$hash } if (-not $includeTGdrafts.IsPresent) { $return = $return | Where-Object {$_.Draft -eq $False} } if (-not $includeTGpreview.IsPresent) { $return = $return | Where-Object {$_.Preview -eq $False} } if (-not $AllTGVersions.IsPresent) { $return = $return | Where-Object {$_.version -eq $_.highestversion} } } Default {Write-Error "unaccepted type found. Accepted values are BuildDefintion, ReleaseDefinition, TaskGroup"} } return $return } function Get-AzDoAPIToolsDefinitionStepsAsYAMLPrepped { [CmdletBinding()] param ( # InputObjects [Parameter(Mandatory=$true)] [Array] $InputDefinitions, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Projectname [Parameter(mandatory=$false)] [string] $profilename, # future switch for expanding nested taskgroups [Parameter(mandatory=$false)] [switch] $ExpandNestedTaskGroups ) process { foreach ($definition in $inputdefinitions) { $definitiontype = $definition.type $definition = $definition.value $jobs = $definition.process.phases $jobcount = $jobs.count $definitionjobs = @() $retunreddefinitionjobs = [ordered]@{} [bool]$pipelinedemands = ($definition.PSobject.Properties.name.contains('demands')) foreach ($job in $jobs) { $definitionsteps = [ordered]@{} $definitionjob = [ordered]@{} $demandstoadd = $null [array]$steps = @() [array]$stepsPlusStepproperties = @() $steppropertiestoadd = [ordered]@{} [bool]$custompool = ($job.target.PSobject.Properties.name.contains('queue')) [bool]$dependencies = ($job.PSobject.Properties.name.contains('dependencies')) [bool]$jobdemands = ($job.target.PSobject.Properties.name.contains('demands')) #populating stepproperties #first do checkout as it has to be the first task always if($definition.repository.properties.skipSyncSource -eq 'true'){ $steppropertiestoadd.add('checkout', 'none') }else { $steppropertiestoadd.add('checkout', 'self') } #adding other properties $repooptions = Get-TaskProperties -InputTaskObject $definition.repository -propertiestoskip @('properties','id','type','name','url','defaultBranch') $repooptions.PSobject.Properties | ForEach-Object{ $steppropertiestoadd.add($_.name,$_.value) } $repoproperties = Get-TaskProperties -InputTaskObject $definition.repository.properties -propertiestoskip @('labelSources','labelSourcesFormat','reportBuildStatus','cleanOptions','skipSyncSource') $repoproperties.PSobject.Properties | ForEach-Object{ if ($_.name -eq 'submodules') { $steppropertiestoadd.$($_.name) = $_.value }else{ $steppropertiestoadd.add($_.name,$_.value) } } if ($job.target.allowScriptsAuthAccessOption -eq 'true') { $steppropertiestoadd.add('persistCredentials', 'true') } if ($jobcount -gt 1 -or $custompool -or $pipelinedemands -or $jobdemands) { $definitionjob.add('job',$job.refName) ### Adding displayname $definitionjob.add('displayName',$job.name) ### add job pool properties if ($custompool -and $job.target.type -eq 1) { $poolToAdd = Get-AzDoAPIToolsAgentPool -poolURL $job.target.queue._links.self.href -AgentIdentifier $job.target.agentSpecification.identifier [array]$stepsPlusStepproperties = $steppropertiestoadd }elseif (!$custompool -and $job.target.type -eq 1) { $poolToAdd = Get-AzDoAPIToolsAgentPool -PoolURL $definition.queue._links.self.href -agentidentifier $definition.process.target.agentSpecification.identifier [array]$stepsPlusStepproperties = $steppropertiestoadd } elseif($job.target.type -eq 2){ $poolToAdd = 'server' [array]$stepsPlusStepproperties = @() } if ($pooltoAdd.count -ge 1) { $definitionjob.add('pool',$poolToAdd) } ### Adding job demands if ($jobdemands) { $demandstoadd += $job.target.demands } if ($pipelinedemands) { $demandstoadd += $definition.demands } if ($demandstoadd.count -ge 1 -and $job.target.type -eq 1) { if($definitionjob.Contains('pool')){ $definitionjob.pool.add('demands',$demandstoadd) }else{ Write-Error "No Pool construct found to add demands to." } } ### Adding Job Properties #pipeline specific timeouts if($definition.jobTimeoutInMinutes -ne 60){ $definitionjob.add('timeoutInMinutes',$definition.jobTimeoutInMinutes) } if($definition.jobCancelTimeoutInMinutes -ne 5){ $definitionjob.add('cancelTimeoutInMinutes',$definition.jobCancelTimeoutInMinutes) } #job specific properties $jobproperties = Get-TaskProperties -InputTaskObject $job -propertiestoskip @('steps','target','name','refname','jobAuthorizationScope','dependencies') $jobproperties.PSObject.Properties | ForEach-Object{ if ($_.name -eq 'timeoutInMinutes' -and ($definitionjob.timeoutInMinutes)) { $definitionjob.timeoutInMinutes = $_.value }elseif ($_.name -eq 'cancelTimeoutInMinutes' -and ($definitionjob.cancelTimeoutInMinutes)) { $definitionjob.cancelTimeoutInMinutes = $_.value }else{ $definitionjob.add($_.name,$_.value) } } #add section for dependancies if ($dependencies) { $definitionjob.add('dependsOn',$job.dependencies.scope) } #populating jobs/steps [array]$steps = Convert-TaskStepsToYAMLSteps -InputArray $job -Projectname $projectname -profilename $profilename -inputtype $definitiontype -ExpandNestedTaskGroups:$ExpandNestedTaskGroups.isPresent [array]$stepsPlusStepproperties += $steps $definitionjob.add('steps',$stepsPlusStepproperties) $definitionjobs += $definitionjob }else{ $definitionsteps.add('steps',$stepsPlusStepproperties) } } if ($jobcount -gt 1 -or $custompool -or $pipelinedemands -or $jobdemands) { $retunreddefinitionjobs.add('jobs',$definitionjobs) return $retunreddefinitionjobs }else{ return $definitionsteps } } } } function Get-AzDoAPIToolsDefinitionTriggersAsYAMLPrepped { [CmdletBinding()] param ( # InputObjects [Parameter(Mandatory=$true)] [Array] $InputDefinitions ) process { foreach ($definition in $InputDefinitions) { $definition = $definition.value if ($definition.triggers) { $citriggers = $definition.triggers | Where-Object {$_.triggerType -eq 'continuousIntegration'} if ($citriggers) { $triggers = [ordered]@{} if ($citriggers.batchChanges -eq 'true') { $triggers.add('batch',$citriggers.batchChanges) } if($citriggers.branchFilters){ $branchtriggers = Get-DefinitionInputIncludeExclude -inputs $citriggers.branchFilters if ($branchtriggers.count -ge 1) { $triggers.add('branches', $branchtriggers) } } if($citriggers.pathFilters){ $pathtriggers = Get-DefinitionInputIncludeExclude -inputs $citriggers.pathFilters if ($pathtriggers.count -ge 1) { $triggers.add('paths' , $pathtriggers) } } if ($triggers.Count -ge 1) { $returnedtriggerobject = @{ 'trigger' = $triggers } } } } return $returnedtriggerobject } } } function Get-AzDoAPIToolsDefinitionVariablesAsYAMLPrepped { [CmdletBinding()] param ( # InputObjects [Parameter(Mandatory=$true)] [Array] $InputDefinitions ) process { foreach ($definition in $InputDefinitions) { $definition = $definition.value $variables = @() $parameters = @() if ($definition.variables) { foreach ($var in $definition.variables.psobject.properties){ if (!$var.value.issecret) { if ($var.value.allowOverride) { $parameter = [ordered]@{} $parameter.Add('name',$var.name) $parameter.Add('type','string') $parameter.Add('default',$var.value.value) $parameters += $parameter }else{ $variable = [ordered]@{} $variable.Add('name',$var.name) $variable.Add('value',$var.value.value) $variables += $variable } } else{ Write-Verbose "variable $($var.name) is secret. not exporting to yaml. Please bear in mind to add this variable yourself" } } } if($definition.variableGroups){ foreach($vargroup in $definition.variableGroups){ $variable = @{} $variable.Add('group',$vargroup.name) $variables += $variable } } $returnvar = [ordered]@{} if ($parameters.count -ge 1) { $returnvar.Add('parameters',$parameters) } if ($variables.count -ge 1) { $returnvar.Add('variables',$variables) } return $returnvar } } } function Get-AzDoAPIToolsDefinitonsTaskGroupNames { param ( # type [Parameter(mandatory=$true)] [ValidateSet('BuildDefinition','ReleaseDefinition','TaskGroup')] [string] $ApiType, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Profilename [Parameter(mandatory=$false)] [string] $profilename ) switch ($apitype) { BuildDefinition { $response = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'build' -resource 'definitions' -profilename $profilename -version '5.1-preview' } ReleaseDefinition { $response = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'release' -resource 'definitions' -profilename $profilename -version '5.1-preview' } TaskGroup { $response = Use-AzDoAPI -method 'Get' -projectname $Projectname -area 'distributedtask' -resource 'taskgroups' -profilename $profilename -version '5.1-preview' } Default {Write-Error "unaccepted type found. Accepted values are BuildDefintion, ReleaseDefinition, TaskGroup"} } $response = $response.value | Select-Object -ExpandProperty Name | Get-Unique return $response } function Get-AzDoAPIToolsTaskGroupAsYAMLPrepped { param ( # Parameter help description [Parameter(mandatory=$true)] [Array] $TaskGroupsToConvert, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Profilename [Parameter(mandatory=$false)] [string] $profilename, # OutputPath [Parameter(mandatory=$true)] [string] $OutputPath, # future switch for expanding nested taskgroups [Parameter(mandatory=$false)] [switch] $ExpandNestedTaskGroups, # Switch to output file instead of retruning an object [Parameter(mandatory=$false)] [switch] $Outputasfile ) Import-Module powershell-yaml foreach ($TaskGroup in $TaskGroupsToConvert) { $yamlTemplate = [PSCustomObject]@{} $inputs = Convert-TGInputsToYamlTemplateInputs -InputArray $TaskGroup -Projectname $Projectname -profilename $profilename -ExpandNestedTaskGroups:$ExpandNestedTaskGroups.IsPresent $steps = Convert-TaskStepsToYAMLSteps -InputArray $TaskGroup -projectname $projectname -profilename $profilename -ExpandNestedTaskGroups:$ExpandNestedTaskGroups.IsPresent -inputtype $TaskGroup.type $yamlTemplate | Add-Member -NotePropertyName 'parameters' -NotePropertyValue $inputs $yamlTemplate | Add-Member -NotePropertyName 'steps' -NotePropertyValue $steps if ($outputasfile.IsPresent) { if (!$outputpath) { Write-Error "You have used the -Outputfile switch without mentioning OutputPath" }else{ Convert-AzDoAPIToolsYamlObjectToYAMLFile -InputObject $yamlTemplate -outputpath $OutputPath -Outputfilename "$($taskgroup.name).yml" } }else { return $yamlTemplate } } } function Set-AzdoAPIToolsConfig { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] $configfilepath ) process { if(!$configfilepath){ Write-verbose "No `$configfilepath parameter supplier. setting to default path." $configfilepath = "{0}\AzDoAPITools\config.json" -f $env:appdata } if (Test-Path $configfilepath) { Write-Verbose "Found an existing configfile in $configfilepath. loading it" $existingconfig = Get-AzdoAPIToolsConfig -configfilepath $configfilepath if (Get-Confirmation "Do you want to overwrite the existing config in [$configfilepath] (Y) or add to / replace in existing config (N)?") { $OutConfig = Get-AzDoAPIToolsConfigDetails -new }else{ $config = Get-AzDoAPIToolsConfigDetails | ConvertFrom-Json $matchingobject = $existingconfig.profiles | Where-Object {$_.profilename -eq $config.profilename} if( $matchingobject ){ Write-Host "Found [$($config.profilename)] in existing configfile. OverWriting existing entry" $matchingobject.Organization = $config.Organization $matchingobject.PAT = $config.PAT $OutConfig = $existingconfig | ConvertTo-Json -Depth 3 }else{ Write-Verbose "found no profile with current details. Adding the new config" $existingconfig.profiles += $config $OutConfig = $existingconfig | ConvertTo-Json -Depth 3 } } }else{ Write-verbose "no configfile found at $configfilepath. Continuing new file setup" $OutConfig = Get-AzDoAPIToolsConfigDetails -new } if ($OutConfig) { $OutConfig | Out-File $configfilepath } } } function Convert-TaskIDToYAMLTaskIdentifier { param ( # TaskID [Parameter(Mandatory=$true)] [guid] $InputTaskID, [Parameter(mandatory=$false)] [string] $profilename, # TaskVersion [Parameter(Mandatory=$true)] [int] $InputTaskVersion ) $task = Use-AzDoAPI -method 'Get' -area 'distributedtask' -resource 'tasks' -profilename $profilename -version '5.1' -id $InputTaskID $task = $task.value | Where-Object {$_.version.major -eq $InputTaskVersion} if($task -and $task.count -le 1){ $taskcontributionIdentifier = $task.contributionIdentifier $taskname = $task.name if ($taskcontributionIdentifier) { $yamltaskid = "$taskcontributionIdentifier.$taskname@$InputTaskVersion" }else{ $yamltaskid = "$taskname@$InputTaskVersion" } } Return $yamltaskid } function Convert-TaskStepsToYAMLSteps { param ( # input array [Parameter()] [Array] $InputArray, # inputtype [Parameter(mandatory=$true)] [string] $inputtype, # inputtype [Parameter(mandatory=$false)] [string] $parentinputtype, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Projectname [Parameter(mandatory=$false)] [string] $profilename, # future switch for expanding nested taskgroups [Parameter(mandatory=$false)] [switch] $ExpandNestedTaskGroups ) foreach ($input in $InputArray) { if ($inputtype -eq 'BuildDefinition') { $steps = $input.steps }elseif ($inputtype -eq 'TaskGroup') { $steps = $input.value.tasks } if($steps.count -ge 1){ $taskscount = $input.count [array]$convertedsteps = @() $convertedcount = 0 Write-verbose "Found $taskscount tasks" foreach ($step in $steps) { $yamlstep = [ordered]@{} $stepid = $step.task.id $stepversion = $step.task.versionspec.split(".`*")[0] -as [int] if($step.task.definitionType -eq 'task'){ #####converting TaskID to YAML taskidentifier Write-verbose "version $stepversion found in $inputtype for task $stepid" $yamltaskid = Convert-TaskIDToYAMLTaskIdentifier -InputTaskID $stepid -InputTaskVersion $stepversion -profilename $profilename $yamlstep.add('task',$yamltaskid) ### Adding Other Task Properties to Step Object $taskproperties = Get-TaskProperties -InputTaskObject $step -propertiestoskip @('environment','inputs','task','AlwaysRun','refName') $taskproperties.PSObject.Properties | ForEach-Object{ $yamlstep.add($_.name,$_.value) } #### Adding Inputs to the task $YamlInputs = Get-TaskInputs -InputTaskObject $step -profilename $profilename -inputType $inputtype -parentinputtype $parentinputtype if ($YamlInputs.count -ge 1) { $yamlstep.add('inputs', $YamlInputs) } #### Adding Step to Steps [array]$convertedsteps += $yamlstep }elseif($step.task.definitionType -eq 'metaTask' -and $step.enabled -eq 'true'){ $TGtemplate = Get-AzDoAPIToolsDefinitionsTaskGroupsByID -ID $stepid -TGVersion $stepversion -ApiType 'TaskGroup' -Projectname $projectname -profilename $profilename if ($ExpandNestedTaskGroups.IsPresent) { [array]$nestedtaskgrouptasks = Convert-TaskStepsToYAMLSteps -profilename $profilename -Projectname $projectname -InputArray $TGTemplate -ExpandNestedTaskGroups -inputType $tgtemplate.type -parentinputtype $inputtype [array]$convertedsteps += $nestedtaskgrouptasks }else { $TGTemplateName = "$($TGTemplate.name).yml" $yamlstep.add('template',$TGTemplateName) $TGTemplateparameters = $YamlInputs = Get-TaskInputs -InputTaskObject $step -profilename $profilename -inputType $inputtype if ($TGTemplateparameters.count -ge 1) { $yamlstep.add('parameters', $TGTemplateparameters) } [array]$convertedsteps += $yamlstep } } Write-Verbose "added taskids for $convertedcount steps" } }else{ Write-Verbose 'No Tasks found. skipping steps.' } if ($convertedsteps.Count -lt 1) { [array]$convertedsteps = @() } return [array]$convertedsteps } } function Convert-TGInputsToYamlTemplateInputs { param ( # input array [Parameter()] [Array] $InputArray, # Projectname [Parameter(mandatory=$true)] [string] $Projectname, # Projectname [Parameter(mandatory=$false)] [string] $profilename, # future switch for expanding nested taskgroups [Parameter(mandatory=$false)] [switch] $ExpandNestedTaskGroups ) foreach ($taskgroup in $InputArray) { $InputsArray = @() $taskgroup = $taskgroup.value if($taskgroup.inputs.count -ge 1){ $taskgroup.inputs | ForEach-Object { $yamlparam = [ordered]@{} $yamlparam.Add('name', $_.name) $yamlparam.Add('type', 'string') $InputsArray += $yamlparam } }else{ Write-Verbose "No inputs found for task group. skipping yaml generation" } if($ExpandNestedTaskGroups.IsPresent){ $taskgrouptasks = $taskgroup.tasks | Where-Object {$_.task.definitionType -eq 'metaTask'} $taskgrouptaskscount = $taskgrouptasks | Measure-Object | Select-Object -ExpandProperty count if($taskgrouptaskscount -ge 1){ foreach ($nestedtaskgrouptask in $taskgrouptasks) { $nestedtaskgroupid = $nestedtaskgrouptask.task.id $nestedtaskgroupversion = $nestedtaskgrouptask.task.versionspec.split(".`*")[0] -as [int] $nestedtaskgroup = Get-AzDoAPIToolsDefinitionsTaskGroupsByID -apitype 'Taskgroup' -projectname $Projectname -profilename $profilename -id $nestedtaskgroupid -TGVersion $nestedtaskgroupversion $nestedtaskgroupinputs = Convert-TGInputsToYamlTemplateInputs -profilename $profilename -Projectname $Projectname -InputArray $nestedtaskgroup -ExpandNestedTaskGroups foreach ($nestedinput in $nestedtaskgroupinputs) { if ($nestedinput.name -notin $InputsArray.name) { $InputsArray += $nestedinput } } } } } return $InputsArray } } function Get-AzDoAPIToolsAgentPool { [CmdletBinding()] param ( # PoolURL [Parameter(Mandatory = $true)] [String] $PoolURL, # agentidentifier [Parameter(Mandatory = $false)] [String] $agentidentifier ) process { $returnedpool = [ordered]@{} $pool = Use-AzDoAPI -url $PoolURL -method 'get' if ($pool.pool.isHosted -eq 'true') { $returnedpool.Add('vmImage',$agentidentifier) }else{ $returnedpool.add('name',$pool.name) } } end { if ($returnedpool.Count -ge 1) { return $returnedpool } } } function Get-AzdoAPIToolsConfig { [CmdletBinding()] param ( [Parameter(Mandatory = $false)] $configfilepath ) if(!$configfilepath){ Write-verbose "No `$configfilepath parameter supplier. setting to default path." $configfilepath = "{0}\AzDoAPITools\config.json" -f $env:appdata # $configfilepath = "$((Get-Item $PSScriptRoot).Parent.FullName)\config\config.json" } if (Test-Path $configfilepath) { $configJSON = Import-JSON -JSONFile $configfilepath return $configJSON }else{ Write-Host "No config file found at $configfilepath" if (Get-Confirmation "Would you like to create a new config file in $configfilepath ?") { Set-AzDoAPIToolsConfig -configfilepath $configfilepath $configJSON = Import-JSON -JSONFile $configfilepath return $configJSON }else{ Write-Error "no configfile found at $configfilepath. please run Set-AzDoAPIToolsConfig to create a profile" } } } function Get-AzdoAPIToolsConfigDetails { param ( # New Switch [Parameter(mandatory=$false)] [switch] $new ) $profilename = Read-Host -prompt "Please provide an name / alias for the organization you want to add" $organization = Read-Host -prompt "Please provide the organization name for the Azure DevOps instance you want to connect to (https://dev.azure.com/<organizationname>)" $pat = Read-Host -prompt "Please provide a valid PAT string you want to add for $organization" -AsSecureString $pat = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pat)) $encodedpat = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("basic:$pat")) $JSONDetailConstruct = @" { "profilename": "$profilename", "Organization": "$organization", "pat": "$encodedpat" } "@ $JSONFULL = @" { "profiles":[ $JSONDetailConstruct ] } "@ if ($profilename -and $organization -and $pat) { if ($new.IsPresent) { $return = $JSONFULL }else{ $return = $JSONDetailConstruct } }else{ Write-error "one or more questions were not answered. please retry" } Return $return } function Get-AzdoAPIToolsProfile { [cmdletbinding()] param ( [Parameter(Mandatory = $false)] [psobject] $configfile, # profilename [Parameter(mandatory = $false)] [string] $profilename ) process{ if(!$configfile){ Write-verbose "No `$configfile parameter supplied. attempting to grab it from config" $configfile = Get-AzdoAPIToolsConfig } if ($configfile.profiles) { if ($profilename) { Write-verbose "Searching profiles for $profilename" $configprofile = $configfile.profiles | Where-Object {$_.profilename -eq $profilename} }else{ Write-Verbose "Returning first profile found" $configprofile = $configfile.profiles | Select-Object -First 1 } if ($configprofile) { return $configprofile }else{ Write-Error "Unable to find $profilename in configfile provided" throw; } }else{ Write-Error 'Unable to find profiles section in configfile' throw; } } } function Get-AzdoAPIURL { [CmdletBinding()] param( [string]$profilename, [string]$area, [string]$resource, [string]$id, [string]$subdomain, [string]$projectname, [string]$version ) process { $profile = Get-AzdoAPIToolsProfile -profilename $profilename $subdomain = $profile.Organization $sb = New-Object System.Text.StringBuilder $sb.Append('Https://') | Out-Null if($area -eq 'Release'){ $sb.Append('vsrm.') | Out-Null } $sb.Append('dev.azure.com') | Out-Null $sb.Append("/$subdomain") | Out-Null if ($projectname) { $sb.Append("/$projectname") | Out-Null } $sb.Append("/_apis") | Out-Null if ($area) { $sb.Append("/$area") | Out-Null } if ($resource) { $sb.Append("/$resource") | Out-Null } if ($id) { $sb.Append("/$id") | Out-Null } if ($version) { $sb.Append("?api-version=$version") | Out-Null } $url = $sb.ToString() return $url } } function Get-Confirmation ( [string] $message ) { do { $input = Read-Host -prompt "$message (Y/N)? " if ($input -Like 'Y') { return $true } elseif ($input -Like 'N') { return $false } } while (true); } function Get-CronFromSchedule { [CmdletBinding()] param ( # InputSchedule [Parameter(mandatory=$true)] [array] $InputSchedules ) begin { $weekdays =[ordered]@{ 'Sunday' = 64 'Monday' = 1 'Tuesday' = 2 'Wednesday' = 4 'Thursday' = 8 'Friday' = 16 'Saturday' = 32 } $schedulehour = $null $scheduleminute = $null $scheduledaysofweek = @() } process { foreach ($schedule in $InputSchedules) { if ($schedule.daysToBuild -eq 'all') { $bitvalue = ($weekdays.Values | Measure-Object -Sum).Sum }elseif ($schedule.daysToBuild -in $weekdays.Keys) { $bitvalue = $weekdays[$schedule.daysToBuild] }else{ $bitvalue = $schedule.daysToBuild } if ($bitvalue -gt 0) { $weekdays.getenumerator() | ForEach-Object{ if($_.value -band $bitvalue){ $targetdayofweekint = $($weekdays.keys).indexOf($_.key) $currentdayofweekint = (Get-date).DayOfWeek.value__ $datetimetoconvert = Get-Date -Date (Get-Date -Hour $schedule.startHours -Minute $schedule.startMinutes -Second 00).AddDays($targetdayofweekint - $currentdayofweekint).ToString('yyyy-MM-dd HH:mm:ss') $SourceTimezone = [System.TimeZoneInfo]::FindSystemTimeZoneById($schedule.timezoneid) if($SourceTimezone.SupportsDaylightSavingTime -eq $true -and $datetimetoconvert.IsDaylightSavingTime() -eq $true){ $datetimetoconvert = $datetimetoconvert.AddHours(1) } $utcdatetimeobject = [System.TimeZoneInfo]::ConvertTimeToUtc($datetimetoconvert, $SourceTimezone) $schedulehour = $utcdatetimeobject.Hour $scheduleminute = $utcdatetimeobject.Minute $scheduledaysofweek += $utcdatetimeobject.DayOfWeek.value__ } } $CRON = "$scheduleminute $schedulehour * * $($scheduledaysofweek -join ',')" return $CRON } } } end{ } } function Get-DefinitionInputIncludeExclude { [CmdletBinding()] param ( # inputs [Parameter(mandatory=$true)] [array] $inputs ) begin{ $included = @() $excluded = @() $return = [ordered]@{} $regex = '^([\+\-])[\\\/]?(.+)' } process { foreach ($input in $inputs) { if ($input.startswith("+")){ $input = $input -replace $regex, '$2' $included += $input }elseif ($input.startswith("-")) { $input = $input -replace $regex, '$2' $excluded += $input } } } end{ if ($included.count -ge 1) { $return.add('include',$included) } if ($excluded.count -ge 1) { $return.add('exclude',$excluded) } return $return } } function Get-HighestTaskGroupVersion { param ( $TaskGroupObject, $Taskgroupid, # also return previews [Parameter(mandatory=$false)] [switch] $includeTGpreview ) $versionnumber = 0 if (-not $includeTGpreview.IsPresent) { $TaskGroupObject = $TaskGroupObject | Where-Object {!$_.Preview} } $TaskGroupObject | ForEach-Object { #figure out a way to easily include preview if ($_.id -eq $Taskgroupid){ if($_.version.major -gt $versionnumber){ $versionnumber = $_.version.major } } } # Write-Output "Highest version for [$($Taskgroupid)] was [$($versionnumber)]" return $versionnumber } function Get-TaskInputs { param ( # InputTaskObject [Parameter(Mandatory=$true)] [PSObject] $InputTaskObject, # inputtype [Parameter(mandatory=$true)] [string] $inputtype, # parentinputtype [Parameter(mandatory=$false)] [string] $parentinputtype, # profilename [Parameter(mandatory=$false)] [string] $profilename ) # $ReturnedYAMLInputs = [PSCustomObject]@{} $TaskInputProperties = @{} if(!$InputTaskObject.inputs){ }else{ if($InputTaskObject.task.definitionType -eq 'task'){ $InputTaskid = $InputTaskObject.task.id $InputTaskVersion = $InputTaskObject.task.versionspec.split(".`*")[0] -as [int] $task = Use-AzDoAPI -method 'Get' -area 'distributedtask' -resource 'tasks' -profilename $profilename -version '5.1' -id $InputTaskID $task = $task.value | Where-Object {$_.version.major -eq $InputTaskVersion} $taskdefaultinputs = $task.inputs } $InputTaskObject.Inputs.PSObject.Properties | ForEach-Object{ $regexpatternSingle = '(?<prefix>\$\()(?<FullVariable>(?<SingleVariable>\w+))(?<suffix>\))' $regexpatternDouble = '(?<prefix>\$\()(?<FullVariable>(?<DoubleVariable>(?<DoubleFirstPart>\w+)\.+(?<DoubleSecondPart>\w+)))(?<suffix>\))' $inputname = $_.name $inputvalue = $_.value if($InputTaskObject.task.definitionType -eq 'task'){ $defaultinput = $taskdefaultinputs | Where-Object {$_.name -eq $inputname} $defaultinputvalue = $defaultinput.defaultValue } if(( ($defaultinputvalue -ne $inputvalue) -or ($InputTaskObject.task.definitionType -ne 'task') ) ){ if ($inputtype -eq 'TaskGroup' -and $parentinputtype -ne 'BuildDefinition') { switch -regex ($inputvalue) { $regexpatternSingle { $inputvalue = $inputvalue -replace $regexpatternSingle, '${{parameters.$2}}' } $regexpatternDouble { $predefinedvariableprefixes = @('Build', 'Agent', 'System', 'Pipeline', 'Environment', 'Release') if ($matches.DoubleFirstPart -notin $predefinedvariableprefixes) { $inputvalue = $inputvalue -replace $regexpatternDouble, '${{parameters.$2}}' } } Default {} } } $TaskInputProperties.Add($inputname,$inputvalue) }else{ write-verbose "Skipping input since it matches defaultvalue" } } # $ReturnedYAMLInputs | Add-Member -NotePropertyName 'inputs' -NotePropertyValue $TaskInputProperties } return $TaskInputProperties } function Get-TaskProperties { param ( # InputTaskObject [Parameter(Mandatory=$true)] [PSObject] $InputTaskObject, # properties to skip [Parameter(Mandatory=$false)] [array] $propertiestoskip ) $FilteredTaskProperties = [PSCustomObject]@{} # $propertiestoskip = ('environment','inputs','task','AlwaysRun') $InputTaskObject.PSObject.Properties | ForEach-Object{ $propertyname = $_.Name $propertyvalue = $_.value ### processing skipped properties if($propertyname -notin $propertiestoskip){ ### Skipping Default values to keep output clean switch ($propertyname) { continueOnError { if($propertyvalue -ne $false){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } condition { if($propertyvalue -ne 'succeeded()'){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } enabled { if($propertyvalue -ne $true){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } timeoutInMinutes { if($propertyvalue -ne 0){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } jobTimeoutInMinutes { if($propertyvalue -ne 0){ $FilteredTaskProperties | Add-Member -NotePropertyName 'timeoutInMinutes' -NotePropertyValue $propertyvalue } } jobCancelTimeoutInMinutes { if($propertyvalue -ne 0){ $FilteredTaskProperties | Add-Member -NotePropertyName 'cancelTimeoutInMinutes' -NotePropertyValue $propertyvalue } } clean { if($propertyvalue -ne $false){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } gitLfsSupport { if($propertyvalue -ne $false){ $FilteredTaskProperties | Add-Member -NotePropertyName 'lfs' -NotePropertyValue $propertyvalue } } checkoutSubmodules { if($propertyvalue -ne $false){ $FilteredTaskProperties | Add-Member -NotePropertyName 'submodules' -NotePropertyValue $propertyvalue } } checkoutNestedSubmodules { if($propertyvalue -ne $false){ $FilteredTaskProperties | Add-Member -NotePropertyName 'submodules' -NotePropertyValue 'recursive' } } fetchDepth { if($propertyvalue -ne 0){ $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } Default { $FilteredTaskProperties | Add-Member -NotePropertyName $propertyname -NotePropertyValue $propertyvalue } } } } return $FilteredTaskProperties } function Import-JSON { param ( # Parameter help description [Parameter(Mandatory = $true)] [string] $JSONFile ) try { $JSONObject = Get-Content $JSONFile -Raw | ConvertFrom-Json return $JSONObject } catch { Write-Error "Invalid JSON File or unable to import" $Error exit 1 } } function Use-AzDoAPI { [CmdletBinding()] param( [Parameter(ValueFromPipeline = $true)] # [Object]$configprofile, [string]$profilename, [string]$area, [string]$resource, [string]$id, [ValidateSet('Get', 'Post', 'Patch', 'Delete', 'Options', 'Put', 'Default', 'Head', 'Merge', 'Trace')] [string]$method, [Parameter(ValueFromPipeline = $true)] [object]$body, [string]$Url, [string]$ContentType, [string]$subdomain, [string]$projectname, [string]$version, [string]$pat ) process { # If the caller did not provide a Url build it. if (-not $Url) { $buildUriParams = @{ } + $PSBoundParameters; $extra = 'method', 'body','ContentType','config','profile','pat' foreach ($x in $extra) { $buildUriParams.Remove($x) | Out-Null } $Url = Get-AzdoAPIURL @buildUriParams } $configprofile = Get-AzdoAPIToolsProfile -profilename $profilename $pat = $configprofile.pat if ($body) { Write-Verbose "Body $body" } $params = $PSBoundParameters $params.Add('Uri', $Url) $params.Add('TimeoutSec', 30) $params.Add('Headers', @{Authorization = "Basic $pat" }) $extra = 'profile', 'profilename', 'Area', 'Id', 'Url', 'Resource','config','profile','subdomain','projectname','version','pat' foreach ($e in $extra) { $params.Remove($e) | Out-Null } try { $resp = Invoke-RestMethod @params if ($resp) { Write-Verbose "return type: $($resp.gettype())" Write-Verbose $resp } return $resp } catch { Write-Error "$_" throw } } } |