Friday, February 23, 2018

Uploading extensions to TFS

When creating a TFS extension, one ends up uploading it to the server all the time. Doing so through the Web UI is tedious, so here's a script for that.

It takes two parameters:

  • Server - the URL of the TFS instance, e. g. http://tfs.acme.com:8080/tfs/
  • File - the filename of the compiled .VSIX file
The script looks inside the VSIX to determine the publisher, the extension ID, and the version.

The script was meant for on-prem TFS. Microsoft's TFX command line tool can do the same for VSTS, but it doesn't support NTLM auth (because Node.js' HTTP client doesn't). Mine is in Powershell, where NTLM support comes out of the box.

Friday, February 16, 2018

All OAuth scopes in TFS

Did I already complain that the API surface of Team Foundation Server is sorely underdocumented? Well, it is. In today's episode, we're going to explore the gamut of things an OAuth Web client can and cannot do.

In my work, the OAuth clients are TFS client-side extensions. In order to access the TFS API, they need to declare the set of scopes they're interested in in the extension manifest. The scopes are supposed to be documented here, but the list over there is incomplete.

Fortunately, one can dump a complete list from a live instance of on-prem TFS, together with API endpoints that they cover. Copy the linked file to C:\Program Files\Microsoft Team Foundation Server NN\Application Tier\Web Services on the TFS Web server, then navigate to http://mytfs:8080/tfs/tfsscopes.aspx. I've done exactly that, and here's the list for TFS 2017 update 2. I don't have an instance of TFS2018 handy yet.

Not all endpoints that are listed here are live in the on-prem TFS. Some return error 404; I presume they're only active in VSTS. For example, most of the endpoints under vso.profile are unusable.

preview_api_all
  • /_apis#OPTIONS
  • /DefaultCollection/_apis#OPTIONS
  • /_apis/connectiondata#GET
  • /DefaultCollection/_apis/connectiondata#GET
  • /_apis/ServiceDefinitions#GET
  • /_apis/build/builds#GET+PATCH+DELETE
  • /_apis/build/qualities#GET+PUT+DELETE
  • /_apis/build/requests#GET+POST+PATCH+DELETE
  • /_apis/build/definitions
  • /_apis/build/queues
  • /DefaultCollection/_apis/build/builds#GET+PATCH+DELETE
  • /DefaultCollection/_apis/build/qualities#GET+PUT+DELETE
  • /DefaultCollection/_apis/build/requests#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/build/definitions
  • /DefaultCollection/_apis/build/queues
  • /DefaultCollection/_apis/resources/Containers#GET
  • /DefaultCollection/*/_apis/build/builds#GET+PATCH+DELETE+PUT+POST
  • /DefaultCollection/*/_apis/build/definitions#GET+POST+PUT+DELETE
  • /DefaultCollection/*/_apis/build/requests#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/build/qualities#GET+PUT+DELETE
  • /DefaultCollection/*/_apis/build/tags#GET
  • /DefaultCollection/*/_apis/build/options#GET
  • /DefaultCollection/_apis/build/queues#GET
  • /DefaultCollection/_apis/build/options#GET
  • /DefaultCollection/_apis/build/controllers#GET
  • /_apis/accounts
  • /_apis/profile/profiles
  • /_apis/projectCollections
  • /_apis/tagging#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/projects#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/tagging#GET+POST+PATCH+DELETE
  • /_apis/notifications/*/eventdefinitions
  • /_apis/hooks/consumers
  • /_apis/hooks/publishers
  • /_apis/hooks/subscriptions#GET+POST+PUT+DELETE
  • /_apis/hooks/inputValuesQuery#POST
  • /_apis/hooks/notificationsQuery#POST
  • /_apis/hooks/subscriptionsQuery#POST
  • /_apis/hooks/publishersQuery#POST
  • /DefaultCollection/_apis/hooks/consumers
  • /DefaultCollection/_apis/hooks/publishers
  • /DefaultCollection/_apis/hooks/subscriptions#GET+POST+PUT+DELETE
  • /DefaultCollection/_apis/hooks/inputValuesQuery#POST
  • /DefaultCollection/_apis/hooks/notificationsQuery#POST
  • /DefaultCollection/_apis/hooks/subscriptionsQuery#POST
  • /DefaultCollection/_apis/hooks/publishersQuery#POST
  • /_apis/chat/rooms/*/messages#GET+POST+PUT+PATCH+DELETE
  • /_apis/chat/rooms#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/chat/rooms/*/messages#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/chat/rooms#GET+POST+PUT+PATCH+DELETE
  • /_apis/tfvc/branches
  • /_apis/tfvc/changesets
  • /_apis/tfvc/labels
  • /_apis/tfvc/shelvesets
  • /_apis/tfvc/workspaces#GET+POST
  • /_apis/tfvc/changesetsBatch#POST
  • /_apis/tfvc/itemBatch#POST
  • /DefaultCollection/_apis/tfvc/branches
  • /DefaultCollection/_apis/tfvc/changesets
  • /DefaultCollection/_apis/tfvc/items
  • /DefaultCollection/_apis/tfvc/labels
  • /DefaultCollection/_apis/tfvc/shelvesets
  • /DefaultCollection/_apis/tfvc/workspaces#GET+POST
  • /DefaultCollection/_apis/tfvc/changesetsBatch#POST
  • /DefaultCollection/_apis/tfvc/itemBatch#POST
  • /DefaultCollection/*/_apis/tfvc#GET+POST
  • /_apis/git/repositories#GET+POST
  • /_apis/git/repositories/*/commits
  • /_apis/git/*/repositories/*/commits
  • /_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /_apis/git/*/repositories/*/commits/*/statuses#GET+POST
  • /_apis/git/repositories/*/pushes#GET+POST
  • /_apis/git/*/repositories/*/pushes#GET+POST
  • /_apis/git/repositories/*/pushes/*
  • /_apis/git/*/repositories/*/pushes/*
  • /_apis/git/repositories/*#GET+PATCH+DELETE
  • /_apis/git/*/repositories/*#GET+PATCH+DELETE
  • /_apis/git/*/repositories#GET+POST
  • /DefaultCollection/_apis/git/repositories#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/commits
  • /DefaultCollection/_apis/git/*/repositories/*/commits
  • /DefaultCollection/_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/*/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pushes#GET+POST
  • /DefaultCollection/_apis/git/*/repositories/*/pushes#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pushes/*
  • /DefaultCollection/_apis/git/*/repositories/*/pushes/*
  • /DefaultCollection/_apis/git/repositories/*#GET+PATCH+DELETE+POST
  • /DefaultCollection/_apis/git/*/repositories/*#GET+PATCH+DELETE+POST
  • /DefaultCollection/_apis/git/*/repositories#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pullrequests#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/git/*/repositories/*/pullrequests#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/repositories#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/codereview/reviews#GET
  • /DefaultCollection/*/_apis/codereview/reviews#GET+POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/codereview/reviewsbatch#POST
  • /DefaultCollection/*/_apis/codereview/settings#GET+POST+PUT
  • /DefaultCollection/_apis/visits/artifactVisits#PUT
  • /DefaultCollection/_apis/visits/artifactVisitsBatch#POST
  • /DefaultCollection/_apis/visits/artifactStatsBatch#POST
  • /DefaultCollection/*/_apis/policy/Evaluations#GET+PATCH
  • /_apis/wit/attachments#GET+POST
  • /_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/wit/attachments#GET+POST
  • /DefaultCollection/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/*/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /_apis/wit/fields#GET
  • /_apis/wit/wiql#GET+POST
  • /_apis/wit/workitemrelationtypes#GET
  • /_apis/wit/workitems#GET+POST+PATCH
  • /_apis/wit/workitemtypecategories#GET
  • /_apis/wit/workitemtypes#GET
  • /_apis/wit/$ruleEngine#POST
  • /_apis/wit/$batch#POST
  • /_apis/wit/artifactlinktypes#GET
  • /_apis/wit/artifacturiquery#POST
  • /DefaultCollection/_apis/wit/artifactlinktypes#GET
  • /DefaultCollection/_apis/wit/artifacturiquery#POST
  • /DefaultCollection/_apis/wit/fields#GET
  • /DefaultCollection/_apis/wit/wiql#GET+POST
  • /DefaultCollection/_apis/wit/workitemrelationtypes#GET
  • /DefaultCollection/_apis/wit/workitems#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/wit/$ruleEngine#POST
  • /DefaultCollection/_apis/wit/$batch#POST
  • /DefaultCollection/_apis/wit/workitemtypetemplate#GET+POST
  • /DefaultCollection/*/_apis/wit/fields#GET
  • /DefaultCollection/*/_apis/wit/classificationnodes#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/wiql#GET+POST
  • /DefaultCollection/*/_apis/wit/workitems#GET+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/workitems#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/workitemtypecategories#GET
  • /DefaultCollection/*/_apis/wit/workitemtypes#GET
  • /DefaultCollection/*/_apis/wit/workitemtypetemplate#GET+POST
  • /DefaultCollection/*/*/_apis/wit/templates#GET+PUT+POST+DELETE
  • /DefaultCollection/*/*/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/*/_apis/wit/wiql#GET+POST
  • /DefaultCollection/_apis/resources/Containers/*
  • /_apis/resources/Containers/*
  • /_api/_wit/teamProjects
  • /DefaultCollection/_api/_wit/teamProjects
  • /DefaultCollection/*/_apis/work/boards#GET+PUT+PATCH
  • /DefaultCollection/*/*/_apis/work/boards#GET+PUT+PATCH
  • /DefaultCollection/*/_apis/work/teamsettings#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/*/_apis/work/teamsettings#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/*/_apis/work/processconfiguration#GET
  • /DefaultCollection/*/*/_apis/work/backlogconfiguration#GET
  • /_apis/clt/testdrops#GET+POST
  • /_apis/clt/testruns#GET+POST+PATCH
  • /_apis/clt/testruns/*/errors
  • /_apis/clt/testruns/*/messages
  • /_apis/clt/testruns/*/results
  • /_apis/clt/testruns/*/counterinstances
  • /_apis/clt/testruns/*/countersamples
  • /_apis/clt/apm
  • /_apis/clt/configuration
  • /_apis/clt/agentgroups#GET+POST+PATCH+DELETE
  • /_apis/clt/agentgroups/*/agents#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test#GET+POST+PATCH+DELETE
  • /_apis/test/suites
  • /_apis/Identities#GET+PUT+DELETE
  • /_apis/IdentityBatch#POST
  • /_apis/Groups#GET+DELETE+POST
  • /_apis/Scopes#GET+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/discussion/threads#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/discussion/threadsBatch#POST
  • /DefaultCollection/_apis/discussion/comments#PATCH
  • /_apis/Commerce/*#GET
  • /_apis/Commerce/Subscription/*/*#GET+PUT
  • /_apis/gallery/*#GET
  • /_apis/gallery/acquisitionrequests/*#POST
vso.base
  • /_apis#OPTIONS
  • /DefaultCollection/_apis#OPTIONS
  • /_apis/connectiondata#GET
  • /DefaultCollection/_apis/connectiondata#GET
  • /_apis/ServiceDefinitions#GET
  • /_apis/resourceareas#GET
  • /DefaultCollection/_apis/resourceareas#GET
  • /_apis/operations#GET
  • /DefaultCollection/_apis/operations#GET
  • /_apis/permissions#GET
  • /_apis/SecurityNamespaces#GET
  • /_apis/AccessControlLists#GET
  • /_apis/security/permissionEvaluationBatch#POST
  • /DefaultCollection/_apis/permissions#GET
  • /DefaultCollection/_apis/SecurityNamespaces#GET
  • /DefaultCollection/_apis/AccessControlLists#GET
  • /DefaultCollection/_apis/security/permissionEvaluationBatch#POST
vso.profile
  • /_apis/account#GET
  • /_apis/account/regions#GET
  • /_apis/accounts#GET
  • /_apis/projectCollections#GET
  • /_apis/profile/profiles#GET
  • /_apis/profile/UserDefaults#GET
  • /_apis/profile/regions#GET
  • /_apis/profile/georegion#GET
  • /_apis/profile/Locations#GET
  • /_apis/profile/Settings#GET
  • /_apis/profile/Attributes#GET+PATCH
  • /_apis/ClientNotification/Subscriptions#GET
  • /_apis/process/processes#GET
  • /DefaultCollection/_apis/projects#GET
  • /DefaultCollection/_apis#OPTIONS
  • /_apis/Organization/Regions#GET
vso.profile_write
  • /_apis/profile/profiles#PATCH+PUT
vso.acquisition_write
  • /*/_apis/ServiceDefinitions#GET
  • /*/_apis/projectCollections#GET
  • /*/_apis/projects#GET+POST
  • /*/_apis/operations#GET
  • /_apis/ServiceDefinitions#GET
  • /_apis/projects#GET+POST
  • /_apis/operations#GET
  • /_apis/profile/avatar#GET+POST+DELETE+PUT
  • /_apis/profile/profiles#GET+POST+PATCH
  • /_apis/profile/UserDefaults#GET+PUT
  • /_apis/accounts#GET+POST
  • /api/account#GET+POST
  • /_apis/delegatedauth/registration#GET
  • /_apis/delegatedauth/registrationsecret#GET
  • /_apis/delegatedauth/authorizations#GET
  • /_apis/UserMapping/UserAccountMappings#GET
vso.identity
  • /*/_apis/identities#GET
  • /*/_apis/groups#GET
  • /*/_apis/scopes#GET
  • /*/_apis/identitybatch#POST
  • /_apis/identities#GET
  • /_apis/groups#GET
  • /_apis/scopes#GET
  • /_apis/identitybatch#POST
  • /DefaultCollection/_apis/identities#GET
  • /DefaultCollection/_apis/groups#GET
  • /DefaultCollection/_apis/scopes#GET
  • /DefaultCollection/_apis/identitybatch#POST
vso.identity_manage
  • /_apis/identities#GET+POST+PUT+DELETE
  • /_apis/groups#GET+POST+PUT+DELETE
  • /_apis/scopes#GET+POST+PUT+DELETE+PATCH
  • /DefaultCollection/_apis/identities#GET+POST+PUT+DELETE
  • /DefaultCollection/_apis/groups#GET+POST+PUT+DELETE
  • /DefaultCollection/_apis/scopes#GET+POST+PUT+DELETE+PATCH
vso.hooks
  • /_apis/hooks/consumers#GET
  • /_apis/hooks/inputValuesQuery#POST
  • /_apis/hooks/notificationsQuery#POST
  • /_apis/hooks/publishers#GET
  • /_apis/hooks/publishersQuery#POST
  • /_apis/hooks/subscriptions#GET
  • /_apis/hooks/subscriptionsQuery#POST
  • /_apis/hooks/testNotifications#POST
  • /DefaultCollection/_apis/hooks/consumers#GET
  • /DefaultCollection/_apis/hooks/inputValuesQuery#POST
  • /DefaultCollection/_apis/hooks/notificationsQuery#POST
  • /DefaultCollection/_apis/hooks/publishers#GET
  • /DefaultCollection/_apis/hooks/publishersQuery#POST
  • /DefaultCollection/_apis/hooks/subscriptions#GET
  • /DefaultCollection/_apis/hooks/subscriptionsQuery#POST
  • /DefaultCollection/_apis/hooks/testNotifications#POST
vso.hooks_write
  • /_apis/hooks/subscriptions#GET+POST+PUT+DELETE
  • /DefaultCollection/_apis/hooks/subscriptions#GET+POST+PUT+DELETE
vso.hooks_interact
  • /DefaultCollection/_apis/wit/workitems#PATCH
  • /DefaultCollection/*/_apis/wit/workitemtypes/*/states#GET
vso.work
  • /_apis/tagging#GET
  • /DefaultCollection/_apis/tagging#GET
  • /DefaultCollection/_apis/wit/attachments#GET
  • /DefaultCollection/_apis/wit/fields#GET
  • /DefaultCollection/_apis/wit/workitemrelationtypes#GET
  • /DefaultCollection/_apis/wit/queries#GET
  • /DefaultCollection/_apis/wit/wiql#GET+POST
  • /DefaultCollection/_apis/wit/workitems#GET
  • /DefaultCollection/*/_apis/wit/fields#GET
  • /DefaultCollection/*/_apis/wit/classificationnodes#GET
  • /DefaultCollection/*/_apis/wit/queries#GET
  • /DefaultCollection/*/_apis/wit/wiql#GET+POST
  • /DefaultCollection/*/_apis/wit/workitemtypecategories#GET
  • /DefaultCollection/*/_apis/wit/workitemtypes#GET
  • /DefaultCollection/*/_apis/wit/workitemtypetemplate#GET
  • /DefaultCollection/*/_apis/wit/workitems#GET
  • /DefaultCollection/*/*/_apis/wit/queries#GET
  • /DefaultCollection/*/*/_apis/wit/wiql#GET+POST
  • /DefaultCollection/*/*/_apis/wit/templates#GET
  • /DefaultCollection/_apis/wit/workitemtypetemplate#GET
  • /DefaultCollection/_apis/wit/$ruleEngine#POST
  • /DefaultCollection/*/_apis/work/boards#GET
  • /DefaultCollection/*/*/_apis/work/boards#GET
  • /DefaultCollection/*/_apis/work/teamsettings#GET
  • /DefaultCollection/*/*/_apis/work/teamsettings#GET
  • /DefaultCollection/*/_apis/work/processconfiguration#GET
  • /DefaultCollection/*/*/_apis/work/backlogconfiguration#GET
  • /DefaultCollection/_apis/work/boardcolumns#GET
  • /DefaultCollection/*/_apis/work/boardcolumns#GET
  • /DefaultCollection/_apis/work/boardrows#GET
  • /DefaultCollection/*/_apis/work/boardrows#GET
  • /DefaultCollection/_apis/wit/reporting/workItemRevisions#GET+POST
  • /DefaultCollection/*/_apis/wit/reporting/workItemRevisions#GET+POST
  • /DefaultCollection/_apis/wit/reporting/workItemLinks#GET
  • /DefaultCollection/*/_apis/wit/reporting/workItemLinks#GET
  • /DefaultCollection/_apis/process/processes#GET
  • /DefaultCollection/_apis/wit/artifactlinktypes#GET
  • /DefaultCollection/_apis/wit/artifacturiquery#POST
vso.work_write
  • /_apis/tagging#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/wit/attachments#GET+POST+PUT
  • /DefaultCollection/*/_apis/wit/classificationnodes#GET+POST+DELETE+PATCH
  • /DefaultCollection/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/*/_apis/wit/queries#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/wit/workitems#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/wit/recyclebin#GET+PATCH+DELETE
  • /DefaultCollection/_apis/tagging#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/workitems#PATCH
  • /DefaultCollection/_apis/tagging#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/work/boards#GET+PUT+PATCH
  • /DefaultCollection/*/*/_apis/work/boards#GET+PUT+PATCH
  • /DefaultCollection/*/_apis/wit/workitems#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/wit/recyclebin#GET+PATCH+DELETE
  • /DefaultCollection/_apis/wit/$batch#POST
  • /DefaultCollection/*/_apis/work/teamsettings#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/*/*/_apis/work/teamsettings#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/*/_apis/wit/workitemtypetemplate#GET+POST
  • /DefaultCollection/_apis/wit/workitemtypetemplate#GET+POST
  • /DefaultCollection/*/*/_apis/wit/templates#GET+PUT+POST+DELETE
vso.build
  • /DefaultCollection/_apis/resources/Containers#GET
  • /DefaultCollection/*/_apis/build/builds#GET
  • /DefaultCollection/*/_apis/build/definitions#GET
  • /DefaultCollection/*/_apis/build/requests#GET
  • /DefaultCollection/*/_apis/build/qualities#GET
  • /DefaultCollection/*/_apis/build/tags#GET
  • /DefaultCollection/*/_apis/build/options#GET
  • /DefaultCollection/*/_apis/build/repos#GET
  • /DefaultCollection/_apis/build/builds#GET
  • /DefaultCollection/_apis/build/queues#GET
  • /DefaultCollection/_apis/build/options#GET
  • /DefaultCollection/_apis/build/controllers#GET
  • /DefaultCollection/*/_apis/distributedtask/hubs/build/plans#GET
vso.build_execute
  • /DefaultCollection/*/_apis/build/builds#GET+PATCH+DELETE+PUT+POST
  • /DefaultCollection/*/_apis/build/requests#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/build/qualities#GET+PUT+DELETE
  • /DefaultCollection/*/_apis/build/definitions#GET+POST+PUT+DELETE
  • /DefaultCollection/_apis/build/builds#GET+PATCH+DELETE+PUT+POST
  • /DefaultCollection/_apis/build/queues#GET+POST+DELETE
  • /DefaultCollection/*/_apis/distributedtask/hubs/build/plans#GET+PUT
vso.code
  • /DefaultCollection/_apis/tfvc/branches#GET
  • /DefaultCollection/*/_apis/tfvc/branches#GET
  • /DefaultCollection/_apis/tfvc/changesets#GET
  • /DefaultCollection/*/_apis/tfvc/changesets#GET
  • /DefaultCollection/_apis/tfvc/changesetsBatch#POST
  • /DefaultCollection/_apis/tfvc/labels#GET
  • /DefaultCollection/*/_apis/tfvc/labels#GET
  • /DefaultCollection/_apis/tfvc/labelItems#GET
  • /DefaultCollection/_apis/tfvc/shelvesets#GET
  • /DefaultCollection/_apis/tfvc/items#GET
  • /DefaultCollection/*/_apis/tfvc/items#GET
  • /DefaultCollection/_apis/tfvc/itemBatch#POST
  • /DefaultCollection/*/_apis/tfvc/itemBatch#POST
  • /DefaultCollection/*/*/_git/*#GET
  • /DefaultCollection/*/_git/*#GET
  • /DefaultCollection/_git/*#GET
  • /DefaultCollection/*/*/_git/_full/*#GET
  • /DefaultCollection/*/_git/_full/*#GET
  • /DefaultCollection/_git/_full/*#GET
  • /DefaultCollection/*/*/_git/_optimized/*#GET
  • /DefaultCollection/*/_git/_optimized/*#GET
  • /DefaultCollection/_git/_optimized/*#GET
  • /DefaultCollection/*/*/_git/*/git-upload-pack#POST
  • /DefaultCollection/*/_git/*/git-upload-pack#POST
  • /DefaultCollection/_git/*/git-upload-pack#POST
  • /DefaultCollection/*/*/_git/_full/*/git-upload-pack#POST
  • /DefaultCollection/*/_git/_full/*/git-upload-pack#POST
  • /DefaultCollection/_git/_full/*/git-upload-pack#POST
  • /DefaultCollection/*/*/_git/_optimized/*/git-upload-pack#POST
  • /DefaultCollection/*/_git/_optimized/*/git-upload-pack#POST
  • /DefaultCollection/_git/_optimized/*/git-upload-pack#POST
  • /DefaultCollection/*/*/_git/*/info/lfs/objects#POST
  • /DefaultCollection/*/_git/*/info/lfs/objects#POST
  • /DefaultCollection/_git/*/info/lfs/objects#POST
  • /DefaultCollection/*/*/_git/_full/*/info/lfs/objects#POST
  • /DefaultCollection/*/_git/_full/*/info/lfs/objects#POST
  • /DefaultCollection/_git/_full/*/info/lfs/objects#POST
  • /DefaultCollection/*/*/_git/_optimized/*/info/lfs/objects#POST
  • /DefaultCollection/*/_git/_optimized/*/info/lfs/objects#POST
  • /DefaultCollection/_git/_optimized/*/info/lfs/objects#POST
  • /DefaultCollection/*/*/_git/*/gvfs/*#POST
  • /DefaultCollection/*/_git/*/gvfs/*#POST
  • /DefaultCollection/_git/*/gvfs/*#POST
  • /DefaultCollection/*/*/_git/_full/*/gvfs/*#POST
  • /DefaultCollection/*/_git/_full/*/gvfs/*#POST
  • /DefaultCollection/_git/_full/*/gvfs/*#POST
  • /DefaultCollection/*/*/_git/_optimized/*/gvfs/*#POST
  • /DefaultCollection/*/_git/_optimized/*/gvfs/*#POST
  • /DefaultCollection/_git/_optimized/*/gvfs/*#POST
  • /DefaultCollection/*/_apis/git/*/repositories#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/blobs#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/commits
  • /DefaultCollection/*/_apis/git/*/repositories/*/commits/*/statuses#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/commitsBatch#POST
  • /DefaultCollection/*/_apis/git/*/repositories/*/diffs/commits#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/items#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/itemsBatch#POST
  • /DefaultCollection/*/_apis/git/*/repositories/*/filepaths#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/pullrequests#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/pullrequestquery#POST
  • /DefaultCollection/*/_apis/git/*/repositories/*/pushes#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/refs#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/stats/branches#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/trees#GET
  • /DefaultCollection/*/_apis/git/favorites/refs#GET
  • /DefaultCollection/*/_apis/git/favorites/refs#POST
  • /DefaultCollection/*/_apis/git/favorites/refs#PUT
  • /DefaultCollection/*/_apis/git/repositories#GET
  • /DefaultCollection/*/_apis/git/repositories/*/blobs#GET
  • /DefaultCollection/*/_apis/git/repositories/*/commits
  • /DefaultCollection/*/_apis/git/repositories/*/commits/*/statuses#GET
  • /DefaultCollection/*/_apis/git/repositories/*/commitsBatch#POST
  • /DefaultCollection/*/_apis/git/repositories/*/diffs/commits#GET
  • /DefaultCollection/*/_apis/git/repositories/*/items#GET
  • /DefaultCollection/*/_apis/git/repositories/*/itemsBatch#POST
  • /DefaultCollection/*/_apis/git/repositories/*/filepaths#GET
  • /DefaultCollection/*/_apis/git/repositories/*/pullrequests#GET
  • /DefaultCollection/*/_apis/git/repositories/*/pullrequestquery#POST
  • /DefaultCollection/*/_apis/git/repositories/*/pushes#GET
  • /DefaultCollection/*/_apis/git/repositories/*/refs#GET
  • /DefaultCollection/*/_apis/git/repositories/*/stats/branches#GET
  • /DefaultCollection/*/_apis/git/repositories/*/trees#GET
  • /DefaultCollection/*/_apis/git/repositories/*/limitedRefCriteria#GET
  • /DefaultCollection/*/_apis/git/*/repositories/*/objects#GET
  • /DefaultCollection/*/_apis/git/repositories/*/objects#GET
  • /DefaultCollection/_apis/git/*/repositories#GET
  • /DefaultCollection/_apis/git/*/repositories/*/blobs#GET
  • /DefaultCollection/_apis/git/*/repositories/*/commits
  • /DefaultCollection/_apis/git/*/repositories/*/commits/*/statuses#GET
  • /DefaultCollection/_apis/git/*/repositories/*/commitsBatch#POST
  • /DefaultCollection/_apis/git/*/repositories/*/diffs/commits#GET
  • /DefaultCollection/_apis/git/*/repositories/*/items#GET
  • /DefaultCollection/_apis/git/*/repositories/*/itemsBatch#POST
  • /DefaultCollection/_apis/git/*/repositories/*/filepaths#GET
  • /DefaultCollection/_apis/git/*/repositories/*/pullrequests#GET
  • /DefaultCollection/_apis/git/*/repositories/*/pullrequestquery#POST
  • /DefaultCollection/_apis/git/*/repositories/*/pushes#GET
  • /DefaultCollection/_apis/git/*/repositories/*/refs#GET
  • /DefaultCollection/_apis/git/*/repositories/*/stats/branches#GET
  • /DefaultCollection/_apis/git/*/repositories/*/trees#GET
  • /DefaultCollection/_apis/git/repositories#GET
  • /DefaultCollection/_apis/git/repositories/*/blobs#GET
  • /DefaultCollection/_apis/git/repositories/*/commits
  • /DefaultCollection/_apis/git/repositories/*/commits/*/statuses#GET
  • /DefaultCollection/_apis/git/repositories/*/commitsBatch#POST
  • /DefaultCollection/_apis/git/repositories/*/diffs/commits#GET
  • /DefaultCollection/_apis/git/repositories/*/items#GET
  • /DefaultCollection/_apis/git/repositories/*/itemsBatch#POST
  • /DefaultCollection/_apis/git/repositories/*/filepaths#GET
  • /DefaultCollection/_apis/git/repositories/*/pullrequests#GET
  • /DefaultCollection/_apis/git/repositories/*/pullrequestquery#POST
  • /DefaultCollection/_apis/git/repositories/*/pushes#GET
  • /DefaultCollection/_apis/git/repositories/*/refs#GET
  • /DefaultCollection/_apis/git/repositories/*/stats/branches#GET
  • /DefaultCollection/_apis/git/repositories/*/trees#GET
  • /DefaultCollection/_apis/git/repositories/*/limitedRefCriteria#GET
  • /DefaultCollection/_apis/git/*/repositories/*/objects#GET
  • /DefaultCollection/_apis/git/repositories/*/objects#GET
  • /DefaultCollection/*/_apis/git/pullRequests#GET
  • /DefaultCollection/_apis/codereview/reviews#GET
  • /DefaultCollection/*/_apis/codereview/reviews#GET
  • /DefaultCollection/*/_apis/codereview/reviewsbatch#POST
  • /DefaultCollection/*/_apis/codereview/settings#GET
  • /DefaultCollection/_apis/visits/artifactVisitsBatch#POST
  • /DefaultCollection/_apis/visits/artifactStatsBatch#POST
  • /DefaultCollection/*/_apis/policy/Evaluations#GET
vso.code_write
  • /DefaultCollection/_apis/tfvc/changesets#GET+POST
  • /DefaultCollection/*/_apis/tfvc/changesets#GET+POST
  • /DefaultCollection/*/*/_git/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_git/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_git/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/*/_git/_full/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_git/_full/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_git/_full/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/*/_git/_optimized/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_git/_optimized/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_git/_optimized/*#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/*/repositories/*/pullrequests#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/*/repositories/*/pushes#POST
  • /DefaultCollection/*/_apis/git/*/repositories/*/refs#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/*/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/*/_apis/git/repositories/*/pullrequests#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/repositories/*/pushes#POST
  • /DefaultCollection/*/_apis/git/repositories/*/refs#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/*/_apis/git/*/repositories/*/objects#PUT
  • /DefaultCollection/*/_apis/git/repositories/*/objects#PUT
  • /DefaultCollection/_apis/git/*/repositories/*/pullrequests#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/git/*/repositories/*/pushes#POST
  • /DefaultCollection/_apis/git/*/repositories/*/refs#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/git/*/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pullrequests#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/git/repositories/*/pushes#POST
  • /DefaultCollection/_apis/git/repositories/*/refs#POST+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/*/repositories/*/objects#PUT
  • /DefaultCollection/_apis/git/repositories/*/objects#PUT
  • /DefaultCollection/*/_apis/codereview/reviews#POST+PUT+PATCH+DELETE
  • /DefaultCollection/*/_apis/codereview/settings#POST+PUT
  • /DefaultCollection/_apis/visits/artifactVisits#PUT
  • /DefaultCollection/*/_apis/policy/Evaluations#PATCH
vso.code_manage
  • /DefaultCollection/*/_apis/git/repositories#POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/*/repositories#POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/git/repositories/*/limitedRefCriteria#PUT
  • /DefaultCollection/_apis/git/repositories#POST+PATCH+DELETE
  • /DefaultCollection/_apis/git/*/repositories#POST+PATCH+DELETE
  • /DefaultCollection/_apis/git/repositories/*/limitedRefCriteria#PUT
vso.code_status
  • /DefaultCollection/_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/*/_apis/git/repositories/*/commits/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pullrequests/*/statuses#GET+POST
  • /DefaultCollection/*/_apis/git/repositories/*/pullrequests/*/statuses#GET+POST
  • /DefaultCollection/_apis/git/repositories/*/pullrequests/*/iterations/*/statuses#GET+POST
  • /DefaultCollection/*/_apis/git/repositories/*/pullrequests/*/iterations/*/statuses#GET+POST
vso.chat_write
  • /DefaultCollection/_apis/chat/rooms#GET
  • /DefaultCollection/_apis/chat/rooms/*/messages#GET+POST+PATCH+DELETE
  • /DefaultCollection/_apis/chat/rooms/*/users#GET+PUT+DELETE
vso.chat_manage
  • /DefaultCollection/_apis/chat/rooms#GET+POST+PATCH+PUT+DELETE
vso.agentpools
  • /_apis/distributedtask/packages/agent#GET
  • /_apis/distributedtask/pools#GET
  • /_apis/distributedtask/pools/*/agents#GET
  • /_apis/distributedtask/pools/*/jobrequests#GET
  • /_apis/distributedtask/queues#GET
  • /_apis/distributedtask/tasks#GET
  • /DefaultCollection/_apis/distributedtask/packages/agent#GET
  • /DefaultCollection/_apis/distributedtask/pools#GET
  • /DefaultCollection/_apis/distributedtask/pools/*/agents#GET
  • /DefaultCollection/_apis/distributedtask/pools/*/jobrequests#GET
  • /DefaultCollection/_apis/distributedtask/queues#GET
  • /DefaultCollection/*/_apis/distributedtask/queues#GET
  • /DefaultCollection/_apis/distributedtask/tasks#GET
vso.agentpools_manage
  • /_apis/distributedtask/packages/agent#GET
  • /_apis/distributedtask/pools#DELETE+GET+PATCH+POST
  • /_apis/distributedtask/pools/*/agents#DELETE+GET+PATCH+POST+PUT
  • /_apis/distributedtask/pools/*/jobrequests#GET
  • /_apis/distributedtask/queues#DELETE+GET+PATCH+POST+PUT
  • /_apis/distributedtask/tasks#DELETE+GET+PATCH+POST+PUT
  • /DefaultCollection/_apis/distributedtask/packages/agent#GET
  • /DefaultCollection/_apis/distributedtask/pools#DELETE+GET+PATCH+POST
  • /DefaultCollection/_apis/distributedtask/pools/*/agents#DELETE+GET+PATCH+POST+PUT
  • /DefaultCollection/_apis/distributedtask/pools/*/jobrequests#GET
  • /DefaultCollection/_apis/distributedtask/queues#DELETE+GET+PATCH+POST
  • /DefaultCollection/*/_apis/distributedtask/queues#DELETE+GET+PATCH+POST+PUT
  • /DefaultCollection/_apis/distributedtask/tasks#DELETE+GET+PATCH+POST+PUT
vso.agentpools_listen
  • /_apis/distributedtask/pools/*/jobrequests#GET+PATCH+DELETE
  • /_apis/distributedtask/pools/*/messages#GET+DELETE
  • /_apis/distributedtask/pools/*/sessions#GET+POST+DELETE
  • /_apis/distributedtask/pools/*/agents/*/updates#PUT
  • /DefaultCollection/_apis/distributedtask/pools/*/jobrequests#GET+PATCH+DELETE
  • /DefaultCollection/_apis/distributedtask/pools/*/messages#GET+DELETE
  • /DefaultCollection/_apis/distributedtask/pools/*/sessions#GET+POST+DELETE
  • /DefaultCollection/_apis/distributedtask/pools/*/agents/*/updates#PUT
vso.governance.extension_write
  • /_apis/governance/components#GET
  • /_apis/governance/products#GET
  • /_apis/governance/products/*/registrations/*/policyStatuses#PUT
vso.packaging
  • /DefaultCollection/_apis/packaging#GET
  • /DefaultCollection/_packaging#HEAD+GET
vso.packaging_write
  • /DefaultCollection/_apis/packaging#GET+POST+PUT
  • /DefaultCollection/_packaging#HEAD+GET+POST+PUT
  • /DefaultCollection/_packaging/*/nuget/v2#DELETE
  • /DefaultCollection/_packaging/*/npm#DELETE
  • /DefaultCollection/_apis/packaging/*/packages/*/versions#DELETE
vso.packaging_manage
  • /DefaultCollection/_apis/packaging#GET+PATCH+POST+PUT+DELETE
  • /DefaultCollection/_packaging#HEAD+GET+PATCH+POST+PUT+DELETE
vso.oss
  • /DefaultCollection/_apis/oss/*#GET
vso.oss_write
  • /DefaultCollection/_apis/oss/Requests#POST+PUT+PATCH
  • /DefaultCollection/_apis/oss/Versions#PATCH
  • /DefaultCollection/_apis/oss/Validation#POST
vso.oss_manage
  • /DefaultCollection/_apis/oss/*#POST+PUT+PATCH+DELETE
vso.oss.extension_read
  • /DefaultCollection/_apis/oss/ExtensionData/*#GET
vso.oss.extension_write
  • /DefaultCollection/_apis/oss/ExtensionData/*#POST+PUT+PATCH+DELETE
vso.test
  • /DefaultCollection/_apis/test/suites#GET
  • /DefaultCollection/*/_apis/test/plans#GET
  • /DefaultCollection/*/_apis/test/runs#GET
  • /DefaultCollection/*/_apis/test/extensionFields#GET
  • /DefaultCollection/*/_apis/test/suites#GET
  • /DefaultCollection/*/_apis/test/results#GET
  • /DefaultCollection/*/_apis/test/testSettings#GET
  • /DefaultCollection/*/_apis/test/codeCoverage#GET
  • /DefaultCollection/*/_apis/test/configurations#GET
  • /DefaultCollection/*/_apis/test/variables#GET
  • /DefaultCollection/*/_apis/test/cloneOperation#GET
  • /DefaultCollection/*/_apis/test/session#GET
  • /DefaultCollection/*/_apis/test/suiteEntry#GET
  • /DefaultCollection/*/_apis/test/ResultRetentionSettings#GET
  • /DefaultCollection/*/_apis/test/ResultSummaryByRelease#GET
  • /DefaultCollection/*/_apis/test/ResultSummaryByBuild#GET
  • /DefaultCollection/*/_apis/test/ResultSummaryByRequirement#GET
  • /DefaultCollection/*/_apis/test/ResultDetailsByRelease#GET
  • /DefaultCollection/*/_apis/test/ResultDetailsByBuild#GET
  • /DefaultCollection/*/_apis/test/TestMethods#GET
  • /DefaultCollection/*/_apis/test/Points#GET
  • /DefaultCollection/*/*/_apis/test/session#GET
vso.test_write
  • /DefaultCollection/*/_apis/test/plans#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test/suites#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test/runs#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test/results#GET+POST
  • /DefaultCollection/*/_apis/test/extensionFields#GET+POST
  • /DefaultCollection/*/_apis/test/testSettings#GET+POST+DELETE
  • /DefaultCollection/*/_apis/test/codeCoverage#GET+POST
  • /DefaultCollection/*/_apis/test/configurations#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test/variables#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/_apis/test/cloneOperation#GET+POST
  • /DefaultCollection/*/_apis/test/session#GET+POST+PATCH
  • /DefaultCollection/*/_apis/test/suiteEntry#GET+PATCH
  • /DefaultCollection/*/_apis/test/ResultRetentionSettings#GET+PATCH
  • /DefaultCollection/*/_apis/test/ResultSummaryByRelease#GET+POST
  • /DefaultCollection/*/_apis/test/ResultSummaryByRequirement#GET+POST
  • /DefaultCollection/*/_apis/test/TestMethods#GET+POST+DELETE
  • /DefaultCollection/*/_apis/test/Points#GET+POST+PATCH+DELETE
  • /DefaultCollection/*/*/_apis/test/session#GET+POST+PATCH
vso.loadtest
  • /_apis/clt/testdrops
  • /_apis/clt/testruns
  • /_apis/clt/testruns/*/errors
  • /_apis/clt/testruns/*/messages
  • /_apis/clt/testruns/*/results
  • /_apis/clt/testruns/*/counterinstances
  • /_apis/clt/testruns/*/countersamples
  • /_apis/clt/apm
  • /_apis/clt/configuration
  • /_apis/clt/agentgroups
  • /_apis/clt/agentgroups/*/agents
vso.loadtest_write
  • /_apis/clt/testdrops#GET+POST
  • /_apis/clt/testruns#GET+POST+PATCH
  • /_apis/clt/agentgroups#GET+POST+PATCH+DELETE
  • /_apis/clt/agentgroups/*/agents#GET+POST+PATCH+DELETE
vso.licensing
  • /_apis/licensing/clientrights
  • /_apis/licensing/ExtensionEntitlements/*/*#GET
vso.extension
  • /_apis/ExtensionManagement/InstalledExtensions#GET
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions#GET
vso.extension_manage
  • /_apis/ExtensionManagement/InstalledExtensions#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions#GET+POST+PATCH+DELETE+PUT
  • /_apis/ExtensionManagement/InstalledExtensionsByName#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensionsByName#GET+POST+PATCH+DELETE+PUT
  • /_apis/ExtensionManagement/RequestedExtensions#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/RequestedExtensions#GET+POST+PATCH+DELETE+PUT
  • /_apis/ExtensionManagement/AcquisitionOptions#GET
  • /DefaultCollection/_apis/ExtensionManagement/AcquisitionOptions#GET
  • /_apis/ExtensionManagement/AcquisitionRequests#POST
  • /DefaultCollection/_apis/ExtensionManagement/AcquisitionRequests#POST
vso.extension.data
  • /_apis/ExtensionManagement/InstalledExtensions/*/Data#GET
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/Data#GET
  • /_apis/ExtensionManagement/InstalledExtensions/*/*/Data#GET
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/*/Data#GET
  • /_apis/Contribution/InstalledApps#GET
  • /DefaultCollection/_apis/Contribution/InstalledApps#GET
  • /_apis/ExtensionManagement/InstalledExtensions/*/*/ExtensionDataCollectionQuery#POST
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/*/ExtensionDataCollectionQuery#POST
vso.extension.data_write
  • /_apis/ExtensionManagement/InstalledExtensions/*/Data#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/Data#GET+POST+PATCH+DELETE+PUT
  • /_apis/ExtensionManagement/InstalledExtensions/*/*/Data#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/*/Data#GET+POST+PATCH+DELETE+PUT
vso.extension.default
  • /_apis/Contribution/InstalledApps#GET
  • /DefaultCollection/_apis/Contribution/InstalledApps#GET
  • /_apis/Contribution/nodes/query#POST
  • /DefaultCollection/_apis/Contribution/nodes/query#POST
vso.extension.preview
  • /_apis/ExtensionManagement/InstalledExtensions/*/Data#GET+POST+PATCH+DELETE+PUT
  • /DefaultCollection/_apis/ExtensionManagement/InstalledExtensions/*/Data#GET+POST+PATCH+DELETE+PUT
vso.commerce.write
  • /_apis/Commerce/*#GET+POST+PATCH
  • /_apis/Commerce/Subscription/*/*#GET+PUT+POST
vso.gallery
  • /_apis/gallery/publishers#GET
  • /_apis/gallery/extensions#GET
vso.gallery_acquire
  • /_apis/gallery/acquisitionrequests#POST
  • /_apis/gallery/acquisitionoptions#GET+POST
vso.gallery_publish
  • /_apis/gallery/publishers/*/extensions#POST+PUT+DELETE
  • /_apis/gallery/publisher/*/extension/*/accountsbyname#POST+DELETE
  • /_apis/gallery/extensions#POST+PUT+DELETE
  • /_apis/gallery/extensions/*/accounts#POST+DELETE
vso.gallery_manage
  • /_apis/gallery/publishers#POST+PUT+DELETE
vso.entitlements
  • /_apis/licensing/entitlements#GET
preview_msdn_licensing
  • /_apis/licensing/msdn/me
user_impersonation
  • /_signedin#GET+POST
tenant_picker
  • /aad/tenants
signout
  • /_signout
app_token
  • /*#*
vso.release
  • /DefaultCollection/*/_apis/release/definitions#GET
  • /DefaultCollection/*/_apis/release/definitions/*/revisions#GET
  • /DefaultCollection/*/_apis/release/releases#GET
  • /DefaultCollection/*/_apis/release/releases/*/workitems#GET
  • /DefaultCollection/*/_apis/release/approvals#GET
  • /DefaultCollection/*/_apis/release/releases/*/manualInterventions#GET
  • /DefaultCollection/*/_apis/release/deployments#GET+POST
  • /DefaultCollection/*/_apis/distributedtask/hubs/release/plans#GET
vso.release_execute
  • /DefaultCollection/*/_apis/release/definitions#GET+POST+PUT
  • /DefaultCollection/*/_apis/release/definitions/*/revisions#GET
  • /DefaultCollection/*/_apis/release/releases#GET+POST+PATCH+PUT
  • /DefaultCollection/*/_apis/release/releases/*/workitems#GET
  • /DefaultCollection/*/_apis/release/releases/*/environments#PATCH
  • /DefaultCollection/*/_apis/release/approvals#GET
  • /DefaultCollection/*/_apis/release/releases/*/manualInterventions#GET
  • /DefaultCollection/*/_apis/release/deployments#GET+POST
vso.release_manage
  • /DefaultCollection/*/_apis/release/definitions#GET+POST+PUT+DELETE
  • /DefaultCollection/*/_apis/release/definitions/*/revisions#GET
  • /DefaultCollection/*/_apis/release/releases#GET+POST+PATCH+PUT+DELETE
  • /DefaultCollection/*/_apis/release/releases/*/workitems#GET
  • /DefaultCollection/*/_apis/release/releases/*/environments#PATCH
  • /DefaultCollection/*/_apis/release/approvals#GET+PATCH
  • /DefaultCollection/*/_apis/release/releases/*/manualInterventions#GET+PATCH
  • /DefaultCollection/*/_apis/release/deployments#GET+POST
vso.release_logs
  • /DefaultCollection/*/_apis/release/releases/*/logs#GET
  • /DefaultCollection/*/_apis/release/releases/*/environments/*/tasks/*/logs#GET
  • /DefaultCollection/*/_apis/release/releases/*/environments/*/deployPhases/*/tasks/*/logs#GET
vso.machinegroup_manage
  • /DefaultCollection/*/_apis/distributedtask/machinegroups/*#GET+POST+DELETE+PATCH
  • /DefaultCollection/*/_apis/distributedtask/deploymentgroups/*#GET+POST+DELETE+PATCH+PUT
vso.live_updates
  • /signalr#GET+POST
vso.taskgroups
  • /DefaultCollection/*/_apis/distributedtask/taskgroups#GET
  • /DefaultCollection/*/_apis/distributedtask/taskgroups/*/revisions#GET
vso.taskgroups_add
  • /DefaultCollection/*/_apis/distributedtask/taskgroups#POST
vso.taskgroups_manage
  • /DefaultCollection/*/_apis/distributedtask/taskgroups#GET+POST+PUT+DELETE
vso.serviceendpoint
  • /DefaultCollection/*/_apis/distributedtask/serviceendpoints#GET
  • /DefaultCollection/_apis/distributedtask/serviceendpointtypes#GET
vso.serviceendpoint_query
  • /DefaultCollection/*/_apis/distributedtask/serviceendpointproxy#POST
vso.serviceendpoint_manage
  • /DefaultCollection/*/_apis/distributedtask/serviceendpoints#GET+POST+PUT+DELETE
vso.drop
  • /DefaultCollection/_apis/drop/client#HEAD
  • /DefaultCollection/_apis/blob/blobs/*#GET
  • /DefaultCollection/_apis/blob/blobsbatch#POST
  • /DefaultCollection/_apis/drop/drop#OPTIONS
  • /DefaultCollection/_apis/drop/drop/*#GET
  • /DefaultCollection/_apis/drop/fetch/*#GET+POST
  • /DefaultCollection/_apis/drop/drops/*#OPTIONS+GET
  • /DefaultCollection/_apis/drop/manifests/*#OPTIONS+GET
vso.drop_write
  • /DefaultCollection/_apis/blob/blobs#OPTIONS
  • /DefaultCollection/_apis/blob/blobs/*#POST
  • /DefaultCollection/_apis/drop/drop/*#PUT+POST+PATCH
  • /DefaultCollection/_apis/drop/drops/*#OPTIONS+PUT+POST+PATCH
  • /DefaultCollection/_apis/drop/manifests/*#OPTIONS+POST
vso.drop_manage
  • /DefaultCollection/_apis/drop/drop/*#DELETE
  • /DefaultCollection/_apis/drop/drops/*#DELETE
vso.buildcache
  • /_apis/buildcache/cachedeterminismguid/*#GET
  • /_apis/buildcache/contenthashlist#OPTIONS
  • /_apis/buildcache/contenthashlist/*#GET
  • /_apis/buildcache/selector#OPTIONS
  • /_apis/buildcache/selector/*#GET
  • /_apis/blob/blob/referencesbatch#POST
vso.buildcache_write
  • /_apis/buildcache/contenthashlist/*#POST
  • /_apis/buildcache/incorporateStrongFingeprint/*#PUT
  • /_apis/blob/blobs#OPTIONS
  • /_apis/blob/blobs/*#POST
vso.authorization_grant
vso.project
  • /DefaultCollection/_apis/projects#GET
vso.project_write
  • /DefaultCollection/_apis/projects#PATCH
vso.project_manage
  • /DefaultCollection/_apis/projects#POST+DELETE
vso.symbols
  • /DefaultCollection/_apis/symbol/symsrv#OPTIONS
  • /DefaultCollection/_apis/symbol/symsrv/*#GET
  • /DefaultCollection/_apis/symbol/debugentries#OPTIONS
  • /DefaultCollection/_apis/symbol/debugentries/*#GET
  • /DefaultCollection/_apis/symbol/requests#OPTIONS+GET
vso.symbols_write
  • /DefaultCollection/_apis/symbol/requests#POST
  • /DefaultCollection/_apis/symbol/requests/*#POST+PATCH
  • /DefaultCollection/_apis/blob/blobs#OPTIONS
  • /DefaultCollection/_apis/blob/blobs/*#POST
vso.symbols_manage
  • /DefaultCollection/_apis/symbol/requests/*#DELETE
vso.analytics
  • /DefaultCollection/_odata/*#GET
  • /DefaultCollection/*/_odata/*#GET
vso.identitypicker
  • /_apis/IdentityPicker/identities#POST
  • /_apis/IdentityPicker/identities/*/avatar#GET
vso.dashboards
  • /DefaultCollection/*/*/_apis/dashboard/dashboards#HEAD+GET
  • /DefaultCollection/*/*/_apis/dashboard/dashboards/*/widgets#HEAD+GET
vso.dashboards_manage
  • /DefaultCollection/*/*/_apis/dashboard/dashboards#HEAD+GET+POST+DELETE
  • /DefaultCollection/*/*/_apis/dashboard/dashboards/*/widgets#HEAD+GET+POST+PUT+PATCH+DELETE
vso.connected_server
  • /_apis/Commerce/CommercePackage#GET
  • /_apis/Commerce/CommercePackage/*#GET
vso.notification
  • /_apis/notification/EventTypes#GET
  • /_apis/notification/EventTypes/*/fieldValuesQuery#POST
  • /_apis/notification/Subscriptions#GET
  • /_apis/notification/SubscriptionQuery#POST
  • /_apis/notification/Follows#GET
  • /_apis/notification/SubscriptionTemplates#GET
  • /_apis/notification/StatisticsQuery#POST
  • /DefaultCollection/_apis/notification/EventTypes#GET
  • /DefaultCollection/_apis/notification/EventTypes/*/fieldValuesQuery#POST
  • /DefaultCollection/_apis/notification/Subscriptions#GET
  • /DefaultCollection/_apis/notification/SubscriptionQuery#POST
  • /DefaultCollection/_apis/notification/Follows#GET
  • /DefaultCollection/_apis/notification/SubscriptionTemplates#GET
  • /DefaultCollection/_apis/notification/StatisticsQuery#POST
vso.notification_write
  • /_apis/notification/Subscriptions#GET+PATCH+POST+DELETE+PUT
  • /DefaultCollection/_apis/notification/Subscriptions#GET+PATCH+POST+DELETE+PUT
  • /_apis/notification/SubscriptionEvaluationRequest#GET+POST
  • /DefaultCollection/_apis/notification/SubscriptionEvaluationRequest#GET+POST
  • /_apis/notification/Follows#GET+POST+DELETE
  • /DefaultCollection/_apis/notification/Follows#GET+POST+DELETE
vso.notification_manage
  • /_apis/notification/BatchNotificationOperations#POST
  • /DefaultCollection/_apis/notification/BatchNotificationOperations#POST
vso.notification_publish
  • /_apis/notification/Events#POST
  • /DefaultCollection/_apis/notification/Events#POST
vso.settings
  • /_apis/Settings/*#GET
  • /DefaultCollection/_apis/Settings/*#GET
vso.settings_write
  • /_apis/Settings/*#GET+PUT+PATCH+DELETE
  • /DefaultCollection/_apis/Settings/*#GET+PUT+PATCH+DELETE
vso.proxy
  • /Services/v4.0/item.ashx
  • /Services/v4.0/FileHandlerService.asmx#POST
  • /TeamFoundation/Administration/v3.0/LocationService.asmx#POST
  • /Services/v3.0/LocationService.asmx#POST
  • /DefaultCollection/Services/v4.0/item.ashx
  • /DefaultCollection/Services/v4.0/FileHandlerService.asmx#POST
  • /DefaultCollection/Services/v3.0/LocationService.asmx#POST
  • /*/*/*/_git/*#GET
  • /*/*/_git/*#GET
  • /*/_git/*#GET
  • /*/*/*/_git/_full/*#GET
  • /*/*/_git/_full/*#GET
  • /*/_git/_full/*#GET
  • /*/*/*/_git/*/git-upload-pack#POST
  • /*/*/_git/*/git-upload-pack#POST
  • /*/_git/*/git-upload-pack#POST
  • /*/*/*/_git/_full/*/git-upload-pack#POST
  • /*/*/_git/_full/*/git-upload-pack#POST
  • /*/_git/_full/*/git-upload-pack#POST
  • /*/*/*/_git/*/gvfs/*#POST
  • /*/*/_git/*/gvfs/*#POST
  • /*/_git/*/gvfs/*#POST
  • /*/*/*/_git/_full/*/gvfs/*#POST
  • /*/*/_git/_full/*/gvfs/*#POST
  • /*/_git/_full/*/gvfs/*#POST
vso.graph
  • /_apis/graph/groups/*#GET
  • /_apis/graph/subjectlookup#POST
  • /_apis/graph/memberships/*#GET
  • /_apis/graph/memberships/*/*#GET+HEAD
  • /_apis/graph/scopes/*#GET
  • /_apis/graph/users/*#GET
  • /DefaultCollection/_apis/graph/groups/*#GET
  • /DefaultCollection/_apis/graph/subjectlookup#POST
  • /DefaultCollection/_apis/graph/memberships/*#GET
  • /DefaultCollection/_apis/graph/memberships/*/*#GET+HEAD
  • /DefaultCollection/_apis/graph/scopes/*#GET
  • /DefaultCollection/_apis/graph/users/*#GET
vso.graph_write
  • /_apis/graph/groups/*#POST+PATCH+DELETE
  • /_apis/graph/memberships/*/*#HEAD+PUT+DELETE
  • /_apis/graph/users/*#POST+DELETE
  • /DefaultCollection/_apis/graph/groups/*#POST+PATCH+DELETE
  • /DefaultCollection/_apis/graph/memberships/*/*#HEAD+PUT+DELETE
  • /DefaultCollection/_apis/graph/users/*#POST+DELETE
vso.security_manage
  • /_apis/SecurityNamespaces#POST
  • /_apis/AccessControlLists#POST+DELETE
  • /_apis/AccessControlEntries#POST+DELETE
  • /_apis/Permissions#DELETE
  • /DefaultCollection/_apis/SecurityNamespaces#POST
  • /DefaultCollection/_apis/AccessControlLists#POST+DELETE
  • /DefaultCollection/_apis/AccessControlEntries#POST+DELETE
  • /DefaultCollection/_apis/Permissions#DELETE
vso.memberEntitlementManagement
  • /_apis/MemberEntitlements/*#GET
  • /_apis/MemberEntitlements#GET
  • /DefaultCollection/_apis/MemberEntitlements/*#GET
  • /DefaultCollection/_apis/MemberEntitlements#GET
vso.memberEntitlementManagement_write
  • /_apis/MemberEntitlements/*#PATCH+DELETE
  • /_apis/MemberEntitlements#POST
  • /DefaultCollection/_apis/MemberEntitlements/*#PATCH+DELETE
  • /DefaultCollection/_apis/MemberEntitlements#POST

Sunday, December 31, 2017

Lightning fast

Last time I've revisited Google Play order processing, there was a 6 hour gap between order submission and the card being charged. The delay was artificial, naturally. I'm not exactly sure what was the reasons for such a design, probably, that was an allowance for return and refund; if the customer's remorse kicks in before the card is charged, Google doesn't have to pay the card transaction fee.

Effective July 2017, the gap is no longer on the order of hours; now it's 5-7 minutes.

Friday, December 15, 2017

Team Foundation Server schema

I happen to run an on-premises instance of Microsoft Team Foundation Server for a medium sized software shop. TFS has pretty good reporting capabilities, but out of the box, almost no cross-collection reporting. Fortunately, those who are blessed with admin rights in TFS get to connect to the production database server.

The schema of TFS databases (there are multiple) is occasionally convoluted, but generally approachable. Each collection gets a database, and the server-level information is stored under Tfs_Configuration. If you've dabbled with TFS REST API, you'd know that both collections and projects are identified with GUIDs in addition to their regular names.

The list of team collections is in table Tfs_Configuration.dbo.tbl_ServiceHost. The field HostId corresponds to the collection's  GUID.

The list of projects is in table dbo.tbl_projects in each collection's database. The GUID is under project_id. Table dbo.tbl_Project doesn't have the GUID, just the dataspace ID.

The build/release definitions and queues are stored on per-project basis, but there's no project ID there. Instead, there's an integer field DataspaceId which can be linked back to the project via the dbo.tbl_Dataspace table. In that table, the field DataspaceIdentifier (distinct from DataspaceId!) contains the project GUID. Dataspaces have a string type, stored in DataspaceCategory; e. g. the agent queue dataspace corresponds to type "DistributedTask", and release definitions belong to dataspace of type "ReleaseManagement".

The table Release.tbl_DefinitionEnvironmentStep doesn't contain steps. It contains approvals. Same goes for Release.tbl_ReleaseEnvironmentStep. The latter stores the approvals received during execution, the former stores the configured approvals.

References to users and groups are stored in collection tables as GUIDs. The GUIDs are collection specific; in order to resolve them, use the table db.tbl_IdentityMap. The field localId corresponds to a collection-specific GUID, masterId is the global GUID.

In order to resolve the masterId further to the actual group or AD user, use either table Tfs_Configuration.dbo.tbl_Group or Tfs_Configuration.dbo.tbl_Identity. Even collection- and project-level groups can be found in the former. The latter one stores references to the AD accounts.

The table tbl_Group contains both server-, collection-, and project-level groups. In order to retrieve the collection and/or project, use the InternalScopeId field. It's a reference to Tfs_Configuration.dbo.tbl_GroupScope. The server level scope has a hard-coded ID 1.

Friday, December 1, 2017

Who does that?

Amazing discovery of the day: Microsoft Excel respects Scroll Lock.

Thursday, November 2, 2017

Abusing COM for tightly coupled process interaction

Twice in my career, I had to deal with unreliable third party algorithm libraries in a server situation. There's a service type program that follows a general request/response pattern. Processing a request involves calling a third party library that I don't control and that crashes far too often for comfort. The service must survive the crash, log it, and emit an error response.

Both the server and the library are native code, so a global try/catch around the library call is not really an option. So this calls for a dispatcher/worker architecture; the service receives requests and routes them to worker processes, one request at a time. If a worker crashes, the service will know and act accordingly.

One of the projects where I had to deal with this was on Linux; that's a story for another day. The other one was on Windows, and that's what I would like to discuss.

So, dispatcher/worker communication in Windows. It all hinges on the choice of an interprocess communication mechanism. I'd like an IPC that:

  • Reliably detects server crashes
  • Is message-based as opposed to stream-based
  • Has a built-in datatype marshaling logic

Component Object Model (COM) comes to mind. The worker program would be the COM server with a single object, the dispatcher would instantiate the server object and call its methods. Each request translates into one or more COM method calls. Server crash detection - check. Built-in marshaling - check. But there's a wrinkle. A COM out-of-process server is not supposed to run multiple instances. Here's how COM usually works:

  • A server executable is listed in the registry under the CLSID
  • A client calls CoCreateInstance() with that CLSID
  • The run-time starts the server executable
  • The server executable calls CoRegisterClassObject() for the CLSID
  • The run-time calls the object factory

If a subsequent call for the same CLSID comes, the run-time would reuse the same server process rather than starting a new one.

Also, the loosely coupled nature of COM is a bit of an overkill for my scenario. I never meant to expose my worker program to clients other than my dispatcher. The whole COM machinery for making servers exposed and user friendly to third party clients is irrelevant to my case.

So, how can we have a COM client creating multiple, identical COM objects running in different processes? Running Object Table (ROT) to the rescue. COM servers can publish their objects in a global repository, identified by arbitrary monikers. So the idea is:

  • The dispatcher starts multiple worker processes
  • Each gets a unique integer parameter (a cookie) via the command line
  • The worker registers an object in the ROT, identified by the cookie
  • The dispatcher retrieves that object
This is different from the regular object creation protocol. The worker program has no object factory (since it's only running exactly one object). There's no need to register the server in the registry. The object needs no CLSID. As for the interface, in my case, I'd use raw IDispatch, so there's no need for any marshaling code, either. My dispatcher/worker exchange protocol can be perfectly served by passing an array of VARIANTs both ways.

The only addition to that protocol is that the dispatcher needs to know once the worker's COM object is available in the ROT. I did that with a named event object, where the name contains the cookie. Once the worker starts up and registers its object, it would set the event. Maybe a short sleep on the dispatcher side would accomplish the same, but this is both faster and safer.

In my case, the worker can only process one request at a time, so the worker doesn't need to be multithreaded. So the CoInitialize() call in the worker would specify COINIT_APARTMENTTHREADED, and then the worker's WinMain() would have to run a message loop.

Now, dealing with the worker process crashes. There can be three kinds:
  1. During process startup
  2. During the request
  3. Between the requests
The first one is rather easy. I mentioned that the dispatcher starts the worker and waits for the "I'm ready" event. Make that a wait for two objects, the event handle and the worker process handle, and see if the process terminates before the event is set. If it does, that means a startup crash.

If the process crashes during the request, COM will report an error. The question is, which one? After some extensive testing with numerous crashes, I've identified the following HRESULT values:
  • HRESULT_FROM_WIN32(RPC_E_SERVERFAULT)
  • HRESULT_FROM_WIN32(RPC_S_CALL_FAILED)
  • RPC_E_DISCONNECTED
Either of those means the server process terminated during the COM call, one way or another.

If the process terminates between requests, the next COM call would return HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE).

What are the quit conditions for the worker? One can implement a "please quit" method, and tell all workers to quit during dispatcher shutdown. A worker might quit when the client disconnects (e. g. the main object is released to zero). It may quit after a timeout of inactivity. In my implementation, I'd pass the PID of the dispatcher to the workers, and made them quit if the dispatcher terminates. The message loop becomes a MsgWaitForMultipleObjects() loop, with the objects being the parent process handle.

Rather than post the code here, I've published it as a Gist. The gist contains a sample worker, a sample dispatcher that creates several threads and calls the worker on each of them. The worker crashes at random with an access violation, but the dispatcher is handling that gracefully.

There's no ATL dependency in the project. There's a Native COM reference in the dispatcher, but that can be easily avoided, if dependencies are a problem. I've compiled it with Visual Studio 2017. Dispatcher is meant to be a console project (it prints some lines), while Worker is a Windows GUI one (it has a message loop).

To summarize, this is a fun little way of making COM dance. No registration, no typelibs, no proxy-stub machinery, no ref counting. Just the bits that we like - reliable interprocess communication, cross-process error handling, friendly passing of simple-typed parameters as VARIANTs.





Monday, April 3, 2017

The saga continues

Remember, some time ago Google has removed the order amount in USD from their Merchant Console? Ever since, I've used the earnings report at the end of each month to capture that data item.

Two months in, starting with the March 2017 earnings report, that's not an option, either. The order amount is there, so is the transaction fee, but the order ID isn't.

At least the support representative seemed to agree that it was a bug.

UPDATE: they've fixed it without any announcements.