Seeking Ownerless Apps

Experienced commentators and ISVs have paid lots of attention to app management recently. Practical365.com started along this path in April 2021 when Vasil Michev wrote about how to use the Graph APIs to inventory apps and permissions. I created a more recent (and enhanced) version in 2024. It’s nice to see everyone joining us on the journey to better app management within Microsoft 365.

As a quick internet search will confirm, blogs and articles sometimes overhype the issue of ownerless apps when discussing app management. Microsoft recommends that Entra ID applications have at least two owners to manage details of app configurations, including who can use apps. However, Entra ID doesn’t automatically add the person who creates a new registered app as the owner (Figure 1) if the user holds a privileged role like Application administrator.

No owner for a newly created registered app
Figure 1: No owner for a newly created registered app

Tenants should not allow unprivileged users to create new apps (disable the Users can register applications setting in User Settings), but if unprivileged users are allowed to create apps, they become the owner of the app. Even if one or more owners are added for an app, over time, the assigned owners might leave the organization, and the app eventually becomes ownerless.

Finding Ownerless Apps

Ownerless apps sound like a problem that should be highlighted for administrators to solve. As it turns out, discovering the set of ownerless apps in a tenant is simple. An advanced Entra ID query against the applications end point quickly reveals apps without owners (anything that involving checking the count of items for Entra ID objects becomes an advanced query).

Here’s some example code using a Graph request to find the set of ownerless apps. As you can see, just a few lines of PowerShell solve the problem of finding the set of ownerless apps:

Connect-MgGraph -Scopes application.read.all
$Headers = @{}
$Headers.Add("ConsistencyLevel","eventual")
$Uri = "https://graph.microsoft.com/v1.0/applications?`$filter=owners/`$count eq 0&`$count=true&`$select=id,displayName, appId"
[array]$Apps = Invoke-MgGraphRequest -Uri $Uri -Method Get -Headers $Headers -OutputType PSObject | Select-Object -ExpandProperty Value
$Apps | Format-Table DisplayName, Id, AppId -AutoSize

If you prefer using the Microsoft Graph PowerShell SDK, the Get-MgApplication cmdlet offers an alternative. In most cases, I prefer using SDK cmdlets because I don’t need to worry about writing code to handle pagination. The example above will find up to 100 apps, but if more apps are present in the tenant, you must paginate to retrieve all available objects. Many SDK cmdlets support the All parameter. If, as here, you include the All parameter, the Graph fetches all available objects automatically.

Connect-MgGraph -Scopes application.read.all
[array]$Apps = Get-MgApplication -Filter "owners/`$count eq 0" -CountVariable CountVar -ConsistencyLevel eventual -All -Property displayName, Id, AppId
$Apps | Format-Table DisplayName, Id, AppId -AutoSize

What to do About Ownerless Apps

So far, we’ve established that it’s bad to have ownerless apps, and it’s easy to discover which apps need some attention. The question then is what action administrators should take to resolve the issue.

The simple answer is that administrators should decide who should be the owner for each app. They can then update the app with the selected owner. That approach certainly works, but repetitive manual processes tend to quickly become boring and error prone. Given the number of apps often found in Microsoft 365 tenants, administrators might have hundreds of apps to update. That’s not an attractive prospect, even if the administrators knew which owners to assign to what apps.

Automation is the obvious answer. The simplest answer is to assign the same owner (usually an administrator) to all the ownerless apps. This works, and it resolves the problem, but the result is probably not what you want. A better answer is to put some effort into selecting an appropriate owner for each app. You can then:

  • Export the set of ownership apps to a CSV file:
$Apps | Select-Object DisplayName, AppId, Id | Export-CSV -NotypeInformation Apps.CSV
  • Open the file with Excel, add a column called “Owner”, and input the name of the owner to assign to each app.
  • Import the updated CSV into an array and loop through each app to assign the owner to the app with the New-MgApplicationOwnerByRef cmdlet:
$OwnerRef = ("https://graph.microsoft.com/v1.0/directoryObjects/{0}" -f $UserId)
$OwnerId = @{}
$OwnerId.Add("@odata.id", $OwnerRef)
New-MgApplicationOwnerByRef -ApplicationId $App.Id -BodyParameter $OwnerId

Use Audit Data to Find Which Users Should Take Over Ownerless Apps

Another approach using information from the unified audit log to find the last account that managed an app (created the app or updated its properties) and assigns that account as the app owner. Entra ID audit logs hold information about app creations and updates for 30 days, but the unified audit log holds the data for 180 days (Purview audit standard – E3) or 365 days (Purview audit premium – E5). It’s therefore possible to go back quite a long time to find potential app owners.

To illustrate the principle, I wrote a script (downloadable from GitHub). The script signs into an interactive Microsoft Graph PowerShell SDK session (the Application.Read.All permission is required) and the Exchange Online management module. Most of the work is done with SDK cmdlets, but the Exchange module is needed to run the audit search. If you prefer to do everything with the Microsoft Graph PowerShell SDK, you can use the AuditLog Query API to fetch audit data.

The major steps in the script are:

  • Performs an audit search to find records for app creation and updates for the last 180 days.
  • Extracts the information about the updates, like the object identifier and user principal name of the account that interacted with the app, the app name, identifier, and client identifier, and the timestamp for the action.
  • Sorts the records by application identifier and finds the most recent update for an app. This information is stored in an array.
  • Run the Get-MgApplication cmdlet to find all the apps in the tenant. The script could report just ownerless apps, but it’s best to create a full report covering all apps.
  • For each app, check if it has owners. If it has, note the app details and owners in the output report.
  • If the app is ownerless, the script checks if audit information is available for the app and use the data to add the account noted in the audit information as the owner of the app.
  • If no audit information is available, report that fact and leave it to administrators to sort out the ownership for these apps.

Figure 2 shows the output at the end of the script, where we see that audit information was available for eight apps. However, a bunch of ownerless apps remain, one of which goes back to 2019.

Running the script to find and update the owners for ownerless apps.
Figure 2: Running the script to find and update owners for ownerless apps

Some of the ownerless apps are apps created by other Microsoft Cloud components. Two of the apps (SharePoint Online Client Extensibility Web Application Principal and its Helper) are apps created by the SharePoint Framework. I have no idea why these are registered apps and not enterprise apps created and managed by Microsoft. It certainly seems that they should be. Another is an app created for an Azure Automation RunAs account (a now deprecated feature), so it can be removed. Finally, there’s an MTO Synchronization app that’s used to synchronize user accounts within a multi-tenant organization.

The other apps seem to have been created to allow app-only access by Graph-based apps. It would be nice if I could report that I always create a meaningful name for apps, but it’s obvious that I don’t.

Ownerless Apps Isn’t a Hard Problem to Crack

The easiest way to avoid ownerless apps is to always be sure to assign owners when creating apps. That’s probably not going to happen, but a scheduled Azure Automation runbook that executes monthly to find and update recently created apps based on audit data will make sure that all apps have an owner. Whether that owner is the right person is quite another question, but at least there won’t be any ownerless apps in the tenant.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Yves

    Nice guide and explanation, thank you! This is challenging when you have deployment runner service principals generating other service principals to do other deployment actions in your tenant, and it’s not listing a user object as the owner

  2. Jander

    This is fine if you want to give owner permissions to a user, but what if I want to put a user as an administrative contact for this app but without permissions? What is your advice in this case?

    1. Avatar photo
      Tony Redmond

      I guess you could put someone’s name in the comments for the app and surface the information in some manner…

Leave a Reply