Create TFS Tasks from XML with validation
#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."
}