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]@{}
           $phaserefs = @{}

           foreach ($job in $jobs) {
               $definitionsteps = [ordered]@{}
                $definitionjob = [ordered]@{}
              $steps = Convert-TaskStepsToYAMLSteps -InputArray $job -Projectname $projectname -profilename $profilename -inputtype $definitiontype -ExpandNestedTaskGroups:$ExpandNestedTaskGroups.isPresent
              
              [bool]$custompool = ($job.target.PSobject.Properties.name.contains('queue'))

              [bool]$dependencies = ($job.PSobject.Properties.name.contains('dependencies'))

              $phaserefs.Add($job.refName,$job.name)

              if ($jobcount -gt 1 -or $custompool) {
                  
                $definitionjob.add('job',$job.name.replace(" ","_"))
                ### add job pool properties
                if ($custompool) {

                    $poolToAdd = Get-AzDoAPIToolsAgentPool -poolURL $job.target.queue._links.self.href -AgentIdentifier $job.target.agentSpecification.identifier
                    if ($pooltoAdd.count -ge 1) {
                        $definitionjob.add('pool',$poolToAdd)
                    }
                }

                $jobproperties = Get-TaskProperties -InputTaskObject $job -propertiestoskip @('steps','target','name','refname','jobAuthorizationScope','dependencies')
                    
                $jobproperties.PSObject.Properties | ForEach-Object{
                    $definitionjob.add($_.name,$_.value)
                }

                #add section for dependancies
                if ($dependencies) {
                    $dependancy = $phaserefs.$($job.dependencies.scope)
                    $definitionjob.add('dependsOn',$dependancy.replace(" ","_"))
                }

                $definitionjob.add('steps',$steps)
                
                $definitionjobs += $definitionjob
              }else{
                $definitionsteps.add('steps',$steps)
              }
           }

           if ($jobcount -gt 1 -or $custompool) {
               $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.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 ($citriggers.batchChanges -eq 'true') {

                        $triggers.add('batch',$citriggers.batchChanges) 
                    }

                    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
            $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
                    $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) {
                        $nestedtaskgrouptasks = Convert-TaskStepsToYAMLSteps -profilename $profilename -Projectname $projectname -InputArray $TGTemplate -ExpandNestedTaskGroups -inputType $tgtemplate.type -parentinputtype $inputtype
                        $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)
                            
                        }
                        
                        $convertedsteps += $yamlstep
                    }
                    
                }
            
            Write-Verbose "added taskids for $convertedcount steps"
            }   

        }else{
            Write-Verbose 'No Tasks found. skipping steps.'
        }

    return $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 = @{}
        $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){
                            $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 = @()
        $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) {
            $includedinput = @{
                'include' = $included
            }
            $return += $includedinput
        }

        if ($excluded.count -ge 1) {
            $excludedinput = @{
                'exclude' = $excluded
            }
            $return += $excludedinput
        }  

        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
                    }
                }
                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
       }
    }
 }