-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathen.search-data.min.897570ab34c220f22bea7259b3b408e213bce0c7248b059c19f63ecc19ecca4a.js
1 lines (1 loc) · 190 KB
/
en.search-data.min.897570ab34c220f22bea7259b3b408e213bce0c7248b059c19f63ecc19ecca4a.js
1
'use strict';(function(){const indexCfg={cache:true};indexCfg.doc={id:'id',field:['title','content'],store:['title','href'],};const index=FlexSearch.create('balance',indexCfg);window.bookSearchIndex=index;index.add({'id':0,'href':'/docs/v3/setup/azdo-authn/','title':"Azure DevOps Personal Access Token",'content':"Azure DevOps Personal Access Token (PAT) # The below instructions are based on Create personal access tokens to authenticate access.\nA PAT has the same or less permissions than the person/identity that creates it. We recommend that the PAT is issued by an Azure DevOps Organization Administrator Identity.\nKeep in mind that a PAT has a limited timespan and must be periodically renewed. If you don\u0026rsquo;t, the Rules will stop working. When using the impersonate directive, mapping a rule to execute impersonated or configuring a rule impersonated, the used identity for creating the PAT must have the permission: \u0026ldquo;Bypass rules on work item updates\u0026rdquo;\nAggregator needs at least the following Scopes:\n Identity: Read Project \u0026amp; Team: Read Service connections: Read, query \u0026amp; manage Work Items: Read, write \u0026amp; manage Careful with the Expiration: Aggregator stores the PAT in the Azure Function configuration, allowing the rules to access Azure DevOps. You should refresh the PAT before expires or save a new PAT in Aggregator configuration using the configure.instance command.\nRenew Azure DevOps Personal Access Token (PAT) # Azure DevOps Personal Access Token (PAT) expires and is up to the administrator to renew or replace them. Aggregator stores the PAT in the Azure Function configuration, allowing the rules to access Azure DevOps. You should refresh the PAT before expires or save a new PAT in Aggregator configuration using the configure.instance command.\n"});index.add({'id':1,'href':'/docs/v2/intro/','title':"Introduction",'content':"Introduction to TFS Aggregator # TFS Aggregator is an extension for Team Foundation Server (TFS) and Azure DevOps Server that enables running custom script when Work Items change, allowing dynamic calculation of field values in TFS and more. (For example: Dev work + Test Work = Total Work).\nYou can use the Server Plugin, for TFS or Azure DevOps Server.\n TFS Server API changed frequently in the past: the Server Plugin contains code specific for each TFS version, which means that TFS Aggregator Server Plugin binaries must be build for a specific TFS version.\n See the Changelog for an history of releases.\nExample Uses # Update the state of a Bug, PBI (or any parent) to \u0026ldquo;In Progress\u0026rdquo; when a child gets moved to \u0026ldquo;In Progress\u0026rdquo; Update the state of a Bug, PBI (or any parent) to \u0026ldquo;Done\u0026rdquo; when all children get moved to \u0026ldquo;Done\u0026rdquo; or \u0026ldquo;Removed\u0026rdquo; Update the \u0026ldquo;Work Remaining\u0026rdquo; on a Bug, PBI, etc with the sum of all the Task\u0026rsquo;s \u0026ldquo;Work Remaining\u0026rdquo;. Update the \u0026ldquo;Work Remaining\u0026rdquo; on a Sprint with the sum of all the \u0026ldquo;Work Remaining\u0026rdquo; of its grandchildren (i.e. tasks of the PBIs and Bugs in the Sprint). Sum up totals on a single work item (i.e. Dev Estimate + Test Estimate = Total Estimate) Create new work items Create new work item links Setup \u0026amp; install # The easiest way to deploy the Web Service version is to use the Deploy to Azure button on the repository page.\nThe latest Install package contains A fully automated setup for the Server Plugin.\nA manual installation after building from source follows the following process:\n Download and extract the binaries from the latest release Create a file named TFSAggregator2.ServerPlugin.policies (or rename one of the existing samples to get started) and change the example settings to your actual settings. Syntax Example. Test your policy using the command line tool. Copy TFSAggregator2.ServerPlugin.dll, TFSAggregator2.Core.dll and TFSAggregator2.ServerPlugin.policies to the plugin location on the Application Tier of your TFS Servers That is all. TFS will detect that a file was copied in and will load it in.\nWe ship with an MSI installer which will automatically detect your TFS server folder.\n WARNING When upgrading your TFS server you should uninstall the TFS Aggregator Server Plugin prior to the upgrade and then run the new installer when your server upgrade has completed.\n Betas # You can pick development binaries directly from VSTS, asking access to the team, see CI build for details.\nTroubleshooting # Is it not working? Here is the troubleshooting and how to get help page: TFS Aggregator Troubleshooting\nSupported TFS versions # The following table lists the TFS version supported by the Server Plugin.\n TFS version Breaking changes Supported Build configuration 2013 RTM No n/a 2013.5 Yes Yes 2013 2015 RTM Yes Yes 2015 2015.1 Yes No 2015.1 2015.2 Yes No 2015.2 2015.2.1 No 2015.2 2015.3 No 2015.2 2015.4 Yes 2015.2 2017 RTM Yes Yes 2017 2017.1 Yes 2017 2017.2 Yes Yes 2017.2 2017.3 Not yet n/a 201x RTM Not yet n/a Note: this list may be out of date. Please check\n Build and customize # We used Visual Studio Community Edition 2017 Update 2 to develop this version. In the Contributor section you will find all the details to rebuild and customize TFS Aggregator.\n"});index.add({'id':2,'href':'/docs/v3/setup/','title':"Setup",'content':"Setup for Aggregator 3 # Prerequisites # We developed and tested Aggregator on Windows 10 and presumably it works fine on Windows Server 2019. The integration tests runs on Ubuntu so CLI works on Ubuntu too. If you find that works on different operating systems, let us know.\nYou need Access to an Azure DevOps Services Organization, or at least a Project. A Personal Access Token with sufficient permissions on the Organization or Project is added to the interpreter configuration. This page explains how to get a PAT and the permission required.\nWe support two hosting scenarios for the Rule interpreter:\n in Azure Functions, explained here. in a Docker container. Production configuration has some additional caveats listed here.\nGet the executable # Finally to run Aggregator CLI, download the latest aggregator-cli*.zip file from GitHub Releases. Select the file appropriate for your platform and unzip it in a convenient location.\nWhile the CLI is mostly designed for the Azure Functions scenario, it offers some useful command for the Docker deployment.\n"});index.add({'id':3,'href':'/docs/v2/intro/how-it-works/','title':"How it Works",'content':"Web Service # The Web Service is deprecated, use Aggregator CLI instead.\n Visual Studio Team Services (VSTS) and TFS (2015 and later) can integrate with other systems through Service hooks. TFS Aggregator Web Service leverage the Web Hooks variant to receive notifications of work items changes in VSTS/TFS.\nThe common scenario is:\n A user changes some work item\u0026rsquo;s data using Visual Studio or TFS Web Interface, then presses the Save button (or hits Ctrl-S); VSTS/TFS validates the edit and saves the changes to the database; VSTS/TFS call the Aggregator Web Service using HTTPS, the message contains information on the type of change, the instance, project and work item; Aggregator see which Rules apply and execute them, which may call back the loading additional work items; Aggregator calls back VSTS/TFS using HTTPS to save any changed work item. Note that steps 3 and 5 requires authentication.\nServer Plugin # The following diagram may help understand the control flow.\n A user changes some work item\u0026rsquo;s data using Visual Studio or TFS Web Interface, then presses the Save button (or hits Ctrl-S); TFS validates the edit and saves the changes to the database; TFS notifies the Aggregator plugin of the ID of the changed work item Aggregator see which Rules apply and execute them, which may cause the loading and saving of additional work items. Note that step 4 may trigger Aggregator again on the just saved work items.\n Keep also in mind that scripts and applications can change work items without user intervention, like Team Foundation Server Integration Tools. Make sure that TFS Aggregator kicks in only when intended.\nConfiguration changes # Aggregator loads and parses the Rules from TFSAggregator2.ServerPlugin.policies file at first run. The result is cached in memory and the cache invalidates if the file changes.\n"});index.add({'id':4,'href':'/docs/v2/intro/slack-coc/','title':"Code of Conduct",'content':"TFS Aggregator Slack - Code of Conduct # Be polite: there is a person on the other side, not a machine, could be a 12-years old developer (yes, it happened). Think before writing.\n"});index.add({'id':5,'href':'/docs/v3/setup/azure/','title':"Azure CLI Setup",'content':"Setup for Aggregator CLI # Prerequisites # We developed and tested Aggregator on Windows 10 and presumably it works fine on Windows Server 2019. The integration tests runs on Ubuntu so CLI works on Ubuntu too. If you find that works on different operating systems, let us know.\nTo use Aggregator you need three things:\n Access to an Azure DevOps Services Organization (or at least a Project) a Personal Access Token with sufficient permissions on the Organization or Project Access to an Azure Subscription (or at least a Resource Group) a Service Principal with, at least, Contributor permission on a Resource Group of the Subscription .Net Core 3.1 runtime installed on the machine where you run Aggregator CLI Aggregator uses a Service Principal (SP) to interact with Azure. For testing you can grant the SP powerful permissions; in a production scenario, is more typical to create one or more Azure Resource Groups and grant the SP permission to those. In addition Aggregator uses an Azure DevOps Personal Access Token (PAT) to create the event subscriptions and interact with object in the target Azure DevOps Project. See here about the PAT.\nProduction configuration has some additional caveats listed here.\nHow to create an Azure Service Principal # The below instructions are based on Use portal to create an Azure Active Directory application and service principal that can access resources.\nIf you do not have Contributor or Owner permission to the Azure Subscription, ask your administrator to do the instructions that follow.\nTo create an Azure Service Principal you can use Azure Powershell or Azure CLI. Shortly, run this Azure CLI command:\naz ad sp create-for-rbac --name AggregatorServicePrincipal when successful, it outputs something similar\n{ \u0026quot;appId\u0026quot;: \u0026quot;12345678-90ab-cdef-1234-567890abcedf\u0026quot;, \u0026quot;displayName\u0026quot;: \u0026quot;AggregatorServicePrincipal\u0026quot;, \u0026quot;name\u0026quot;: \u0026quot;http://AggregatorServicePrincipal\u0026quot;, \u0026quot;password\u0026quot;: \u0026quot;23456789-0abc-def1-2345-67890abcdef1\u0026quot;, \u0026quot;tenant\u0026quot;: \u0026quot;34567890-abcd-ef12-3456-7890abcedf12\u0026quot; } take note of this information and keep it in a safe place. If you prefer a different password you can force a new one with a similar command:\naz ad app credential reset --id 12345678-90ab-cdef-1234-567890abcedf --append --password P@ssw0rd! The GUIDs in this section are completely made up. Expect different values when you run the commands. Assign permissions to Service Principal # The Service Principal must have Contributor permission to the Azure Subscription or, in alternative, pre-create the Resource Group in Azure and give the service account Contributor permission to the Resource Group.\nThis example creates a Resource Group and gives permission to the Service Principal created in the previous example.\n# create Resource Group with permission to Service Principal az group create --name aggregator-cli-demo --location westeurope az role assignment create --role Contributor --assignee 12345678-90ab-cdef-1234-567890abcedf --resource-group aggregator-cli-demo In Azure Portal you can check the permissions in the IAM menu for the selected Resource Group\nAzure DevOps Personal Access Token (PAT) # See \nGet the executable # Finally to run Aggregator CLI, download the latest aggregator-cli*.zip file from GitHub Releases. Select the file appropriate for your platform and unzip it in a convenient location.\n"});index.add({'id':6,'href':'/docs/v3/commands/','title':"CLI Commands",'content':"Aggregator CLI Commands # This is the complete list of Aggregator CLI commands. Click on a link to read a full description.\n Verb Use Action logon.azure Azure Logon into Azure. This must be done before other verbs. logon.ado Both Logon into Azure DevOps. This must be done before other verbs. logon.env Both Logon into both Azure and Azure DevOps using Environment Variables. [v0.9.14] install.instance Azure Creates a new Aggregator instance in Azure. add.rule Azure Add a rule to existing Aggregator instance in Azure. map.rule Azure Maps an Aggregator Rule in Azure to existing Azure DevOps Projects, DevOps events are sent to the rule. map.local.rule Docker Maps an Aggregator Rule hosted in Docker to existing Azure DevOps Projects, DevOps events are sent to the rule. [v1.0] list.instances Azure Lists Aggregator instances in the specified Azure Region or Resource Group or in the entire Subscription. list.rules Azure List the rules in an existing Aggregator instance in Azure. list.mappings Azure Lists mappings from existing Azure DevOps Projects to Aggregator Rules. invoke.rule Both Executes a rule locally or in an existing Aggregator instance. configure.instance Azure Configures an existing Aggregator instance (currently the Azure DevOps authentication). update.instance Azure Updates the runtime of an Aggregator instance in Azure. configure.rule Azure Change a rule configuration (currently only enabling/disabling). update.rule Azure Update the code of a rule and/or its runtime. update.mappings Azure Updates any mapping from an old Aggregator instance in Azure to a new Aggregator instance. unmap.rule Azure Removes a mapping to an Aggregator Rule from a Azure DevOps Project. remove.rule Azure Remove a rule from existing Aggregator instance in Azure, removing any mapping to the Rule. uninstall.instance Azure Destroy an Aggregator instance in Azure, removing any mapping to the Rules. logoff Both Logoff removing any locally cached credential. help Both Display more information on a specific command. version Both Displays the Aggregator version. You can see a few Command examples in Sample Aggregator CLI usage.\nExit codes # Aggregator CLI returns 0 when a command completes successfully. You can check the return code using %ERRORLEVEL% in cmd, $LastExitCode in PowerShell, or $? in bash.\n Exit Code Meaning 0 Command succeeded 1 Command failed 2 Invalid argument(s) or unknown command 3 Specified resource(s) not found e.g. in list commands 99 Unexpected internal error Environment Variables # Telemetry [v0.9.14] # Aggregator CLI collects anonymised telemetry data, to report on which commands and command options are most used.\nYou can turn off telemetry by setting the AGGREGATOR_TELEMETRY_DISABLED to a true value, e.g.\nAGGREGATOR_TELEMETRY_DISABLED=1 The Runtime Engine, running in the Azure Function, does not collect any telemetry data.\nAutomatic Version Check [v1.0] # You can turn off the automatic check for new CLI version, setting to a true value:\nAGGREGATOR_NEW_VERSION_CHECK_DISABLED=1 Shared secret [v1.0] # Aggregator_SharedSecret is used by the map.local.rule command for initial authentication. Use a long complex password. This value must be identical on CLI and the Docker container.\n"});index.add({'id':7,'href':'/docs/v2/using/','title':"Users' Guide",'content':"Users\u0026rsquo; Guide to TFS Aggregator # This Guide explains how to write the Rules.\n"});index.add({'id':8,'href':'/docs/v3/commands/common-options/','title':"Common options",'content':"Verbose option # All commands accept the --verbose option (or -v in the short form) to print additional messages for troubleshooting.\nNaming Templates # With --namingTemplate, you can specify affixes for all Azure resource that will be created or used. follows\n{ \u0026#34;ResourceGroupPrefix\u0026#34;: \u0026#34;aggregator-\u0026#34;, \u0026#34;ResourceGroupSuffix\u0026#34;: \u0026#34;-sfx\u0026#34;, \u0026#34;FunctionAppPrefix\u0026#34;: \u0026#34;fp\u0026#34;, \u0026#34;FunctionAppSuffix\u0026#34;: \u0026#34;fs\u0026#34;, \u0026#34;HostingPlanPrefix\u0026#34;: \u0026#34;hp\u0026#34;, \u0026#34;HostingPlanSuffix\u0026#34;: \u0026#34;hs\u0026#34;, \u0026#34;AppInsightPrefix\u0026#34;: \u0026#34;aip\u0026#34;, \u0026#34;AppInsightSuffix\u0026#34;: \u0026#34;ais\u0026#34;, \u0026#34;StorageAccountPrefix\u0026#34;: \u0026#34;strg\u0026#34;, \u0026#34;StorageAccountSuffix\u0026#34;: \u0026#34;31415\u0026#34; } If you use the --namingTemplate option, the --resourceGroup option is mandatory.\n"});index.add({'id':9,'href':'/docs/v2/using/writing-rules/writing-rules/','title':"Writing Rules and Policies",'content':"The samples\\TFSAggregator2.ServerPlugin.policies should be your starting point. This file contains a no-harm policy: it simply logs an \u0026ldquo;Hello, World\u0026rdquo; message when invoked. The comments remind the syntax.\nEditing a policy # The XML Schema definition is in file Aggregator.Core\\Configuration\\AggregatorConfiguration.xsd. This is the ultimate truth: policy file is checked against XSD before being used.\nIt can also help you editing the policy file in Visual Studio. Open TfsAggregator2 solution in Visual Studio, open a policy file using built-in Xml editor, Select XML from the main menu, then Schemas. In the dialog windows select Use this Schema for AggregatorConfiguration.xsd from the drop-down list. "});index.add({'id':10,'href':'/docs/v3/commands/authentication-commands/','title':"Authentication commands",'content':"The first step for using Aggregator CLI is to logon into both Azure and Azure DevOps. You can a single command, logon.env, or two separate commands, logon.azure and logon.ado. The former is most useful in an automated scenario, while the other two can be more practical in an interactive session. Use the logoff command to remove any cached authentication data.\nlogon.azure # Logon into Azure. This must be done before other verbs. The credentials are cached locally and expire after 2 hours. Takes four mandatory options.\n Option Short form Description --subscription -s Azure Subscription Id. --tenant -t Azure Active Directory Tenant Id. --client -c Service Principal Client Id also known as Application Id. --password -p Service Principal password also known as Client secret. logon.ado # Logon into Azure DevOps. This must be done before other verbs. The credentials are cached locally and expire after 2 hours. Takes three mandatory options.\n Option Short form Description --url -u Account/server URL, e.g. myaccount.visualstudio.com. --mode -m Authentication mode. Currently the only valid mode is PAT. --token -t Azure DevOps Personal Authentication Token. logon.env [v0.9.14] # Logon into both Azure and Azure DevOps using Environment. The credentials are cached locally and expire after 2 hours. It has no specific options.\n Environment Variable Description AGGREGATOR_SUBSCRIPTIONID Azure Subscription Id AGGREGATOR_TENANTID Azure Active Directory Tenant Id. AGGREGATOR_CLIENTID Service Principal Client Id also known as Application Id. AGGREGATOR_CLIENTSECRET Service Principal password also known as Client secret. AGGREGATOR_AZDO_URL Account/server URL, e.g. myaccount.visualstudio.com . AGGREGATOR_AZDO_MODE Authentication mode. Currently the only valid mode is PAT. AGGREGATOR_AZDO_TOKEN Azure DevOps Personal Authentication Token. logoff [v0.9.14] # Logoff removing any locally cached credential.\n"});index.add({'id':11,'href':'/docs/v2/using/policy-syntax/','title':"Configuration XML syntax",'content':"This page describes the content of a policy file.\n\u0026lt;?xml version=\u0026quot;1.0\u0026quot; encoding=\u0026quot;utf-8\u0026quot;?\u0026gt; This is the basic beginning to an XML file. Do not change it.\n\u0026lt;AggregatorConfiguration\u0026gt; AggregatorConfiguration: The main node for all the configuration options. (Single)\nruntime section # This section controls general behaviour for TFS Aggregator, e.g. authentication, credentials or logging level.\n \u0026lt;runtime debug=\u0026quot;False\u0026quot;\u0026gt; runtime: Configure generic behavior. (Once, Optional)\n debug: turns on debugging options (Optional, default: False) \u0026lt;rateLimiting interval=\u0026quot;00:00:10.00\u0026quot; changes=\u0026quot;10\u0026quot; /\u0026gt; rateLimiting: Define how Aggregator rejects incoming requests. (Once, Optional)\n interval: Timespan to validate. (Optional, default: 00:00:01.00) changes: Maximum number of changes in interval. (Optional, default: 5 ) Use RateLimiting.policy to test your configuration on a server.\n \u0026lt;logging level=\u0026quot;Diagnostic\u0026quot;/\u0026gt; logging: Define logging behavior. (Once, Optional)\n level: The level of logging. (Optional) Valid values are: Critical Error Warning Information or Normal \u0026ndash; default value Verbose Diagnostic. See the Help page for more information: TFS Aggregator Troubleshooting \u0026lt;script language=\u0026quot;C#\u0026quot; /\u0026gt; script: Define script engine behavior. (Once, Optional)\n language: The language used to express the rules. (Optional) Valid values are: CS,CSHARP,C# \u0026ndash; default value VB,VB.NET,VBNET PS,POWERSHELL \u0026ndash; Experimental! \u0026lt;authentication autoImpersonate=\u0026quot;true\u0026quot; /\u0026gt; \u0026lt;authentication personalToken=\u0026quot;abcd1234654sdfsfsdfs45645654645\u0026quot; /\u0026gt; \u0026lt;authentication username=\u0026quot;user1\u0026quot; password=\u0026quot;password1\u0026quot; /\u0026gt; authentication: Define authentication behavior. (Once, Optional)\n autoImpersonate: false (default) the TFS Service account, true the user requesting. (Optional) personalToken: A Personal Access Token. (WebService, Optional) username: A Username. (WebService, Optional) password: A Password in clear text. (WebService, Optional) \u0026lt;server baseUrl=\u0026quot;http://localhost:8080/tfs\u0026quot; /\u0026gt; server: Define server configuration. (2.1, Once, Optional)\n baseUrl: Forces the URL that Aggregator use to access TFS. (Optional) Can be useful is TFS is misconfigured, or you have some special requirements. Avoid to use if possibile. snippet sections # These sections can contain helper code to write the Rules (see below).\n \u0026lt;snippet name=\u0026quot;MySnippet\u0026quot;\u0026gt; \u0026lt;/snippet\u0026gt; snippet: Represents a code macro rule. (2.2, Repeatable)\n name: The name of this code macro. (Mandatory) functions sections # These sections can contain helper code to write the Rules (see below).\n \u0026lt;function\u0026gt; \u0026lt;/function\u0026gt; function: Contains methods callable from rules. (2.2, Repeatable)\nrule sections # This is the core of this file: these sections contain the Rules applied to work items.\n \u0026lt;rule name=\u0026quot;Noop\u0026quot; appliesTo=\u0026quot;Task,Bug\u0026quot; hasFields=\u0026quot;System.Title,System.Description\u0026quot;\u0026gt; \u0026lt;/rule\u0026gt; rule: Represents a single rule. (Repeatable)\n name: The name of this rule. (Mandatory) appliesTo: The name of the work item type that this rule will target. (All: *, List, Optional, List separators: ,;) hasFields: The work item must have the listed fields for the rule to apply. (All: *, List, Optional, List separators: ,;) changes: New,Change,Delete,Restore (default) what change event triggers the Rule. (WebService, Optional) content: the script to execute when the rule triggers. (Mandatory) The self ($self in PowerShell) variable contains the work item that triggered the plugin. The self ($self in PowerShell) variable contains the work item that triggered the plugin. We recommended using CDATA to wrap script code. See Scripting for additional details on Rules\u0026rsquo; code.\nTo invoke a snippet the syntax is ${_nameOfSnippet_}.\npolicy sections # These sections maps rule to Collections and Projects.\n \u0026lt;policy name=\u0026quot;DefaultPolicy\u0026quot;\u0026gt; policy: Represent a set of aggregation rules that apply to a particular scope. (Repeatable)\n name: The name of this policy. (Mandatory) All scopes must match for the policy to apply (logical and).\n \u0026lt;collectionScope collections=\u0026quot;DefaultCollection\u0026quot; /\u0026gt; collectionScope: Scope the policy to a list of collections. (Optional)\n collections: The TFS Collection to which the policy applies. (All: *, List, Mandatory, List separators: ,;) \u0026lt;templateScope name=\u0026quot;Scrum\u0026quot; /\u0026gt; templateScope: Scope the policy to Team Projects using a particular Process Template. (Optional, Repeatable)\n DEPRECATED: our implementation does not work with VSTS or recent versions of TFS.\n name: Name of Process Template matching. (Mandatory, mutually exclusive with typeid) templateScope requires that name must be present.\n \u0026lt;projectScope projects=\u0026quot;Project1,Project2\u0026quot; /\u0026gt; projectScope: Scope the policy to listed Team Projects. (Optional)\n projects: List of Team Project names. (All: *, List, Mandatory, List separators: ,;) \u0026lt;ruleRef name=\u0026quot;Noop\u0026quot; /\u0026gt; ruleRef: Reference to a previously declared rule. (Repeatable)\n name: Name of existing Rule. (Required) Rules apply in order.\n"});index.add({'id':12,'href':'/docs/v3/commands/instance-commands/','title':"Instance commands",'content':"Azure Resource Names # Aggregator CLI has three ways of generating names for Azure Resources. Keep in mind, that Azure Function App and Storage resources must have unique names in Azure.\nBasic template # This template is used when you do not specify the --resourceGroup option.\nIn this scenario Aggregator tries to create an Azure Resource Group for each instance. The name of the resource group derives from the instance name.\n Object Generated name Instance Name foo Resource Group aggregator-foo StorageAccount aggregator######## (8 random characters) Function App fooaggregator Hosting Plan fooaggregator-plan AppInsight fooaggregator-ai Default template # This template is used when you specify the --resourceGroup option\n Object Generated name Instance Name foo Resource Group bar StorageAccount aggregator######## (8 random characters) Function App fooaggregator Hosting Plan fooaggregator-plan AppInsight fooaggregator-ai In this scenario, you can have more than one Instance (Azure Function) in the same Resource Group.\nCustom template # This template allows a complete customisation of the generated names. You specify both --namingTemplate and --resourceGroup options.\nGiven a custom template with this data\n{ \u0026#34;ResourceGroupPrefix\u0026#34;: \u0026#34;rgp-\u0026#34;, \u0026#34;ResourceGroupSuffix\u0026#34;: \u0026#34;-rgs\u0026#34;, \u0026#34;FunctionAppPrefix\u0026#34;: \u0026#34;p\u0026#34;, \u0026#34;FunctionAppSuffix\u0026#34;: \u0026#34;s\u0026#34;, \u0026#34;HostingPlanPrefix\u0026#34;: \u0026#34;hp-\u0026#34;, \u0026#34;HostingPlanSuffix\u0026#34;: \u0026#34;-hs\u0026#34;, \u0026#34;AppInsightPrefix\u0026#34;: \u0026#34;aip-\u0026#34;, \u0026#34;AppInsightSuffix\u0026#34;: \u0026#34;-ais\u0026#34;, \u0026#34;StorageAccountPrefix\u0026#34;: \u0026#34;strg\u0026#34;, \u0026#34;StorageAccountSuffix\u0026#34;: \u0026#34;123456\u0026#34; } you have\n Object Generated name Instance Name foo Resource Group rgp-bar-rgs StorageAccount strgfoo123456 Function App pfoos Hosting Plan hp-foo-hs AppInsight aip-foo-ais GitHub cache # Aggregator CLI caches GitHub data for 24 hours to prevent API rate limit exceeded error. If CLI fails to find a specific version, check that it is still available in GitHub and delete the githubresponse.cache file in the %LOCALAPPDATA%\\aggregator-cli (Windows) or $HOME/.local/share/aggregator-cli directory (Linux).\nThis cache applies to both install.instance and update.instance commands.\ninstall.instance # Creates a new Aggregator instance in Azure. It requires two mandatory options.\n Option Short form Description --name -n The name for the new Aggregator instance. --location -l Name of Azure region that will host the Aggregator instance. This command creates a few resources in Azure:\n An Azure Function App that will host the Rule Engine and receive the notifications from Azure DevOps. The associated Hosting Plan. A Storage account to support the Function App. An AppInsight instance to store the traces and messages generated by the Rules. After the required Azure Resources are in place, it downloads the latest Aggregator Runtime from GitHub and uploads it so it is shared by future added Rules.\nThe overall process may require a few minutes. It is a good practice to set the --verbose option to track the progress.\nIf the process fails, the resources create are still in Azure. You can try re-run the command after fixing the failing step.\nYou can refine the process with additional options.\n Option Short form Description --resourceGroup -g Azure Resource Group that contains the Aggregator instances. If you specify this option, the Resource Group must already exists and the logon account must be a Contributor. --namingTemplate n/a Specify affixes for all Azure resources that will be created. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --requiredVersion n/a Version of Aggregator Runtime required. This is mutually exclusive with --sourceUrl. The matching version is retrieved from GitHub and uploaded to the Azure Function App. The version number must match one of the Aggregator releases in GitHub. Note that GitHub caching applies. --sourceUrl n/a URL of Aggregator Runtime. This is mutually exclusive with --requiredVersion. It should be used in enterprise scenario where the CLI cannot read from GitHub. It supports http, https and file scheme, so you can download the Runtime on a network share. --hostingPlanTier -t Azure AppPlan Service tier hosting the Aggregator instances. Defaults to Dynamic. --hostingPlanSku -k Azure AppPlan SKU hosting the Aggregator instances. Defaults to Y1, matching the Dynamic tier. --appInsightLocation n/a Name of Azure region that will host AppInsight for the new Aggregator instance. This allows to host the Function App in a region where AppInsight is not available. configure.instance # Reconfigures an existing Aggregator instance. It requires two mandatory options.\n Option Short form Description --name -n The name of an existing Aggregator instance. --location -l Name of Azure region hosting the Aggregator instance. You can use these two options to further specify the name.\n Option Short form Description --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. Furthermore, you must specify one of these two options.\n Option Short form Description --authentication -a Refresh the Azure DevOps authentication data saved in the Function App configuration. --saveMode -m Sets the algorithm to save the data changed by Rules back to Azure DevOps. update.instance # Updates an existing Aggregator instance in Azure, with latest runtime binaries. It requires one mandatory options.\nIt is not guaranteed to work across major version of runtime.\n Option Short form Description --instance -i The name of an existing Aggregator instance. You can use these two options to further specify the name.\n Option Short form Description --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. Furthermore, you must specify one of these two options to identify the Runtime version you want to upgrade to.\n Option Short form Description --requiredVersion n/a Version of Aggregator Runtime required. This is mutually exclusive with --sourceUrl. The matching version is retrieved from GitHub and uploaded to the Azure Function App. The version number must match one of the Aggregator releases in GitHub. Note that GitHub caching applies. --sourceUrl n/a URL of Aggregator Runtime. This is mutually exclusive with --requiredVersion. It should be used in enterprise scenario where the CLI cannot read from GitHub. It supports http, https and file scheme, so you can download the Runtime on a network share. uninstall.instance # Destroys an Aggregator instance in Azure, that is the Azure Function App created by install.instance. It requires two mandatory options.\n Option Short form Description --name -n The name of an existing Aggregator instance. --location -l Name of Azure region hosting the Aggregator instance. You can use these two options to further specify the operation.\n Option Short form Description --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --dont-remove-mappings -m Do not remove mappings from Azure DevOps (default is to remove them). The Resource Group is deleted only if it was created by Aggregator.\n"});index.add({'id':13,'href':'/docs/v2/using/scripting/','title':"Scripting the Rules",'content':"Script languages # You can use C# and VB.Net to write your rules. Powershell also works but we had little tested it.\nC# and VB # You can use only types from these assemblies:\n System System.Core Microsoft.TeamFoundation.WorkItemTracking.Client Aggregator.Core Any other reference will result in compile errors. The following namespaces are imported (C# using, VB Imports):\n Microsoft.TeamFoundation.WorkItemTracking.Client System.Linq Microsoft.TeamFoundation.WorkItemTracking.Client.CoreFieldReferenceNames Aggregator.Core and descendants Code # You can make your code more modular, using macro snippets or functions.\n\u0026lt;snippet name=\u0026quot;MySnippet\u0026quot;\u0026gt;\u0026lt;![CDATA[ logger.Log(\u0026quot;You entered MySnippet snippet.\u0026quot;); ]]\u0026gt;\u0026lt;/snippet\u0026gt; \u0026lt;function\u0026gt;\u0026lt;![CDATA[ int MyFunc() { return 42; } ]]\u0026gt;\u0026lt;/function\u0026gt; \u0026lt;rule name=\u0026quot;MyFirstRule\u0026quot; appliesTo=\u0026quot;Task\u0026quot; hasFields=\u0026quot;Title\u0026quot;\u0026gt;\u0026lt;![CDATA[ ${MySnippet} logger.Log(\u0026quot;Hello, World from {1} #{0}!\u0026quot;, self.Id, self.TypeName); logger.Log(\u0026quot;MyFunc returns {0}.\u0026quot;, MyFunc()); ]]\u0026gt;\u0026lt;/rule\u0026gt; This way you can reuse the same code in multiple rules.\n"});index.add({'id':14,'href':'/docs/v2/using/objects-reference/','title':"Objects Reference",'content':"Aggregator exposes some predefined objects or variables to your rules:\n self as the pivot for all computation. store to access the entire set of work items. logger to add a trace message to the log output. Library access to a library of pre-canned functions. We will refer the TeamCollection containing the self work item as the current Collection.\n"});index.add({'id':15,'href':'/docs/v3/commands/rule-commands/','title':"Rule commands",'content':"add.rule # Add a rule to existing Aggregator instance in Azure. It requires three mandatory options.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --name -n The name of the new Rule. This will be the name of the Azure Function. --file -f Relative or absolute path to the file with the Rule code. You can use these two options to further specify the Instance name.\n Option Short form Description --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. Note that the Rules sharing the same Instance use the same Runtime version.\ninvoke.rule # Executes a rule locally or in an existing Aggregator instance emulating a typical message from Azure DevOps.\nCAVEAT\nEven when run locally the Rule connects to Azure DevOps, so remember to add the --dryrun option unless you want to write the changes back to Azure DevOps.\n Common options # These options defines the payload sent to the Rule.\n Option Short form Description --project -p Name of existing Azure DevOps project. --event -e Event to emulate: can be workitem.created workitem.updated workitem.restored or workitem.deleted. --workItemId -w Valid Id of Azure DevOps Work Item. These options defines the modes of execution.\n Option Short form Description --dryrun -d Aggregator will not committing the changes to Azure DevOps. --saveMode -m Sets the algorithm to save the data changed by Rules back to Azure DevOps. --impersonate n/a Do rule changes on behalf of the person triggered the rule execution. See impersonate directive, for details, requires special account privileges. Local Execution # Option Short form Description --local -n The Rule Engine runs locally, not on Azure. --source -s Relative or absolute path to the file with the Rule code. Remote Execution # IF you do not specify the --local, the Rule Engine will run on Azure, in an existing Instance. The Rule must be already added to Instance.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --name -n The name of an existing Rule to run. It must be previously uploaded using add.rule. --account -a Azure DevOps account name. configure.rule # Changes a rule configuration.\nThese parameters identify the Rule.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --name -n The name of an existing Rule to run. It must be previously uploaded using add.rule. Disable a Rule # Option Short form Description --disable -d Disable the Azure Function that implements the Rule. --enable -e Enable the Azure Function that implements the Rule. A disabled Azure Function returns an error to Azure DevOps.\nImpersonate # Option Short form Description --enableImpersonate -e Enable the rule to do the changes on behalf of the person triggered the rule execution. See impersonate directive, for details, requires special account privileges. --disableImpersonate -d Disable impersonation. update.rule # Update a Rule\u0026rsquo;s code.\nThese parameters identify the Rule.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --name -n The name of an existing Rule to run. It must be previously uploaded using add.rule. The file option points to the new code version.\n Option Short form Description --file -f Relative or absolute path to the file with the Rule code. NOTE: It is up to you to manage the story of rule code. Aggregator offers no versioning.\n remove.rule # Remove a rule from existing Aggregator instance in Azure. All mappings pointing to this Rule are automatically deleted.\nThese parameters identify the Rule to delete.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --name -n The name of an existing Rule to run. It must be previously uploaded using add.rule. The Instance (Azure Function App) will be empty if you delete the last Rule.\n"});index.add({'id':16,'href':'/docs/v2/using/objects-reference/self-object/','title':"self Object",'content':"Represents the work item that triggered the rule and corresponds to the IWorkItemExposed interface.\nFields collection # You can directly access a Field using its name:\nself.Fields[\u0026quot;field_name\u0026quot;] Prefer using Reference names e.g. System.Title as they do not depend on localization and are more resilient to Process template changes.\nTo simply access a field value, you can use self[\u0026quot;field_name\u0026quot;] as a shorthand for self.Fields[\u0026quot;field_name\u0026quot;].Value\nFor numeric and dates you may prefer using\nvar num = self.GetField\u0026lt;int\u0026gt;(\u0026quot;field_name\u0026quot;, 42); for other type of fields we suggest to use the following syntax\nvar str = self[\u0026quot;field_name\u0026quot;]?.ToString(); See Field for more information.\nParent property # Helper property to navigate a work item\u0026rsquo;s parent in the Parent-Child hierarchy. Applies to any work item object.\nself.Parent[\u0026quot;System.Title\u0026quot;] Children property # Helper property to navigate a work item\u0026rsquo;s children in the Parent-Child hierarchy.\nforeach (var child in self.Children) { if (child.TypeName == \u0026quot;Bug\u0026quot;) { //... } } HasParent / HasChildren / HasRelation methods # Helper methods to avoid referencing null properties. Applies to any work item object.\nif (self.HasParent()) { self.Parent[\u0026quot;System.Title\u0026quot;] = \u0026quot;*** \u0026quot; + self.Parent[\u0026quot;System.Title\u0026quot;]; } Always prefer the Immutable name of the Link Type, e.g. System.LinkTypes.Hierarchy-Reverse instead of Parent in HasRelation. You can use the pre-defined WorkItemImplementationBase.ChildRelationship and WorkItemImplementationBase.ParentRelationship.\nAddWorkItemLink methods # Add a link to another work item, the arguments are the linked workitem and the name of Link Type.\nvar parent = self; if (!self.HasChildren()) { var child = store.MakeNewWorkItem((string)parent[\u0026quot;System.TeamProject\u0026quot;], \u0026quot;Task\u0026quot;); child[\u0026quot;Title\u0026quot;] = \u0026quot;Task auto-generated for \u0026quot; + parent[\u0026quot;Title\u0026quot;]; child.AddWorkItemLink(parent, WorkItemImplementationBase.ParentRelationship); } You can use the pre-defined WorkItemImplementationBase.ChildRelationship and WorkItemImplementationBase.ParentRelationship for the name.\n Be careful to use the Immutable name of a Link Type, e.g. System.LinkTypes.Hierarchy-Reverse.\n AddHyperlink method # Add an hyperlink to an URL, with an optional comment.\nself.AddHyperlink(\u0026quot;https://github.com/tfsaggregator/tfsaggregator\u0026quot;, \u0026quot;Automatically added\u0026quot;); WorkItemLinks property (v2.3) # Collection of existing work item links.\nforeach (var link in self.WorkItemLinks) { if (link.Target.Id == 1) { logger.Log( \u0026quot;RemoveLinkRule removing {0} to #{1}\u0026quot; , link.LinkTypeEndImmutableName, link.Target.Id); break; } } RemoveWorkItemLink method (v2.3) # Remove a link to another work item.\nforeach (var link in self.WorkItemLinks) { if (link.Target.Id == 1) { logger.Log( \u0026quot;RemoveLinkRule removing {0} to #{1}\u0026quot; , link.LinkTypeEndImmutableName, link.Target.Id); self.RemoveWorkItemLink(link); break; } } History and related properties # self offers the History, RevisedDate and Revision properties.\nIn addition, the LastRevision property offers access to latest Fields values. While PreviousRevision and NextRevision can be used to traverse the history of the workitem.\nMiscellaneous properties # The IsValid method is important to check is you set some invalid field value on a work item.\nYou can get the Id and TypeName of a work item. The Uri property returns the uniform resource identifier (Uri) of this work item.\nTransitionToState method # Set the state of self Work Item.\nA Process Templates can limit the possibile transition states, for example many templates do not allow you to go directly from a New state to a Done state. With this method TFS Aggregator will cycle the work item through what ever states it needs to to find the shortest route to the target state. (For most templates that is also the route that makes the most sense from a business perspective too.)\nself.TransitionToState(\u0026quot;Closed\u0026quot;, \u0026quot;Closed by TFS Aggregator\u0026quot;); Fluent Queries # You can get work items related using the utility methods to build a query. Applies to any work item object.\n WhereTypeIs filters on work item type AtMost depth of search, i.e. maximum number of links to follow FollowingLinks filters on link type It is particularly useful for traversing many links.\nExample # var tests = self.FollowingLinks(\u0026quot;Microsoft.VSTS.Common.TestedBy-Forward\u0026quot;).WhereTypeIs(\u0026quot;Test Case\u0026quot;); foreach (var test in tests) { if (test[\u0026quot;Microsoft.VSTS.Common.Severity\u0026quot;] == \u0026quot;1 - Critical\u0026quot;) { // do something } } Linq # You can use Linq queries on these collections:\n Children Fields Examples # Roll-up code\nvar totalEffort = self.Parent.Children.Where(child =\u0026gt; child.TypeName == \u0026quot;Task\u0026quot;).Sum(child =\u0026gt; child.GetField(\u0026quot;TaskEffort\u0026quot;, 0)); Sum children\u0026rsquo;s estimate\nforeach (var child in self.Children.Where(child =\u0026gt; child.Field.Any(field =\u0026gt; field.ReferenceName == \u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;))) { if (child.TypeName != \u0026quot;autogenerated\u0026quot;) { checkedValue += child.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;, 0.0); othersCount += 1; } else { autogeneratedCount += 1; } } "});index.add({'id':17,'href':'/docs/v2/using/objects-reference/field-object/','title':"Field Object",'content':"Fields collection # You can iterate over the Field collection of a work item.\nforeach (var f in self.Fields) { logger.Log(\u0026quot;{0} #{1} has {2} field\u0026quot;, self.TypeName, self.Id, f.Name); } You can directly access a Field using its name:\nself.Fields[\u0026quot;Title\u0026quot;] Prefer using Reference names, e.g. System.Title, as they do not depend on localization and are more resilient to Process template changes.\nField Object # The Field object exposes the following properties:\n Name ReferenceName Value Status OriginalValue DataType TfsField returns the native TFS Field object "});index.add({'id':18,'href':'/docs/v2/using/objects-reference/store-object/','title':"store Object",'content':"Represents the current Collection\u0026rsquo;s Work Items and corresponds to the IWorkItemRepositoryExposed interface. It exposes these methods:\n GetWorkItem MakeNewWorkItem GetGlobalList AddItemToGlobalList (v2.3) RemoveItemFromGlobalList (v2.3) GetWorkItem method # Retrieves a work item from the current Collection by ID.\nvar myWorkitem = store.GetWorkItem(42); MakeNewWorkItem methods # Add a new WorkItem to current Collection.\nvar newWorkItem = store.MakeNewWorkItem((string)self[\u0026quot;System.TeamProject\u0026quot;], \u0026quot;Bug); This syntax will create the new work item in the same TeamProject as self.\nvar newWorkItem = store.MakeNewWorkItem(\u0026quot;MyProject\u0026quot;, \u0026quot;Bug\u0026quot;); Using this overload, you can specify the TeamProject.\nBoth methods require specifying the new work item\u0026rsquo;s type.\nThe new work item Fields have default values; it is not committed to the database until all the rules have fired and Aggregator returns control to TFS.\nGetGlobalList method # Retrieves the collection of items for the named Global List.\nvar items = store.GetGlobalList(\u0026quot;Aggregator - UserParameters\u0026quot;); The global list name must be unique per-collection.\n AddItemToGlobalList method (v2.3) # Add an element to the named Global List. If the Global List does not exists, it is created.\nstore.AddItemToGlobalList(\u0026quot;Aggregator - UserParameters\u0026quot;,\u0026quot;userValue1\u0026quot;); The global list name must be unique per-collection.\n RemoveItemFromGlobalList method (v2.3) # Remove an element from the named Global List.\nstore.RemoveItemFromGlobalList(\u0026quot;Aggregator - UserParameters\u0026quot;,\u0026quot;userValue1\u0026quot;); The global list name must be unique per-collection.\n "});index.add({'id':19,'href':'/docs/v2/using/objects-reference/logger-object/','title':"logger Object",'content':"Allows to add a trace message to the log output via the Log method.\nLog method # It works like Console.WriteLine, accepting a format string followed by optional arguments. If you do not specify the importance, the message will be logged at Verbose level.\nExamples # logger.Log(\u0026quot;Hello, World from {1} #{0}!\u0026quot;, self.Id, self.TypeName); logger.Log(LogLevel.Warning, \u0026quot;Unexpected work item state!\u0026quot;); Possible LogLevel values are:\n Critical Error Warning Information Verbose Diagnostic Each message goes to\n Debug output (visible using DebugView). Windows Application EventLog using TFS Aggregator source, when level is Warning or Critical. .Net Trace listeners. The .Net Trace Source for user messages is TfsAggregator.User; TFS Aggregator own messages have TfsAggregator.ServerPlugin Trace Source.\nRuleName property (v2.1) # Returns the name of current executing Rule.\nlogger.Log(\u0026quot;Hello from Rule {0} processing {1} #{2}.\u0026quot;, logger.RuleName, self.TypeName, self.Id); "});index.add({'id':20,'href':'/docs/v2/using/objects-reference/library-objects/','title':"Library Objects",'content':"Library of utility functions. (v2.2) It exposes two static methods SendMail and GetEmailAddress.\nGetEmailAddress # Retrieve the email address for a user.\n Caveat: This method works only in the Server Plugin\n You can use the DOMAIN\\user form,\nstring email = Library.GetEmailAddress(\u0026quot;WIN-3H7CMUV7KDM\\\\User1\u0026quot;, \u0026quot;[email protected]\u0026quot;); or the User Display name.\nstring email = Library.GetEmailAddress(\u0026quot;User One\u0026quot;, \u0026quot;[email protected]\u0026quot;); SendMail # Send an email using TFS current configuration.\n Caveat: This method works only in the Server Plugin\n string to = \u0026quot;[email protected]\u0026quot;; string subject = \u0026quot;Sent from a Rule\u0026quot;; string body = \u0026quot;It worked!\u0026quot;; Library.SendMail(to, subject, body); The From address is configured in TFS and cannot be changed.\n"});index.add({'id':21,'href':'/docs/v2/using/examples/','title':"Examples of Policies",'content':"Sample polices # The Unit tests use policy files from UnitTests.Core\\ConfigurationsForTests folder, this are the most correct and tested. They do not show how to solve real world\nThe samples\\TFSAggregator2.ServerPlugin.policies should be your starting point. This file contains a no-harm policy: it simply logs an \u0026ldquo;Hello, World\u0026rdquo; message when invoked. The comments remind the syntax.\nFrom time to time, we test the policies contained in the ManualTests folder. They are good to learn but should not be trusted 100%, at least until we are able to setup automated integration testing.\nSimple rules # Calculated field # \u0026lt;rule name=\u0026quot;Sum\u0026quot; appliesTo=\u0026quot;Task\u0026quot; hasFields=\u0026quot;Title,Description\u0026quot;\u0026gt;\u0026lt;![CDATA[ self[\u0026quot;Estimated Work\u0026quot;] = (double)self[\u0026quot;Estimated Dev Work\u0026quot;] + (double)self[\u0026quot;Estimated Test Work\u0026quot;]; ]]\u0026gt;\u0026lt;/rule\u0026gt; This aggregation will total the values found in the Estimated Dev Work and Estimated Test Work fields for any Task work item. The total will be placed in the Estimated Work field on the same work item as the source values were found.\nRollup field on parent # \u0026lt;rule name=\u0026quot;Rollup\u0026quot; hasFields=\u0026quot;Estimated Dev Work;Estimated Test Work\u0026quot;\u0026gt;\u0026lt;![CDATA[ if (self.HasParent()) { self.Parent[\u0026quot;Total Estimate\u0026quot;] = (double)self.Parent[\u0026quot;Estimated Dev Work\u0026quot;] + (double)self[\u0026quot;Estimated Test Work\u0026quot;]; } ]]\u0026gt;\u0026lt;/rule\u0026gt; This aggregation will total the values found in the Estimated Dev Work and Estimated Test Work fields for all Task work items on the parent. The total will go in the Total Estimate field on the parent one level up from the Task (i.e. the direct parent). In the Microsoft Visual Studio Scrum template that is always a Bug or Product Backlog Item.\n Note on States: TFS has controls setup on State Transitions. Most templates do not allow you to go directly from a New state to a Done state.\n Pick children satisfying condition # var child in self.Children.Where( child =\u0026gt; child.Field.Any( field =\u0026gt; field.ReferenceName == \u0026quot;Custom.Product\u0026quot;) ); "});index.add({'id':22,'href':'/docs/v3/commands/map-commands/','title':"Mapping commands",'content':"map.rule # Maps an Aggregator Rule to existing Azure DevOps Projects. In other words it creates a webhook in Azure DevOps for the selected event and project.\nThese parameters identify the Rule.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --rule -r The name of an existing Rule to run. It must be previously uploaded using add.rule. These parameters identifies the Azure DevOps Project event that will be sent to the Rule.\n Option Short form Description --project -p Name of existing Azure DevOps project. --event -e Event to emulate: can be workitem.created workitem.updated workitem.restored or workitem.deleted. --filterType n/a Filters Azure DevOps event to include only Work Items of the specified Work Item Type. --filterFields n/a Filters Azure DevOps event to include only Work Items with the specified Field(s) changed (applies only to workitem.updated event). --filterOnlyLinks n/a [v1.2] Filters Azure DevOps event to include only Work Items with added or removed Links (applies only to workitem.created and workitem.updated events). The last three permits to filter events before they Azure DevOps sends them to Aggregator. They corresponds to elements of the Web Hook dialog.\nFinally, you can optionally set this option.\n Option Short form Description --impersonate n/a Enable the rule to do the changes on behalf of the person triggered the rule execution. See impersonate directive, for details, requires special account privileges. map.local.rule [v1.0] # Maps an Aggregator Rule to existing Azure DevOps Projects. In other words it creates a webhook in Azure DevOps for the selected event and project.\nThis command fails if the Aggregator_SharedSecret variable is not set or its value does not match the Docker configuration.\nThese parameters identify the Rule.\n Option Short form Description --targetUrl -t Base Url of an existing Aggregator instance running in Docker (e.g. https://server:5320). --rule -r The name of an existing Rule to run. The name must match a rule file deployed in the Docker volume. These parameters identifies the Azure DevOps Project event that will be sent to the Rule.\n Option Short form Description --project -p Name of existing Azure DevOps project. --event -e Event to emulate: can be workitem.created workitem.updated workitem.restored or workitem.deleted. --filterType n/a Filter Azure DevOps event to include only Work Items of the specified Work Item Type. --filterFields n/a Filter Azure DevOps event to include only Work Items with the specified Field(s) changed. The last two permits to filter events before they go out of Azure DevOps.\nFinally, you can optionally set this.\n Option Short form Description --impersonate n/a Enable the rule to do the changes on behalf of the person triggered the rule execution. See impersonate directive, for details, requires special account privileges. update.mappings [v1.0.1] # Updates any mapping from an old Aggregator instance in Azure to a new Aggregator instance in the same Resource Group. The destination instance must have at least the same rules with the same names as the old source instance.\nThis command is useful in two scenarios:\n migration from an older version of Aggregator or Azure Function runtime, where the update.instance falls short; Blue/green deployment of rules. Option Short form Description --sourceInstance -s The name of an existing Aggregator instance (Azure Function App), where the mappings currently point. --destInstance -d The name of an existing Aggregator instance (Azure Function App), where the mappings will point if the command is successful. --resourceGroup -g Azure Resource Group that contains both Aggregator instances. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --project -p Name of existing Azure DevOps project or the * wildcard to select all Projects. unmap.rule # Unmaps an Aggregator Rule from a Azure DevOps Project by removing the underlying webhook.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. --rule -r The name of an existing Rule to run. It must be previously uploaded using add.rule. --project -p Name of existing Azure DevOps project or the * wildcard to select all Projects. --event -e Event to emulate: can be workitem.created workitem.updated workitem.restored or workitem.deleted. "});index.add({'id':23,'href':'/docs/v2/using/examples/accumulate-to-grand-parent/','title':"Accumulate to grand-parent example",'content':"Process template: Any\nAdd the AccumulatedWork from Tasks up to grand-parent, i.e. Feature.\n\u0026lt;rule name=\u0026quot;updateFeatureScrumAccrued\u0026quot; appliesTo=\u0026quot;Task\u0026quot; hasFields=\u0026quot;CustomField.AccumulatedWork\u0026quot;\u0026gt;\u0026lt;![CDATA[ IWorkItemExposed topFeature = self.FollowingLinks(\u0026quot;System.LinkTypes.Hierarchy-Reverse\u0026quot;).WhereTypeIs(\u0026quot;Feature\u0026quot;).AtMost(5).FirstOrDefault(); if (topFeature != null) { logger.Log(\u0026quot;Feature {0}\u0026quot;, topFeature.Id); var taskChildren = topFeature.FollowingLinks(\u0026quot;System.LinkTypes.Hierarchy-Forward\u0026quot;).WhereTypeIs(\u0026quot;Task\u0026quot;).AtMost(5); var sum = 0.0; foreach (var task in taskChildren) { sum += task.GetField\u0026lt;double\u0026gt;(\u0026quot;CustomField.AccumulatedWork\u0026quot;, 0.0); } topFeature[\u0026quot;CustomField.ScrumAccrued\u0026quot;] = sum; } ]]\u0026gt;\u0026lt;/rule\u0026gt; "});index.add({'id':24,'href':'/docs/v2/using/examples/auto-create-children/','title':"Auto-Create Children example",'content':"Example: Auto-Create Children # Process template: Any\nThis example can serve to create a set of standard tasks for work items of a certain type. Say:\n Analyze issue Fix issue Test issue \u0026lt;!-- WorkItems --\u0026gt; \u0026lt;rule name=\u0026quot;NewTask\u0026quot; appliesTo=\u0026quot;Bug\u0026quot;\u0026gt; \u0026lt;![CDATA[ var parent = self; if (!self.HasChildren()) { // use self to pass in the Team Project Context var child = store.MakeNewWorkItem(self, \u0026quot;Task\u0026quot;); child[\u0026quot;Title\u0026quot;] = \u0026quot;Task auto-generated for \u0026quot; + parent[\u0026quot;Title\u0026quot;]; // use the name of the relationship or one of the pre-defined static values // by adding the link to the child, you don't change the parent in this script. child.AddWorkItemLink(parent, \u0026quot;parent\u0026quot;); // child.AddWorkItemLink(parent, WorkItemImplementationBase.ParentRelationship); //should also work } ]]\u0026gt; \u0026lt;/rule\u0026gt; See also:\n https://github.com/tfsaggregator/tfsaggregator/blob/master/UnitTests.Core/ConfigurationsForTests/NewObjects.policies "});index.add({'id':25,'href':'/docs/v2/using/examples/auto-open-auto-close/','title':"Auto-Open and Auto-Close example",'content':"Applies to Scrum Process template\n\u0026lt;rule name=\u0026quot;AutoOpen\u0026quot; appliesTo=\u0026quot;Task\u0026quot;\u0026gt; \u0026lt;!-- Update Work Item to Committed if a task became \u0026quot;active\u0026quot; --\u0026gt; \u0026lt;![CDATA[ if (new[] {\u0026quot;In Progress\u0026quot;, \u0026quot;To Do\u0026quot;}.Contains((string)self[\u0026quot;System.State\u0026quot;])) { if(self.HasParent() \u0026amp;\u0026amp; ((string)self.Parent[\u0026quot;System.State\u0026quot;]) != \u0026quot;Committed\u0026quot;) { self.Parent.TransitionToState(\u0026quot;Committed\u0026quot;, \u0026quot;Auto Activated\u0026quot;); } } ]]\u0026gt; \u0026lt;/rule\u0026gt; \u0026lt;rule name=\u0026quot;AutoClose\u0026quot; appliesTo=\u0026quot;Task\u0026quot;\u0026gt; \u0026lt;!-- Update Work Item to Done if a all child tasks are Done or Removed --\u0026gt; \u0026lt;![CDATA[ if ((string)self[\u0026quot;System.State\u0026quot;] == \u0026quot;Done\u0026quot; \u0026amp;\u0026amp; self.HasParent() \u0026amp;\u0026amp; ((string)self.Parent[\u0026quot;System.State\u0026quot;]) != \u0026quot;Done\u0026quot;) { if (self.Parent.Children.All(child =\u0026gt; new[] {\u0026quot;Removed\u0026quot;, \u0026quot;Done\u0026quot;}.Contains((string)child[\u0026quot;System.State\u0026quot;]))) { self.Parent.TransitionToState(\u0026quot;Done\u0026quot;, \u0026quot;Auto done\u0026quot;); } } ]]\u0026gt; \u0026lt;/rule\u0026gt; "});index.add({'id':26,'href':'/docs/v2/using/examples/log-time-synch/','title':"Remaining Work and Completed Work synchronization for Task WI",'content':"This is an example of automation that allows keep consistency between Remaining Work and Completed Work for Task WI\nGeneral Flow:\n Remaining Work decreased (and Completed Work not changed) =\u0026gt; then increase Completed Work accordingly Completed Work increased (and remainin Work not changed) =\u0026gt; then decrease Remaining Work accordingly All other cases don\u0026rsquo;t require any modifications as they are threated as re-estimation and should follow user decision Process template: Agile or any template that has RemainingWork and CompletedWork fields for Task WI\nImpersonation in configuration for TFS aggregator should be turned Off to be able identify Real User actions and TFS Aggregator created ones\nTFSAutomation - should be updated with your user name under which TFS Aggregator is running.\n\u0026lt;authentication autoImpersonate=\u0026quot;false\u0026quot; /\u0026gt; \u0026lt;rule name=\u0026quot;LogTimeSynch\u0026quot; appliesTo=\u0026quot;Task\u0026quot; hasFields=\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork,Microsoft.VSTS.Scheduling.CompletedWork\u0026quot; changes=\u0026quot;Change\u0026quot;\u0026gt; \u0026lt;![CDATA[ var lastRevisionWI = self.LastRevision; var lastChangedBy = self.Fields[\u0026quot;System.ChangedBy\u0026quot;].OriginalValue; logger.Log(\u0026quot;This item was before changed by {0}\u0026quot;,(string)lastChangedBy); //Verify that this change was done by User not an Automation Bot under which TFS Aggregator is running (Impersonation should be turned off in order to face this behavior) if ((string)lastChangedBy != \u0026quot;TFSAutomation\u0026quot;) { // Verify which values were changed: Remaining Time or Completed Time var PrevValueRemaining = 0.0; var PrevValueComplete = 0.0; //verify work fields aren't null if (lastRevisionWI.Fields[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;].OriginalValue != null) { PrevValueRemaining = (double)lastRevisionWI.Fields[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;].OriginalValue; } if (lastRevisionWI.Fields[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;].OriginalValue != null) { PrevValueComplete = (double)lastRevisionWI.Fields[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;].OriginalValue; } var CurrentValueRemaining = (double)(self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] ?? 0d); var CurrentValueComplete = (double)(self[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;] ?? 0d); var RemainingDiff = (double)PrevValueRemaining - (double)CurrentValueRemaining; var CompletedDiff = (double)PrevValueComplete - (double)CurrentValueComplete; logger.Log(\u0026quot;Prev Remaining is {0}\u0026quot;, PrevValueRemaining); logger.Log(\u0026quot;Current Remaining is {0}\u0026quot;, CurrentValueRemaining); logger.Log(\u0026quot;Remaining Diff is {0}\u0026quot;, RemainingDiff); logger.Log(\u0026quot;Prev Complete is {0}\u0026quot;, PrevValueComplete); logger.Log(\u0026quot;Current Complete is {0}\u0026quot;, CurrentValueComplete); logger.Log(\u0026quot;Complete Diff is {0}\u0026quot;, CompletedDiff); // process case when Remaining time was decreased so that Completed Time should be increased accordingly if (RemainingDiff \u0026gt; 0 \u0026amp;\u0026amp; CompletedDiff == 0) { self[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;] = (double)PrevValueComplete + (double)RemainingDiff; } // process case when Completed Time was increased so that Remaining Time should be decreased accordingly if (RemainingDiff == 0 \u0026amp;\u0026amp; CompletedDiff \u0026lt; 0) { if ((double)PrevValueRemaining + (double)CompletedDiff \u0026lt; 0) { self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = 0.0; } else { self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = (double)PrevValueRemaining + (double)CompletedDiff; } } //all other cases means reestimation so that no changes should be applied by Bot } else { logger.Log(\u0026quot;Changed by TFSAutomation user, stop processing\u0026quot;); } ]]\u0026gt; \u0026lt;/rule\u0026gt; "});index.add({'id':27,'href':'/docs/v2/using/examples/rollup/','title':"Rollup examples",'content':"Using Linq to Aggregate # Applies to Scrum or CMMI Process templates.\n\u0026lt;rule name=\u0026quot;RollupTask\u0026quot; appliesTo=\u0026quot;Task\u0026quot;\u0026gt;\u0026lt;![CDATA[ if (self.HasParent()) { var parent = self.Parent; parent[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;, 0d)); parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;, 0d)); parent[\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;, 0d)); } ]]\u0026gt;\u0026lt;/rule\u0026gt; This rule updates a Product Backlog Item or Bug whenever any child Task is added or changes. The Completed Work, Remaining Work and Original Estimate on the parent become the sum of the corresponding fields of children Tasks. Note that children Bug or Test Case do not update the parent for the appliesTo clause.\nAggregating WIT\u0026rsquo;s hierarhy from Task to Epic level # You need to ensure that you have Microsoft.VSTS.Scheduling.RemainingWork on all WIT\u0026rsquo;s involved\n\u0026lt;rule name=\u0026quot;RollupTask\u0026quot; appliesTo=\u0026quot;Task\u0026quot;\u0026gt;\u0026lt;![CDATA[ if (self.HasParent()) { var parent = self.Parent; parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;, 0d)); } ]]\u0026gt;\u0026lt;/rule\u0026gt; \u0026lt;rule name=\u0026quot;RollupPBI\u0026quot; appliesTo=\u0026quot;Product Backlog Item\u0026quot;\u0026gt;\u0026lt;![CDATA[ if (self.HasParent()) { var parent = self.Parent; parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = parent.Children.Sum(pbi =\u0026gt; pbi.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;, 0d)); } ]]\u0026gt;\u0026lt;/rule\u0026gt; \u0026lt;rule name=\u0026quot;RollupEpic\u0026quot; appliesTo=\u0026quot;Feature\u0026quot;\u0026gt;\u0026lt;![CDATA[ if (self.HasParent()) { var parent = self.Parent; parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = parent.Children.Sum(feature =\u0026gt; feature.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;, 0d)); } ]]\u0026gt;\u0026lt;/rule\u0026gt; This rule updates all WIT\u0026rsquo;s (up to Epic level) whenever any child WIT is added or changes. The Remaining Work on the parents become the sum of the Remaining Work of children\u0026rsquo;s WIT\u0026rsquo;s.\n"});index.add({'id':28,'href':'/docs/v3/commands/info-commands/','title':"Informational commands",'content':"list.instances # Lists Aggregator instances, that is Azure Function Apps, within the Azure Subscription.\nYou should specify at least one of these options to limit the search scope.\n Option Short form Description --location -l Name of Azure region hosting the Aggregator instance. --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. list.rules # List the Rules in an existing Aggregator instance in Azure.\nThese parameters identify the Instance.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. list.mappings # Lists mappings from existing Azure DevOps Projects to Aggregator Rules.\nYou can restrict specifying an Instance.\n Option Short form Description --instance -i The name of an existing Aggregator instance (Azure Function App). --resourceGroup -g Azure Resource Group that contains the Aggregator instance. --namingTemplate n/a Specify affixes for Azure resources. This option requires defining --resourceGroup, also. This turns off automatic name generation and allow comply with enterprise requirements. Or you can specify a Project (or both).\n Option Short form Description --project -p Name of existing Azure DevOps project or the * wildcard to select all Projects. "});index.add({'id':29,'href':'/docs/v2/using/scripting-tricks-n-tips/scripting-tricks-n-tips/','title':"Tricks \u0026 Tips",'content':"The examples are in C#\nUse Fields\u0026rsquo; Reference Names # Write your rules using the Reference name for fields, e.g. Microsoft.VSTS.Common.Priority. That way your rules will work on process templates in different languages: Priority becomes Priorität in a German template.\nAlso the name may change in different template or newer version of the same template. Your rules are more likely to survive TFS upgrades unharmed.\nUse Link Types\u0026rsquo; Reference Names # Write your rules using the Reference name for link types, e.g. Microsoft.VSTS.Common.TestedBy-Forward.\nField can be null # Remember that a work item field value is of type object and can be null.\nStyle #1: use the null-coalescing operator\n(double)(self.Parent[\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;] ?? 0d) notice the parentheses and the cast.\nStyle #2: use the GetField helper function\nself.Parent.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;, 0d) internally uses a try..catch block.\nStyle #3: check the Valid property\nvar parentField = self.Parent[\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;]; if (parentField.Status == FieldStatus.Valid) { parentField.Value += (double)self[\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;]; } notice that parent field is left untouched is it has no value.\nTrace your steps # Consider using the logger object in non-trivial rules or when you are uncertain of the values in use.\nif (self.HasParent()) { var parent = self.Parent; logger.Log(\u0026quot;Task #{0} updates {1} #{2}\u0026quot;, self.Id, parent.TypeName, parent.Id); parent[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;, 0d)); parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;] = parent.Children.Sum(task =\u0026gt; task.GetField\u0026lt;double\u0026gt;(\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;, 0d)); logger.Log(\u0026quot;CompletedWork is {0}, RemainingWork is {1}\u0026quot;, parent[\u0026quot;Microsoft.VSTS.Scheduling.CompletedWork\u0026quot;], parent[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;]); } The output is sent to debugger output and .Net Trace.\nYou can use DebugView from Microsoft\u0026rsquo;s SysInternals site at http://technet.microsoft.com/en-us/sysinternals/bb896647.\nDebugView is a lightweight Trace Listener and will capture the trace messages from TFSAggregator. We would recommend adding the *TFSAggregator:* filter to DebugView so that you only see the TFSAggregator traces. Make sure to enable the Capture Global Win32 option.\n You have to run DebugView on all TFS Application Tier machines.\n Infinite cycle # Always set a Rate Limit feature to limit the damage of infinite loops.\n\u0026lt;AggregatorConfiguration\u0026gt; \u0026lt;runtime\u0026gt; \u0026lt;rateLimiting interval=\u0026quot;00:00:01.00\u0026quot; changes=\u0026quot;5\u0026quot; /\u0026gt; Use Template Scope # Instead of scoping Rules to a Collection or a set of Projects, you can define to apply the Rules to any Project using a specific template. E.g.\n\u0026lt;templateScope name=\u0026quot;Scrum\u0026quot; /\u0026gt; Remember that name must match exactly. Verify before each TFS upgrade if any Process Template has changed name and update your policies eventually.\nUse hasFields instead of appliesTo # The appliesTo filter restrict a Rule to a specific set of work item types. You can define a more generic rule using the hasFields filter. Using it the code does not depends on specific work item type and the way they are named.\nThis has the advantage that the rule will also work when people customize the template by copying an existing Work Item Type, like Task -\u0026gt; In Sprint Bug.\nNumeric Deltas # The Problem # Consider the following rule\nself.Parent[\u0026quot;Custom.RemainingWork\u0026quot;] = (double)self.Parent[\u0026quot;Custom.RemainingWork\u0026quot;] + (double)self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;]; The problem is that the parent value will be increased with every change.\nIf the child has a value of 5, the parent value will be increased by 5. When I change the child value from 5 to 2, the parent value will be increased be 2, but it should be decreased with 3.\nThe requirement is to aggregate the difference from the new value to the old value and not the raw new value only.\nA Solution # A simple way is adding an hidden field, say Custom.PreviousRemainingWork. The rule will become something like\ndouble delta = (double)(self[\u0026quot;Custom.PreviousRemainingWork\u0026quot;]??0d) - (double)(self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;]??0d); self.Parent[\u0026quot;Custom.RemainingWork\u0026quot;] = (double)(self.Parent[\u0026quot;Custom.RemainingWork\u0026quot;]??0d) + delta; self[\u0026quot;Custom.PreviousRemainingWork\u0026quot;] = (double)(self[\u0026quot;Microsoft.VSTS.Scheduling.RemainingWork\u0026quot;]??0d); "});index.add({'id':30,'href':'/docs/v2/using/scripting-pitfalls/','title':"Common Pitfalls",'content':"Null # Any field can return null. Casting null to a numeric value or a date throws a NullReferenceException. The following C# code\n(double)self.Parent[\u0026quot;Microsoft.VSTS.Scheduling.OriginalEstimate\u0026quot;] may succeed or throw.\nThere are many ways to overcame this issue: the null-coalescing operator, use the GetField helper function or check the Valid property. See Tricks\u0026amp;Tips for examples.\nHistory # The History and Revision properties are tricky.\nImagine this sequence:\n A user opens a work item, whose Revision property values 7 She edits the Description field and saves TFS save the changes to the database and increments Revision to 8 Aggregator is notified of the change At this point self.Revision is 8 and LastRevision.Index is 7. Throught LastRevision one can see that self.LastRevision.Fields[\u0026quot;Description\u0026quot;].Value equals what is saved in the database, while OriginalValue is the value before user edit.\nIf the Rule changes any field, you have this: 5. Aggregator run a rule that changes a field 6. Aggregator notices the edit and save the workitem to the database 7. TFS triggers Aggregator again (maybe on a different machine), this time Revision property is 9\nSee History field for full presentation.\n"});index.add({'id':31,'href':'/docs/v2/using/field-history/','title':"History field",'content':"History field # The History field always appends a new message, the property allows to edit the message until work item is saved.\nFor example, this code causes an infinite loop (eventually stopped by rateLimiting feature).\nself.History = \u0026quot;Hello\u0026quot;; Aggregator is triggered again and again for the same work item.\nSome background information # The TFS aggregator only updates a work item if any field has changed by its calculations. If the calculations yield the same value as last time, the work item is considered clean and no new revision is created.\nThe History field is a strange kind of field, it always being empty, unless you\u0026rsquo;ve added a new value to it while processing. Therefore, setting the history field to anything other than string.Empty or null will cause a workitem to be saved.\nWhenever a workitem is saved, the TFS Aggregator triggers again. This is by design and allows for cascading rules. It\u0026rsquo;s also why we\u0026rsquo;ve recently added a rate limiter. Normally the same set of rules triggers again, all calculations yield the same value and that breaks the loop that would otherwise be created.\nExcept when a rule always sets the value of the history field. As it was empty when we started calculating and is now not empty it will cause the work item to be saved.\nTo prevent this from happening there are a few things you can do:\n Before setting the History field, check the .IsDirty property of the work item. If it\u0026rsquo;s not dirty, don\u0026rsquo;t add anything to the history. Iterate through the revisions of the work item to ensure that you\u0026rsquo;re not adding the same comment you added last time. This causes additional database and network traffic, and is therefore less desirable. Don\u0026rsquo;t store your data in the History Field, instead stick it in a custom field or use any of the pre-existing fields to store your data in. (Don\u0026rsquo;t append to an existing field as that would cause the same behavior) If you\u0026rsquo;re not using Impersonation, check whether the \u0026ldquo;Changed By\u0026rdquo; field is set to the service account and assume that the changes are not made by a human, therefore skipping the changes to the history field. Marker Technique # This technique corresponds to suggestion #2, i.e. using a Marker to distinguish between user and rule changes.\nconst string HistoryMarker = \u0026quot;===\u0026quot;; var lastRevision = self.LastRevision; var history = lastRevision.Fields[\u0026quot;History\u0026quot;]; string lastValue = (string) history.Value; logger.Log(\u0026quot;At revision {0} History value is '{1}'\u0026quot;, lastRevision.Index, lastValue); if (!lastValue.StartsWith(HistoryMarker)) { self.History = HistoryMarker + \u0026quot;Hello\u0026quot;; logger.Log(\u0026quot;Marker added\u0026quot;); } "});index.add({'id':32,'href':'/docs/v3/commands/command-examples/','title':"Examples",'content':"Sample Aggregator CLI usage # This page will show you increasing complex examples of using Aggregator CLI.\nTo run Aggregator CLI type aggregator-cli.exe on Windows, ./aggregator-cli on Linux or dotnet aggregator-cli.dll on any followed by the command and options. In the examples, we will use aggregator-cli. In PowerShell you can define an alias to exactly match the examples:\nSet-Alias aggregator-cli (Resolve-Path .\\aggregator-cli.exe) IMPORTANT: Please avoid naïve copy\u0026amp;paste of the examples, or other user will not be able to run the same. Instance names cannot be duplicated in Azure! At least, delete the Azure Resources, after testing the examples.\n Study all three scenarios and try some of the sample commands to make yourself comfortable with Aggregator.\nBasic example # This example shows the basic steps to write and deploy Aggregator Rules. It not intended to represent a more realistic, enterprise-oriented scenario like the Intermediate and the Advanced examples.\nThis example assumes that the Azure account has Contributor permission on the whole Azure Subscription and also to an Azure DevOps Project.\nLogon (basic) # You are required to log into both Azure and ADO. The credentials are cached locally and expire after 2 hours. (Replace the below asterisks * with valid values.)\naggregator-cli logon.azure --subscription ************ --client ************ --password *********** --tenant ************ aggregator-cli logon.ado --url https://dev.azure.com/youraccount --mode PAT --token *************************************** Create the Aggregator Instance (basic) # The Aggregator Instance is an Azure Function Application plus the Aggregator Runtime that execute the Rules.\nThe next snippet creates a new instance \u0026ndash; and a new Azure Resource Group \u0026ndash; in the West Europe region.\naggregator-cli install.instance --verbose --name inst1 --location westeurope Note:\n The Aggregator instance name must be unique in Azure (CLI automatically appends aggregator suffix to minimize the chance of a clash, unless you override using a Naming Template). You can specify the version of Aggregator Runtime using the requiredVersion option. Look in our releases for valid version numbers. You can use the Azure CLI to get the list of Azure Regions: az account list-locations -o table. This is the slowest command: it typically takes a few minutes to create the resources in Azure and upload Aggregator Runtime. Confirm that the Aggregator Instance is accessible (basic) # The next command searches the entire Azure Subscription (previously connected to via logon.azure):\naggregator-cli list.instances It should list inst1.\nAdd two Aggregator Rules (basic) # The Aggregator Rule is a special kind of Azure Function.\nWrite a text file named hello.rule with this content:\n$\u0026quot;Hello { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026quot; Check if the Rule is correct, by running the code locally. Note that the SampleProject must exists in Azure DevOps while the WorkItem with ID 14 is fake. You can use a different project name.\naggregator-cli invoke.rule --dryrun --project SampleProject --event workitem.created --workItemId 14 --local --source hello.rule Note that the --source parameter is a local file relative to the working directory. Even if you skip the above step, the add.rule command will validate the syntax of the code before uploading.\nThe next snippet adds two rules where the file parameter is a local file relative to the working directory.\naggregator-cli add.rule --verbose --instance inst1 --name hello --file hello.rule aggregator-cli list.rules --verbose --instance inst1 The last command should output\nRule inst1/hello Write a second text file named close_parent.rule with this content\nstring message = \u0026quot;\u0026quot;; if (self.Parent != null) { var parent = self.Parent; var children = parent.Children; if (children.All(c =\u0026gt; c.State == \u0026quot;Closed\u0026quot;)) { parent.State = \u0026quot;Closed\u0026quot;; message = \u0026quot;Parent was closed\u0026quot;; } else { message = \u0026quot;Parent was already closed\u0026quot;; } parent.Description = parent.Description + \u0026quot; aggregator was here.\u0026quot;; } return message; aggregator-cli add.rule --verbose --instance inst1 --name parent --file close_parent.rule aggregator-cli list.rules --verbose --instance inst1 You may have noted that the name of the Rule can be different from the file name.\nTell Azure DevOps to call the Rules (basic) # The next commands will add two service hooks to Azure DevOps, each invoking a different Rule, the one we added before.\naggregator-cli map.rule --verbose --project SampleProject --event workitem.created --instance inst1 --rule hello aggregator-cli map.rule --verbose --project SampleProject --event workitem.updated --instance inst1 --rule parent The same rule can be triggered by multiple events from different Azure DevOps projects. Currently only these events are supported:\nworkitem.created\nworkitem.updated\nworkitem.deleted\nworkitem.restored\nworkitem.commented\nThe list commands below should give the same results. Note the various options for restricting the search.\naggregator-cli list.mappings --verbose --instance inst1 aggregator-cli list.mappings --verbose --project SampleProject aggregator-cli list.mappings --instance inst1 --project SampleProject Clean up (basic) # The first command will delete the Azure Function App and all the webhooks in Azure DevOps.\naggregator-cli uninstall.instance --name inst1 --location westeurope aggregator-cli logoff Intermediate example # This scenario is a middle ground between the Basic and the more Advanced. It is suitable for most cases, including production deployment and automation. It still goes through the four basic configuration steps:\n Logon Create the Aggregator Instance Add two Aggregator Rules Tell Azure DevOps to call the Rules This example assumes that an Azure Resource Group named myRG1 already exists and the Azure account has Contributor permission on it and also to an Azure DevOps Project. Create an Azure DevOps Project name SampleProject. It must use a custom template, Custom Agile in the screenshots, but you can use a different name.\nAdd a custom field to template Task.\nand name the new field CustomText.\n.\nLogon (intermediate) # You are required to log into both Azure and ADO. We recommend using logon.env to define credentials data in environment variables.\nUsing PowerShell (Replace the below asterisks * with valid values.):\n$env:AGGREGATOR_SUBSCRIPTIONID = \u0026#39;************\u0026#39; $env:AGGREGATOR_TENANTID = \u0026#39;************\u0026#39; $env:AGGREGATOR_CLIENTID = \u0026#39;************\u0026#39; $env:AGGREGATOR_CLIENTSECRET = \u0026#39;************\u0026#39; $env:AGGREGATOR_AZDO_URL = \u0026#34;https://dev.azure.com/********\u0026#34; $env:AGGREGATOR_AZDO_MODE = \u0026#39;PAT\u0026#39; $env:AGGREGATOR_AZDO_TOKEN = \u0026#39;***************************************\u0026#39; aggregator-cli logon.env --verbose Using bash (Replace the below asterisks * with valid values.):\nexport AGGREGATOR_SUBSCRIPTIONID = \u0026#39;************\u0026#39; export AGGREGATOR_TENANTID = \u0026#39;************\u0026#39; export AGGREGATOR_CLIENTID = \u0026#39;************\u0026#39; export AGGREGATOR_CLIENTSECRET = \u0026#39;************\u0026#39; export AGGREGATOR_AZDO_URL = \u0026#34;https://dev.azure.com/********\u0026#34; export AGGREGATOR_AZDO_MODE = \u0026#39;PAT\u0026#39; export AGGREGATOR_AZDO_TOKEN = \u0026#39;***************************************\u0026#39; aggregator-cli logon.env --verbose The credentials are cached locally and expire after 2 hours. This approach works well in automation scenarios.\nCreate the Aggregator Instance (intermediate) # The next snippet creates a new Aggregator Instance on an existing Resource Group named myRG1 in the West Europe region using a specific Runtime version available in GitHub.\naggregator-cli install.instance --name inst2 --resourceGroup myRG1 --location westeurope --requiredVersion 0.9.14 If you cannot access GitHub, you can download the runtime on a local folder and point to it explicitly.\naggregator-cli install.instance --name inst2 --resourceGroup myRG1 --location westeurope --sourceUrl file://C:/temp/FunctionRuntime.zip Now, check that the instance is there as expected.\naggregator-cli list.instances --resourceGroup myRG1 An instance is enough for most purposes, we will see in the next section how to manage alternative version of the Rules. In the Advanced scenario we will look at using more than one instance and why.\nAdd Aggregator Rules (intermediate) # Again write a text file named hello.rule with this content:\n$\u0026quot;Hello { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026quot; Check if the Rule is correct, by running the code locally. Note that the SampleProject must exists in Azure DevOps, if it doesn\u0026rsquo;t please use the name of an existing Project.\naggregator-cli invoke.rule --dryrun --project SampleProject --event workitem.created --workItemId 14 --local --source hello.rule Now, we add the same rule twice with different names. This techniques is a simple way to test alternative versions of a Rule.\naggregator-cli add.rule --verbose --instance inst2 --resourceGroup myRG1 --name hello --file hello.rule aggregator-cli add.rule --verbose --instance inst2 --resourceGroup myRG1 --name hello-dev --file hello.rule aggregator-cli list.rules --verbose --instance inst2 --resourceGroup myRG1 The last command should output\nRule inst2/hello Rule inst2/hello-dev Tell Azure DevOps to call the Rules (intermediate) # aggregator-cli map.rule --verbose --project SampleProject --event workitem.updated --instance inst2 --resourceGroup myRG1 --rule hello --filterType Task aggregator-cli map.rule --verbose --project SampleProject --event workitem.updated --instance inst2 --resourceGroup myRG1 --rule hello-dev --filterType Task --filterFields Custom.CustomText aggregator-cli configure.rule --verbose --instance inst2 --resourceGroup myRG1 --name hello --disable=true Now create a Task, save it and update the CustomText field. The first webhook will see a 404, because the underlying Azure Function is disabled, while the second should succeed.\nThe second webhook is used only when the CustomText field changes.\nTo reenable e stop any calls to the dev version of the hello Rule:\naggregator-cli configure.rule --verbose --instance inst2 --resourceGroup myRG1 --name hello --enable=true aggregator-cli unmap.rule --verbose --project SampleProject --event workitem.updated --instance inst2 --resourceGroup myRG1 --rule hello-dev Note that you can set less parameter to delete all matching webhooks.\nClean up (intermediate) # To delete all the object previously created, you simply delete the Instance.\naggregator-cli uninstall.instance --name inst2 --resourceGroup myRG1 --location westeurope aggregator-cli logoff The command will automatically remove any mapping (webhook) in Azure DevOps that use the Instance.\nWarning\nSome Azure Resources are not automatically deleted, namely the AppService Plan, the AppInsights instance and the Storage Account. This is deliberate to support audit.\n Advanced example # The last scenario shows how to control the name of Azure Resources \u0026ndash; so you can comply with enterprise policies \u0026ndash; and some additional management techniques.\nLogon (advanced) # Authentication does not change from the previous example.\nCreate the Aggregator Instance (advanced) # Again write a text file named my-naming-template.json with this content:\n{ \u0026#34;ResourceGroupPrefix\u0026#34;: \u0026#34;my\u0026#34;, \u0026#34;ResourceGroupSuffix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;FunctionAppPrefix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;FunctionAppSuffix\u0026#34;: \u0026#34;-my\u0026#34;, \u0026#34;HostingPlanPrefix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;HostingPlanSuffix\u0026#34;: \u0026#34;-my-plan\u0026#34;, \u0026#34;AppInsightPrefix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;AppInsightSuffix\u0026#34;: \u0026#34;-my-appinsight\u0026#34;, \u0026#34;StorageAccountPrefix\u0026#34;: \u0026#34;strg\u0026#34;, \u0026#34;StorageAccountSuffix\u0026#34;: \u0026#34;12345\u0026#34; } and create the Instance\naggregator-cli install.instance --name inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --location westeurope --requiredVersion latest You can use a binary store for your artifacts, as long as allows for anonymous download, e.g.\naggregator-cli install.instance --name inst3 --resourceGroup myRG1 --location westeurope --sourceUrl https://artifactory.example.com/artifactory/generic-github-remote/aggregator-cli/v0.9.14/FunctionRuntime.zip This is an optional step: create another instance with dedicated resources for production workload. There is no hard-and-fast rule if dedicated is better than dynamic allocation: discuss with your Azure Architect the pros and cons of dedicated resources.\n WARNING: The cost profile of dedicated resources is very different.\n aggregator-cli install.instance --name inst4 --resourceGroup RG1 --namingTemplate my-naming-template.json --location westeurope --hostingPlanTier Premium --hostingPlanSku P1V2 You can see that the resources satisfy the Naming Template.\nYou can scale up or down the compute resources associated with the Plan. Add an Aggregator Rule (advanced) # In this example we add a single Rule to serve two events. Once more, write a new text file named smart-hello.rule with this content:\nif (eventType == \u0026quot;workitem.created\u0026quot;) { return $\u0026quot;Hello new { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026quot;; } else { return $\u0026quot;Hi again { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026quot;; } The next snippet adds two rules where the file parameter is a local file relative to the working directory.\naggregator-cli add.rule --verbose --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --name smart-hello --file smart-hello.rule aggregator-cli list.rules --verbose --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json Tell Azure DevOps to call the Rule (advanced) # As before, we glue Azure DevOps to the Rules hosted in Azure Functions. Note that both events goes to the same Rule.\naggregator-cli map.rule --verbose --project SampleProject --event workitem.created --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --rule smart-hello aggregator-cli map.rule --verbose --project SampleProject --event workitem.updated --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --rule smart-hello Now, if you move to the AppInsight instance associated with the Function and run this Query\ntraces | where operation_Name==\u0026#39;smart-hello\u0026#39; | project timestamp, message You can see the traces collected from the Rule.\nExecute Impersonated # A Rule can use impersonation, that is, instead of authenticating with Azure DevOps as the account who generated the PAT, the Rule can tell Azure DevOps to use the identity of the who generated the event.\nAttention: To use this feature, the identity accessing Azure DevOps needs special permissions; see Rule Examples.\naggregator-cli configure.rule --verbose --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --name smart-hello --enableImpersonate=true Updating a Rule # After some time you may want to change a Rule. After testing thoroughly in a development environment, you want to update production with the new version of the Rule.\naggregator-cli update.rule --verbose --instance inst3 --resourceGroup RG1 --namingTemplate my-naming-template.json --name smart-hello --file smart-hello-v2.rule Careful: updating a Rule may cause downtime.\n "});index.add({'id':33,'href':'/docs/v2/using/upgrade-from-v1/','title':"Upgrading from v1",'content':"Migrating from v1 # TFS Aggregator 2 is a full rewrite of the plugin. The old rule syntax is no longer supported. In case you\u0026rsquo;re looking for the latest version of version 1.01, you can still find it here (including a large number of fixes and security updates).\nIf you want to upgrade to 2.x you\u0026rsquo;ll have to rewrite your rules in the new format, the installation and upgrade process are explained below.\nNote: we won\u0026rsquo;t provide any further support on this old version. But if you have a large investment in the old-style rules, it may provide you a better, stabler version until you\u0026rsquo;re ready to move to V2.\nNote: You can run both V1 and V2 side-by-side on the same TFS system, you will have to be extra careful not to create infinite loops though.\nUpgrade binaries # Remove old version, namely delete the TFSAggregator.dll and AggregatorItems.xml files from the plugin location on the Application Tier of your TFS Server.\nThe plugin folder is usually at this path: C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Application Tier\\Web Services\\bin\\Plugins\nAt this point deploy the new version as described in Install.\nConvert the rules # Please refer old syntax page and new syntax.\nSample conversion # The old aggregation adds up the estimated work on the task.\n\u0026lt;AggregatorItem operationType=\u0026quot;Numeric\u0026quot; operation=\u0026quot;Sum\u0026quot; linkType=\u0026quot;Self\u0026quot; workItemType=\u0026quot;Task\u0026quot;\u0026gt; \u0026lt;TargetItem name=\u0026quot;Estimated Work\u0026quot;/\u0026gt; \u0026lt;SourceItem name=\u0026quot;Estimated Dev Work\u0026quot;/\u0026gt; \u0026lt;SourceItem name=\u0026quot;Estimated Test Work\u0026quot;/\u0026gt; \u0026lt;/AggregatorItem\u0026gt; The equivalent rule in the policy is\n\u0026lt;rule name=\u0026quot;Sum\u0026quot; appliesTo=\u0026quot;Task\u0026quot; hasFields=\u0026quot;Title,Description\u0026quot;\u0026gt;\u0026lt;![CDATA[ self[\u0026quot;Estimated Work\u0026quot;] = (double)self[\u0026quot;Estimated Dev Work\u0026quot;] + (double)self[\u0026quot;Estimated Test Work\u0026quot;]; ]]\u0026gt;\u0026lt;/rule\u0026gt; Note the cast on fields\u0026rsquo; values.\n"});index.add({'id':34,'href':'/docs/v2/admin/','title':"Administrator Guide",'content':"Administrator Guide to TFS Aggregator # This Guide explains how install TFS Aggregator on premise and in the cloud for both the Web Service and Server Plugin flavors.\n"});index.add({'id':35,'href':'/docs/v3/setup/docker/','title':"Docker configuration",'content':"Docker configuration [v1.0] # Please make sure you are familiar with Aggregator design as explained in Design.\nPrerequisites # Azure DevOps PAT # You must have an Azure DevOps Personal Access Token as described here to authenticate against Azure DevOps. This is the identity that authenticates and acts on Azure DevOps.\nDNS name # Azure DevOps Server does not allow web hooks to localhost. The container must be accessible through a proper DNS name.\nSSL Certificate # Azure DevOps requires TLS encrypted connections to third party. The docker image assumes to find a certificate in the secrets folder to use for HTTPS.\nCreate / get a certificate in PFX format, name it aggregator.pfx. If you have a PEM (.pem, .crt, .cer) or PKCS#7/P7B (.p7b, .p7c) file, you can use OpenSSL to produce an equivalent PFX format (e.g. follow this guide).\nYou can generate a test certificate with a similar PowerShell snippet.\n$cert = New-SelfSignedCertificate -KeyLength 2048 -KeyAlgorithm RSA -Type SSLServerAuthentication -FriendlyName \u0026#34;Aggregator\u0026#34; -NotAfter 2025-12-31 -Subject \u0026#34;aggregator.example.com\u0026#34; -TextExtension @(\u0026#34;2.5.29.17={text}DNS=aggregator.example.com\u0026amp;IPAddress=127.0.0.1\u0026amp;IPAddress=::1\u0026#34;) $certPass = Read-Host -Prompt \u0026#34;My password\u0026#34; -AsSecureString Export-PfxCertificate -FilePath \u0026#34;aggregator.pfx\u0026#34; -Cert $cert -Password $certPass Define the set of valid API Keys # Add a file apikeys.json, similar to the following.\n[ { \u0026#34;key\u0026#34;: \u0026#34;api-123457890abcdef1234567890abcdef\u0026#34; }, { \u0026#34;key\u0026#34;: \u0026#34;api-23457890abcdef1234567890abcdef1\u0026#34; } ] A valid API Key is made of 32 hexadecimal characters prefixed by api- for a total 36 chars. In PowerShell\n\u0026#34;api-$( (New-Guid) -replace \u0026#39;-\u0026#39;,\u0026#39;\u0026#39; )\u0026#34; or on Linux/Mac\necho \u0026#34;api-$( uuidgen | tr -d \u0026#39;-\u0026#39; )\u0026#34; or\necho \u0026#34;api-$( openssl rand -hex 16 )\u0026#34; The API Key values will be used to authenticate the Web Hook call from Azure DevOps.\nShared secret with CLI (optional) # Define the environment variable Aggregator_SharedSecret both at the container and where you launch the CLI. This is required to use CLI commands like map.local.rule to configure Azure DevOps.\nVolume with Rule files # Create a folder to contain the Rule files, e.g. C:\\aggregator-cli\\docker\\rules\\ and copy there the Rule files. Test the Rules before using the invoke.rule command.\nTest the container # Pull the latest image from Docker Hub using the version matching the operating system.\ndocker pull tfsaggregator/aggregator3:latest Example of running the container. Windows docker run --rm -it -p 5320:5320 -e Aggregator_VstsToken=******** -e ASPNETCORE_Kestrel__Certificates__Default__Password=\u0026#34;********\u0026#34; --mount type=bind,source=c:/src/github.com/tfsaggregator/aggregator-cli/docker/secrets/,target=c:/secrets --mount type=bind,source=c:/src/github.com/tfsaggregator/aggregator-cli/docker/rules/,target=c:/rules tfsaggregator/aggregator3:latest Linux docker run --rm -it -p 5320:5320 -e Aggregator_VstsToken=******** -e ASPNETCORE_Kestrel__Certificates__Default__Password=\u0026#34;********\u0026#34; -v /mnt/c/src/github.com/tfsaggregator/aggregator-cli/docker/rules:/rules -v /mnt/c/src/github.com/tfsaggregator/aggregator-cli/docker/secrets:/secrets tfsaggregator/aggregator3:latest Clearly, replace the asterisks (********) with secret values.\nThe output should be similar to the following\nDocker mode. info: Microsoft.Hosting.Lifetime[0] Now listening on: https://[::]:5320 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Production info: Microsoft.Hosting.Lifetime[0] Content root path: C:\\app Azure DevOps refuses localhost connections for Web hooks. The container must be exposed using a DNS name. Try access the /config/status endpoint to check connectivity, e.g.\ncurl -X GET https://aggregator.example.com:5320/config/status Add the --insecure if you are using a self-signed certificate (not recommended for production). If your system hasn\u0026rsquo;t curl, you can test using PowerShell\nInvoke-RestMethod -Method Get -Uri https://aggregator.example.com:5320/config/status Add -SkipCertificateCheck if you are using a self-signed certificate (not recommended).\nEnvironment Variables # The container is configurable using these environment variables.\nWindows Variable Use Default value ASPNETCORE_URLS Set the listening port https://*:5320 ASPNETCORE_Kestrel__Certificates__Default__Path SSL Certificate c:\\\\secrets\\\\aggregator.pfx ASPNETCORE_Kestrel__Certificates__Default__Password SSL Certificate password Logging__LogLevel__Aggregator Level of Application Logging Debug Aggregator_ApiKeysPath Valid API Keys c:\\\\secrets\\\\apikeys.json Aggregator_SharedSecret Shared password to authenticate CLI Aggregator_RulesPath Directory with Rule files c:\\\\rules Aggregator_VstsTokenType Type of Azure DevOps authentication PAT Aggregator_VstsToken Azure DevOps Personal Authentication Token Aggregator_AzureDevOpsCertificate Azure DevOps certificate to trust AGGREGATOR_TELEMETRY_DISABLED Control telemetry false Linux Variable Use Default value ASPNETCORE_URLS Set the listening port https://*:5320 ASPNETCORE_Kestrel__Certificates__Default__Path SSL Certificate /secrets/aggregator.pfx ASPNETCORE_Kestrel__Certificates__Default__Password SSL Certificate password Logging__LogLevel__Aggregator Level of Application Logging Debug Aggregator_ApiKeysPath Valid API Keys /secrets/apikeys.json Aggregator_SharedSecret Shared password to authenticate CLI Aggregator_RulesPath Directory with Rule files /rules Aggregator_VstsTokenType Type of Azure DevOps authentication PAT Aggregator_VstsToken Azure DevOps Personal Authentication Token Aggregator_AzureDevOpsCertificate Azure DevOps certificate to trust AGGREGATOR_TELEMETRY_DISABLED We do not recommend using unsecured HTTP. The certificate should be trusted by the Azure DevOps instance. Note that the backslash character (\\) must be doubled for Windows paths. Azure DevOps SSL certificate (optional) # This option is useful if Azure DevOps is using a certificate issued by an internal Certificate Authority (CA). Aggregator running in the container trusts only certificate issued by public CAs.\nTo add a certificate to the trusted roots, copy the .cer in the secrets folder and set the Aggregator_AzureDevOpsCertificate environment variable to the container internal path, like for Aggregator SSL cert.\nSample run: Windows docker run --rm -it -p 5320:5320 -e Aggregator_AzureDevOpsCertificate=c:/secrets/myazuredevops.cer -e Aggregator_VstsToken=******** -e ASPNETCORE_Kestrel__Certificates__Default__Password=\u0026#34;********\u0026#34; --mount type=bind,source=c:/src/github.com/tfsaggregator/aggregator-cli/docker/secrets/,target=c:/secrets --mount type=bind,source=c:/src/github.com/tfsaggregator/aggregator-cli/docker/rules/,target=c:/rules tfsaggregator/aggregator3:latest Linux This will work also for a self-signed certificate.\nChecklist for Docker setup [v1.0] # DNS entry for Aggregator generate a valid certificate in .pfx format (matches the above) pull the docker image prepare apikeys.json copy the pfx and json files above in a secrets directory on the docker host write the rule files (you can test them using the CLI) copy the rule files in a rules directory on the docker host run the container, mounting both the secrets and rules directories, and defining the Aggregator_SharedSecret environment variable Now the Aggregator instance running in the container is ready to serve.\n create a PAT in Azure DevOps download the CLI on any machine that can see both Azure DevOps and the Aggregator Container set the Aggregator_SharedSecret environment variable in the shell running the CLI use the map.local.rule to setup the WebHook Subscriptions in Azure DevOps "});index.add({'id':36,'href':'/docs/v3/rules/','title':"Rules",'content':"An Aggregator Rule is a simple text file ending in .rule. At the beginning of the file you can put one or more directive to control the Interpreter. It is recommended to add the event directive. The remaining part of the file is a normal programming language with access to a few objects and interact with Azure DevOps.\n.directive1 .directive2 arg1 .directive3 arg1 arg2 /* * valid C# code using predefined Objects, Variables and Types */ Directives # A directive is metadata used to change a behavior of the Rule Interpreter. The complete list of directives is here.\nC# language # The Rule language use C# as defined in Microsoft documentation. The code uses predefined items specific for the Azure DevOps events supported by Aggregator.\nPre-defined Variables # You can access any of these variable in any rule.\n Variable Type Description eventType string Name of event that triggered the rule. logger IAggregatorLogger offers methods to log messages with different levels. More details in Common objects.\nWork Item Variables # The following variables are available only for Work Item events.\n Variable Type Description self WorkItemWrapper The Work Item which triggered the rule. selfChanges WorkItemUpdateWrapper Represents the changes made to the Work Item object. Valid only for the workitem.updated event. store WorkItemStore Allows retrieval, creation and removal of work items. More details in WorkItem event objects.\n"});index.add({'id':37,'href':'/docs/v3/rules/directives/','title':"Directives",'content':"Directives # The directives must appear in the first lines of a Rule file. They are parsed by Aggregator and removed before compiling the code.\nlanguage directive # .lang=C# .language=Csharp\nCurrently the only supported language is C#. You can use the .lang directive to specify the programming language used by the rule. If no language is specified: C# is default.\nreference directive # Loads the specified assembly in the Rule execution context\nExample .reference=System.Xml.XDocument\nimport directive # Equivalent to C# namespace .import=System.Collections.Generic\nimpersonate directive # Aggregator uses credentials for accessing Azure DevOps. By default the changes which were saved back to Azure DevOps are done with the credentials provided for accessing Azure DevOps. In order to do the changes on behalf of the account who initiated an event, which Aggregator is going to handle, specify .impersonate=onBehalfOfInitiator\nAttention: To use this the identify accessing Azure DevOps needs special permissions, see Rule Examples.\ncheck directives # The check directives allow a fine control on a rule\u0026rsquo;s behaviour.\ncheck revision directive # This directive disable the safety check which forbids concurrent updates (see Parallelism). If you set .check revision false, and the work item was updated after the rule was triggered but before any change made by the rule are saved, the rule changes With .check revision true (assumed by default), you will receive a VS403351 error, in case the work item changed in between the rule reading and writing.\nevent directive [v1.0] # Restricts the events that may trigger the Rule, see also Common objects.\nExample\n.event=workitem.created .event=workitem.updated or in one line\n.events workitem.created,workitem.updated bypassrules directive [v1.1] # Do not enforce server-side rules associated with the Work Item Type while saving changes. By default Aggregator enforces Azure DevOps rules, e.g. a User Story in Agile Process cannot transition directly from New to Closed (see Workflow states).\nExample\n.bypassrules=true "});index.add({'id':38,'href':'/docs/v2/admin/install/','title':"Installing the Server Plugin",'content':" This information does not apply to the Web Service version.\n Pre-requisites # TFS # The TFS Aggregator works with the following versions of TFS:\n TFS 2013 update 2,3,4,5 TFS 2015 RTM TFS 2015 update 1,2,3 TFS 2017 RTM TFS 2017 update 1 Azure DevOps Server 2019 Azure DevOps Server 2020 Azure DevOps Server 2022 The installer will detect the correct TFS version and will install the appropriate binary. If you\u0026rsquo;re upgrading, please uninstall your current TFS Aggregator version, upgrade TFS, then run the installer to automatically install the matching version.\nWindows Event log # The TFS Aggregator plugins writes to Windows Event log critical errors; the TFSAggregator Source must be defined in the Application log. The MSI takes care of this.\nAccount permissions # The TFS service account must have permissions to change your work items (on behalf of others if you have enabled impersonation).\nAutomated Setup using Windows Installer # Download TFSAggregator MSI file from the latest Release.\nUnblock the downloaded file.\nLaunch the installer and accept the license\nThe MSI packages all three Aggregator flavors, one for each supported TFS version. The installer detects the TFS version installed and deploy the correct assemblies.\n The installer detects automatically the TFS version: it fails if TFS is not present.\n It will install under %ProgramFiles%, typically C:\\Program Files\\TFS Aggregator, three folders: bin, docs and samples. Furthermore it installs the plugin in the proper directory — for TFS 2017 can be C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\bin\\Plugins — the plugin assemblies and the default no-op Policy file samples\\TFSAggregator2.ServerPlugin.policies.\n An existing Policy file is not overwritten.\n Sample Install scripts # In the samples folder you will find two Powershell scripts to install or remove Aggregator in case the TFS Administrator does not want (or can) use the MSI file. The scripts require an elevated Powershell prompt.\nConfigure # To configure Aggregator you must add new rules in the policy file.\n Edit a copy of the sample TFSAggregator2.ServerPlugin.policies file. Test your new policy using TFSAggregator2.ConsoleApp.exe command line tool Copy the new file to the plugin folder; usually at this path for TFS 2017: C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\bin\\Plugins Verify that your new policy works; see TFS Aggregator Troubleshooting in case of problems. See Console Application for more information on using the command line tool.\nUpgrade TFS to a new version # Due to the fact that there have been breaking changes between TFS Server Object Model versions, there may be a need to fix the TFS aggregator after an upgrade.\nWhen upgrading TFS / Azure DevOps Server, you need to uninstall TFS aggregator before upgrading the Application Tier server re-install it afterwards.\nAn uninstall+reinstall is required, repairing may not work. TFS will detect the incompatibility and will not load the plugin if the version mismatches.\nManual Setup # Manual install # Download and extract the binaries from the latest release Create TFSAggregator2.ServerPlugin.policies using one of the provides examples to build your actual policy. You will find the complete syntax and examples following the links. Test your policy using TFSAggregator2.ConsoleApp.exe command line tool, see TFS Aggregator Troubleshooting. Register the EventLog source for TFS Aggregator, using an elevated Powershell prompt, by running New-EventLog -LogName \u0026quot;Application\u0026quot; -Source \u0026quot;TFSAggregator\u0026quot; Copy TFSAggregator2.ServerPlugin.dll, TFSAggregator2.Core.dll and TFSAggregator2.ServerPlugin.policies to the plugin location on the Application Tier of your TFS Servers: The plugin folder is usually at this path for TFS 2017: C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\bin\\Plugins; You must copy the exact same files on all TFS Application Tier servers. TFS detects automatically that a file was copied in and will load it in.\nYou can verify if assembly version matches TFS version using this Powershell code\n$pathToAssemblyFile = \u0026quot;C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\bin\\Plugins\\TFSAggregator2.ServerPlugin.dll\u0026quot; [System.Reflection.Assembly]::LoadFile($pathToAssemblyFile).GetCustomAttributesData() | ?{ $_.AttributeType -eq [System.Reflection.AssemblyConfigurationAttribute] } | select ConstructorArguments Manual Uninstall # Remove the TFSAggregator2.* files from the plugin location on the Application Tier of your TFS Servers\nThe plugin folder is usually at this path:\n TFS 2015: C:\\Program Files\\Microsoft Team Foundation Server 14.0\\Application Tier\\Web Services\\bin\\Plugins TFS 2013: C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Application Tier\\Web Services\\bin\\Plugins "});index.add({'id':39,'href':'/docs/v2/admin/console-app/','title':"Using the console to test Policies",'content':"The TFSAggregator2.ConsoleApp.exe command line tool is extremely useful to test and validate your policy files before applying to TFS.\n BEWARE Any changed workitem is written to TFS database! Use a test TFS instance.\n Syntax # TFSAggregator2.ConsoleApp.exe \u0026lt;command\u0026gt; [\u0026lt;options\u0026gt;] The only supported command is run.\nIf you launch the command without arguments, it will display an help screen.\nOptions # The available options are:\n Option (short form) Option (long form) Usage -h --help Shows help message and exit -f --policyFile=VALUE Policy file to test -c --teamProjectCollectionUrl=VALUE TFS Team Project Collection Url, e.g. http://localhost:8080/tfs/DefaultCollection -p --teamProjectName=VALUE TFS Team Project -n --id=VALUE[,VALUE...] \u0026lt;br\u0026gt; --workItemId=VALUE[,VALUE...] List of Work Item Ids -l --logLevel=VALUE Logging level (critical, error, warning, information, normal, verbose, diagnostic) The log level specified on the command line takes precedence over the level written in the policy file.\nSample invocation # TFSAggregator2.ConsoleApp.exe run --policyFile=samples\\TFSAggregator2.ServerPlugin.policies --teamProjectCollectionUrl=http://localhost:8080/tfs/DefaultCollection --teamProjectName=TfsAggregatorTest1 --workItemId=42 --logLevel=diagnostic Sample output # The output from the previous invocation should be similar to the following screenshot.\nDifferences from TFS Plugin # Here are some major behavioral differences.\n All logging is redirected to the console. If a work item is changed by the rule, it will be processed again by the tool to emulate TFS behavior. The order of processing may be different from TFS. TFS may use different application tier servers to process rules. The following diagrams may help understand the control flow.\nNormal flow using plugin\nDevelopment flow using Console Application\n"});index.add({'id':40,'href':'/docs/v3/rules/workitem/','title':"WorkItem event objects",'content':"These objects are available only for Work Items events.\nWorkItem Object # The initial WorkItem object, the one which triggered the rule, is contained in the self variable.\nRevisions # Navigate to previous versions of the work item.\nWorkItem PreviousRevision Returns a read-only copy of the previous revision of this work item.\nIEnumerable\u0026lt;WorkItem\u0026gt; Revisions Returns a read-only copy of all revisions of this work item.\nRelations # Navigate to related work items. See also Type WorkItemRelation or WorkItemRelationCollection\nIEnumerable\u0026lt;WorkItemRelation\u0026gt; RelationLinks Returns all relations as WorkItemRelation.\nWorkItemRelationCollection Relations Returns a collection to navigate and modify relations.\nIEnumerable\u0026lt;WorkItemRelation\u0026gt; ChildrenLinks Returns the children links in Hierarchy relation, i.e. System.LinkTypes.Hierarchy-Forward.\nIEnumerable\u0026lt;WorkItem\u0026gt; Children Returns the children work items in Hierarchy relation, i.e. System.LinkTypes.Hierarchy-Forward. E.g. a Task can be a child of a User Story.\nWorkItemRelation ParentLink Returns the parent link in Hierarchy relation, i.e. System.LinkTypes.Hierarchy-Reverse.\nWorkItem Parent Returns the parent work item in Hierarchy relation, i.e. System.LinkTypes.Hierarchy-Reverse. E.g. a User Story is the parent of a Task.\nLinks # Navigate links to non-work-item objects.\nIEnumerable\u0026lt;WorkItemRelation\u0026gt; RelatedLinks Returns related work items as WorkItemRelation.\nIEnumerable\u0026lt;WorkItemRelation\u0026gt; Hyperlinks Returns hyperlinks.\nint ExternalLinkCount Returns the number of links to external objects.\nint HyperLinkCount Returns the number of hyperlinks.\nint RelatedLinkCount Returns the number of related work items.\nCore Fields helpers # Data fields of the work item. See Work item field index for a complete description.\nint AreaId The unique ID of the area to which this work item is assigned.\nstring AreaPath Groups work items into product feature or team areas. The area must be a valid node in the project hierarchy.\nIdentityRef AssignedTo The name of the team member who currently owns the work item.\nIdentityRef AuthorizedAs\nDateTime? AuthorizedDate\nIdentityRef ChangedBy The name of the team member who modified the work item most recently.\nDateTime? ChangedDate The date and time when a work item was modified.\nIdentityRef CreatedBy The name of the team member who created the work item.\nDateTime? CreatedDate The date and time when a work item was created.\nstring Description Use this field to provide in-depth information about a work item. Caveat: It may contains HTML! string History The record of changes that were made to the work item after it was created.\nint Id Read-only. The unique identifier that is assigned to a work item. Negative when IsNew equals true.\nint IterationId The unique ID of the iteration to which this work item is assigned.\nstring IterationPath Groups work items by named sprints or time periods. The iteration must be a valid node in the project hierarchy.\nstring Reason The reason why the work item is in the current state.\nint Rev Read-only. A number that is assigned to the historical revision of a work item.\nDateTime? RevisedDate The date and time stamp when a test case or shared step is revised.\nstring State The current state of the work item.\nstring Tags A tag corresponds to a one or two keyword phrase that you define and that supports your needs to filter a backlog or query, or define a query.\nstring TeamProject The project to which a work item belongs.\nstring Title A short description that summarizes what the work item is and helps team members distinguish it from other work items in a list.\nstring Url Read-only.\ndouble Watermark Read-only. A system managed field (not editable) that increments with changes made to a work item.\nstring WorkItemType Read-only. The name of the work item type.\nFields # object this[string field] Read-write access to non-core fields. Must use reference name, like System.Title, instead of language specific, like Titolo, Titel or Title.\n Careful: Reference name is case-sensitive.\n public T GetFieldValue\u0026lt;T\u0026gt;(string field, T defaultValue) Typed read-only access to non-core fields. The value is converted to the requested type, if the field has no value, defaultValue is returned. Example:\nvar customField1 = self.GetFieldValue\u0026lt;string\u0026gt;(\u0026quot;MyOrg.StringCustomField1\u0026quot;, \u0026quot;MyDefault\u0026quot;); var customField2 = self.GetFieldValue\u0026lt;decimal\u0026gt;(\u0026quot;MyOrg.NumericCustomField2\u0026quot;, 3.0m); Custom Fields # When the Azure DevOps process has a custom fields, for example one named \u0026ldquo;Created In\u0026rdquo;, the field gets created as \u0026ldquo;Custom.CreatedIn\u0026rdquo;.\nHow to get its value:\nstring createdIn = (string)self[\u0026quot;Custom.CreatedIn\u0026quot;];\nHow to update its value:\nself[\u0026quot;Custom.CreatedIn\u0026quot;] = \u0026quot;New Value\u0026quot;;\nStatus properties # bool IsDeleted Read-only, returns true if the work item is currently located in recycle bin.\nbool IsReadOnly Read-only, returns true if work item cannot be modified.\nbool IsNew Read-only, returns true if work item is new.\nbool IsDirty Read-only, returns true if work item changed after retrieval.\nAttachments # int AttachedFileCount Returns the number of attached files.\nWorkItem Changes # If the rule was triggered by the workitem.updated event, the changes which were made to the WorkItem object, are contained in the selfChanges variable.\nFields # Data fields of the work item update.\nint Id Read-only. The unique identifier of the Update. Each change leads to an increased update id, but not necessarily to an updated revision number. Changing only relations, without changing any other information does not increase revision number.\nint WorkItemId Read-only. The unique identifier of the work item.\nint Rev Read-only. The revision number of work item update.\nIdentityRef RevisedBy Read-only. The Identity of the team member who updated the work item.\nDateTime RevisedDate Read-only. The date and time when the work item updates revision date.\nWorkItemFieldUpdate Fields[string field] Read-only. Access to the list of updated fields. Must use reference name, like System.Title, instead of language specific, like Titolo, Titel or Title.\nWorkItemRelationUpdates Relations Read-only. Returns the information about updated relations\nWorkItemFieldUpdate # Updated Field Information containing old and new value.\nobject OldValue Read-only. Returns the previous value of the field or null\nobject NewValue Read-only. Returns the new value of the field\nWorkItemRelationUpdates # Groups the changes of the relations\nICollection\u0026lt;WorkItemRelation\u0026gt; Added Read-only. Returns the added relations as WorkItemRelation.\nICollection\u0026lt;WorkItemRelation\u0026gt; Removed Read-only. Returns the removed relations as WorkItemRelation.\nICollection\u0026lt;WorkItemRelation\u0026gt; Updated Read-only. Returns the updated relations as WorkItemRelation.\nWorkItemStore Object # The WorkItemStore object allows retrieval, creation and removal of work items. This object is contained in the store variable.\nWorkItem GetWorkItem(int id) Returns a single work item.\nWorkItem GetWorkItem(WorkItemRelation item) Returns a single work item following the relation.\nIList\u0026lt;WorkItem\u0026gt; GetWorkItems(IEnumerable\u0026lt;int\u0026gt; ids) Returns a list of work items.\nIList\u0026lt;WorkItem\u0026gt; GetWorkItems(IEnumerable\u0026lt;WorkItemRelation\u0026gt; collection) Returns a list of work items following the relation.\nWorkItem NewWorkItem(string workItemType) Returns a new work item with a temporary Id. The work item is created when the rules ends. IsNew returns true.\nbool DeleteWorkItem(WorkItem workItem) Deletes the given work item and returns true if work item can be deleted.\nbool RestoreWorkItem(WorkItem workItem) Restores the given work item from recycle bin and returns true if work item can be restored.\nIEnumerable\u0026lt;WorkItemTypeCategory\u0026gt; GetWorkItemCategories() Returns a list of work item category names with the mapped work item types, see WorkItemTypeCategory\nIEnumerable\u0026lt;BacklogWorkItemTypeStates\u0026gt; GetBacklogWorkItemTypesAndStates() Returns a list of backlog work item types with their backlog level information and the state to state category mappings, see BacklogWorkItemTypeStates. WorkItemTypeCategory # Work item categories group work items types together, you can see a list of available categories in query editor:\nstring ReferenceName Category ReferenceName, e.g. \u0026ldquo;Microsoft.EpicCategory\u0026rdquo;\nstring Name Category Display Name, e.g. \u0026ldquo;Epic Category\u0026rdquo;\nIEnumerable\u0026lt;string\u0026gt; WorkItemTypeNames WorkItemType Names in this Category, e.g. \u0026ldquo;Epic\u0026rdquo; or \u0026ldquo;Test Plan\u0026rdquo;\nBacklogWorkItemTypeStates # A work item type with its Backlog Level Information and the work item State to State Category mapping. The mappings can be seen per work item template in the states configuration, e.g. \u0026ldquo;Epic\u0026rdquo;:\nstring Name WorkItem Name, e.g. \u0026ldquo;Epic\u0026rdquo;\nBacklogInfo Backlog Backlog Level Information for this WorkItem Type.\nIDictionary\u0026lt;string, string[]\u0026gt; StateCategoryStateNames State Category (Meta-State) to WorkItem state name mapping.\nExample: mapping for the WorkItem Type Epic of default Agile Process:\n \u0026ldquo;Proposed\u0026rdquo; = \u0026ldquo;New\u0026rdquo; \u0026ldquo;InProgress\u0026rdquo; = \u0026ldquo;Active\u0026rdquo;, \u0026ldquo;Resolved\u0026rdquo; \u0026ldquo;Resolved\u0026rdquo; = \u0026lt;empty\u0026gt; \u0026ldquo;Complete\u0026rdquo; = \u0026ldquo;Closed\u0026rdquo; \u0026ldquo;Removed\u0026rdquo; = \u0026ldquo;Removed\u0026rdquo; BacklogInfo # Available Backlog Levels can be seen in the used process configuration. Example: The default Agile Backlog level names are: Epics, Features, Stories, Tasks\nstring ReferenceName The Category Reference Name of this Backlog Level, e.g. \u0026ldquo;Microsoft.EpicCategory\u0026rdquo; or \u0026ldquo;Microsoft.RequirementCategory\u0026rdquo;\nstring Name The Backlog Level Display Name, e.g. \u0026ldquo;Epics\u0026rdquo; or \u0026ldquo;Stories\u0026rdquo;\nWorkItemRelationCollection type # Navigate and modify related objects.\nIEnumerator\u0026lt;WorkItemRelation\u0026gt; GetEnumerator() Returns an enumerator on relations to use in foreach loops.\nAdd(WorkItemRelation item) Adds the element to the collection.\nAddChild(WorkItem child) Adds a child work item.\nAddParent(WorkItem parent) Adds a parent work item.\nAddLink(string type, string url, string comment) Adds an element to the collection.\nAddHyperlink(string url, string comment = null) Adds a hyperlink to the collection.\nAddRelatedLink(WorkItem item, string comment = null) Adds a related work item to the collection.\nAddRelatedLink(string url, string comment = null) Adds a related work item to the collection.\nClear() Removes all elements from the collection.\nbool Contains(WorkItemRelation item) Returns true if the element is present in the collection.\nbool Remove(WorkItemRelation item) Removes the element from the collection.\nint Count Returns the number of elements in the work item collection.\nbool IsReadOnly Returns true is collection is read-only.\nWorkItemRelation type # int LinkedId Read-only, returns the Id to the target object.\nstring Title Read-only, returns the title property of the relation.\nstring Rel Read-only, returns the type of the relation, e.g. System.LinkTypes.Hierarchy-Reverse. See Link type reference.\nstring Url Read-only, returns the URL to the target object.\nIDictionary\u0026lt;string, object\u0026gt; Attributes To manipulate the possible attributes of the relation. Currently Azure DevOps uses only the comment attribute.\n"});index.add({'id':41,'href':'/docs/v3/rules/common-rule-objects/','title':"Common objects",'content':"The following objects are not event specific and can be used in any Rule.\nEvent variable [v0.9.11] # The event variable describes what triggered the rule. It can hold one of the following string constants.\n\u0026#34;workitem.created\u0026#34; \u0026#34;workitem.updated\u0026#34; \u0026#34;workitem.commented\u0026#34; \u0026#34;workitem.deleted\u0026#34; \u0026#34;workitem.restored\u0026#34; This makes easier to write a single rule which reacts to multiple events.\nLogger Object # The Function logger object is contained in the logger variable. It support four methods:\n WriteVerbose(message) WriteInfo(message) WriteWarning(message) WriteError(message) IdentityRef type # Represents a User identity. Use mostly as a read-only object. Use the DisplayName property to assign a user.\nstring DirectoryAlias\nstring DisplayName Read-write, use this property to set an identity Field like AssignedTo.\nstring Id Read-only; Unique Id.\nstring ImageUrl Read-only;\nbool Inactive Read-only; true if account is not active.\nbool IsAadIdentity\nbool IsContainer Read-only; true for groups, false for users.\nstring ProfileUrl\nstring UniqueName\nstring Url\n"});index.add({'id':42,'href':'/docs/v2/admin/logging/','title':"Logging",'content':"TFS Aggregator logging is quite rich but the exact configuration depends on your environment.\nLogging levels # The possible Logging levels are: Critical, Error, Warning, Information or Normal, Verbose, and Diagnostic.\nEnable Debug Logging # To control the verbosity in TFS Aggregator, you have to set a level attribute to the logging element in your TFSAggregator2.ServerPlugin.policies file. Use a value like Verbose or Diagnostic.\n\u0026lt;?xml version=\u0026quot;1.0\u0026quot; encoding=\u0026quot;utf-8\u0026quot;?\u0026gt; \u0026lt;AggregatorConfiguration\u0026gt; \u0026lt;runtime\u0026gt; \u0026lt;logging level=\u0026quot;Diagnostic\u0026quot;/\u0026gt; \u0026lt;/runtime\u0026gt; Note that you can use the logger object in your rules to trace execution and values.\nStartup logging (2.3) # During TFS Aggregator start, before the Policies file is read, the logging level is controlled by TFSAggregator2.ServerPlugin.dll.config, for the Server Plugin, and by Web.config, for the Web Service, through the DefaultLoggingLevel key in the appSettings section, as in this example.\n\u0026lt;configuration\u0026gt; \u0026lt;appSettings\u0026gt; \u0026lt;add key=\u0026quot;DefaultLoggingLevel\u0026quot; value=\u0026quot;Verbose\u0026quot;/\u0026gt; \u0026lt;/appSettings\u0026gt; \u0026lt;/configuration\u0026gt; If the file or section is absent, the default value is Normal.\nCapturing log on TFS # For TFS on premises or hosted, you can download DebugView from Microsoft\u0026rsquo;s SysInternals site.\nDebugView is a Trace Listener and will capture the trace messages from TFS Aggregator. You have to run DebugView on all TFS Application Tier machines.\n We would recommend adding the *TFSAggregator* filter to DebugView so that you only see the TFS Aggregator traces.\nMake sure to enable the Capture Global Win32 option. Download DebugView at http://technet.microsoft.com/en-us/sysinternals/bb896647.\nTFS Production Logging (2.1) # TFS Aggregator log messages go to:\n Debug output (appers in the Output window of a debugger) Application EventLog (TFS Aggregator source) when message level is Warning or Critical Trace listeners User messages \u0026ndash; i.e. logger.Log calls in Rules \u0026ndash; use a specific Trace source: TfsAggregator.User.\nNow, you can send traces to a file by adding to TFS web.config a system.diagnostics section similar to the this:\n\u0026lt;system.diagnostics\u0026gt; \u0026lt;sources\u0026gt; \u0026lt;source name=\u0026quot;TfsAggregator.ServerPlugin\u0026quot; switchValue=\u0026quot;All\u0026quot;\u0026gt; \u0026lt;listeners\u0026gt; \u0026lt;remove name=\u0026quot;Default\u0026quot; /\u0026gt; \u0026lt;add name=\u0026quot;filelog\u0026quot; /\u0026gt; \u0026lt;/listeners\u0026gt; \u0026lt;/source\u0026gt; \u0026lt;source name=\u0026quot;TfsAggregator.User\u0026quot; switchValue=\u0026quot;All\u0026quot;\u0026gt; \u0026lt;listeners\u0026gt; \u0026lt;remove name=\u0026quot;Default\u0026quot; /\u0026gt; \u0026lt;add name=\u0026quot;filelog\u0026quot; /\u0026gt; \u0026lt;/listeners\u0026gt; \u0026lt;/source\u0026gt; \u0026lt;/sources\u0026gt; \u0026lt;sharedListeners\u0026gt; \u0026lt;add name=\u0026quot;filelog\u0026quot; type=\u0026quot;Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\u0026quot; BaseFileName=\u0026quot;TfsAggregator.log\u0026quot; DiskSpaceExhaustedBehavior=\u0026quot;ThrowException\u0026quot; Location=\u0026quot;Custom\u0026quot; CustomLocation = \u0026quot;C:\\Temp\u0026quot; MaxFileSize=\u0026quot;81920000\u0026quot; LogFileCreationSchedule=\u0026quot;Daily\u0026quot;/\u0026gt; \u0026lt;/sharedListeners\u0026gt; \u0026lt;trace autoflush=\u0026quot;true\u0026quot;/\u0026gt; \u0026lt;/system.diagnostics\u0026gt; TFS web.config is usually located in C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\web.config or similar.\n"});index.add({'id':43,'href':'/docs/v2/admin/troubleshooting/','title':"Troubleshooting",'content':"So you setup TFS Aggregator and it just sits there\u0026hellip;. doing nothing\u0026hellip;\nWell, this is a check list of things to double check.\nServer Plugin Checklist # You are using it on a TFS 2013 update 2 or later server You installed the right version for your TFS server You can verify if assembly version matches TFS version using this Powershell code $pathToAssemblyFile = \u0026quot;C:\\Program Files\\Microsoft Team Foundation Server 15.0\\Application Tier\\Web Services\\bin\\Plugins\\TFSAggregator2.ServerPlugin.dll\u0026quot; [System.Reflection.Assembly]::LoadFile($pathToAssemblyFile).GetCustomAttributesData() | ?{ $_.AttributeType -eq [System.Reflection.AssemblyConfigurationAttribute] } | select ConstructorArguments You copied the DLLs and the Policies file to the plugins location on all TFS Application Tier Servers (Usually at: :\\Program Files\\Microsoft Team Foundation Server {version}\\Application Tier\\Web Services\\bin\\Plugins) You have given permission to the user running the plugin, e.g. add the \u0026ldquo;TFS Service Account\u0026rdquo; to the Project Collection Administrators TFS Group; if the user has not permission, you see a similar error in the Event Log of the TFS Server(s) You may have to do this from the command line using tfssecurity /collection:http://server:8080/tfs/DefaultCollection /g+ \u0026quot;Project Collection administrators\u0026quot; \u0026quot;LOCAL SERVICE\u0026quot; if your service account is either LocalService, NetworkService or any other Windows Well-known identity, since they are no longer shown in the permission UI. When using the Impersonation option, make sure the user executing the plugin (generally the TFS Service account) has the \u0026ldquo;Make requests on behalf of others\u0026rdquo; permission at the server level If you upgraded your TFS and did not uninstall the TFS Aggregator before doing this TFS upgrade, the Aggregator does not work. Remove the TFS Aggregator from the TFS Program Files folder or run the uninstall of the TFS Aggegrator (backup your policies!). Then re-install the TFS Aggegrator setup or install manually for TFS 2015 or 2017 as described here. Every TFS version has its \u0026ldquo;own\u0026rdquo; assembly for the aggregator so it is important to use the right version against the right TFS. Also if you are having issues we recommend debugging your Policies file using TFSAggregator2.ConsoleApp.exe and trying that out (see below).\nPolicy / Rules Checklist # You have updated a work item that triggers a rule. (The TFS Aggregation only works once a work item that has aggregation rules on it is updated. This may change in a future version.) If the rule navigates between work items, work items have a proper Link (e.g. Parent-Child). You have valid names for source and destination fields in TFSAggregator2.ServerPlugin.policies. When you saved the policy file you saved it as UTF-8 encoding (in Notepad++ it is called “utf-8 without BOM”) (This should not be an issue, but it does not hurt to check). Use Console Application # The TFSAggregator2.ConsoleApp.exe command line tool is extremely useful to test and validate your policy files before applying to TFS.\nSample invocation\nTFSAggregator2.ConsoleApp.exe run --logLevel=diagnostic --policyFile=samples\\TFSAggregator2.ServerPlugin.policies --teamProjectCollectionUrl=http://localhost:8080/tfs/DefaultCollection --teamProjectName=TfsAggregatorTest1 --workItemId=42 See Console Application for more information on using the command line tool.\n"});index.add({'id':44,'href':'/docs/v2/admin/support/','title':"Support",'content':"If you checked everything in Troubleshooting and it still does not work, create a new Issue on GitHub or send an email to [[email protected]](mailto:[email protected]?subject=Support Request). Please add any useful information like:\n Aggregator version TFS version or VSTS Content of your TFSAggregator2.ServerPlugin.policies file (e.g. save it on https://gist.github.com/ and copy the link in the Issue) Definition of your work item types (use witadmin exportwitd) If you built the assemblies yourselves, they take a fixed 2.0.0.0 version. Please report, the source code version (Tag or commit SHA) used to build your binaries.\n Mask or remove any sensitive information before posting!\n Namely PAT, users and passwords, machine names, URLs.\nConsider that this is a voluntary work and we have families and daily jobs, which means: no guarantee of fast response. Timezone differences may also impact on response times.\n"});index.add({'id':45,'href':'/docs/v2/admin/security/','title':"Security",'content':"We strove to limit the API exposed to Rules and the chance of unwanted access.\nIt is up to the TFS Administrator validate and deploy the policy file on production TFS.\nTest the policy file on a TFS staging environment with a single Application Tier server. If you have more than one enabled, TFS can be turned temporarily off on a server using TFSServiceControl quiesce command.\nUse the Rate Limit feature to reduce the chance of infinite loops.\n"});index.add({'id':46,'href':'/docs/v3/rules/rule-examples-basic/','title':"Basic examples",'content':"Basic Rule examples # To start with simple rules you can see here some examples, for more usage please see Advanced Examples\nHello World # A trivial rule that returns some core fields of the work item which triggered the rule.\n$\u0026#34;Hello { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026#34; Auto-close parent # This is more similar to classic TFS Aggregator. It moves a parent work item to Closed state, if all children are closed. The major difference is the navigation: Parent and Children properties do not returns work items but relation. You have to explicitly query Azure DevOps to retrieve the referenced work items.\nstring message = \u0026#34;\u0026#34;; var parent = self.Parent; if (parent != null) { var children = parent.Children; if (children.All(c =\u0026gt; c.State == \u0026#34;Closed\u0026#34;)) { parent.State = \u0026#34;Closed\u0026#34;; message = \u0026#34;Parent was closed\u0026#34;; } else { message = \u0026#34;Parent was not closed\u0026#34;; } parent.Description = parent.Description + \u0026#34; aggregator was here.\u0026#34;; } return message; Work item update # Check if a work item was updated and execute actions based on the changes, e.g. if work item Title was updated.\nif (selfChanges.Fields.ContainsKey(\u0026#34;System.Title\u0026#34;)) { var titleUpdate = selfChanges.Fields[\u0026#34;System.Title\u0026#34;]; return $\u0026#34;Title was changed from \u0026#39;{titleUpdate.OldValue}\u0026#39; to \u0026#39;{titleUpdate.NewValue}\u0026#39;\u0026#34;; } else { return \u0026#34;Title was not updated\u0026#34;; } History # PreviousRevision is different because retrieves a read-only version of the work item.\nreturn self.PreviousRevision.PreviousRevision.Description; Create new Work Item # var parent = self; // test to avoid infinite loop if (parent.WorkItemType == \u0026#34;Task\u0026#34;) { return \u0026#34;No root type\u0026#34;; } var children = parent.Children; // test to avoid infinite loop if (!children.Any(c =\u0026gt; c.Title == \u0026#34;Brand new child\u0026#34;)) { var newChild = store.NewWorkItem(\u0026#34;Task\u0026#34;); newChild.Title = \u0026#34;Brand new child\u0026#34;; parent.Relations.AddChild(newChild); return \u0026#34;Item added\u0026#34;; } return parent.Title; Iterate Successor Work Items # var allWorkItemLinks = self.RelationLinks; foreach(var successorLink in allWorkItemLinks.Where(link =\u0026gt; string.Equals(\u0026#34;System.LinkTypes.Dependency-Forward\u0026#34;, link.Rel))) { // load successor from store var successor = store.GetWorkItem(successorLink); //do update of successor with e.g. title of self successor.Title = \u0026#34;new Title: (predecessor \u0026#34; + self.Title + \u0026#34;)\u0026#34;; } return self.Title; For additional link types see here.\nIterate linked Test Cases # // assume self is a \u0026#39;User Story\u0026#39; var allTests = new List\u0026lt;string\u0026gt;(); foreach(var testedByLink in self.RelationLinks.Where(link =\u0026gt; link.Rel == \u0026#34;Microsoft.VSTS.Common.TestedBy-Forward\u0026#34;)) { var testCase = store.GetWorkItem(testedByLink); allTests.Add( $\u0026#34;{testCase.Id.Value} - {testCase.Title}\u0026#34; ); } // display the linked test cases return string.Join(\u0026#34;,\\r\\n\u0026#34;, allTests); For additional link types see here.\n"});index.add({'id':47,'href':'/docs/v3/rules/rule-examples-advanced/','title':"Advanced examples",'content':"Advanced Rule examples # Backlog work items: Auto-activate parent # This is a more advanced version which has no hard coded work item type names. It moves a parent work item to active state (activates parent), if a child gets activated and both parent and child are backlog work items\n//Method to check if a \u0026#39;workItem\u0026#39; is in a \u0026#39;Progress\u0026#39; state bool IsInProgress(WorkItemWrapper workItem, BacklogWorkItemTypeStates workItemType) { var concreteStateNames = workItemType?.StateCategoryStateNames .Where(category =\u0026gt; string.Equals(\u0026#34;InProgress\u0026#34;, category.Key, StringComparison.OrdinalIgnoreCase)) .SelectMany(category =\u0026gt; category.Value); return concreteStateNames?.Contains(workItem.State) ?? false; } // First simple check if there is a parent var parentWorkItem = self.Parent; if (parentWorkItem == null) { return \u0026#34;No Parent\u0026#34;; } // now get the backlog work item types with their state to category mapping var backlogWorkItems = await store.GetBacklogWorkItemTypesAndStates(); var backlogWorkItemsLookup = backlogWorkItems.ToDictionary(itemType =\u0026gt; itemType.Name, itemType =\u0026gt; itemType); // Check if we are a back log work item and we are in an InProgress state var workItemType = backlogWorkItemsLookup.GetValueOrDefault(self.WorkItemType); if (!IsInProgress(self, workItemType)) { return workItemType == null ? \u0026#34;No Backlog work item type\u0026#34; : $\u0026#34;work item not \u0026lt;InProgress\u0026gt; (State={self.State})\u0026#34;; } // Check if parent already in progress state and a back log work var parentWorkItemType = backlogWorkItemsLookup.GetValueOrDefault(parentWorkItem.WorkItemType); if (IsInProgress(parentWorkItem, parentWorkItemType)) { return parentWorkItemType == null ? \u0026#34;No Backlog work item type\u0026#34; : $\u0026#34;work item already \u0026lt;InProgress\u0026gt; (State={parentWorkItem.State})\u0026#34;; } // Now set the parent to a state of the InProgress category parentWorkItem.State = parentWorkItemType.StateCategoryStateNames[\u0026#34;InProgress\u0026#34;].First(); return $\u0026#34;updated Parent {parentWorkItem.WorkItemType} #{parentWorkItem.Id} to State=\u0026#39;{parentWorkItem.State}\u0026#39;\u0026#34;; Backlog work items: Auto-Resolve parent # This is more similar to classic TFS Aggregator. It moves a parent backlog work item to Resolved state, if all children are closed or terminated.\n//base method to check state bool IsBacklogWorkItemInState(WorkItemWrapper workItem, BacklogWorkItemTypeStates workItemType, IEnumerable\u0026lt;string\u0026gt; expectedStateCategories) { bool IsInExpectedStateCategory(KeyValuePair\u0026lt;string, string[]\u0026gt; category) { return expectedStateCategories.Any(expectedStateCategroy =\u0026gt; string.Equals(expectedStateCategroy, category.Key, StringComparison.OrdinalIgnoreCase)); } var concreteStateNames = workItemType?.StateCategoryStateNames .Where(IsInExpectedStateCategory) .SelectMany(category =\u0026gt; category.Value); return concreteStateNames?.Contains(workItem.State) ?? false; } //Method to check if a \u0026#39;workItem\u0026#39; is in a \u0026#39;Removed\u0026#39; or \u0026#39;Completed\u0026#39; state bool IsRemovedOrCompleted(WorkItemWrapper workItem, BacklogWorkItemTypeStates workItemType) { var expectedStateCategories = new string[] { \u0026#34;Completed\u0026#34;, \u0026#34;Removed\u0026#34;, }; return IsBacklogWorkItemInState(workItem, workItemType, expectedStateCategories); } var parentWorkItem = self.Parent; if (parentWorkItem == null) { return \u0026#34;No Parent\u0026#34;; } var backlogWorkItems = await store.GetBacklogWorkItemTypesAndStates(); var backlogWorkItemsLookup = backlogWorkItems.ToDictionary(itemType =\u0026gt; itemType.Name, itemType =\u0026gt; itemType); var workItemType = backlogWorkItemsLookup.GetValueOrDefault(self.WorkItemType); if (!IsRemovedOrCompleted(self, workItemType)) { return workItemType == null ? \u0026#34;No Backlog work item type\u0026#34; : $\u0026#34;work item not \u0026lt;Removed\u0026gt; or \u0026lt;Completed\u0026gt; (State={self.State})\u0026#34;; } var parentWorkItemType = backlogWorkItemsLookup.GetValueOrDefault(parentWorkItem.WorkItemType); if (IsRemovedOrCompleted(parentWorkItem, parentWorkItemType)) { return parentWorkItem == null ? \u0026#34;No Backlog work item type\u0026#34; : $\u0026#34;work item already \u0026lt;Removed\u0026gt; or \u0026lt;Completed\u0026gt; (State={parentWorkItem.State})\u0026#34;; } if (!parentWorkItem.Children.All(item =\u0026gt; IsRemovedOrCompleted(item, backlogWorkItemsLookup.GetValueOrDefault(item.WorkItemType)))) { return $\u0026#34;Not all child work items \u0026lt;Removed\u0026gt; or \u0026lt;Completed\u0026gt;: {string.Join(\u0026#34;,\u0026#34;, parentWorkItem.Children.Select(item =\u0026gt; $\u0026#34;#{item.Id}={item.State}\u0026#34;))}\u0026#34;; } var progressStates = parentWorkItemType.StateCategoryStateNames[\u0026#34;InProgress\u0026#34;]; parentWorkItem.State = progressStates.Last(); return $\u0026#34;updated Parent #{parentWorkItem.Id} to State=\u0026#39;{parentWorkItem.State}\u0026#39;\u0026#34;; "});index.add({'id':48,'href':'/docs/v2/contrib/','title':"Contributing",'content':"Contributors\u0026rsquo; Guide # Contributors are welcome!\nEven if you are not a developer, you can help the project by testing new versions or reviewing the documentation.\nIn this guide you can discover TFS Aggregator internal structure and the teams\u0026rsquo; rules.\n"});index.add({'id':49,'href':'/docs/v3/design/','title':"Design",'content':"In this page we want to explain the overall design and principles behind Aggregator.\nOverview # The Azure DevOps Service scenario (cloud) is best served by hosting Aggregator in an Azure Function App. The next diagram illustrates the relation between the major components of the cloud scenario. The Azure DevOps Server scenario (on premises) is best served by using the Aggregator Docker image. The next diagram illustrates the relation between the major components of the on premises scenario. Major components # CLI # Through the CLI you manage the Aggregator configuration. A configuration can be quite complex encompassing Azure Resources, Azure DevOps objects or Docker containers.\nInstance # An Aggregator Instance is a process that hosts the Rule Interpreter and exposes it via one or more endpoints.\nIn the cloud scenario it is an Azure Function Application in its own Resource Group, sharing the same Azure DevOps Service credential and version of Aggregator Runtime.\nThe name you pick for the Instance must be unique amongst all Aggregator Instances in Azure! This is a consequence of the uniqueness of Azure Function Application DNS hostname. You can have more than one Instance, for example a staging instance with limited computational resource and a production instance.\nYou can host an Instance in a Docker container. In this case, you have full control of the host environment. It can run on premises or in the cloud, as long as it can be reached by Azure DevOps. Note that you can host the container on Windows and on Linux.\nRule Interpreter (Aggregator Runtime) # The Rule Interpreter parses and executes the Rule file when triggered by an event. You can manually run the interpreter emulating an event via CLI.\nThe Rules must be previously uploaded to the hosting environment. This is a docker volume or an Azure Function.\nThe CLI checks the Interpreter version deployed on Azure Function. If there is a more recent version in GitHub Releases, the Interpreter is updated, unless the user has specified a desired version. In this latter case if the runtime does not match the version requested by the user, CLI uploads the one requested by the user.\nRules # A Rule is code that reacts to one or more Azure DevOps event. The Rule language is C# (hopefully more in the future) and uses Aggregator Runtime which exposes an object model to interact with data received in the event or later requested from Azure DevOps.\nMapping # An Aggregator Mapping is an Azure DevOps Service Hook Subscription triggered by a specific event. Currently we support only Work Item events. When triggered the Service Hook Subscription invokes a single Aggregator Rule. Azure DevOps saves the authentication secret (an Azure Function Key or an API Key) in the Service Hook configuration. Each event requires a unique Subscription, the same event can be monitored by more than one Subscription, and more than one Subscription can invoke the same Rule URL.\nRelationship between components # You can deploy the same Rule in different Instances, map the same Azure DevOps event to many Rules or map multiple events to the same Rule: it is up to you choosing the best way to organize.\n"});index.add({'id':50,'href':'/docs/v3/setup/production/','title':"Production on Azure",'content':"Production Configuration and Administration of Azure Functions # Renew Azure DevOps Personal Access Token (PAT) # Azure DevOps Personal Access Token (PAT) expires and is up to the administrator to renew or replace them. Aggregator stores the PAT in the Azure Function configuration, allowing the rules to access Azure DevOps. You should refresh the PAT before expires or save a new PAT in Aggregator configuration using the configure.instance command.\nControl Azure performance and costs # You can specify the size for the Virtual machine running the rules using the hostingPlanSku and hostingPlanTier options. The defaults are Y1/Dynamic, known as Consumption plan. You can select a Premium plan instead, like Premium/P2V2 for example.\nAzure Production monitoring # Disable built-in logging and use Application Insights. Build-in log streaming is useful only for developers as it may skip or duplicate messages as in this picture. Azure Troubleshooting # For limited testing you can use the stream command: it connects for 30 minutes to the Azure Application and prints the logging messages.\nstream.logs --instance my7 --resourceGroup test-aggregator7 --verbose You can stop the program using Ctrl+C keystroke or closing the command window.\nThe traces generated by the Aggregator Rule Engine are sent to the Application Insight instance that was created aside the Azure Function. More information on Application Insights can be found in Azure documentation.\n"});index.add({'id':51,'href':'/docs/v3/design/caveat/','title':"Caveats",'content':"Azure DevOps behavior # Azure DevOps may do a few revisions when creating a new Work Item, backlog ordering service and few other things may cause the aggregator to receive the first notification as an edit and not a create event (see events). Or you may get the create event and the save may fail, because the backlog ordering (sparsification) has triggered. In which case you\u0026rsquo;ll also get ae edit event right after, so you can ignore the failure.\nSomething you cannot do # You cannot automatically refresh the UI or disable a UI control from an Aggregator Rule.\n"});index.add({'id':52,'href':'/docs/v2/contrib/developer-intro/','title':"Introduction to Contributors",'content':"So, you want to build yourself the binaries or want to fix a bug.\nTo enhance or fix bugs, please read Source code page to introduce yourself to the code. In Local build, we describe the build process: a mandatory read. Useful tips are contained in Debugging and Troubleshooting pages.\nTFS Breaking changes # TFS Server API changed frequently in the past: TFS Aggregator contains specific checks for the TFS version. These check can be:\n source code conditional compile WiX sources MSBuild project files So, caveat emptor: TFS versions are scattered in many places. Compile # Note that to rebuild, edit or debug the code you must use Visual Studio 2017, Community or Professional Edition at a minimum (see Local build for details). The Community edition is free. TFS is not required locally: you can use Remote Debugging.\nThe CI build page explains some important things of our CI build infrastructure in VSTS.\nBranches # Branch Artifacts Purpose master Yes Released code, versions are tagged hotfix/* Yes Fast release cycle for bug fixes, branches from tag, merged to master after Issuer confirms fix is working release/* Yes Release candidates, branch named after soon-to-be-released version, tags mark interim releases develop No Integration branch for developers feature/* No New feature, idea Note: you must take care of using the same branches in all the repositories involved in a change.\nDocumentation # tfsaggregator-docs-hugo master branch content must match the latest release. To prepare documentation for a future release, use a branch as in the code repository.\nRelease process # collect solved issues prepare Markdown release notes update tfsaggregator-docs-hugo release/v# branch tag all three repos merge release/v# branch to master (use PR when possible) create GitHub Release by pasting release notes merge tfsaggregator-docs-hugo release/v# branch to master and delete it download binaries and upload to Release merge master branch to develop increment v# in develop branch and push update Visual Studio Marketplace extensions pasting release notes spread the news: Twitter, Blog wait for bugs to arrive "});index.add({'id':53,'href':'/docs/v2/contrib/source-code/','title':"Source Code",'content':"This page explains the design and internal organization of TFS Aggregator v2\u0026rsquo;s code. If you want to rebuild, customize, submit changes this is the place to start.\nMajor Components # The Aggregator.Core assembly contains the logic to process a Work Item and run the aggregate scripts. It is normally used by Aggregator.ServerPlugin which intercept the TFS server side events and forward them to Aggregator.Core. Aggregator.ConsoleApp is a simple console app that helps users in developing and testing policies without installing the server plugin. Aggregator.WebHooks (new in 2.3) is a WebAPI application that uses Web Hooks to receive notifications from TFS/VSTS.\nSource Code Organization (up to v2.2.1) # The project is available on GitHub. We use a simple master/develop/pull-request branching scheme. All the source is available in the TFS-Aggregator-2.sln Visual Studio 2015 solution. To produce the MSI, see Local build.\nSource Code Organization (up to v2.3 and later) # Code is split in three repositories:\n tfsaggregator-core contains the core code of Aggregator; there are 2 different project files Aggregator.Core.Plugin.csproj and Aggregator.Core.WebHooks.csproj both producing Aggregator.Core.dll, they must be manually synched using a tool like Beyond Compare or WinMerge; in addition the repo holds the Unit tests. tfsaggregator use tfsaggregator-core as submodule; it contains the tfs-aggregator-plugin.sln solution that produce the Server Plugin; see Local build for MSI details. tfsaggregator-webhooks use tfsaggregator-core as submodule; it contains the TFS-Aggregator-WebHook.sln solution that produce the Web Service which is not included in the MSI Git Tip: add remotes to each submodule so they reference each other and you can quickly synchronize the local folders.\n Policy data # Aggregator parses the Policy file at start. The logic is contained in Aggregator.Core/Configuration; whose entry point is the Aggregator.Core.Configuration.TFSAggregatorSettings class. That class is also the root of the configuration data model: Aggregator code gets a reference to a TFSAggregatorSettings instance to configure.\nYou can populate this class from a different source like a database.\nObject Model # Aggregator\u0026rsquo;s Object Model solves some objectives:\n simplifying the scripts decouple from TFS Object Model Ease mocking i.e. testing You find the OM interfaces in Aggregator.Core/Interfaces and the implementation for the TFS OM in Aggregator.Core/Facade.\nSee Scripting for an introduction.\nScripting # Aggregator.Core/Script contains the build and execute logic for all scripting engines. For C# and VB, the script code is compiled once and the produced assembly is reused until the plug-in restarts. The DotNetScriptEngine base class contains all the logic while CSharpScriptEngine and VBNetScriptEngine define how to sew the script\u0026rsquo;s code snippets together. Powershell support is experimental.\nLogging # The Core is decoupled from the logging sub-system: interesting events are pushed via the Aggregator.Core.ILogEvents interface that each client must implement. This way the same event generate a message in the log file or on the console. Important messages create EventLog messages on the server but not on the console application.\nTo add a message you have to:\n add a method to ILogEvents interface implement the method in TextLogComposer class Note that the calling site is invoking a method passing typed parameters. TextLogComposer implementation set the message level and compose the text properly formatting the parameters.\n What\u0026rsquo;s next # Please read Local build, Debugging and Troubleshooting to get a complete picture.\n"});index.add({'id':54,'href':'/docs/v2/contrib/local-build/','title':"Local Build",'content':"Building the Solution # To rebuild, edit or debug the code you must use Visual Studio 2017, Community or Professional Edition at a minimum. In addition you must install the following extensions from Visual Studio Gallery:\n xUnit.net 1.0 WiX 3.10 (optional) TFS is not required to build nor debugging if you copy locally the required DLLs (for this latter you can use Remote Debugging). WiX is not required if you use the build-installer.proj MSBuild script.\nReferences # Building requires a number of TFS assemblies that cannot be redistributed. You can find the complete list in these files\n TFS 2013 update 2 or newer: References/2013/PLACEHOLDER.txt TFS 2015: References/2015/PLACEHOLDER.txt TFS 2015 Update 1: References/2015.1/PLACEHOLDER.txt TFS 2015 Update 2: References/2015.2/PLACEHOLDER.txt TFS 2017: References/2017/PLACEHOLDER.txt TFS 2017 Update 2: References/2017.2/PLACEHOLDER.txt if you have TFS installed on your development machine, the assemblies for that version will be loaded automatically from the installation folder.\nSolution Configurations # The Build Configuration selected, like Debug-2013 or Release-2015, determines the target TFS version, that is the referenced assemblies.\nSadly, similar to the release of TFS 2013 update 2 when there were breaking changes, TFS 2015 Update 1 introduces a breaking change in the API, so you can find some conditionally compiled code based on the symbols TFS2013, TFS2015 or TFS2015u1.\nSee Supported TFS versions for the full list.\nVersion numbers # Assemblies produced locally always have the 2.2.0.0 version, at least until we further enhance the build scripts.\nProduce the MSI Windows Installer # The build-installer.proj MSBuild script takes care of generating the Windows Installer MSI file.\nThe MSI packages all three Aggregator flavors, one for each supported TFS version. The installer detects the TFS version installed and deploy the correct assemblies.\nThe MSBuild script builds multiple times the tfs-aggregator-plugin.sln solution, one for each supported TFS version. Then the files are copied in a simple layout in the _collect folder. Some WiX source is generated and finally the Setup.Aggregator\\Setup.Aggregator.wixproj is launched to produce the MSI package.\n Caveat: TFS versions values are in multiple places, e.g.\n source code conditional compile WiX sources MSBuild project files To generate the MSI, in an MSBuild Command Prompt (VS2015+) or a Developer Command Prompt (VS2013) run\nmsbuild build-installer.proj /p:Configuration=Release The Configuration property is mandatory; allowed values are Debug and Release.\nThree MSBuild properties BuildSolution, CollectFiles, BuildMSI, whose meaning is self-explanatory, can be used to skip some process\u0026rsquo; steps.\nmsbuild build-installer.proj /p:Configuration=Release /p:BuildSolution=False /p:CollectFiles=False /p:BuildMSI=True MSI file is available in Setup.Aggregator\\bin\\$(Configuration) as TFSAggregator-0.2.2-alpha+local-$(Configuration).msi. Notice the zero version number to highlight that is has been produced locally.\n Caveat: avoid modifying anything in this area unless you are fluent in MSBuild, WiX and Windows Installer technologies.\n "});index.add({'id':55,'href':'/docs/v2/contrib/debugging/debugging/','title':"Debugging",'content':"For the best development experience, use a TFS Virtual Machine with Visual Studio 2017 installed and work directly on the machine.\n Do not ever debug on a production server!\n Server Plugin # You can then set the output folder for the project to C:\\Program Files\\Microsoft Team Foundation Server 12.0\\Application Tier\\Web Services\\bin\\Plugins\\ or use the deploy.cmd file in Aggregator.ServerPlugin project to refresh Aggregator\u0026rsquo;s assembly on a target test system. Here is a sample\n@echo off set CONFIGURATION=%1 set TARGETDIR=%2 set PLUGIN_FOLDER=C:\\Program Files\\Microsoft Team Foundation Server 14.0\\Application Tier\\Web Services\\bin\\Plugins echo Deploy '%CONFIGURATION%' from '%TARGETDIR%' to '%PLUGIN_FOLDER%' copy /Y \u0026quot;%TARGETDIR%\\TFSAggregator2.Core.dll\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; copy /Y \u0026quot;%TARGETDIR%\\TFSAggregator2.Core.pdb\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; copy /Y \u0026quot;%TARGETDIR%\\TFSAggregator2.ServerPlugin.dll\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; copy /Y \u0026quot;%TARGETDIR%\\TFSAggregator2.ServerPlugin.pdb\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; copy /Y \u0026quot;%TARGETDIR%\\TFSAggregator2.ServerPlugin.dll.config\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; IF NOT EXIST \u0026quot;%PLUGIN_FOLDER%\\TFSAggregator2.ServerPlugin.policies\u0026quot; ( copy \u0026quot;samples\\TFSAggregator2.ServerPlugin.policies\u0026quot; \u0026quot;%PLUGIN_FOLDER%\u0026quot; ) echo Deploy complete. Do not commit changes to this file!\nTo debug attach to the w3wp.exe on the server and set breakpoints as you would normally.\n Note. Use 12.0 for TFS 2013, 14.0 for TFS 2015 and 15.0 for TFS 2017.\n Remote Debugging # TFS is not required to successfully build and debug Aggregator. In fact we successfully used Remote Debugging.\nSee Local build for additional details.\nWeb Service # If TFS is running on a different machine, e.g. a VM. you have to permit incoming connections to IIS Express.\nSay that the machine running Visual Studio has address 192.168.192.168 and the Aggregator.WebHooks.csproj listen on http://localhost:54145/.\nSteps:\n Add \u0026lt;binding protocol=\u0026quot;http\u0026quot; bindingInformation=\u0026quot;*:54145:192.168.192.168\u0026quot; /\u0026gt; below \u0026lt;binding protocol=\u0026quot;http\u0026quot; bindingInformation=\u0026quot;*:54145:localhost\u0026quot; /\u0026gt; to .vs\\config\\applicationhost.config Run netsh http add urlacl url=http://192.168.192.168:54145/ user=everyone from an elevated prompt netsh advfirewall firewall add rule name=\u0026quot;IISExpressWeb\u0026quot; dir=in protocol=tcp localport=54145 profile=private remoteip=localsubnet action=allow Export the TFS SSL certificate Copy the PFX file on the host machine and import as Trusted root iisexpress.exe Error: 0 : [Critical] 01.410 faf47830-acf6-4fde-b9bf-6c99118f8c1d Exception encountered processing notification: TF400324: Team Foundation services are not available from server https://name-of-tfs-server/DefaultCollection/. Technical information (for administrator): The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. "});index.add({'id':56,'href':'/docs/v2/contrib/continuous-integration/','title':"Continuous Integration",'content':"We moved to VSTS for Continuous Integration.\nCI Builds for:\n develop feature/* do not produce artifacts, i.e. the MSI file. Builds that actually generate artifacts:\n master release/* hotfix/* The Reference folder is filled with files from $/TfsAggregator2/References as the assemblies are not redistributable. As explained in Local build these assemblies are tied to the target TFS version.\nThe script build Debug and Release configuration of TFS-Aggregator-2.sln and run tests.\nIf the tests are green, it produces the per-configuration MSIs launching the build-installer.proj.\nAsking access # VSTS does not currently offers public projects, so if you need access to the CI build, ask the team for an invite.\n"});index.add({'id':57,'href':'/docs/v2/contrib/documentation/','title':"Documentation",'content':"The source for documentation is tfsaggregator-docs-hugo. Note that you need to pull down also the theme submodule.\nThe master branch content must match the latest release. To prepare documentation for a future release, use a branch as in the code repository.\nGet Hugo Hugo v0.18.1.\nRun hugo server to test the changes locally.\nRead the full details in this blog post.\n"});index.add({'id':58,'href':'/docs/v3/api/','title':"REST API",'content':"REST API # The aggregator-host ASP.NET exposes a simple API.\nAuthentication # Authenticated calls require the X-Auth-ApiKey HTTP Header. The value must be listed in the secrets/apikeys.json file.\nConfiguration # These endpoints help manage Aggregator.\nGET /config/status # This endpoint is not authenticated and always returns the string OK. It is useful to check that the installation works correctly.\nGET /config/version # This endpoint is not authenticated and always returns the version of Aggregator, e.g. 1.0.0.\nPOST /config/key # This endpoint is used by CLI to retrieve valid Api Keys.\nWork Item # This endpoint is designed to receive Azure DevOps Web Hook subscription calls.\nPOST /workitem/{ruleName} # This endpoint is authenticated.\n"});index.add({'id':59,'href':'/docs/v3/troubleshoot/','title':"Troubleshoot",'content':"Please make sure you are familiar with Aggregator design as explained in Design.\nTroubleshooting Azure Function # Check things are in the right place with the correct names using the informational commands.\nCheck the WebHooks for errors\nUse the Application Insight instance that was created aside the Azure Function, to search for errors in logs.\nTroubleshooting Docker deployment # Check the WebHooks for errors\nLook at the running container logs for errors.\n"});index.add({'id':60,'href':'/docs/v3/contrib/','title':"Contribute",'content':"Build # Building locally requires\n Visual Studio 2022 with .Net 6.0 SDK Azure Functions and Web Jobs Tools Debug # Custom/development Aggregator runtime # In Visual Studio, src\\aggregator-function\\Directory.Build.targets will automatically package and copy the runtime needed by CLI. You might have to change the version number in src\\aggregator-function\\aggregator-manifest.ini to force your local version.\nYou can also use the Pack right-click command on the aggregator-function project and make sure to copy the created zip into your CLI directory so it uploads the correct one when creating an instance.\nCLI # Set aggregator-cli as Start-up project Use the Visual Studio Project properties to set the Command line arguments.\nDebug Aggregator Runtime locally # Open aggregator-cli.sln in Visual Studio.\nSet aggregator-function as the Start-up project.\nStart the project in Debug mode (hit F5).\nCreate a folder with the same name as the rule, e.g. test1.\nAdd to the folder a file with the same name and suffix .rule, test1.rule in this example, with the code you want to test, e.g.\n$\u0026#34;Hello { self.WorkItemType } #{ self.Id } - { self.Title }!\u0026#34; Add to the folder a file named function.json with this\n{ \u0026#34;bindings\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;httpTrigger\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;in\u0026#34;, \u0026#34;webHookType\u0026#34;: \u0026#34;genericJson\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;req\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;out\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;res\u0026#34; } ], \u0026#34;disabled\u0026#34;: false } Add to the folder a file named run.csx with this content:\n#r \u0026#34;../bin/aggregator-function.dll\u0026#34; #r \u0026#34;../bin/aggregator-shared.dll\u0026#34; using aggregator; public static async Task\u0026lt;object\u0026gt; Run(HttpRequestMessage req, ILogger logger, ExecutionContext context) { var handler = new AzureFunctionHandler(logger, context); return await handler.Run(req); } Set a breakpoint.\nSend the request message at http://localhost:7071/api/test1 using Postman or similar tool.\nThe Function should log a similar sequence of trace messages.\n[2019-08-19 12:07:04] Initial WorkItem 12345 retrieved from https://dev.azure.com/mytestorg/56789abc-def0-1234-5678-9abcdef01234 [2019-08-19 12:07:04] Executing Rule... [2019-08-19 12:07:06] Rule succeeded with Hello Hazard #12345 - My test Work Item! [2019-08-19 12:07:06] No changes saved to Azure DevOps. [2019-08-19 12:07:06] execResult Hello Hazard #12345 - My test Work Item! [2019-08-19 12:07:06] Returning 'Hello Hazard #12345 - My test Work Item!' from 'test1' [2019-08-19 12:07:06] Executed 'Functions.test1' (Succeeded, Id=890abcde-f123-4567-890a-bcdef0123456) [2019-08-19 12:07:06] Executed HTTP request: { [2019-08-19 12:07:06] \u0026quot;requestId\u0026quot;: \u0026quot;f0123456-789a-bcde-f012-3456789abcde\u0026quot;, [2019-08-19 12:07:06] \u0026quot;method\u0026quot;: \u0026quot;POST\u0026quot;, [2019-08-19 12:07:06] \u0026quot;uri\u0026quot;: \u0026quot;/api/test1\u0026quot;, [2019-08-19 12:07:06] \u0026quot;identities\u0026quot;: [ [2019-08-19 12:07:06] { [2019-08-19 12:07:06] \u0026quot;type\u0026quot;: \u0026quot;WebJobsAuthLevel\u0026quot;, [2019-08-19 12:07:06] \u0026quot;level\u0026quot;: \u0026quot;Admin\u0026quot; [2019-08-19 12:07:06] } [2019-08-19 12:07:06] ], [2019-08-19 12:07:06] \u0026quot;status\u0026quot;: 200, [2019-08-19 12:07:06] \u0026quot;duration\u0026quot;: 2531 [2019-08-19 12:07:06] } Debug Aggregator Runtime (live Azure Function) # View live Aggregator log messages # In Azure Portal, open the Resource Group hosting the Instance (e.g. aggregator- followed by name of instance).\nOpen the Function App hosting the Instance (e.g. instance name followed by aggregator).\nScroll down (1) and select Log stream (2) in the Monitoring group.\nThis is useful while developing rules, not in production scenarios.\nIntegration tests # Integration tests emulate a user running Aggregator CLI. They require a few resources in Azure DevOps and in Azure. The best way to configure them is to use the Terraform scripts in src\\integrationtests-setup.\nWindows setup # ### install Azure CLI Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList \u0026#39;/I AzureCLI.msi /quiet\u0026#39;; rm .\\AzureCLI.msi ### get terraform $v = \u0026#39;0.12.28\u0026#39; $p = \u0026#39;windows_amd64\u0026#39; $res = Invoke-WebRequest \u0026#34;https://releases.hashicorp.com/terraform/${v}/terraform_${v}_SHA256SUMS\u0026#34; $shaLine = $res.Content -split \u0026#39;\\n\u0026#39; | where { $_ -like \u0026#34;*${p}*\u0026#34; } $expectedSha = ($shaLine -split \u0026#39; \u0026#39;)[0] Invoke-WebRequest \u0026#34;https://releases.hashicorp.com/terraform/${v}/terraform_${v}_${p}.zip\u0026#34; -o terraform.zip $actualSha = (Get-FileHash terraform.zip -Algorithm SHA256).Hash if ($expectedSha -ne $actualSha) { throw \u0026#34;SHA does not match!\u0026#34; } Expand-Archive terraform.zip -DestinationPath . Remove-Item terraform.zip Set-Alias -Name terraform (Resolve-Path .\\terraform.exe) Linux setup # ### install Azure CLI curl -L https://aka.ms/InstallAzureCli | bash ### get terraform $v = \u0026#39;0.12.28\u0026#39; $p = \u0026#39;linux_amd64\u0026#39; $res = Invoke-WebRequest \u0026#34;https://releases.hashicorp.com/terraform/${v}/terraform_${v}_SHA256SUMS\u0026#34; $shaLine = $res.Content -split \u0026#39;\\n\u0026#39; | where { $_ -like \u0026#34;*${p}*\u0026#34; } $expectedSha = ($shaLine -split \u0026#39; \u0026#39;)[0] Invoke-WebRequest \u0026#34;https://releases.hashicorp.com/terraform/${v}/terraform_${v}_${p}.zip\u0026#34; -o terraform.zip $actualSha = (Get-FileHash terraform.zip -Algorithm SHA256).Hash if ($expectedSha -ne $actualSha) { throw \u0026#34;SHA does not match!\u0026#34; } Expand-Archive terraform.zip -DestinationPath . Remove-Item terraform.zip chmod +x ./terraform Set-Alias -Name terraform (Resolve-Path ./terraform) Setup Azure resources # cd \u0026#39;src\\integrationtests-setup\u0026#39; terraform init az login terraform plan -out _plan -var \u0026#39;azdo_personal_access_token=REPLACE_WITH_PAT_HERE\u0026#39; terraform apply _plan These tests require configuration data in src/integrationtests-cli/logon-data.json to connect to Azure and Azure DevOps and run the tests.\n{ \u0026#34;subscription\u0026#34;: \u0026#34;PUT-AZURE-SUBSCRIPTION-GUID-HERE\u0026#34;, \u0026#34;client\u0026#34;: \u0026#34;PUT-AZURE-SERVICE-PRINCIPAL-GUID-HERE\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;PUT-AZURE-SERVICE-PRINCIPAL-PASSWORD-HERE\u0026#34;, \u0026#34;tenant\u0026#34;: \u0026#34;PUT-AZURE-TENANT-GUID-HERE\u0026#34;, \u0026#34;devopsUrl\u0026#34;: \u0026#34;https://dev.azure.com/PUT-AZUREDEVOPS-ORGANIZATION-NAME-HERE\u0026#34;, \u0026#34;pat\u0026#34;: \u0026#34;PUT-AZUREDEVOPS-PAT-HERE\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;PUT-AZURE-REGION-NAME-HERE\u0026#34;, \u0026#34;resourceGroup\u0026#34;: \u0026#34;PUT-AZURE-RESOURCE-GROUP-NAME-HERE\u0026#34;, \u0026#34;projectName\u0026#34;: \u0026#34;PUT-AZUREDEVOPS-PROJECT-NAME-HERE\u0026#34; } To avoid committing this file in Git, use git update-index --assume-unchanged src/integrationtests-cli/logon-data.json and edit the file content or better,\n"});index.add({'id':61,'href':'/docs/v3/contrib/pipelines/','title':"Pipelines",'content':"Build and deploy # The build-and-deploy.yml pipeline is encompassing all steps required to publish all Aggregator components. This build is triggered by any push of commits or tags as long as they touch the /src/ directory or a workflow.\nSome steps and jobs runs only for a tag starting with v, others when the build is run on the default master branch: you see a label v or m or both aside each conditional phase.\nSteps can leverage dotnet local tools. Currently we use:\n sonarscanner coverlet reportgenerator To ensure cross-platform portability, almost every build step is executed on Linux (ubuntu-latest GitHub environment) to counterbalance Windows development. The only step on Windows builds the Docker Image for Windows.\nBuild # The whole /src/aggregator-cli.sln is built and restored. Build configuration is Release. Assembly version is GitVersion majorMinorPatch and preReleaseTag.\nUnit tests # Unit tests use /src/unittests-* projects and include Code Coverage via coverlet. Test data is included in Sonar analysis.\nIntegration tests [vm] # Integration tests will create resources in our Azure Subscription. They use a logon-data.json file defining the Azure Resource Group, the Azure DevOps Project, and credentials to access them. The file content is pulled from GitHub Secrets and removed when done.\nSonarQube [vm] # The build runs an initial PowerShell script at its start, to assure that all .csproj have a valid ProjectGuid property. This hack is still required by Sonarcube scanner. The scanner collects all test results and code coverage data. For convenience the code coverage information is available as HTML pages in the build artifacts thanks to ReportGenerator.\nPackage [v] # The binaries for the Function Runtime and the CLI (in three OS flavor) are collected, zipped and uploaded as artifacts for later publishing.\nDraft GitHub Release [v] # Packaged Artifacts are downloaded and their hash computed. A new draft Release is created with the artifacts and description from Next-Release-ChangeLog.md. The Release may need additional editing before publishing. When the draft is published, additional workflows will trigger.\nDocker build # Docker builds the two /docker/linux-x64.Dockerfile and /docker/win-x64.Dockerfile using the entire repo as context (there are some odd dependencies). The workflow defines two build arguments MAJOR_MINOR_PATCH and PRERELEASE_TAG. The set of tests in unittests-function.csproj is not run. The tags used for the images are uploaded as build artifacts.\nDocker push # Creates a docker manifest (aka manifest lists) to include both the Windows and Linux image under a single tag and generates the lastest image. It uses the tags generated in the previous step. The images and manifests are published to two Registries:\n Docker Hub GitHub Container Registry Finally, the README for DockerHub is published using a jinja2 transformation. Visual Studio Marketplace # This workflow is triggered by the Publishing of the new GitHub Release.\nUses the tfx (aka TFS Cross Platform Command Line Interface) to refresh the Visual Studio Marketplace page for Aggregator using the content of the /marketplace directory.\nTwitter # This workflow is triggered by the Publishing of the new GitHub Release.\nUse an Action to advertise on Twitter the availability of the new release.\nDocumentation # This workflow (publish-on-master.yml) is used to validate pull requests and publish when merged on the default master branch.\nIt uses Hugo and the hugo-book theme to generate the site, which is then published via GitHub Pages on a different repository.\n"});index.add({'id':62,'href':'/docs/v2/changelog/','title':"Changelog",'content':"History of Changes # See the releases on GitHub.\n"});index.add({'id':63,'href':'/docs/v3/','title':"Aggregator 3 (CLI)",'content':"Aggregator 3 (CLI \u0026amp; Docker) # This is the successor to renowned TFS Aggregator. The current Server Plugin version (2.x) will be maintained to support TFS. The Web Service flavour will be discontinued in favour of this new tool for two reasons:\n deployment and configuration of Web Service was too complex for most users; both the Plugin and the Service rely heavily on TFS Object Model which is deprecated. Aggregator 3.x supports two scenarios:\n Azure DevOps Services with Rules running in Azure Functions. Docker container running in the cloud or on-premise. [v1.0] The latter permits replacing the Server Plugin after migrating the Rule code.\nReleases # Download CLI Docker Image from DockerHub Docker Image from GitHub Container Registry Aggregator is evolving rapidly and some feature are not available on older versions. A release number like [v0.9.11] signals the version when the feature was introduced. A complete list of releases including log of changes is in GitHub.\nMajor features # use of new Azure DevOps REST API simple deployment via CLI tool or Docker container Rule language similar to TFS Aggregator v2 What you can and cannot do # Aggregator is always 1 save away from the Azure DevOps User Interface: it does not intercept anything. Aggregator is notified after Azure DevOps commits user changes to the database. It can do fancy calculations and can work across hierarchies and queries (which the built-in rules won\u0026rsquo;t allow), but needs additional round-trips to Azure DevOps. Thus it is only reactive and may need a UI refresh for calculations to show up. It cannot be used to block updates either. The data has already been saved once the aggregator runs. You could do compensating changes, but can\u0026rsquo;t prevent them. When aggregator changes a value, it will also invoke itself once more (it runs on every change), so Aggregator rules must run idempotent.\nPlease go to the Design section for further details.\nRequirements # To write a Rule is required some basic knowledge of C# language and Azure Boards. In addition you need:\n an Azure DevOps Services Project a Personal Access Token with sufficient permissions on the Project CLI \u0026amp; Azure # The CLI scenario has two additional requirements:\n an Azure Subscription a Service Principal with, at least, Contributor permission on a Resource Group of the Subscription Docker # The Docker scenario requires:\n an SSL Certificate an host for Docker containers (Windows or Linux) How the CLI works with Azure Functions # As the name implies, this is a command line tool: you download the latest aggregator-cli*.zip appropriate for your platform from GitHub releases and unzip it on your client machine. Read more below in the Usage section.\nThrough the CLI you create one or more Azure Functions in your Subscription. The Functions use a library named Aggregator Runtime to run your Rules. A Rule is code that reacts to one or more Azure DevOps event; currently, the only language for writing rules is C#.\nThe CLI automatically checks GitHub Releases to ensure that you use the more recent version of Aggregator Runtime available. To avoid automatic upgrades, specify the Runtime version or point to a specific Runtime package file, using an http: or file: URL.\nAfter you setup the Rules in Azure, you must add at least one Mapping. A mapping is an Azure DevOps Service Hook that send a message to the Azure Function when a specific event occurs. Currently we support only Work Item events. When triggered, the Azure DevOps Service Hook invokes a single Aggregator Rule i.e. the Azure Function hosting the Rule code. Azure DevOps saves the Azure Function Key in the Service Hook configuration.\nYou can deploy the same Rule in different Instances, map the same Azure DevOps event to many Rules or map multiple events to the same Rule: you choose the best way to organize your code.\nCLI Authentication # You must instruct Aggregator which credential to use. To do this, run the login.azure and login.ado commands.\nTo create the credentials, you need an Azure Service Principal and a Azure DevOps Personal Access Token. Full details in the Setup section.\nAggregator stores the logon credentials locally and expires them after 2 hours.\nThe PAT is also stored in the Azure Function settings: whoever has access to the Resource Group can read it!\nThe Service Principal must have Contributor permission to the Azure Subscription or, in alternative, pre-create the Resource Group in Azure and give the service account Contributor permission to the Resource Group. If you go this route, remember add the --resourceGroup to all commands requiring an instance.\nFor Docker only Azure DevOps logon is required.\nCLI Usage # Download and unzip the latest CLI.zip file from Releases. It requires .Net Core 3.1 installed on the machine. To run Aggregator run aggregator-cli.exe (Windows), aggregator-cli (Linux) or dotnet aggregator-cli.dll followed by a verb and its options.\nCLI Verbs # There are about 20 commands described in detail at Commands.\nThey can be grouped in a few categories:\n Authentication to logon into Azure and Azure DevOps. Instance creation, configuration and update. Rule deployment, configuration and update. Mapping from Azure DevOps to Rules. Informational commands, to read configuration. Testing commands to validate configuration. Most commands manage Azure Function, but a few can be used in the Docker scenario.\nWe collected some usage scenarios at Command Examples.\nHow the Docker image works # Pull the latest image from Docker Hub. It works on Linux and Windows. Start a container with the image, setting configuration through environment variables. The Rules are simply files on a Docker volume that the container uses. The container must expose a port reachable from your Azure DevOps instance, either Server or Service. Add one or web hook to Azure DevOps using the container URL. Use one Aggregator API Key to authenticate the call. You may use the CLI to add these mappings.\nMore details at Docker configuration\nRule language # Currently we offer only C# as the language to write Rules. The Rules can access a few objects:\n The Current Work Item. Work Item Store to retrieve additional Work Items. The Event which triggered the Rule. Project information. A Logger object to track Rule steps. See Rule Language for a list of objects and properties to use. For examples see Rule Examples.\nMaintenance # Aggregator stores the PAT in the Azure Function configuration. Before the PAT expire you should refresh it from Azure DevOps or save a new PAT using the configure.instance command.\nRead Production Configuration and Administration for recommendations on running Aggregator in production.\nTroubleshooting # Tips and suggestion when things do not work are in the Troubleshoot section.\nContributing # Details on building your own version and testing are in the Contribute section.\n"});index.add({'id':64,'href':'/docs/','title':"Docs",'content':""});index.add({'id':65,'href':'/docs/v3/design/parallelism/','title':"Parallelism",'content':"Normal flow # The diagram shows the normal Azure DevOps and Aggregator interaction.\n mermaid.initialize({ flowchart: { useMaxWidth:true } }); sequenceDiagram User -+ AzDO : New Work Item AzDO --- User : Work Item(id=42, ver=1) activate Aggregator AzDO -+ Aggregator : event(workitem.created, id=42) Aggregator -+ AzDO : readWorkItem(id=42) AzDO --- Aggregator : ver=1 Aggregator - DataModel : new(WorkItem, id=42, ver=1) Aggregator -+ ruleA : triggers ruleA - DataModel : get(WorkItem, id=42) ruleA - DataModel : update(WorkItem, id=42) ruleA --- Aggregator : returns Aggregator - AzDO : updateWorkItem(id=42, ver=1) AzDO -- Aggregator : ver=2 deactivate Aggregator opt Cycle until exahustion activate Aggregator AzDO -+ Aggregator : event(workitem.updated, id=42, ver=2) Aggregator - DataModel : new(WorkItem, id=42, ver=2) Aggregator -+ ruleA : triggers ruleA - DataModel : get(WorkItem, id=42) ruleA - DataModel : update(WorkItem, id=42) ruleA --- Aggregator : returns Aggregator - AzDO : updateWorkItem(id=42, ver=2) AzDO -- Aggregator : ver=3 deactivate Aggregator end Parallel changes # Sequence diagram showing a failing Azure DevOps and Aggregator interaction.\nsequenceDiagram User -+ AzDO : New Work Item AzDO --- User : Work Item(id=42, ver=1) activate Aggregator AzDO -+ Aggregator : event(workitem.created, id=42) Aggregator -+ AzDO : readWorkItem(id=42) AzDO --- Aggregator : ver=1 Aggregator - DataModel : new(WorkItem, id=42, ver=1) Aggregator -+ rule : triggers rule - DataModel : get(WorkItem, id=42) Note right of User: Same or another user User -+ AzDO : Update Work Item AzDO --- User : Work Item(id=42, ver=2) rule - DataModel : update(WorkItem, id=42) rule --- Aggregator : returns rect rgb(240, 120, 120) Aggregator -X AzDO : updateWorkItem(id=42, ver=1) end deactivate Aggregator activate Aggregator AzDO -+ Aggregator : event(workitem.updated, id=42, ver=2) deactivate Aggregator VS403351: Test Operation for path /rev failed\n The VS403351 error happens because Aggregator adds a specific check to the JSON Patch document for the Work Item revision.\nOperation = Operation.Test, Path = \u0026#34;/rev\u0026#34;, Value = item.Rev It can be disabled on a per rule base using the check revision directive.\n"});index.add({'id':66,'href':'/docs/v2/','title':"TFS Aggregator v2",'content':"TFS Aggregator v2 # TFS Aggregator is an extension for Team Foundation Server (TFS) and Azure DevOps Server that enables running custom script when Work Items change, allowing dynamic calculation of field values in TFS and more. (For example: Dev work + Test Work = Total Work).\n"});})();