Back in May 2016, I wrote a post to show you how to update the Client Secret of a SharePoint add-in. This used the PowerShell module MSOL. https://cann0nf0dder.wordpress.com/2016/05/18/updating-an-expired-client-secret-of-sharepoint-add-in/
The MSOL module is now (or going to be) deprecated. Therefore, I needed to find a different way of doing this, and ideally something that could be done with Azure Dev-ops pipelines. I originally started with AZ CLI. Although the Client Secret is tied to a Service Principal in Azure AD, I was unable to change the Key Credentials or Password Credentials for it.
Due to the issues I was getting with AZ CLI, I used Azure-AD module instead. See at end of blog post, how I got Az CLI to work with a workaround.
Updating Client Secret
Connect to Azure-AD
First you will need to connect to Azure-AD
#Install AzureAD | |
Write-Information -MessageData:"Getting if the AzureAD powershell module is available..." | |
if(-not (Get-Module AzureAD)) { | |
Write-Information -MessageData:"Installing the NuGet Package provider..." | |
Install-PackageProvider -Name:NuGet -Force -Scope:CurrentUser | |
Write-Information -MessageData:"Installing the AzureAD Powershell Module..." | |
Install-Module AzureAD -Scope:CurrentUser -Force | |
} | |
$Credential = Get-Credential | |
Connect-AzureAD -Credential $Credential |
The above code ensures that you have Azure AD installed on your machine, and logs you in.
Getting the Add-in as a Service Principal
Once you have logged in, you will be able to call back your SharePoint Add-in. The SharePoint Add-in is actually a Service Principal within Azure AD, and we will grab this using Get-AzureADServicePrincipal. You can do this by using the AppId, or the AppName. The AppId is fine to use, but when you want to use the same script across multiple environments, you will need to ensure you are passing in the different AppId for each environment. THis is why I have used the Name of the Add-In. (Assuming you have given your App the same name in each environment)
$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq 'Demo App'" | |
#OR If using APP ID. | |
$serviceprincipalByID = Get-AzureADServicePrincipal -All:$true -Filter "AppId eq 'ab739749-827d-4437-90e5-bf181c5407e0'" |
Create a new Secret
Next you need to be able to create a new secret. This is done by creating random bytes and converting to a Base64String. I ensure the password is valid for an additional 2 years.
$bytes = New-Object Byte[] 32 | |
$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create() | |
$rand.GetBytes($bytes) | |
$rand.Dispose() | |
$newClientSecret = [System.Convert]::ToBase64String($bytes) | |
$dtStart = [System.DateTime]::Now | |
$dtEnd = $dtStart.AddYears(2) | |
write-output $newClientSecret |
Create Key Credentials and Password Credential for the App
The SharePoint Add-In requires 2 Key Credentials (One Sign and one Verify) and 1 Password Credentials. The following script creates new ones, this allows both the old password and the new password to continue working at the same time, until you are able to update the code that uses the ClientID and ClientSecret.
Write-Information "Updating KeyCredential Usage Sign..." | |
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Sign -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null | |
Write-Information "Updating KeyCredential Usage Verify..." | |
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Verify -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null | |
Write-Information "Updating PasswordCredential..." | |
New-AzureADServicePrincipalPasswordCredential -ObjectId $serviceprincipal.ObjectId -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null |
Removing the original Key and Password Credential for the App
The following code shows how to loop round the Key and Password credentials and remove the original ones. It does this by looking for any Credentials that were created before the start date of the new ones. I would only run this part of my code, once I know I have updated my application to use the new password. Not in my example here, but where I’m using it in the real world, my Client Secret is stored within a Keyvault. I would update the keyvault value (ensuring to disable the previous version).
Write-Information "Remove all KeyCredential started before $(Get-Date $dtStart -Format 'O' )..." | |
$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq '$SharePointAddInName'" | |
$serviceprincipal.KeyCredentials | ForEach-Object{ | |
$credential = $PSItem | |
if($($credential.StartDate) -lt $dtStart) | |
{ | |
Write-Information -MessageData:"Removing KeyCredential $($credential.KeyId)" | |
Remove-AzureADServicePrincipalKeyCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId | |
} | |
} | |
Write-Information "Remove all PasswordCredential started before $(Get-Date $dtStart -Format 'O' )..." | |
$serviceprincipal.PasswordCredentials | ForEach-Object{ | |
$credential = $PSItem | |
if($($credential.StartDate) -lt $dtStart) | |
{ | |
Write-Information -MessageData:"Removing PasswordCredential $($credential.KeyId)" | |
Remove-AzureADServicePrincipalPasswordCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId | |
} | |
} |
Connecting with AZ Cli Workaround
Using Azure Dev-Ops Pipeline, I really wanted to use AZ cli to be able to update the Client Secret of a SharePoint Add-in. Due to the error messages when I attempted to update the Service Principal Key Credential and Password Credentials, I was forced to use Azure-AD instead. So how can I uses AZ Cli.
My Dev-Ops Pipeline uses a service account, and I have ensured this service account has permissions to update the directory.
Then I can connect from Az Cli to Azure-AD doing the following:
#Once signed into Azure CLI | |
$Token = az account get-access-token --resource-type "aad-graph" | ConvertFrom-Json | |
$AzAccount = az account show | ConvertFrom-Json | |
Connect-AzureAD -AadAccessToken $($Token.accessToken) -AccountId:$($AzAccount.User.Name) -TenantId:$($AZAccount.tenantId) |
I add the above code in the full script just after the parameter, as the pipeline will already be signed in as the Pipeline Service Principal. It will then grab the Access token to sign in with Azure AD, and then able to run the rest of the script.