> 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/windows-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

After the initial SCA Win32 application has been created and successfully tested, you can use the supplied Azure DevOps pipeline to update the existing application.

{% hint style="warning" %}
The Azure DevOps pipeline is **update-only**. It does not create the initial Intune application, configuration profile, assignments, or detection rule. Complete Steps 1–4 before configuring update automation.
{% endhint %}

## Before You Begin: Access and Responsibilities

Use an Intune tenant with an active subscription, an existing SCA **Windows app (Win32)**, and a successfully tested Windows pilot device. You also need an Azure DevOps project with Azure Repos and available Microsoft-hosted Windows pipeline capacity. Azure DevOps access alone does not grant access to Intune.

The administrator configuring the solution and the identity running the pipeline require different permissions:

<table><thead><tr><th width="220">Responsibility</th><th>Required access</th></tr></thead><tbody><tr><td>Review and manage the SCA app in Intune</td><td>Use the Intune <strong>Intune Administrator</strong> role, or an approved custom role with the relevant app read, update, and assignment permissions. The role assignment must include the applicable scope groups and scope tags. Read access is sufficient for inspection; changing properties or assignments requires the corresponding write permissions.</td></tr><tr><td>Create and maintain the Entra App Registration</td><td>The setup administrator must be allowed to register applications and manage this registration, including its certificates. An owner of the registration can maintain its credentials. If application registration is restricted, ask your Entra administrator to create the registration and assign the approved owners.</td></tr><tr><td>Grant Microsoft Graph application consent</td><td>A <strong>Privileged Role Administrator</strong> or <strong>Global Administrator</strong> grants tenant-wide admin consent. Application Administrator, Cloud Application Administrator, and Intune Administrator alone do not grant this Microsoft Graph application consent. Activate the required role through PIM first if your organization uses eligible assignments.</td></tr><tr><td>Run the unattended Intune update</td><td>The pipeline service principal uses the Microsoft Graph <strong>Application</strong> permission <code>DeviceManagementApps.ReadWrite.All</code>, with admin consent, and a registered certificate. No interactive administrator sign-in occurs during the run.</td></tr><tr><td>Create and maintain the Azure DevOps pipeline</td><td>Use an account with access to Azure Repos, normally Basic access or higher, repository Read and Contribute permissions, and the pipeline permissions needed to create and edit build pipelines. Creating a new repository additionally requires Create repository permission.</td></tr><tr><td>Operate and review the pipeline</td><td>Operators need <strong>Queue builds</strong> to start runs and <strong>View builds</strong> / <strong>View build pipeline</strong> to review them. Maintaining its variables and definition requires <strong>Edit build pipeline</strong>. The selected agent pool and repository must be available to the pipeline.</td></tr></tbody></table>

Confirm access before creating credentials:

* In Intune, open the SCA app and confirm that you can inspect its properties, assignments, and installation status. Ask your Intune role administrator to review the role assignment if the app is missing or an action is unavailable.

* In Entra ID, agree who owns the automation registration and who will grant consent. The Intune administrator does not need permanent Global Administrator access to operate this solution.

* In Azure DevOps, have the project administrator check repository and pipeline security if you cannot save or run the pipeline. The pipeline build identity needs repository read access for checkout; it does not need repository write access for this workflow.

* For verification, ensure that you can inspect the pilot device. Triggering **Sync** from the Intune admin center requires the relevant remote-task permission; alternatively, initiate synchronization locally on the test device.

{% hint style="danger" %}
`DeviceManagementApps.ReadWrite.All` permits application management across the tenant. `INTUNE_APP_ID` selects the target in the script; it does not restrict the credential to that one app. Do not treat the operator's Intune scope tags or the What-If checkbox as a security boundary for this application credential. Restrict who can edit the repository, pipeline, and variables, and who can queue runs with these credentials. The supplied YAML has no separate production approval stage.
{% endhint %}

## How the Pipeline Works

The Windows pipeline supports the following modes:

| Mode            | Description                                                                                                                                        |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Validation-only | Downloads and validates the selected SCA release and publishes audit artifacts. It does not connect to or modify Intune.                           |
| What-If         | Compares the latest release with the existing Intune application and records the decision. It does not package, upload, or modify the application. |
| Publish         | Updates the existing Intune application only when the selected GitHub release is newer. An equal or older version completes without an update.     |

{% hint style="info" %}
What-If requires the existing Intune app and Microsoft Graph authentication because it compares the release with the current Intune application. Validation-only does not require Graph credentials.
{% endhint %}

{% stepper %}
{% step %}

## Identify the Existing Intune Application

* Open the Microsoft Intune admin center and go to **Apps → All apps.**<br>
* Open the SCA Windows app created in Step 3 and record its application ID. If the ID is not displayed in the application details, copy the GUID following `appId/` in the browser address while the app is open.<br>
* Under **Properties**, verify that the app has exactly one MSI ProductCode detection rule.<br>
* Review **Assignments** and confirm which devices receive this application.<br>

{% hint style="warning" %}
Publishing updates the application object selected by `INTUNE_APP_ID`. All devices already assigned to that object may receive the update. For the first publish test, use an application assigned only to pilot devices.
{% endhint %}
{% endstep %}

{% step %}

## Create the Pipeline App Registration

{% hint style="info" %}
Have a **Privileged Role Administrator** or **Global Administrator**&#x20;
{% endhint %}

* Open the Microsoft Entra admin center and navigate to **App registrations → New registration**.<br>
* Enter **SCA Intune Update Pipeline** as the name.<br>
* Select **Accounts in this organizational directory only**, leave the redirect URI empty, and select **Register**.<br>
* On the Overview page, record the **Directory (tenant) ID** and **Application (client) ID**. These become `INTUNE_TENANT_ID` and `INTUNE_CLIENT_ID` in the pipeline.<br>
* Open **API permissions → Add a permission → Microsoft Graph → Application permissions**.<br>
* Search for `DeviceManagementApps.ReadWrite.All`, select it, and choose **Add permissions**.<br>
* Open this registration and select **Grant admin consent for your organization**. If you do not hold that role, hand over the registration name, tenant ID, client ID, requested permission, and its purpose to your Entra administrator.<br>
* Confirm that `DeviceManagementApps.ReadWrite.All` appears with **Type: Application** and a granted status for the correct tenant. Adding the permission without granting consent is not sufficient. A delegated permission does not provide the unattended access used here.<br>

{% hint style="info" %}
Record the responsible application owners and certificate renewal contact. Do not assign Global Administrator or Intune Administrator to the service principal for this workflow; it uses the consented Graph application permission.<br>
{% endhint %}

{% hint style="info" %}
The pipeline App Registration is a separate automation identity. Do not use the SCA Enterprise Application ID or the MSI ProductCode as `INTUNE_CLIENT_ID`. `INTUNE_APP_ID` identifies the Windows app in Intune; `INTUNE_CLIENT_ID` identifies this new App Registration in Entra ID.
{% endhint %}

{% endstep %}

{% step %}

## Create and Register the Certificate

* Obtain a certificate according to your organization's certificate policy. You need a public `.cer` file and a matching password-protected `.pfx` file containing the private key.<br>

{% hint style="info" %}
For a pilot, you can create a self-signed certificate using the following example. If you already have the required files, continue with uploading the public certificate.
{% endhint %}

* Open PowerShell on your Windows administrator workstation and run:

```powershell
$scaCertFolder = Join-Path $env:LOCALAPPDATA 'SCA-Pipeline-Certificate'
New-Item -ItemType Directory -Path $scaCertFolder -Force | Out-Null

$scaCertificate = New-SelfSignedCertificate `
    -Subject 'CN=SCA-Intune-Update-Pipeline' `
    -CertStoreLocation 'Cert:\CurrentUser\My' `
    -KeyExportPolicy Exportable `
    -KeySpec Signature `
    -KeyAlgorithm RSA `
    -KeyLength 2048 `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears(1)

$scaPfxPassword = Read-Host 'Enter a strong PFX password' -AsSecureString

Export-Certificate -Cert $scaCertificate `
    -FilePath (Join-Path $scaCertFolder 'SCA-Intune-Update-Pipeline.cer') | Out-Null

Export-PfxCertificate -Cert $scaCertificate `
    -FilePath (Join-Path $scaCertFolder 'SCA-Intune-Update-Pipeline.pfx') `
    -Password $scaPfxPassword | Out-Null
```

{% hint style="info" %}
Record the PFX password in your approved password manager. Keep the PowerShell session open for the conversion step.
{% endhint %}

* In the pipeline App Registration, open **Certificates & secrets → Certificates → Upload certificate**.<br>
* Select the public `.cer` file and choose **Add**. The public certificate goes into Entra ID; the PFX is used by the pipeline.<br>
* Confirm that the certificate is listed and record its expiration date.<br>
* Convert the PFX to Base64 and copy the result to your clipboard:<br>

```powershell
$scaPfxPath = Join-Path $scaCertFolder 'SCA-Intune-Update-Pipeline.pfx'
[Convert]::ToBase64String([IO.File]::ReadAllBytes($scaPfxPath)) | Set-Clipboard
```

{% hint style="info" %}
If you are using an existing PFX, set `$scaPfxPath` to its full path. Paste the copied value into the secret variable `INTUNE_CERTIFICATE_BASE64` in the pipeline variables.
{% endhint %}

{% hint style="danger" %}
Base64 is encoding, not encryption. Treat the Base64 PFX value as a private credential. Store it and its password as secret variables. Do not place them in YAML, the source repository, or pipeline logs.
{% endhint %}
{% endstep %}

{% step %}

## Create the Azure DevOps Pipeline

* Open the release repository linked in [Step 3](broken://pages/77dfbe2334422d6868108b6663894d91cee53530). Select **Code → Download ZIP**, extract the archive, and locate `.azure-pipelines`.<br>
* In your Azure DevOps project, open **Repos → Files** and create or select an administrator-owned automation repository.<br>
* Add and commit the complete `.azure-pipelines` folder using your normal Git workflow. Keep this structure:<br>

```
.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
```

* Open **Pipelines → New pipeline** and select **Azure Repos Git**.<br>
* Select your automation repository, then choose **Existing Azure Pipelines YAML file**.<br>
* Select the branch containing the files and the path `.azure-pipelines/azure-publish-sca-intune-win.yml`.<br>
* Continue to the YAML review and save the pipeline. Use **Save** rather than starting a publish run during setup.<br>
* Give the pipeline a recognizable name such as **SCA Windows - Update Intune**.<br>

{% hint style="danger" %}
Review the supplied YAML. It uses `windows-latest`, installs the pinned `IntuneWin32App` module version `1.5.0`, and reads releases from `Provectus-Software-GmbH/SCA_Desktop_Releases`.
{% endhint %}

{% hint style="info" %}
The supplied YAML authenticates directly through a certificate in its PowerShell steps. It does not require an Azure Resource Manager service connection. Your Azure DevOps organization must have access to a Microsoft-hosted Windows agent.
{% endhint %}

{% hint style="warning" %}
The repository checked out by the pipeline must contain exactly one copy of `Sync-SecureContactsToIntune.ps1`. The pipeline stops when it finds zero or multiple copies.
{% endhint %}

{% endstep %}

{% step %}

## Configure Pipeline Variables

* Open your saved pipeline and select **Edit → Variables → New variable**.<br>
* Add the following variables using the names exactly as shown. For this guide, use pipeline variables directly; a variable group additionally requires a YAML group reference and pipeline authorization.<br>

| Variable                          | Value                                                                                         | Secret |
| --------------------------------- | --------------------------------------------------------------------------------------------- | ------ |
| `INTUNE_TENANT_ID`                | Microsoft Entra Directory (tenant) ID.                                                        | No     |
| `INTUNE_CLIENT_ID`                | Application (client) ID of the pipeline App Registration.                                     | No     |
| `INTUNE_APP_ID`                   | Intune application ID of the existing SCA Windows app created in Step 3.                      | No     |
| `INTUNE_CERTIFICATE_BASE64`       | Base64-encoded content of the password-protected PFX file.                                    | Yes    |
| `INTUNE_CERTIFICATE_PASSWORD`     | Password of the PFX file.                                                                     | Yes    |
| `SECURE_CONTACTS_GITHUB_TOKEN`    | Optional GitHub token for authenticated release access or to avoid anonymous API rate limits. | Yes    |
| `SECURE_CONTACTS_MSI_SHA256`      | Optional expected SHA-256 value for an explicitly controlled release.                         | No     |
| `SECURE_CONTACTS_EXPECTED_SIGNER` | Optional expected MSI signer used during package validation.                                  | No     |

{% hint style="info" %}
The GitHub token is optional while the release repository is public and the anonymous API limit is sufficient. If a token is used, use a customer-owned fine-grained token with **Contents: Read-only** access and store it as a secret variable.
{% endhint %}

* Enable **Keep this value secret** for `INTUNE_CERTIFICATE_BASE64`, `INTUNE_CERTIFICATE_PASSWORD`, and the GitHub token if used. Paste the Base64 PFX value copied during certificate setup and enter the password used for that PFX.<br>

{% hint style="info" %}
Leave the optional hash and signer override variables undefined unless your organization deliberately uses them. A fixed MSI hash must be updated when the release changes.
{% endhint %}

* Save the variables and pipeline.&#x20;

{% hint style="info" %}
Do not enter a certificate thumbprint manually. The pipeline imports the PFX, derives its thumbprint, and uses it for authentication. Its cleanup step removes the temporary PFX and imported certificate.
{% endhint %}
{% endstep %}

{% step %}

## Run Validation-only

* Open the pipeline and select **Run pipeline**.<br>
* Select the configured branch. Leave both **Publish to Intune** and **Review only (What-If)** unchecked.<br>
* Select **Run** and watch the steps **Locate Windows pipeline script**, **Install pinned IntuneWin32App module**, and **Validate or publish Secure Contacts**.<br>

{% hint style="info" %}
When the run finishes, open its summary and download the published artifact `secure-contacts-intune-artifacts`. In the release-tag folder, review `manifest.json` and confirm that the generated `.intunewin` package is present. Validation-only also packages the MSI; it does not upload it to Intune.
{% endhint %}

{% hint style="success" %}
Validation-only confirms that the pipeline can retrieve and process the release. It does not authenticate to Microsoft Graph; the next run verifies that connection.
{% endhint %}
{% endstep %}

{% step %}

## Run a What-If Preview

* Select **Run pipeline** again and set:

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

* Start the run. The pipeline imports the certificate and connects to Microsoft Graph.<br>
* Open the run summary and download `secure-contacts-intune-artifacts`.<br>
* Open `decision-manifest.json` in the release-tag folder and confirm `IntuneAppId`, `IntuneDisplayName`, `IntuneVersion`, `ReleaseTag`, and `Decision` against your intended target.<br>

{% hint style="info" %}
If a newer release exists, the preview validates its MSI without creating or uploading an update package. If the candidate is equal to or older than the Intune version, `NoUpdateRequired` is a successful result.<br>
{% endhint %}

{% hint style="info" %}
For a newer candidate, review the planned MSI ProductCode change in the decision and logs. A `NoUpdateRequired` run returns before downloading the MSI or checking its detection-rule compatibility. It confirms the version comparison, not a complete update rehearsal. What-If does not test write access, upload, or installation on a device.
{% endhint %}

{% hint style="success" %}
Use What-If before the first update and whenever you want to verify the selected target and release without changing Intune.
{% endhint %}

{% endstep %}

{% step %}

## Publish the Update

* Return to the application in Intune and recheck its assignments. Confirm that the assigned devices may receive the update.

* In Azure DevOps, select **Run pipeline** and set:

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

* Start the run. The pipeline compares the release with the existing Intune application. If the release is newer, it downloads and validates the MSI, creates the IntuneWin package, and updates the application.

* Review the **Validate or publish Secure Contacts** result and the published audit artifacts.

{% hint style="warning" %}
The generated `manifest.json` records the package and requested action before publication. `FinalAction: UpdateRequested` is not proof that the upload completed. Check the run result and Intune application state as well. Do not start another publish run for the same app while the first is still running.
{% endhint %}

* In Intune, review the application version, install command, uninstall command, and MSI detection rule.

* If the MSI ProductCode changes, the pipeline replaces the existing MSI detection rule and preserves unrelated detection rules.

{% hint style="danger" %}
The existing SCA application must have exactly one MSI ProductCode detection rule. The update stops without changing Intune if the application has no MSI rule or more than one MSI rule.
{% endhint %}

{% hint style="warning" %}
Each run resolves the latest release independently. A Publish run may select a newer release than an earlier What-If run. To pin a release, add a `ReleaseTag` entry with the approved tag to the script's `$parameters` hashtable in the YAML before both runs. The supplied Run pipeline dialog does not expose a release-tag parameter.
{% endhint %}

{% endstep %}

{% step %}

## Validate the Update

* Synchronize a Windows test device with Intune and allow the update to be processed.

* Confirm that Intune reports the app as installed and that SCA starts with the new version.

* Verify sign-in, licensing, App Configuration, contact synchronization, and the required desktop functions.

{% hint style="info" %}
If the app was assigned only to pilot devices, expand its assignments after accepting the results. If production devices were already assigned, publishing has already made the update available to that scope.
{% endhint %}

{% endstep %}

{% step %}

## Optional: Schedule Automatic Updates

The supplied YAML runs manually by default and includes a commented daily schedule example.

Complete a successful manual publish and pilot verification first.

* Edit `azure-publish-sca-intune-win.yml` in your Azure DevOps repository.<br>
* In the existing `parameters` block, change the default for `publish` to `true`. Leave `whatIf` set to `false`:

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

* Replace the existing block; do not add a second parameter block. Scheduled runs use these defaults, and they also become the defaults displayed for manual runs.<br>
* Uncomment the existing 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 your pipeline uses another branch. Keep the existing pool, variables, and steps.<br>
* Commit the changes to that branch. In Azure DevOps, check **Scheduled runs** to confirm the next execution. If you have configured a schedule in the pipeline UI, remove it before relying on the YAML schedule.<br>
* Review the first scheduled run and its artifacts.

{% hint style="info" %}
Assign an operational owner to review failed runs and certificate expiry. Use your organization's Azure DevOps notification settings to notify the responsible team of build failures. To pause automatic publishing, remove or comment out the YAML schedule and commit the change; also check for schedules configured in the pipeline UI. Already running jobs must be handled separately.
{% endhint %}

{% hint style="info" %}
The example runs daily at 06:00 UTC. `always: true` allows it to check for releases even when the automation repository has not changed. With `publish` defaulting to `true`, a newer release is automatically published to the selected Intune app and becomes available to its assigned devices.
{% endhint %}

{% endstep %}
{% endstepper %}

## Troubleshooting and Certificate Renewal

| Result or error                   | Action                                                                                                                  |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| No hosted agent is available      | Check your Azure DevOps hosted parallel-job entitlement and agent access.                                               |
| Expected exactly one script       | Check the full checkout for missing or duplicate `Sync-SecureContactsToIntune.ps1` files.                               |
| Certificate import fails          | Check the Base64 value, PFX password, and whether the PFX contains the private key.                                     |
| Graph authentication fails        | Check tenant ID, client ID, certificate expiry, and the public certificate registered on the pipeline App Registration. |
| Graph access is denied            | Check the `DeviceManagementApps.ReadWrite.All` application permission and admin consent.                                |
| Target app cannot be found        | Check the tenant and `INTUNE_APP_ID`. Use the Intune app ID, not an Entra ID or MSI ProductCode.                        |
| MSI rule validation fails         | Check that the target Win32 app has exactly one MSI detection rule.                                                     |
| GitHub access or rate-limit error | Check release access and configure the optional read-only token if required.                                            |
| `NoUpdateRequired`                | No change is needed. The selected release is not newer than the version in Intune.                                      |

If a publish run fails after upload starts, inspect the app's content state, version, commands, and detection rules before retrying. The upload and metadata update are separate operations; a failed run does not guarantee that Intune is unchanged. The script does not automatically roll back. It also skips an equal or older release, so selecting an older release tag is not a rollback procedure.

### Renew the Pipeline Certificate

{% stepper %}
{% step %}

## Arrange the renewal

* Arrange renewal before the recorded expiry date and obtain a new public certificate and matching password-protected PFX as described in section 3.
  {% endstep %}

{% step %}

## Upload the new certificate

* Upload the new public certificate to the **same** App Registration. Keep the previous certificate registered during the transition.
  {% endstep %}

{% step %}

## Update pipeline credentials

* When no publish run is active, replace both `INTUNE_CERTIFICATE_BASE64` and `INTUNE_CERTIFICATE_PASSWORD` with the new matching values. Keep both marked as secret. The tenant ID, client ID, and Intune app ID stay the same.
  {% endstep %}

{% step %}

## Verify authentication

* Save and start a **Review only (What-If)** run with **Publish to Intune** unchecked. Confirm successful certificate authentication and lookup of the expected Intune app. A `NoUpdateRequired` result is sufficient to verify this authentication step.
  {% endstep %}

{% step %}

## Remove the old certificate

* After successful verification and confirmation that no other approved job uses the old credential, remove the old certificate from the App Registration. Record the new expiry date and renewal owner.
  {% endstep %}

{% step %}

## Handle the private-key files

* Retain or dispose of the exported private-key files according to your organization's credential policy. Pipeline cleanup does not remove the PFX or certificate from the administrator workstation used to create them.

{% endstep %}
{% endstepper %}

{% hint style="success" %}
The existing SCA Win32 application is now ready for repeatable updates through Azure DevOps Pipelines.
{% 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/windows-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.
