Use this PowerShell script and Azure logic app to sync library’s based on Azure AD Group assignments.

Ever had to Sync 100 folders to 10 or more different security groups, and didn’t want to create configuration profiles for each group? Or had to wait up to 8 hours before Intune finally synced the sites?

Back in the day it would be easy based on the user’s groups, a script with whoami /groups and you’re all set. This changed with Azure AD.

I’ve come up with a way that handles this, which will save you time and gives you more control.

This solution utilizes Azure Logic Apps and a Azure Storage Account Table to get user group membership and match that membership with SharePoint odopen url’s to map. This is extremely useful if you don’t want to create a ton of Configuration Profiles in Intune for each Document Library or Folder that needs to be synced.

This is a low cost solution, even if you execute it on all computers for all users every sign in. 1000 users would costs +- $50 per Month.

Odopen SharePoint URL

Odopen Document Library’s and Folders have different queries, this solution works regardless of the type of odopen you’re trying to sync as it builds the new url with the same names and values.

Grabbing the folder or document library information could possibly be automated with a script or GUI, but since this doesn’t tent to change a lot, I’ve chosen to manually grab the information and enter it into a Storage Account Table.

Grabbing this odopen url is only possible on an Azure AD Joined computer, with Edge signed-in.

  1. Open Edge, and browser to the folder or document library that you wish to sync.
  2. Open Developer Tools (press F12),  and go to the network tab.
  3. Now click on the 3 dots, and hit Sync as shown in this screenshot:
  4. Two popups will open, which you can ignore, we’re interested in the red sync line in the Network tracer tab. Right click and copy the address:
  5. This should copy a “odopen://” url. Save this url for later, or if everything is already configured continue with adding it to the table:
    Add it to the table in the storage account with the group information, and you’re done!
  6. (skip this step if you haven’t setup the resources yet)
    The Partition key should contain the Azure AD Group you want the folder linked to, Rowkey can be anything but should be unique, paste the entire odopen url in the URL field.

Prepare Azure resources

Start with creating a logic app and assign it a “System Assigned ID” as shown below in the screenshot.

In the Azure Active Directory portal, assign the system managed ID Global Reader rights.

Create a Storage Account, and assign the system managed ID Storage Table Data Reader.

Go to Storage Explorer, tables and create a new table named “sharepointmapping”.

Finally, add one entry in here, with the sharepoint odopen url from before, and a group with a ‘SP -‘ prefix (step 6).

Finally, go to Endpoints in the menu on the storage account, and note the “Table service” endpoint, which should reflect https://storageaccountname.table.core.windows.net/

Configure Logic App

You’ve already created the logic app, and assigned it rights on the table and Azure AD. We will continue with designing the flows.

Start the logic app with an HTPP trigger, which we will use in the PowerShell script to requests the data needed to map the folders. You will need to copy this URL in the PowerShell script.

Schema:
{
    "properties": {
        "upn": {
            "type": "string"
        }
    },
    "type": "object"
}

With the UPN received from the PS script, we will now request Azure AD with detailed information we need to request the group membership. The green window indicates it’s an HTPP Request.

Purple windows are parse json actions. With this action we grab the ID of the user only, as we don’t need the other information:

Schema:
{
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "id": {
            "type": "string"
        }
    },
    "type": "object"
}

Next we grab the user’s groups, the purple id is a dynamic value from the parse user action.

Parse this information as well:

{
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "value": {
            "items": {
                "properties": {
                    "displayName": {
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "type": "array"
        }
    },
    "type": "object"
}

We need to gather the information from the following actions, so first we initialize a variable:

Next, is up is the tricky part. We need to loop trough the groups that were parsed, luckily the designer handles this for you.

Create a new Condition Action:

Add the displayName from the parse group action, and enter a group prefix you would like to use to assign sharepoint folders or sites with.

As soon as you’ve selected the displayName, the Logic App Designer will create a loop, which executes the True block, if the group matches the ‘SP -‘ prefix, which we will use for site assignments.

To speed up the runtime of this logic app during execution, it’s advisable to configure Currency Control (hit settings with the 3 dots to the right.

In the True block, add an “Get entities (V2)” action with an Filter Query you can add with the new parameter dropdown:

In the True block, add an “Get entities (V2)” action with an Filter Query you can add with the new parameter dropdown:

Schema:
{
    "properties": {
        "odata.metadata": {
            "type": "string"
        },
        "value": {
            "items": {
                "properties": {
                    "PartitionKey": {
                        "type": "string"
                    },
                    "RowKey": {
                        "type": "string"
                    },
                    "Timestamp": {
                        "type": "string"
                    },
                    "URL": {
                        "type": "string"
                    },
                    "odata.etag": {
                        "type": "string"
                    }
                },
                "required": [
                    "odata.etag",
                    "PartitionKey",
                    "RowKey",
                    "Timestamp",
                    "URL"
                ],
                "type": "object"
            },
            "type": "array"
        }
    },
    "type": "object"
}

The next action is another loop, we will append the URL that’s parsed from the previous action to the sharepointlist variable created earlier, followed by an comma.   ,

The designer will auto create the For loop again, resulting in:

Finish the logic app with an ‘Response’ action, that returns the matched odopen urls to the PowerShell scripts. All is executed in +- 5 seconds if configured correctly.

Your entire Logic App should now look like this:

The PowerShell Script

The scripts is fairly simple, as most of the logic is handled by the Logic App. Simply modify the URL in the script and it should map the SharePoint document library if your user matches the groups.

You can run the script as a Device script that’s run in the user context, or modify it slightly to a Remediation profile.

The script is listed below, but could be outdated, best to grab a copy from my github: https://github.com/JeroenP87/SharePointMapping/blob/main/SharePointMapping.ps1

#Sharepointmapping
#2023.08.04 potsolutions.nl

$upn = whoami /upn
#$upn = "[email protected]"

#Request odopen urls from logic app
$uri = "ADDYOURLOGICAPPURLHERE"

$postBody = @{
    upn = $upn
} | ConvertTo-Json
$response = Invoke-WebRequest -Method POST -Uri $uri -UseBasicParsing -Body $postBody -ContentType "application/json"

$sites = $response.Content.Split(',')

#wait for OneDrive to have started
Do{
    $ODStatus = Get-Process onedrive -ErrorAction SilentlyContinue
    If ($ODStatus) 
    {
        start-sleep -Seconds 10
    }
}
Until ($ODStatus)

#loop through the sites
foreach ($site in $sites) {
    if (!($site.Contains("site"))) {
        continue
    }

    #here we create a psobject and add all the queries
    $url = [uri] $site
    $ParsedQueryString = [System.Web.HttpUtility]::ParseQueryString($url.Query)

    $i = 0
    $queryParams = @()
    foreach($QueryStringObject in $ParsedQueryString){
        $queryObject = New-Object -TypeName psobject
        $queryObject | Add-Member -MemberType NoteProperty -Name Query -Value $QueryStringObject
        $queryObject | Add-Member -MemberType NoteProperty -Name Value -Value $ParsedQueryString[$i]
        $queryParams += $queryObject
        $i++
    }

    $queryParams
    $onedrivesite = ""
    $onedrivefolder = ""
    $odourl = "odopen://sync?"

    #loop through all the names and values, modify them where needed, build the odopen url tailored to the user
    foreach ($queryParam in $queryParams) {
        if ($queryParam.Query -eq "userId") { continue }
        if ($queryParam.Query -eq "isSiteAdmin") { continue }
        if ($queryParam.Query -eq "userEmail") { 
            $queryParam.Value = $upn
        }

        if ($queryParam.Query -eq "folderName") { 
            $onedrivefolder = $queryParam.Value
        }
        if ($queryParam.Query -eq "webTitle") { 
            $onedrivesite = $queryParam.Value
        }
        $odourl = $odourl + $queryParam.Query + "=" + $queryParam.Value + "&"
    }

    #check if the site has already been mapped for the current user
    $registry = Get-Item HKCU:\Software\Microsoft\OneDrive\Accounts\Business1\ScopeIdToMountPointPathCache -erroraction silentlycontinue

    foreach ($reg in $registry.Property) {
        $prop = Get-ItemProperty HKCU:\Software\Microsoft\OneDrive\Accounts\Business1\ScopeIdToMountPointPathCache -Name $reg 
            if ($prop.$($reg).EndsWith($onedrivesite + " - " + $onedrivefolder)) {
                $odourl = ""
                continue
            }
    }
    if ($odourl) {
        #execution was not skipped, new folder to map, execute the odopen url!
        Start-Process $odourl

        start-sleep -Seconds 10
    }
}

Happy Syncing!

Categories:

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *