diff --git a/Architecture.md b/Architecture.md index b97c09b..b98724b 100644 --- a/Architecture.md +++ b/Architecture.md @@ -4,7 +4,7 @@ The diagram below details the architecture of the Provision Assist solution and ``` mermaid graph TD - A(Canvas Power App) --> | Submit data | B[(SharePoint List)] --> C(Power Automate Approval Flow) --> D(Logic App) --> E(Azure AD App) <--> | Secret stored in Key Vault | F(Azure Key Vault) --> G{Type of collaboration space} --> |SharePoint Site| H[SharePoint REST API] + A(Canvas Power App) --> | Submit data | B[(SharePoint List)] --> C(Power Automate Approval Flow) --> D(Logic App) --> E(Entra ID App) <--> | Secret stored in Key Vault | F(Azure Key Vault) --> G{Type of collaboration space} --> |SharePoint Site| H[SharePoint REST API] G --> | Office 365 Group | I(Microsoft Graph) G --> | Viva Engage Community | J(Yammer REST API) I --> K(Azure Automation) diff --git a/Data-access-security.md b/Data-access-security.md index fc96e67..e27e114 100644 --- a/Data-access-security.md +++ b/Data-access-security.md @@ -2,33 +2,33 @@ The Provision Assist solution uses the **Microsoft Graph API**, **Yammer REST API** and the **SharePoint REST API** to perform provisioning of Groups, Sites, Teams and Yammer Communities. -Provisioning is carried out using an **Azure AD App Registration** which has the required permissions to the Microsoft Graph API assigned to it. For the most part **Application Permissions** are used with one exception - the application of sensitivity labels. +Provisioning is carried out using an **Entra ID App Registration** which has the required permissions to the Microsoft Graph API assigned to it. For the most part **Application Permissions** are used with one exception - the application of sensitivity labels. At the time of writing (July 2023), the Graph API does not support applying sensitivity labels to Groups and Teams using Application Permissions therefore a Service Account is used (no MFA) and **Delegated Permissions** configured to the relevant Graph endpoint. If you choose to disable or not use the sensitivity label functionality, then this is not required. -The **Client ID** and **Client Secret** of the Azure AD app are stored in a dedicated Key Vault that is created for the Provision Assist solution. These are then extracted for use in the Logic Apps using the Key Vault action, the action is set to hide the input and outputs so the secret value cannot be seen when viewing the run history. +The **Client ID** and **Client Secret** of the Entra ID app are stored in a dedicated Key Vault that is created for the Provision Assist solution. These are then extracted for use in the Logic Apps using the Key Vault action, the action is set to hide the input and outputs so the secret value cannot be seen when viewing the run history. The full list of the required API permissions for the Microsoft Graph and SharePoint tenant can be found below. ## API Permissions -The API permissions required for the Azure AD app are as follows: +The API permissions required for the Entra ID app are as follows: ### Microsoft Graph | API Permission | Type | Description| Reason | |--|--|--|--| | Directory.Read.All | Application | Read directory data |Used to read Users, Groups and Teams from the tenant.| -| Directory.ReadWrite.All | Application | Read and write directory data |Used to create guest users in Azure AD if they are requested.| +| Directory.ReadWrite.All | Application | Read and write directory data |Used to create guest users in Entra ID if they are requested.| | Group.ReadWrite.All | Delegated | Read and write all groups |Used to apply sensitivity labels to created groups/teams.| | Group.ReadWrite.All | Application | Read and write all groups |Used to create and update the properties of groups/teams.| | InformationProtectionPolicy.Read.All | Application | Read all published labels and label policies for an organization. |Used to syncronize sensivity labels in the tenant to a SharePoint list.| | Sites.FullControl.All | Application | Have full control of all site collections. |Update the properties of provisioned SharePoint sites.| | TeamsTemplates.Read.All | Application | Read all available Teams Templates |Used to read the teams templates in the tenant and syncronize them to a SharePoint list.| -| User.Invite.All | Application | Invite guest users to the organization |Used to invite guest users in Azure AD if they are requested.| -| User.ReadWrite.All | Application | Read and write to all users' full profiles |Used to update guest users in Azure AD if they are requested.| +| User.Invite.All | Application | Invite guest users to the organization |Used to invite guest users in Entra ID if they are requested.| +| User.ReadWrite.All | Application | Read and write to all users' full profiles |Used to update guest users in Entra ID if they are requested.| ### SharePoint @@ -36,7 +36,7 @@ The API permissions required for the Azure AD app are as follows: |--|--|--|--| | Sites.FullControl.All | Application | Have full control of all site collections| Used to read and write to created SharePoint sites. | -In addition to the above, the Azure AD App must be registered as a **SharePoint add-in** and granted **Full Control permissions** to the SharePoint tenant. +In addition to the above, the Entra ID App must be registered as a **SharePoint add-in** and granted **Full Control permissions** to the SharePoint tenant. This is required because, as part of the provisioning there is a check to see if a SharePoint site matching the URL already exists both as an active site but also in the tenant recycle bin. diff --git a/Deployment-guide.md b/Deployment-guide.md index 5bd2aee..a874cd8 100644 --- a/Deployment-guide.md +++ b/Deployment-guide.md @@ -10,38 +10,58 @@ To begin, you will need: - Service account (used by Logic Apps to connect to SPO, Outlook and Teams) with an appropriate Microsoft 365 license (This account should NOT be an admin). This account CAN have MFA. - Service account for sensitivity label functionality (applying sensitivity labels), if you wish to use it. This can be the same account as the above if you wish however at the time of writing this account CANNOT use MFA due to restrictions in the Microsoft Graph. - Windows 10/11 machine on which to execute the PowerShell deployment script. +- PowerShell 7 downloaded and installed - https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4. - Azure CLI (Command Line Interface) - https://learn.microsoft.com/en-us/cli/azure/install-azure-cli. - Firewall/Proxy configured to allow connectivity using the Azure CLI - please test the 'az login' cmdlet works before proceeding. - - Global Administrator (to execute the `createazureadapp.ps1` script and authorize PnP PowerShell). + - Global Administrator (to execute the `createazureadapp.ps1` script and create/authorize the PnP app registration). - A user account with **Owner** rights to the Azure Subscription that is also a SharePoint, Power Platform and Teams Administrator. - A certificate (self-signed is ok) to use for Microsoft Graph and SharePoint REST API authentication (**Optional** as the deployment script will create a self-signed cert for you if preffered). + - App Registration for PnP PowerShell (see below). + + #### PnP PowerShell App Registration + + PnP PowerShell no longer supports the 'multi-tenant app registration' option. This previously created an app registration automatically for PnP PowerShell with all neccessary permissions. + + In order to authenticate and use PnP PowerShell moving forward, it is neccessary to create your own app registration with the required permissions. + + Before you execute the deployment script for Provision Assist, make sure you have created this app registration and have the certificate file and password to hand. + + The minimum permissions required for the PnP app registration to be able to run the deployment script are: + + **Microsoft Graph** + + - Group.Create + - Group.Read.All + + **SharePoint** + + - Sites.FullControl.All + + Once the Provision Assist deployment is complete, you may delete the PnP PowerShell app registration or remove the above permissions if you no longer require them. + + You can find more details on the changes to PnP PowerShell authentication [here](https://pnp.github.io/blog/post/changes-pnp-management-shell-registration/). + + Please see [this video](https://www.youtube.com/watch?v=ecRZrHOucz4&t=359s) for details of how to create and use the app registration. + + If Sites.FullControl.All is a concern, you may create the SharePoint site for Provision Assist manually and make sure the name in the parameters.json file matches the name of the site you created. + +#### PowerShell 7.x + +The Provision Assist deployment script requires PowerShell 7 and no longer supports 5.1. Please ensure PowerShell 7 is installed before installing the PowerShell modules listed below. #### PowerShell Modules The following PowerShell modules are used by the deployment script and must be installed before executing the script: -- PnP.PowerShell (We do not support v2 - please install the latest v1 version) +- PnP.PowerShell - Az -- AzureADPreview - ImportExcel - WriteAscii -- Microsoft.Graph (Due to a dependency issue, please install version 2.9.1 ONLY) - -Once installed, please perform the following steps to 'authorize' the PnP Management Shell (PnP PowerShell). PnP now uses an Azure AD app registration to carry out operations, this requires the consent of a **Global Administrator** and must be performed BEFORE the main deployment script can be executed. - -1. Launch PowerShell as an Administrator (Global Administrator). -2. Connect to your SharePoint admin center by running the following cmdlet - ```Connect-PnPOnline -Url https://contoso-admin.sharepoint.com``` (replace contoso with your tenant name). -3. Check the 'Consent on behalf of your organization' checkbox and click 'Accept', wait for the cmdlet to finish. -4. Close the PowerShell window. - -![PnP powershell consent screenshot](/Images/PnPPowerShellConsent.png) - -You can delete this Azure AD app registration AFTER deployment of Provision Assist is completed if you wish. ## Step 1: Configuring PowerShell 1. Download the [latest release](https://github.com/pnp/provision-assist-m365/releases/latest) of Provision Assist. -2. Launch PowerShell as an Administrator. +2. Launch PowerShell 7 as an Administrator. 3. Set the PowerShell Execution Policy to 'Unrestricted' by running the following cmdlet - ```Set-ExecutionPolicy -ExecutionPolicy unrestricted``` ## Step 2: Updating Parameters.json file @@ -62,13 +82,13 @@ You may refer to the following to understand each parameter: - `managedPath ` - Managed path configured in the tenant e.g. 'sites' or 'teams' (no forward slash). -- `subscriptionId` - Azure subscription to deploy the solution to (MUST be associated with the Azure AD of the Microsoft 365 tenant that you wish to deploy this solution to). +- `subscriptionId` - Azure subscription to deploy the solution to (MUST be associated with the Entra ID directory of the Microsoft 365 tenant that you wish to deploy this solution to). - `region` - Azure region in which to create the resources. The internal name should be used e.g. `uksouth`. The location MUST support Automation and Logic Apps. See [Valid Azure locations](https://azure.microsoft.com/en-gb/explore/global-infrastructure/products-by-region/?products=logic-apps%2Cautomation®ions=all). - `resourceGroupName` - Name for a new resource group to deploy the solution to - the script will create this resource group. - - `appName` - Name for the Azure AD app that will be created e.g. `ProvisionAssist`. + - `appName` - Name for the Entra ID app that will be created e.g. `ProvisionAssist`. - `createSelfSignedCert` - Specifies whether to create a self-signed certificate as part of the deployment. If set to true, a self-signed cert will be created through the Azure CLI with the name specified in the 'certName' parameter. @@ -76,13 +96,17 @@ You may refer to the following to understand each parameter: - `certValidityDays` - Number of days that the certificate is valid for (if 'createSelfSignedCert' is set to true). The default is 365 days. + - `pnpAppId` - Id of the PnP Entra app registration that you created when configuring PnP PowerShell. + +- `pnpCertPath` - Path to the PnP certificate on your local machine that you created when configuring PnP PowerShell. + - `siteLogoPath` (**Optional)** - Path to a company logo (ideally stored in SharePoint) that all users can access to set as the logo for created sites. Please ensure this path is to an image, if you don't have an image leave this blank. - `serviceAccountUPN` - UPN of Service Account to be used for the solution - used to connect the Logic App API connections. Service account should be a standard Microsoft 365 user who has SPO/Exchange/Teams licenses enabled. Refer to [Assign licenses to users](https://docs.microsoft.com/en-us/microsoft-365/admin/manage/assign-licenses-to-users?view=o365-worldwide) for more details. - `isEdu` - Specifies whether the current tenant is an Education tenant. If set to true, the Education Teams Templates will be deployed. These will be skipped if set to false or left blank -- `KeyVaultName` - Name to use for the Key Vault that is provisioned by the deployment script. The Key Vault stores the app id and secret of the Azure AD app that this solution uses. This ensures that these are held securely. The name of the key vault must be unique across the Azure region that you are deploying to. If a key vault matching the name provided exists ***in*** the current subscription, it can be used. **PLEASE NOTE - IF YOU USE AN EXISTING KEY VAULT, IT WILL BE OVERWRITTEN AND CONFIGURATION SUCH AS ROLE ASSIGNMENTS WILL BE LOST. WE RECOMMEND USING A DEDICATED KEY VAULT FOR PROVISION ASSIST**. The script will validate that the name is available and if not, an alternative name will need to be provided. +- `KeyVaultName` - Name to use for the Key Vault that is provisioned by the deployment script. The Key Vault stores the app id and secret of the Entra ID app that this solution uses. This ensures that these are held securely. The name of the key vault must be unique across the Azure region that you are deploying to. If a key vault matching the name provided exists ***in*** the current subscription, it can be used. **PLEASE NOTE - IF YOU USE AN EXISTING KEY VAULT, IT WILL BE OVERWRITTEN AND CONFIGURATION SUCH AS ROLE ASSIGNMENTS WILL BE LOST. WE RECOMMEND USING A DEDICATED KEY VAULT FOR PROVISION ASSIST**. The script will validate that the name is available and if not, an alternative name will need to be provided. - `enableSensitivity` - Enable the Sensitivity Label functionality. Note - this will require you to have a Service Account with NO MFA, this can be the same service account as above if you wish. @@ -90,16 +114,16 @@ You may refer to the following to understand each parameter: ## Step 3: Execute the scripts -### Azure AD App Creation +### Entra ID App Creation -The first step is to execute the dedicated script responsible for creating the Azure AD app and granting admin consent for the Microsoft Graph API permissions. +The first step is to execute the dedicated script responsible for creating the Entra ID app and granting admin consent for the Microsoft Graph API permissions. **This part of the deployment requires a user account with Global Administrator access.** -1. Launch a PowerShell window as an Administrator. +1. Launch a PowerShell 7 window as an Administrator. 2. Navigate to the 'Scripts' folder. -3. Execute the createazureadapp script in the PowerShell window - ```.\createazureadapp.ps1``` -4. Enter a name for the Azure AD app when prompted (**This must be the same name as the 'appName' parameter in the parameters.json file**). +3. Execute the createazureadapp script in the PowerShell window - ```.\createentraidapp.ps1``` +4. Enter a name for the Entra ID app when prompted (**This must be the same name as the 'appName' parameter in the parameters.json file**). 5. Wait for the script to complete. ### Deployment of Resources @@ -108,15 +132,15 @@ The next step of deployment is to execute the deploy script. **Ensure the account you are using at this stage has owner rights to the Azure Subscription and is also a SharePoint Administrator.** -**The deployment script generates a secret for the AD app created above. The default expiry period for this secret is to 1 year, for details on how refresh the secret when it expires, see [Refreshing app secret](./Refreshing-app-secret.md).** +**The deployment script generates a secret for the Entra ID app created above. The default expiry period for this secret is to 1 year, for details on how refresh the secret when it expires, see [Refreshing app secret](./Refreshing-app-secret.md).** As the script uses various PowerShell modules to perform deployment, it will prompt for authentication a number of times. -1. Launch a PowerShell window as an Administrator. +1. Launch a PowerShell 7 window as an Administrator. 2. Navigate to the 'Scripts' folder. 3. Execute the deploy script in the PowerShell window - ```.\deploy.ps1``` -During installation PnP Management Shell will request permissions for the application on behalf of your organization. Please grant consent. You will be able to revoke that access after the deployment of the solution. +You will be prompted during the script to enter the password for the PnP app registration certificate. If you chose to enable the Sensitivity Label functionality, a dialog will be displayed prompting for the password for the Service Account. Please complete the prompt. @@ -347,7 +371,7 @@ Details of what these are and what they do can be seen below: - **GetHubSites** (Retrieves all Hub Sites in the tenant and creates these as list items in the 'Hub Sites' list in the SharePoint site.) - **GetSiteTemplates** (Retrieves all SharePoint Site Templates deployed in the tenant and creates these as list items in the 'Site Templates' list in the SharePoint site.) - **GetTeamsTemplates** (Retrieves Teams Templates configured in the Teams Admin Center and creates references to these as list items in the 'Teams Templates' list in the SharePoint site.) -- **SyncGroupSettings** (Gets group settings - Blocked Words and Classifications from Azure AD and updates list items in the 'Provisioning Request Settings' list in the SharePoint site.) +- **SyncGroupSettings** (Gets group settings - Blocked Words and Classifications from Entra ID and updates list items in the 'Provisioning Request Settings' list in the SharePoint site.) - **SyncLabels** (Retrieves all Sensitivity labels from Purview in the tenant and adds these to the 'IP Labels' list in the SharePoint site.) Follow these steps to run them 'on demand': diff --git a/Naming-conventions.md b/Naming-conventions.md index c2d512f..0b39485 100644 --- a/Naming-conventions.md +++ b/Naming-conventions.md @@ -53,11 +53,11 @@ e.g. VE_My Viva Engage Community_IT (Where IT is the users' department). ![Viva Engage naming convention screenshot text](./images/VivaEngageNamingConvention.png) -**Azure AD:** +**Entra ID:** -The group name and email address in Azure AD will match the specified naming convention. The group name will retain spaces but the email will have these automatically removed. +The group name and email address in Entra ID will match the specified naming convention. The group name will retain spaces but the email will have these automatically removed. -![Azure ad naming convention screenshot](./images/AADNamingConvention.png) +![Entra id naming convention screenshot](./images/AADNamingConvention.png) ## Configuration diff --git a/README.md b/README.md index aacbc46..4a68705 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ | [Deployment guide](/Deployment-guide.md) | [Architecture](/Architecture.md) | [Data Stores](/Data-stores.md) | [Cost Estimates](/Cost-estimates.md) | [Data Access & Security](/Data-access-security.md) | [Naming Conventions](/Naming-conventions.md) | [Provisioning Types](/Provisioning-types.md) | [Site Templates](/Site-templates.md) | [Sensitivity Labels](/Sensitivity-labels.md) | [Teams Templates](/Teams-templates.md) | [PnP Templates](/PnP-templates.md) | [Retention Labels](/Retention-labels.md) | [Approval Flow](/Approval-flow.md) | [Regional Settings](/Regional-settings.md) | [Recommendation Scoring](/Recommendation-scoring.md) | [Translations](/Translations.md) | [Refreshing App Secret](/Refreshing-app-secret.md) | [V2](/Version2.md) | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | -Provision Assist is a Power Platform and Azure based solution that provides an alternative to self-service creation in Microsoft 365. It provides governance over this process through a frontend Power App allowing users to request Collaboration 'Spaces' (Teams, Groups, SharePoint Online Sites & Viva Engage Communities) and backend Azure components providing automated provisioning. +Provision Assist is a Power Platform and Azure based solution that provides an alternative to self-service creation in Microsoft 365. It provides governance over this process through a frontend Power App allowing users to request Collaboration 'Spaces' (Teams, Groups, SharePoint Online Sites & Viva Engage Communities) and backend Azure components providing automated provisioning. Provision Assist can be used as part of a Copilot for Microsoft 365 deployment in order to establish a layer of governance over self-service. ![Provision Assist Home Screenshot](/Images/ProvisionAssistHome.png) diff --git a/Refreshing-app-secret.md b/Refreshing-app-secret.md index a77295c..d390bc3 100644 --- a/Refreshing-app-secret.md +++ b/Refreshing-app-secret.md @@ -1,6 +1,6 @@ # Refreshing App Secret -From time to time you may need to update/refresh the client secret used in the Azure AD App for Provision Assist. This may be because the secret has expired or you wish to generate a new one. +From time to time you may need to update/refresh the client secret used in the Entra ID App for Provision Assist. This may be because the secret has expired or you wish to generate a new one. When you deploy Provision Assist, the secret generated for the AD app has a default expiry of 1 year from the date the deployment script was executed. diff --git a/Source/Scripts/createazureadapp.ps1 b/Source/Scripts/createentraidapp.ps1 similarity index 80% rename from Source/Scripts/createazureadapp.ps1 rename to Source/Scripts/createentraidapp.ps1 index d6ffa4b..074a82f 100644 --- a/Source/Scripts/createazureadapp.ps1 +++ b/Source/Scripts/createentraidapp.ps1 @@ -1,15 +1,15 @@ <# .SYNOPSIS - Creates the Azure AD App Registration for the Provision Assist solution and grants required API Permissions. + Creates the Entra ID App Registration for the Provision Assist solution and grants required API Permissions. .DESCRIPTION - Creates the Azure AD App Registration for the Provision Assist solution and grants API permissions. + Creates the Entra ID App Registration for the Provision Assist solution and grants API permissions. - This script uses the Azure CLI to create/update the AD App. + This script uses the Azure CLI to create/update the Entra ID App. This script should be executed using an account that has Global Administrator rights, this is neccessary to grant the requried API permissions. - This script accepts a single parameter, which is the name of the AD App you wish to use for Provision Assist. + This script accepts a single parameter, which is the name of the Entra ID App you wish to use for Provision Assist. .EXAMPLE createadapp.ps1 @@ -49,8 +49,8 @@ If (-not (Test-Path -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2") -a break } -# Gets the azure ad app -function GetAzureADApp { +# Gets the entra id app +function GetEntraIDApp { param ($appName) $app = az ad app list --filter "displayName eq '$appName'" | ConvertFrom-Json @@ -61,15 +61,15 @@ function GetAzureADApp { function CreateAzureADApp { try { - Write-Host "### AZURE AD APP CREATION ###" -ForegroundColor Yellow + Write-Host "### ENTRA ID APP CREATION ###" -ForegroundColor Yellow # Check if the app already exists - script has been previously executed - $app = GetAzureADApp $appName + $app = GetEntraIDApp $appName if (-not ([string]::IsNullOrEmpty($app))) { - # Update azure ad app registration using CLI - Write-Host "Azure AD App $($appName) already exists - updating existing app..." -ForegroundColor Yellow + # Update entra id app registration using CLI + Write-Host "Entra ID App $($appName) already exists - updating existing app..." -ForegroundColor Yellow az ad app update --id $app.appId --required-resource-accesses './appmanifest.json' @@ -79,14 +79,14 @@ function CreateAzureADApp { Start-Sleep -s 60 - Write-Host "Updated Azure AD App" -ForegroundColor Green + Write-Host "Updated Entra ID App" -ForegroundColor Green } else { # Create the app - Write-Host "Creating Azure AD App - $($appName)..." -ForegroundColor Yellow + Write-Host "Creating Entra ID App - $($appName)..." -ForegroundColor Yellow - # Create azure ad app registration using CLI + # Create entra id app registration using CLI $app = az ad app create --display-name $appName --required-resource-accesses './appmanifest.json' $appId = $app | ConvertFrom-Json | Select-Object appid @@ -97,7 +97,7 @@ function CreateAzureADApp { Start-Sleep -s 60 - Write-Host "Created Azure AD App" -ForegroundColor Green + Write-Host "Created Entra ID App" -ForegroundColor Green } @@ -113,24 +113,24 @@ function CreateAzureADApp { Write-Host "Granted admin consent" -ForegroundColor Green - Write-Host "### AZURE AD APP CREATION FINISHED ###" -ForegroundColor Green + Write-Host "### ENTRA ID APP CREATION FINISHED ###" -ForegroundColor Green } catch { - throw('Failed to create the Azure AD App {0}', $_.Exception.Message) + throw('Failed to create the Entra ID App {0}', $_.Exception.Message) } } $ErrorActionPreference = "stop" -Write-Host "### CREATE AD APP SCRIPT STARTED `n(c) Microsoft Corporation ###" -ForegroundColor Magenta +Write-Host "### CREATE ENTRA ID APP SCRIPT STARTED `n(c) Microsoft Corporation ###" -ForegroundColor Magenta Write-Ascii -InputObject "Provision Assist" -ForegroundColor Green # Initialise connections - Azure Az/CLI Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow -$cliLogin = az login +az login Write-Host "Connected to Azure" -ForegroundColor Green CreateAzureADApp diff --git a/Source/Scripts/deploy.ps1 b/Source/Scripts/deploy.ps1 index 5832ce1..52a7cd5 100644 --- a/Source/Scripts/deploy.ps1 +++ b/Source/Scripts/deploy.ps1 @@ -3,17 +3,17 @@ Deploys the following assets of the Provision Assist solution - -SharePoint Site & Assets - -Azure AD App - Creates sectet + -Entra ID App - Creates sectet -Azure Automation Account & Runbooks -Logic App .DESCRIPTION Deploys the Provision Assist solution (excluding the PowerApp and Flows). - This script uses the Azure CLI, Azure Az PowerShell, SharePoint PnP PowerShell and the Microsoft Graph PowerShell Modules to perform the deployment. + This script uses the Azure CLI, Azure Az PowerShell and PnP PowerShell Modules to perform the deployment. - As part of the deployment, the script will generate a secet for the Azure AD App created by the 'createadapp.ps1' script. + As part of the deployment, the script will generate a secet for the Entra ID App created by the 'createadapp.ps1' script. - The account running this script must be able to create secrets for Azure AD Applications. The 'Cloud Application Administrator' role will suffice. + The account running this script must be able to create secrets for Entra ID Applications. The 'Cloud Application Administrator' role will suffice. The script requires input during execution, requires sign-in to a number of services and therefore should be monitored. @@ -62,7 +62,7 @@ $templatePath = "Templates\provisionassist-sitetemplate.xml" $settingsPath = "Settings\SharePoint List items.xlsx" # Required PS modules -$preReqModules = "PnP.PowerShell", "Az", "AzureADPreview", "ImportExcel", "WriteAscii", "Microsoft.Graph" +$preReqModules = "PnP.PowerShell", "Az", "ImportExcel", "WriteAscii" # Worksheets $provRequestSettingsWorksheetName = "Provisioning Request Settings" @@ -113,7 +113,6 @@ $global:teamsTemplatesListId = $null $global:appId = $null $global:appSecret = $null $global:appServicePrincipalId = $null -$global:siteClassifications = $null $global:tenantUrl = $null # Validates if a parameter in the json file is valid @@ -212,6 +211,26 @@ function ValidateParameters { $isValid = $false; } + if (-not (IsValidParam($parameters.pnpAppId))) { + Write-Host "Invalid certValidityDays" -ForegroundColor Red + $isValid = $false; + } + + if (-not (IsValidParam($parameters.pnpAppId))) { + Write-Host "Invalid certValidityDays" -ForegroundColor Red + $isValid = $false; + } + + if (-not (IsValidParam($parameters.pnpCertPath))) { + Write-Host "Invalid certValidityDays" -ForegroundColor Red + $isValid = $false; + } + + if (-not (IsValidParam($parameters.fullTenantName))) { + Write-Host "Invalid certValidityDays" -ForegroundColor Red + $isValid = $false; + } + return $isValid } @@ -401,9 +420,6 @@ function ConfigureSharePointSite { if ($setting.Title -eq "SPOManagedPath") { $setting.Value = $parameters.managedPath.Value } - if ($setting.Title -eq "SiteClassifications") { - $setting.Value = $global:siteClassifications - } if ($setting.Title -eq "EnableSensitivityLabels") { If ($parameters.enableSensitivity.Value) { $setting.Value = "true" @@ -639,36 +655,13 @@ function ConfigureSharePointSite { } } -# Get configured site classifications -function GetSiteClassifications { - try { - $groupDirectorySetting = AzureADPreview\Get-AzureADDirectorySetting | Where-Object DisplayName -eq "Group.Unified" - $classifications = $groupDirectorySetting.Values | Where-Object Name -eq "ClassificationList" | Select-Object Value - - $global:siteClassifications = $classifications.Value - } - catch { - throw('Failed to retrieve site classifications {0}', $_.Exception.Message) - } -} - -function UploadFiles ($context, $targetFolder, $sourcePath, $sourceFolder, $libraryName) { +function UploadFiles ($targetFolder, $sourcePath, $sourceFolder, $libraryName) { # Upload files into the folder - $folder = $Web.GetFolderByServerRelativeUrl($targetFolder) - $context.Load($folder) - $context.ExecuteQuery() - Get-ChildItem (Join-Path $sourcePath $sourceFolder) | Foreach-Object { - $FileStream = New-Object IO.FileStream($_.FullName, [System.IO.FileMode]::Open) - $FileCreationInfo = New-Object Microsoft.SharePoint.Client.FileCreationInformation - $FileCreationInfo.Overwrite = $true - $FileCreationInfo.ContentStream = $FileStream - $FileCreationInfo.URL = $_ - $Upload = $folder.Files.Add($FileCreationInfo) - $context.Load($Upload) - $context.ExecuteQuery() - Write-Host "Uploaded $($_.FullName) to $libraryName" -ForegroundColor Green + $file = $_.FullName + Add-PnPFile -Path $file -Folder $targetFolder + Write-Host "Uploaded $($_.Name) to $libraryName" -ForegroundColor Green } } @@ -677,12 +670,9 @@ function UploadFiles ($context, $targetFolder, $sourcePath, $sourceFolder, $libr function UploadAssets { try { Write-Host "Uploading assets" -ForegroundColor Yellow - $context = Get-PnPContext - $web = $context.Web - $context.ExecuteQuery() - UploadFiles $context $imageFolderUpload $packageRootPath $imagesDir "Site Assets" - UploadFiles $context $iconFolderUpload $packageRootPath $iconsDir "Site Assets" + UploadFiles $imageFolderUpload $packageRootPath $imagesDir "Site Assets" + UploadFiles $iconFolderUpload $packageRootPath $iconsDir "Site Assets" Write-Host "Uploaded files to Site Assets`n**PROVISION ASSIST SPO SITE CONFIGURATION COMPLETE**" -ForegroundColor Green } @@ -691,8 +681,8 @@ function UploadAssets { } } -# Gets the azure ad app -function GetAzureADApp { +# Gets the Entra ID app +function GetEntraIDApp { param ($appName) $app = az ad app list --filter "displayName eq '$appName'" | ConvertFrom-Json @@ -701,9 +691,9 @@ function GetAzureADApp { } -function CreateAzureADAppSecret { +function CreateEntraIDAppSecret { try { - Write-Host "### AZURE AD APP SECRET CREATION ###" -ForegroundColor Yellow + Write-Host "### ENTRA ID APP SECRET CREATION ###" -ForegroundColor Yellow # Check if the app already exists - script has been previously executed $app = GetAzureADApp $parameters.appName.Value @@ -713,9 +703,9 @@ function CreateAzureADAppSecret { $global:appId = $app.appId # Create a secret - this will autogenerate a password - Write-Host "Azure AD App $($parameters.appName.Value) found..." -ForegroundColor Yellow + Write-Host "Entra ID App $($parameters.appName.Value) found..." -ForegroundColor Yellow - Write-Host "Creating secret for Azure AD App - $($parameters.appName.Value)..." -ForegroundColor Yellow + Write-Host "Creating secret for Entra ID App - $($parameters.appName.Value)..." -ForegroundColor Yellow $secret = az ad app credential reset --id $global:appId @@ -730,19 +720,19 @@ function CreateAzureADAppSecret { } else { - throw("Azure AD App $($parameters.appName.Value)' does not exist. Please run the createadapp script first.") + throw("Entra ID App $($parameters.appName.Value)' does not exist. Please run the createentraidapp.ps1 script first.") } - Write-Host "### AZURE AD APP SECRET CREATION FINISHED ###" -ForegroundColor Green + Write-Host "### ENTRA ID APP SECRET CREATION FINISHED ###" -ForegroundColor Green } catch { - throw('Failed to create the secret for the Azure AD App {0}', $_.Exception.Message) + throw('Failed to create the secret for the Entra ID App {0}', $_.Exception.Message) } } function CreateAutomationRoleAssignments { - # Create automation role assignments for AD app service principal if they do not already exist + # Create automation role assignments for Entra ID app service principal if they do not already exist $jobOperatorRoleAssignment = Get-AzRoleAssignment -ObjectId $global:appServicePrincipalId -ResourceName $automationAccountName -ResourceType Microsoft.Automation/automationAccounts -ResourceGroupName $parameters.resourceGroupName.Value -RoleDefinitionName "Automation Job Operator" @@ -766,22 +756,15 @@ function AssignManagedIdentityPermissions { Write-Host "Assigning SharePoint app role to managed identity" -ForegroundColor Yellow # Get service principal for the automation account - $paAutoServicePrincipal = Get-MgServicePrincipal -Filter "DisplayName eq '$($automationAccountName)'" + $paAutoServicePrincipal = Get-AzADServicePrincipal -DisplayName "$automationAccountName" - $spoResource = Get-MgServicePrincipal -Filter "DisplayName eq 'Office 365 SharePoint Online'" + $spoResource = Get-AzADServicePrincipal -DisplayName "Office 365 SharePoint Online" # Get the app role we need to assign - $spoFullControlAppRole = $spoResource.AppRoles | Where-Object { $_.value -eq 'Sites.FullControl.All' } + $spoFullControlAppRole = $spoResource.AppRole | Where-Object DisplayName -eq 'Have full control of all site collections' # Get existing role assignments - $roles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $paAutoServicePrincipal.Id - - # Build the params - $spoParams = @{ - "PrincipalId" = $paAutoServicePrincipal.Id - "ResourceId" = $spoResource.Id - "AppRoleId" = $spoFullControlAppRole.Id - } + $roles = Get-AzADServicePrincipalAppRoleAssignment -ServicePrincipalId $paAutoServicePrincipal.Id # Check that the role assigments do not already exist @@ -789,7 +772,7 @@ function AssignManagedIdentityPermissions { if ($null -eq $existingRoleAssignment) { # Assign SharePoint app roles to the service principal - New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $paAutoServicePrincipal.Id -BodyParameter $spoParams + New-AzADServicePrincipalAppRoleAssignment -ServicePrincipalId $paAutoServicePrincipal.Id -AppRoleId $spoFullControlAppRole.Id -ResourceId $spoResource.Id } Write-Host "Finished assigning SharePoint app role to managed identity" -ForegroundColor Green @@ -799,7 +782,6 @@ function AssignManagedIdentityPermissions { } } - # Deploy ARM templates function DeployARMTemplates { try { @@ -994,33 +976,33 @@ Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow $azConnect = Connect-AzAccount -Subscription $parameters.subscriptionId.Value -Tenant $parameters.tenantId.Value ValidateKeyVault ValidateAzureLocation -Write-Host "Launching Azure AD sign-in..." -ForegroundColor Yellow -AzureADPreview\Connect-AzureAD Write-Host "Launching Azure CLI sign-in..." -ForegroundColor Yellow -$cliLogin = az login +az login Write-Host "Connected to Azure" -ForegroundColor Green -# Connect to Microsfot Graph - -# Define the scopes to use -$scopes = @("AppRoleAssignment.ReadWrite.All", "Application.Read.All", "Directory.Read.All") - -# Connect to Microsoft Graph using the specified scopes -Write-Host "Launching Microsoft Graph sign-in...please consent" -Connect-MgGraph -Scopes $scopes -Write-Host "Connected to Microsoft Graph" -ForegroundColor Green - # Connect to PnP Write-Host "Launching PnP sign-in..." -ForegroundColor Yellow -Connect-PnPOnline -Url "https://$($parameters.spoTenantName.Value)-admin.sharepoint.com" -Interactive + +$pnpCertPassword = Read-Host -Prompt "Enter password for PnP certificate (leave blank if your certficate is not secured with a password)" -AsSecureString + +if ($pnpCertPassword.Length -eq 0) { + Connect-PnPOnline -Url "https://$($parameters.spoTenantName.Value)-admin.sharepoint.com" -ClientId $parameters.pnpAppId.Value -CertificatePath $parameters.pnpCertPath.Value -Tenant $parameters.fullTenantName.Value +} else { + Connect-PnPOnline -Url "https://$($parameters.spoTenantName.Value)-admin.sharepoint.com" -ClientId $parameters.pnpAppId.Value -CertificatePath $parameters.pnpCertPath.Value -CertificatePassword $pnpCertPassword -Tenant $parameters.fullTenantName.Value +} Write-Host "Connected to SPO" -ForegroundColor Green $currUserId = az ad signed-in-user show --query id | ConvertFrom-Json CreateAzureADAppSecret -GetSiteClassifications CreateRequestsSharePointSite # Connect to the new site -Connect-PnPOnline $requestsSiteUrl -Interactive + +if ($pnpCertPassword.Length -eq 0) { + Connect-PnPOnline -Url $requestsSiteUrl -ClientId $parameters.pnpAppId.Value -CertificatePath $parameters.pnpCertPath.Value -Tenant $parameters.fullTenantName.Value +} else { + Connect-PnPOnline -Url $requestsSiteUrl -ClientId $parameters.pnpAppId.Value -CertificatePath $parameters.pnpCertPath.Value -CertificatePassword $pnpCertPassword -Tenant $parameters.fullTenantName.Value +} + ConfigureSharePointSite UploadAssets diff --git a/Source/Scripts/parameters.json b/Source/Scripts/parameters.json index fb710f1..0d32d20 100644 --- a/Source/Scripts/parameters.json +++ b/Source/Scripts/parameters.json @@ -1,58 +1,70 @@ { "tenantId": { - "Value": "<>", + "Value": "", "Description": "Id of the tenant to deploy to. Can be found in the Azure Active Directory blade." }, "spoTenantName": { - "Value": "<>", + "Value": "", "Description": "Name of the SharePoint tenant to deploy to (excluding onmicrosoft.com) e.g. contoso" }, + "fullTenantName": { + "Value": "", + "Description": "Full name of the tenant to deploy to e.g. contoso.onmicrosoft.com" + }, "requestsSiteName": { - "Value": "<>", + "Value": "", "Description": "Name of the SharePoint site to store the requests, can include spaces (URL/Alias auomatically generated). If the site exists, it will prompt to overwrite and will apply the provisioning template." }, "requestsSiteDesc": { - "Value": "<>", + "Value": "", "Description": "Description for the site that will be created above." }, "managedPath": { - "Value": "<>", + "Value": "", "Description": "Managed path configured in the tenant e.g. 'sites' or 'teams' (no forward slash)." }, "subscriptionId": { - "Value": "<>", + "Value": "", "Description": "Azure subscription id to deploy the solution to. All resources in an Azure subscription are billed together." }, "region": { - "Value": "<>", + "Value": "", "Description": "The Azure region to deploy to. The internal name should be used. Not every resource is available in every region." }, "resourceGroupName": { - "Value": "<>", + "Value": "", "Description": "A name for a new resource group to deploy the solution to - if the resource group does not exist it will be created." }, "appName": { - "Value": "<>", - "Description": "Name for the Azure ad app that will be created." + "Value": "", + "Description": "Name for the Entra ad app registration that will be created." }, "createSelfSignedCert": { "Value": true, "Description": "Specifies whether a self signed certificate should be created and uploaded to the AD app and Key Vault. If set to false, the certificate must be uploaded manually to the app registration and Key Vault." }, "certName": { - "Value": "<>", + "Value": "provisionassist-cert", "Description": "Name for the self signed certificate that will be created (if createSelfSignedCert is set to true)." }, "certValidityDays": { "Value": 365, "Description": "Number of days the self signed certificate will be valid for (if createSelfSignedCert is set to true)." }, + "pnpAppId": { + "Value": "", + "Description": "Id of the PnP Entra app registration." + }, + "pnpCertPath": { + "Value": "", + "Description": "Path to your PnP certificate that will be used to connect with PnP PowerShell. Please ensure to double escape the backslashes above." + }, "siteLogoPath": { - "Value": "<>", + "Value": "", "Description": "Path to a company logo which will be set as the site logo for Communication sites and no Group Team Sites when provisioned. Optional - can be left blank." }, "serviceAccountUPN": { - "Value": "<>", + "Value": "", "Description": " UPN of Service Account to be used for the solution - used to connect to SharePoint and send Emails in the Logic App." }, "isEdu": { @@ -60,11 +72,11 @@ "Description": "Specifies whether the current tenant is an Education tenant. If set to true, the Education Teams Templates will be deployed. These will be skipped if set to false or left blank." }, "keyVaultName": { - "Value": "<>", + "Value": "", "Description": "Name for the Key Vault that will be provisioned to store the Azure ad app ID and secret. The Key Vault name must be unique and not exist in another subscription." }, "enableSensitivity": { - "Value": false, + "Value": true, "Description": "Enable the sensitivity label functionality." }, "skipApplySPOTemplate": {