Found some really good articles:
Tag Archives: Azure
Track SharePoint App-only Service Principals in Microsoft 365
Scenario
Developers in the organization can use both – Azure Apps and SharePoint Apps to work with SharePoint sites in their “daemon” applications.
It is recommended to use Azure apps so, you want to know – what are SharePoint Apps registered and their owners, who registered SharePoint Apps. Eventually you would disable SharePoint Apps-only principal but before that you’d move Devs from SP-App-only to Azure App (see Disable Custom App Authentication).
(SharePoint App-only service principals aka SP-App-Only are SPN or App registered from within SharePoint using AppRegNew.aspx system page).
One of the approaches – track Apps/Owners with Unified Audit Log
Use Unified Audit Logs
The following PowerShell code:
$operations = 'Add service principal.'
$recordType = 'AzureActiveDirectory'
Search-UnifiedAuditLog -StartDate $start -EndDate $end -ResultSize $resultSize -Formatted -Operations $operations -RecordType $recordType
returns events with operation = ‘Add service principal.’ Nice, but…
if an app was registered in Azure – event contains an UPN under UserIds property:

Unfortunately, in case with registering app in SharePoint, an audit log event will be like:

i.e. UserId registerd is “spo_service@support.onmicrosoft.com”, so we do not know who registered a SharePoint-only app
In theory – we could use events recorded immediately before and after “Add service principal” event to track a user and site who has registered a SharePoint-only app… But for me it seems like too complicated for automation.

Instead we can do simple search through audit log for events “AppRegNew.aspx page visited”. This gives us a good approximation of who registered SP-App-only principal. Worst scenario – we reach more people than we really need (including those who started registering sp-app-only but did not complete) but all of them would be definitely our target auditory.
Consider the following code:
$freeText = "appregnew" $operations = 'PageViewed' $recordType = 'SharePoint' $results = Search-UnifiedAuditLog -StartDate $start -EndDate $end -ResultSize $resultSize -FreeText $freeText -Operations $operations -RecordType $recordType
this would give you all users who loaded “/_layouts/15/appregnew.aspx” page
Update: Sites.Selected API MS Graph permissions was introduced by Microsoft in 2021. It was a good move towards site-level development, but still developers were limited with only what MS Graph API provides for SharePoint dev.
So devs had to use AppInv.aspx at site level to provide ACS permissions to their apps to be able to use SharePoint CSOM and REST APIs.
Recently Microsoft introduced Sites.Selected SharePoint API permissions for registered Azure Apps! So now devs should be fully happy without ACS-based permissions.
References
- Export all SharePoint App Principals using the Microsoft Graph PowerShell Module
- Use a PowerShell script to search the audit log
- PowerShell samples to get Apps registrations from Unified Audit Log
- Azure App, ACS, SP-App only and Disable Custom App Authentication
- Accessing SharePoint using an application context, also known as app-only
Access SPO Site Programmatically via MS Graph API and SharePoint API
Scenario
You are a software developer. Your company uses Microsoft Office 365 (SharePoint, Teams etc.). The need is to work with a specific site collection programmatically (from code – Python, C#, Java, PowerShell, JavaScript etc.) – e.g. upload/download documents, update list items, search etc.
The code must run without user interaction (unattended, aka daemon app). Sometimes this is also called “SharePoint Automation”.
The solution is based on a new Graph API feature – Sites.Selected and a classic SP-Only app.
Solution
- Register an Azure App and configure it as usual.
Select API Permissions blade and add two permissions:
– Microsoft Graph -> Applications Permissions -> “sites.selected”
– SharePoint -> Applications Permissions -> “sites.selected“ - Request “Grant admin consent” from a tenant/global admin
- Request SharePoint admin to run PowerShell code (e.g. this one) to assign proper permissions to your azure app for a specific site collection (consider site owner consent)
- (optionally) Provide SharePoint API permissions:
(require Site Collection Owner/Admin account) – use
https://YourTenant.sharepoint.com/teams/YourSite/_layouts/15/appinv.aspx
to add SharePoint API permissions to your app. E.g. full control permissions to site collection would be
<AppPermissionRequests AllowAppOnlyPolicy="true">
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection"
Right="FullControl" />
</AppPermissionRequests>
Consider minimal permissions (e.g. as per Sumit)
Problem Solved
- you get access to one and only one site collection (“least privilege” principal)
- you get both – SharePoint API and Microsoft Graph API permissions to SharePoint
- you can use app secret or certificate to authenticate – depending on what are your security requirements
Note: if your scenario require authenticated user present – the solution would be a little different: Connect-PnPOnline Interactive with Client App Id
Update:
Sites.Selected API MS Graph permissions was introduced by Microsoft in 2021. It was a huge step forward, but still devs were limited with MS Graph API against SharePoint.
So devs had to use AppInv at site level to provide ACS permissions to their apps to use SharePoint CSOM and REST APIs.
Recently Microsoft introduced Sites.Selected SharePoint API permissions for registered Azure Apps! So now devs should be fully happy without ACS-based permissions AppInv.aspx. (See more here on disabling SP Apps Only SPNs)
Thanks to Leon Armston and Scott Murdock
References:
- Register an application with the Microsoft identity platform
- Controlling app access on a specific SharePoint site collections is now available in Microsoft Graph
- Working with SharePoint sites in Microsoft Graph
- SharePoint Add-In — Permission XML cheat sheet
- Accessing SharePoint using an application context, also known as app-only
- Connect-PnPOnline Interactive with Client App Id
- Disable Custom App Authentication
Connect-PnPOnline with a certificate stored in Azure Key Vault
Scenario
You run some PnP PowerShell code unattended e.g. daemon/service app, background job – under application permissions – with no user interaction.
Your app needs to connect to SharePoint and/or Microsoft Graph API. Your organization require authentication with a certificate (no secrets). You want certificate stored securely in Azure Key Vault.
Solution (Step-by-step process)
- Obtain a certificate (create a self-signed or request trusted)
- In Azure where you have Microsoft 365 SharePoint tenant
- Create a new Registered App in Azure; save App (client) id, Directory (Tenant) Id
- Configure App: add MS Graph and SharePoint API application (not delegated) permissions
- Upload the certificate to the app under “Certificates & secrets”
- In Azure where you have paid subscription (could be same or different)
- Create an Azure Key Vault
- Upload certificate to the Key Vault manually (with GUI)
- While you develop/debug your custom daemon application at your local machine
- Provide permissions to the Key Vault via Access Control and Access Policies to your personal account
- Connect to Azure (the one where your Key Vault is) running Connect-AzAccount
– so your app can get a Certificate to authenticate to SharePoint Online
- For your application deployed to Azure (e.g. Azure Function App )
- Turn On managed identity (Your Function App -> Identity -> Status:On) and Save; notice an Object (Principal) Id just created
- Provide for your managed identity principal Id permissions to the Key Vault via Key Vault Access Policies, so when your daemon app is running in the cloud – it could go to the key Vault and retrieve Certificate
Here is the sample PowerShell code to get certificate from Azure Key Vault and Connect to SharePoint with PnP (Connect-PnPOnline):
# ensure you use PowerShell 7
$PSVersionTable
# connect to your Azure subscription
Connect-AzAccount -Subscription "<subscription id>" -Tenant "<tenant id>"
Get-AzSubscription | fl
Get-AzContext
# Specify Key Vault Name and Certificate Name
$VaultName = "<azure key vault name>"
$certName = "certificate name as it stored in key vault"
# Get certificate stored in KeyVault (Yes, get it as SECRET)
$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $certName
$secretValueText = ($secret.SecretValue | ConvertFrom-SecureString -AsPlainText )
# connect to PnP
$tenant = "contoso.onmicrosoft.com" # or tenant Id
$siteUrl = "https://contoso.sharepoint.com"
$clientID = "<App (client) Id>" # Azure Registered App with the same certificate and API permissions configured
Connect-PnPOnline -Url $siteUrl -ClientId $clientID -Tenant $tenant -CertificateBase64Encoded $secretValueText
Get-PnPSite
The same PowerShell code in GitHub: Connect-PnPOnline-with-certificate.ps1
References:
- https://docs.microsoft.com/en-us/powershell/module/az.keyvault/get-azkeyvaultcertificate?view=azps-5.3.0
- https://stackoverflow.com/questions/43837362/keyvault-generated-certificate-with-exportable-private-key
- Do I need certificate or secret to Connect to SharePoint Online from code
- Register and configure Azure App to Get access to an SPO site from code
Long-running PowerShell Office 365 reports
(WIP)
PowerShell is our best friend when it comes to ad-hoc and/or scheduled reports in Microsoft 365. PnP team is doing great job providing more and more functionality with PnP PowerShell module for Office 365 SharePoint and Teams.
Small and medium business organizations are mostly good, but for large companies it might be a problem due to just huge amount of data stored in SharePoint. PowerShell reports on all users or all sites might run days… which is probably OK if you run this report once, but totally not acceptable if you need this report e.g. daily/weekly or on-demand.
How can we make heavy PowerShell scripts run faster?
Of course, you start with logic (algorithm) and leveraging full PowerShell functionality (e.g. PowerShell 7 parallelism or PnP batching).
(examples)
What if you did everything, but it still takes too long? You need something like brute force – the closer your code runs to your tenant – the better.
What are the option?
– Automation account runbook (+workflow)
– Azure Function Apps
– Azure VM in the region closest to your Tenant
Automation account runbook (+workflow)
Seemed like a good option, but not something Microsoft promotes. Even opposite – automation accounts support only PowerShell 5 (not 7), no plug-ins for VS Code and recently there were messages on some retirement or smth.
Meantime, I tested it – and did not find any significant increasing in speed. In a nutshell, what is behind this service? Same windows machines running somewhere in Azure .
TBC
References
– PnP PowerShell
Exact Location of your SharePoint Online Microsoft Office 365 tenant
Quick answer: spin-up a few VMs in different Azure regions, then ping your SharePoint tenant. The moment you see 1ms ping you know the tenant exact location.
Full story
Microsoft says: “Customers should view tenant specific data location information in your Microsoft 365 Admin Center in Settings | Org settings | Organization Profile | Data location.”
And it might look like:

That’s accurate to the geography (e.g. US, UE, AP), but not to the region (for instance – “Central US”, “UK West” or “Australia Southeast”).
In other words, If you know your data are in the US, you do not know where exactly – East/West/Central or South US.
Meantime when you create an Azure resource (e.g. Virtual Machine) – you can select specific region.
How do I know – where is my Microsoft 365 tenant actually located?
Can we just ping the tenant, analyze result and find Office 365 tenant region?
Luckily, SharePoint tenant is pinging with just
PS>ping tenantName.SharePoint.com
I have tested 5 regions and 4 different tenants:
ping from/to (ms) | tenant 1 (US) | tenant 2 (EU) | tenant 3 (US) | tenant 4 (US) |
North Europe | 73 | 17 | 96 | 101 |
East US | 1 | 83 | 39 | 31 |
Central US | 22 | 114 | 23 | 23 |
West US | 63 | 146 | 36 | 33 |
South Central US | 31 | 112 | 1 | 1 |
So I figured it out:
My o365 tenants #3 and #4 regions are South Central US (Texas, San Antonio),
tenant #1 resides in East US.
Why do I need this?
Imagine you are running heavy reports against your tenant.
So probably you want your code running as close as possible to your tenant.
For this, you can spin-up a VM in Azure or use Azure Functions – just select proper region 🙂
(please check also “Long-running PowerShell reports optimization in Office 365“)
References:
– Where your Microsoft 365 customer data is stored