{"id":9908,"date":"2014-10-07T13:07:00","date_gmt":"2014-10-07T13:07:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/buckh\/2014\/10\/07\/how-to-add-licensed-users-to-vs-online-via-the-api\/"},"modified":"2014-10-07T13:07:00","modified_gmt":"2014-10-07T13:07:00","slug":"how-to-add-licensed-users-to-vs-online-via-the-api","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/buckh\/how-to-add-licensed-users-to-vs-online-via-the-api\/","title":{"rendered":"How to add licensed users to VS Team Services via the API"},"content":{"rendered":"<p>NOTE (Sept. 16, 2016): I need to update the solution file to work with VS 2015 and test the code again.<\/p>\n<p>A while back someone asked a question about how to use the API to add licensed users to a VSTS account and change licenses for existing users (instead of the <a href=\"http:\/\/www.visualstudio.com\/get-started\/assign-licenses-to-users-vs\">web UI to invite and assign licenses to users<\/a>). Abhijeet, a dev on my team, gave me some code to get me started, as the identity APIs are not easy to figure out.<\/p>\n<p>Licensing is different on VSTS than it is with Team Foundation Server. With VSTS, every user who is added to an account must have a license assigned. We need to be able to add a new user and also to change the license for an existing user. Let\u2019s take a look at how the code to do this works.<\/p>\n<p><strong>Shared Platform Services<\/strong><\/p>\n<p>The first thing to notice is that the code is operating on the account that is stored in what we call Shared Platform Services (SPS). SPS is a core set of services, such as identity, account, and profile, that are used by every service in VSTS. The notion of a collection, which is a concept that was introduced in TFS 2010, doesn\u2019t exist in SPS. That\u2019s because not every service in VSTS has to have the notion of a collection. Collection is a TFS concept that is used by version control, work item tracking, etc.<\/p>\n<p>Since we need to make a change to the account, we are going to be talking to SPS. Rather than making calls to <a href=\"https:\/\/MyAccount.visualstudio.com\">https:\/\/MyAccount.visualstudio.com<\/a> as you are used to seeing, we are going to be calling <a href=\"https:\/\/MyAccount.vssps.visualstudio.com\">https:\/\/MyAccount.vssps.visualstudio.com<\/a>. The \u201cvssps\u201d part of the URL takes us to SPS, and the account name in the URL gives SPS the context.<\/p>\n<p>You can see SPS in some scenarios if you watch the address bar in your browser. For example, go to <a href=\"http:\/\/visualstudio.com\">http:\/\/visualstudio.com<\/a> and sign in. Then click on your name to go to your profile page. You will see that the URL is <a href=\"https:\/\/app.vssps.visualstudio.com\/profile\/view?mkt=en-us\" title=\"https:\/\/app.vssps.visualstudio.com\/profile\/view?mkt=en-us\">https:\/\/app.vssps.visualstudio.com\/profile\/view?mkt=en-us<\/a> (or you can directly click that URL). In that context, you are asking SPS for your profile and the list of accounts that you have across the system. SPS is responsible for that information.<\/p>\n<p>Once we\u2019ve connected to SPS using the same credentials that you normally use as administrator of your account, we get the client representations of the identity and account services in SPS that we\u2019ll use. The next thing we do is to determine if the user is new or an existing user.<\/p>\n<p><strong>Adding a New User with a License<\/strong><\/p>\n<p>New users have to be added to the system. We call that process \u201cbind pending.\u201d What we mean by that is that we will add a reference to a user \u2013 nothing but the email address. That email address is not bound to anything. Once a user logs into VSTS with an email address that matches a bind-pending entry, we\u2019ll create a full Identity object that references the unique identifier of that user. We\u2019ll also have enter basic info for a profile (e.g., display name) and link that to the identity.<\/p>\n<p>The Identity API calls for this operation are not at all obvious, and this is something we need to improve. I\u2019ve added a lot of comments to the code to help explain what\u2019s going on. At a high level, we first have to construct a descriptor for this bind pending identity. Each account in VSTS is either an MSA-backed account (i.e., it only uses MSAs, also known as LiveIDs) or AAD-backed (i.e., all identities reside in an Azure Active Directory tenant). We make that determination using the properties of the administrative user. Then we first have to add a new user to a group in order for the user to be part of the system \u2013 for there to be a reference to the identity. Since every user must have a license, we add the user to the licensed users group.<\/p>\n<p><strong>Changing the License for an Existing User<\/strong><\/p>\n<p>This case is much simpler. If the user is already in the account, the code just makes a call to set the license with <span style=\"font-family: 'Courier New'\">licensingClient.AssignEntitlementAsync<\/span>.<\/p>\n<p><strong>Notes on Licensing<\/strong><\/p>\n<p>The names for the licenses match what you\u2019ll find in our documentation for pricing levels except <span style=\"font-family: 'Courier New'\">AccountLicense.Express<\/span>. That\u2019s the name that the code uses for Basic (at one time it was going to be called Express, and the code didn\u2019t get updated). If you look at the <span style=\"font-family: 'Courier New'\">AccountLicense<\/span> enum, you\u2019ll also find <span style=\"font-family: 'Courier New'\">AccountLicense.EarlyAdopter<\/span>. That was only valid until VSTS became GA, so it can no longer be used.<\/p>\n<p>The MSDN benefits license is different than the other licenses because it is dependent upon a user\u2019s MSDN license level. While you could explicitly set the license to a particular MSDN benefits level, you\u2019d only cause yourself problems. Setting it to <span style=\"font-family: 'Courier New'\">MsdnLicense.Eligible<\/span> as the code does below means that the service will handle setting the user\u2019s MSDN benefits level properly upon logging in.<\/p>\n<p>The licensing API right now uses an enum rather than a string for the licenses. The result is that <span style=\"font-family: 'Courier New'\">AccountLicense.Stakeholder <\/span>doesn\u2019t exist in the client API prior to Update 4. You\u2019ll see in the code that I commented out <span style=\"font-family: 'Courier New'\">Stakeholder<\/span> so that it builds with Update 3, which is the latest RTM update at the time this post is being written. In the future the API will allow for a string so that as new license types are added the API will still be able to use them.<\/p>\n<p>There are limits for licenses based on what you have purchased. For example, if you try to add a sixth user licensed for Basic, you will get an error message. Here\u2019s how to <a href=\"http:\/\/www.visualstudio.com\/en-us\/get-started\/get-more-user-licenses-vs.aspx\">add licenses to your VSTS account<\/a>.<\/p>\n<p>One other thing that I\u2019ll mention is that you may have to search around for the credential dialog prompt. That dialog ends up parented to the desktop, so it\u2019s easy for it to get hidden by another window.<\/p>\n<p>I\u2019ve attached the VS solution to this blog post, in addition to including the code in the post. I recommend downloading the solution, which will build with VS 2013 Update 3 or newer (it may work with earlier versions of 2013, but I didn\u2019t try it).<\/p>\n<p>Enjoy!<\/p>\n<p><em>Follow me on Twitter at <\/em><a href=\"https:\/\/twitter.com\/tfsbuck\"><em>twitter.com\/tfsbuck<\/em><\/a><\/p>\n<pre class=\"code\"><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">System;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">System.Linq;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.TeamFoundation;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.TeamFoundation.Framework.Common;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.VisualStudio.Services.Client;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.VisualStudio.Services.Identity;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.VisualStudio.Services.Identity.Client;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.VisualStudio.Services.Licensing;\n<\/span><span style=\"background: white;color: blue\">using <\/span><span style=\"background: white;color: black\">Microsoft.VisualStudio.Services.Licensing.Client;\n\n<\/span><span style=\"background: white;color: blue\">namespace <\/span><span style=\"background: white;color: black\">AddUserToAccount\n{\n    <\/span><span style=\"background: white;color: blue\">public class <\/span><span style=\"background: white;color: #2b91af\">Program\n    <\/span><span style=\"background: white;color: black\">{\n        <\/span><span style=\"background: white;color: blue\">public static void <\/span><span style=\"background: white;color: black\">Main(<\/span><span style=\"background: white;color: blue\">string<\/span><span style=\"background: white;color: black\">[] args)\n        {\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(args.Length == 0)\n            {\n                <\/span><span style=\"background: white;color: green\">\/\/ For ease of running from the debugger, hard-code the account and the email address if not supplied\n                \/\/ The account name here is just the name, not the URL.\n                \/\/args = new[] { \"Awesome\", \"example@outlook.com\", \"basic\" };\n            <\/span><span style=\"background: white;color: black\">}\n\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(!Init(args))\n            {\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Add a licensed user to a Visual Studio Account\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Usage: [accountName] [userEmailAddress] &lt;license&gt;\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"  accountName - just the name of the account, not the URL\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"  userEmailAddress - email address of the user to be added\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"  license - optional license (default is Basic): Basic, Professional, or Advanced\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: blue\">return<\/span><span style=\"background: white;color: black\">;\n            }\n\n            AddUserToAccount();\n        }\n\n        <\/span><span style=\"background: white;color: blue\">private static void <\/span><span style=\"background: white;color: black\">AddUserToAccount()\n        {\n            <\/span><span style=\"background: white;color: blue\">try\n            <\/span><span style=\"background: white;color: black\">{\n                <\/span><span style=\"background: white;color: green\">\/\/ Create a connection to the specified account.\n                \/\/ If you change the false to true, your credentials will be saved.\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">creds = <\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">VssClientCredentials<\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: blue\">false<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">vssConnection = <\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">VssConnection<\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">Uri<\/span><span style=\"background: white;color: black\">(VssAccountUrl), creds);\n\n                <\/span><span style=\"background: white;color: green\">\/\/ We need the clients for two services: Licensing and Identity\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">licensingClient = vssConnection.GetClient&lt;<\/span><span style=\"background: white;color: #2b91af\">LicensingHttpClient<\/span><span style=\"background: white;color: black\">&gt;();\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">identityClient = vssConnection.GetClient&lt;<\/span><span style=\"background: white;color: #2b91af\">IdentityHttpClient<\/span><span style=\"background: white;color: black\">&gt;();\n\n                <\/span><span style=\"background: white;color: green\">\/\/ The first call is to see if the user already exists in the account.\n                \/\/ Since this is the first call to the service, this will trigger the sign-in window to pop up.\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Sign in as the admin of account {0}. You will see a sign-in window on the desktop.\"<\/span><span style=\"background: white;color: black\">,\n                                  VssAccountName);\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">userIdentity = identityClient.ReadIdentitiesAsync(<\/span><span style=\"background: white;color: #2b91af\">IdentitySearchFilter<\/span><span style=\"background: white;color: black\">.AccountName, \n                                                                      VssUserToAddMailAddress).Result.FirstOrDefault();\n\n                <\/span><span style=\"background: white;color: green\">\/\/ If the identity is null, this is a user that has not yet been added to the account.\n                \/\/ We'll need to add the user as a \"bind pending\" - meaning that the email address of the identity is \n                \/\/ recorded so that the user can log into the account, but the rest of the details of the identity \n                \/\/ won't be filled in until first login.\n                <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(userIdentity == <\/span><span style=\"background: white;color: blue\">null<\/span><span style=\"background: white;color: black\">)\n                {\n                    <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Creating a new identity and adding it to the collection's licensed users group.\"<\/span><span style=\"background: white;color: black\">);\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ We are adding the user to a collection, and at the moment only one collection is supported per\n                    \/\/ account in VSTS.\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">collectionScope = identityClient.GetScopeAsync(<\/span><span style=\"background: white;color: #a31515\">VSSAccountName<\/span><span style=\"background: white;color: black\">).Result;\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ First get the descriptor for the licensed users group, which is a well known (built in) group.\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">licensedUsersGroupDescriptor = <\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">IdentityDescriptor<\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: #2b91af\">IdentityConstants<\/span><span style=\"background: white;color: black\">.TeamFoundationType, \n                                                                              <\/span><span style=\"background: white;color: #2b91af\">GroupWellKnownSidConstants<\/span><span style=\"background: white;color: black\">.LicensedUsersGroupSid);\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ Now convert that into the licensed users group descriptor into a collection scope identifier.\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">identifier = <\/span><span style=\"background: white;color: #2b91af\">String<\/span><span style=\"background: white;color: black\">.Concat(<\/span><span style=\"background: white;color: #2b91af\">SidIdentityHelper<\/span><span style=\"background: white;color: black\">.GetDomainSid(collectionScope.Id),\n                                                   <\/span><span style=\"background: white;color: #2b91af\">SidIdentityHelper<\/span><span style=\"background: white;color: black\">.WellKnownSidType,\n                                                   licensedUsersGroupDescriptor.Identifier.Substring(<\/span><span style=\"background: white;color: #2b91af\">SidIdentityHelper<\/span><span style=\"background: white;color: black\">.WellKnownSidPrefix.Length));\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ Here we take the string representation and create the strongly-type descriptor\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">collectionLicensedUsersGroupDescriptor = <\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">IdentityDescriptor<\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: #2b91af\">IdentityConstants<\/span><span style=\"background: white;color: black\">.TeamFoundationType, \n                                                                                        identifier);\n\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ Get the domain from the user that runs this code. This domain will then be used to construct\n                    \/\/ the bind-pending identity. The domain is either going to be \"Windows Live ID\" or the Azure \n                    \/\/ Active Directory (AAD) unique identifier, depending on whether the account is connected to\n                    \/\/ an AAD tenant. Then we'll format this as a UPN string.\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">currUserIdentity = vssConnection.AuthorizedIdentity.Descriptor;\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">directory = <\/span><span style=\"background: white;color: #a31515\">\"Windows Live ID\"<\/span><span style=\"background: white;color: black\">; <\/span><span style=\"background: white;color: green\">\/\/ default to an MSA (fka Live ID)\n                    <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(currUserIdentity.Identifier.Contains(<\/span><span style=\"background: white;color: #a31515\">'\\\\'<\/span><span style=\"background: white;color: black\">))\n                    {\n                        <\/span><span style=\"background: white;color: green\">\/\/ The identifier is domain\\userEmailAddress, which is used by AAD-backed accounts.\n                        \/\/ We'll extract the domain from the admin user.\n                        <\/span><span style=\"background: white;color: black\">directory = currUserIdentity.Identifier.Split(<\/span><span style=\"background: white;color: blue\">new char<\/span><span style=\"background: white;color: black\">[] { <\/span><span style=\"background: white;color: #a31515\">'\\\\' <\/span><span style=\"background: white;color: black\">})[0];\n                    }\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">upnIdentity = <\/span><span style=\"background: white;color: blue\">string<\/span><span style=\"background: white;color: black\">.Format(<\/span><span style=\"background: white;color: #a31515\">\"upn:{0}\\\\{1}\"<\/span><span style=\"background: white;color: black\">, directory, VssUserToAddMailAddress);\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ Next we'll create the identity descriptor for a new \"bind pending\" user identity.\n                    <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">newUserDesciptor = <\/span><span style=\"background: white;color: blue\">new <\/span><span style=\"background: white;color: #2b91af\">IdentityDescriptor<\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: #2b91af\">IdentityConstants<\/span><span style=\"background: white;color: black\">.BindPendingIdentityType, \n                                                                  upnIdentity);\n\n                    <\/span><span style=\"background: white;color: green\">\/\/ We are ready to actually create the \"bind pending\" identity entry. First we have to add the\n                    \/\/ identity to the collection's licensed users group. Then we'll retrieve the Identity object\n                    \/\/ for this newly-added user. Without being added to the licensed users group, the identity \n                    \/\/ can't exist in the account.\n                    <\/span><span style=\"background: white;color: blue\">bool <\/span><span style=\"background: white;color: black\">result = identityClient.AddMemberToGroupAsync(collectionLicensedUsersGroupDescriptor, \n                                                                       newUserDesciptor).Result;\n                    userIdentity = identityClient.ReadIdentitiesAsync(<\/span><span style=\"background: white;color: #2b91af\">IdentitySearchFilter<\/span><span style=\"background: white;color: black\">.AccountName, \n                                                                      VssUserToAddMailAddress).Result.FirstOrDefault();\n                }\n\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Assigning license to user.\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: blue\">var <\/span><span style=\"background: white;color: black\">entitlement = licensingClient.AssignEntitlementAsync(userIdentity.Id, VssLicense).Result;\n\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Success!\"<\/span><span style=\"background: white;color: black\">);\n            }\n            <\/span><span style=\"background: white;color: blue\">catch <\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: #2b91af\">Exception <\/span><span style=\"background: white;color: black\">e)\n            {\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"\\r\\nSomething went wrong...\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(e.Message);\n                <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(e.InnerException != <\/span><span style=\"background: white;color: blue\">null<\/span><span style=\"background: white;color: black\">)\n                {\n                    <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(e.InnerException.Message);\n                }\n            }\n        }\n\n        <\/span><span style=\"background: white;color: blue\">private static bool <\/span><span style=\"background: white;color: black\">Init(<\/span><span style=\"background: white;color: blue\">string<\/span><span style=\"background: white;color: black\">[] args)\n        {\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(args == <\/span><span style=\"background: white;color: blue\">null <\/span><span style=\"background: white;color: black\">|| args.Length &lt; 2)\n            {\n                <\/span><span style=\"background: white;color: blue\">return false<\/span><span style=\"background: white;color: black\">;\n            }\n\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: blue\">string<\/span><span style=\"background: white;color: black\">.IsNullOrWhiteSpace(args[0]))\n            {\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Error: Invalid accountName\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: blue\">return false<\/span><span style=\"background: white;color: black\">;\n            }\n\n            VssAccountName = args[0];\n\n            <\/span><span style=\"background: white;color: green\">\/\/ We need to talk to SPS in order to add a user and assign a license.\n            <\/span><span style=\"background: white;color: black\">VssAccountUrl = <\/span><span style=\"background: white;color: #a31515\">\"https:\/\/\" <\/span><span style=\"background: white;color: black\">+ VssAccountName + <\/span><span style=\"background: white;color: #a31515\">\".vssps.visualstudio.com\/\"<\/span><span style=\"background: white;color: black\">;\n\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(<\/span><span style=\"background: white;color: blue\">string<\/span><span style=\"background: white;color: black\">.IsNullOrWhiteSpace(args[1]))\n            {\n                <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Error: Invalid userEmailAddress\"<\/span><span style=\"background: white;color: black\">);\n                <\/span><span style=\"background: white;color: blue\">return false<\/span><span style=\"background: white;color: black\">;\n            }\n\n            VssUserToAddMailAddress = args[1];\n\n            VssLicense = <\/span><span style=\"background: white;color: #2b91af\">AccountLicense<\/span><span style=\"background: white;color: black\">.Express; <\/span><span style=\"background: white;color: green\">\/\/ default to Basic license\n            <\/span><span style=\"background: white;color: blue\">if <\/span><span style=\"background: white;color: black\">(args.Length == 3)\n            {\n                <\/span><span style=\"background: white;color: blue\">string <\/span><span style=\"background: white;color: black\">license = args[2].ToLowerInvariant();\n                <\/span><span style=\"background: white;color: blue\">switch <\/span><span style=\"background: white;color: black\">(license)\n                {\n                    <\/span><span style=\"background: white;color: blue\">case <\/span><span style=\"background: white;color: #a31515\">\"basic\"<\/span><span style=\"background: white;color: black\">:\n                        VssLicense = <\/span><span style=\"background: white;color: #2b91af\">AccountLicense<\/span><span style=\"background: white;color: black\">.Express;\n                        <\/span><span style=\"background: white;color: blue\">break<\/span><span style=\"background: white;color: black\">;\n                    <\/span><span style=\"background: white;color: blue\">case <\/span><span style=\"background: white;color: #a31515\">\"professional\"<\/span><span style=\"background: white;color: black\">:\n                        VssLicense = <\/span><span style=\"background: white;color: #2b91af\">AccountLicense<\/span><span style=\"background: white;color: black\">.Professional;\n                        <\/span><span style=\"background: white;color: blue\">break<\/span><span style=\"background: white;color: black\">;\n                    <\/span><span style=\"background: white;color: blue\">case <\/span><span style=\"background: white;color: #a31515\">\"advanced\"<\/span><span style=\"background: white;color: black\">:\n                        VssLicense = <\/span><span style=\"background: white;color: #2b91af\">AccountLicense<\/span><span style=\"background: white;color: black\">.Advanced;\n                        <\/span><span style=\"background: white;color: blue\">break<\/span><span style=\"background: white;color: black\">;\n                    <\/span><span style=\"background: white;color: blue\">case <\/span><span style=\"background: white;color: #a31515\">\"msdn\"<\/span><span style=\"background: white;color: black\">:\n                        <\/span><span style=\"background: white;color: green\">\/\/ When the user logs in, the system will determine the actual MSDN benefits for the user.\n                        <\/span><span style=\"background: white;color: black\">VssLicense = <\/span><span style=\"background: white;color: #2b91af\">MsdnLicense<\/span><span style=\"background: white;color: black\">.Eligible;\n                        <\/span><span style=\"background: white;color: blue\">break<\/span><span style=\"background: white;color: black\">;\n                    <\/span><span style=\"background: white;color: green\">\/\/ Uncomment the code for Stakeholder if you are using VS 2013 Update 4 or newer.\n                    \/\/case \"Stakeholder\":\n                    \/\/    VssLicense = AccountLicense.Stakeholder;\n                    \/\/    break;\n                    <\/span><span style=\"background: white;color: blue\">default<\/span><span style=\"background: white;color: black\">:\n                        <\/span><span style=\"background: white;color: #2b91af\">Console<\/span><span style=\"background: white;color: black\">.WriteLine(<\/span><span style=\"background: white;color: #a31515\">\"Error: License must be Basic, Professional, Advanced, or MSDN\"<\/span><span style=\"background: white;color: black\">);\n                        <\/span><span style=\"background: white;color: green\">\/\/Console.WriteLine(\"Error: License must be Stakeholder, Basic, Professional, Advanced, or MSDN\");\n                        <\/span><span style=\"background: white;color: blue\">return false<\/span><span style=\"background: white;color: black\">;\n                }\n            }\n\n            <\/span><span style=\"background: white;color: blue\">return true<\/span><span style=\"background: white;color: black\">;\n        }\n\n        <\/span><span style=\"background: white;color: blue\">public static string <\/span><span style=\"background: white;color: black\">VssAccountUrl { <\/span><span style=\"background: white;color: blue\">get<\/span><span style=\"background: white;color: black\">; <\/span><span style=\"background: white;color: blue\">set<\/span><span style=\"background: white;color: black\">; }\n        <\/span><span style=\"background: white;color: blue\">public static string <\/span><span style=\"background: white;color: black\">VssAccountName { <\/span><span style=\"background: white;color: blue\">get<\/span><span style=\"background: white;color: black\">; <\/span><span style=\"background: white;color: blue\">set<\/span><span style=\"background: white;color: black\">; }\n        <\/span><span style=\"background: white;color: blue\">public static string <\/span><span style=\"background: white;color: black\">VssUserToAddMailAddress { <\/span><span style=\"background: white;color: blue\">get<\/span><span style=\"background: white;color: black\">; <\/span><span style=\"background: white;color: blue\">set<\/span><span style=\"background: white;color: black\">; }\n        <\/span><span style=\"background: white;color: blue\">public static <\/span><span style=\"background: white;color: #2b91af\">License <\/span><span style=\"background: white;color: black\">VssLicense { <\/span><span style=\"background: white;color: blue\">get<\/span><span style=\"background: white;color: black\">; <\/span><span style=\"background: white;color: blue\">set<\/span><span style=\"background: white;color: black\">; }\n    }\n}\n<\/span><\/pre>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/10\/56\/31\/02\/AddUserApp.zip\">AddUserApp.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>NOTE (Sept. 16, 2016): I need to update the solution file to work with VS 2015 and test the code again. A while back someone asked a question about how to use the API to add licensed users to a VSTS account and change licenses for existing users (instead of the web UI to invite [&hellip;]<\/p>\n","protected":false},"author":94,"featured_media":10268,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[20],"class_list":["post-9908","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-visual-studio-team-services"],"acf":[],"blog_post_summary":"<p>NOTE (Sept. 16, 2016): I need to update the solution file to work with VS 2015 and test the code again. A while back someone asked a question about how to use the API to add licensed users to a VSTS account and change licenses for existing users (instead of the web UI to invite [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/posts\/9908","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/users\/94"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/comments?post=9908"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/posts\/9908\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/media\/10268"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/media?parent=9908"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/categories?post=9908"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/buckh\/wp-json\/wp\/v2\/tags?post=9908"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}