> For the complete documentation index, see [llms.txt](https://docs.secure-contacts.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.secure-contacts.com/quickstart-guide/macos-intune-managed-steps-to-activate-sca-in-your-entra-tenant/optional-step-automatic-desktop-update-using-azure-devops-pipeline.md).

# optional Step - Automatic Desktop Update - using Azure DevOps pipeline

{% hint style="danger" %}
Complete Steps 1–4 before.
{% endhint %}

{% hint style="warning" %}
This workflow updates an existing **macOS app (PKG)**. It does not create the initial Intune application, configuration profile, or assignments. Start with an app assigned only to pilot devices.
{% endhint %}

## Before You Begin: Permissions and Responsibilities

You need an active Intune tenant, the existing macOS app ID, an Azure DevOps project and repository, and available Microsoft-hosted **macOS** pipeline capacity.

| Responsibility                    | Required access                                                                                                                                                                                                                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Manage the Intune application     | Use Application Manager or an approved custom role with the required app read, update, and assignment permissions. Include the correct scope groups and tags. Configuration profile changes require separate profile-management permissions.                                       |
| Maintain the App Registration     | The administrator must be permitted to register applications and manage the selected registration and its certificates. If registration is restricted, have your Entra administrator create it and assign its approved owners.                                                     |
| Grant Graph application consent   | A Privileged Role Administrator or Global Administrator grants consent. Intune Administrator, Application Administrator, and Cloud Application Administrator alone cannot grant this Microsoft Graph application permission. Activate the required role through PIM if applicable. |
| Run the unattended update         | The pipeline service principal requires Microsoft Graph **Application** permission `DeviceManagementApps.ReadWrite.All`, admin consent, and a registered certificate.                                                                                                              |
| Maintain Azure DevOps             | Use repository access, normally Basic or higher, with Read and Contribute permissions, plus the required Create build pipeline and Edit build pipeline permissions. Creating a repository requires Create repository permission.                                                   |
| Run and inspect pipelines         | Operators need Queue builds, View builds, and View build pipeline. Editing variables requires Edit build pipeline. The pipeline needs access to the repository, hosted agent pool, and deployment environment.                                                                     |
| Manage the deployment environment | An environment Creator can create the environment; its Administrator can maintain access. Assign approved people to manage its checks and approve deployments.                                                                                                                     |

Agree who maintains the registration, credentials, pipeline, and failed-run notifications before enabling scheduled updates. \
\
The administrator who operates the pipeline does not need permanent Global Administrator access. The build identity needs repository read access for checkout; this workflow does not require it to write to the repository.

{% hint style="danger" %}
The Graph application permission permits Intune application management across the tenant. The target app ID is a script input, not a permission restriction. Protect repository changes, pipeline variables, and queue permissions accordingly. The deployment environment's approval check controls the supplied Publish stage, but cannot protect against a pipeline editor rewriting the credential-bearing validation steps.
{% endhint %}

## How the Pipeline Works

| Mode                  | Behavior                                                                                                                                                                                             |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Validation-only       | Downloads the PKG and matching checksum, validates the package, and publishes artifacts. No Graph authentication or Intune changes.                                                                  |
| Review only (What-If) | Authenticates, reads the existing macOS PKG app, and compares its SCA bundle version with the selected release. Produces a decision manifest. No PKG download, package validation, or Intune writes. |
| Publish to Intune     | Compares versions, downloads and validates a newer package, then passes that artifact to the Publish stage. The stage uses the configured deployment environment and updates the existing app.       |

{% hint style="warning" %}
Use only the checkbox combinations shown below. Do not select both **Publish to Intune** and **Review only (What-If)**. The supplied YAML does not exclude What-If in its Publish-stage condition; selecting both can enter that stage without the required validated package artifact.
{% endhint %}

{% stepper %}
{% step %}

## Identify the Existing Intune Application

* In Intune, open **Apps → All apps** and select the SCA macOS application created in Step 2.<br>
* Confirm its type is **macOS app (PKG)**. Record its Intune app ID from the application address if necessary: the GUID following `appId/`.<br>
* Review **Included apps**. There must be exactly one entry matching `de.provectus.SecureContactsDesktop`, with the actual deployed numeric bundle version.<br>
* Confirm **Ignore app version** is **No** for this version-controlled rollout and review the app's assignments. Record the current package version and pilot scope.<br>

{% hint style="warning" %}
Publishing replaces content in the selected app object. Existing assignments remain effective, so already assigned production devices may receive the update. The environment name does not select a pilot group or tenant; the app ID and its assignments determine the target.
{% endhint %}
{% endstep %}

{% step %}

## Create the Pipeline App Registration

* Open **Microsoft Entra admin center → App registrations → New registration**.<br>
* Enter **SCA macOS Intune Update Pipeline**, select **Accounts in this organizational directory only**, and leave the redirect URI empty. Select **Register**.<br>
* Record the **Directory (tenant) ID** and **Application (client) ID**.<br>
* Open **API permissions → Add a permission → Microsoft Graph → Application permissions**. Add `DeviceManagementApps.ReadWrite.All`.<br>

{% hint style="info" %}
**Privileged Role Administrator** or **Global Administrator** grant admin consent.&#x20;
{% endhint %}

* Confirm the permission is listed as **Application** and granted for your tenant.&#x20;

{% hint style="info" %}
`INTUNE_CLIENT_ID` identifies the pipeline App Registration: **who performs the update**. `INTUNE_MACOS_APP_ID` identifies the existing Intune PKG app: **which app is updated**. Neither variable contains the SCA Enterprise Application ID or the macOS bundle ID.
{% endhint %}
{% endstep %}

{% step %}

## Prepare and Register the Certificate

* Obtain an organization-approved certificate with a public certificate file and a matching password-protected PKCS#12 file (`.p12` or `.pfx`) containing the private key.

* Open Terminal and run:

```bash
mkdir -p "$HOME/SCA-Pipeline-Certificate"
chmod 700 "$HOME/SCA-Pipeline-Certificate"
cd "$HOME/SCA-Pipeline-Certificate"
umask 077

openssl req -x509 -newkey rsa:2048 -sha256 -days 365 \
  -keyout sca-pipeline-key.pem \
  -out sca-pipeline-cert.pem \
  -subj "/CN=SCA-macOS-Intune-Update-Pipeline"

openssl x509 -in sca-pipeline-cert.pem -outform DER \
  -out sca-pipeline-cert.cer

openssl pkcs12 -export \
  -inkey sca-pipeline-key.pem \
  -in sca-pipeline-cert.pem \
  -out sca-pipeline.p12
```

{% hint style="info" %}
Enter a strong private-key passphrase when prompted. During PKCS#12 export, supply that passphrase and choose a strong export password.&#x20;
{% endhint %}

* Store the **export password** in your approved password manager; this is the pipeline certificate password.

* In the pipeline App Registration, select **Certificates & secrets → Certificates → Upload certificate**. Upload the public `.cer` file.<br>

* Confirm that the certificate is listed. Record its expiry date and responsible owner.

* Copy the PKCS#12 file as a single Base64 value:

```bash
openssl base64 -A -in "$HOME/SCA-Pipeline-Certificate/sca-pipeline.p12" | pbcopy
```

For an existing certificate, substitute its actual path. Paste the result into the secret variable in **Configure Pipeline Variables** below.

{% hint style="danger" %}
The Base64 value contains the private credential. Base64 does not encrypt it. Keep it and the export password in secret variables; do not commit them or print them in logs. Upload only the public certificate to Entra ID.
{% endhint %}
{% endstep %}

{% step %}

## Create the Pipeline and Deployment Environment

* Open the release repository through [Step 2](broken://pages/b2eb7b5c3b9ddbbc489be6d34cc731d00a2a5695), download the source ZIP, and locate `.azure-pipelines`.<br>
* Commit the complete folder to your administrator-owned Azure Repos repository using your normal reviewed Git workflow:

```
.azure-pipelines/
  README.md
  azure-publish-sca-intune-win.yml
  azure-publish-sca-intune-macos.yml
  Sync-SecureContactsToIntune.ps1
  Sync-SecureContactsToIntune.sh
  Validate-SecureContactsPackage.sh
```

* The checkout must contain exactly one `Validate-SecureContactsPackage.sh`, with `Sync-SecureContactsToIntune.sh` beside it.<br>
* In **Pipelines → New pipeline**, select **Azure Repos Git**, your repository, and **Existing Azure Pipelines YAML file**.<br>
* Select the reviewed branch and `.azure-pipelines/azure-publish-sca-intune-macos.yml`. Save the pipeline and name it **SCA macOS - Update Intune**.<br>
* Open **Pipelines → Environments → Create environment**. Create an empty environment named exactly **intune-production**, matching the supplied YAML. <br>
* Select **None** for resources.<br>

{% hint style="info" %}
Restrict environment pipeline access to the SCA macOS pipeline. If the environment already exists, have its administrator review the existing access and checks before using it.
{% endhint %}

* Confirm that the pipeline can use the environment and that an approver is available for the first publication.&#x20;
* An environment name alone does not create an approval requirement.

{% hint style="info" %}
Both stages use `macOS-latest`. Authentication uses Azure CLI with a certificate. This workflow does not need an Azure Resource Manager service connection or an Azure subscription role; Intune access comes from the consented Graph application permission.
{% endhint %}
{% endstep %}

{% step %}

## Configure Pipeline Variables

* Open the pipeline and select **Edit → Variables → New variable**.<br>
* Add these exact names as pipeline variables:

| Variable                       | Value                                                                         | Secret |
| ------------------------------ | ----------------------------------------------------------------------------- | ------ |
| `INTUNE_TENANT_ID`             | Directory (tenant) ID.                                                        | No     |
| `INTUNE_CLIENT_ID`             | Application (client) ID of the pipeline registration.                         | No     |
| `INTUNE_MACOS_APP_ID`          | Intune app ID of the existing macOS PKG app.                                  | No     |
| `INTUNE_CERTIFICATE_BASE64`    | Single-line Base64 content of the password-protected PKCS#12 file.            | Yes    |
| `INTUNE_CERTIFICATE_PASSWORD`  | PKCS#12 export password.                                                      | Yes    |
| `SECURE_CONTACTS_GITHUB_TOKEN` | Optional customer-owned token for release access or authenticated API limits. | Yes    |

* Enable **Keep this value secret** for the certificate, password, and optional token. \
  Use a read-only GitHub token with access to the release repository if required.<br>
* Save the variables. \
  \
  If you use a variable group instead, add its group reference to the YAML and authorize this pipeline to use it; creating a group alone does not make its values available.

{% hint style="info" %}
The YAML maps `INTUNE_MACOS_APP_ID` to the script environment variable `INTUNE_APP_ID`. Configure the macOS-specific name in Azure DevOps. The supplied macOS YAML does not use the Windows MSI hash, signer-override, or certificate-thumbprint variables.
{% endhint %}
{% endstep %}

{% step %}

## Run Validation-only

* Select **Run pipeline** and choose the reviewed branch.<br>
* Leave both **Publish to Intune** and **Review only (What-If)** unchecked. <br>
* Select **Run**.<br>

Watch **Download and validate package** in the Validate stage. It checks the checksum, installer signer, Gatekeeper assessment, bundle identity, version, and ARM64 executable.

Download `secure-contacts-macos-intune-artifacts` from the run summary. Review the PKG, its matching checksum file, and `validation-manifest.json`. Confirm `ValidationResult` is `Validated` and the package identity and version are expected.

{% hint style="success" %}
Validation-only checks the package without connecting to Intune. It does not test certificate authentication or app installation.
{% endhint %}
{% endstep %}

{% step %}

## Run a What-If Preview

Start another run with:

| Azure DevOps option       | Selection |
| ------------------------- | --------- |
| **Publish to Intune**     | Unchecked |
| **Review only (What-If)** | Checked   |

* Review **Compare GitHub and Intune versions** and open `decision-manifest.json` in `secure-contacts-macos-intune-artifacts`.<br>
* Check `IntuneAppId`, `BundleId`, `CandidateVersion`, `IntuneBundleVersion`, and `Decision`. Confirm that the target is the intended macOS app.<br>

Interpret the result:

* `UpdateExistingApp`: the candidate version is newer.<br>
* `NoUpdateRequired`: the candidate is equal to or older than the existing included-app version.

{% hint style="info" %}
macOS What-If is a metadata-only comparison. Even when an update is available, it does not download or validate the PKG. A successful result also does not prove upload permission or successful installation.
{% endhint %}
{% endstep %}

{% step %}

## Publish the Update

* Recheck the target app's assignments and confirm that the currently assigned devices may receive the update.<br>
* Start a run with:

| Azure DevOps option       | Selection |
| ------------------------- | --------- |
| **Publish to Intune**     | Checked   |
| **Review only (What-If)** | Unchecked |

* The Validate stage compares versions. If no newer version is available, the Publish stage is skipped. Otherwise, it downloads and validates the package.<br>
* If approval is configured on `intune-production`, review the **validation artifact from this run** before approving the Publish stage. Confirm its package version, bundle ID, and validation result.<br>
* The Publish stage consumes that artifact, validates it again, authenticates, uploads the encrypted content, and commits the new version to Intune.
* Review **Publish validated package to Intune**, the final run result, and `secure-contacts-macos-intune-publish-artifacts`. A validation manifest alone is not proof of successful publication.

{% hint style="warning" %}
Separate runs resolve the latest release independently. The preflight and download within Validate also resolve it separately. Approve the actual validation artifact, not just an earlier What-If result. Publish uses the validated artifact through `--use-existing-package`. For a fixed release, an administrator can add the same `--release-tag vMAJOR.MINOR.PATCH` argument to both release-resolving script calls in Validate; replace that example with the approved tag. The supplied run dialog has no release-tag field.
{% endhint %}
{% endstep %}

{% step %}

### Verify in Intune

* Confirm the new package and bundle version is updated in Intune.
* The script updates the package content, file name, primary bundle version, SCA's version and the description to `Secure Contacts Desktop - Release: <version>`. <br>

{% hint style="info" %}
The bundle identity is preserved, and unrelated Included apps entries are retained. The script does update SCA's version metadata used by detection; it does not create a new detection model or change the app's assignments or configuration profile.
{% endhint %}
{% endstep %}

{% step %}

## Verify the Updated Mac

* Synchronize the pilot Mac and allow Intune to process the update.<br>
* Confirm Intune installation status and the actual installed version.<br>
* Verify that managed preferences, licensing, sign-in, and contact synchronization still work. Updating the package does not update the preference profile.<br>
* Expand assignments after pilot acceptance. If the app already had production assignments, publishing has already exposed the update to that scope.<br>
  {% endstep %}

{% step %}

## Optional: Schedule Update Checks and Publishing

* Complete a successful manual publication and pilot verification first.<br>
* In the existing YAML parameter block, set the default for `publish` to `true` and keep `whatIf` at `false`:

```yaml
parameters:
- name: publish
  displayName: Publish to Intune
  type: boolean
  default: true
- name: whatIf
  displayName: Review only (What-If)
  type: boolean
  default: false
```

\
Uncomment the supplied schedule, or add this top-level block after `trigger: none`:

```yaml
schedules:
- cron: '0 6 * * *'
  displayName: Daily 06:00 UTC run
  branches:
    include:
    - main
  always: true
```

* Replace `main` if necessary and commit to that branch. Remove any conflicting UI schedule, then commit a change so Azure DevOps reevaluates the YAML schedule. Inspect **Scheduled runs**.<br>
* Review the first scheduled run and assign an owner for failures and certificate expiry. Configure the team's Azure DevOps notifications according to your operating process.

{% hint style="info" %}
The example checks daily at 06:00 UTC even without repository changes. Environment approval checks still apply. With an approval check, a newer package waits for approval; without one, it can publish automatically. These parameter defaults also appear in manual runs.
{% endhint %}
{% endstep %}
{% endstepper %}

## Operational Acceptance

Before enabling unattended operation, record:

* The Azure DevOps pipeline, reviewed branch, and source revision.
* The target tenant and macOS Intune app ID.
* The environment approval policy and authorized approvers.
* The registration owners, certificate expiry, and renewal contact.
* The successful publish run and corresponding pilot-device results.
* The team responsible for reviewing failed or approval-pending runs.

Keep the decision and validation artifacts with the release record. Review repository updates before replacing your pipeline bundle; copying the files once does not automatically keep the customer repository synchronized with upstream changes.

## Troubleshooting and Certificate Renewal

| Result or error                      | Action                                                                                                                                 |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| No macOS agent available             | Check hosted parallel-job capacity and agent-pool access.                                                                              |
| Environment missing or unauthorized  | Check the exact environment name, its existence, and pipeline permissions.                                                             |
| Waiting for approval                 | Review the current run's artifact and contact the configured environment approver.                                                     |
| Script lookup fails                  | Keep one validator in the checkout and the sync script beside it.                                                                      |
| Certificate cannot be unlocked       | Check the Base64 value, export password, and private key in the PKCS#12 file.                                                          |
| Graph authentication or access fails | Check tenant/client IDs, certificate registration and expiry, application permission, and admin consent.                               |
| Target is not a macOSPkgApp          | Use the Intune ID of the macOS app (PKG), not a LOB app or the Windows app.                                                            |
| Included-app match or version fails  | Check the exact SCA bundle ID and its single numeric version entry.                                                                    |
| Package validation fails             | Inspect the first validator error and the selected release assets. Do not bypass validation.                                           |
| NoUpdateRequired                     | The included-app version is already equal or newer; the normal pipeline skips publication.                                             |
| Publishing fails or times out        | Inspect the app's content state and version before retrying. Publication uses several operations and is not automatically rolled back. |

Do not run concurrent publications for the same app. A failed run can leave uncommitted content or partially completed changes. An older release is not an automatic rollback: the publisher rejects downgrades, and the normal preflight skips equal or older releases.

## To renew the certificate:

{% stepper %}
{% step %}

## Obtain the new certificate

* Obtain the new certificate and matching password-protected PKCS#12 file before expiry.
  {% endstep %}

{% step %}

## Upload the public certificate

* Upload the new public certificate to the same registration while keeping the old certificate during the transition.
  {% endstep %}

{% step %}

## Replace pipeline secrets

* With no publish job active, replace both secret certificate variables with the new matching values.
  {% endstep %}

{% step %}

## Verify authentication

* Run **Review only (What-If)** with **Publish to Intune** unchecked. Confirm authentication and lookup of the intended app; `NoUpdateRequired` is sufficient for this credential test.
  {% endstep %}

{% step %}

## Remove the old certificate

* Once verification succeeds and no other approved job needs the old credential, remove the old certificate from the registration.
  {% endstep %}

{% step %}

## Record certificate lifecycle details

* Record the new expiry date. Retain or dispose of workstation private-key files according to your credential policy.
  {% endstep %}
  {% endstepper %}

The pipeline removes its temporary certificate files, and the script cleans up its temporary authentication files. That does not remove credentials exported on the administrator workstation.

{% hint style="success" %}
The macOS update workflow is ready for regular operation after a successful publication and device-level pilot verification.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.secure-contacts.com/quickstart-guide/macos-intune-managed-steps-to-activate-sca-in-your-entra-tenant/optional-step-automatic-desktop-update-using-azure-devops-pipeline.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
