Showing posts with label Microsoft. Show all posts
Showing posts with label Microsoft. Show all posts

June 12, 2025

Best Practices for SharePoint Term Store and Power Automate Integration

Introduction:

Managing metadata effectively is crucial for scalable and maintainable SharePoint environments. SharePoint’s Term Store provides a centralized way to manage taxonomy, while Power Automate enables automation across your organisation. When combined, these tools can significantly enhance content tagging, data consistency, and process automation. In this post, we’ll walk through best practices for integrating the SharePoint Term Store with Power Automate.

1. Understand the Term Store Structure

Before integrating, ensure you understand how the Term Store is organised:

  • Term Groups: Top-level containers for organising Term Sets.
  • Term Sets: Collections of related terms.
  • Terms: Individual metadata entries used for tagging.

Ensure proper governance and naming conventions are in place to prevent confusion or duplication.


2. Use PnP PowerShell or PnPjs for Bulk Term Management

Managing terms programmatically (via PnP PowerShell or PnPjs in SharePoint Framework) is more efficient than manual entry, especially in large taxonomies.

  • Automate term provisioning.
  • Keep staging and production environments in sync.

Example: Use Power Automate to trigger a PowerShell script that syncs term sets from a central source.

3. Accessing the Term Store in Power Automate

Out-of-the-box Power Automate connectors do not provide native actions to query the Term Store. Workarounds include:

  • HTTP Requests to SharePoint REST API
  • Azure Functions or Custom Connectors
Example REST API call:

GET _api/v2.1/termStore/sets('{termSetId}')/terms

Ensure the flow has the correct permissions (App-Only or Delegated permissions via Azure AD).

4. Create Reusable Flows for Term Retrieval

Develop modular flows to fetch terms from a term set and reuse them across different workflows:

  • Input: Term Set ID
  • Output: List of terms (as an array or JSON)

This promotes reusability and reduces redundancy.

5. Use Term GUIDs, Not Labels

Avoid using plain text labels when referencing terms in automated flows.

  • Use the term GUIDs to ensure uniqueness
  • Prevent issues with label duplication or localisation
6. Dynamic Tagging in SharePoint List Items

Use retrieved term GUIDs to tag list items dynamically:


"TaxCatchAll": [
  {
    "Label": "India",
    "TermID": "b8b3a6ab-0c4d-4c4a-8a9e-d0e74f9623fe"
  }
]

Use the Send an HTTP request to SharePoint action to update list items programmatically.

7. Error Handling and Logging

Include error handling for API calls:

  • Retry policies
  • Logging to a SharePoint list or Dataverse table
  • Email alerts for failures
8. Security and Permissions
  • Ensure flows run under an account with Term Store access
  • Use Azure-managed identities or certificate-based app registration for sensitive operations
9. Limit API Calls and Throttle Efficiently

SharePoint API has throttling limits:

  • Use pagination when querying large term sets
  • Implement delays using Delay action in Power Automate
10. Maintain Documentation

Document:

  • Term Set structures
  • API endpoints
  • Power Automate flow logic

This helps in onboarding, debugging, and long-term maintenance.


Conclusion:

When integrated correctly, the SharePoint Term Store and Power Automate provide a powerful solution for automating metadata-driven processes. By following these best practices, you ensure scalability, reliability, and maintainability across your organisation. Start small, build reusable components, and always document your integrations.

If you have any questions you can reach out our SharePoint Consulting team here.

April 17, 2025

Automating Azure Service App Deployment with Azure DevOps Pipelines

Introduction

In modern software development, Continuous Integration and Continuous Deployment (CI/CD) are crucial in ensuring smooth, automated deployments. Azure DevOps provides robust pipeline capabilities that enable developers to automate the deployment of their applications, reducing manual effort and minimizing errors.

In this blog, we’ll walk through setting up an Azure DevOps pipeline for an Azure Service App, ensuring seamless deployment whenever changes are pushed to the repository.

Setting Up the Azure DevOps Pipeline


Before diving into the pipeline configuration, ensure you have:
  • An Azure Service App created in the Azure Portal.
  • service connection in Azure DevOps is linked to your Azure subscription.
  • Your Service App code is stored in a repository like Azure Repos, GitHub, or Bitbucket.

Pipeline Configuration

Below is a YAML-based Azure DevOps pipeline that automates the build and deployment of an Azure Service App.


trigger:
  branches:
    include:
    - main # Change this to your branch if needed
  paths:
    include:
    - ServiceAppCode/*

variables:
  azureSubscription: 'ServiceAppDeployment' # Azure service connection name
  serviceAppName: 'TestingApp' # Azure Service App name
  serviceAppPath: 'ServiceAppCode' # Path to Service App source code
  buildConfiguration: 'Release' # Build service app source code in release
  publishDirectory: '$(Build.ArtifactStagingDirectory)/publish'

pool:
  vmImage: 'ubuntu-22.04' # Also use ubuntu-latest

stages:
- stage: Build
  displayName: 'Build Stage'
  jobs:
  - job: Build
    displayName: 'Build Job'
    steps:
    - task: UseDotNet@2
      displayName: 'Install .NET SDK'
      inputs:
        packageType: 'sdk'
        version: '8.0.x'
        includePreviewVersions: false

    - script: |
        echo "Cleaning up existing publish directory..."
        rm -rf $(publishDirectory)
        mkdir -p $(publishDirectory)
      displayName: 'Ensure Clean Publish Directory'

    - task: DotNetCoreCLI@2
      displayName: 'Restore Dependencies'
      inputs:
        command: 'restore'
        projects: '$(serviceAppPath)/*.csproj'

    - task: DotNetCoreCLI@2
      displayName: 'Build Service App'
      inputs:
        command: 'build'
        projects: '$(serviceAppPath)/*.csproj'
        arguments: '--configuration $(buildConfiguration) /p:WarningLevel=0' # /p:WarningLevel=0 Remove the warning at the time of build service app

    - task: DotNetCoreCLI@2
      displayName: 'Publish Service App'
      inputs:
        command: 'publish'
        projects: '$(serviceAppPath)/*.csproj'
        publishWebProjects: false
        arguments: '--configuration $(buildConfiguration) --output $(publishDirectory)'
        zipAfterPublish: true

    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifacts'
      inputs:
        pathToPublish: '$(publishDirectory)'
        artifactName: 'drop'

- stage: Deploy
  displayName: 'Deploy Stage'
  dependsOn: Build
  condition: succeeded()
  jobs:
  - job: Deploy
    displayName: 'Deploy to Azure Service App'
    steps:
    - download: current
      displayName: 'Download Build Artifacts'
      artifact: 'drop'

    - task: AzureServiceApp@1
      displayName: 'Deploy to Azure Service App'
      inputs:
        azureSubscription: '$(azureSubscription)'
        appType: 'webApp' # functionApp for function app OR webApp for web app
        appName: '$(serviceAppName)'
        package: '$(Pipeline.Workspace)/drop/*.zip'

Understanding the Pipeline

1. Trigger Configuration

  • The pipeline is triggered when changes are pushed to the main branch.
  • It monitors specific folders where Service App updates are made.

2. Defining Variables

  • azureSubscription: The name of the Azure DevOps service connection.
  • serviceAppName: The name of the Service App in Azure.
  • serviceAppPath: The directory containing the Service App source code.
  • publishDirectory: The folder where the build output is stored.

3. Choosing the Right Agent Pool

  • The pipeline uses the Ubuntu 22.04 VM image for the build process

4. Build Stage

  • Installing .NET SDK: Ensures that the required version is available.
  • Cleaning the Publish Directory: Prevents old files from interfering with new builds.
  • Restoring Dependencies: Ensures that all NuGet dependencies are downloaded.
  • Building the Service App: Compiles the service app with the specified configuration.
  • Publishing the Service App: Packages the compiled app for deployment.
  • Publishing Artifacts: Stores the published files as build artifacts for the deployment stage.

5. Deploy Stage

  • Downloading Build Artifacts: Retrieves the published application files.
  • Deploying to Azure: Uses the AzureServiceApp@1 task to deploy the service app to Azure.

Conclusion

By setting up this Azure DevOps pipeline, you can automate the deployment of your Azure Service App, ensuring quick and error-free releases.

With automation in place, you can focus on developing new features while Azure DevOps handles the heavy lifting of deployments!


If you have any questions you can reach out our SharePoint Consulting team here.

Custom PowerShell Script to Copy Site Groups, Permissions, and Settings Without Migration Tool

Introduction 

Migrating permissions, site groups, and settings from a SharePoint Hub Site to an associated site is a common task, especially in large organizations. While available migration tools can make this easy, they often come at a cost. 

In this blog post, we’ll walk through a custom PowerShell script that automates this process without using any migration tool.  

What This Script Does 

  • Connects to both Hub Site and Associated Site 
  • Copies custom permission levels 
  • Copies site groups with users 
  • Applies appropriate role assignments 
  • Updates sharing capabilities 
  • Streamlines migration process for SharePoint Online environments 

Script Parameters 

Param ( 
    [string] $ClientId = $(Throw "Please provide ClientId"), 
    [string] $HubSiteURL = $(Throw "Please provide HubSiteURL"), 
    [string] $AdminSiteURL = $(Throw "Please provide AdminSiteURL"), 
    [string] $ClientSecret = $(Throw "Please provide ClientSecret"), 
    [string] $AssociatedSiteURL = $(Throw "Please provide Associated Site URL") 
) 

You’ll need to register an Azure AD App with appropriate SharePoint API permissions and provide its ClientId and ClientSecret. This ensures secure authentication without using stored credentials. 

Authentication and Setup 

$HubSiteConnection = Connect-PnPOnline -Url $HubSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection 
$AssociatedSiteConnection = Connect-PnPOnline -Url $AssociatedSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection

This part of the script connects to both the Hub Site and the Associated Site using PnP PowerShell, returning secure connections for use in the following functions. 

Updating Sharing Capability 

function Update-ExternalSharing { 
    param ( 
        [string] $AssociatedSiteURL, 
        [string] $AdminSiteURL 
    ) 
 
    Connect-PnPOnline -Url $AdminSiteURL -ClientId $ClientId -ClientSecret $ClientSecret 
    Set-PnPTenantSite -Url $AssociatedSiteURL -SharingCapability ExternalUserSharingOnly 
}
This function sets the sharing capability of the associated site to allow external users to access the site — but only if they are authenticated. This is done by updating the SharingCapability property of the site to ExternalUserSharingOnly. 

Copying Permission Levels 

function Copy-PermissionLevels { 
    param ( 
        $HubSiteConnection, 
        $AssociatedSiteConnection 
    ) 
 
    $AllPermissionLevels = Get-PnPRoleDefinition -Connection $HubSiteConnection 
 
    foreach ($PermissionLevel in $AllPermissionLevels) { 
        if (-not $PermissionLevel.Hidden) { 
            $ExistingPermission = Get-PnPRoleDefinition -Identity $PermissionLevel.Name -Connection $AssociatedSiteConnection -ErrorAction SilentlyContinue 
            if (!$ExistingPermission) { 
                $selectedPermissions = New-Object Microsoft.SharePoint.Client.BasePermissions 
                [Enum]::GetValues([Microsoft.SharePoint.Client.PermissionKind]) | ForEach-Object { 
                    if ($PermissionLevel.BasePermissions.Has($_)) { 
                        $selectedPermissions.Set($_) 
                    } 
                } 
 
                $newRole = Add-PnPRoleDefinition -RoleName $PermissionLevel.Name -Description $PermissionLevel.Description -Connection $AssociatedSiteConnection 
                $newRole.BasePermissions = $selectedPermissions 
                $newRole.Update() 
                Invoke-PnPQuery -Connection $AssociatedSiteConnection 
            } 
        } 
    } 
} 
This function copies non-hidden permission levels from the hub site to the associated site. It checks if the permission already exists to avoid duplication and applies the same Base Permissions.  Copying Groups and Users 
function Copy-HubSiteGroups { 
    param ( 
        $HubSiteName, 
        $AssociatedSiteName, 
        $HubSiteConnection, 
        $AssociatedSiteConnection 
    ) 
 
    $GroupMappings = @( 
        @{ Source = "$HubSiteName Owners"; Destination = "$AssociatedSiteName Owners" }, 
        @{ Source = "$HubSiteName Members"; Destination = "$AssociatedSiteName Members" }, 
        @{ Source = "$HubSiteName Visitors"; Destination = "$AssociatedSiteName Visitors" } 
    ) 
 
    foreach ($Mapping in $GroupMappings) { 
        $SourceGroup = Get-PnPGroup -Identity $Mapping.Source -Connection $HubSiteConnection 
        $DestinationGroup = Get-PnPGroup -Identity $Mapping.Destination -Connection $AssociatedSiteConnection -ErrorAction SilentlyContinue 
 
        if (-not $DestinationGroup) { 
            New-PnPGroup -Title $Mapping.Destination -Connection $AssociatedSiteConnection 
        } 
 
        $SourceGroup.Users | ForEach-Object { 
            $LoginName = if ($_ -like "*#ext#*") { $_.Email } else { $_.LoginName } 
            Add-PnPGroupMember -Group $Mapping.Destination -LoginName $LoginName -Connection $AssociatedSiteConnection 
        } 
    } 
} 

This function replicates SharePoint default groups (Owners, Members, Visitors) from the hub to the associated site. It handles both internal and external users and ensures group membership is preserved. 

Main Function

To simplify script execution, we can wrap all functional calls inside a Main function:

function Main {
    # Connect to the Hub Site and Associated Site using PnP PowerShell
    $HubSiteConnection = Connect-PnPOnline -Url $HubSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection
    $AssociatedSiteConnection = Connect-PnPOnline -Url $AssociatedSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection

    # Update external sharing settings before proceeding
    Update-ExternalSharing -AdminSiteURL $AdminSiteURL -AssociatedSiteURL $AssociatedSiteURL

    # Copy custom permission levels from Hub Site to Associated Site
    Copy-PermissionLevels -HubSiteConnection $HubSiteConnection -AssociatedSiteConnection $AssociatedSiteConnection

    # Extract names of sites to match default group names
    $HubSiteName = ($HubSiteURL -split "/")[-1]
    $AssociatedSiteName = ($AssociatedSiteURL -split "/")[-1]

    # Copy site groups and their users
    Copy-HubSiteGroups -HubSiteName $HubSiteName -AssociatedSiteName $AssociatedSiteName -HubSiteConnection $HubSiteConnection -AssociatedSiteConnection $AssociatedSiteConnection
}

Main
Running the Script To run this PowerShell script, first make sure you’ve saved it with a .ps1 extension — for example, name it CopyGroupsAndPermissions.ps1. 

Once saved, navigate to the directory where the script is stored using PowerShell and run the following command (make sure to replace the placeholder values with your actual credentials and URLs): 

./CopyGroupsAndPermissions.ps1 ` 
    -ClientId "xxxx-xxxx-xxxx-xxxx" ` 
    -HubSiteURL "https://yourtenant.sharepoint.com/sites/hubsite" ` 
    -ClientSecret "your-client-secret" ` 
    -AdminSiteURL "https://yourtenant-admin.sharepoint.com" ` 
    -AssociatedSiteURL "https://yourtenant.sharepoint.com/sites/associatedsite" 
  

Final Thoughts 

This script is ideal for scenarios where: 

  • You need to replicate security and structure across multiple sites 
  • You want to avoid third-party tools like ShareGate 

If you're working on managing large SharePoint environments with many associated sites, this can save time and reduce manual effort.

If you have any questions you can reach out our SharePoint Consulting team here.