Create TFS Tasks from XML with validation

15/10/2012 17:33


#we have two files, one for TFS Tasks creation and one with XSD validation.

#Then your xml file and xsd file are needed.

#TFS Task creation file

 

#input parameters
$sourceFilePath="c:\temp\TFSdefaultTasksXMLinvalid1.xml"
$XsdSchemaPath="c:\temp\TFSdefaultTasksXML.xsd"
$parentId=391
$defaultPriority=1000
#takes component name from areaPath (e. g. External Video)
$prefix="External Video: "#($areaPath.Split('\')[1]).split('.')[1].Trim()
#test if file exists
if(!(test-path $sourceFilePath))
{
    write-host "Source XML file doesn't exist at " $sourceFilePath
    break
}
#get xml file
$xml=[xml](Get-Content -path $sourceFilePath)
if($xml -eq $null)
{
    write-host "Source XML file is not in valid XML format."
    break 
}
#test if XSD file exists
if(!(test-path $XsdSchemaPath))
{
    write-host "XSD file doesn't exist at " $XsdSchemaPath
    break
}
#validate XML against XSD
#include script
$originalLocation=(get-location).Path
$currentLocation=$myinvocation.InvocationName | split-path -parent
set-location $currentLocation
$wasIncluded=$false
$IsValid=$false
. .\XsdValidationSeparate.ps1
#go back to the original location
set-location $originalLocation
if(-not $wasIncluded)
    {
        write-host "Script for XML validation was not included. Check if it's in the same folder as this script."
        break
    }
   
if($IsValid)
{
  write-host "XML is valid." 
}
else
{
  write-host "XML validation failed."
  break
}   
#input parameters validation
if($parentId -eq $null)
{
    write-host "Parent id cannot be null."
    break      
}            
 #save previous setting
 $defaultErrorAction=$ErrorActionPreference
 #Force program to catch exception in case of error
 $ErrorActionPreference="Stop"
try
{
    #load assemblies
    [void][System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.TeamFoundation.Client")
    [void][System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.TeamFoundation.WorkItemTracking.Client")
    #get TFS structure, project
    [psobject] $tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer(“https://tfs.dcs.avd.com/tfs/chamcf")
    $WorkItemStore=$tfs.TfsTeamProjectCollection.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
    $project=$WorkItemStore.Projects["SomeArea"]
    $type=$project.WorkItemTypes["Task"]
    #if used parameters are valid
    $parent=$WorkItemStore.GetWorkItem($parentId)
    $areaPath=$parent.AreaPath
    $iterationPath=$parent.IterationPath
   
    #create parent-child relationship
    $linkType = $WorkItemStore.WorkItemLinkTypes[[Microsoft.TeamFoundation.WorkItemTracking.Client.CoreLinkTypeReferenceNames]::Hierarchy]
}
catch [Exception]
{
            write-host "Error occured when loading assemblies, TFS work item store or parent loading."
            write-error $_
            #restore original setting for catching errors
            $ErrorActionPreference=$defaultErrorAction
            break
}
#xml structure, schema only, not used in script
$s="    

 

              "
function CreateTaskInTFS
   {
    param(
   
    [string]$itemTitle,
    [string]$itemDescription,
    [int]$priority,
    [int]$time,
    [string]$areaPath,
    [string]$iterationPath
    )
   
 #define new instance of link object
 #save previous setting
$defaultErrorAction=$ErrorActionPreference
#Force program to catch exception in case of error
$ErrorActionPreference="Stop"
 try
 {
    $link = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemLink($linkType.ReverseEnd, $parentId)
 }
 catch [Exception]
 {
    write-host "Error occured while creating link to parent. This item will be skipped."
    return $false
 }
 finally
 {
    #restore original setting for catching errors
    $ErrorActionPreference=$defaultErrorAction
 }
   
 #create Task
 $item = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem $type
 #set properties
 #properties from common file - different for each item
 #item title is required
 if([string]::IsNullOrEmpty($itemTitle))
  {
    write-host "Required Task title is not defined in XML file. This item will be skipped."
    return $false
  }
  else
  {
    #add component prefix to the title
    $taskTitle=[string]::format("{0}: {1}", $prefix, $itemTitle);
    $item.Title = $taskTitle
 }
 #fill custom fields
 $item.Fields["Description HTML"].Value = $itemDescription
 #set default priority, if it's not set in xml file
 if(($priority -eq 0) -or [string]::IsNullOrEmpty($priority))
 {
    $priority = $defaultPriority
 }
 #set 'Continue' in case custom fields doesn't exist
 #no matter, it's ok just to skipp them
 $ErrorActionPreference="Continue"
 $item.Fields["Backlog Priority"].Value = $priority
 $item.Fields["Remaining Work"].Value = $time
 #properties from input parameters - common for all items
 $item.AreaPath = $areaPath
 $item.IterationPath = $iterationPath
 $item.WorkItemLinks.Add($link)
 #save task
 $item.Save()
   #print information on screen
    write-host "Title:" $itemTitle
    write-host "Desc:"$itemDescription
    write-host "Priority: "$priority
    write-host "Time: "$time
    write-host "Area: "$areaPath
    write-host "Iteration Path: "$iterationPath
    write-host "Link: "$link
 
 write-host "New Task created."  
 
}
#Call function for each task item in XML file
$i=1
foreach($xmlTask in $xml.Tasks.Task)
{
    write-host ""
    write-host "-----------"
    write-host "Item " $i
    write-host "-----------"
    write-host "Parent Id: "$parentId
   
    CreateTaskInTFS -itemTitle $xmlTask.Title -itemDescription $xmlTask.Description -priority $xmlTask.Priority -time $xmlTask.Effort -areaPath $areaPath -iterationPath $iterationPath|out-null
    $i++
  
}
#Code in XsdValidationSeparate.ps1 file - XSD validation is separated
$wasIncluded=$true
Function Validate-Xml{
    param(    
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [xml]$xml,
        [Parameter(
            Mandatory = $true,
            Position = 1,
            ValueFromPipeline = $false)]
        [string]$schema
        )
    #Declare the array to hold our error objects   
    [int]$errorCount=0;
        
    #Check to see if we have defined our namespace cache variable,     #and create it if it doesnt exist. We do this in case we want to make
    #lots and lots of calls to this function, to save on excessive file IO.
    if (-not $schemas){ ${GLOBAL:schemas} = @{} }
    
     #Remove our schema from $scheams due to error it already exists
     if($schemas.ContainsKey($schema))
        {
            $schemas.Remove($schema)
        }
    
    
    #Check to see if the namespace is already in the cache,if not then add it    #     #     if (-not $schemas[$schema]) {
        #Read in the schema file        [xml]$xmlschema = Get-Content $schema
        #Extract the targetNamespace from the schema        $namespace = $xmlschema.get_DocumentElement().targetNamespace
        #Add the schema/namespace entry to the global hashtable        $schemas.Add($schema,$namespace)
        #
    }
    else {
        #         #Pull the namespace from the schema cache        $namespace = $schemas[$schema]
    }
    #     #Define the script block that will act as the validation event handler$code = @'
   $errorCount++;
   Write-Host $("`nError found in XML: " + $_.Message + "`n") -ForegroundColor Red
'@
    #Convert the code block to as ScriptBlock    $validationEventHandler = [scriptblock]::Create($code)
    
    #Create a new XmlReaderSettings object    $rs = new-object System.Xml.XmlreaderSettings
    #Load the schema into the XmlReaderSettings object    [Void]$rs.schemas.add($namespace,(new-object System.Xml.xmltextreader($schema)))
    #Instruct the XmlReaderSettings object to use Schema validation    $rs.validationtype = "Schema"
    $rs.ConformanceLevel = "Auto"
    #Add the scriptblock as the ValidationEventHandler    $rs.add_ValidationEventHandler($validationEventHandler)
    
    #Create a temporary file and save the Xml into it    $xmlfile = [System.IO.Path]::GetTempFileName()
    $xml.Save($xmlfile)
    
    #Create the XmlReader object using the settings defined previously    $reader = [System.Xml.XmlReader]::Create($xmlfile,$rs)
    
    #Temporarily set the ErrorActionPreference to SilentlyContinue,     #as we want to use our validation event handler to handle errors
    $previousErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = "SilentlyContinue"
    
    #Read the Xml using the XmlReader    while ($reader.read()) {$null}
    #Close the reader    $reader.close()
    
    #Delete the temporary file    Remove-Item $xmlfile
    
    #Reset the ErrorActionPreference back to the previous value    $ErrorActionPreference = $previousErrorActionPreference 
    
    #Return the array of validation errors    return $errorCount
}
#
################################################################
## Start script
################################################################
#call function
$xmlFileName=$sourceFilePath #"c:\temp\TFSdefaultTasksXML.xml"
$schema = $XsdSchemaPath #"c:\temp\TFSdefaultTasksXML.xsd"
[xml]$xml = gc $xmlFileName
$result=0
$result = Validate-Xml $xml $schema
#Show result in case of valid
if([int]::Parse($result) -eq 0)
{
    $IsValid=$true;
    #write-host "XML is valid."
}
else
{
    $IsValid=$false;
    #write-host "XML is not valid."
}