Determine All Sites where App is Added

In the classic SharePoint Admin center, you can find out the number of sites where a client-side SharePoint Framework application has been installed. However, this does not tell you which sites have the app installed.

I thought SharePoint Online Management Shell (with pnp) would help here, but Get-PnPAppInstance has been deprecated, and Get-PnPApp does not seem to work.

So I decided to use C# and CSOM in a console application. I will include the full class at the bottom of the post.

First you will need to add references to the following DLLs (I am using version 16.1.0.0 but other versions will probably work too.

Then create using statements in your class:

using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.Security;
using System.IO;

Here is the Main method. It sets up the file stream and passwords, connections, etc.

  static void Main(string[] args)
  {
        FileStream ostrm;
        StreamWriter writer;
        TextWriter oldOut = Console.Out;

        try
        {
            ostrm = new FileStream("c:\\temp\\Output.txt", FileMode.OpenOrCreate, FileAccess.Write);
            writer = new StreamWriter(ostrm);
            writer.AutoFlush = true;

        }
        catch (Exception e)
        {
            Console.WriteLine("Cannot open Output.txt for writing");
            Console.WriteLine(e.Message);
            return;
        }
        Console.SetOut(writer);

        string siteUrl = "https://yoursite-admin.sharepoint.com";

        string userName = "yourtenantadminaccount@yourdomain.org";

        SecureString password = GetSecureString("yourpassword");

        Console.WriteLine();

        getAllSiteCollections(siteUrl, userName, password);

        Console.WriteLine("Process Completed!");
        Console.SetOut(oldOut);
        writer.Close();
        ostrm.Close();
        Console.WriteLine("Done");
  }

The GetSecureString() method is standard boilerplate for creating a SecureString object that is needed for the credentials of the context you will use later (included in full class at bottom this post.)

Connect to your tenant and get the information for the sites that match your criteria. Add these to a list of site properties (List<SiteProperties>)


            var credentials = new SharePointOnlineCredentials(userName, password);
            ClientContext ctx = new ClientContext(siteUrl);
            ctx.Credentials = credentials;

            Tenant tenant = new Tenant(ctx);

            SPOSitePropertiesEnumerableFilter sspFilter = new SPOSitePropertiesEnumerableFilter();
            sspFilter.IncludePersonalSite = PersonalSiteFilter.Exclude;
            sspFilter.IncludeDetail = true;

            SPOSitePropertiesEnumerable siteProps = null;
 
            string nextIndex = null;

            List<SiteProperties> list = new List<SiteProperties>();

             do{

                sspFilter.StartIndex = nextIndex;
                siteProps = tenant.GetSitePropertiesFromSharePointByFilters(sspFilter);

                ctx.Load(siteProps);
                ctx.ExecuteQuery();

                list.AddRange(siteProps);
                nextIndex = siteProps.NextStartIndexFromSharePoint;
            } while (nextIndex != null);

Once you have a list of all the sites in your tenant, iterate through each of the sites (and each web in each site) to see if the app is in your site.

 foreach (var site in list)
        {
            if (site.Template != "GROUP#0") //don't want to include group-connected sites
            {
                using (ClientContext context = new ClientContext(site.Url))
                {
                    try
                    {
                        context.Credentials = credentials;
                        Web web = context.Web;
                        context.Load(web);
                        context.ExecuteQuery();
                        CheckIfAppsAreInWeb(site, context, web);

                        context.Load(web.Webs);
                        context.ExecuteQuery();

                        foreach (Web sub in web.Webs)
                        {
                            context.Load(sub);
                            context.ExecuteQuery();
                            CheckIfAppsAreInWeb(site, context, sub);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(site.Url + "|Error processing site: " + ex.Message);
                    }
                }
            }
        }

Here is the method that will determine if the app you are looking for is in your site (web) and write a line to the file stream if it does.

 private static void CheckIfAppsAreInWeb(SiteProperties site, ClientContext context, Web web)
        {
            var addInInstance = AppCatalog.GetAppInstances(context, web);
            addInInstance.Context.Load(addInInstance);
            addInInstance.Context.ExecuteQuery();
            foreach (var appInstance in addInInstance)
            {
                if (string.IsNullOrEmpty(appInstance.AppWebFullUrl))
                {
                    if (appInstance.Title == "Your App Title")
                    {
                        Console.WriteLine(web.Url + "| Your App Name" );
                    }
                }
            }
        }

Here is the entire class:


using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.Security;
using System.IO;

namespace IsAppInSite
{
    class ProgramDemo
    {
        static void Main(string[] args)
        {
            FileStream ostrm;
            StreamWriter writer;
            TextWriter oldOut = Console.Out;

            try
            {
                ostrm = new FileStream("c:\\temp\\Output.txt", FileMode.OpenOrCreate, FileAccess.Write);
                writer = new StreamWriter(ostrm);
                writer.AutoFlush = true;

            }
            catch (Exception e)
            {
                Console.WriteLine("Cannot open Redirect.txt for writing");
                Console.WriteLine(e.Message);
                return;
            }
            Console.SetOut(writer);

            string siteUrl = "https://yourdomain-admin.sharepoint.com";

            string userName = "yourtenantadmin@yourdomain.com";

            SecureString password = GetSecureString("yourpassword");

            getAllSiteCollections(siteUrl, userName, password);

            Console.SetOut(oldOut);
            writer.Close();
            ostrm.Close();
        }

        private static void getAllSiteCollections(string siteUrl, string userName, SecureString password)
        {

            var credentials = new SharePointOnlineCredentials(userName, password);
            ClientContext ctx = new ClientContext(siteUrl);
            ctx.Credentials = credentials;

            Tenant tenant = new Tenant(ctx);

            SPOSitePropertiesEnumerableFilter sspFilter = new SPOSitePropertiesEnumerableFilter();
            sspFilter.IncludePersonalSite = PersonalSiteFilter.Exclude;
            sspFilter.IncludeDetail = true;

            SPOSitePropertiesEnumerable siteProps = null;
 
            string nextIndex = null;

            List<SiteProperties> list = new List<SiteProperties>();

             do{

                sspFilter.StartIndex = nextIndex;
                siteProps = tenant.GetSitePropertiesFromSharePointByFilters(sspFilter);

                ctx.Load(siteProps);
                ctx.ExecuteQuery();

                list.AddRange(siteProps);
                nextIndex = siteProps.NextStartIndexFromSharePoint;
            } while (nextIndex != null);



            foreach (var site in list)
            {
                if (site.Template != "GROUP#0")
                {
                    using (ClientContext context = new ClientContext(site.Url))
                    {
                        try
                        {
                            context.Credentials = credentials;
                            Web web = context.Web;
                            context.Load(web);
                            context.ExecuteQuery();
                            CheckIfAppsAreInWeb(site, context, web);

                            context.Load(web.Webs);
                            context.ExecuteQuery();

                            foreach (Web sub in web.Webs)
                            {
                                context.Load(sub);
                                context.ExecuteQuery();
                                CheckIfAppsAreInWeb(site, context, sub);
                            }

                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(site.Url + "|Error processing site: " + ex.Message);
                        }
                    }
                }
            }
        }

        private static void CheckIfAppsAreInWeb(SiteProperties site, ClientContext context, Web web)
        {
            var addInInstance = AppCatalog.GetAppInstances(context, web);
            addInInstance.Context.Load(addInInstance);
            addInInstance.Context.ExecuteQuery();

            foreach (var appInstance in addInInstance)
            {
                if (string.IsNullOrEmpty(appInstance.AppWebFullUrl))
                {
                    if (appInstance.Title == "Your App Title")
                    {
                        Console.WriteLine(web.Url + "|Your App Title");
                    }
                }
            }
        }

        private static SecureString GetSecureString(string pw)
        {
            SecureString securePassword = new SecureString();
 
            foreach (char c in pw)
                securePassword.AppendChar(c);
            securePassword.MakeReadOnly();

            return securePassword;
        }
    }
}

%d bloggers like this:
search previous next tag category expand menu location phone mail time cart zoom edit close