A complete, screenshot-driven walkthrough: from signing in to Azure and creating your organization, to provisioning projects, repos, and pipelines in Azure DevOps.

Steps in this guide

130 steps in eleven parts. The numbering is continuous and no step has been reordered — the parts only mark where one subject hands over to the next.

More steps will be added as screenshots are provided…

βœ… Before you start

Part 1 of 11

Account, portal and the Function App resource

Steps 1–9 · signing in with the Microsoft account, the verification codes, the portal tour, and creating the Function App resource.

1

Sign in to Microsoft Azure and pick your account

Part 1 · Account & portal

Everything starts from the Azure portal, since your Azure DevOps organization will be tied to the Microsoft identity (and, optionally, the subscription) you sign in with.

  1. Open a browser and go to https://portal.azure.com. The Microsoft sign-in page opens.
  2. On the “Pick an account” screen, click the account you want to use β€” in this example, veeduriabg@gmail.com (highlighted in red below).
  3. If your account is not listed, click Use another account and enter your credentials.
Microsoft Azure sign-in page showing the 'Pick an account' dialog with the account veeduriabg@gmail.com highlighted

Step 1 β€” Pick the Microsoft account to continue to the Azure portal.

πŸ’‘ Tip: Use the same account consistently throughout this guide. The Azure DevOps organization you create later will be created under the directory (tenant) of this sign-in.
2

Verify your identity β€” send a code to your email

Part 1 · Account & portal

Because you are signing in with a personal Microsoft account (such as one registered with a Gmail address), Microsoft asks you to prove your identity with a one-time verification code instead of a classic password prompt.

  1. After selecting your account, the “Get a code to sign in” screen appears, confirming the email the code will be sent to β€” in this example, veeduriabg@gmail.com.
  2. Click the Send code button (highlighted in red below).
  3. Microsoft emails a short one-time code to that address.
  4. On the next screen, open your email inbox, copy the code, paste/enter it into the verification field, and continue.
Microsoft 'Get a code to sign in' screen offering to email a verification code to veeduriabg@gmail.com, with the Send code button highlighted

Step 2 β€” Click "Send code" to receive a one-time sign-in code by email.

πŸ’‘ Tip: The code expires after a few minutes. If it doesn't arrive within a minute or two, check your spam/junk folder, then use the resend option on the verification screen.
3

Retrieve the single-use code from your inbox

Part 1 · Account & portal

Microsoft has emailed the verification code. Now switch to your email provider β€” in this example, Gmail β€” to fetch it. Since the sign-in used a Gmail address, the code arrives in the Gmail inbox.

  1. Open a new browser tab and go to https://mail.google.com, signed in with the same account you used for Azure (veeduriabg@gmail.com).
  2. In the inbox, locate the email from “Microsoft account team” with the subject “Your single-use code” (highlighted in red below) β€” it usually appears within seconds.
  3. Open it and copy the code shown in the body β€” in this example, 514944.
  4. Return to the Azure sign-in tab, enter the code in the verification field, and continue.
  5. If the email has not arrived after a minute or two, go back to the Azure tab, click resend and try again.
Gmail inbox showing the email from the Microsoft account team with subject 'Your single-use code' highlighted

Step 3 β€” In Gmail, open the "Your single-use code" email from the Microsoft account team.

πŸ’‘ Tip: The single-use code is valid for a limited time and works only once. Never share it with anyone β€” Microsoft will never ask you for it by phone or unsolicited email.
4

Open the email and copy the code

Part 1 · Account & portal

The email from the Microsoft account team (account-security-noreply@accountprotection.microsoft.com) contains the one-time code you need to finish signing in.

  1. Open the “Your single-use code” email.
  2. Find the line “Your single-use code is:” β€” in this example the code is 514944 (highlighted in red below).
  3. Copy the code and switch back to the Azure sign-in tab.
  4. Paste or type the code into the verification field and continue.
Opened Gmail email 'Your single-use code' from the Microsoft account team showing the code 514944 highlighted

Step 4 β€” The email body shows the single-use code to enter on the Azure sign-in screen.

πŸ’‘ Tip: As the email itself warns: only enter this code on an official website or app, and don't share it with anyone. If the code has expired by the time you enter it, simply request a new one from the Azure sign-in screen.
5

Confirm the “Stay signed in?” prompt

Part 1 · Account & portal

After the code is accepted, Microsoft asks whether to keep the session open on this device so you won't have to repeat the whole sign-in (and email verification) every time you visit the portal.

  1. On the “Stay signed in?” screen, click Yes (highlighted in red below) β€” recommended for your own development machine.
  2. Choose No instead if you are on a shared or public computer.
  3. You are then redirected to the Azure portal home dashboard.
Microsoft 'Stay signed in?' dialog for veeduriabg@gmail.com with the Yes button highlighted

Step 5 β€” Click "Yes" to skip repeated sign-ins on this device.

πŸ’‘ Tip: Choosing Yes keeps you authenticated on this browser profile, which saves a lot of time while following the rest of this guide. Just avoid it on shared machines.
6

Dismiss the Azure portal welcome tour

Part 1 · Account & portal

The first time you reach the portal, Azure offers a guided tour of the interface. You don't need it for this guide, so you can skip it and go straight to the dashboard.

  1. When the “Welcome to Microsoft Azure” dialog appears (“Let's show you around before you get started”), click Maybe later (highlighted in red below).
  2. Optionally click Start tour instead if you'd like Azure to walk you through the portal basics now β€” you can always reopen the tour later from the help menu.
  3. The dialog closes and the Azure portal home dashboard is fully visible.
Azure portal 'Welcome to Microsoft Azure' dialog with Start tour and Maybe later buttons, Maybe later highlighted

Step 6 β€” Click "Maybe later" to skip the guided tour and reach the dashboard.

πŸ’‘ Tip: The tour is purely informational β€” skipping it changes nothing in your subscription. You can launch it any time from the globe/help icon in the portal's top toolbar.
7

Create a resource β€” start the Azure Function setup

Part 1 · Account & portal

You are now on the Azure portal home (portal.azure.com/#home). Besides configuring Azure DevOps, this guide creates an Azure Function as the cloud resource that the DevOps pipeline will build and deploy to. Everything starts from the Create a resource shortcut, which opens the Azure Marketplace.

  1. If you don't have a subscription yet, the home screen offers options such as “Start with an Azure free trial” ($200 credit for 30 days plus 12 months of popular free services) or “Azure for Students”.
  2. Under Azure services, click Create a resource β€” the first tile with the + icon (highlighted in red below).
  3. The Create a resource page (Marketplace) opens; in the next steps you will search for Function App and configure it.
Azure portal home screen with the 'Create a resource' tile highlighted under Azure services

Step 7 β€” On the portal home, click "Create a resource" to begin creating the Azure Function.

πŸ’‘ Tip: You can reach the same page from anywhere in the portal: click + Create a resource in the top-left toolbar, or press G+/ from the global search bar.
8

Choose “Function App” from the Marketplace

Part 1 · Account & portal

The Create a resource page is Azure's Marketplace, with categories on the left (Compute, Databases, DevOps, Web, …) and popular services and products in the center. The service we need β€” a Function App β€” is among the most popular, so it appears right at the top.

  1. On the Create a resource page, locate Function App in the Popular Azure services list (highlighted in red below) β€” or type “Function App” into the Search services and marketplace box.
  2. Click Create on the Function App tile (or the tile itself).
  3. The Create Function App wizard opens, starting with the Basics tab that you will fill in next.
Azure 'Create a resource' Marketplace page with the Function App tile highlighted under Popular Azure services

Step 8 β€” Pick "Function App" (the lightning-bolt icon) from the Marketplace.

πŸ’‘ Tip: Don't confuse Function App with Web App β€” both are compute services shown near each other in the list. A Function App hosts event-driven, serverless code; a Web App hosts a continuously running website or API.
9

Activate an Azure free trial (no subscription yet)

Part 1 · Account & portal

Every resource in Azure β€” including a Function App β€” must live inside a subscription. If your account doesn't have one yet, Azure intercepts the Create a resource flow and shows the “Welcome to Azure! Don't have a subscription?” page with sign-up options.

  1. On the “Welcome to Azure!” page, choose “Start with an Azure free trial” (highlighted in red below) and click Start. It includes $200 of free credit for 30 days plus 12 months of popular free services.
  2. Complete the free-trial sign-up form Azure presents: confirm your profile, verify your identity by phone, and add a credit card (used to validate your identity β€” you won't be charged unless you explicitly upgrade).
  3. Alternative: if you are a student, pick “Azure for Students” instead β€” it grants free credit without requiring a credit card.
  4. Once the trial is active, return to Create a resource and continue with the Function App selection from the previous step.
Azure 'Welcome to Azure!' no-subscription page with the 'Start with an Azure free trial' option highlighted

Step 9 β€” No subscription yet? Start the Azure free trial to unlock resource creation.

πŸ’‘ Tip: The free trial is more than enough for this guide β€” a Function App on the Consumption plan falls within the free monthly grant (1 million executions). Set a spending alert in Cost Management anyway so you always know where you stand.
Part 2 of 11

Collaborators and the guest directory

Steps 10–20 · inviting an external user, accepting the invitation from the guest side, and confirming the guest in the directory.

10

Open Users to invite collaborators as contributors

Part 2 · Collaborators

From the primary Azure account (the one that owns the subscription), the other members of the team will be invited to participate as contributors. The Users shortcut on the portal home takes you straight to user management in Microsoft Entra ID.

  1. On the Azure portal home, under Azure services, click the Users tile (highlighted in red below).
  2. The Users | All users page opens, listing every identity in your directory.
  3. From here you will invite the remaining team members (next step): add each user and then grant them the Contributor role so they can work on the subscription's resources β€” for example the Function App and its resource group.
Azure portal home with the Users tile highlighted under Azure services, and recent resources listed below

Step 10 β€” Click "Users" to manage directory users and start inviting contributors.

πŸ’‘ Tip: Contributor can create and manage everything under the granted scope, but cannot change who else has access β€” that requires Owner. Grant the role at the narrowest scope that makes sense (e.g., the project's resource group rather than the whole subscription).
11

Create or invite the new user

Part 2 · Collaborators

The Users | All users page (default directory) lists every identity in your tenant. Here you add the teammates who will collaborate on the project. The list shows both Member accounts and Guest accounts added by invitation β€” in the example below, a guest user added via invitation and the member Manuela CortΓ©s Granados.

  1. On Users | All users, click + New user (highlighted in red below).
  2. Pick how to add the person:
    • Create new user β€” generates a corporate identity in your directory (id @<domain>.onmicrosoft.com with an auto-issued password).
    • Invite external user β€” sends an email invitation; ideal for personal accounts such as Gmail-registered Microsoft accounts. The person must redeem the invitation to gain access, and appears with User type: Guest.
  3. Fill in the identity basics (display name and email / user principal name) and click Review + create (or Invite for guests).
  4. Once the user exists in the directory, grant them the Contributor role (covered in the next step).
Microsoft Entra ID 'Users | All users' page with the '+ New user' button highlighted and two users listed

Step 11 β€” Click "+ New user" to add the next collaborator to the directory.

πŸ’‘ Tip: Members authenticate with directory-managed credentials; guests keep using their own account (e.g., their Gmail-backed Microsoft account). For a study or demo project like this one, inviting external users as guests is usually the simplest path.
12

Choose “Invite external user”

Part 2 · Collaborators

The + New user button is actually a split button: clicking its arrow reveals the two ways of adding someone. Since the collaborators join with their own personal Microsoft accounts (Gmail-registered), the invitation route is the one to use.

  1. Open the + New user dropdown (highlighted in red below).
  2. Select “Invite external user” β€” “Invite an external user to collaborate with your organization” β€” rather than “Create new user”, which would mint an internal account (@<domain>.onmicrosoft.com) instead.
  3. The Invite external user form opens: enter the person's email address, a display name, and an optional welcome message.
  4. Review the assignment options shown on the form (you can add group memberships here), then click Review + invite to send the invitation.
  5. The guest receives an acceptance email; once they accept, they appear in the users list with User type: Guest.
New user dropdown open with the 'Invite external user' option highlighted

Step 12 β€” Select "Invite external user" so collaborators can join with their own accounts.

πŸ’‘ Tip: Invited guests keep signing in with their personal credentials β€” no new password to manage. If someone's invitation email never arrives, you can copy the direct redemption link shown after sending and share it with them yourself.
13

Fill in and send the invitation

Part 2 · Collaborators

The Invite external user wizard has four tabs β€” Basics, Properties, Assignments, and Review + invite. Only the Basics tab is required to get the guest into the directory.

  1. On the Basics tab, under Identity, enter the collaborator's email in Email β€” here veeduriabg@gmail.com. The Display name auto-fills from the address; adjust it to the person's real name if you prefer (both fields are highlighted in red below).
  2. Optionally type a short Message so the invitation doesn't feel like spam, and add a Cc recipient if someone else should get a copy. Leave Send invite message checked.
  3. Leave the Invite redirect URL at its default (myapplications.microsoft.com) β€” it's where the guest lands after accepting.
  4. Click Review + invite (highlighted at the bottom) to send the invitation right away. Alternatively, use Next: Properties to add job details first, or the Assignments tab to add the guest to groups or roles during the invite.
  5. The guest receives an email titled with your organization's invitation β€” they must accept it (redeem it) to become active.
Invite external user form with Email and Display name filled in as veeduriabg@gmail.com, and the Review + invite button highlighted

Step 13 β€” Enter the guest's email, keep the defaults, and click "Review + invite".

πŸ’‘ Tip: You don't have to assign the Contributor role during the invite β€” Assignments is optional. The next steps cover granting the role explicitly at the subscription scope, which is the more transparent way.
14

Review the summary and send the invite

Part 2 · Collaborators

The Review + invite tab condenses everything the guest will receive into one summary. Take a few seconds to verify it β€” once sent, the invitation email is on its way.

  1. Open the Review + invite tab (highlighted at the top).
  2. Check the summary: Email and Display name β€” veeduriabg@gmail.com in this example; Send invite message: Yes; the redirect URL; and under Properties, User type: Guest. The Assignments section (Groups, Roles) is empty because none were selected.
  3. Click Invite (highlighted at the bottom-left).
  4. A confirmation notification appears and the invitation email is sent. The guest must open it and accept β€” from that moment they show up in Users | All users as a Guest.
Invite external user 'Review + invite' tab showing the guest summary with the Invite button highlighted

Step 14 β€” Verify the guest summary and click "Invite" to send the email invitation.

πŸ’‘ Tip: If the guest says the email never arrived, open their profile under Users | All users and use the Resend invitation action β€” it regenerates the redemption link without creating a duplicate account.
15

Confirmation β€” “User invitation in progress”

Part 2 · Collaborators

As soon as you click Invite, the portal reports the result in the notification panel at the top-right of the toolbar.

  1. Look at the top-right corner of the portal β€” the notification (highlighted in red below) reads “User invitation in progress for user veeduriabg@gmail.com”.
  2. Within a few seconds it updates to “Successfully invited user” β€” the invitation email has been sent and the guest account now exists in the directory.
  3. The collaborator can now accept the invitation from their inbox; once accepted, they appear as an active guest in Users | All users.
Azure portal notification 'User invitation in progress for user veeduriabg@gmail.com' highlighted

Step 15 β€” The portal confirms the invitation is being processed for the guest user.

πŸ’‘ Tip: Clicking the notification bell (the 1 badge next to it) opens the full notification history, useful to double-check that every teammate's invitation went through before moving on to role assignment.
16

Guest side β€” the invitation email arrives in Gmail

Part 2 · Collaborators

Now switching seats: everything so far was done by the subscription owner. From here, the steps are performed by the invited collaborator, starting in their own inbox.

  1. Sign in to Gmail with the invited account and look in the inbox for a message from “Microsoft Invitations” (highlighted in red below).
  2. The subject reads “Directorio predeterminado invited you to access applications within their organization” β€” the directory name your teammate entered on the invite.
  3. Note the security line in the preview: “Please only act on this email if you trust the organization represented below”. Verify you recognize the sender organization before doing anything else.
  4. Open the email and click Accept invitation β€” the redemption flow is covered in the next step.
Gmail inbox showing the Microsoft Invitations email 'Directorio predeterminado invited you to access applications within their organization' highlighted

Step 16 β€” In the guest's Gmail, open the Microsoft Invitations email.

πŸ’‘ Tip: If the message isn't in the inbox, check Updates or spam β€” Microsoft invitation emails are occasionally auto-filtered on Gmail.
17

Open the email and accept the invitation

Part 2 · Collaborators

The invitation email contains everything needed to verify who is inviting you and where you will land after accepting β€” worth a quick check before clicking anything.

  1. Open the invitation email. Verify the sender: “Microsoft Invitations on behalf of Directorio predeterminado” (invites@microsoft.com) and the red warning banner about acting only on invitations you trust.
  2. Confirm the details in the body: Organization: Directorio predeterminado and Domain: manuelacortesgranadosgmail.onmicrosoft.com β€” your teammate's Azure directory.
  3. Click Accept invitation (highlighted in red below).
  4. The browser opens the redemption page at myapplications.microsoft.com, where you sign in with the invited Microsoft account and confirm access β€” covered next.
Opened invitation email from Microsoft Invitations on behalf of Directorio predeterminado with the Accept invitation link highlighted

Step 17 β€” Verify organization and domain, then click "Accept invitation".

πŸ’‘ Tip: The Domain line is the fastest authenticity check: it must match the tenant that invited you. If it references a domain you don't recognize, don't accept β€” report the email as phishing instead.
18

Guest accepts the requested permissions

Part 2 · Collaborators

After clicking Accept invitation and signing in with the invited Microsoft account, Microsoft shows the consent screen that formalizes the relationship between the guest and the organization's directory.

  1. Review the “Permissions requested by” screen: the organization is Directorio predeterminado (manuelacortesgranadosgmail.onmicrosoft.com).
  2. By accepting, you allow the organization to:
    • Receive your profile data.
    • Collect and log your activity.
    • Use your profile data and activity data.
  3. Click Accept (highlighted in red below) to grant the permissions and complete the redemption. Cancel aborts the process and the guest account stays inactive.
  4. You are redirected to myapplications.microsoft.com (My Apps), now appearing as a guest of the organization. From the owner's side, the user now shows as an active Guest in Users | All users.
Microsoft consent screen 'Permissions requested by Directorio predeterminado' with the Accept button highlighted

Step 18 β€” Review the permissions and click "Accept" to finish redeeming the invitation.

πŸ’‘ Tip: These are standard consent permissions for any guest invite (profile and activity data), not access to your files or mail. Guests can later review and revoke consent at myaccount.microsoft.com under Organizations.
19

Guest lands on My Apps (myapplications.microsoft.com)

Part 2 · Collaborators

This is the redirect destination promised by the invitation email β€” the guest's proof that the redemption worked. The My Apps portal is the personal launchpad a user gets for every organization they belong to.

  1. After clicking Accept on the permissions screen, the browser lands on myapplications.microsoft.com β€” the Apps dashboard.
  2. “There are no apps to show” is the expected result: the guest hasn't been assigned any enterprise applications yet, and none are needed for this project.
  3. The important part is invisible: the invited Microsoft account is now linked to the Directorio predeterminado directory as a Guest.
  4. The guest can simply close this tab. The rest of the work β€” granting the Contributor role β€” happens on the owner's account, as shown from step 24 on.
My Apps dashboard at myapplications.microsoft.com showing 'There are no apps to show'

Step 19 β€” The empty My Apps dashboard confirms the guest redemption completed.

πŸ’‘ Tip: If the guest ever needs to switch between their personal context and this organization, the account icon (top-right) lists every directory they belong to.
20

Verify the guest appears in the users list

Part 2 · Collaborators

Back on the owner's account (check the top-right corner: the session is signed in as manuelacortesgranados… in Directorio predeterminado), refresh the directory to confirm the invitation landed.

  1. Reopen Users | All users (breadcrumb highlighted at the top-left).
  2. The list now reports 3 users found: the member Manuela CortΓ©s Granados, the earlier guest Aura Cristina GarzΓ³n Rodriguez, and β€” highlighted in red β€” the newly invited veeduriabg@gmail.com with User type: Guest and Creation type: Invitation.
  3. Seeing the guest listed means the invitation was redeemed successfully β€” no pending acceptance remains.
  4. Optionally click the user's name to open their profile (status, contact info, resend/reset actions live there).
  5. The directory is ready; next comes granting the Contributor role on the subscription.
Users | All users page showing 3 users with the newly invited guest veeduriabg@gmail.com highlighted

Step 20 β€” The invited guest now appears in Users | All users as "Guest / Invitation".

πŸ’‘ Tip: If a guest shows up but with a Pending acceptance state, they haven't redeemed the invitation yet β€” open their profile and use Resend invitation, or share the redemption link directly.
Part 3 of 11

Subscription access and creating the app

Steps 21–32 · subscriptions, IAM role assignments, switching accounts, and creating the Function App as a Contributor.

21

Open Subscriptions from the portal home

Part 3 · Subscription access

The Contributor role will be granted at the subscription level, so the first move is to reach your subscription. The portal home offers a direct shortcut to it.

  1. On the Azure portal home, under Azure services, click the Subscriptions tile β€” the yellow key icon (highlighted in red below).
  2. You can also reach it from the Resources list below, where Azure subscription 1 appears under Recent.
  3. The Subscriptions page opens, listing your subscription β€” click Azure subscription 1 to open it; the Access control (IAM) step follows.
Azure portal home with the Subscriptions tile highlighted under Azure services and recent resources listed

Step 21 β€” Click the "Subscriptions" tile to reach Azure subscription 1.

πŸ’‘ Tip: Subscriptions are billing and access boundaries β€” everything created in this guide (resource group, Function App) lives inside Azure subscription 1, which is why roles are assigned at that level.
22

Open Azure subscription 1

Part 3 · Subscription access

The Subscriptions page lists every subscription you can manage, with its health at a glance β€” role, cost, and status. One click takes you inside.

  1. On the Subscriptions page, locate Azure subscription 1 (highlighted in red below) β€” the only subscription in this directory.
  2. Check the row's details before opening it: Status: Active, My role: Owner, Current cost: $0.00, and the subscription ID 0e9f2fce-a0ea-4f4d-9efa-f7113d99c023.
  3. Click the subscription name to open its Overview blade.
  4. The next step works from this blade's Access control (IAM) menu, where the Contributor role will be granted to the invited guest.
Subscriptions page with Azure subscription 1 highlighted, showing Owner role, Active status and $0.00 current cost

Step 22 β€” Open "Azure subscription 1" (Active, you are Owner) to manage its access.

πŸ’‘ Tip: Only Owners and User Access Administrators can assign roles. If your My role column shows Reader or Contributor instead, ask an owner to either elevate your access or perform the assignment for you.
23

Open Access control (IAM) from the subscription Overview

Part 3 · Subscription access

Inside Azure subscription 1, the Overview blade shows the essentials (ID, directory, status, your role, plan) plus cost and usage charts. The menu on the left is where access management lives.

  1. On the subscription's Overview blade, look at the left navigation menu.
  2. Click Access control (IAM) β€” highlighted in red with the arrow annotation below, right under Activity log.
  3. The IAM blade opens, defaulted to the Check access view. The Role assignments tab β€” covered in the next step β€” is where you'll see and add role assignments.
Azure subscription 1 Overview blade with Access control (IAM) highlighted in the left menu

Step 23 β€” Click "Access control (IAM)" in the subscription's left menu.

πŸ’‘ Tip: IAM stands for Identity and Access Management β€” Azure RBAC's control center. The same blade exists at every scope (management group, resource group, single resource), so the same flow applies if you ever want to grant access to just one resource group.
24

Open Access control (IAM) → Role assignments on the subscription

Part 3 · Subscription access

With the guest active in the directory, it's time to grant them permissions over the subscription's resources. Back on the owner's account, Azure RBAC (role-based access control) is managed from the subscription's Access control (IAM) blade.

  1. From the portal, go to Subscriptions and open Azure subscription 1.
  2. In the left menu, click Access control (IAM) (highlighted in red below).
  3. Select the Role assignments tab (highlighted, with the arrow annotation) β€” this lists everyone who currently has a role on the subscription.
  4. To grant the Contributor role, click Add role assignment in the “Grant access to this resource” card β€” or the + Add button at the top of the blade. The assignment wizard is covered in the next steps.
Azure subscription 1 Access control (IAM) page with the Role assignments tab highlighted and the Add role assignment card visible

Step 24 β€” On the subscription, open Access control (IAM) and go to the Role assignments tab.

πŸ’‘ Tip: The orange banner about classic administrator roles being retired (August 31, 2024) only matters for old Co-Administrator/Service Administrator setups β€” always use Azure RBAC roles like Contributor, as this guide does.
25

Start the assignment β€” Add → Add role assignment

Part 3 · Subscription access

On the Role assignments tab you can review who already has access β€” the list below shows Owner (2) and Contributor (2) entries at the This resource scope, with 5 of the 4,000 allowed assignments in use. To add a new one, the + Add button in the toolbar opens the correct entry point.

  1. On the Role assignments tab, open the + Add dropdown in the toolbar (highlighted in red below).
  2. Choose Add role assignment β€” the RBAC route.
  3. Avoid the other two options: Add co-administrator belongs to the retired classic administrator model, and Add custom role is for defining brand-new role definitions, not assigning existing ones.
  4. The Add role assignment wizard opens with its four tabs β€” Role, Members, Conditions, and Review + assign β€” configured in the following steps.
IAM Role assignments tab with the + Add dropdown open and 'Add role assignment' highlighted

Step 25 β€” Toolbar "+ Add" β†’ "Add role assignment" starts the wizard.

πŸ’‘ Tip: Azure allows up to 4,000 role assignments per subscription. Assigning roles to groups instead of individual users counts as one assignment and scales much better as the team grows.
26

Sign out to switch accounts

Part 3 · Subscription access

This guide switches between two identities β€” the guest (veeduriabg@gmail.com) and the subscription owner (Manuela CortΓ©s Granados). The account menu in the portal's top-right corner is how you move between them. Here, the signed-in guest signs out so the owner can continue the Add role assignment flow.

  1. Click your avatar / account name in the portal's top-right corner. The menu shows the current identity β€” veeduriabg@gmail.com under Directorio predeterminado β€” with View account and Switch organization links.
  2. Click Sign out (highlighted in red below) to end the session completely.
  3. To change identity without a full sign-out, use “Sign in with a different account” at the bottom of the same menu β€” handy when both accounts are yours.
  4. Sign back in as the subscription owner (Manuela CortΓ©s Granados) and return to Access control (IAM) to continue the role assignment wizard.
Azure portal account menu for veeduriabg@gmail.com with the Sign out option highlighted

Step 26 β€” The account menu: "Sign out" ends the session; "Sign in with a different account" switches identity.

πŸ’‘ Tip: Both accounts belong to the same directory, so Switch organization is unnecessary here β€” that option is for users who belong to several tenants and need to hop between them.
27

As the Contributor: choose Function App again

Part 3 · Subscription access

Now the collaborator's turn. With the Contributor role granted, the guest (veeduriabg@gmail.com β€” check the top-right corner) can create and manage resources on their own. The Marketplace flow is the same one from steps 8 and 9, but this time it succeeds under the guest's own credentials.

  1. Signed in as veeduriabg@gmail.com, open Create a resource from the portal home.
  2. In Popular Azure services, click the Function App tile (highlighted in red below) β€” or search for it β€” and then Create.
  3. Thanks to the Contributor assignment, the wizard will let you pick the subscription (Azure subscription 1) and complete creation β€” without it, the Review + create validation would fail with a “does not have permission” error.
  4. Fill in the wizard's tabs as before: Basics (resource group, name, runtime stack, region, plan), then Hosting, Networking, Monitoring, and finally Review + create.
Create a resource Marketplace page signed in as veeduriabg@gmail.com with the Function App tile highlighted

Step 27 β€” The collaborator (Contributor) selects Function App to create it themselves.

πŸ’‘ Tip: A Contributor can do almost everything with resources, but cannot modify access (that's Owner/User Access Administrator territory) β€” the reason the role-assignment wizard had to be run by Manuela.
28

Select the hosting option β€” Flex Consumption

Part 3 · Subscription access

The Create Function App wizard starts by asking how the app will be hosted β€” this choice determines how it scales, what resources each instance gets, and how it is billed. The page lays the five options side by side in a comparison table.

  1. Review the comparison table: Scale to zero, Scale behavior, Virtual networking, Dedicated compute / cold start, and Max scale out (instances).
  2. Select Flex Consumption (highlighted in red below) β€” high scalability with compute choices, virtual networking support, pay-as-you-go billing, scale to zero, fast event-driven scaling, and up to 1,000 instances.
  3. The alternatives, for reference: Consumption (the classic pay-per-execution plan, up to 200 instances), Functions Premium (pre-warmed instances, no cold start), App Service (runs web and function apps on a dedicated plan), and Container Apps environment (containerized microservices with KEDA).
  4. Click Select (highlighted at the bottom-left) to confirm and move on to the Basics tab.
Create Function App hosting option comparison with Flex Consumption selected and the Select button highlighted

Step 28 β€” Pick "Flex Consumption" and click Select to continue.

πŸ’‘ Tip: For a learning or low-traffic project, Flex Consumption and classic Consumption are effectively free at small volumes (monthly free grant). Flex is the newer generation and the portal's default β€” a safe choice for this guide.
29

Fill in the Basics tab

Part 3 · Subscription access

The Basics tab gathers the identity of the app: where it lives (subscription and resource group), what it is called, where it runs, and on what technology. The fields highlighted in red below are the ones touched in this example.

  1. Project Details: confirm Subscription: Azure subscription 1 and set Resource Group: AzureResourceGroup1 (or click Create new for a fresh group).
  2. Instance Details: set the Function App name β€” here FunctionAppGrupoLoyal. It must be globally unique: it becomes functionappgrupoloyal.azurewebsites.net. The green check means the name is available.
  3. Choose the Region: Canada Central β€” normally the region closest to your users.
  4. Pick the technology: Runtime stack: .NET with Version: 8 (LTS), isolated worker model, and Instance size: 2048 MB.
  5. Click Review + create (highlighted at the bottom-left) to jump straight to validation β€” or walk the remaining tabs (Storage, Networking, Monitoring, Deployment, Authentication, Tags) with Next if you need non-default settings.
Create Function App Basics tab with subscription, resource group, name FunctionAppGrupoLoyal, runtime stack .NET and instance size highlighted

Step 29 β€” Basics: subscription, resource group, unique name, region, .NET 8 runtime, instance size.

πŸ’‘ Tip: The isolated worker model for .NET 8 runs your code in a separate process β€” it's the model Microsoft recommends for .NET 8 and the only one that will support future .NET versions in Functions.
30

Review the summary and click Create

Part 3 · Subscription access

The Review + create tab runs a final validation and condenses the whole configuration into one summary β€” the last chance to catch a typo before resources are actually provisioned (and before anything can bill).

  1. Open the Review + create tab (highlighted at the top). The blue banner about basic authentication being disabled is informational β€” modern deployments use Microsoft Entra credentials instead.
  2. Check the Details block: subscription 0e9f2fce-…, resource group AzureResourceGroup1, name FunctionAppGrupoLoyal, runtime .NET 8 (LTS), isolated worker model, instance size 2048 MB.
  3. Check the Hosting block β€” Azure will create these automatically: storage account azureresourcegroup1a3fd (required by Functions) and the Flex Consumption plan ASP-AzureResourceGroup1-9c29 (Linux, Canada Central), plus Monitoring (New) β€” the Application Insights resource.
  4. Optionally click Download a template for automation to export the equivalent ARM/Bicep template β€” handy for Infrastructure as Code later.
  5. Click Create (highlighted at the bottom-left). This fires the template deployment confirmed by the notification in the next step.
Review + create summary for FunctionAppGrupoLoyal with the Create button highlighted and an arrow pointing at it

Step 30 β€” Validate the summary and click "Create" to provision everything.

πŸ’‘ Tip: Note what gets created for you: one Function App, one storage account, one hosting plan, and one Application Insights instance. Keeping them in the same resource group (AzureResourceGroup1) means you can delete β€” or redeploy β€” the whole project as a unit.
31

Confirmation β€” “Initializing deployment” of the Function App

Part 3 · Subscription access

After the Function App wizard's final Review + create validation passes and Create is clicked, Azure submits an ARM template deployment behind the scenes β€” the portal reports it immediately in the notification panel.

  1. Look at the top-right notification (highlighted in red below): “Initializing deployment…” β€” Initializing template deployment to resource group ‘AzureResourceGroup1’.
  2. This means the Function App and its dependencies (storage account, Application Insights, hosting plan) are being rolled out as a single template deployment into AzureResourceGroup1.
  3. After a minute or two the notification changes to “Deployment is in progress” and finally “Your deployment is complete”, with a Go to resource shortcut.
  4. To follow the rollout in detail, open the resource group (AzureResourceGroup1) and check Deployments β€” or click the notification itself, which links to the deployment's progress page.
Azure portal notification 'Initializing deployment… β€” Initializing template deployment to resource group AzureResourceGroup1'

Step 31 β€” The template deployment to AzureResourceGroup1 has started.

πŸ’‘ Tip: A Function App deployment typically completes in 2–4 minutes. Navigating away doesn't cancel it β€” but keeping the notification open makes it easy to jump straight to the finished resource via Go to resource.
32

Deployment complete β€” go to the resource

Part 3 · Subscription access

A couple of minutes after Create, the deployment's own Overview page confirms the result with a green check: “Your deployment is complete”. This page is reachable by clicking the completion notification, or under the resource group's Deployments blade.

  1. Read the deployment header: name Microsoft.Web-FunctionApp-Portal-d7487089-9723, subscription Azure subscription 1, resource group AzureResourceGroup1, plus the start time and the Correlation ID (useful when opening a support ticket).
  2. Expand Deployment details to see every resource the template provisioned β€” the Function App, its hosting plan, the storage account, and Application Insights.
  3. Click Go to resource (highlighted in red below) under Next steps β€” it opens the new Function App's overview page.
  4. The left menu here (Inputs, Outputs, Template) stays available: the Template view shows the exact ARM JSON that was deployed, ideal for reusing as Infrastructure as Code.
Deployment Overview page showing 'Your deployment is complete' with the Go to resource button highlighted

Step 32 β€” "Your deployment is complete" β€” click "Go to resource" to open the Function App.

πŸ’‘ Tip: Bookmark the deployment's Template export (or the Download button in the toolbar): it's the fastest way to turn a portal-created resource into a repeatable Bicep/ARM deployment for future environments.
Part 4 of 11

Organization, project and repository

Steps 33–42 · the DevOps organization, the first project, the empty repository and its clone URL.

33

Azure DevOps sign-up β€” complete your profile details

Part 4 · Organization & repo

With the Azure side ready, it's time for the star of this guide: Azure DevOps. Browse to https://dev.azure.com and sign in with your Microsoft account β€” the very first time, DevOps asks for a few profile details before creating your organization.

  1. In a browser, open https://dev.azure.com and sign in with the Microsoft account (here, veeduriabg@gmail.com β€” note the account name in the top-right bar).
  2. On the “We need a few more details” screen, confirm your name β€” Veeduria Ciudadana Informatica y Buen Gobierno β€” and the contact email, both pre-filled from your Microsoft account.
  3. Select your country/region in the From dropdown (required).
  4. Optionally untick the box about receiving information, tips, and resources about Microsoft developer tools if you don't want marketing email.
  5. Click Continue (highlighted in red below). By continuing you accept the Terms of Service, Privacy Statement, and Code of Conduct.
  6. The next screen lets you create (or join) an Azure DevOps organization β€” covered in the following steps.
Azure DevOps 'We need a few more details' screen with name, contact email, country selector and the Continue button highlighted

Step 33 β€” Fill in the DevOps profile details and click Continue.

πŸ’‘ Tip: Azure DevOps is free for individuals: the Basic plan includes up to 5 free users with unlimited private Git repos and pipelines with a free monthly grant β€” no credit card required for this part of the guide.
34

Click “Create new organization”

Part 4 · Organization & repo

With the profile completed, the Get started with Azure DevOps landing page appears. An organization is the top-level container in Azure DevOps: it groups your projects, repos, pipelines, and boards, and is billed (or made free) as a unit.

  1. Review the profile panel on the left β€” Veeduria Ciudadana Informatica y Buen Gobierno, veeduriabg@gmail.com, account type Microsoft account, country Colombia β€” and use Edit profile if anything needs correcting.
  2. Click Create new organization (highlighted in red below) on the right side.
  3. The creation form opens, asking for an organization name and hosting region β€” configured in the next step.
Get started with Azure DevOps page with the Create new organization button highlighted

Step 34 β€” Click "Create new organization" to set up your DevOps organization.

πŸ’‘ Tip: The Visual Studio Dev Essentials panel at the bottom is free to join and bundles extra benefits (learning content, monthly Azure credits) that pair nicely with this project.
35

Confirm the account and Continue

Part 4 · Organization & repo

A compact confirmation dialog appears before the organization is actually provisioned β€” it binds the new organization to a specific Microsoft account and directory.

  1. Verify the identity shown at the top: veeduriabg@gmail.com. The new organization will be created under this account.
  2. Optionally click Switch directory to host the organization in a different Microsoft Entra tenant (for example, the work directory instead of the personal one) β€” for this guide, the default directory is fine.
  3. Untick the information/offers checkbox if you prefer no marketing email.
  4. Click Continue (highlighted in red below). This accepts the Terms of Service, Privacy Statement, and Code of Conduct, and provisions the organization with a default name β€” you can rename it right after.
Azure DevOps 'Get started' confirmation dialog with the Continue button highlighted

Step 35 β€” Confirm the identity and click Continue to provision the organization.

πŸ’‘ Tip: Azure DevOps generates a default organization name from your email and region. You can rename it in seconds (Organization settings β†’ Overview) β€” but choose something readable early, because it appears in every URL: dev.azure.com/<org>.
36

Name the organization and complete the CAPTCHA

Part 4 · Organization & repo

The “Almost done…” screen collects the three final inputs for the organization: its name, its hosting region, and a human-verification code.

  1. Set Name your Azure DevOps organization β€” here veeduriaibg, which becomes the permanent URL dev.azure.com/veeduriaibg. Use letters, digits, and hyphens only.
  2. Choose where projects will be hosted in the We'll host your projects in dropdown β€” this example selects Europe. (Pick the region closest to your team; for Colombia, a US region is usually lower-latency.)
  3. Type the characters you see in the CAPTCHA image β€” in the example, the code 6SLRSJ has been entered.
  4. Click Continue (highlighted in red below). The organization is provisioned and you land in it, ready to create the first project.
Azure DevOps 'Almost done' screen with organization name veeduriaibg, Europe region, CAPTCHA entered and Continue highlighted

Step 36 β€” Organization name, hosting region, CAPTCHA… then Continue.

πŸ’‘ Tip: The organization name is the only part of the URL that's hard to change later, while the hosting region is fixed at creation. Decide both with the final team size and location in mind β€” renaming an org is possible but breaks every cloned repo URL and service hook.
37

Provisioning β€” “Taking you to your organization…”

Part 4 · Organization & repo

After Continue, Azure DevOps provisions the organization behind the scenes. This loading screen is the only thing between you and your brand-new DevOps home.

  1. Wait a few seconds β€” no action is required while the spinner runs and the message “Taking you to your Azure DevOps organization…” is displayed.
  2. The redirect ends inside your organization, at dev.azure.com/veeduriaibg, showing the “Create a project to get started” page.
  3. If the screen hangs for more than a minute, refresh the page β€” the organization is usually already created and the session simply needs to resume.
Azure DevOps loading screen 'Taking you to your Azure DevOps organization...' with a spinner

Step 37 β€” Brief provisioning spinner on the way into the new organization.

πŸ’‘ Tip: From this point on, your organization's URL (dev.azure.com/veeduriaibg) is the single address your whole team needs β€” projects, repos, and pipelines all hang off it.
38

Create the first project

Part 4 · Organization & repo

Inside the new organization, the first thing Azure DevOps asks for is a project β€” the container for your boards, repositories, pipelines, and test plans. This minimal form only needs a name and a visibility choice.

  1. Type the Project name β€” in this example ProyectoVeeduriaBG001 (highlighted in red below).
  2. Choose the Visibility: Private is selected here, so only the people you grant access to can view the project. Public is disabled for this organization β€” it can be turned on later via organization policies, with the caveat that some features (like TFVC) are not supported for public projects.
  3. The streamlined form applies DevOps defaults for everything else β€” Git version control and the Agile work item process β€” both changeable later in project settings.
  4. Click + Create project (highlighted in red, arrow annotation). After a few seconds the project's Summary page opens, with Boards, Repos, Pipelines, and Test Plans ready in the left menu.
Create a project screen with name ProyectoVeeduriaBG001, Private visibility selected and the Create project button highlighted

Step 38 β€” Name the project, keep it Private, and click "+ Create project".

πŸ’‘ Tip: Choose project names without spaces or special characters β€” the name becomes part of clone URLs, REST API paths, and pipeline identifiers. Renaming later is possible, but every old reference has to catch up.
39

Provisioning β€” “New project is being created…”

Part 4 · Organization & repo

Right after clicking + Create project, the form grays out and a spinner appears. Behind the scenes, DevOps is scaffolding the project's default team, board, Git repository, and service connections.

  1. Wait a few seconds while the highlighted banner shows “New project is being created…” β€” the name field and visibility choice stay locked during this step.
  2. When provisioning finishes, the banner is replaced by the project's Summary page β€” the project home with its Wiki and dashboard shortcuts.
  3. If creation seems stuck, a page refresh usually resolves it; the project either exists or the form returns with an error explaining what to fix (most commonly, a duplicate project name).
Create a project screen showing the 'New project is being created...' spinner highlighted

Step 39 β€” The brief provisioning spinner right after "+ Create project".

πŸ’‘ Tip: Provisioning also creates a default team named after the project (with its own backlog and dashboard) β€” you can add more teams later in Project settings, one team per work stream.
40

Project created β€” open Repos

Part 4 · Organization & repo

Provisioning finished β€” the Summary page greets you with “Welcome to the project!” and shortcut buttons for every service. The breadcrumb confirms the full hierarchy: veeduriaibg / ProyectoVeeduriaBG001 / Summary.

  1. Take note of the left navigation β€” this is the hub you'll live in: Boards (work items, sprints), Repos (source control), Pipelines (CI/CD), Test Plans, and Artifacts (package feeds), plus Overview/Dashboards/Wiki above them.
  2. Click Repos in the left menu (highlighted in red with the arrow annotation below) β€” or the Repos shortcut button in the welcome panel.
  3. Repos opens the project's empty Git repository, offering to clone it, import existing code, or initialize it with a README β€” the next step of the guide.
Project Summary 'Welcome to the project' page with Repos highlighted in the left navigation

Step 40 β€” The project is live; open Repos to start with source control.

πŸ’‘ Tip: Every service is also reachable from the horizonal switcher at the very top of the page β€” useful once you work across several projects in the same organization.
41

Empty repository β€” clone URL and Generate Git Credentials

Part 4 · Organization & repo

Repos → Files opens on the project's default Git repository, but nothing has been committed yet, so DevOps greets you with “ProyectoVeeduriaBG001 is empty. Add some code!”. The page is a menu of ways to put code in: clone it, push an existing local repo, import one, or initialize it with a first file.

  1. Check where you are: the breadcrumb reads veeduriaibg / ProyectoVeeduriaBG001 / Repos / Files, and the repo picker (with the red repo icon) points at ProyectoVeeduriaBG001 β€” the only repository in the project for now. The + next to the project name creates additional repos later.
  2. Under Clone to your computer, pick HTTPS (selected) or SSH and copy the URL: https://veeduriaibg@dev.azure.com/veeduriaibg/ProyectoVeeduriaBG001/_git/ProyectoVeeduriaBG001 β€” it is the address you paste into git clone, Visual Studio, VS Code, or any other Git client.
  3. Click Generate Git Credentials (highlighted in red with the arrow annotation below) when you need an explicit username/password pair for HTTPS instead of relying on the credential manager. DevOps shows the generated credentials once β€” copy them immediately.
  4. The dropdown beside Clone in VS Code offers the same one-click clone for other editors and IDEs, and the “Having problems authenticating?” link underneath points to the Git for Windows and IDE plugin downloads.
  5. If the code already exists on your machine, use the Push an existing repository from command line block: it hands you the finished git remote add origin command with this repository's URL already filled in.
  6. Import a repository migrates an existing repo from another host (GitHub, Bitbucket, another DevOps organization) without touching your local clone.
  7. The last panel, Initialize main branch with a README or gitignore, is the fastest way to get a non-empty repo: Add a README is already ticked, .gitignore is set to None, and clicking Initialize commits that first file and creates the main branch.
Empty Azure DevOps repository page with the Clone URL, Generate Git Credentials button highlighted in red, and the push, import and initialize panels

Step 41 β€” The empty repo page: clone URL, Generate Git Credentials (highlighted), push, import, and initialize options.

πŸ’‘ Tip: Decide now between Initialize and push existing code β€” the two do not mix well. If you initialize with a README and later push a local repo that has its own history, Git rejects the push as unrelated histories and you end up force-pushing or merging with --allow-unrelated-histories.
42

Copy the clone URL, then read the generated Git credentials

Part 4 · Organization & repo

This step is the practical half of the clone block: taking the URL out of the page and getting a working password for it. After Generate Git Credentials is clicked, an inline Username / Password panel unfolds right below the clone URL β€” and it is the only time those values are ever displayed.

  1. The first red box is the whole Clone to your computer area: HTTPS is the selected tab (SSH sits beside it), and the field next to it holds the full clone URL ending in /_git/ProyectoVeeduriaBG001.
  2. Click the copy icon at the right edge of the URL field (second red box) to put the whole URL on the clipboard β€” typing it by hand is how mismatched characters sneak into remote configurations.
  3. Click Generate Git Credentials. The third red box appears: Username already filled with veeduriaibg β€” the same identity embedded before the @ in the clone URL.
  4. The Password field holds a long randomly generated secret (it starts Atg3n… in this example). It is the password Git must be given when it prompts for HTTPS authentication β€” not your Microsoft account password.
  5. Copy the password immediately using its own copy icon, before touching anything else. Read the warning banner first: “Copy the password now. We don't store it and you won't be able to see it again.” Once the panel closes or the page reloads, the value is gone for good β€” the only recovery is generating a new one.
  6. Use the pair on the first git clone, git push, or git pull: paste the username, then the password at the prompt. On Windows, Git Credential Manager stores it after that first successful authentication, so you stop being asked.
  7. If the password is lost, leaked, or rejected later, come back to this same button and generate a fresh pair β€” nothing else in the repository changes.
Clone to your computer block with the copy icon highlighted, and the generated Username and Password panel with the one-time copy warning

Step 42 β€” Copy the URL, generate the credentials, and save the password before the panel closes.

πŸ’‘ Tip: These Git credentials are personal and broad β€” good for a developer machine, poor for scripts and pipelines. For anything automated, create a Personal Access Token (User settings → Personal access tokens) instead: it carries an expiry date and a narrow scope such as Code (Read & Write), so it can be revoked without touching your account.
Part 5 of 11

SourceTree: install and first clone

Steps 43–60 · installing SourceTree, setting the commit author, and cloning with generated Git credentials.

43

Find the SourceTree download page on Google

Part 5 · SourceTree

The repository exists and the credentials are in hand, but there is still no Git client on the machine. This guide does the rest of the work with SourceTree, a free Git GUI for Windows and Mac, instead of typing every command by hand. Step one is getting to the official site without landing on a mirror or an ad.

  1. In Google, search for sourcetree download and press Enter. The query is reflected in the address bar (google.com/search?q=sourcetree+download&…) β€” the long rlz/gs_lcrp parameters after it are ordinary tracking noise and can be ignored.
  2. Look at the first organic result. Above the title, Google shows the site name SourceTree with the globe icon and the domain https://www.sourcetreeapp.com. That domain check is the whole point of this step: sourcetreeapp.com is Atlassian's official site.
  3. Click the blue title “Sourcetree | Free Git GUI for Mac and Windows” (outlined in red with the arrow annotation) to open the site.
  4. The snippet underneath confirms what the tool is: “A Git GUI that offers a visual representation of your repositories. Sourcetree is a free Git client for Windows and Mac.” β€” so the same download covers both platforms used later in this guide.
  5. The two sitelinks below, Learn more and Download SourceTree, lead into the same site; clicking the main title and then using the download button on the page is the most predictable route.
Google search results for 'sourcetree download' with the official sourcetreeapp.com result highlighted in red

Step 43 β€” The official SourceTree result; confirm the domain before clicking through.

πŸ’‘ Tip: Read the domain, not the title β€” search results for developer tools are a favourite target for copycat download pages and bundled installers. Stick to sourcetreeapp.com (Atlassian) and, on first launch, expect the app to ask for a free Atlassian account before it lets you add repositories.
44

Click Download for Windows on sourcetreeapp.com

Part 5 · SourceTree

The official landing page loads in one screen β€” no account needed to download. Everything relevant is in the top band: a Download free button in the header, and the hero section with the platform-specific button this step clicks.

  1. Confirm the address bar shows sourcetreeapp.com over HTTPS β€” the same domain verified in the previous step.
  2. The dark blue Download free button at the top right of the header is the global shortcut; it leads to the same installer as the hero button below.
  3. Read the headline β€” “Simplicity and power in a beautiful Git GUI” β€” then click the big blue Download for Windows button (outlined in red with the arrow annotation). This downloads the Windows installer; nothing is installed yet.
  4. On a Mac the same page detects the platform and offers the macOS build instead; the Also available for Mac OS X link underneath the button is the manual way to reach it.
  5. Latest release notes: Mac OS X & Windows opens the changelog for both platforms β€” useful to check which version the installer will be before running it.
  6. The screenshot on the right is a preview of the window you will work in shortly: the toolbar (Commit, Pull, Push, Branch, Merge, Stash, Terminal, Settings), the sidebar (Workspace, Branches, Bookmarks, Tags, Remotes, Shelved, Subrepositories), and the log columns Graph, Commit, Author, Description, Date.
SourceTree landing page with the blue Download for Windows button highlighted in red

Step 44 β€” Download the Windows installer from the official SourceTree page.

πŸ’‘ Tip: The header button and the hero button fetch the same file β€” pick either. If the browser silently blocks the download, check the download icon in the address bar and allow the file from there rather than hunting for a mirror site.
45

Accept the license agreement to start the download

Part 5 · SourceTree

SourceTree's download button does not start a transfer directly β€” it opens a short legal gate first. An Important information dialog appears over the dimmed page, and the installer is only released once the agreement is ticked.

  1. Read the body of the dialog: “To continue downloading this product you must read and agree to the Atlassian Software License Agreement and Privacy Policy.” Both names are links to the full documents.
  2. Tick the checkbox “I agree to the Atlassian Software License Agreement and Privacy Policy.” β€” it is the only checkbox in the dialog, and the download does not proceed while it stays unticked.
  3. Click Download (outlined in red with the arrow annotation) to release the installer. The file then downloads through the browser like any other.
  4. To back out, use Cancel next to it or the × in the top right corner β€” both close the dialog without downloading anything, and the page underneath is unchanged.
Important information dialog with the Atlassian license agreement checkbox ticked and the Download button highlighted in red

Step 45 β€” Agree to the Atlassian terms, then click Download.

πŸ’‘ Tip: This dialog is per browser session β€” if the installer download is interrupted and you restart it later, expect the agreement prompt again. It is not a second account or a paid step: the terms cover the free client, not a subscription.
46

Locate SourceTreeSetup in the Downloads folder

Part 5 · SourceTree

Once the license gate is accepted, the browser drops the installer into the default Downloads folder. Before running anything, it is worth confirming the file is complete β€” a truncated download is the usual reason an installer refuses to start.

  1. Open File Explorer (or press Win + E) and go to Downloads in the left pane. The address bar confirms the location: This PC › Local Disk (C:) › Users › ACGR_GIT › Downloads.
  2. Find the freshly downloaded file: SourceTreeSetup-3.4.23.exe (outlined in red with the arrow annotation), sorted into the Today (1) group so it sits at the top.
  3. Sanity-check the columns before double-clicking β€” Type reads Application, Size is 25,054 KB (roughly 24.5 MB), and Date modified matches the moment of the download.
  4. The name also tells you the version being installed: 3.4.23. Worth noting, in case a later step or a support thread refers to a specific build.
  5. With the file selected, double-click it (or press Enter) to launch the installer β€” that is the next step.
Windows File Explorer Downloads folder with SourceTreeSetup-3.4.23.exe selected and outlined in red

Step 46 β€” The downloaded installer, verified by type, size and date.

πŸ’‘ Tip: A file still named something like .crdownload or .part, or one that is only a few hundred KB, is not a finished installer β€” let the browser complete the transfer instead of renaming it and forcing it to run.
47

Registration step β€” click Skip

Part 5 · SourceTree

The installer launches into a four-stage wizard, and the second stage asks for an Atlassian account. That requirement is about Bitbucket, not about Git β€” so against an Azure DevOps repository it can be skipped without losing any functionality used in this guide.

  1. Follow the progress rail on the left: Install is already ticked, Registration is the current stage, and Install tools and Preferences come after it.
  2. Read what the panel actually asks: “Sourcetree is a free product that requires a one-time registration using your Atlassian Bitbucket account. You can connect additional accounts such as Github, Gitlab, Visual Studio Team Services, etc. once logged in.”
  3. Note the two provider buttons β€” Bitbucket Server (self-hosted) and Bitbucket (Cloud). Neither matches this project, whose remote lives in Azure DevOps.
  4. Click Skip (outlined in red with the arrow annotation). SourceTree is a Git client for any remote; the Atlassian login only unlocks the Bitbucket integrations, and the Azure DevOps remote will be authenticated with the credentials generated in step 42.
  5. Optional: “Don't have a Bitbucket Cloud account? Create one for free” opens the sign-up page in the browser β€” not needed here, and it can be done later from Tools → Options → Authentication if Bitbucket ever enters the picture.
SourceTree installer Registration step with Bitbucket Server and Bitbucket buttons, and the Skip button highlighted in red

Step 47 β€” Skip the Bitbucket registration; the Azure DevOps remote does not need it.

πŸ’‘ Tip: Skipping is reversible. If a Bitbucket or Atlassian-hosted repository shows up later, add the account in Tools → Options → Authentication β€” no reinstall needed.
48

Install tools β€” keep Git checked

Part 5 · SourceTree

Third wizard stage. SourceTree is only a GUI shell β€” it needs an actual Git engine underneath, and Pick tools to download and install is where that engine is chosen.

  1. The rail now shows Install and Registration ticked, with Install tools active and only Preferences left after it.
  2. Git is already checked and reports 56.19 MB (outlined in red with the arrow annotation). Leave it checked: this is the Git for Windows download that the client will drive for every clone, commit, and push.
  3. Mercurial is unchecked at 7.42 MB. It is a different version-control system β€” the Azure DevOps project created in step 38 is Git, so there is nothing to check here.
  4. Advanced Options (the collapsed chevron) expands extra settings for the tools being installed β€” worth opening only if this machine must reuse an already-installed Git rather than the bundled one.
  5. Click Next in the bottom right corner to start the download and installation of the selected tools.
SourceTree installer 'Pick tools to download and install' step with Git checked and Mercurial unchecked

Step 48 β€” Ship the bundled Git with SourceTree; Mercurial stays unchecked.

πŸ’‘ Tip: SourceTree installs and uses its own Git build, so it works even when no Git is on the machine β€” and it keeps the version identical across teammates' machines. If you already have Git installed, the two coexist without conflict.
49

Progress β€” “Downloading version control systems…”

Part 5 · SourceTree

As soon as Next is clicked, the tool selection is replaced by a single progress panel. This is the ~56 MB Git bundle being fetched and unpacked, and there is nothing to choose on this screen.

  1. The heading switches to “Downloading version control systems…” and a blue progress bar fills from the left (outlined in red).
  2. Read the status line under the bar: it currently says “Extracting 'Git'” β€” so the transfer for Git has finished and the installer is unpacking it. Expect a download phase, then an extraction phase, then the actual installation.
  3. Next stays grayed out for the whole duration β€” the wizard is locked until the extraction completes, so there is no way to skip ahead.
  4. Wait. On a normal connection this takes a couple of minutes; the rail still shows Install tools as the active stage until it finishes.
  5. When the bar completes, the panel moves on to the Preferences stage automatically β€” no extra click is required here.
SourceTree installer progress panel 'Downloading version control systems...' with the bar partially filled and 'Extracting Git' below it

Step 49 β€” Git is downloaded and extracted; Next stays disabled until it ends.

πŸ’‘ Tip: If the bar sits motionless for several minutes, the blocker is almost always network-side β€” a corporate proxy or firewall refusing the CDN. Closing the wizard and re-running it from behind a working connection is faster than waiting it out.
50

“Tool installation completed.” β€” continue

Part 5 · SourceTree

The download and extraction finished cleanly. This screen is pure confirmation β€” a green check and a single line of text β€” so there is nothing to configure before moving on.

  1. The heading reads “Tool installation completed.” above a large green circle with a white checkmark: the Git bundle is now on disk.
  2. The rail confirms the same story β€” Install and Registration ticked, Install tools on its way out, and Preferences as the last remaining stage.
  3. Click Next (outlined in red with the arrow annotation) to move to the Preferences stage, where the wizard asks how SourceTree should present itself.
  4. Nothing is validated or entered here, so if the wizard was closed by accident at this point, simply launching SourceTree again is equivalent to having pressed Next.
SourceTree installer confirmation screen reading 'Tool installation completed.' with a green check icon and the Next button highlighted

Step 50 β€” Tools installed; click Next to reach the Preferences stage.

πŸ’‘ Tip: To verify later which Git SourceTree ended up with, open Tools → Options → Git in the installed app β€” the Git Version row shows the build carried by this step.
51

Preferences β€” set the commit author name and email

Part 5 · SourceTree

The last wizard stage, Preferences, asks for the two values Git stamps onto every commit. They are not cosmetic: they are the identity Azure DevOps reads when it attributes a push to a user.

  1. The rail is now fully ticked β€” Install, Registration, Install tools β€” with Preferences as the active stage.
  2. Fill the first field with the full name to appear as commit author. In this example: Veeduria Ciudadana IBG.
  3. Fill the second field with the email address β€” here veeduriaibg@gmail.com, the address highlighted with the blue focus outline because it is the field being edited.
  4. Make the email match the account you use in Azure DevOps. That is what lets the Pushes and Commits views show your name instead of an unlinked email address.
  5. Click Next (outlined in red with the arrow annotation) to close the wizard and start SourceTree for the first time.
SourceTree installer Preferences step with the full name 'Veeduria Ciudadana IBG' and the email veeduriaibg@gmail.com filled in

Step 51 β€” Name and email become the author of every commit you make.

πŸ’‘ Tip: Both values are written to Git's global config, so they apply to every repository on the machine. If a commit ever shows the wrong author, fix it in the app under Tools → Options → General β€” or from a terminal with git config --global user.name and user.email.
52

“Load SSH Key?” β€” choose No

Part 5 · SourceTree

The wizard's counterpart on the first real launch of SourceTree. It offers to wire up an SSH key pair immediately β€” relevant only if the repositories will be reached over SSH instead of HTTPS.

  1. The dialog Load SSH Key? opens with a key icon in the title bar and an information glyph next to the text.
  2. Read the offer: “Do you have an SSH key that you'd like to load now? If not you can click 'No' and create one later if you like.”
  3. Click No (outlined in red with the arrow annotation). The clone URL taken in step 41 is the HTTPS form, and its authentication comes from the generated username and password of step 42 β€” no key pair is involved.
  4. Yes is the default button and only makes sense if a private key already exists on the machine; it prompts for the key file so SourceTree can use it for ssh.dev.azure.com-style remotes.
  5. Declining is not final β€” keys and the SSH client can be attached later from Tools → Options → General, so nothing has to be decided now.
SourceTree dialog 'Load SSH Key?' asking whether to load an SSH key now, with the No button highlighted in red

Step 52 β€” No SSH key needed: this repository authenticates over HTTPS.

πŸ’‘ Tip: Pick one authentication path and stay on it. An HTTPS remote works with user/password credentials, an SSH remote works with keys β€” mixing the two in the same repo only produces authentication prompts that look like failures but are not.
53

SourceTree opens β€” click Clone

Part 5 · SourceTree

Installation is done and the client opens on an empty Local repositories tab. The toolbar at the top is the whole starting point of the workflow: it holds the three ways to bring a repository in.

  1. A tab named New tab is open, with a dropdown and a + beside it for additional tabs β€” one tab per repository is the usual arrangement later on.
  2. The Local view is selected (Remote sits next to it). The list is empty and the hint reads “Add a new bookmark or drag & drop repository folders into this area to begin”, with a Search box for when it is not.
  3. Distinguish the three toolbar actions before clicking: Clone downloads a remote repository into a new local folder, Add bookmarks a folder that is already a working copy on disk, and Create initializes a brand-new local repository.
  4. Click Clone (outlined in red with the arrow annotation) β€” the Azure DevOps repository created in step 38 has to come down to this machine, so Clone is the right entry point.
  5. The same command lives in the menu bar under File, in case the toolbar is hidden or the window is too narrow to show it.
SourceTree main window with an empty Local repositories list and the Clone toolbar button highlighted in red

Step 53 β€” Empty Local view; use Clone to pull the Azure DevOps repo down.

πŸ’‘ Tip: Clone and Add are the pair people mix up. Use Clone when the code is only on the server; use Add when the folder already exists locally, otherwise the clone creates a second copy next to the first.
54

Back to the browser β€” copy the clone URL

Part 5 · SourceTree

The Clone dialog in SourceTree will ask for a source path, so the browser tab with the Azure DevOps repository comes back into play. This step is just retrieving the value that goes into that field.

  1. Switch to the browser tab holding the repository. The address bar shows the repo-scoped URL β€” dev.azure.com/veeduriaibg/_git/ProyectoVeeduriaBG001 β€” a shortcut to the same Repos → Files page used earlier.
  2. In the Clone to your computer panel, the HTTPS URL field is boxed in red and the arrow carries the annotation “Copiar este valor” β€” copy this value. It is the address SourceTree needs.
  3. Click the copy icon at the right edge of the field (second red box). Note that the field itself shows the URL cut off at the right β€” the copy button takes the complete string, so never retype what is visible on screen.
  4. Look at the credential pane underneath: Username veeduriaibg and a Password are still on display. If this is a fresh generation, copy the password now as well β€” the clone will prompt for both.
  5. The project name in the page heading appears selected (blue highlight) simply because it was clicked; it is not the value to copy β€” the URL is.
Azure DevOps repository page with the HTTPS clone URL boxed and the copy icon highlighted, annotated 'Copiar este valor'

Step 54 β€” Copy the HTTPS clone URL for the SourceTree Clone dialog.

πŸ’‘ Tip: Copy the whole URL including the /_git/ProyectoVeeduriaBG001 tail. Pasting only the project URL (…/ProyectoVeeduriaBG001) makes the clone fail with a repository-not-found error even though the address looks right.
55

Credential helper selector β€” choose <no helper>

Part 5 · SourceTree

The moment the clone reaches for authentication, a small CredentialHelperSelector window appears β€” this dialog belongs to the Git for Windows that SourceTree installed, not to SourceTree itself. It decides whether the password gets cached on disk or asked for every time.

  1. Under Select a credential helper, four radio options are offered: <no helper>, wincred (listed twice) and manager.
  2. <no helper> is already selected (outlined in red). It means Git stores nothing and prompts for the username and password on each remote operation β€” the generated credentials from step 42 get re-entered when needed.
  3. The other choices persist credentials: wincred puts them in the Windows Credential Manager, and manager hands them to Git Credential Manager, which also handles multi-factor flows and token refresh.
  4. Leave the Always use checkbox ticked β€” the field beside it shows where the decision is written, a Git config file under C:\Users\ACGR_GIT\AppData\Local\Atlassian\SourceTree\…, so it applies to SourceTree's bundled Git rather than to a system-wide install.
  5. Click Select (outlined in red with the arrow annotation) to confirm. The clone then completes and the repository appears in the Local tab.
CredentialHelperSelector window with 'no helper' selected, the Always use checkbox ticked and the Select button highlighted

Step 55 β€” No helper: nothing is cached, the generated password is asked for on demand.

πŸ’‘ Tip: <no helper> is the strict choice and the right one on a shared or lab machine β€” credentials never touch disk. On a personal workstation, manager saves retyping the long generated password on every pull and push; switch later by re-running the clone or editing the Git config directly.
56

Copy the username and password into the Git prompt

Part 5 · SourceTree

With <no helper> chosen in the previous step, the clone stops and asks for credentials. Both values come from this panel, and each has its own copy button β€” using them is what keeps the long password intact.

  1. Bring the browser forward: the Clone to your computer panel still shows Username veeduriaibg and the generated Password, both boxed together in the larger red outline.
  2. Click the copy icon beside Username (first small red box) and paste it into the Username field of the Git prompt.
  3. Click the copy icon beside Password (second small red box) and paste it into the Password field. The string is longer than the visible width of the box β€” copy it, do not type it, and do not trust what the field appears to show.
  4. Confirm the prompt. The clone then starts transferring; an empty repository finishes almost instantly and the local copy appears in SourceTree.
  5. Remember the warning strip under the fields: “Copy the password now. We don't store it and you won't be able to see it again.” If the panel has already been dismissed, regenerate the pair with Generate Git Credentials before trying again.
Azure DevOps credentials panel with the Username and Password block boxed and both copy icons highlighted in red

Step 56 β€” Copy both values with their own buttons; the password cannot be recovered.

πŸ’‘ Tip: A password copied by hand regularly loses a character or picks up a trailing space, and the resulting failure looks exactly like wrong credentials. If authentication is rejected, regenerate the pair and paste again before suspecting the URL or permissions.
57

The Authenticate dialog β€” login to dev.azure.com

Part 5 · SourceTree

This is where the two copied values land. The dialog identifies the host it is challenging, which is a quick way to confirm the remote URL was pasted correctly before credentials are even sent.

  1. The title is Authenticate, and the line underneath reads “Login required for: dev.azure.com” β€” the correct host for this repository. Any other host here means the clone URL is wrong; cancel and fix it instead of typing credentials into the wrong prompt.
  2. Username is filled with veeduriaibg, the value copied from the DevOps panel in the previous step.
  3. Password shows a row of dots matching the pasted generated password, with the field focused (blue outline) β€” the point at which any missing character would already be baked in.
  4. Remember password is ticked, so the client keeps this pair and stops asking on every pull or push. Unticking it restores the prompt each time, which is the behaviour <no helper> alone would give.
  5. Click Login (outlined in red with the arrow annotation). On success the clone completes; if the dialog simply comes back, the credentials were rejected β€” regenerate them rather than retyping the same string.
SourceTree Authenticate dialog for dev.azure.com with username veeduriaibg, a masked password, Remember password ticked and Login highlighted

Step 57 β€” Confirm the host, paste username and password, tick Remember password, click Login.

πŸ’‘ Tip: This is not your Microsoft or Azure AD password. The Azure DevOps Git credential is a separate secret generated in step 42 β€” using the account password here is the single most common cause of a login loop at this screen.
58

Confirm the helper with Always use this from now on

Part 5 · SourceTree

The credential helper selector comes back once the login is attempted, and this time the whole window is visible β€” including the option that turns a one-off answer into a permanent setting. That checkbox is the actual subject of this step.

  1. The dialog is the same CredentialHelperSelector, opened over the SourceTree toolbar β€” the Clone, Add and Create buttons are still visible behind it, so the app is waiting on this answer.
  2. Confirm the radio list has not changed: <no helper> selected, then wincred twice, then manager.
  3. Read the checkbox label now that it is not cut off: “Always use this from now on”, ticked. Ticked, the choice is written to the Git config and this dialog never appears again for this installation.
  4. Unticked, the helper applies to the current operation only β€” acceptable when you want to test manager before committing to it, but it means the dialog returns at the next authentication.
  5. Click Select (outlined in red with the arrow annotation) to accept <no helper> permanently and return to the clone.
CredentialHelperSelector dialog with 'no helper' selected, 'Always use this from now on' ticked and the Select button highlighted

Step 58 β€” Ticking the checkbox makes the helper choice stick for every repo.

πŸ’‘ Tip: Always use is one-way in the UI β€” the dialog will not offer itself again. To reverse it, change the credential.helper entry in the Git config (or reinstall the tools); there is no "reset" button in SourceTree.
59

Fill the Clone form and start the clone

Part 5 · SourceTree

The Clone form takes over the tab β€” the toolbar entry stays underlined while it is open. Five fields decide what gets copied and where it lands; only two of them normally need any attention.

  1. Source Path receives the URL copied in step 54 β€” the field shows it truncated on the left, ending in …/veeduriaibg/ProyectoVeeduriaBG001/_git/ProyectoVeeduriaBG001. Browse next to it is for a local or network path, not for this case.
  2. Ignore the hint “Cloning is even easier if you set up a remote account”: it points at Bitbucket/GitHub account integration, and this remote authenticates with the DevOps credentials instead.
  3. Repository Type reads “This is a Git repository” with the red diamond icon. That line is a live validation β€” if the URL were malformed or pointed at a non-Git endpoint, it would say so instead.
  4. Destination Path holds K:\ProyectoVeeduriaBG001, the local folder the working copy is written to. This is the one field worth checking before continuing; Browse changes it.
  5. Name is filled automatically as ProyectoVeeduriaBG001 β€” that is the bookmark label listed in the Local tab afterwards.
  6. Local Folder stays on [Root], the top level of the sidebar tree. Advanced Options holds the rarely used settings (shallow or sparse checkouts) and can stay collapsed.
  7. Click Clone (outlined in red, annotated “Hacer click aqui para clonar el repositorio GIT”) to start the transfer.
SourceTree Clone form with the Azure DevOps URL, K drive destination path, repository type detected as Git and the Clone button highlighted

Step 59 β€” URL, destination and name confirmed; click Clone.

πŸ’‘ Tip: The destination folder must not already contain files β€” SourceTree refuses to clone into a non-empty directory. Cloning straight to a drive root (as with K:\ here) works, but keeping one parent folder per client or per project makes the repository easier to find later.
60

Clone progress strip β€” source, destination and bar

Part 5 · SourceTree

No dialog this time: the clone runs in a thin strip docked at the top of the main window, over the Clone form left open underneath. It is the only feedback SourceTree gives while the transfer happens.

  1. The line on the left spells out the whole operation: “Cloning from https://veeduriaibg@dev.azure.com/veeduriaibg/ProyectoVeeduriaBG001/_git/ProyectoVeeduriaBG001 to K:\ProyectoVeeduriaBG001”. Reading it is the fastest way to catch a wrong URL or destination without waiting for the job to finish.
  2. The horizontal bar tracks progress left to right β€” the grey span is what remains, the blue span is what has been transferred (here it is already in its final third).
  3. The small spinner at the right end of the bar indicates the process is alive; it disappears when the clone ends.
  4. Show Full Output, unticked, expands a console pane with the raw Git output β€” tick it if the bar stalls, since the real error message appears there rather than in the strip.
  5. Cancel aborts the transfer. Once it completes, the strip disappears and SourceTree opens the cloned repository.
SourceTree clone progress strip reading 'Cloning from ... to K:\ProyectoVeeduriaBG001' with a partially filled bar, Show Full Output checkbox and Cancel button

Step 60 β€” The clone in progress: source, destination and transfer bar.

πŸ’‘ Tip: An empty repository is only a few kilobytes, so this strip can flash past in under a second. If it lingers with no movement instead, the credential prompt is usually waiting behind the main window β€” bring SourceTree to the front before assuming the clone failed.
Part 6 of 11

Service connection from DevOps to Azure

Steps 61–74 · the Azure Resource Manager connection, the App Registration error, and the Entra role that resolves it.

61

Back in DevOps β€” open Project settings

Part 6 · Service connection

The browser comes back into play: with the repository cloned locally, the remaining configuration lives on the project's administration pages. This step lands on the project home and walks to the gear that opens them.

  1. The address bar shows the project root β€” dev.azure.com/veeduriaibg/ProyectoVeeduriaBG001 β€” and the breadcrumb confirms veeduriaibg / ProyectoVeeduriaBG001 / Overview / Summary: the same Summary page seen at the end of provisioning in step 40.
  2. Do not be misled by the welcome panel β€” “Welcome to the project! What service would you like to start with?” β€” and its service buttons (Boards, Repos, Pipelines, Test Plans, Artifacts). None of them is needed yet.
  3. Check the left navigation is fully expanded: Overview (Summary, Dashboards, Wiki), Boards, Repos, Pipelines, Test Plans, Artifacts.
  4. At the very bottom left sits Project settings with a gear icon (outlined in red with the arrow annotation), next to the double-chevron that collapses the navigation.
  5. Click Project settings to open the project's own administration area β€” the place for repositories, policies, teams, and pipeline defaults.
Azure DevOps project Overview with the full left navigation and the Project settings gear highlighted in red at the bottom

Step 61 β€” Project Overview; Project settings sits at the bottom of the left rail.

πŸ’‘ Tip: Two gears exist and they are easy to confuse β€” Project settings here (bottom left, inside the project) and Organization settings (the gear on the organization landing page). Repository and pipeline options live in the first; users, billing and extensions in the second.
62

Project Settings β€” open Service connections

Part 6 · Service connection

Project settings opens as a two-pane hub: a flat tree of every administrative page on the left, the selected page on the right. The breadcrumb changes to Settings, and the URL gains the /_settings/adminservices segment.

  1. Learn the tree structure first β€” settings are grouped, not listed flat: General (Overview, Teams, Permissions, Notifications, Service hooks, Dashboards), Boards (Project configuration, Team configuration, GitHub connections), Pipelines (Agent pools, Parallel jobs, Settings), Test management, Repos (Repositories), Artifacts (Storage).
  2. The Pipelines group is outlined in red to place the target inside it: Service connections sits at the end of that group, after Agent pools, Parallel jobs and Settings, just above XAML build services.
  3. Click Service connections (second red outline, arrow pointing to it).
  4. The right pane is the empty state: “Create your first service connection”, with the explanation “Service connections help you manage, protect, and reuse authentications to external services.”
  5. Bookmark the blue Create service connection button β€” it is the entry point for the next step.
Azure DevOps Project Settings page with the Pipelines group and Service connections item highlighted in red, and the empty 'Create your first service connection' pane on the right

Step 62 β€” Service connections under Pipelines in Project Settings, currently empty.

πŸ’‘ Tip: Service connections are scoped to the project, not the organization β€” a connection created here is invisible from another project and has to be recreated (or granted explicitly) there. That is why the empty state reads “your first” every time a new project is set up.
63

New service connection β€” pick Azure Resource Manager

Part 6 · Service connection

Create service connection opens a type picker rather than a form. The choice made here decides which fields appear next, so it is worth understanding what each family of entries is for before clicking through.

  1. The dialog New service connection opens with the instruction “Choose a service or connection type” and a Search connection types box directly under it.
  2. The list is long and scrolls β€” Azure Repos/Team Foundation Server, Azure Service Bus, Bitbucket Cloud, Cargo, Chef, Docker Host, Docker Registry, Generic, GitHub, GitHub Enterprise Server, Incoming WebHook, Jenkins, Jira, Kubernetes and more below the fold. Typing in the search box beats scrolling.
  3. Select Azure Resource Manager (radio filled, outlined in red with the arrow annotation). This is the connection type that lets pipelines act on an Azure subscription β€” the one holding the Function App built earlier in this guide.
  4. Note the neighbouring options for contrast: Azure Repos/Team Foundation Server links another DevOps server to this organization, while Generic covers any endpoint that is not on the list (URL plus token).
  5. Click Next at the bottom right to move to the authentication form for the selected type. Learn more on the left opens the documentation for service connections.
New service connection dialog listing connection types with Azure Resource Manager selected and highlighted in red

Step 63 β€” Azure Resource Manager is the type used to deploy into the Azure subscription.

πŸ’‘ Tip: Picking the wrong type is not fatal but it is not switchable either β€” a connection's type is fixed once created, so the only fix is deleting it and starting over. Confirm the family before pressing Next.
64

Sign in with the Azure account in the Microsoft popup

Part 6 · Service connection

Next in the Azure Resource Manager flow hands authentication to Microsoft Entra ID: a separate browser window opens on login.microsoftonline.com with an OAuth authorization request. This is the account the connection will act as.

  1. Recognise the window β€” the title bar reads “Sign in to your account — Google Chrome”, so it is a popup, not a tab in the DevOps window. It must stay open until the flow ends.
  2. Read the address while it lasts: login.microsoftonline.com/…/oauth2/authorize?client_id=499b84ac-… β€” an OAuth authorization request where the client_id is the application asking for access, i.e. Azure DevOps.
  3. Type the account that owns the subscription into the field β€” here veeduriaibg@gmail.com (outlined in red), the same identity used from step 1 onward. The autocomplete list behind it is just saved-account suggestions.
  4. Click Next (second red outline) to move to the password and verification pages.
  5. Alternative routes are listed underneath and are not needed here: Sign in with GitHub, Sign-in options (passkey, security key, other methods), and Can’t access your account? for recovery.
Microsoft sign-in popup with the account veeduriaibg@gmail.com entered and the Next button highlighted in red

Step 64 β€” The Microsoft Entra sign-in that grants the service connection its identity.

πŸ’‘ Tip: Sign in with the identity that actually holds the role assignment. A different account can complete this flow successfully and still fail every deployment with an authorization error, because the connection ends up with permissions nobody granted it.
65

“Get a code to sign in” β€” send the verification code

Part 6 · Service connection

Entering the account is not enough for this flow. Because the request grants access to Azure resources, Microsoft inserts an identity check before the consent screen β€” a one-time code delivered by email.

  1. The popup switches to “Get a code to sign in”, with the Microsoft logo above and the account being verified in the small chip: veeduriaibg@gmail.com.
  2. Confirm the chip shows the right mailbox. The explanation below it reads “Because you're accessing sensitive info, we'll send a code to veeduriaibg@gmail.com to verify your identity.”
  3. Click Send code (outlined in red with the arrow annotation). That triggers an email carrying a single-use code.
  4. Switch to the inbox in another tab β€” this is the same pattern already used in steps 2–4 for the original Azure sign-in: the code arrives by mail, is copied, and is pasted back into this window.
  5. Return to the popup and paste the code to continue to the permission consent screen for the service connection.
Microsoft 'Get a code to sign in' screen for veeduriaibg@gmail.com with the Send code button highlighted in red

Step 65 β€” Request the one-time code; it arrives by email.

πŸ’‘ Tip: The message can take a minute, and codes expire quickly β€” request it only when you are ready to paste. If it never arrives, check the spam folder before pressing Send code again, since each request invalidates the previous code.
66

Read the single-use code in the email

Part 6 · Service connection

The message lands in Gmail within seconds, and it is deliberately short β€” a greeting, one sentence of context, and the code. Nothing to click, nowhere to navigate.

  1. Check the sender before reading anything else: Microsoft account team <account-security-noreply@accountprotection.microsoft.com>. The accountprotection.microsoft.com domain is the legitimate sender for these codes.
  2. The greeting names the account that requested it β€” “Hi veeduriaibg@gmail.com” β€” followed by “We received your request for a single-use code to use with your Microsoft account.”
  3. The code sits on its own line: “Your single-use code is:” followed by six digits (highlighted with the red outline). Select exactly those characters.
  4. Return to the sign-in popup and paste it into the code field. This mirrors the retrieval flow from steps 3–4 at the very beginning of the guide.
  5. Leave the email open until the sign-in completes β€” if the flow times out, a new code is needed and the old one becomes invalid.
Gmail message from the Microsoft account team containing a single-use verification code highlighted in red

Step 66 β€” The six-digit code, sent from accountprotection.microsoft.com.

πŸ’‘ Tip: A genuine code email never asks you to open a link or hand the code to anyone. If a message urges you to “confirm here” or forwards the digits elsewhere, treat it as phishing regardless of how close the sender name looks.
67

Fill the Azure service connection form

Part 6 · Service connection

Once the Microsoft sign-in returns, the popup becomes a form titled New Azure service connection with Azure Resource Manager as its subtitle. Every field decides how much of the subscription the pipeline will be allowed to touch, so the order β€” identity, then scope, then target, then name β€” matters.

  1. Identity type: App registration (automatic), marked Recommended. DevOps creates the Entra ID app registration behind the scenes instead of asking for one you would have to prepare in the Azure portal. Need help choosing a connection type? opens the comparison doc.
  2. Credential: Workload identity federation, also Recommended. It uses federated trust instead of a secret, so there is no client secret to store, rotate, or leak.
  3. Scope level: Subscription is selected, which is the usual choice; Management Group widens the reach to everything under a management group, and Machine Learning Workspace is a narrow special case.
  4. Subscription: pick Azure subscription 1 β€” the ID beginning 0e9f2fce-… shown after the name. This is the subscription that holds the Function App. If it is missing from the list, the note below the field points at the manual fallback.
  5. Resource group: AzureResourceGroup1. Choosing a group here scopes the connection down to that group only β€” deployments to any other group will be rejected even though the subscription matches.
  6. Service Connection Name: ServiceConnectionName1. This is the identifier pipelines reference, so keep it short and rename-proof.
  7. Service Management Reference and Description are optional; the first only matters when the organization requires a reference on Entra app registrations.
  8. Security: Grant access permission to all pipelines is ticked. Leave it that way unless you intend to authorize each pipeline by hand.
  9. Click Save (outlined in red with the arrow annotation) β€” Back returns to the type picker, and Learn more opens the docs.
New Azure service connection form with App registration identity, workload identity federation credential, subscription and resource group selected, name filled and the Save button highlighted

Step 67 β€” Identity, scope, subscription, resource group, name, then Save.

πŸ’‘ Tip: The service connection name is referenced verbatim in pipeline YAML (azureSubscription: ServiceConnectionName1). Renaming it later silently breaks every pipeline that names the old one, so decide the final name here rather than after the first deploy.
68

Error β€” “App Registration failed” (insufficient privileges)

Part 6 · Service connection

Saving does not always succeed. With the automatic identity type, DevOps has to create an application registration in Microsoft Entra ID on your behalf β€” and that is a directory permission, unrelated to anything granted on the Azure subscription.

  1. After Save, a warning banner with a triangle icon replaces nothing on the form but blocks the creation. It closes with the × on its right.
  2. Read the full text: “App Registration failed: Failed to create an app in Microsoft Entra. Error: Insufficient privileges to complete the operation in Microsoft Graph. Ensure that the user has permissions to create a Microsoft Entra Application.”
  3. Diagnose the cause: creating an app registration requires a directory role in Entra ID (typically Application Administrator, Cloud Application Administrator or Application Developer) or the tenant setting that lets users register applications. An account that only holds Contributor on the subscription β€” as added in step 25 β€” has none of that.
  4. The banner suggests its own workaround: “Please create a service connection using managed identity for federation” β€” relevant when the pipeline runs on a self-hosted agent that can carry a managed identity.
  5. The practical alternatives are to have a tenant administrator create the app registration for you and then switch Identity type to the manual option, or to have your account granted the directory rights and retry the same form.
Azure DevOps warning banner reading 'App Registration failed: Insufficient privileges to complete the operation in Microsoft Graph'

Step 68 β€” The automatic app registration is refused: a directory-level permission is missing.

πŸ’‘ Tip: This error is often mistaken for a subscription problem, but Contributor on a subscription only manages resources inside it. Identity creation lives in the directory, which is a separate permission model with its own administrators.
69

Microsoft Entra admin center β€” confirm directory and account

Part 6 · Service connection

The fix for the previous step lives in a different portal. entra.microsoft.com is where directory roles and app registrations are managed β€” not the Azure portal and not Azure DevOps. Before changing anything, verify which directory and which administrator you are working as.

  1. Open entra.microsoft.com/#home (outlined in red top-left) β€” the Microsoft Entra admin center. The left rail confirms the product: Home, What's new, Diagnose & solve problems, Identity, Protection, Identity Governance, Learn & support.
  2. Look at the account chip in the top right (second red outline). It shows manuelacortesgranados… and DIRECTORIO PREDETERMINADO β€” a different identity from the one that hit the error, deliberately: this is the tenant administrator.
  3. Cross-check the Directorio predeterminado card in the centre: Tenant ID beginning 616e0d5a-…, the primary domain, and the object counts (3 users, 1 group, 0 devices, 3 apps).
  4. The same card lists the signed-in administrator β€” Manuela CortΓ©s Granados with a Global Administrator badge β€” and My role assignments counting 1 high-privileged role. That badge is exactly the directory authority the DevOps user lacked.
  5. Then open Identity → Users → All users in the left navigation (third red outline) β€” the entry point for granting the missing permission to the DevOps account.
Microsoft Entra admin center home with the entra.microsoft.com URL, the administrator account chip and the All users navigation entry highlighted in red

Step 69 β€” Right directory, right administrator; now go to Identity β†’ Users β†’ All users.

πŸ’‘ Tip: Global Administrator in the directory does not imply ownership of the Azure subscription, and subscription Owner does not imply directory rights β€” the two planes are separate. Whichever permission failed, fix it in the plane that owns it, not the other one.
70

All users β€” locate the guest account

Part 6 · Service connection

The user list is the directory's roster, and its columns answer the question from the error screen: what kind of account is being denied the right to create an app registration.

  1. Users opens on All users with the subtitle Directorio predeterminado. The banner underneath notes the rename: “Azure Active Directory is now Microsoft Entra ID” β€” same service, new name.
  2. Read the toolbar before touching a row: New user, Edit (Preview), Delete, Download users, Bulk operations, Refresh, Manage view, Per-user MFA, Got feedback?
  3. A search box and Add filter sit above the list; 3 users found confirms the directory is small enough to read in one glance.
  4. Compare the three rows in the User type column: Ana Cristina GarzΓ³n RodrΓ­guez and veeduriaibg@gmail.com are Guest; Manuela CortΓ©s Granados is Member. Creation type shows Invitation for the two guests β€” they arrived through the invite flow used earlier in this guide.
  5. Click the row veeduriaibg@gmail.com (outlined in red) to open that account's profile β€” the place where the missing directory role gets assigned.
Microsoft Entra All users list showing three accounts with user type and creation type columns, and the veeduriaibg@gmail.com guest row highlighted in red

Step 70 β€” The DevOps account is a Guest created by invitation; open it to assign a role.

πŸ’‘ Tip: Guests are members of the directory with a deliberately thin default permission set β€” they can sign in and use assigned resources, but directory-level actions such as creating app registrations are denied until a role is granted. That is precisely the wall hit in step 68.
71

Assigned roles β€” grant Application Administrator

Part 6 · Service connection

The user profile is a small application of its own, with a blade per concern. The one that matters is Assigned roles: the directory roles granted to this specific account, as opposed to the Azure RBAC roles granted on a subscription.

  1. The breadcrumb shows Home > Users > veeduriaibg@gmail.com, and the header reads “veeduriaibg@gmail.com | Assigned roles” with the subtitle User (outlined in red at the top).
  2. Before adding anything, note the two sibling blades in the left navigation: Assigned roles (what is being edited here β€” Entra directory roles) and Azure role assignments further down (subscription-level RBAC, the kind granted in step 25). They are different systems and are not interchangeable.
  3. Assigned roles is already selected (second red outline). The table below starts empty, which is why the app registration failed: no directory role, no directory write access.
  4. Click Add assignments in the toolbar (third red outline, with the long arrow) to open the role picker.
  5. Under Administrative roles β€” described as “Administrative roles can be used to grant access to Microsoft Entra ID and other Microsoft services” β€” select Application Administrator (fourth red outline). Its description is the requirement from step 68 verbatim: “Can create and manage all aspects of app registrations and enterprise apps.” Resource type is Organization, Assignment path Direct, Type Built-in.
  6. Confirm the assignment in the flyout's footer (the next step) β€” after which Azure DevOps can create the app registration on the guest's behalf.
Entra user Assigned roles page with Add assignments highlighted and Application Administrator role selected in the role list

Step 71 β€” Application Administrator is the least-privileged role that unblocks app registrations.

πŸ’‘ Tip: Global Administrator would also work, but it grants control over the entire tenant to fix one narrow need. Application Administrator covers app registrations and enterprise apps only β€” the least-privilege choice for this task.
72

The Directory roles picker β€” find and tick the role

Part 6 · Service connection

Add assignments opens a full-height flyout listing every built-in directory role with its description. It is long and alphabetical, which is why the search box at the top is the practical way to use it.

  1. The flyout is titled Directory roles and carries a licensing note worth reading: “To assign custom roles to a user, your organization needs Microsoft Entra ID Premium P1 or P2.” Built-in roles such as the one needed here do not require that.
  2. The instruction line confirms the intent: “Choose admin roles that you want to assign to this user. Learn more”.
  3. Use Search by name or description rather than scrolling. Typing application narrows the list to the handful of relevant entries.
  4. Watch for near-misses in the visible rows: Application Developer β€” “Can create application registrations independent of the 'Users can register applications' setting” β€” also touches app registrations, and Azure DevOps Administrator manages DevOps organization policy. Neither is the role chosen in the previous step.
  5. Tick Application Administrator β€” the entry whose description reads “Can create and manage all aspects of app registrations and enterprise apps” β€” then confirm the selection in the flyout's footer.
  6. Some rows carry a small tag marker (for instance the Attribute … roles). Those belong to custom security attributes and depend on the licensing mentioned in the banner β€” ignore them for this task.
Directory roles flyout listing built-in roles with checkboxes, descriptions, a search box and an Entra ID Premium licensing note

Step 72 β€” Search the role by name; the checkbox is what assigns it.

πŸ’‘ Tip: The descriptions in this list are the fastest way to confirm you picked the right role β€” they state the capability in plain language. If a role's description does not mention app registrations, it will not fix the step 68 error no matter how administrative the name sounds.
73

Service connections now lists the new connection

Part 6 · Service connection

Back in Azure DevOps after granting the directory role. The page that was empty in step 62 now holds one row β€” the concrete proof that the app registration was created and the connection exists.

  1. The breadcrumb still reads veeduriaibg / ProyectoVeeduriaBG001 / Settings / Service connections, and the heading is Service connections.
  2. The list contains a single entry, ServiceConnectionName1 (outlined in red), with the Azure cloud icon marking it as an Azure Resource Manager connection. The “Create your first service connection” empty state is gone.
  3. Read the toolbar before clicking the row: Security sets who may view, use or administer service connections across the project, the filter icon toggles column filters, and New service connection starts another one.
  4. The secondary row offers Filter by keywords and a Created by selector β€” useful once a project accumulates several connections and you need only the ones you own.
  5. Click ServiceConnectionName1 to open its details: subscription, resource group, scope, and the pipelines authorized to use it.
Azure DevOps Service connections page listing ServiceConnectionName1 with the Azure icon, highlighted in red

Step 73 β€” The connection exists; open it to review scope and pipeline authorization.

πŸ’‘ Tip: The name is the only reliable handle in pipeline YAML β€” the icon and details are for humans. Before wiring it into a pipeline, confirm in the details view that the subscription and resource group are the ones you intend to deploy into.
74

Connection details β€” OIDC, ID, creator and links

Part 6 · Service connection

Opening the row gives the connection's own page. It is small, but every line on it answers a question that comes up later β€” how it authenticates, where it lives, and who owns it.

  1. The header repeats the name, ServiceConnectionName1, with a back arrow to the list and its identifier underneath: ID: e17168ff-cd76-4a56-9c97-5e18088c8e61. That GUID is what API calls and support threads refer to.
  2. Three tabs split the page: Overview (selected), Usage history β€” which pipelines used the connection and when β€” and Approvals and checks, where deployments can be gated behind an approval or a branch check.
  3. In Details, the type is Azure Resource Manager with the subtitle “using workload identity federation with openid connect”. No client secret is stored on either side; the pipeline exchanges a signed token for an Azure token at run time.
  4. Two operational links sit under the type: Manage service connection roles controls who may use or administer the connection, and Manage App registration jumps straight to the Entra application that was created automatically in step 67.
  5. On the right, Creator shows Veeduria Ciudadana Informatica y Buen Gobierno / veeduriaibg@gmail.com β€” the account that just received Application Administrator, which is why the creation succeeded this time.
  6. Edit reopens the form for name and description; the identity and scope choices are not re-designable after creation.
Service connection details page showing Azure Resource Manager type with workload identity federation, the connection ID, manage links and the creator account

Step 74 β€” The connection authenticates via OIDC federation, created by the DevOps guest account.

πŸ’‘ Tip: Manage App registration is the shortcut to diagnose authentication failures later: the app's Federated credentials tab must still list the DevOps issuer and subject. If that entry has been deleted, the connection exists but every deployment fails at sign-in.
Part 7 of 11

Self-hosted agent: pool, token, registration

Steps 75–92 · the agent pool, the personal access token, config.cmd, run.cmd and the Online status in the pool.

75

Pipelines → Agent pools β€” where builds run

Part 7 · Self-hosted agent

The service connection answers where deployments go; agent pools answer where the jobs run. Both live under Project Settings, one group apart.

  1. In the left tree, Pipelines (outlined in red) holds the compute-side settings. Open Agent pools directly beneath it (second red outline).
  2. The URL keeps the older naming β€” /_settings/agentqueues β€” even though the page is titled Agent pools; the two terms survive side by side in Azure DevOps.
  3. Two pools are already available to the project: Azure Pipelines (cloud icon) β€” Microsoft's hosted agents, the ones you get for free with an organization β€” and Default, the pool where self-hosted agents register when installed on your own machines.
  4. The table reports Queued jobs and Running jobs per pool, which is the quickest way to see whether a build is waiting or executing.
  5. The toolbar offers a filter icon, Security (who may administer pools in this project) and Add pool (outlined in red) β€” at project scope this exposes an existing organization-level pool to this project rather than creating new hardware.
Project Settings Agent pools page listing Azure Pipelines and Default pools with the Pipelines and Agent pools navigation entries and the Add pool button highlighted

Step 75 β€” Azure Pipelines (hosted) and Default (self-hosted) are the two pools available.

πŸ’‘ Tip: If jobs sit in the queue forever with an available pool, the usual cause is the parallel-job allowance, configured separately under Pipelines → Parallel jobs β€” not a missing pool.
76

Add agent pool β€” create VeeduriaAgentPool1

Part 7 · Self-hosted agent

Add pool opens a side panel that starts with a choice of pool type, because the three options differ radically in who owns the machine and who pays for it.

  1. The panel is titled Add agent pool and its subtitle sets the scope: “Agent pools are shared across an organization.” β€” a pool created here is visible beyond this project.
  2. Pool to link: New is selected, meaning a pool is being created rather than attached. Existing would link a pool that already exists in the organization.
  3. Pool type offers three paths: Managed DevOps Pool (Microsoft maintains the agents), Self-hosted (custom agents on infrastructure you control) and Azure virtual machine scale set (agents on a VMSS in your own subscription, with a separate configuration guide).
  4. Keep Self-hosted selected (outlined in red) β€” its description reads “Create a pool of custom agents hosted on your own infrastructure for maximum control and flexibility.”
  5. Name: VeeduriaAgentPool1 (second red outline). The name is what the pipeline references as pool: name, so treat it as an identifier, not a label.
  6. Description is optional and accepts Markdown β€” useful for noting which machine or team the pool belongs to.
  7. Pipeline permissions: leave Grant access permission to all pipelines ticked (third red outline) so pipelines can target the pool without a per-pipeline authorization round.
  8. Click Create (bottom right, fourth red outline).
Add agent pool panel with New pool selected, Self-hosted type, the name VeeduriaAgentPool1, all-pipelines permission ticked and the Create button highlighted

Step 76 β€” A self-hosted pool named VeeduriaAgentPool1, open to all pipelines.

πŸ’‘ Tip: Creating the pool does not create a machine. A fresh pool is empty until an agent is installed and registered against it β€” pipelines targeting it will queue indefinitely until then.
77

The pool appears in the list β€” open it

Part 7 · Self-hosted agent

Create returns to the same Agent pools page, now with three rows instead of two. The new entry is easy to tell apart from the built-in ones at a glance.

  1. Azure Pipelines and Default keep their subtitle Azure Pipelines β€” that is the Microsoft-hosted origin.
  2. VeeduriaAgentPool1 (outlined in red) carries the organization's name underneath, Veeduria Ciudadana Informatica y Buen Gobierno. That subtitle is the signal that the pool belongs to your organization rather than to Microsoft's hosted fleet.
  3. Both Queued jobs and Running jobs are empty for the new pool. Expected at this point: nothing has been registered to run on it yet.
  4. Because pools are organization-wide, this one is available to every project in veeduriaibg that grants access to it β€” not only to ProyectoVeeduriaBG001.
  5. Click VeeduriaAgentPool1 to open the pool's own page, where the Agents tab lists registered machines and offers the agent download and registration instructions.
Agent pools list with Azure Pipelines, Default and the new VeeduriaAgentPool1 pool shown with the organization name and highlighted in red

Step 77 β€” The pool exists and is empty; open it to register an agent.

πŸ’‘ Tip: Zero queued and zero running jobs is the healthy state for an unused pool, but it also means any pipeline pointing at it will wait forever. Register at least one agent before switching a real pipeline over.
78

Pool page β€” tabs and the New agent button

Part 7 · Self-hosted agent

The pool has its own workspace now. Six tabs cover it, but only one of them answers the question of this section: how does a machine start running jobs for this pool?

  1. The breadcrumb ends in Settings › Agent pools › VeeduriaAgentPool1, and the URL carries ?queueId=10 β€” the pool's numeric id inside this organization, the same value REST calls and some YAML references use.
  2. The header card (first red outline) holds the name and the tabs: Jobs (selected) β€” runs that targeted this pool; Agents β€” the registered machines and their online state; Details β€” whether the pool is organization- or project-scoped; Security β€” who may use or administer it; Approvals and checks β€” gates before a job starts; Analytics β€” pipeline trends over time.
  3. The body shows the empty state “No jobs have run on this agent pool” with the hint “Run a pipeline on this agent pool to see more details.” Nothing is wrong β€” no pipeline has targeted it yet.
  4. Update all agents pushes the newest agent software to every machine registered in the pool; it only becomes useful once agents exist.
  5. Click New agent (second red outline) to open the download-and-configure instructions β€” the agent package for your platform plus the config command that registers it against this pool.
Agent pool detail page with the pool name and tab strip highlighted, the empty 'No jobs have run' state and the New agent button highlighted in red

Step 78 β€” Pool workspace: Jobs is empty, and New agent starts the registration flow.

πŸ’‘ Tip: When a pipeline waits without starting, these two tabs tell the story β€” Jobs shows the queued demand, Agents shows whether anything is online to serve it. A perpetually queued job with an empty Agents list is a registration problem, not a pipeline bug.
79

Get the agent β€” download and the four commands

Part 7 · Self-hosted agent

New agent opens the registration instructions, and they are written for copy-paste rather than for reading. Four blocks: download, unpack, configure, run.

  1. Pick the platform across the top β€” Windows is selected (outlined in red), macOS and Linux beside it β€” then the architecture on the left: x64 is active, x86 below it for 32-bit installs.
  2. Configure your account links to the prerequisites: the machine needs the agent prerequisites, and registration itself will ask for a Personal Access Token, so have one ready before running the configure command.
  3. Download the agent: click Download (second red outline) or use the copy icon to grab the URL. The file it delivers is vsts-agent-win-x64-4.255.0.zip β€” that version number matters in the next block.
  4. Create the agent β€” the first code block makes the folder and unpacks the archive there: mkdir agent ; cd agent followed by [System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\vsts-agent-win-x64-4.255.0.zip", "$PWD"). Run it in PowerShell, not in Command Prompt.
  5. Configure the agent β€” .\config.cmd inside C:\agent runs the interactive setup: server URL, authentication token, pool name, agent name. This is where the pool created in step 76 gets targeted. Detailed instructions documents every prompt.
  6. Optionally run the agent interactively β€” .\run.cmd for a foreground session. If the agent was registered as a Windows service instead, skip this block; the service starts it automatically.
  7. The final “That’s it!” line closes the instructions; the panel can be dismissed once the commands are copied.
Get the agent panel with the Windows tab and Download button highlighted, plus PowerShell blocks for unzipping, configuring and running the agent

Step 79 β€” Windows x64 agent: download, extract to C:\agent, configure, run.

πŸ’‘ Tip: The extraction line hard-codes the version from the zip you actually downloaded. If the file is vsts-agent-win-x64-4.256.0.zip or sits in a different folder, edit the path in the command β€” a "file not found" error here is almost always that mismatch, not a permissions problem.
80

The agent download β€” 219 MB into Downloads

Part 7 · Self-hosted agent

Unlike the installer earlier, this file is big. The browser reports it in its downloads flyout, and the only action required here is patience β€” the next command assumes a complete archive in the Downloads folder.

  1. Clicking Download opens the browser's Recent download history panel with one entry.
  2. The row names the archive, vsts-agent-win-x64-4.255.0.zip (with the zip icon), and reports live progress: 36.4 / 219 MB with an estimate such as 52 seconds left.
  3. The thin bar underneath is the transfer itself β€” it fills left to right as the file arrives.
  4. The × at the top right dismisses the panel only; the download keeps running and stays reachable from the browser's download icon.
  5. Wait for it to finish before running the extraction command from step 79: the archive has to be complete, and it lands by default in $HOME\Downloads β€” exactly the path that command expects.
Browser download flyout showing vsts-agent-win-x64-4.255.0.zip with 36.4 of 219 MB transferred and a progress bar

Step 80 β€” A 219 MB download; let it complete before extracting.

πŸ’‘ Tip: Extracting a partial archive fails with a cryptic "unexpected end of archive" style error. If the extraction breaks, check the size of the zip in Downloads first β€” resuming or re-downloading is faster than debugging the PowerShell line.
81

Download finished β€” Show in folder

Part 7 · Self-hosted agent

The same flyout a minute later. The transfer is over, and one small button takes you to the file with its real location in view β€” which is what the extraction command depends on.

  1. The status line under the filename has changed from a live counter to “219 MB β€’ Done”, the full size matching the estimate from the previous step.
  2. Hover the folder icon next to the entry (outlined in red) β€” the tooltip reads Show in folder.
  3. Click it to open File Explorer with that zip already selected, confirming both the file and its directory.
  4. The neighbouring ↗ icon opens the file itself rather than its location; useful only if you want to inspect the archive contents.
  5. The entry below, SourceTreeSetup-3.4.23.exe, is the earlier download still sitting in the same history β€” a reminder that both files land in the same folder by default.
Browser download flyout showing the agent zip completed at 219 MB with the Show in folder button highlighted in red

Step 81 β€” Complete at 219 MB; Show in folder opens the file's directory.

πŸ’‘ Tip: On machines where the Downloads folder is redirected to OneDrive, the real path is not $HOME\Downloads and the step 79 command fails. Copy the address from the Explorer window this button opens and paste it into the extraction command instead.
82

Extract the agent archive with WinRAR

Part 7 · Self-hosted agent

The zip can be unpacked with the PowerShell line from step 79 or from Explorer's context menu β€” the second route is what this screenshot takes, since WinRAR is installed and owns the .zip association on this machine.

  1. Explorer is at This PC › Local Disk (C:) › Users › ACGR_GIT › Downloads, and the Today (2) group holds both downloads: the agent zip and the SourceTree installer.
  2. The zip vsts-agent-win-x64-4.255.0 is selected; the status bar confirms 1 item selected at 218 MB, and the tooltip spells out the details β€” Type: WinRAR ZIP archive. Consistent with the 219 MB reported by the browser (the two count in different units).
  3. Right-click the archive. Because WinRAR is registered, the top of the menu is WinRAR's own: Open, the editor shortcuts, Open with WinRAR, Extract files….
  4. Two extraction entries are outlined in red: Extract Here unpacks into the current folder, and Extract to “vsts-agent-win-x64-4.255.0\” creates a subfolder of that name and unpacks inside it β€” the tidier option of the two.
  5. This guide needs the files in C:\agent (the path used by the .\config.cmd command). Either extract into the subfolder here and move it to C:\agent, or use Extract files… and type C:\agent as the destination directly.
Windows File Explorer Downloads folder with the agent zip selected and a context menu showing WinRAR's Extract Here and Extract to folder entries highlighted

Step 82 β€” Extract to a named subfolder rather than into Downloads itself.

πŸ’‘ Tip: Extract Here scatters the agent's several hundred files straight into Downloads, mixed with everything else. Either use the named-folder variant, or skip WinRAR entirely and keep the step 79 PowerShell line, which writes directly to the folder you choose.
83

Extraction progress β€” the two percentages

Part 7 · Self-hosted agent

WinRAR reports the unpacking in a small dialog that shows two different progress numbers at once, which is the only confusing part of this step. Everything else is waiting.

  1. The title bar reads Extracting from vsts-ag… β€” truncated, but it is the agent archive being unpacked.
  2. The first block names the archive and the file currently being written: vsts-agent-win-x64-4.255.0.zip, extracting, node.exe, with a small percentage (6% here) and its own green bar.
  3. That first percentage is per file. node.exe is the agent's Node.js runtime β€” by far the largest item in the bundle, so this block sits on it for most of the run.
  4. The third block, Processed 66%, is the archive-wide figure. This is the one to follow: the extraction is really 66% done even though the current file reads 6%.
  5. Elapsed time and Time left tick along in the middle block β€” seconds, not minutes, for a 219 MB archive on local disk.
  6. The buttons need no input: Background hides the dialog and keeps extracting, Pause suspends it, Cancel aborts, Mode… changes the dialog's verbosity, Help opens WinRAR's documentation. Letting the dialog close by itself is the expected path.
WinRAR extraction dialog showing node.exe being extracted at 6 percent, elapsed and remaining time, and Processed at 66 percent with a green bar

Step 83 β€” Per-file 6%, archive-wide 66%: watch the Processed line.

πŸ’‘ Tip: A dialog that appears frozen at a low number is usually just the per-file bar working through node.exe. Check whether Elapsed time keeps advancing and Processed keeps climbing before assuming the extraction hung.
84

Open PowerShell as administrator

Part 7 · Self-hosted agent

The agent is configured by a script, and that script needs to write into C:\agent and, typically, install a Windows service. Start elevated once and the rest of the flow stays smooth.

  1. Open Start and type powershell. Under Programs (9) the entry powershell appears (outlined in red at the bottom left), alongside look-alikes: Developer PowerShell for VS, powershell_ise and Windows PowerShell.
  2. Right-click the plain powershell result to get the context menu shown here β€” Open, Run as administrator, editor shortcuts, archive entries, and the usual file operations.
  3. Choose Run as administrator (outlined in red with the arrow). Accept the UAC prompt if it appears; the window title afterwards starts with Administrator:.
  4. The annotation spells out why: “Ejecutar powershell para posteriormente ejecutar config.cmd” β€” run PowerShell now, so that config.cmd can be run from it next.
  5. Before running anything, move to the folder that holds the extracted agent and confirm with dir that config.cmd is present. Depending on the route taken in step 82 that is either cd C:\agent (the path the step 79 script writes to) or the named subfolder inside Downloads β€” cd "$env:USERPROFILE\Downloads\vsts-agent-win-x64-4.255.0". The next step runs the script from whichever directory actually contains it.
Windows Start search for powershell with the result highlighted and the context menu option Run as administrator outlined in red

Step 84 β€” Right-click the powershell result and pick Run as administrator.

πŸ’‘ Tip: Elevation is needed because config.cmd offers to register the agent as a Windows service. Start it without elevation and that option fails midway, leaving a half-configured agent that has to be removed and set up again.
85

config.cmd β€” server URL, auth type and token

Part 7 · Self-hosted agent

The configuration script is interactive, one question at a time. The banner tells you which agent build is being registered, and the first three answers establish where it connects and who it authenticates as.

  1. The window title confirms elevation β€” Administrator: Windows PowerShell β€” and the prompt shows the working directory the script runs from: C:\Users\ACGR_GIT\Downloads\vsts-agent-win-x64-4.255.0. Any folder that contains the agent files works; only the presence of config.cmd matters.
  2. The command is ./config.cmd. It prints the Azure Pipelines banner with its build: agent v4.255.0, (commit 470b366) β€” the same version as the zip.
  3. Enter server URL > https://dev.azure.com/veeduriaibg/ β€” the organization root, ending in a slash. Pasting a repository URL here is the classic mistake; the agent needs the organization.
  4. Enter authentication type (press enter for PAT) > PAT. Pressing Enter accepts the default, which is also PAT.
  5. Enter personal access token > β€” paste the token and press Enter. The token must carry the Agent Pools (Read & manage) scope, otherwise registration is refused a few prompts later.
Administrator PowerShell running config.cmd with the Azure Pipelines banner and the three prompts for server URL, authentication type and personal access token outlined in red

Step 85 β€” Organization URL, PAT as the auth type, then the token itself.

πŸ’‘ Tip: The prompt is empty until you paste, and then config.cmd masks what you pasted with one asterisk per character (visible in step 90). Asterisks appearing is the normal feedback that the value was received β€” press Enter once and let the next question appear before pasting again, or you will submit a concatenated token.
86

Account menu β€” open Personal access tokens

Part 7 · Self-hosted agent

Step 85 already asked for a token, so this is the detour that produces it. It does not start from the agent pool: the pool page is a dead end for credentials, and the token lives behind the account menu in the top-right corner.

  1. The starting point is Project Settings › Agent pools › VeeduriaAgentPool1, on the Jobs tab. The tab strip reads Jobs Β· Agents Β· Details Β· Security Β· Approvals and checks Β· Analytics (outlined in red at the top), and it is all pool administration β€” no token anywhere in it.
  2. The empty state explains itself: “No jobs have run on this agent pool”, Run a pipeline on this agent pool to see more details. Nothing can run until an agent is registered, and registering that agent is what the next steps are for.
  3. Your account is the VG avatar at the far right of the top bar β€” the second red box in the screenshot. Click it.
  4. The dropdown that opens is the user menu: Preview features, Profile, Time and Locale, Permissions, Notifications, Theme, Usage, then Personal access tokens, then SSH public keys.
  5. Choose Personal access tokens (outlined in red, with the pointer on it).
  6. This entry is account-wide, not project-scoped: the same menu item opens the same list no matter which project or page you are on, so the pool you were looking at a moment ago is irrelevant to where the token comes from.
Azure DevOps agent pool page with the pool tabs outlined and the VG account menu open, Personal access tokens highlighted in red

Step 86 β€” Agent pool page, then avatar → Personal access tokens.

πŸ’‘ Tip: Do not go looking for the token under the pool's own Security tab. That tab controls who may administer the pool β€” different thing entirely, and it cannot mint credentials.
87

The token list β€” existing Git tokens, and New Token

Part 7 · Self-hosted agent

One click later you are in User settings, and the first thing the page does is show you tokens you must not touch. Both red annotations here are about that distinction: one says create, the other says leave alone.

  1. The URL confirms the scope switch: dev.azure.com/veeduriaibg/_usersSettings/tokens, under User settings › Veeduria Ciudadana Informatica y… in the left rail.
  2. The left rail groups the settings: Account (Profile, Time and Locale, Permissions), Preferences (Notifications, Theme, Usage) and Security β€” where Personal access tokens is highlighted, followed by SSH public keys and Authorizations.
  3. The heading is Personal Access Tokens, with the subtitle These can be used instead of a password for applications like Git or can be passed in the authorization header to access REST APIs β€” which is precisely how config.cmd will use it.
  4. Two filters sit above the table: Access scope: veeduriaibg and Status: Active. Leave both as they are; they simply restrict the grid to live tokens of this organization.
  5. The toolbar next to Manage tokens holds Revoke, Regenerate and Edit, all three greyed out. They act on a selected row, and nothing is ticked yet β€” they stay disabled until you check a box, which is also why the checkboxes in the two rows are empty here.
  6. The grid is sorted by Expires on descending (the arrow beside the column header), and its four columns are Token name, Organization, Status and Expires on.
  7. Under Manage tokens the grid already holds two rows, both named Git: https://dev.azure.com/veeduriaibg on the website., scope Code (Read & write), organization veeduriaibg, status Active, expiring 6/10/2025. Those were created for you by the Git credential prompt during the SourceTree clone in steps 55–58.
  8. The red arrow at the top right points at + New Token, annotated “Hacer click en este boton para agregar nuevo Token” β€” click this button to add a new token. That is the whole action required on this screen.
  9. The rows are boxed, with an arrow and the note “No borrar, sino tocaria reconfigurar GIT desde cero a nivel local” β€” do not delete these, or Git would have to be reconfigured from scratch locally. They are what keeps the existing clone and pushes working.
Azure DevOps Personal Access Tokens page with the New Token button and the two existing active Git tokens highlighted in red

Step 87 β€” Create a new token; leave the two existing Git tokens alone.

πŸ’‘ Tip: Note the Expires on column while you are here β€” 6/10/2025. Token expiry is the usual cause of a clone that suddenly starts asking for a password again, so it is worth a glance before blaming the credentials.
88

Create the token β€” name, organization, expiry and scopes

Part 7 · Self-hosted agent

+ New Token opens a single dialog with four inputs and a pair of radio buttons. Every value on it matters later, because a token that is scoped or timed wrongly fails at registration with a bare 401/403 rather than a helpful message.

  1. The dialog is titled Create a new personal access token, with an × at the top right that does exactly what Cancel does β€” closes it and creates nothing.
  2. Name β€” VeeduriaToken1 here (outlined in red). Any label works; a name that says what it is for, such as the agent pool it will register, saves guesswork in a few months when the list has grown.
  3. Organization β€” veeduriaibg, preselected. The token is bound to this organization only, which is why the same value had to go into the server URL prompt in step 85.
  4. Expiration (UTC) β€” the preset dropdown reads 30 days, and the read-only date field beside it (the one with the calendar icon) resolves that to 10/06/2025, one month out. The dropdown also offers 90 days and custom dates, and every value there is UTC, not local time.
  5. Scopes β€” the section is introduced by Authorize the scope of access associated with this token, then two radios. Full access is selected here (outlined in red); Custom defined is the alternative. Full access covers every scope, including the Agent Pools (Read & manage) permission that agent registration needs β€” which is why it works out of the box.
  6. If your organization forbids Full access, switch to Custom defined, open Show all scopes and tick Agent Pools → Read & manage only. Same result for config.cmd, far smaller blast radius.
  7. Click Create (bottom left, outlined in red) β€” it is live here precisely because the Name box is filled. The greyed Cancel beside it, and the × at the top right, both discard the dialog without creating anything.
Create a new personal access token dialog with the name VeeduriaToken1, organization veeduriaibg, 30 day expiration, Full access scope and the Create button highlighted

Step 88 β€” Name it, keep the organization, 30 days, Full access (or Agent Pools only).

πŸ’‘ Tip: Full access is the default here because it is one click, not because it is best practice β€” it also permits deleting pools, editing repositories and reading every project in the organization. For a token whose only job is registering one agent, Custom defined with Agent Pools (Read & manage) is the better habit.
89

“Success!” β€” copy the token before closing

Part 7 · Self-hosted agent

The token exists now, and this dialog is the only place it will ever be displayed. What you do in the next ten seconds decides whether step 85 can be completed or has to be redone from this screen.

  1. The dialog title is Success! with the line You have successfully added a new personal access token. Copy the token now!, followed by VeeduriaToken1 token β€” the name typed in step 88, so you can tell which one just came back.
  2. Below it sits the token itself in a read-only box β€” a long mixed-case string that the box width truncates. Beside the field is a copy icon (outlined in red, pointer on it).
  3. Click that icon. The value goes to the clipboard, and that is what gets pasted at the Enter personal access token > prompt from step 85.
  4. Read the warning panel below the field β€” pale amber, with a triangular ⚠ on the left: “Warning - Make sure you copy the above token now. We don't store it and you will not be able to see it again.” Azure DevOps keeps only a hash of the token β€” there is no screen, anywhere, that will show this value a second time.
  5. Paste it somewhere you control before clicking anything else: straight into the waiting config.cmd prompt, or a password manager. Not into a chat window, a ticket, or the repository.
  6. Close (bottom right, outlined in red) dismisses the dialog β€” and so does the × at the top right, which is easy to hit by reflex while the token is still uncopied. Closing is not what invalidates the token β€” losing the value is. The token stays active and usable regardless.
  7. If you did close it without copying, do not try to recover it: Regenerate on the list from step 87 issues a new value (and kills the old one), which is the same amount of work as creating a token again.
Success dialog after creating a personal access token, with the copy icon highlighted in red, a warning that the token is shown only once, and the Close button

Step 89 β€” Copy the token now; the warning is literal, it is shown once.

πŸ’‘ Tip: If config.cmd rejects the token a few prompts later, check these in order: it was created in veeduriaibg (not another organization), its expiry has not already passed, its scope covers Agent Pools, and the paste did not pick up a trailing space. Each of those fails with the same generic authorization error.
90

Register the agent β€” pool, name, work folder, service prompts

Part 7 · Self-hosted agent

The second half of config.cmd, immediately after the token. Six prompts, all of which can be answered with Enter on this run, and one line in the middle β€” Successfully added the agent β€” that is the only proof the registration actually worked.

  1. Reconfirm the context: an Administrator: Windows PowerShell window, the command ./config.cmd run from C:\Users\ACGR_GIT\Downloads\vsts-agent-win-x64-4.255.0, and the Azure Pipelines banner already printed (agent v4.255.0, commit 470b366).
  2. Under >> Connect: the three answers from step 85 are visible above this run's new prompts β€” the organization URL https://dev.azure.com/veeduriaibg/, the auth type PAT, and the token rendered as a long run of * characters. That masking is the paste landing correctly.
  3. Connecting to server ... follows. This is the point where a wrong token, a wrong organization or an expired PAT stops the script β€” the next banner never arrives.
  4. >> Register Agent: — Enter agent pool (press enter for default) > was answered with a bare Enter, which accepts the prompt's suggestion of the organization's default pool. The pool name typed here is the only thing that decides which pool the agent appears in β€” nothing about the URL or the project influences it β€” so a dedicated pool such as VeeduriaAgentPool1 has to be typed explicitly.
  5. Enter agent name (press enter for DESKTOP-MDE2U74) > β€” Enter again accepts the machine name as the agent name. Any name is legal; the machine name is what shows up in the pool's Agents tab.
  6. Scanning for tool capabilities. β€” the agent inspects the machine (Node, .NET, Git and similar) and reports what it finds to the server, so pipelines can be routed to agents that have the tools they need. A short pause here is normal.
  7. Connecting to the server. then Successfully added the agent β€” the registration is done at this line. Testing agent connection. follows immediately, and if the network path works you get no error and simply the next question.
  8. Enter work folder (press enter for _work) > β€” Enter accepts _work, a relative folder created inside the agent directory. That is where every pipeline checks out sources and writes build output, so it grows to several GB over time.
  9. 2025-05-12 04:17:10Z: Settings Saved. β€” config.cmd has written its configuration into the agent folder (the Z means UTC, and the timestamp is when this machine was configured).
  10. Enter run agent as service? (Y/N) (press enter for N) > β€” Enter accepts N, so no Windows service is installed. The agent will only be alive while run.cmd is left open in a console, and stops when the window closes or the machine reboots.
  11. Enter configure autologon and run agent on startup? (Y/N) (press enter for N) > β€” Enter again accepts N: no stored Windows logon and no startup entry. Choosing Y on either prompt is what makes an agent survive a reboot without anyone logging in, and it is worth revisiting once the manual run has been verified.
  12. Control returns to the prompt, PS C:\Users\ACGR_GIT\Downloads\vsts-agent-win-x64-4.255.0>. The agent is registered but not running: nothing is listening yet, so a pipeline queued now would wait indefinitely.
Administrator PowerShell showing the rest of config.cmd: masked token, agent pool and name prompts answered with Enter, Successfully added the agent, work folder, and the service and autologon prompts answered N

Step 90 β€” Pool, name, work folder, then N to both service prompts.

πŸ’‘ Tip: Two choices here are easy to regret. The pool prompt defaults to default, so a dedicated pool has to be named explicitly or the agent lands somewhere other than the one built in step 76. And answering N to the service prompt means the agent dies with the console window. Neither is an error, but if a pipeline later sits in Waiting for an agent, those are the first two settings to check.
91

Start it β€” ./run.cmd and “Listening for Jobs”

Part 7 · Self-hosted agent

Configuration ends and execution begins: the last two prompts of config.cmd, then one command that keeps the window busy forever. Three areas are outlined in red β€” the two service prompts, the command itself, and the single line that proves the agent is live.

  1. The window repeats the whole sequence β€” banner, >> Connect:, >> Register Agent: β€” because config.cmd was run again on the same machine. The two timestamps say so on their own: Settings Saved at 04:17:10Z in step 90 and 04:17:58Z here, 48 seconds apart.
  2. The first red box covers the tail of the two persistence prompts β€” Enter run agent as service? (Y/N) (press enter for N) > and Enter configure autologon and run agent on startup? (Y/N) (press enter for N) >. No Y appears after either prompt, so both were accepted with Enter: this agent is still console-bound, with no service and no startup entry.
  3. Those two prompts are the ones to answer deliberately if you want the agent on after a reboot β€” Y to run agent as service installs it as a Windows service (this elevated window was opened in step 84 exactly so that it can), and Y to autologon stores the credentials needed to start it at boot.
  4. Control returns to the prompt, and the command typed there is ./run.cmd β€” the second red box. The ./ prefix is not decoration: PowerShell does not execute a program from the current directory unless it is named that way (or .\run.cmd), so a bare run.cmd can fail with a command-not-found error even though the file is right there.
  5. The agent starts and re-announces itself: Scanning for tool capabilities., then Connecting to the server. β€” the same two lines as during registration, this time establishing a live session instead of saving configuration.
  6. The third red box is the line that matters: 2025-05-12 04:17:58Z: Listening for Jobs. The agent is online and polling the queue; it will pick up the next pipeline job this pool is offered.
  7. No prompt comes back. The console is now occupied by the agent, and the only way to keep it there is to leave the window alone: closing it β€” or logging off, since no service was installed β€” takes the agent offline and leaves pipelines queued in Waiting for an agent.
  8. To stop it on purpose later, press Ctrl+C in this window rather than closing it. The agent then deregisters from the live session cleanly instead of timing out on the server side.
  9. Leave this window visible and check the browser: the pool's Agents tab should now list DESKTOP-MDE2U74 β€” the default name accepted in step 90 β€” as Online / Idle (step 92). If the pool you are looking at is still empty, the agent was registered into the default pool instead, which is what the pool prompt falls back to.
Administrator PowerShell with the two service prompts, the ./run.cmd command and the Listening for Jobs line each outlined in red

Step 91 β€” config.cmd ends on N/N, then ./run.cmd holds the window with Listening for Jobs.

πŸ’‘ Tip: After Listening for Jobs an idle agent prints nothing at all β€” sometimes for hours. That silence is the normal state, not a hang; the console only shows new output when a job arrives or the connection drops. If you need proof it is still alive, look at the pool's Agents tab in the browser rather than at the window.
92

The Agents tab β€” the agent listed as Online

Part 7 · Self-hosted agent

The other half of the round trip: the console said Listening for Jobs, and the server now says the same thing from its side. This is the screen that confirms registration worked β€” a single row, and one word to look for.

  1. The breadcrumb path is the one from step 86, but on a different tab: Project Settings › Agent pools › VeeduriaAgentPool1, with Agents underlined and active β€” the second of the six tabs (Jobs Β· Agents Β· Details Β· Security Β· Approvals and checks Β· Analytics).
  2. Top right, two buttons: Update all agents and New agent (blue). The second one reopens the Get the agent panel from step 79 β€” the place to go when a second machine has to join the same pool.
  3. The grid has one row per registered agent, with five columns: Name, Last run, Current status, Agent version, Enabled.
  4. Name β€” DESKTOP-M…, clipped by the column width: this is DESKTOP-MDE2U74, the machine name that was accepted as the default agent name in step 90. Underneath it sits a green dot and the word Online, which is the only bit the red annotation cares about β€” “Al final debe mostrarse Online”, in the end it must show Online.
  5. Last run β€” empty. No pipeline has ever been dispatched to this agent, which is consistent with the empty Jobs tab in step 86; the field fills in after the first job, with a colour that reflects the result.
  6. Current status β€” Idle. Read it together with Online rather than instead of it: Online means the agent is connected and reachable, Idle means it is connected but holds no job right now. The pair Online + Idle is exactly the healthy state of a freshly configured agent.
  7. Agent version β€” 4.255.0, matching the zip downloaded in step 80 and the banner config.cmd printed. A version that drifts behind the server's is what Update all agents is for.
  8. Enabled β€” the toggle is On (blue). Switch it off and the agent stays registered and connected but is not offered work; that is the clean way to take one machine out of rotation for maintenance without deleting it.
  9. Clicking the agent's name opens its detail view β€” capabilities, the history of finished jobs and the diagnostics download β€” which is where to look if the status here ever reads Offline and the console window is still open.
Agents tab of VeeduriaAgentPool1 listing one agent with a green Online dot, status Idle, version 4.255.0 and the Enabled toggle On

Step 92 β€” Registered and connected: Online, Idle, version 4.255.0, Enabled.

πŸ’‘ Tip: Online is a live claim, not a stored one β€” it drops to Offline within about a minute of the run.cmd window being closed or the machine going to sleep, and comes back by itself when the window is open again. So when a pipeline hangs waiting for an agent, this tab is the first thing to refresh.
Part 8 of 11

Visual Studio and the Functions project

Steps 93–100 · the IDE, the Functions template, the project settings, and the local test in a browser.

93

Visual Studio 2022 β€” the launch splash

Part 8 · Visual Studio

A change of tool: the agent half of the setup is finished, and the work now moves into the IDE. This frame is only the first second of that β€” but it accidentally documents something worth noticing about the console behind it.

  1. What fills the screen is the Microsoft Visual Studio splash: a violet geometric backdrop, the four-square Microsoft logo, the product name, and 2022 in the bottom right corner.
  2. It is a splash, not a dialog β€” there is nothing to click, no Cancel, no progress figure. The IDE shows it while it loads its settings and extensions, then replaces it with the Start Window.
  3. 2022 is the product year, and nothing more: the splash carries no edition and no build number. Community, Professional and Enterprise all look identical here β€” the edition only shows up later, under Help › About Microsoft Visual Studio.
  4. Around the edges the previous window is still visible: a sliver of the PowerShell console along the top (the tail of the ASCII Pipelines banner) and a narrow strip down the left margin with the >> Register… lines from step 90. The agent console was never closed β€” it is simply behind the IDE now, still listening.
  5. The small red marks on that left margin are leftovers too: the rectangles drawn over the previous screenshot were made with an on-screen annotation tool, so they stayed on the desktop and float above whatever window comes next. They are not part of Visual Studio.
  6. Expect a first-run prompt before the IDE is usable β€” sign-in with the Microsoft account from step 1, a colour theme, and a development-settings preset. Answering them once is enough; they do not come back.
  7. The agent does not care about any of this. Opening the IDE does not stop it, and closing the IDE will not stop it either β€” only the run.cmd window does, which is why it is worth leaving that window unminimised where you can see it.
Microsoft Visual Studio 2022 splash screen with violet geometric artwork, over the fragmentary remains of the PowerShell agent console at the top and left edges

Step 93 β€” Visual Studio 2022 starting up, with the agent console still live behind it.

πŸ’‘ Tip: The edges of this screenshot are the diagnostic: as long as the console fragments peek out from behind other windows, the agent window is still open and the pool keeps showing Online. If you look later and those fragments are gone, the window was closed β€” the agent is offline no matter what the IDE is doing.
94

The Start Window β€” four ways in, and Continue without code

Part 8 · Visual Studio

The splash gives way to the Start Window: a two-column page with nothing loaded yet. The left column is history, the right column is intent β€” and the red box picks out one tile plus a small link underneath it.

  1. The title bar still reads Visual Studio 2022 with only the minimise, maximise and close controls β€” no menu bar, no document tabs. That is the signature of the Start Window: the IDE shell is running, but no solution has been opened in it.
  2. Open recent on the left is empty, which is the expected state on a first launch. The two lines underneath explain what will eventually live there: As you use Visual Studio, any projects, folders, or files that you open will show up here for quick access and You can pin anything that you open frequently so that it's always at the top of the list.
  3. On a machine that is already in use this list is the fastest way back into a solution; here there is nothing to click. Its emptiness is also a useful check that the IDE really is starting clean, rather than having silently reopened something old.
  4. Get started on the right offers four tiles, each with an icon, a title and one line of explanation: Clone a repository β€” Get code from an online repository like GitHub or Azure DevOps; Open a project or solution β€” Open a local Visual Studio project or .sln file; Open a local folder β€” Navigate and edit code within any folder; and Create a new project β€” Choose a project template with code scaffolding to get started.
  5. Clone a repository is the one that speaks to this guide's organization: it asks for a URL, and the clone URL copied back in step 42 or 54 works there, with the token from step 89 standing in for a password.
  6. Open a project or solution is the shorter route once the code is already on disk β€” the SourceTree clone from step 60, for instance, where you point the dialog at the .sln file.
  7. The third tile, Open a local folder, is shaded lighter than the others because the pointer is resting on it. It skips projects entirely: the folder is treated as loose code, with no build system attached.
  8. The red box encloses two separate things. The tile itself, Create a new project, leads to the template picker β€” scaffolding a console app, a class library, an Azure Functions project, and so on. The link beneath it, Continue without code →, does the opposite: it opens the IDE with no solution at all, just the shell, ready for a file to be added by hand.
  9. Both entries leave this page in a state the rest of the guide can build on, which is why they are grouped: create when the code does not exist yet, continue without code when it will be brought in by other means and does not need a solution wrapper.
  10. Along the bottom edge a promotion strip is cut off by the window's height, and a scrollbar sits on the right. Neither needs attention β€” the four tiles above are the whole page.
Visual Studio 2022 Start Window with an empty Open recent column on the left and the four Get started tiles on the right, the Create a new project tile and Continue without code link outlined in red

Step 94 β€” Empty Open recent, four Get started tiles, and the red-boxed entry pair.

πŸ’‘ Tip: The Start Window is not a launcher you have to go through again β€” once the IDE is open, File › Start Window brings this page back, and the same four actions are available from File › New, Open and Clone Repository in the menu bar.
95

Create a new project β€” search the templates for Azure Functions

Part 8 · Visual Studio

The template picker opens on the Installed set, which is long and alphabetical β€” far too long to scroll. The search box does the work instead, and the first row it returns is the one this walkthrough wants.

  1. The window is titled Create a new project, and the left column repeats the pattern from the Start Window: Recent project templates, empty, with A list of your recently accessed templates will be displayed here. On this machine no template has been used yet, so the list has nothing to remember.
  2. Top right is the search box, with Azure Functions typed into it. The × inside the box clears the text, and Clear all above the filters resets the search and every dropdown at once β€” the way back to the full list.
  3. The three dropdowns under it stay on their defaults β€” All languages, All platforms, All project types. Typing in the search box alone is enough to narrow the list to five rows; the filters are what you would use to cut it further, for example to C# only.
  4. The results update as you type, and the top row is already selected β€” that grey highlight is a selection, not a hover. The red box marks it: Azure Functions, described as A template to create an Azure Function project., tagged C#, Azure and Cloud.
  5. The amber word Azure inside the description is not a link: it is the search term being highlighted wherever it matches. Read the descriptions, not the highlights, when the list is long.
  6. The four rows below are the near misses, and they show why the search alone is not enough: Azure Resource Group (extended support) twice (an ARM/Bicep deployment project, whose own description recommends Bicep), and Azure WebJob (.NET Framework) twice β€” once for C#, once for Visual Basic. Same Azure tag, none of them a function app.
  7. If the selection is ever lost, click the row once to restore it β€” Next stays inactive until a template is chosen. Here it is enabled, and it is the third element outlined in red.
  8. Click Next to continue to the configuration page, where the project gets its name, location and solution.
Create a new project dialog with Azure Functions typed in the search box, the Azure Functions template selected and outlined in red, four related Azure templates listed below, and the Next button outlined in red

Step 95 β€” Search “Azure Functions”, keep the first row, then Next.

πŸ’‘ Tip: If Azure Functions returns nothing at all, the template is not installed β€” it ships with the Azure development workload. Open the Visual Studio Installer, choose Modify and add that workload; the search will find it again without reinstalling the IDE.
96

Configure your new project β€” name, location and the doubled path

Part 8 · Visual Studio

Four inputs, a checkbox and a preview line. The preview is the part that matters: it is the only place Visual Studio tells you exactly which folder the code will end up in, and it looks wrong until you read why.

  1. The page is titled Configure your new project, and the strip under it repeats the choice from step 95 β€” Azure Functions with its C#, Azure and Cloud tags β€” so a wrong template is still catchable here rather than twenty minutes later.
  2. The first red box encloses the three fields that decide where the code lives: Project name, Location and Solution name.
  3. Project name β€” FunctionApp1ProyectoVeeduriaBG001, with the caret at the end of the text. This string becomes more than a label: it is the assembly name, the root namespace, the folder name and the default name of the function app in Azure.
  4. Location β€” K:\ProyectoVeeduriaBG001\. Note the drive letter: the project is deliberately not on C:, which already carries the OS, the IDE and the 219 MB agent from step 80. The … button browses for another folder, and the caret at the right of the field opens the list of recently used locations.
  5. Solution name β€” the same string, mirrored automatically from the project name as it is typed. It stops mirroring the moment you edit the field by hand. The ⓘ beside the label explains the distinction: a solution is a container that can hold several projects, so on a one-project solution the two names are normally identical.
  6. Place solution and project in the same directory is unticked. That single checkbox decides whether you get two nested folders or one.
  7. The grey preview line spells out the result: Project will be created in "K:\ProyectoVeeduriaBG001\FunctionApp1ProyectoVeeduriaBG001\FunctionApp1ProyectoVeeduriaBG001\" (the text wraps at the window edge, but it is one path).
  8. That doubled segment is not a rendering bug. With the checkbox clear, the structure is Location \ solution folder \ project folder, and because both names are identical the same string appears twice. Tick the checkbox and the project is written directly into the solution folder, collapsing the path to a single level.
  9. Read the preview before going on β€” it is the exact directory to open in SourceTree later, and the one to keep out of the repository if build output ever lands there.
  10. Next β€” third element outlined in red β€” continues to the final page, where the Functions runtime details are chosen. Back returns to the template list from step 95 without losing these values.
Configure your new project page with project name FunctionApp1ProyectoVeeduriaBG001, location K:\ProyectoVeeduriaBG001\, identical solution name, unticked same-directory checkbox, the preview path and the Next button outlined in red

Step 96 β€” Name, location and solution name, then read the preview path.

πŸ’‘ Tip: Keep the project name free of spaces and accents. It propagates into namespaces, assembly metadata, command lines and eventually the Azure resource name, and every one of those handles spaces differently β€” the string in this screenshot is a good pattern: no spaces, no diacritics, and the machine prefix FunctionApp1 kept separate from the project name.
97

Additional information β€” worker, trigger, Azurite and auth level

Part 8 · Visual Studio

The third and last page of the wizard, and the one that decides how the function actually runs. Five settings, all of them pre-set to reasonable defaults, two of which are worth understanding before Create is pressed.

  1. The page is titled Additional information, and the red box encloses every control on it β€” the template strip at the top still reads Azure Functions with C#, Azure and Cloud, exactly as chosen in steps 95 and 96.
  2. Functions worker β€” .NET 8.0 Isolated (Long Term Support). Two things in that string: the runtime (.NET 8, an LTS release, so supported for years rather than months) and the hosting model (Isolated, the worker running in its own process instead of inside the Functions host β€” the model Microsoft now recommends for new projects).
  3. Function β€” Http trigger. This is the starter function written into the new project: an endpoint reachable over HTTP, which is also the easiest way to prove the deployment works β€” open it in a browser and you either get a response or you do not. The dropdown also offers the other trigger types, and a project with no function at all.
  4. Use Azurite for runtime storage account (AzureWebJobsStorage) β€” ticked. The Functions runtime insists on a storage account even when your code never touches one, for its own bookkeeping. Azurite is the local emulator that stands in for it, so development needs no Azure Storage resource and no connection string. Unticking it would point AzureWebJobsStorage at real storage instead.
  5. Enable container support β€” clear. It adds Docker/container publishing assets to the project; irrelevant for a project heading for an Azure Functions app deployed from a pipeline.
  6. Authorization level β€” Function: the HTTP endpoint requires a function-specific key in the query string or header. The alternatives are Anonymous (open to anyone who knows the URL) and Admin (the master key). Function is the practical middle: callable from the pipeline's smoke test with a key, not callable by accident.
  7. Enlist in .NET Aspire orchestration β€” clear. Ticking it wires the project into an Aspire app host for local orchestration; a single function app does not need it.
  8. Every label carries an ⓘ β€” hover any of them for the same explanation in one sentence, which is faster than searching the docs for a setting you are about to change once.
  9. Create β€” the second red box, around Back and Create β€” is the end of the wizard. It writes the project, restores its NuGet packages and opens the solution in the IDE. Back returns to the path page from step 96 with these values preserved.
Additional information page showing .NET 8.0 Isolated worker, Http trigger, Azurite checkbox ticked, container support unticked, Function authorization level, Aspire unticked, and Create outlined in red

Step 97 β€” .NET 8 Isolated, Http trigger, Azurite on, auth level Function, then Create.

πŸ’‘ Tip: Keep the Azurite box ticked while the project is still local. Azurite is installed with the Azure development workload, so if a first debug run fails while the error mentions AzureWebJobsStorage, the emulator is the thing to check β€” not the function code.
98

The solution opens β€” Function1.cs, Program.cs and the run button

Part 8 · Visual Studio

Create did its work: the IDE is up with a solution, a namespace, one file of generated code and a green button that can start the whole thing. Everything on this screen came out of the wizard, which makes it a good moment to check the wizard's choices actually landed.

  1. The menu bar is the full IDE one β€” File Β· Edit Β· View Β· Git Β· Project Β· Build Β· Debug Β· Test Β· Analyze Β· Tools Β· Extensions Β· Window Β· Help β€” and the solution name FunctionApp1ProyectoVeeduriaBG001 sits in the title area beside the search box, so this window is bound to that solution rather than being an empty shell.
  2. The open document is Function1.cs, highlighted on its tab, with the project tab beside it. Under the tabs the breadcrumb reads project › FunctionApp1ProyectoVeeduriaBG001.Function1 › logger, tracking the caret down to the member it currently sits in.
  3. The four using directives describe the hosting model chosen in step 97: Microsoft.AspNetCore.Http, Microsoft.AspNetCore.Mvc, Microsoft.Azure.Functions.Worker and Microsoft.Extensions.Logging. The Functions.Worker namespace β€” not WebJobs β€” is the isolated model's signature.
  4. The class is Function1, in namespace FunctionApp1ProyectoVeeduriaBG001 β€” the project name from step 96 reused as the root namespace, exactly as warned there.
  5. The constructor takes an ILogger<Function1> and stores it in _logger. That is dependency injection, available because the isolated worker runs its own host (started from Program.cs) instead of being instantiated by the legacy Functions runtime.
  6. The method carries two attributes: [Function("Function1")], which is the name the runtime registers, and [HttpTrigger(AuthorizationLevel.Function, "get", "post")], which is where the Function authorization level and the GET/POST verbs chosen in step 97 appear in code.
  7. The body is the template's two lines: it logs “C# HTTP trigger function processed a request.” and returns new OkObjectResult("Welcome to Azure Functions!") β€” a 200 with that string. A dull response, and precisely what makes it useful as a smoke test.
  8. The faint grey hints above the usings, the constructor and the method β€” 4 references, 0 references, 1 reference β€” are CodeLens. They are links, not comments: clicking one opens Find All References for that symbol.
  9. Solution Explorer on the right lists what the template produced: Connected Services, Dependencies, Properties, .gitignore, Function1.cs, host.json, local.settings.json and Program.cs. Program.cs is the worker's entry point, host.json holds host-level configuration, and local.settings.json is where the Azurite connection string from step 97 lives β€” it is also the file that must never be committed, which is what the generated .gitignore is for.
  10. In the toolbar, the debug target reads Debug, the platform Any CPU, and the green start button is labelled with the project name β€” the third area outlined in red. That button (or F5) starts the Functions host locally; the first run is what needs Azurite alive.
  11. Diagnostic Tools is docked at the far right and GitHub Copilot appears in the top-right corner. Neither is required for the next steps.
Visual Studio with Function1.cs open showing the isolated worker template code, Solution Explorer listing Program.cs, host.json and local.settings.json, and the start button outlined in red

Step 98 β€” Generated project: Function1.cs, the isolated-worker usings, and the run button.

πŸ’‘ Tip: Read local.settings.json once before running anything. With the Azurite box ticked in step 97, AzureWebJobsStorage should read UseDevelopmentStorage=true β€” if it holds a real connection string instead, the wizard choice did not stick and the local run will try to reach Azure.
99

The Debug Console β€” the local URL and one successful request

Part 8 · Visual Studio

Pressing F5 starts the Functions host and opens this window, which is where the whole lifecycle of a local run is printed. It reads top to bottom like a timeline: tool versions, project found, worker started, endpoint published, one request served, session stopped.

  1. The window is the Microsoft Visual Studio Debug Console β€” Visual Studio's own console for the debuggee, not the Windows PowerShell window from steps 90–91 and not a browser tab.
  2. The banner names the two components doing the work: Core Tools Version: 4.0.5530 … (64-bit) and Function Runtime Version: 4.28.5.21962. These are the local Azure Functions Core Tools β€” the same host that runs in Azure, running here β€” installed with the Azure development workload.
  3. Found K:\ProyectoVeeduriaBG001\FunctionApp1ProyectoVeeduriaBG001\FunctionApp1ProyectoVeeduriaBG001\FunctionApp1ProyectoVeeduriaBG001.csproj. The doubled folder from step 96, plus the project file name repeating it a third time β€” the preview line in that step was telling the truth. Using for user secrets file configuration means local settings are read from the user-secrets store when present.
  4. Azure Functions .NET Worker (PID: 23480) initialized in debug mode. Waiting for debugger to attach… then Worker process started and initialized. β€” the isolated model in action: a separate .NET process is started, and the debugger binds to it rather than to the legacy in-process runtime.
  5. The Functions: table lists what the host published, one line per function. The red box marks the only line that matters here: Function1: [GET,POST] http://localhost:7018/api/Function1 β€” the verb pair from the HttpTrigger attribute and the address to call. Port 7018 is the project's local HTTP port, not a fixed number.
  6. For detailed output, run func with --verbose flag. is advice for the command-line workflow; inside Visual Studio the verbosity is controlled by the debug settings instead.
  7. Host lock lease acquired by instance ID '0000…0CF8AE546'. β€” proof the storage dependency from step 97 resolved. The host takes a lock in the storage account, and with Azurite ticked that account is the local emulator.
  8. At 04:36:09 the request arrives: Executing 'Functions.Function1' with the reason “This function was programmatically called via the host APIs” and a GUID as the invocation id.
  9. Three lines later the run is accounted for: C# HTTP trigger function processed a request. β€” the template's own LogInformation call, so the code executed β€” then Executing OkObjectResult, writing value of type 'System.String', and finally Executed 'Functions.Function1' (Succeeded, … Duration=332ms). A 200 with the welcome string, in a third of a second.
  10. From 04:36:22 the session ends: Language Worker Process exited. Pid=23480., dotnet.exe exited with code -1 (0xFFFFFFFF), a fresh worker initialising, and finally dotnet.exe (process 16312) exited with code -1 with Press any key to close this window. Stopping the debugger kills the worker, and that is what code -1 means here β€” not an exception in the function.
  11. The practical takeaway sits in the red box: while the session runs, that URL answers on this machine, which is the fastest way to prove the project is sound before any pipeline or Azure resource is involved.
Visual Studio Debug Console showing Core Tools and runtime versions, the found csproj path, worker initialization, the Functions table with the localhost URL outlined in red, a successful request and the session exit codes

Step 99 β€” Core Tools starts, publishes the URL, serves one request in 332 ms, then stops.

πŸ’‘ Tip: The URL lives exactly as long as the debug session. Stop it with Shift+F5 and the port stops answering, so a browser refresh afterwards returns a connection error rather than a 404 β€” that is the port being closed, not a broken function. Read the port from this window each run instead of reusing an older one.
100

The browser answers β€” Welcome to Azure Functions!

Part 8 · Visual Studio

The last loop closes: a browser asks the local host for the route printed in step 99, and the string written in Function1.cs comes back as the entire page. Nothing else is on screen because nothing else was returned.

  1. Open a normal browser tab β€” the same Edge session used throughout the guide β€” and paste the address from the Debug Console, not a remembered one: localhost:7018/api/Function1. No scheme is shown, so the browser is defaulting to http://, which is what the local host serves.
  2. Where the padlock would normally be, this address shows a circled ⓘ β€” the browser's way of saying there is nothing to certify about a plain-HTTP address on the local machine. Expected here, and not a failure.
  3. The response is one line in a monospaced font: Welcome to Azure Functions! The monospace is the browser rendering a text/plain body β€” the function returned a bare string, so there is no HTML, no styling and no page to speak of.
  4. That string is the literal argument from step 98's code: return new OkObjectResult("Welcome to Azure Functions!");. Matching text means the whole chain ran β€” compiled assembly, isolated worker, host routing, trigger binding, your method, and back out as HTTP.
  5. The status code is not displayed, which is normal for a plain-text body. If it matters, the Debug Console already said it: Executed 'Functions.Function1' (Succeeded …) in step 99, and a non-200 would have shown as a failure there instead.
  6. The URL carries no ?code= parameter, yet it answered. Worth remembering when this route is called on Azure rather than on this machine: step 97 chose AuthorizationLevel.Function, so the deployed endpoint expects a function key that this local call did not need to supply.
  7. Now the more useful test: change nothing in the address and reload once more while the debug session is still running. A second identical answer, with a second Executing … Executed pair in the console, confirms it is a live host and not a cached response.
Browser address bar showing localhost:7018/api/Function1 with an information icon, and the page body reading Welcome to Azure Functions in monospace

Step 100 β€” The local endpoint returns the string from Function1.cs.

πŸ’‘ Tip: If this page shows a connection error instead, check the order of events rather than the code: the debug session from step 99 must still be running, and the port in the address bar must be the one the console printed on this run. A stopped session closes the port, which looks identical to a broken function from the browser's point of view.
Part 9 of 11

Commit, push and the pipeline wizard

Steps 101–112 · the first commit, the push, and the wizard from Connect through to Review.

101

SourceTree β€” 18 staged files, and the .vs folder among them

Part 9 · Pipeline wizard

The new project is now code, and code has to be committed before a pipeline can ever see it. SourceTree reports 18 files ready to go β€” which is correct for a brand-new solution, except that a good half of them should never have been picked up in the first place.

  1. The window is SourceTree on the repository cloned back in step 60: ProyectoVeeduriaBG001 in the title bar and its tab. The toolbar offers Commit Β· Pull Β· Push Β· Fetch Β· Branch Β· Merge Β· Stash Β· Discard Β· Tag, and the workspace sidebar on the left lists File Status (active), History, Search, BRANCHES, TAGS, REMOTES and STASHES.
  2. The red box encloses the staging area. It is titled Pending files, sorted by file status, and it reports Staged files (18 files) above and Unstaged files (0 files) below β€” every change belongs to this first commit, and nothing is hanging outside it.
  3. Sixteen of the eighteen rows are visible with two more below the fold, all carrying the green + that means added (a new file, not a modification). The pane on the right reads Select a file to view the diff because no row has been clicked yet.
  4. The paths start with FunctionApp1ProyectoVeeduriaBG001/ twice before the file name β€” repository root › solution folder › project folder, exactly the nesting the preview line described in step 96.
  5. The files that matter are easy to pick out of the list: FunctionApp1ProyectoVeeduriaBG001.sln, .gitignore, Function1.cs, FunctionApp1ProyectoVeeduriaBG001.csproj, host.json and Program.cs. That is the whole project as generated in steps 95–97: solution, project file, one function class, host configuration and the worker entry point.
  6. The rest of the list is .vs/: DesignTimeBuild/.dtbcache.v2, FileContentIndex/*.vsidx, v17/.futdcache.v2, v17/.suo, v17/DocumentLayout.json and the three ProjectEvaluation/*.bin files. These are Visual Studio's local caches and per-user state β€” they are rewritten every time the IDE opens, they differ per machine, and they have no business in a shared repository.
  7. They are staged here for one mundane reason: the files existed before the .gitignore could apply, and that .gitignore is itself part of this same first commit. Ignore rules only govern untracked files, so anything already staged stays staged.
  8. The fix is on this screen, before committing: click a .vs row, then Unstage Selected (top right of the box), or Unstage All and re-add only the six real files. The count in Unstaged files will grow as the cache entries drop out of the commit.
  9. One absence is worth noticing: local.settings.json is not in the eighteen. The template's .gitignore excluded it, which is the behaviour step 98 warned about β€” connection strings stay on the machine.
  10. At the bottom, the commit panel is already filled in: the author Veeduria Ciudadana IBG <veeduriaibg@gmail.com> from the SourceTree preferences set in step 51, and the message First C# Rest Project is created for demo purposes. The checkboxes Push changes immediately to and Amend latest commit are both clear, so this commit will be local only.
  11. Commit β€” the last element outlined in red, bottom right β€” records the snapshot in the local repository. Nothing reaches Azure DevOps until a push, so the commit is still reversible at this point.
SourceTree File Status view listing 18 staged files for the new Functions project, including .vs cache files, with the staging area outlined in red and the Commit button outlined at bottom right

Step 101 β€” Eighteen staged files: six source files and a pile of .vs caches.

πŸ’‘ Tip: Unstage the .vs/ entries before committing. Left in, every future session shows them as modified, diffs fill with binary noise, and .suo carries machine-specific state that will conflict the moment a second developer opens the solution. If a cache file has already been committed in an earlier push, it has to be removed from the index as well as ignored β€” deleting it from disk is not enough.
102

History β€” the first commit, tagged master and origin/master

Part 9 · Pipeline wizard

After the commit, File Status empties and History fills: one row, one node in the graph. The interesting part is not the row but the two small labels sitting on it, and what their presence β€” or absence β€” means.

  1. The sidebar now has History selected instead of File Status, and under BRANCHES the single entry master is in bold, meaning it is the branch currently checked out.
  2. Above the graph, three controls set the view: All Branches, Show Remote Branches (ticked, which is why a remote label can appear at all), and Date Order for sorting. The Jump to box on the right searches the log.
  3. The graph holds exactly one commit: a single node on a single line, still labelled First C# Rest Project is created for demo purposes. β€” the message typed in step 101.
  4. Two chips ride on that node. master (with a monitor icon) is the local branch. The second chip, origin/master, is the remote-tracking branch β€” a local bookmark of where Azure DevOps last left the branch.
  5. Because this is the repository's very first commit, origin/master can only sit on it after a push. Seeing it means the commit has already been published; seeing only master would mean it is still local, which is what the unticked Push changes immediately box in step 101 would normally leave behind.
  6. The row also carries the date 11 May 2025 23:43, the author Veeduria Ciudadada… and the abbreviated hash 9c26037. Nine-forty-three in the evening local time is 04:43 UTC the next day, which places it a few minutes after the test request logged at 04:36:09Z in step 99 β€” the commit followed the successful run, as it should.
  7. The lower-left pane is the commit itself: Commit: 9c26037991ae1a41776afc57f65390f5c69f [9c26037], Author: Veeduria Ciudadana IBG <veeduriaibg@gmail.com>, Date: Sunday, May 11, 2025 11:43:09 PM, Committer: Veeduria Ciudadana IBG, then the message. The full hash is what a pipeline log or a git revert will ask for; the short one is what the graph shows.
  8. Below it, the file list of the commit reports (18 files) β€” the same eighteen from step 101, .vs/ caches included. That means the commit went in as it stood, without unstaging the caches first.
  9. With …/DesignTimeBuild/.dtbcache.v2 selected, the right-hand pane says No changes in this file have been detected, or it is a binary file or it is configured to be ignored by the File patterns, and both Open Before and Open After are greyed out. There is no diff to show because there is no text to compare β€” the file is a binary cache.
  10. That pane is the practical argument against committing .vs/: eighteen files per commit where six carry meaning, a history that cannot be reviewed, and a repository that grows for reasons unrelated to the code.
SourceTree History view with a single commit labelled master and origin/master, the commit details block with hash and author, the 18-file list and a binary file selected showing no diff

Step 102 β€” One commit, two branch labels, eighteen files and a binary diff.

πŸ’‘ Tip: The two chips are the quickest sync check available. master alone means local work not yet pushed; origin/master on the same node means both sides agree; origin/master one commit behind means someone else pushed. On the commit that a pipeline is about to build, that difference is the first thing to look at when a build runs older code than expected.
103

Back in DevOps β€” Repos › Files shows the pushed commit

Part 9 · Pipeline wizard

SourceTree showed the commit locally; this is the same commit seen from the other end. The round trip is closed when the hash matches on both sides, and that is the only thing this screen needs to prove.

  1. The breadcrumb in the top bar reads veeduriaibg / ProyectoVeeduriaBG001 / Repos / Files / ProyectoVeeduriaBG001 β€” organization, project, and the repository whose name repeats the project's, because it was created with it in step 38.
  2. In the left rail, Repos is the active section (first red box) and inside it Files is selected (second red box, with the pointer on it). The sub-items under it β€” Commits, Pushes, Branches, Tags, Pull requests, Advanced Security β€” are the same repository viewed as history, events, refs and reviews.
  3. The middle panel is the repository selector, showing the git icon and the repository name, with the folder that was pushed already listed beneath it.
  4. Above the file table: the branch selector on master, a path breadcrumb sitting at / (the root), the Type to find a file or folder… box, the Files heading and two buttons β€” Set up build (blue) and Clone.
  5. The Contents and History tabs sit under the heading. Contents is active, which is why the table lists a folder rather than commits; History is the tab that lists the commit log.
  6. The table has three columns β€” Name (sortable), Last change, Commits β€” and one row, outlined in red: the folder FunctionApp1ProyectoVeeduriaBG001, last changed 3m ago, last touched by commit 9c26037 with the message First C# Rest Project is created for demo purposes. and the author Veeduria Ciudadana IBG.
  7. That hash is the one from SourceTree in step 102, and it is the whole check: the push worked, and the origin/master label seen there was not stale.
  8. The row is a folder, not a file, because the working copy root sits one level above the solution β€” the same nesting as step 96. Opening it reveals the .sln, the second FunctionApp1ProyectoVeeduriaBG001 folder and, inside it, the .vs/ caches that went in with the commit.
  9. Set up build is the shortest route from code to pipeline: it points the Azure Pipelines wizard at this repository directly, instead of starting from the Pipelines section and choosing a source. Clone repeats the URL copy from step 41 β€” worth noting as the third place it can be retrieved, alongside the empty-repo page and SourceTree's remotes.
  10. The expand icon at the right edge of the table opens the view full width. Handy on the following screens, where the wizard runs.
Azure DevOps Repos Files view of ProyectoVeeduriaBG001 on master, with the pushed project folder listed against commit 9c26037, and the Repos and Files entries outlined in red

Step 103 β€” Same commit, other side: 9c26037, three minutes old.

πŸ’‘ Tip: If this page is empty while SourceTree shows a commit, the push never happened β€” the commit exists only on the machine. Check Branches here first: a missing master means nothing was published, whatever the local history says.
104

Pipelines β€” the empty state and Create Pipeline

Part 9 · Pipeline wizard

The code is in the repository, so the pipeline can be pointed at it. Like the agent pool back in step 86, this section starts on an empty state β€” the difference is that this one has exactly one call to action.

  1. The URL is dev.azure.com/veeduriaibg/ProyectoVeeduriaBG001/_build and the breadcrumb reads Azure DevOps › veeduriaibg › ProyectoVeeduriaBG001 › Pipelines β€” the project's own Pipelines section, not the organization-wide one.
  2. In the left rail, Pipelines is the active section (first red box) and inside it the Pipelines page (second red box) is selected. Its two siblings, Environments and Library, sit right below: the first holds deployment targets, the second holds shared variable groups and secure files for later use. Neither is needed to create a build.
  3. The page body is an empty state, not an error: an illustration of a build in progress and the heading Create your first Pipeline with the line Automate your build and release processes using our wizard, and go from code to cloud-hosted within minutes. Nothing has ever been built in this project, so there is no list to show.
  4. The single button underneath is Create Pipeline (outlined in red with the pointer on it), and it is the only thing on the page that does anything. On the next screen the wizard asks where the code lives β€” Azure Repos Git, GitHub, Bitbucket or another Git host; since the repository created in step 38 is in this same organization, the first option is the right one.
  5. From there the wizard lists the repositories of the chosen source, asks for the repository (ProyectoVeeduriaBG001), and offers a starter template or an existing YAML file β€” the branch selector defaults to the default branch, which is the master that carries commit 9c26037 from step 103.
  6. Once a pipeline has run, this same page stops being an empty state and becomes the list of pipelines with their recent runs, results and durations. The illustration disappears for good.
  7. Test Plans and Artifacts further down the rail are separate services and play no part in this walkthrough; leaving them alone costs nothing.
Azure DevOps Pipelines section of ProyectoVeeduriaBG001 showing the Create your first Pipeline empty state with the Create Pipeline button outlined in red

Step 104 β€” No pipelines yet: one button, one decision.

πŸ’‘ Tip: The wizard's first page also offers a Use the classic editor link at the bottom. That path builds a pipeline through a visual designer instead of a YAML file in the repository β€” it works, but it leaves no reviewable definition in the repo and cannot be edited alongside the code. For a project that already keeps its source in Git, stay with the YAML wizard.
105

“Where is your code?” β€” choose Azure Repos Git

Part 9 · Pipeline wizard

The wizard opens on its first step, and it opens with a question rather than a form: the source of the code decides everything after it β€” which repositories get listed, how authentication works and what the generated YAML looks like.

  1. The URL is the pipeline designer's own address β€” …/ProyectoVeeduriaBG001/_apps/hub/ms.vss-build-web.ci-designer-hub β€” and the breadcrumb still reads Pipelines, so this is a page inside the project rather than a separate site.
  2. Along the top, the wizard's four steps: Connect (active, underlined) → Select → Configure → Review. The remaining three are greyed because each depends on the answer given here.
  3. Under the New pipeline label, the heading asks the question: Where is your code?
  4. Four sources are offered, each with a grey YAML chip beside its name β€” meaning every one of these paths produces a pipeline definition committed to the repository, not a designer-built definition.
  5. Azure Repos Git (first red box, pointer on it) β€” Free private Git repositories, pull requests, and code search. This is the correct choice for this walkthrough: the repository created in step 38 and pushed to in step 102 lives in this same organization, and ProyectoVeeduriaBG001 is it.
  6. Bitbucket Cloud β€” Hosted by Atlassian. For code on Bitbucket, and it asks for an Atlassian authorization before any repository can be listed.
  7. GitHub β€” Home to the world's largest community of developers β€” and GitHub Enterprise Server β€” The self-hosted version of GitHub Enterprise. Both go through a GitHub OAuth handshake, and the enterprise variant additionally needs a server URL.
  8. Choosing Azure Repos Git avoids all of that: the browser is already authenticated against this organization as the account from step 1, so the next step can list repositories immediately instead of redirecting through a third-party consent screen.
  9. Clicking the card moves the wizard to Select, where the repository (ProyectoVeeduriaBG001) and the branch carrying commit 9c26037 are chosen.
Azure Pipelines wizard on its Connect step asking Where is your code, with Azure Repos Git outlined in red above Bitbucket Cloud, GitHub and GitHub Enterprise Server

Step 105 β€” Connect: pick Azure Repos Git, the organization you are already signed in to.

πŸ’‘ Tip: The YAML chip on every card is the detail worth noticing. It means the definition ends up as a file in the repository β€” reviewable in a pull request, versioned with the code, and reproducible. Choosing a source here is only about where the code is, not about how the pipeline is written.
106

Select a repository β€” one repo, one click

Part 9 · Pipeline wizard

The second step is deliberately dull: pick which repository the pipeline watches. There is exactly one candidate, so the only real decision was already made on the previous page.

  1. The wizard rail has advanced: Connect now shows a ✓ and is no longer underlined, while Select is active β€” the second of four, outlined in red at the top.
  2. The URL carries the previous answer along: …ci-designer-hub?sourceProvider=tfsgit&telemetrySe…. The sourceProvider parameter is how the wizard remembers Azure Repos Git from step 105 β€” get the query string wrong, for instance by reloading an older address, and the wizard starts again at Connect.
  3. Under New pipeline the heading is Select a repository, and the list below it is already filtered to this project.
  4. The filter bar has two halves: Filter by keywords on the left, for narrowing a long list by name, and the project dropdown ProyectoVeeduriaBG001 on the right, with an × beside it that clears the project scope and lists repositories from the whole organization.
  5. The one row is ProyectoVeeduriaBG001, with the red diamond that marks an Azure Repos Git repository β€” second red box, pointer on it. It is the repository cloned in step 60 and pushed to in step 102, the only one in this project, and the only one this pipeline needs.
  6. Click the row and the wizard moves to Configure, reads the repository and offers ways to define the pipeline: a starter template, or an existing YAML file if one is already in the branch.
  7. Nothing here writes anything yet. The first write comes two steps later, when Review saves the generated YAML as a new file in the repository.
Pipeline wizard on its Select step with the heading Select a repository, a keyword filter, the ProyectoVeeduriaBG001 project scope and a single repository row outlined in red

Step 106 β€” Select: the project's only repository, ready to be clicked.

πŸ’‘ Tip: The project dropdown is the one control worth remembering. A pipeline can build a repository from another project in the same organization β€” clear the scope with the × and the list shows them all. If a repo you expect is missing, that filter is usually why.
107

Configure your pipeline β€” the Functions template at the top

Part 9 · Pipeline wizard

Third step, and the wizard has already read the repository: the template at the top of the list is the one that matches the project created in step 97. The choice here decides what the YAML will contain, so it is worth reading the list rather than clicking the first row out of habit.

  1. The rail shows Connect and Select both ticked, with Configure active and Review still greyed β€” two decisions down, one to go.
  2. The heading is Configure your pipeline, and the list below it is the template catalogue for this repository, ordered by relevance rather than alphabetically.
  3. The first entry, outlined in red with the pointer on it, is .NET Core Function App to Windows on Azure β€” Build a .NET Core function app and deploy it to Azure as a Windows function App. It sits at the top because the repository contains a Functions project (the .csproj from step 98), and the wizard matches templates to what it finds.
  4. That title carries three facts worth noticing: it builds with the .NET tooling (.NET Core), it targets a Windows agent (Windows), and it includes a deploy step rather than build-and-test only (to … on Azure).
  5. The templates under it cover other project types and are not applicable here: ASP.NET, ASP.NET Core (.NET Framework), .NET Desktop, Universal Windows Platform, Xamarin.Android and Xamarin.iOS, each described in one line.
  6. Starter pipeline β€” Start with a minimal pipeline that you can customize to build and deploy your code β€” is the blank page: a two-line YAML with a single script step and no knowledge of the project. Everything after that would be written by hand.
  7. Existing Azure Pipelines YAML file β€” Select an Azure Pipelines YAML file in any branch of the repository β€” is for the opposite case, a repository that already carries its definition. This one does not yet; the file is created at the next step.
  8. Show more at the bottom expands the rest of the catalogue, for anything the wizard did not rank.
  9. Choosing the Functions template leads to Review: a complete azure-pipelines.yml with the build, publish and deploy steps already wired for a Windows agent, plus the Azure subscription selector that the service connection from steps 63–73 fills in.
Configure your pipeline page with the .NET Core Function App to Windows on Azure template outlined in red at the top, followed by ASP.NET, .NET Desktop, UWP, Xamarin, Starter pipeline and Existing YAML entries

Step 107 β€” Configure: the Functions template is first because the repo is a Functions project.

πŸ’‘ Tip: The template name is a promise about the agent: Windows on Azure means the generated YAML runs on a Microsoft-hosted Windows image, which is what a Windows function app needs. If the target were a Linux plan the template would have to be edited, not just accepted β€” the agent and the hosting plan have to agree.
108

The template asks for an Azure subscription

Part 9 · Pipeline wizard

Clicking the template opens a small dialog before anything is generated, and it asks exactly one question: which subscription the function app should be deployed into. A deploy step cannot be written without a target.

  1. The dialog title is the template name again β€” .NET Core Function App to Windows on Azure β€” with the same one-line description underneath: Build a .NET Core function app and deploy it to Azure as a Windows function App. Title and description repeating the list entry confirms the right template was clicked, which matters when several of them start with .NET.
  2. An × at the top right closes the dialog without generating anything; the wizard returns to the template list from step 107.
  3. Below the description sits the only field: Select an Azure subscription, then a single radio option (outlined in red, pointer on it).
  4. The option reads Azure subscription 1, and under it the identifier 0e9f2fce-a0ea-4f4d-9efa-f7113d99c02… β€” the subscription's GUID, clipped by the dialog's width.
  5. That GUID is the one to verify, not the display name. Azure subscription 1 is the default label Azure gives a subscription, so a second subscription in the same tenant could carry the identical name; the GUID is what disambiguates it, and it is what the portal shows in Subscriptions for the subscription opened back in step 22.
  6. The list is short because the wizard only offers subscriptions the current identity can deploy into, through the Azure Resource Manager service connection created in steps 63–73. One entry here means one connection covers one subscription.
  7. Selecting the radio is what lets the generated YAML name a subscription and a service connection in its deploy task. The dialog then continues to the deployment target itself β€” the function app the release should land in β€” before handing over to Review.
Dialog titled .NET Core Function App to Windows on Azure with a Select an Azure subscription field and one radio option named Azure subscription 1 with its GUID, outlined in red

Step 108 β€” One subscription offered, identified by GUID rather than by name.

πŸ’‘ Tip: An empty list or a failed load here is almost never the template's fault: it means the signed-in identity cannot see any subscription through the service connection. Re-check Project Settings › Service connections β€” the connection from step 73 is what this dialog is reading.
109

Select the subscription and Continue

Part 9 · Pipeline wizard

The same dialog one click later. Two things changed: the radio is filled, and the button that commits the choice has appeared at the bottom right. Then the wizard has everything it needs to write a deploy step.

  1. Title, description and Select an Azure subscription are unchanged from step 108 β€” only the content inside the field is different, which is how you can tell nothing else was edited by accident.
  2. The radio beside Azure subscription 1 is now a filled blue dot instead of an empty circle, and the row is still outlined in red with the pointer at its right edge: this is the selected state.
  3. With the row selected the identifier reads in full β€” 0e9f2fce-a0ea-4f4d-9efa-f7113d99c023, where the previous step showed it clipped after …c02. Same GUID, finally visible end to end.
  4. Continue β€” the blue button at the bottom right, outlined in red β€” is the dialog's single action. Before a subscription is chosen there is nothing to continue to; after it, the wizard can resolve a deployment target in that subscription.
  5. The selection is exclusive: radios, not checkboxes. One pipeline deploys to one subscription, and that subscription ends up written into the generated YAML as the service connection the deploy task uses.
  6. Nothing has been created or deployed at this point. The dialog records a decision; the first real artifact is still the YAML reviewed in the last step of the wizard.
  7. Clicking Continue closes the dialog and returns to the wizard, which moves on to Review with the subscription and the template's build-and-deploy steps already filled in.
Same subscription dialog with Azure subscription 1 selected and the full GUID visible, and the Continue button outlined in red at the bottom right

Step 109 β€” Selected: radio filled, GUID in full, Continue enabled.

πŸ’‘ Tip: Record which subscription this was. Changing it later is not a click in this dialog β€” it means editing the subscription and service-connection fields in azure-pipelines.yml, or switching the connection the pipeline authorizes against. Choosing correctly here is the cheap moment.
110

Microsoft sign-in β€” the same account, asking again

Part 9 · Pipeline wizard

Continue hands off to Microsoft's own sign-in page. It looks like a step backwards, since the browser has been signed in since step 1, but the wizard is asking for something Azure DevOps cannot vouch for: permission to look inside an Azure subscription.

  1. The page is Microsoft's β€” the four-square logo and the word Microsoft at the top left, a white card on grey. That branding matters: any sign-in screen asking for this password inside Azure DevOps that does not look like this is not Microsoft.
  2. The heading is Sign in, with the account field already filled and outlined in red: veeduriaibg@gmail.com, caret at the end of the address.
  3. It is the same identity used from step 1 onwards, and the same mailbox shown as the commit author in steps 102 and 103 (Veeduria Ciudadana IBG <veeduriaibg@gmail.com>). Consistency here is what keeps the subscription visible: a different account would sign in successfully and then find nothing it is allowed to deploy.
  4. Can't access your account? underneath is Microsoft's recovery link β€” irrelevant for this walkthrough, and not a way to switch accounts.
  5. Next (blue, outlined in red, pointer on it) submits the address and moves to the password prompt; with MFA or a passwordless setup it becomes the send a code flow of steps 2–4 instead.
  6. Note what this is not: it is not the personal access token from step 89. The PAT authenticates a machine β€” the agent β€” against the organization. This sign-in authenticates a person against Azure Resource Manager, so the wizard can list subscriptions and, later, the function app to deploy into.
  7. Once the sign-in completes, the window closes by itself and the wizard resumes where it left off: the subscription is confirmed and the pipeline definition is generated.
Microsoft Sign in page with the account veeduriaibg@gmail.com entered in the outlined field and the Next button outlined in red

Step 110 β€” Re-authenticating to Azure with the account from step 1.

πŸ’‘ Tip: If the browser offers to reuse an existing session for a different Microsoft account, decline and type the address instead. Signing in as a second identity is the usual reason a subscription list turns up empty two steps later, and the failure looks like a permissions problem rather than an account mix-up.
111

Function App name and Working Directory

Part 9 · Pipeline wizard

With the subscription settled, the same dialog asks the two remaining questions: which function app to deploy into, and which folder on the build agent holds the project. Both answers are copied into the generated YAML.

  1. Title and description are unchanged β€” .NET Core Function App to Windows on Azure, Build a .NET Core function app and deploy it to Azure as a Windows function App. The dialog has scrolled to its second section rather than opened a new one.
  2. The first red box encloses Function App name and, immediately below, the Working Directory label β€” the two fields that decide where the deploy goes and what gets deployed.
  3. Function App name β€” a dropdown, currently on FunctionApp120250511012141. It lists the function apps the wizard found in the selected subscription, and this name is the tell-tale auto-generated one: FunctionApp1 plus 2025 05 11 01 21 41, the timestamp of when that app was created in Azure. It is the existing app, not a new one.
  4. That name matters beyond this dialog: it becomes the app name the deploy task targets, so a mismatch is how a correct build ends up deploying to the wrong app β€” or to nothing.
  5. The second dropdown is Working Directory, reading $(System.DefaultWorkingDirectory)/FunctionApp1ProyectoVe…, clipped by the field width. The left half is a pipeline variable: System.DefaultWorkingDirectory is the folder where the agent checked out the repository.
  6. The rest of the path repeats the nest from step 96 β€” solution folder, then project folder β€” so the full value resolves to the directory that actually contains FunctionApp1ProyectoVeeduriaBG001.csproj. That is what dotnet publish needs; a directory one level too high would find the .sln and produce nothing deployable.
  7. Back (grey) returns to the subscription section without losing either value. Validate and configure β€” blue, outlined in red with the pointer on it β€” is the commit point of the whole wizard: it checks the chosen app and connection, generates the YAML and moves to Review.
  8. A validation failure here is almost always one of two things: the app name does not exist in that subscription any more, or the service connection is not authorized for it. The message will name the subscription, not the app.
Dialog second section with Function App name set to FunctionApp120250511012141 and a Working Directory starting with System.DefaultWorkingDirectory, plus Back and Validate and configure outlined in red

Step 111 β€” Target app and working directory, then Validate and configure.

πŸ’‘ Tip: Read the app name before clicking anything. Auto-generated names with embedded timestamps are indistinguishable at a glance, and a subscription often holds several of them. Compare the name against the Function App resource in the portal β€” the one from steps 27–32 β€” rather than trusting the dropdown's first entry.
112

“Configuring resources and generating YAML”

Part 9 · Pipeline wizard

A waiting state, and the only one in the wizard with nothing to click. It is worth one screenshot because it is where the wizard stops asking questions and starts writing the file.

  1. The page context is unchanged β€” breadcrumb Azure DevOps › veeduriaibg › ProyectoVeeduriaBG001 › Pipelines, same left rail, same New pipeline eyebrow above the heading Configure your pipeline.
  2. In the rail, Connect and Select keep their ticks, and Configure is still the active step β€” Review cannot become active until there is something to review.
  3. A single status line sits under the heading, inside the red box: Configuring resources and generating YAML, with a blue progress track underneath it.
  4. That track is an activity indicator, not a measurement β€” it does not tell you a percentage or how long is left. Under it, a second, fainter track is the layout's placeholder for the content that will replace this block.
  5. What is actually happening during these seconds: the wizard resolves the service connection from steps 63–73 against the subscription chosen in step 109, confirms the function app named in step 111, and composes the pipeline definition β€” trigger, Windows agent, .NET build, publish of the artifact and the deploy task that consumes it.
  6. No input is expected. There is no cancel button in this state; navigating away simply discards the wizard, and the next visit starts again at Connect.
  7. When the generation finishes the page replaces itself with the final step: the azure-pipelines.yml text and the Save and run button.
Pipeline wizard on the Configure step showing the status line Configuring resources and generating YAML with a blue progress bar, outlined in red

Step 112 β€” Generating: the wizard has stopped asking and started writing.

πŸ’‘ Tip: A normal wait is a few seconds. If this line sits for minutes, the wizard is usually blocked on Azure rather than busy β€” an expired sign-in from step 110 or a service connection that lost its authorization. Reloading does not resume the wizard, so if it must be redone, keep the choices from steps 109 and 111 to hand and start from Connect.
Part 10 of 11

The YAML definition and the first run

Steps 113–120 · the generated YAML, the deploy task, and the first run from Queued to green.

113

Review your pipeline YAML β€” trigger, pool and build steps

Part 10 · YAML & first run

The last step of the wizard is the most consequential: the file it shows is the pipeline, and nothing has been saved yet. Two lines on this screen need reading before Save and run, and one of them is wrong as generated.

  1. The rail is complete β€” Connect, Select and Configure all ticked, with Review active and underlined (first red box). Under the New pipeline eyebrow the heading reads Review your pipeline YAML.
  2. Top right, the second red box holds two controls: Variables, which manages pipeline variables separately from the file, and Save and run, a split button whose dropdown offers saving without queueing a run.
  3. The file chip names the target: ProyectoVeeduriaBG001 / azure-pipelines.yml, with a * marking it as unsaved and a copy icon beside it. It will be created at the repository root, not inside the nested project folder. Show assistant on the right is the built-in generator β€” unnecessary here, since the file already exists on screen.
  4. Lines 1–4 are the trigger: trigger: branches: include: followed by - main and the comment # or your branch name. Read that comment literally: this repository's branch is master, as steps 102 and 103 showed, so as generated the pipeline would only ever run when started by hand.
  5. Change that one word to - master before saving, or list both branches. That single edit is the difference between a pipeline that reacts to pushes and one that waits to be triggered.
  6. Lines 6–9 decide where the job runs, and they come in two versions. Lines 6–7 are commented out β€” #pool: and # vmImage: 'windows-latest' β€” and lines 8–9 are live: pool: then name: 'VeeduriaAgentPool1' (second red box, on the value).
  7. That pool is the one created in step 76, which means the build will be picked up by DESKTOP-MDE2U74 β€” the agent configured in steps 84–92. It also means the agent has to be Online for the job to start; with the run.cmd window closed, the run simply sits queued.
  8. Swapping the comment β€” comment out the pool name and uncomment vmImage: 'windows-latest' β€” moves the job to a Microsoft-hosted Windows agent instead, with no self-hosted machine involved.
  9. Lines 11–12 declare one variable, buildConfiguration: 'Release', referenced later as $(buildConfiguration). It is the single knob for build configuration, and the reason the argument on line 29 is not hard-coded.
  10. Lines 15–18 install the SDK: UseDotNet@2 with packageType: 'sdk' and version: '8.0.303', commented # Or your actual .NET version. This is what makes the build reproducible β€” the SDK is fetched into the agent's tool cache, so the machine does not need .NET 8 preinstalled.
  11. Lines 20–29 are the build itself: DotNetCoreCLI@2 for command: 'restore', then the same task again for command: 'build', both pointing at **/*.csproj. That glob is why the doubled folder from step 96 does not matter β€” the project file is found wherever it sits.
  12. The editor continues past line 32 with the publish step and the Azure Functions deploy task, which reference the app name from step 111 and the service connection from steps 63–73. Scrolling down is worth it once: that tail is what actually pushes code to Azure.
  13. The editor is editable. Nothing is committed until Save and run, which writes azure-pipelines.yml to the branch and queues the first run.
Review your pipeline YAML step showing the generated azure-pipelines.yml with trigger on main, pool name VeeduriaAgentPool1 outlined in red, buildConfiguration variable and UseDotNet and DotNetCoreCLI tasks, plus Save and run outlined in red

Step 113 β€” Two lines to check: the branch on line 4 and the pool on line 9.

πŸ’‘ Tip: The generated pool block is the surprise on this screen. A wizard that ran on a Microsoft-hosted agent by default has written VeeduriaAgentPool1 instead β€” because a self-hosted pool with agents exists in this organization and takes priority over the hosted image. That is either exactly what you built the agent for, or an outage waiting to happen when that machine goes to sleep.
114

The tail of the file β€” the AzureFunctionApp@2 deploy task

Part 10 · YAML & first run

A zoom on lines 37–45 of the same file: the part the build steps exist to feed. This is where the pipeline stops compiling and starts publishing, and it holds the two strings that decide whether it can.

  1. Line 37 β€” zipAfterPublish: true. The setting belongs to the publish task above it: instead of leaving a folder of DLLs, it zips the output into a single artifact package. Every deploy task downstream consumes a zip, so this is what makes line 44 possible.
  2. Line 38 is blank and Settings above line 39 is the editor's inline hint block, not part of the YAML β€” the same grey Settings label that appears above every task in step 113.
  3. Line 39 β€” - task: AzureFunctionApp@2. The deployment task for Azure Functions, version 2. Everything after it is inputs to that one task.
  4. Line 41 β€” azureSubscription: 'ServiceConnectionName1', outlined in red. This must be the name of an Azure Resource Manager service connection in this project, spelled exactly as it appears under Project Settings › Service connections β€” the connection created in steps 63–73.
  5. The value here is the name the wizard falls back to when it could not bind a specific connection, so it is the line most likely to break the first run. If the real connection carries any other name, the deploy step fails while build and publish stay green β€” a green pipeline with a red deploy is almost always this line.
  6. Line 42 β€” appType: 'functionApp'. The Windows flavour; a Linux plan would read functionAppLinux. It matches the template name from step 107 and the hosted image that template assumed.
  7. Line 43 β€” appName: 'FunctionApp120250511012141', outlined in red, and it is exactly the app selected in step 111, timestamp and all. That consistency is what to preserve if either field is edited: the name must exist inside the subscription named on line 41.
  8. Line 44 β€” package: '$(build.ArtifactStagingDirectory)/**/*.zip'. The variable points at the folder where the agent stages artifacts; the glob picks whatever zip the publish step produced. It needs no editing, and it is why zipAfterPublish matters.
  9. Lines below 44 continue the same input block β€” the deployment method among them. They are worth a glance once, but the two boxed values are the ones that must be true for the task to reach Azure at all.
YAML lines 37 to 45 showing zipAfterPublish true and the AzureFunctionApp at 2 task with azureSubscription ServiceConnectionName1 and appName FunctionApp120250511012141 each outlined in red

Step 114 β€” The deploy task: connection name and app name, both boxed.

πŸ’‘ Tip: Open Project Settings › Service connections in a second tab and compare the name character by character with line 41 before saving. The task selector in the editor also offers the connection from a dropdown, which is safer than typing it β€” the string has to match, including case and any suffix the connection picked up when it was created.
115

The publish task β€” and Save and run

Part 10 · YAML & first run

The same page scrolled to the middle of the file, where the three stages meet: build ends at line 29, publish runs to line 37, deploy starts at line 39. And the button that commits all of it is in the top right corner.

  1. The context is unchanged β€” Review still active, same heading, same azure-pipelines.yml chip with its unsaved *. Only the scroll position differs from step 113.
  2. Lines 25–29 close the build stage: command: 'build' with **/*.csproj and --configuration $(buildConfiguration). Same task, same glob, same variable as before.
  3. Line 31–33 start the third use of DotNetCoreCLI@2, this time with command: 'publish' β€” no longer compiling, but producing deployable output.
  4. Line 34 β€” publishWebProjects: false β€” is the line that makes this work for a Functions project. The task defaults to looking for web projects; with the default left on, a Functions project can be skipped entirely and the publish produces nothing to deploy.
  5. Lines 35–36 β€” the same project glob, and --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory): publish output is written straight to the staging directory the agent hands to the deploy task.
  6. Line 37 β€” zipAfterPublish: true, which is what turns that staging folder into the single archive line 44 later collects.
  7. Lines 39–44 are the deploy task from step 114, unchanged: AzureFunctionApp@2, the service connection, appType, appName and the package glob. Read as a whole, the file is three moves β€” build, publish and zip, deploy β€” and the two boxed strings from the previous step are the only parts that depend on the environment rather than on the code.
  8. Save and run β€” outlined in red, pointer on it, top right β€” performs three actions in order: it commits azure-pipelines.yml to master with an autogenerated message, it creates the pipeline definition that watches the trigger, and it queues run number one from the branch as just committed.
  9. The caret beside the button opens the alternative β€” save without queueing β€” which is the safer choice if the trigger branch or the connection name still need verifying from steps 113 and 114.
  10. After the click the wizard is gone; the page becomes the pipeline's run view, with the job either running on VeeduriaAgentPool1 or waiting for an agent if that pool has none online.
Review your pipeline YAML scrolled to lines 25 to 45 showing the publish task with publishWebProjects false and zipAfterPublish true, and the Save and run button outlined in red

Step 115 β€” Build, publish, zip, deploy: one file, and the button that commits it.

πŸ’‘ Tip: Save and run commits and queues, so a mistake in the file becomes a commit in the history. When in doubt, use the caret to save first, read the committed file under Repos › Files, and start the run from the pipeline page once the trigger branch and the service connection name have been checked.
116

The Save and run dialog β€” commit message and branch

Part 10 · YAML & first run

The button does not commit on its own: it opens this dialog first. Three fields, one of them a choice about where the commit lands, and then the same button name again to confirm.

  1. The dialog is titled Save and run, with a × at the top right that cancels and returns to the YAML editor with nothing committed.
  2. The subtitle states the whole transaction in one line: Saving will commit azure-pipelines.yml to the repository. One file, at the repository root, as described in step 113.
  3. The first red box encloses Commit message and its input, prefilled with Set up CI with Azure Pipelines and focused, caret at the end. That string becomes the actual git commit message β€” the one that will show up in Repos › Commits, in SourceTree's history next to 9c26037, and in the pipeline run's summary.
  4. It is worth replacing with something specific β€” Add Azure Pipelines build and deploy for FunctionApp1 β€” because the default text says nothing about which repository, which app or why.
  5. Optional extended description is the commit body: a multi-line box with the placeholder Add an optional description…. Nothing requires it; it is where the reasoning goes if the message above is necessarily short.
  6. The second red box holds the branch decision. Commit directly to the master branch is selected, and Create a new branch for this commit is the alternative.
  7. The selected option confirms the repository's default branch is master β€” the same name the trigger in step 113 should have been corrected to, and evidence that the mismatch there was in the template, not in the repository.
  8. Choosing the new-branch option instead would commit the YAML to a separate branch and offer a pull request, which is how a team reviews a pipeline definition before it can run. On master it goes straight in, which is fine for a solo setup like this one.
  9. Save and run β€” bottom right, outlined in red, pointer on it β€” confirms: commit, create the pipeline, queue run number one. Nothing else asks for permission after this.
Save and run dialog with the prefilled commit message Set up CI with Azure Pipelines, an optional description box, Commit directly to the master branch selected, and the Save and run button outlined in red

Step 116 β€” One commit message, one branch choice, then the run.

πŸ’‘ Tip: The extended description is not decoration: when a deploy fails a month later, the commit that introduced the pipeline is the first place to look, and a message that names the app and the subscription saves a trip to the YAML to work out what it was for.
117

Run #20250512.1 β€” the job sits Queued

Part 10 · YAML & first run

The wizard is gone and the pipeline exists: this is the run page for its first execution. One job, no duration, and a status word that explains a great deal about self-hosted agents.

  1. The breadcrumb has grown a level: … / ProyectoVeeduriaBG001 / Pipelines / ProyectoVeeduriaBG001 / 20250512.1 β€” project, Pipelines, pipeline, run. The pipeline took the repository's name because the wizard does not ask for one.
  2. The run is numbered #20250512.1 β€” a date-based scheme, 20250512 for the day and .1 for the first run of that day. A second run today would be .2.
  3. The title after the number is Set up CI with Azure Pipelines β€” the commit message from step 116. Each run is titled with the commit that produced it, which is why that message is worth writing carefully.
  4. Under the title, the small branch icon and ProyectoVeeduriaBG001 identify the pipeline definition. Cancel on the right stops a queued or running execution; the ⋮ beside it holds the rest of the run actions.
  5. The Summary tab is active, with Code Coverage beside it β€” that one stays empty until tests emit coverage data.
  6. The card names the reason: Individual CI by Veeduria Ciudadana Informatica y Buen Gobierno. So this run arrived as continuous integration on a commit β€” the commit that Save and run itself created β€” rather than as a manual queue.
  7. Repository and version: ProyectoVeeduriaBG001, branch master, commit 2065d10d. That hash is new β€” not 9c26037 from step 102 β€” because it is the commit that added the YAML file.
  8. View 2 changes at the top right of the card opens the diff this run built from, which is the quickest way to confirm the pipeline ran against the file just reviewed.
  9. Time started and elapsed reads Just now with a dash for elapsed, and Related shows 0 work items and 0 artifacts β€” no work item was linked and the build has not produced an artifact yet because it has not run.
  10. The Jobs table has one row, outlined in red: Job, status Queued, and an empty Duration column. The blue i on the row is where warnings for that job would appear.
  11. Queued is not a failure and not a delay β€” it means the server has accepted the job and is waiting for an agent that matches. The YAML asked for VeeduriaAgentPool1 (step 113), so the job needs DESKTOP-MDE2U74 online in that pool.
Pipeline run 20250512.1 summary page showing Individual CI by Veeduria Ciudadana, master at commit 2065d10d, and a single Job row with status Queued outlined in red

Step 117 β€” Run #20250512.1 accepted; the job is waiting for an agent.

πŸ’‘ Tip: A job that stays Queued for more than a minute or two has exactly two usual causes, both already met in this guide: the pool's agent is offline because the run.cmd window was closed (steps 91–92), or the pipeline has not yet been authorised to use that pool. Reopening the console brings the agent back and the job starts on its own.
118

The agent picks it up β€” status Running

Part 10 · YAML & first run

The same page a moment later, with two status changes and two clocks that finally have numbers. This is what the agent built in steps 84–92 was for: the job left the queue because a matching agent came online.

  1. Beside the run number the icon has changed from a filled dot to a circled spinner β€” the page's own indicator that the run is in progress, updating without a manual refresh.
  2. Everything in the summary card is unchanged: Individual CI by Veeduria Ciudadana Informatica y Buen Gobierno, ProyectoVeeduriaBG001, master at 2065d10d, 0 work items, 0 artifacts, View 2 changes still available.
  3. Time started and elapsed now reads Just now with 41s beside it β€” the clock on the run as a whole.
  4. In the Jobs row the icon has become a spinner too, the status reads Running, and the Duration column shows 29s. The row's name is outlined in red: Job, still the only job, because the generated YAML has one job and no stages.
  5. The two numbers together are informative. Forty-one seconds of run against twenty-nine of job means about twelve seconds passed between the run being created and an agent accepting the work β€” the queue wait. A large gap here is the signature of an agent that was offline and came back.
  6. What the job is doing, in the order the file defines it: checking out the repository at 2065d10d, installing .NET SDK 8.0.303 through UseDotNet@2, restoring NuGet packages, building, publishing to the artifact staging directory, zipping, and finally calling AzureFunctionApp@2 with the service connection and app name from steps 113–114.
  7. Clicking the row opens the log view, where each task reports its own status and its own duration β€” the place to find out which of those seven steps is slow, or which one fails.
  8. Cancel is still in the top right and still works while the job runs; cancelling here also tells the agent to stop, which shows up in its console as the job being abandoned.
  9. On the build machine, the run.cmd window from step 91 is printing the same event from the other side: a job picked up, then per-task output. Two views of one execution.
Pipeline run summary with a spinner in the header, 41 seconds elapsed, and the single Job row outlined in red showing Running and 29 seconds

Step 118 β€” Running: 41s on the run, 29s on the job, agent accepted in between.

πŸ’‘ Tip: The first run on any agent is the slowest of the series, because UseDotNet@2 downloads SDK 8.0.303 into that machine's tool cache. Later runs reuse it and the build stage drops by half a minute or more β€” so judge the pipeline's speed on the second run, not this one.
119

The job log β€” task tree and the UseDotNet output

Part 10 · YAML & first run

Clicking the job row in step 118 opens the inside view: a task tree on the left, live output on the right. The tree is the YAML rendered as a checklist, and the pane beside it is the only place where the build says what it is actually doing.

  1. The page is headed Jobs in run #20250512.1 with the pipeline name ProyectoVeeduriaBG001 and a back arrow that returns to the run summary.
  2. The left column lists every step in execution order, first red box around the whole list: Job (running, 56s) at the top, then Initialize job (30s), Checkout ProyectoVeeduriaBGO… (5s, green tick), UseDotNet (running, 20s, highlighted with a blue bar on its left edge), three separate DotNetCoreCLI entries, AzureFunctionApp, and a Post-job: Checkout ProyectoVeeduri… at the bottom.
  3. Read that tree against the file from step 113 and it maps one to one: the three DotNetCoreCLI rows are restore, build and publish; AzureFunctionApp is the deploy; the two Checkout entries are the repository fetch before the steps and the cleanup after them, which the agent inserts on its own.
  4. The icons carry the state: a spinner while running, a green tick when finished, an empty circle for tasks not yet reached, and a grey clock on Initialize job for a task that completed without drama. The seconds to the right are per task, not cumulative.
  5. The right pane shows the selected task β€” UseDotNet β€” in a dark log with line numbers. The first block is the task's own banner: Task: Use .NET Core, Version: 2.254.1, Author: Microsoft Corporation, and a help link.
  6. Line 9–11 are the decision: Tool to install: .NET Core sdk version 8.0.303, Found version 8.0.303 in channel 8.0 for user specified version spec: 8.0.303, then Version 8.0.303 was not found in cache. That last line is why this run takes longer than the ones after it.
  7. Line 13–16 detect the platform: Detecting OS platform…, then Primary: win-x64 and Detected platform (Primary): win-x64 β€” the Windows agent windows-latest would also have been, and the same architecture the local build used in step 98.
  8. Line 14 is the detail that ties the whole guide together: the command being run comes from C:\Users\ACGR_GIT\Downloads\vsts-agent-win-x64-4.255.0\… β€” the folder the archive was extracted into in step 82 and that run.cmd was started from in step 91. The job is running on that machine, not on a Microsoft-hosted image.
  9. Line 17 is the download itself: https://builds.dotnet.microsoft.com/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-win-x64.zip. The agent fetched the SDK from the internet and will cache it locally for the next run.
  10. The search icon and ⋮ at the top right search this task's log and open its download and raw-view options; the back arrow at the top left of the page returns to the run summary.
  11. The tree keeps moving while the job runs β€” the three DotNetCoreCLI rows are still empty circles here, and the Job total already reads 56s, ahead of the 20s of the task in view.
Job log for run 20250512.1 with the task tree outlined in red on the left and the UseDotNet log on the right showing the SDK not in cache, win-x64 detection and the download URL

Step 119 β€” YAML as a checklist on the left, the running task's output on the right.

πŸ’‘ Tip: Version 8.0.303 was not found in cache is the line to look for when comparing runs. On the second run it reads differently and the stage finishes in seconds β€” so a build that suddenly takes a minute longer than yesterday is usually a cache miss or a blocked download, not slower code.
120

Finished green β€” 7m 53s, and where the time went

Part 10 · YAML & first run

The same job page at the end: every row ticked, the job total filled in, and a last task's log in view. This is also the first honest measurement of the pipeline, and the numbers are not spread evenly.

  1. The Job row now carries a green ✓ and 7m 53s, with its chevron pointing down β€” the row is expanded, which is what puts every task beneath it (whole list in the red box).
  2. Initialize job β€” 30s, grey clock. Workspace preparation and job setup. The clock icon marks a task that ran and finished; the ticks mark tasks whose output declared success.
  3. Checkout ProyectoVeeduriaBGO… β€” 5s, tick. The agent cloned the repository at 2065d10d into its work folder β€” the _work directory accepted in step 90, now filling up on disk.
  4. UseDotNet β€” 1m 8s, tick. That is the SDK download from step 119, and it is the clearest candidate to disappear on the next run once 8.0.303 sits in the agent's cache.
  5. The three DotNetCoreCLI rows β€” 18s, 11s and 7s, in that order. Restore, build and publish: the entire compilation of the solution costs 36 seconds.
  6. AzureFunctionApp β€” 5m 30s, tick, and the outlier. Roughly seventy per cent of the whole run is the deployment, not the build. Zip-deploying to a function app that has not been touched in a while is slow the first time; the same step is usually far quicker on later runs.
  7. Post-job: Checkout ProyectoVee…, Finalize Job and Report build status β€” all under a second. These are the agent's own bookkeeping steps, not anything in the YAML.
  8. The arithmetic is worth doing once: 30s + 5s + 68s + 18s + 11s + 7s + 330s comes to about 7m 49s against a reported 7m 53s. The job total is essentially the sum of its tasks β€” nothing is hidden between them.
  9. Hovering the list shows the start time in local form: Started May 12, 2025 at 12:13 AM GMT-5 β€” half an hour after the commit that created the pipeline, and in the machine's own time zone rather than UTC.
  10. The right pane is the log of the selected Finalize Job, six lines long: Cleaning up task key, Start cleaning up orphan processes, Terminate orphan process: pid (35100) (dotnet), Terminate orphan process: pid (5220) (conhost), then Finishing: Finalize Job.
  11. Terminate in that log is routine cleanup, not a failure: the agent is killing the processes the job left behind β€” the .NET worker and its console host β€” so the next job starts on a clean machine. A green job with these lines is the normal case.
  12. All ticks, one green job, and a run total: this is what a working build-and-deploy pipeline looks like on the self-hosted agent. The function app it deployed to is the one from steps 27–32.
Completed job showing all tasks ticked with durations Initialize 30s, Checkout 5s, UseDotNet 1m8s, three DotNetCoreCLI at 18s 11s and 7s, AzureFunctionApp 5m30s, and a 7m53s job total, plus the Finalize Job log

Step 120 β€” Green end to end: 36s of compilation, 5m 30s of deployment.

πŸ’‘ Tip: Keep the per-task durations as a baseline. When this pipeline slows down later, the stage that grew tells you what changed: UseDotNet means a cache miss, the DotNetCoreCLI rows mean dependency or code growth, and AzureFunctionApp means the deploy target is slow to accept the zip. One number, three very different fixes.
Part 11 of 11

Iterate the YAML and verify the deployment

Steps 121–130 · pulling the service-side commit, editing the YAML, and verifying the deployment in Azure.

121

SourceTree again β€” Pull the commit DevOps just made

Part 11 · Deploy & verify

One thing was committed without this machine being involved: the pipeline file, written by the browser when Save and run executed. The local clone therefore does not have it, and SourceTree says so before anything else happens.

  1. SourceTree is back on ProyectoVeeduriaBG001 β€” same tab, same menu bar, same Commit Β· Pull Β· Fetch Β· Branch Β· Merge Β· Stash Β· Discard Β· Tag row, with Git-flow, Remote, Terminal, Explorer and Settings on the right.
  2. The toolbar carries two small numeric badges: 1 on Commit and 2 on Pull (outlined in red, pointer on it). Those badges are SourceTree's pending-change counters from its last fetch β€” the number on Pull is how many commits are waiting on the remote for this branch.
  3. The remote is not hypothetical here: Save and run committed azure-pipelines.yml to master on the server, so the file exists in Azure Repos and not in the local working copy. That commit is what the badge is counting.
  4. In the sidebar, History is selected and master shows a badge of its own β€” 1 β€” the branch's own indicator of divergence between the local branch and its remote.
  5. The log area is mid-refresh: a spinner dot, the word Loading…, and a placeholder row reading 1 Jan 0001 0:00. That date is not data β€” it is what the grid shows before the first commit arrives. Leave the tab for a second and the real history appears.
  6. The filters above the grid are unchanged from step 102 β€” All Branches, Show Remote Branches ticked, Date Order β€” with the Jump to box on the far right.
  7. Pull downloads the new commits and merges them into the local branch in one action. It asks for confirmation first and reports the incoming commit before finishing.
  8. After the pull, azure-pipelines.yml appears in the working copy β€” at the repository root, one level above the solution folder, exactly where step 113 said it would be created.
  9. That matters for the next edit: the trigger on line 4 still says main while the branch is master. Changing it requires the file locally, so pull first, edit in Visual Studio, commit and push β€” and the pipeline will then react to pushes instead of waiting to be started by hand.
  10. Fetch beside it does half the job: it updates the remote-tracking refs and the badge counts without touching the working copy. Useful for seeing what is waiting before deciding to pull.
SourceTree toolbar with the Pull button outlined in red showing a badge of 2, the History view still Loading, and master selected in the sidebar

Step 121 β€” Pull pending: the pipeline's commit lives on the server, not here.

πŸ’‘ Tip: Pull before editing anything the server touched. A local commit made on top of an out-of-date master is rejected on push as a non-fast-forward, and the fix β€” pulling, merging and pushing again β€” takes longer than the pull would have.
122

The Pull dialog β€” origin, master, and four options

Part 11 · Deploy & verify

SourceTree does not pull silently. It shows exactly where the commits come from, where they are going, and four ways to combine them β€” three of which do nothing at all in this particular case.

  1. The dialog is simply titled Pull, with a close control at the top right that cancels.
  2. Pull from remote: origin. Under it, the URL confirms which remote that is: https://veeduriaibg@dev.azure.com/veeduriaibg/ProyectoVeeduriaBG001/_git/ProyectoVeeduriaBG001 β€” organization, project and repository in one line, with the account name in front of the host.
  3. The credentials are not in that URL, and do not need to be: the Git credential helper chosen in step 55 and confirmed in step 58 supplies them, which is why this dialog can read the remote at all.
  4. Remote branch to pull: master, with a Refresh button beside it that re-reads the remote's branch list β€” the control to use if a branch you expect is missing.
  5. Pull into local branch: master, shown as plain text because the local branch is fixed by what is checked out in SourceTree.
  6. Then the Options group, four checkboxes. Commit merged changes immediately is ticked: if the pull produces a merge, SourceTree commits it without asking for confirmation.
  7. Include messages from commits being merged in merge commit β€” clear. Would copy the incoming commit messages into the body of the merge commit, useful when a merge brings in several unrelated changes.
  8. Create a new commit even if fast-forward is possible β€” clear. This is the --no-ff behaviour: force a merge commit so the history records that a merge happened. Teams use it to keep feature branches visible in the graph.
  9. Rebase instead of merge (WARNING: make sure you haven't pushed your changes) β€” clear. Rebasing replays local commits on top of the remote ones, rewriting their hashes; the warning is there because rewritten history breaks anyone who already has the old commits.
  10. In this pull none of the four changes the outcome. The local branch has no commits of its own beyond 9c26037, so the merge is a fast-forward: the branch pointer simply moves to the new commit and the pipeline file appears, with no merge commit and no option to apply.
  11. Pull β€” blue, bottom right, outlined in red with the pointer on it β€” does the fetch and the merge. Cancel beside it leaves the branch untouched.
SourceTree Pull dialog with origin and its Azure DevOps URL, remote branch master, local branch master, four options with only Commit merged changes immediately ticked, and the Pull button outlined in red

Step 122 β€” Pull from origin/master into master; a fast-forward in this state.

πŸ’‘ Tip: Leave Rebase instead of merge alone unless you are deliberately cleaning up local history. On a branch that has already been pushed β€” as master here has β€” rewriting commits is how a repository ends up needing a force-push that nobody else can reconcile.
123

The file arrived β€” azure-pipelines.yml at the repo root

Part 11 · Deploy & verify

After the pull, the working copy has one more item than it had before. File Explorer is the plainest possible confirmation, and the two timestamps on this screen are worth reading carefully.

  1. The window is File Explorer at This PC › ProyectoVeeduriaBG001, with the ribbon on the File tab β€” the repository root, the same folder SourceTree has been committing from since step 101.
  2. It contains exactly two items. First, the folder FunctionApp1ProyectoVeeduriaBG001, modified 5/11/2025 11:34 PM, type File folder β€” the solution folder created by the wizard in step 96, a few minutes before the first commit.
  3. The second item is azure-pipelines.yml, outlined in red with the pointer on it: modified 5/12/2025 12:17 AM, type YML File, size 1 KB.
  4. Its position is the point: at the root, beside the solution folder rather than inside it. That is where Save and run committed it, exactly as step 113's file chip described, and the pull has reproduced that layout locally.
  5. Because it sits above the solution, Visual Studio's Solution Explorer will not list it. A pipeline definition belongs to the repository, not to a project, so this is normal β€” the file is edited in a text editor, or with Show All Files when the repository root is opened as a folder.
  6. The two dates are two different clocks. 11:34 PM on the folder is when the project was written to disk here; 12:17 AM on the YAML is when the pull wrote this file locally β€” not when it was committed on the server, which was a few minutes earlier.
  7. That is why a file's Date modified cannot be used to date a change: git keeps the commit time, Windows keeps the write time, and a fresh clone makes every file look as if it was modified today.
  8. The 1 KB size is a useful sanity check too β€” the whole pipeline fits in a kilobyte, which is the consequence of everything being expressed in tasks rather than in panels.
  9. The file is now the only copy that matters for the next edit: opening it here is how the trigger on line 4 gets changed from main to master, before committing and pushing from SourceTree.
File Explorer at the ProyectoVeeduriaBG001 repository root showing the project folder from 11:34 PM and azure-pipelines.yml dated 12:17 AM outlined in red, 1 KB

Step 123 β€” One new file at the root: the pipeline definition, 1 KB.

πŸ’‘ Tip: Keep the pipeline file at the repository root and keep it small. YAML is read from the branch that runs, so a copy placed inside the project folder would simply be ignored β€” and if you ever see two files with that name, the one the pipeline uses is the one the run's View changes link points at.
124

The first local edit β€” appName changed, one hunk

Part 11 · Deploy & verify

Back in SourceTree with the pulled file edited: File Status now has something to report, and the diff pane shows exactly what changed β€” one line of the deploy task.

  1. The sidebar is on File Status again, and the workspace is empty in the top half: Staged files (0 files), with Unstage All and Unstage Selected greyed out. Nothing has been added to the index yet.
  2. Unstaged files (2 files) below, with Stage All and Stage Selected enabled. The first row is azure-pipelines.yml, selected and highlighted, and the + at its right edge is the per-file stage button β€” clicking it moves that one file into the staged list.
  3. The second row is FunctionApp1ProyectoVeeduriaBG001/.vs/FunctionApp1ProyectoVeeduriaBG001/v17/.suo β€” Visual Studio's per-user solution options, rewritten every time the solution is opened or closed.
  4. The .suo reappearing here is the direct consequence of step 101: it was committed with the first batch, so git now tracks it and reports every change to it. Untracked-and-ignored would have been silent.
  5. The right pane is the diff for the selected file, headed Hunk 1: lines 40–44 β€” one hunk, because only one contiguous region changed.
  6. The hunk reads as a before-and-after of the deploy task: inputs: and azureSubscription: 'ServiceConnectionName1' in white (unchanged context), appType: 'functionApp' in white, then the change itself.
  7. The red line, prefixed –: appName: 'FunctionApp120250511012141' β€” the timestamp name the wizard wrote.
  8. The green line, prefixed +: appName: 'FunctionAppAppPooloyai' β€” the value it was replaced with, both lines outlined in red in the screenshot.
  9. Everything else in the hunk β€” the package glob on line 44 β€” is context and stays as it was. That is the value of a three-line diff: it proves nothing else in the file was touched by accident.
  10. The effect of the edit is concrete: the deploy task now targets a different function app than the wizard chose in step 111. That app must exist in the subscription named on line 41, or the build will succeed and the deploy step will fail at the end of the run.
  11. To commit it: click the + on the YAML row (or Stage Selected), leave the .suo unstaged, write a message that says which app was retargeted, and commit.
SourceTree File Status with 0 staged and 2 unstaged files, azure-pipelines.yml selected, and the diff pane showing the appName line changed from FunctionApp120250511012141 to FunctionAppAppPooloyai

Step 124 β€” One hunk, one line: the deploy target moved.

πŸ’‘ Tip: Stage files individually with the row's + rather than Stage All. In a repository where .vs/ is still tracked, Stage All quietly re-adds the .suo and every cache file alongside the change you meant to commit β€” and the diff you reviewed stops matching the commit you pushed.
125

Staging the change β€” two files, a rename and a commit message

Part 11 · Deploy & verify

A fuller commit than the first one: two files staged, one function renamed inside the code, and a message that explains the change in prose. Three red boxes mark the three parts of the screen that matter.

  1. Staged files (2 files) β€” first red box. azure-pipelines.yml from the previous step, and FunctionApp1ProyectoVeeduriaBG001/FunctionApp1ProyectoVeeduriaBG001/Function1.cs, both with the pencil that means modified.
  2. Those paths again show the doubled folder from step 96: repository root, solution folder, project folder. The YAML sits at the root; the code sits two levels down.
  3. The diff pane (second red box) is Hunk 1: lines 14–24 of Function1.cs, and it contains exactly two changes.
  4. Line 17, removed and re-added: [Function("Function1")] becomes [Function("FunctionGrupoLoyalVeeduriaIBG")]. This attribute value is the name the Functions runtime registers, so the HTTP route changes with it β€” the endpoint becomes /api/FunctionGrupoLoyalVeeduriaIBG instead of /api/Function1.
  5. Line 22: return new OkObjectResult("Welcome to Azure Functions!"); becomes return new OkObjectResult("Welcome to Azure Functions FunctionGrupoLoyalVeeduriaIBG 12 20!"); β€” same behaviour, but the response now identifies which deployment answered.
  6. Nothing else in the file moved: the constructor, the logger injection and the HttpTrigger attribute with its verbs are all context lines. The rename is the whole change.
  7. Unstaged files (3 files) below holds only .vs contents β€” the .suo and two .vsidx caches, one of them marked with a ? for untracked. They were deliberately left out, which is the practice step 124 recommended.
  8. The commit panel at the bottom (third red box) names the author β€” Veeduria Ciudadana IBG <veeduriaibg@gmail.com> β€” and carries a message written in prose rather than a one-liner.
  9. It is worth reading as a model: a title line “ImplementaciΓ³n inicial de Azure Function: FunctionGrupoLoyalVeeduriaIBG”, then bullet points covering the class and namespace, the HTTP trigger and its Function authorization level, the enabled GET and POST verbs, the 200 response with its message, the ILogger<Function1> injection, the log written on each invocation, the purpose of the function, and finally that it is ready to be deployed and validated in Azure.
  10. For a milestone commit that is the right level of detail: it records why the function exists, which no diff can show, and it survives in the history next to the code it describes.
  11. Under the message box sit the usual two checkboxes β€” Push changes immediately to: origin/master and Amend latest commit. The first makes the commit and the push a single action; with a CI trigger in place, that also starts a run the moment it lands.
  12. Commit β€” bottom right, outlined in red β€” writes the commit locally once the message is accepted.
SourceTree with two staged files outlined in red, a diff showing the Function attribute renamed to FunctionGrupoLoyalVeeduriaIBG and the response string changed, and a detailed Spanish commit message outlined in red above the Commit button

Step 125 β€” Two files staged, the function renamed, and a message that explains it.

πŸ’‘ Tip: Renaming the value inside [Function(...)] changes the route, not just a label. Any client, bookmark or pipeline smoke test still calling /api/Function1 will start getting a 404 after this deploy β€” and the pipeline itself will report success, because a deploy succeeding and an old URL still working are two different claims.
126

Run #20250512.4 β€” same YAML, five minutes seven

Part 11 · Deploy & verify

The fourth run of the day, and the first one after the rename and the retargeting were pushed. Same file, same machine, same tasks β€” and a very different set of numbers, which is the interesting part.

  1. The breadcrumb ends in 20250512.4 β€” the fourth run of this date, so the commit from step 125 triggered its own CI run by pushing to the branch the trigger watches.
  2. Page header: Jobs in run #20250512.4. The tree on the left (red box) is the familiar shape β€” Job, Initialize job, Checkout, UseDotNet, three DotNetCoreCLI, AzureFunctionApp, and the two closing tasks β€” this time all ticked, with Job at 5m 7s.
  3. The right pane is not a task log but the job's own log, and its first five lines are the answers to the questions a task tree cannot answer: Pool: VeeduriaAgentPool1, Queued: Today at 12:43 AM, Agent: DESKTOP-MDE2U74, Started: Today at 12:43 AM, Duration: 5m 7s.
  4. That third line is the one worth reading twice: Agent: DESKTOP-MDE2U74 β€” the self-hosted agent from step 92 by name, not a hosted image. The pool line above it explains why.
  5. The link manage_parallel_jobs beside Queued opens the organization's parallel-job settings β€” the page where self-hosted and hosted capacity is bought or adjusted. Not needed here, but it is the answer to "why is my job queued behind another one".
  6. Now the numbers against step 120's 7m 53s: UseDotNet has gone from 1m 8s to <1s, the three DotNetCoreCLI rows from 18s/11s/7s to 3s/6s/7s, and Checkout from 5s to 2s.
  7. UseDotNet under a second is the cache hit predicted in step 119: SDK 8.0.303 was already on this machine, so the task only checked and returned. The step that dominated the first run has effectively disappeared.
  8. AzureFunctionApp is still the expensive one β€” 4m 45s of the 5m 7s, about ninety per cent of the run. Deploying to Azure, not building, remains the cost.
  9. Line 7, The agent request is already running or has already completed, is the page telling you that the request behind this job is no longer live β€” a normal message when reading a finished job's log.
  10. Line 8, Job preparation parameters, is collapsible and currently closed β€” the line numbers jump straight from 8 to 43, which is the collapsed block, not missing output.
  11. From line 44 the pane shows Job live console data: Starting: Job, a series of Async Command Start: / End: WindowsPreinstalledGitTelemetry pairs, and Finishing: Job. These are the agent's own bookkeeping around the job, not the YAML steps β€” the actual build output lives under each task row, as seen in step 119.
Job log for run 20250512.4 showing all tasks ticked with UseDotNet under a second, AzureFunctionApp at 4m45s, a 5m7s total, and the job log naming pool VeeduriaAgentPool1 and agent DESKTOP-MDE2U74

Step 126 β€” Second-generation run: cached SDK, 5m 7s, deploy still 4m 45s.

πŸ’‘ Tip: Put steps 120 and 126 side by side. The only rows that got dramatically faster are the ones that depended on the machine's cache β€” nothing about the code changed. That is the right way to read a pipeline timing: the agent's state explains UseDotNet and Checkout, the code explains the DotNetCoreCLI rows, and the target app explains AzureFunctionApp.
127

Back in the portal β€” Resources › Recent

Part 11 · Deploy & verify

The pipeline says the deploy succeeded. The portal is where that claim gets checked, and the home page is enough: three recently viewed resources, three different names, one of them the app this all started with.

  1. The top strip is Azure services: Create a resource, App Services, Quickstart Center, Azure AI services, Kubernetes services, Virtual machines, Storage accounts, SQL databases, Azure Cosmos DB and More services. None of them are needed for the next step, but this row is the fastest route to any service on the portal.
  2. Below it, Resources with two tabs: Recent (active, underlined) and Favorite. The first is a per-user list ordered by Last viewed; the second is a curated list of the resources someone pinned.
  3. Three columns: Name, Type and Last Viewed β€” recency, not modification, which is why the times are in minutes.
  4. FunctionAppGrupoLoyal β€” Function App, 15 minutes ago: the most recently opened resource, and the name closest to the function renamed in step 125 (FunctionGrupoLoyalVeeduriaIBG).
  5. AzureResourceGroup1 β€” Resource group, 22 minutes ago. The container the apps live in, opened just before the first of them.
  6. FunctionApp120250511012141 β€” Function App, 35 minutes ago, and outlined in red with the pointer on it. This is the timestamp-named app from step 111, the one the wizard originally targeted, and the reason the number in it looked familiar.
  7. Its presence here is worth a moment: the YAML's appName was later changed to FunctionAppAppPooloyai (step 124). Three different-looking identifiers are now in play β€” the resource name, the function name inside the application, and the appName the deploy step writes to.
  8. Those three do not have to match, and nothing in the portal will warn when they do not. What the deploy requires is narrower and absolute: the value of appName must name a function app that exists in the subscription on the line above it.
  9. See all under the list opens All resources β€” the full inventory across resource groups, which is where to look when a name from the YAML does not appear in this short list.
  10. The next check is to open one of these apps and read its Functions blade: a successful deploy leaves a function there with the new name, and a deploy that silently targeted a different app leaves it absent from the one you are looking at.
Azure portal home showing the Azure services tiles and the Resources Recent list with FunctionAppGrupoLoyal, AzureResourceGroup1 and FunctionApp120250511012141 outlined in red

Step 127 β€” Three recent resources, three names, one app from the original wizard.

πŸ’‘ Tip: Recent is a record of what you opened, not of what the pipeline touched β€” the timestamps are visit times. To confirm a deployment, trust the app's Functions list and its Deployment center history, which record what actually arrived and when.
128

The app page β€” the deployed function is listed and Enabled

Part 11 · Deploy & verify

Opening the timestamp-named app from step 127 answers the only question that matters after a deploy: is the code there, and is it running? One row in one table.

  1. The header names the resource: FunctionApp120250511012141, subtitled Function App β€” the original target the wizard picked in step 111, not the app name later written into the YAML.
  2. The command bar offers Browse, Refresh, Get publish profile, Reset publish profile, Download app content, Delete and a feedback menu, with Stop, Restart and Swap greyed out in this view.
  3. The left menu is the app's full surface: Overview (selected), Activity log, Access control (IAM), Tags, Diagnose and solve problems, Microsoft Defender for Cloud, Events, Recommended services, Resource visualizer, then the collapsible groups Functions, Deployment, Settings, Performance, App Service plan, Development Tools, API, Monitoring, Automation and Support + troubleshooting.
  4. Essentials answers the environment questions in one block: Resource group: AzureResourceGroup1, Status: Running, Location: Australia Central, Subscription: Azure subscription 1.
  5. Subscription ID: 0e9f2fce-a0ea-4f4d-9efa-f7113d99c023 β€” the same GUID selected in steps 108 and 109, and the one the deploy task's azureSubscription line points at through its service connection.
  6. Default domain: functionapp120250511012141.azurewebsites.net, Operating System: Windows (consistent with appType: 'functionApp'), App Service Plan: AustraliaCentralPlan. The runtime version row is obscured in this capture.
  7. Those two facts combine into the deployed URL: https://functionapp120250511012141.azurewebsites.net/api/FunctionGrupoLoyalVeeduriaBG β€” default domain plus the route created by the [Function] attribute renamed in step 125.
  8. The tabs under Essentials are Functions (active), Metrics, Properties and Notifications (0). Functions is the inventory of what the app currently runs.
  9. The table has three columns β€” Name, Trigger, Status β€” and one row, outlined in red: FunctionGrupoLoyalVeeduriaBG, trigger HTTP, status Enabled with a green tick.
  10. That single row is the verification the whole pipeline existed for: the name matches the rename from step 125, the trigger matches the HttpTrigger attribute, and Enabled means the runtime has loaded it and is willing to invoke it.
  11. The Set up local environment dropdown above the table generates the tooling instructions and configuration for running this same function on a workstation β€” the cloud counterpart of steps 98–100.
Azure Function App overview for FunctionApp120250511012141 in AzureResourceGroup1, status Running on Windows, with the Functions tab listing FunctionGrupoLoyalVeeduriaBG as HTTP and Enabled outlined in red

Step 128 β€” Deployed and enabled: the renamed function is live in the app.

πŸ’‘ Tip: Enabled is about the function being present, not about the code being the one you just pushed. For that, open Deployment › Deployment center β€” it keeps a dated history of what arrived and from which pipeline, which is the only reliable answer when two apps and three names are in play.
129

Code + Test, functions.metadata and Get function URL

Part 11 · Deploy & verify

One level down from the app: the function's own page. It looks like an editor, cannot be edited, and holds the single most useful button in the whole portal β€” the URL that actually invokes the thing.

  1. The page is titled FunctionGrupoLoyalVeeduriaBG | Code + Test with the app name FunctionApp120250511012141 underneath β€” the function from step 128, opened.
  2. Its tabs are Code + Test (active), Integration, Function Keys, Invocations, Logs and Metrics. In practice: Integration edits bindings, Function Keys manages the keys behind the URL, Invocations and Logs show what calls came in, Metrics charts them.
  3. The command bar has Save and Discard greyed, Refresh and Get function URL active, Test/Run greyed, then Disable, Delete, Upload and Resource JSON.
  4. The amber banner explains the greyed buttons: “Editing .NET isolated Function Apps is not supported in the Azure portal. Use your local development environment to edit this Function App.” The isolated worker ships a compiled DLL, so there is no portal-editable source β€” a direct consequence of the hosting model chosen in step 97.
  5. The file selector above the code reads FunctionApp120250511012141 / functions.metadata: what is displayed is the runtime's own description of the function, not the C# that produced it.
  6. That JSON is still worth reading, because it is the deployed reality rather than the intended one: “name”: “FunctionGrupoLoyalVeeduriaBG”, “scriptFile”: “FunctionApp1ProyectoVeeduriaBG001.dll”, “entryPoint”: “FunctionApp1ProyectoVeeduriaBG001.Function1.Run”, “language”: “dotnet-isolated”.
  7. Read those four fields together: the entry point is the same Function1.Run method from step 98, inside the namespace taken from the project name, and dotnet-isolated confirms the worker model on the server side as well.
  8. The bindings block repeats the trigger from the source: “name”: “req”, “direction”: “In”, “type”: “httpTrigger”, “authLevel”: “Function”, then the accepted methods. That Function value is the authorization level chosen in step 97 and declared on the attribute in step 98 β€” visible here as configuration the runtime is actually enforcing.
  9. Get function URL β€” outlined in red, pointer on it β€” opens the panel on the right with three ready-made URLs, each with a copy button: _master (Host key), default (Function key) and default (Host key).
  10. They differ only in which key is embedded. The host keys work for every function in the app; the function key works for this one only; the _master key is the administrative key. For an authLevel: Function route, the second row is the correct choice β€” which is why its copy button is the third red box.
  11. Those strings are secrets. Copying one into a ticket, a chat or a repository hands over an invocable endpoint; if that happens, rotate from the Function Keys tab rather than hoping nobody noticed.
  12. With the function-key URL in hand, the last check is the same one performed locally in step 100 β€” call it and expect the string from Function1.cs back.
Function page Code plus Test showing functions.metadata JSON with dotnet-isolated and httpTrigger authLevel Function, the Get function URL button outlined in red, and the panel of three keyed URLs with the Function key copy button outlined in red

Step 129 β€” Read-only metadata, and the three URLs behind Get function URL.

πŸ’‘ Tip: A 401 from this URL means the key is missing, wrong or rotated; a 404 means the route does not exist β€” as would be the case for anyone still calling /api/Function1 after the rename in step 125. Two very different problems that look alike in a browser tab.
130

The cloud answers β€” same string, different host

Part 11 · Deploy & verify

The last verification in the guide, and the simplest: the URL from step 129 pasted into a browser. What comes back closes the loop that started with a Microsoft sign-in 129 steps ago.

  1. The address is the app's default domain plus the route plus a key: functionapp120250511012141.azurewebsites.net/api/FunctionGrupoLoyalVeeduriaBG?code=…. No scheme shown, so the browser is using https:// β€” Azure App Service enforces it, unlike the localhost run in step 100.
  2. The ?code= parameter is the function key copied from the default (Function key) row. It is there because the function's authLevel is Function: without the key the same URL answers 401, which is the behaviour that makes the route callable by a pipeline or a client without being open to the internet.
  3. The response body is one line: Welcome to Azure Functions FunctionGrupoLoyalVeeduriaIBG 12 20!
  4. That exact phrasing was written in step 125 β€” the diff there showed return new OkObjectResult("Welcome to Azure Functions FunctionGrupoLoyalVeeduriaIBG 12 20!"); replacing the template's shorter string.
  5. Which makes this more than a liveness check: it proves the deployed code is the committed code. An older build would still answer, but with the original Welcome to Azure Functions!.
  6. The plain monospaced rendering is the same effect as in step 100 β€” a text/plain body, because the function returns a bare string inside an OkObjectResult rather than HTML.
  7. The full chain is now verified end to end: commit in Azure Repos, CI run #20250512.4 on DESKTOP-MDE2U74, the AzureFunctionApp@2 task over the service connection, the app FunctionApp120250511012141, the runtime loading FunctionGrupoLoyalVeeduriaIBG, and an HTTP 200 back in the browser.
  8. Compared with step 100, only the host and the credentials differ: there it was localhost:7018 with no key, here it is an azurewebsites.net domain with a function key. The code, the route name and the response string are the same on both sides.
  9. Because the key travels in the URL, it now sits in the browser's history, in any bookmark made from this tab and in this screenshot. Treat a shared screenshot of a function URL as a leaked key.
Browser address bar showing the azurewebsites.net function URL with a code parameter, and the response body reading Welcome to Azure Functions FunctionGrupoLoyalVeeduriaIBG 12 20

Step 130 β€” The deployed function returns the string committed in step 125.

πŸ’‘ Tip: The 12 20 in that string is doing the work of a build stamp β€” it is how you can tell which deployment answered. In a real project put something machine-generated there (assembly version, build id, commit short hash from $(Build.SourceVersion)); it turns every future "did the deploy land?" question into a single request.