USMC6072
USMC6072

Reputation: 668

How do I migrate Azure DevOPs Iterations through the API and PS

I am trying to migrate iterations between two ADOs. I can retrieve the list of iterations just fine but I get an error when doing the Invoke-RestMethod POST. Here is the error I receive from the POST:

Invoke-RestMethod : {"$id":"1","innerException":null,"message":"You must provide a value for the iteration parameter.","typeName":"Microsoft.VisualStudio.Services.Common.VssPropertyValidationException, Microsoft.VisualStudio.Services.Common","typeKey":"VssPropertyValidationException","errorCode":0,"eventId":3000} At C:\Users\Chris\Downloads\MigrateIterations.ps1:70 char:25

  • ... $response = Invoke-RestMethod -Uri $TargetURL -Headers $Header -Metho ...
  •             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    • FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

As I read the error I assume this is the salient part:

Invoke-RestMethod : {"$id":"1","innerException":null,"message":"You must provide a value for the iteration  parameter.","typeName"

But there is no iteration parameter "typeName", or at least none I could find in any documentation. I even asked ChatGPT to write the script for me and it was essentially identical. The error must not be literal but I can't find anything close online.

I tested my auth to the Target by retrieving a list of Iterations in the project and that worked fine, so my token is good.

Here is my script

$UserName = '[email protected]'
$SourceToken = <PAT>
$TargetToken = <PAT>
$SourceOrg = 'FirstADO'
$SourceProject = 'ProjectA'
$TargetOrg = 'SecondADO'
$TargetProject = 'ProjectB'

$URLBase = "https`://dev.azure.com/"

#log into source ADO
$sourceBase64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UserName, $SourceToken)))
$Header = @{
    Authorization = ("Basic {0}" -f $SourceBase64AuthInfo)
}

$SourceURL = "$URLBase/$SourceOrg/$SourceProject/_apis/work/teamsettings/iterations?api-version=6.0"
Write-Output $SourceURL

Try {
    $SourceIterations = (Invoke-RestMethod $SourceURL -Headers $Header).value
}
Catch {
    if ($_ -match "Access Denied") {
        Throw "Access has been denied, please check your token"
    }
    else {
        Throw $_
    }
}

#log into target ADO
$TargetBase64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UserName, $TargetToken)))
$Header = @{
    Authorization = ("Basic {0}" -f $TargetBase64AuthInfo)
}
$TargetURL = "$URLBase/$TargetOrg/$TargetProject/_apis/work/teamsettings/iterations?api-version=6.0"
Write-Output $TargetURL

Try {
    $TargetIterations = (Invoke-RestMethod $TargetURL -Headers $Header).value
}
Catch {
    if ($_ -match "Access Denied") {
        Throw "Access has been denied, please check your token"
    }
    else {
        Throw $_
    }
}

foreach ($item in $SourceIterations) {
    Write-Output $item

    $iterationPath = "{0}\{1}" -f $TargetProject, $item.name
    $jsonData = '{{
      "name": "{0}",
      "attributes": {{
        "startDate": {1},
        "finishDate": {2} 
        }}
    }}' -f $item.name, $item.attributes.startDate, $item.attributes.finishDate

    $JSON = $jsonData | ConvertTo-Json

    try {
            $response = Invoke-RestMethod -Uri $TargetURL -Headers $Header -Method Post -Body $JSON -ContentType application/json
    Write-Output $response.value
    }
    catch {
        Write-Output $_
    }

}

Upvotes: 0

Views: 98

Answers (1)

Ziyang Liu-MSFT
Ziyang Liu-MSFT

Reputation: 5296

  1. To create a new iteration, you should use REST API Classification Nodes - Create Or Update.

    Sample request:

POST https://dev.azure.com/fabrikam/Fabrikam-Fiber-Git/_apis/wit/classificationnodes/Iterations?api-version=5.0

{
  "name": "Final Iteration",
  "attributes": {
    "startDate": "2014-10-27T00:00:00Z",
    "finishDate": "2014-10-31T00:00:00Z"
  }
}

  1. Remove ConvertTo-Json as mentioned by @Mathias R. Jessen.

  2. Add " to startDate and finishDate.

Below are the modified scripts for your reference:

$UserName = '[email protected]'
$SourceToken = <PAT>
$TargetToken = <PAT>
$SourceOrg = 'FirstADO'
$SourceProject = 'ProjectA'
$TargetOrg = 'SecondADO'
$TargetProject = 'ProjectB'

$URLBase = "https`://dev.azure.com/"

#log into source ADO
$sourceBase64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UserName, $SourceToken)))
$Header = @{
    Authorization = ("Basic {0}" -f $SourceBase64AuthInfo)
}

$SourceURL = "$URLBase/$SourceOrg/$SourceProject/_apis/work/teamsettings/iterations?api-version=6.0"
Write-Output $SourceURL

Try {
    $SourceIterations = (Invoke-RestMethod $SourceURL -Headers $Header).value
}
Catch {
    if ($_ -match "Access Denied") {
        Throw "Access has been denied, please check your token"
    }
    else {
        Throw $_
    }
}

#log into target ADO
$TargetBase64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UserName, $TargetToken)))
$Header = @{
    Authorization = ("Basic {0}" -f $TargetBase64AuthInfo)
}
$TargetURL = "$URLBase/$TargetOrg/$TargetProject/_apis/work/teamsettings/iterations?api-version=6.0"
Write-Output $TargetURL

Try {
    $TargetIterations = (Invoke-RestMethod $TargetURL -Headers $Header).value
}
Catch {
    if ($_ -match "Access Denied") {
        Throw "Access has been denied, please check your token"
    }
    else {
        Throw $_
    }
}

$newTargetUrl = "$URLBase/$TargetOrg/$TargetProject/_apis/wit/classificationnodes/Iterations?api-version=5.0"

foreach ($item in $SourceIterations) {
    Write-Output $item

    $iterationPath = "{0}\{1}" -f $TargetProject, $item.name
    $jsonData = '{{
      "name": "{0}",
      "attributes": {{
        "startDate": "{1}",
        "finishDate": "{2}" 
        }}
    }}' -f $item.name, $item.attributes.startDate, $item.attributes.finishDate

    $jsonData

    try {
            $response = Invoke-RestMethod -Uri $newTargetUrl -Headers $Header -Method Post -Body $jsonData -ContentType application/json
    Write-Output $response.value
    }
    catch {
        Write-Output $_
    }

}

Upvotes: 1

Related Questions