This series of blog posts describes an approach I've taken recently to implement continuous delivery for cross-platform native mobile apps built using Xamarin Forms and C#, targeting both Android and iOS. I've included the Main Goals and Prerequisites sections from my first post here for reference as well.

  • Part 1: focuses on putting together a basic Xamarin Forms application with specific patterns to support continuous delivery.
  • Part 2: focuses on creating a build pipeline that will provide continuous integration for the application source, by building, testing and packaging the code whenever it changes.
  • Part 3 (this post): focuses on creating a release pipeline that will provide staged deployments of app builds to App Center for testing and production release.


Main Goals

  • The application code should be built and tested whenever commits are made to any branch in the source repository.
  • Whenever a build successfully completes on the master branch, the app should be packaged and deployed automatically to the DEV environment. This would normally occur due to a new Pull Request (PR) being merged to the master branch from a feature branch, for example.
  • The configuration of the app packages for each platform should be updated as a release is promoted from one stage to the next (eg from DEV -> UAT, or UAT -> PROD), using settings appropriate for the environment associated with each stage.
  • Manual testers and operators should be notified by email when a new release is available in App Center.



  • I've used Visual Studio 2019 (version 16.7.1) for developing the sample solution, with the Mobile Development for .NET workload installed, which covers Xamarin requirements, but you could also use Visual Studio for Mac.
  • You will need access to a MacBook or similar MacOS X device for building and signing the iOS project, and a paid Apple Developer account that can be used for generating certificates and provisioning profiles - get started at https://developer.apple.com.
  • You will need an Azure DevOps account for creating the build and release pipelines used in this series. Source code is hosted in a git repository in Azure Repos, but you could use any other source control provider such as GitHub or BitBucket if you wish - go to https://dev.azure.com to get started.
  • You will need a Visual Studio App Center account in order to create the platform-specific projects we will need for deploying and testing our app packages - go to https://appcenter.ms/ to get started.


Step 3: Create a continuous release pipeline deploying to App Center

In this post, I'll walk you through the creation of a complete release pipeline that will take the artifacts created from the build pipeline we created in the last post, and deploy to two separate environments defined in App Center. This solution aims to meet the following goals:

  • The pipeline will consist of two stages, DEV and PROD
  • Deployments to DEV will be triggered automatically for successful builds on the main branch, via the build pipeline we created in the previous post in this series
  • Deploying to PROD requires manual approval from an authorised operator in Azure DevOps
  • New releases to a specific stage will automatically cancel any pending deployments to the same stage from older releases
  • New releases pushed to App Center should be notified to manual testers via a distribution group
  • Manual testers should be able to download and install app releases directly onto their devices from App Center
  • New testers and devices for iOS can be added to App Center at any time for testing an existing release, using automatic provisioning

Note that I'm not going to cover deployment to App Stores in this post, and focus just on how releases are pushed from Azure DevOps to App Center and tested manually on real devices. See my Going Further notes at the end of the post for links to other App Center features that you can use.

Also, while I'm only covering a basic set of environments here (DEV -> PROD), you can easily use the guidance in this post to add additional stages as well, for example, DEV -> UAT -> PROD, which is a very common configuration for projects using Continuous Delivery practices.

App Center Setup

Before we start making any changes in Azure DevOps, we will start by creating and configuring the projects we will be using in Visual Studio App Center. If you haven't already created an account in App Center, go ahead and do that now.

An App Center project is always platform-specific, so because we have 2 unique stages and 2 unique platforms, we will need 4 projects in total:


Name Operating System Platform Release Type (optional)
Mobile-DevOps-SampleApp-DEV-iOS iOS Xamarin Beta
Mobile-DevOps-SampleApp-DEV-Android Android Xamarin Beta
Mobile-DevOps-SampleApp-PROD-iOS iOS Xamarin Production
Mobile-DevOps-SampleApp-PROD-Android Android Xamarin Production


Note that you can name the projects however you want, but I strongly recommend including the stage and OS keys as suffixes. You can also optionally specify an icon for each project which will identify it in the App Center UI.

Also, if you are creating projects as part of an existing organisation, it's a really good idea to change the ownership of the project to the organisation as well, so that if in future your account is disabled or compromised, the project can still be administered by other users in the organisation. See Creating and Managing Apps in the Microsoft documentation to get more details about how to do that.

1) Once you have signed in, you should see a home page in App Center similar to the following, click the Add new app button to add a new project:

2) Enter the details as indicated in the dialog, and click Add new app:

Repeat these steps for the remaining 3 projects as described in the table above.

Next, we will add a distribution group to each project which will contain the user accounts of our testers that we want to notify automatically whenever a new release is pushed to each project from Azure DevOps.

1) From your App Center home page (My Apps), click the Android-DEV project as indicated:

2) In the left-hand navigation bar, click Distribute and then the sub-menu item Groups, then click the Add Group button:

3) In the modal popup that appears, enter the group name - I've used "Manual Testers" but you can use any name you like. You can also choose whether the group is only for specific users, or allows anyone access to your releases, I highly recommend that you keep this setting set to Off (not public) unless you are creating an open beta or similar. You can add specific users by typing their name or email address in the Add testers field - if they are not already registered users within the project or organisation, then you can invite them to join. Note that you have to press Enter while this field is focussed to commit your users to the group, and you can do this as many times as you need. I've added two of my personal accounts to this group as indicated in the screenshot, you can remove pending users from the group as well by clicking the trashcan icon that appears when hovering over a user. Click Create Group to finish:

4) You should see a screen now as indicated in the following screenshot. The final step to complete is to obtain the ID for this group, so that we can reference it in Azure DevOps when we build our release pipeline later on - click the wrench icon as indicated to open the group settings dialog:

5) In the Group settings modal popup, note the ID field - just select and copy the entire value and save it somewhere for later reference. I recommend keeping a spreadsheet of your project configuration somewhere which includes the IDs of any of the distribution groups you create:

Repeat these steps to add the same distribution group configuration for the remaining 3 projects.

Automatic Provisioning for iOS Projects

To complete our initial App Center configuration, we are also going to enable automatic device provisioning for users added to distribution groups in our iOS projects. This allows you to add or remove testers from groups in these projects, and App Center will do the work of creating and distributing a provisioning profile that allows the app release to run on devices currently registered in the group. If we didn't enable this functionality then you would have to manually manage these profiles yourself for all your testers and their iOS devices, which is EXTREMELY painful, believe me!

To successfully complete this set of steps, you will need the following:

  • A paid Apple Developer subscription and associated Apple ID that can be used to sign in to that subscription.
  • An Apple Distribution certificate exported into *.p12 format with a password that you know. If you have followed my second blog post in this series then you should already have this file, as you would have needed to upload it to Azure DevOps as a Secure File to complete the build process for iOS, please refer to that post if you want full setup details. App Center will also need to have secure access to this certificate so that it can create new provisioning profiles on demand for your apps that have been signed using that certificate.
  • A macOS / iOS device, such as a MacBook Pro / iPhone / iPad, that is currently signed into iCloud using the same Apple ID you will be adding to App Center so that it can manage provisioning profiles on your behalf. The reason for this is that when App Center tries to sign into your Apple ID account, it will require you to enter a verification code as well. This verification code CANNOT be currently sent via SMS, it will only be sent to trusted devices that are signed into iCloud for that account, via push notification. This step really tripped me up for a while, which is why I'm going into more detail about it, so that you don't have the same pain I did trying to get this working!

1) In App Center, open your DEV-iOS project, go to the Distribute > Groups navigation item in the left-hand sidebar, then click on the Manual Testers group to open it.

2) Open the Group settings for the distribution group by clicking the wrench icon in the top-right corner, as we did earlier to obtain the ID of the group.

3) In the modal popup, you will notice an additional switch, Automatically manage devices, click the switch to enable and configure this setting:

4) First, you will need to select an Apple ID account - click the dropdown to open it. If you have previously added any Apple accounts in App Center, they will be shown in the dropdown. If the Apple ID is marked as invalid, you will need to sign-in again, otherwise just click + Add account to add your new Apple ID and sign in from App Center. The following steps will cover adding and validating a new account, which covers re-connecting to existing invalid accounts as well:

5) In the modal popup that appears, enter the email address and password for your Apple ID, then click Add:

6) If your credentials are valid, then you will be presented with another dialog that asks for a verification code, which is only sent to a trusted Apple device signed into iCloud for the same Apple ID, as mentioned earlier. The first screenshot indicates what you will see in App Center, and the following screenshots indicate what you would see on a MacBook Pro. Enter the verification code into App Center as shown on the trusted device:

7) If verification succeeds, then the Apple account will be validated and selected in the Group settings popup. Next, open the Distribution certificate dropdown, if you have previously uploaded *.p12 certificates to App Center for this Apple account, they will be shown for selection. Otherwise, click + Add certificate to continue:

8) In the new dialog that appears, select the *.p12 file to upload from your local device, enter the password for that file, and click Done:

9) If successful, the certificate will now be selected in the Group settings popup, just click Done to save your changes and finish this process:

Repeat these steps for the PROD-iOS project, using the same Apple account and certificate. You can use a different Apple ID account or certificate at any point, just by changing these settings, but you MUST always ensure that the *.p12 certificate file you upload and use in App Center for the distribution group is the SAME certificate you use to sign your apps in Azure DevOps for the relevant stage, otherwise your testers won't be able to test the app directly on their devices because automatic provisioning will fail at the point the tester tries to download and run an app release.


Connect Azure DevOps to App Center

Before we can push our app releases to App Center from Azure DevOps, we must create an API Token in App Center that Azure DevOps can use to gain authorised access to our App Center projects.

1) Open http://appcenter.ms and make sure you are signed in, click your avatar icon in the top-right corner, and click Account Settings:

2) Scroll to the User API tokens section and click the edit icon, as indicated:

3) Click the New API token button in the next page:

4) In the dialog that appears, enter a descriptive name for your token, ensure Full Access is selected, then click Add new API token:

5) In the dialog that appears, copy and paste the complete API token value as text to somewhere safe, then close this dialog. You won't be able to retrieve the value again for this token!

Now that we have an API token, we need to create a Service Connection from Azure DevOps to App Center that our release pipeline can use to authorise operations such as pushing releases to our App Center projects.

1) In Azure DevOps, click the Project Settings cog icon, then the Service connections menu item, then the New service connection button:

2) In the dialog that appears, enter "App Center" into the search box, this should result in a single option being selected called Visual Studio App Center, then click Next:

3) In the next dialog, enter the API Token value you obtained from App Center earlier, provide a descriptive name for the connection, ensure Grant access permission to all pipelines is ticked, and click Save:

Take note of the service connection name as you will use this to refer to the connection later on.

This completes the App Center configuration we will need for the remainder of this post. Next, we will build our release pipeline in Azure DevOps, which will take the artifacts from the build pipeline we completed in my second post, configure them for the current stage, deploy to App Center, and notify our testers of a new release in that stage.


Create a Release Pipeline in Azure DevOps

I'm going to be showing the setup for what is currently known as a classic release pipeline, which is created and configured entirely in Azure DevOps using the web-based UI, however release pipeline functionality is also becoming available in the newer multi-stage YAML format for pipelines, which I recommend looking into separately - I provide some links to look at in the Going Further section at the end of this post.

Let's start by opening our Azure DevOps project in a browser, this should be the same project that contains the build pipeline we created previously to build our sample project using continuous integration.

1) Navigate to Pipelines > Releases from the left-hand side-bar, click the + New dropdown button, and click + New release pipeline as indicated:

2) The next screen will open a new pipeline, and open a template dialog that allows us to select from a pre-defined range of pipeline templates for specific purposes. However, we are going to create this pipeline from scratch, so just select the Empty job link as indicated:

3) Edit the name of the pipeline as indicated - I've used Mobile-DevOps-Release but you can use any name you want that identifies your pipeline. Also, note that we have a single stage added by default which is named Stage 1, update this name to DEV in the stage properties dialog on the right-hand side, then close the dialog as well:

4) Next, we are going to specify the build artifacts that will trigger a new instance of our release pipeline, and run the jobs and tasks in our DEV stage automatically. In the Artifacts section, click either the + Add link or the + Add an artifact section, as indicated:

5) In the Add an artifact dialog that appears on the right, select the Build source type (which is also the default type), then select the Project that contains the build pipeline we created in the earlier post - the default selection will be the current project that contains the release pipeline, which is enough for our example. Next, you will need to set the Source field to the name of your build pipeline as shown in the Pipelines area of Azure DevOps. Leave the remaining fields with their default values and click Add:

As you can see, there are many different options for specifying an artifact source trigger for a release pipeline, and it is possible to define multiple source triggers as well. If you want to learn more, check out the Microsoft documentation on classic release pipelines.

6) Click on the lightning icon that appears on the newly-added artifacts to open the Continuous deployment trigger options dialog, enable the switch for the trigger, then click Save to save our changes to the pipeline (if not done already). We don't need to set any other options at this stage:

This will cause a new release pipeline instance to be created for every succcessful build on the main branch of our source repository, using the artifacts produced by that build. No other branch will produce artifacts from that pipeline so we don't need to add additional filters. We also have the option to enable Pull Request triggers as well, but that is outside the scope of this post.

7) We will make one more change to the configuration of the DEV stage before we start to define the jobs and tasks in that stage that execute our deployments to the corresponding App Center projects. Open the Pre-deployment conditions dialog via the trigger icon of the DEV stage as indicated, open the Deployment queue settings area, then set the Subsequent releases option to Deploy latest and cancel the others:

Note that this change will be saved immediately. The purpose of this change is so that any new deployments made from this stage will automatically cancel any prior deployments for other release pipeline instances created from earlier builds. If we didn't make this change, a release admin would have to manually cancel any deployments that were currently in progress for other pipelines, which is an impediment to continuous delivery practices.

Basically, we just want to have the latest version of our code active in our release pipeline at any point. App Center will store all previous releases for us anyway. We also need to do this for each stage when we follow this principle, so that the behavior of the release pipeline is uniform regardless of how far our artifacts have progressed through the overal pipeline structure.

Create a Job and Tasks for Android Deployment to App Center (DEV)

Next, we are going to create a job to represent our deployment to the DEV-Android project in App Center, and define the specific tasks within that job that will execute the logic of our deployment. This will all be done within the release pipeline UI of Azure DevOps. We will need to create a Variable Group in the Library as well, which will contain all the settings required to run all the jobs and tasks in the DEV stage of our pipeline.

Before you can complete the setup for this job, you will need to install the following extensions into your Azure DevOps organisation:

  • ApkTool Build and Release Tasks by Built to Roam
  • Mobile App Tasks for iOS and Android by James Montemagno (installation of this extension is detailed in my previous post)

1) Ensure the release pipeline is open in Azure DevOps, then click either the Tasks tab or jobs / tasks link, as indicated:

2) Click the default job that is created, set the Display name field to Android and the Agent Specification field to windows-2019, leave all other fields at default values, then click Save:

3) Click the + icon inside the job header, this will bring up a list of all the available tasks that can be selected for use in our pipelines. Search for "ApkTool", then click Add on the ApkTool task, which will be available once the relevant extension has been installed:

4) Enter the fields for the task as indicated in the following screenshot. Don't worry about the variable references inside $(...) delimiters, we will create those later. The purpose of this task is to decode and extract the contents of the *.APK package that was produced by our build pipeline to a local folder on the build agent, so that we can manipulate any of the contents of the package, for example, update the Android Manifest file, modify the application settings file, change the application icon, etc.:

5) Add another task to the Android job, search for the "Android Manifest Package Name" task and click Add, then enter the values for that task as indicated. This task will update the package name and application name in the Android Manifest file to match our current stage, if required, or you can just use the same package name / application name throughout. Having this flexibility to customise packages for each environment is very useful though, which is why I've included it here:

Note that this task comes from James Montemagno's task extension for Mobile DevOps, which you should have installed already.

6) Add another task to the Android job, search for the "File transform" task, then click Add, and enter the details for the task as indicated. This task will transform any settings in our appsettings.json file by automatically interpolating variables scoped to the current stage that match property paths in the JSON document. I'll describe this process in more detail later when we create the Variable Group for this stage:

7) Now that we've made all the changes we need to the contents of the package, we need to repack all the files back into a new APK file, ready for signing. Add another ApkTool task to the Android job and enter the values as indicated:

Note that the repacked APK file name also includes the current release version, which will be a number similar to 1.0.12. This makes it much easier to work with the package files in App Center when downloading to a real device as the release version is always identified in the file name as well. I'll explain how this number is calculated when we add our variables later on.

8) Before we can sign the modified APK file, we must ensure that we have an Android keystore file uploaded to the Secure Files section of the Library in Azure DevOps, and that we know the password for that keystore file as well, as we need a keystore file to sign an APK file before it can be run on an Android device. If you already have a keystore file to use, you can skip the following steps. I'll generate a keystore file for the sample app we're using via Visual Studio 2019, so open the sample app solution in Visual Studio (available at https://github.com/sam-piper/xamarin-devops).

9) First, set the current build configuration to Release, then build the MobileDevOps.SampleApp.Android project. Once the build succeeds, right-click the project node in Solution Explorer, and select the Archive... option. This will open up the Archive Manager window, which will automatically prepare an archived APK for the last Release build. To generate a keystore file, click the Distribute button as indicated on the last successful archive:

NOTE: if you encounter an error saying the archive file or folder path is too long, you may need to try the following solutions:

  • Enable Windows 10 Long Paths
  • Find a way to shorten the path to your source project as much as possible
  • Change the Archives Location folder path setting to a shorter location in Visual Studio, via Tools > Options... > Xamarin > Android Settings - for example, I use C:\Xamarin\Archives

10) In the following Distribute dialog, click the Ad Hoc button:

11) In the Signing Identity area, click the + button:

12) In the Create Android Keystore dialog that appears, provide values for the fields as appropriate for your organisation (or use the values I've provided), then click the Create button:

Note that the Alias field can be any value and does not need to correlate specifically to the package name used in the Android app itself, although using a reverse-DNS-style name as shown is a recommended practice. Also, I recommend only using alphanumeric characters for the keystore password, to avoid problems with special characters in passwords that could cause issues in our pipeline tasks later on. Make sure you record the Alias and Password fields associated with the keystore file you create as well, as you will need these details later on.

13) Now we will need to locate the keystore file directly on disk as unfortunately there is no way to do this currently from Visual Studio. On Windows, the path to keystore files is:

C:\Users\[username]\AppData\Local\Xamarin\Mono for Android\Keystore\[alias]\[alias].keystore

14) Once you have located the file, upload it to the Secure Files section in Pipelines > Library of your Azure DevOps project, and also make sure the file is authorised for use in all pipelines (otherwise, you will have to manually authorise the use of this file the first time you run your release pipeline).

15) Now that we have a keystore file and password, return to the release pipeline, add a new task to the Android job, search for the "Android signing" task, click Add, then enter the field details as indicated, leave all other fields at their default values:

16) Our final task for this job is to push our package to App Center. Add a new task to the Android job, search for "App Center distribute", and click Add, then enter the details as shown in the following screenshots:

Note that the App slug is in the format [user or organisation name]/[project name], which you can easily extract from the project homepage URL as shown in the browser location bar, for example, https://appcenter.ms/users/sam.piper-readify.net/apps/Mobile-DevOps-SampleApp-DEV-iOS which yields an app slug of sam.piper-readify.net/Mobile-DevOps-SampleApp-DEV-iOS for that project.

Also, the Distribution Group ID is the value you copied out of App Center when you set up the relevant distribution group for the target project, make sure it is correct for the project you are deploying to.


Create Release Variables for the DEV stage

We've completed the configuration of our Android job, but we won't be able to run it yet, as we need to define the variables that are referenced in the job definition. Next we will create a Variable Group that will contain all the variables needed in our pipeline with the exception of the ReleaseVersion variable which will be created in the pipeline itself as it will be used across all the different stages.

Let's start by creating our release version variable, which will be based on the unique ID of the build that triggered the release pipeline. This consists of a fixed major/minor version, and a number representing our unique build, so that each release will have a unique version as well, all the way through to App Center. This is just a simple versioning strategy - more complex examples, such as extracting the major/minor version components from build artifacts, are beyond the scope of this post.

1) Ensure your release pipeline is in edit mode, click the Variables tab, and enter the following details in the Pipeline variables section as indicated, then save your changes:

2) Go to the Pipelines > Library section via the left-hand navigation sidebar, and click + Variable group:

3) Enter the group fields and variable definitions as indicated in the screenshot, then click Save:

Please ensure that the variable values you use reflect your specific environment, although the variable names should be exactly as shown. Note that the values you use for the AppCenterKeys.Android and AppCenterKeys.iOS variables can be any values for the purposes of following this series of posts, but in a real-world mobile application they would be the actual keys you use in your code to integrate with the App Center SDKs for the relevant projects, and should always be marked as secrets as well.

The AndroidKeyStoreAlias and AndroidKeyStorePassword values should reflect the keystore file you created earlier when defining the Android job in the release pipeline, and the password should also be marked as a secret as well.

I recommend reading the File Transform task documentation, particularly around JSON substitution, to understand exactly how the variables in our pipeline are matched and injected into the settings in our appsettings.json file, which we created in the first post in this series.

4) Now that our variable group is defined, we just need to link it to the DEV stage of our release pipeline. Edit the pipeline, click the Variables tab, select the Variable groups section, then click the Link variable group button:

5) Select the variable group you defined earlier, make sure it is linked specifically to the DEV stage, and click Link:

6) Save your changes to the release pipeline

That's it! We can now run our build pipeline, and it should automatically trigger a new instance of our release pipeline from the artifacts of that build, deploying automatically to our DEV-Android project in App Center. Try it out now by queueing a new build on master for the source build pipeline. When the build succeeds, it should trigger a new instance of your release pipeline as well, with automatic deployment to the DEV stage.

If the deployment succeeds, then a new release should appear in App Center, and your testers should be notified of the release by email as well. They can proceed to download and install the release package onto their test devices for manual testing.

Create a Job and Tasks for iOS Deployment to App Center (DEV)

We've covered the Android side of things, so now we have to do the same to cover the iOS side of things. We're going to add a new agent job to the DEV stage of our pipeline, and define the tasks required to perform the same logical work as we did for the Android deployment, but with iOS-equivalent functionality, and we will push releases to our DEV-iOS project as well.

Before you can complete this set of steps, you will need to ensure you have done the following:

  • Installed the Apple App Store task extension into your Azure DevOps organisation
  • Have access to a valid Apple certificate in *.p12 format and provisioning profile from Pipelines > Library > Secure Files

Don't worry about adding any missing variables referenced in the iOS steps, we will fix that up at the end.

1) Edit the release pipeline, and go to the Tasks > DEV tab. In the top DEV deployment process box, click the ellipsis icon, then click Add an agent job:

2) Enter the Display name and Agent Specification as indicated, leave all other options at their defaults, then click Save:

This job will need to run on a macOS image, hence why we use the latest macOS image available.

3) Add a new task to the iOS job, search for "Copy files", and click the Add button on that task. Enter the details for the task as indicated:

The purpose of this step is just to make it easier to work with paths later on in our scripts, by copying the *.IPA file from the iOS artifact directory to the default working directory on the agent.

4) Add a new task to the iOS job, search for "Copy files", and click the Add button on that task. Enter the details for the task as indicated:

This Bash script just unzips the copied *.IPA file into the working directory, using the built-in unzip utility - we don't need a specialised tool to do this like we do with *.APK files.

5) Add a new task to the iOS job, search for "iOS Bundle Identifiers", and click the Add button on that task. Enter the details for the task as indicated:

This step allows us to customise the bundle identifier properties for the iOS app (identifier, name, display name), by modifying the extracted Info.plist file.

6) Add a new task to the iOS job, search for "File transform", and click the Add button on that task. Enter the details for the task as indicated:

This step works in exactly the same manner as for the Android version, so I won't cover it in any more detail here.

7) Add a new task to the iOS job, search for "Bash", and click the Add button on that task. Enter the details for the task as indicated:

This script step creates a new global variable (IPA_NEW) to define the new IPA filename including the ReleaseVersion variable, then uses the built-in zip command to re-package the updated contents of the app, ready for signing later on.

8) Before we can sign the IPA file again, we need to install the Apple Distribution certificate and provisioning profile that we want to use for signing. This could be a different certificate for each stage, but for this example we will use the same certificate that we used when signing the original IPA file in our build pipeline. Add a new task to the iOS job, search for "Install Apple certificate", and click the Add button on that task. Enter the details for the task as indicated in the following screenshots:

9) Add a new task to the iOS job, search for "Download secure file", and click the Add button on that task. Enter the details for the task as indicated:

Note that we are just downloading the file directly to the agent here, then in the Output Variables section we add a task reference of provisioningFileDownloadTask, which creates variables we can then use to refer to the path of the downloaded file. This is much easier than trying to manually locate where the file ends up after being downloaded, in my experience!

We need a profile to sign an IPA package, however, it's not used for provisioning actual devices, that's what App Center does for us once we enable Automatic Provisioning - otherwise, you would have to keep this profile up-to-date in Azure DevOps every time you needed to add or invalidate a device registration, for example.

Also, secure files that are downloaded to an agent are always removed after the agent finishes the job, so we don't have to do it manually.

10) Add a new task to the iOS job, search for "Ipa Resign", and click the Add button on that task. Enter the details for the task as indicated in the following screenshots:

This task comes from the Apple App Store extension from Microsoft, which you should have already installed into your Azure DevOps organisation. Provided that the certificate and profile files are correct, the IPA file should be signed again, so that it can be deployed.

NOTE: if your iOS app requires a specific Entitlements.plist file to run correctly (such as enabling Push Notifications or HealthKit, for example), then you can follow the same process as you would for the provisioning profile - if the file needs to be different for a given stage, just download a secure file from the library, give it a task name, then hook it into the IPA signing task as a variable reference.

11) Our final task for this job is to push our IPA package to App Center. Add a new task to the iOS job, search for "App Center distribute", and click Add, then enter the details as indicated:

Make sure you update the App Slug and Destination ID properties to be correct for your DEV-iOS project in App Center, so that the right testers get notified.

12) That completes the agent job. Before we can test, we need to add the remaining variables to the variable group we created earlier. Open up the DEV variable group associated with the stage, and add the remaining variables as indicated in the screenshot, replacing the AppleCertificatePassword value as appropriate for the *.p12 file you are using in this stage (and make sure it's marked as a secret):

You can create a new release of the pipeline directly from the last outputs of your build pipeline, to test that the iOS job is working. If something isn't working, you'll need to look at the job and task logs to determine where the failure is occurring and how to resolve.

Create a PROD stage with manual approval

Now that we have a working deployment to our DEV projects in App Center, we are going to add one more stage, PROD, which will also require manual approval from an authorised user before the deployment can occur. We are going to clone the existing DEV stage and variable group in our pipeline and update the cloned stage as appropriate to reflect our production environment.

1) Edit your release pipeline, hover over the DEV stage and click the Clone button, click the copied stage and update the name to PROD, then click Save:

This will clone all the jobs and tasks as well for the new stage, we will come back to update them soon.

2) Next, open the variable group associated with the DEV stage in Pipelines > Library, click the Clone button, and replace "DEV" with "PROD". Note that secrets are NOT copied across, you will have to enter them manually again - you can just use the same keystore and certificate passwords for this example though, but they could be completely different in a real-world configuration. Here's an idea of what you should end up with:

3) Edit the release pipeline and switch to the Variables tab, then click the Variable groups section. First, change the scope of the DEV variable group link back to DEV only (it will be DEV, PROD) - a side effect of cloning a stage is that we also cloned the variable references as well, which we don't want. Finally, link the new PROD variable group to the PROD stage, and click Save. Your variable groups screen should look similar to this:

4) Now we will configure the PROD stage with a manual approval requirement, so that our DEV deployments do not automatically deploy to PROD when successful. Switch back to the Pipeline tab, click the Pre-deployment conditions icon on the left-side of the PROD stage, then enable the Pre-deployment approvals switch, and enter the name of one or more users or groups that you want to be able to approve deployments to this stage. Make sure you have saved your changes:


That completes our release pipeline! You should now create a new release for this pipeline - the DEV deployment to App Center should occur as it did before we added the PROD stage. Once the DEV deployment is completed successfully, the PROD deployment stage will trigger automatically, but the deployment will not occur until an authorised user manually approves the deployment, which is done from the Release Overview page in the Web UI. Once a user has approved the release, the deployment will continue automatically, and you should get a new release pushed to your PROD-Android and PROD-iOS projects, with notifications sent to the distribution groups associated with those projects.

Going Further

In addition to the features we have already used in this post, there are many other features in App Center that can easily be integrated into your apps to provide an all-in-one Mobile DevOps Management system:

  • Diagnostics allows crash reports, errors with full stack traces, and other types of log messages to be logged to your App Center projects for later analysis so that you can easily monitor the health of your apps in real-time as they run.
  • Analytics supports rich, performant event tracking from your app, including the ability to define custom events, so that you can understand your user and device segmentation and build rich dashboards that display detailed information about how your users are consuming your apps.
  • Distribution allows installed versions of your app to be updated in-place on target devices, without requiring App Store mediation or a separate Mobile Device Management system such as Intune. You can also use App Center to automatically distribute new releases to Apple App Store, Google Play, Intune or TestFlight.
  • Test Cloud allows your coded UI tests for your app releases to be executed in a cloud-based environment on hundreds of different combinations of device model and platform, with full automation support. For Xamarin, supported frameworks include Xamarin.UITest which is based on NUnit and supports both Android and iOS.

If you want to learn more about building CI / CD pipelines using the new YAML format, then check out the Microsoft documentation.