Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"shared_sharepointonline":"ff855bae-28f2-4a92-b639-8967beb6f93d"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"shared_sharepointonline":"44c279dc-36b3-4935-b191-14e78df6fc2c"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"43128f15-3c9c-4169-9034-98832d0c77a7","id":"/providers/Microsoft.Flow/flows/43128f15-3c9c-4169-9034-98832d0c77a7","type":"Microsoft.Flow/flows","properties":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_logicflows","displayName":"Site Admin Lookup","definition":{"metadata":{"workflowEntityId":null,"processAdvisorMetadata":null,"flowChargedByPaygo":null,"flowclientsuspensionreason":"None","flowclientsuspensiontime":null,"flowclientsuspensionreasondetails":null,"creator":{"id":"4af50d87-e728-484e-874c-3f4536b38fee","type":"User","tenantId":"aecb54e0-7430-409d-86a4-7639c743eee7"},"provisioningMethod":"FromDefinition","failureAlertSubscription":true,"clientLastModifiedTime":"2026-08-25T20:18:46.4747484Z","lastModifiedBy":"4af50d87-e728-484e-874c-3f4536b38fee","connectionKeySavedTimeKey":"2026-08-25T20:18:46.4747484Z","creationSource":"Portal","modifiedSources":"Portal"},"$schema":"https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#","contentVersion":"undefined","parameters":{"$authentication":{"defaultValue":{},"type":"SecureObject"},"$connections":{"defaultValue":{},"type":"Object"}},"triggers":{"manual":{"metadata":{},"type":"Request","kind":"Http","inputs":{"schema":{"url":""},"triggerAuthenticationType":"All"}}},"actions":{"Get_the_site_admins":{"runAfter":{"admins":["Succeeded"]},"type":"OpenApiConnection","inputs":{"parameters":{"dataset":"@variables('siteUrl')","parameters/method":"GET","parameters/uri":"_api/web/SiteUserInfoList/Items?$filter=IsSiteAdmin eq 1"},"host":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","connectionName":"shared_sharepointonline","operationId":"HttpRequest"},"authentication":"@parameters('$authentication')"}},"Respond_to_a_Power_App_or_flow":{"runAfter":{"Loop_through_admins":["Succeeded"],"Loop_through_owners":["Succeeded"]},"type":"Response","kind":"PowerApp","inputs":{"schema":{"type":"object","properties":{"admins":{"title":"admins","type":"string","x-ms-content-hint":"TEXT","x-ms-dynamically-added":true},"owners":{"title":"owners","type":"string","x-ms-content-hint":"TEXT","x-ms-dynamically-added":true}},"additionalProperties":{}},"statusCode":200,"body":{"admins":"@{variables('admins')}","owners":"@{variables('owners')}"}}},"admins":{"runAfter":{"siteUrl":["Succeeded"]},"type":"InitializeVariable","inputs":{"variables":[{"name":"admins","type":"array"}]}},"Loop_through_admins":{"foreach":"@body('Get_the_site_admins')?['d']?['results']","actions":{"Add_the_site_admin":{"type":"AppendToArrayVariable","inputs":{"name":"admins","value":{"name":"@items('Loop_through_admins')?['Title']","email":"@items('Loop_through_admins')?['EMail']","type":"@items('Loop_through_admins')?['SPSUserType']"}}}},"runAfter":{"Get_the_site_admins":["Succeeded"]},"type":"Foreach"},"owners":{"runAfter":{"siteUrl":["Succeeded"]},"type":"InitializeVariable","inputs":{"variables":[{"name":"owners","type":"array"}]}},"Get_the_site_owners_group":{"runAfter":{"owners":["Succeeded"]},"type":"OpenApiConnection","inputs":{"parameters":{"dataset":"@variables('siteUrl')","parameters/method":"GET","parameters/uri":"_api/web/AssociatedOwnerGroup/Users"},"host":{"apiId":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","connectionName":"shared_sharepointonline","operationId":"HttpRequest"},"authentication":"@parameters('$authentication')"}},"Loop_through_owners":{"foreach":"@body('Get_the_site_owners_group')['d']['results']","actions":{"Add_the_owner":{"type":"AppendToArrayVariable","inputs":{"name":"owners","value":{"name":"@items('Loop_through_owners')?['Title']","email":"@items('Loop_through_owners')?['Email']","type":"@items('Loop_through_owners')?['PrincipalType']"}}}},"runAfter":{"Get_the_site_owners_group":["Succeeded"]},"type":"Foreach"},"siteUrl":{"runAfter":{},"type":"InitializeVariable","inputs":{"variables":[{"name":"siteUrl","type":"string","value":"@triggerBody()['url']"}]}}}},"connectionReferences":{"shared_sharepointonline":{"connectionName":"shared-sharepointonl-66a6d844-5e1b-4fa5-a8f1-40d37f28a005","source":"Embedded","id":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","tier":"NotSpecified","apiName":"sharepointonline","isProcessSimpleApiReferenceConversionAlreadyDone":false}},"flowFailureAlertSubscribed":false,"isManaged":false}}
1 change: 1 addition & 0 deletions flows/GetAdminsOwners/Microsoft.Flow/flows/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"packageSchemaVersion":"1.0","flowAssets":{"assetPaths":["b0890fb0-8376-435a-bd78-094a5bc5fa4a"]}}
1 change: 1 addition & 0 deletions flows/GetAdminsOwners/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"schema":"1.0","details":{"displayName":"Site Admin Tool - Get Admins/Owners","description":"Flow to return the site admins and owners of a site.","createdTime":"2026-08-25T21:14:03.9176328Z","packageTelemetryId":"744faeef-8171-4ff9-91bf-a4fb3a96c35b","creator":"N/A","sourceEnvironment":"Commercial"},"resources":{"b0890fb0-8376-435a-bd78-094a5bc5fa4a":{"type":"Microsoft.Flow/flows","suggestedCreationType":"New","creationType":"Existing, New, Update","details":{"displayName":"Site Admin Lookup"},"configurableBy":"User","hierarchy":"Root","dependsOn":["ff855bae-28f2-4a92-b639-8967beb6f93d","44c279dc-36b3-4935-b191-14e78df6fc2c"]},"ff855bae-28f2-4a92-b639-8967beb6f93d":{"id":"/providers/Microsoft.PowerApps/apis/shared_sharepointonline","name":"shared_sharepointonline","type":"Microsoft.PowerApps/apis","suggestedCreationType":"Existing","details":{"displayName":"SharePoint","iconUri":"https://static.powerapps.com/resource/ppcr/releases/v1.0.1813/1.0.1813.4769/sharepointonline/icon.png"},"configurableBy":"System","hierarchy":"Child","dependsOn":[]},"44c279dc-36b3-4935-b191-14e78df6fc2c":{"type":"Microsoft.PowerApps/apis/connections","suggestedCreationType":"Existing","creationType":"Existing","details":{"displayName":"gunjan@dattaspo.onmicrosoft.com","iconUri":"https://static.powerapps.com/resource/ppcr/releases/v1.0.1813/1.0.1813.4769/sharepointonline/icon.png"},"configurableBy":"User","hierarchy":"Child","dependsOn":["ff855bae-28f2-4a92-b639-8967beb6f93d"]}}}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"adm-zip": "^0.5.18",
"datatables.net": "^3.0.0",
"datatables.net-bs5": "^3.0.0",
"dattatable": "^2.11.87",
"dattatable": "^2.11.88",
"exceljs": "^4.4.0",
"fast-xml-parser": "^5.9.3",
"gd-sprest-bs": "^10.15.92",
Expand Down
22 changes: 13 additions & 9 deletions spfx/src/webparts/siteAdmin/SiteAdminWebPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ export interface ISiteAdminWebPartProps {
AppTitle: string;
AuditOnly: boolean;
DisableSensitivityLabelOverride: boolean;
FlowGetSiteAdminsAndOwners: string;
HideAppPermissionsTab: boolean;
HideAuditToolsTab: boolean;
HideFeaturesTab: boolean;
HideListsTab: boolean;
HideCreateSiteBtn: boolean;
HideLoadAdminOwnerBtn: boolean;
HideLoadOneDriveBtn: boolean;
HideManagementTab: boolean;
HideSearchTab: boolean;
Expand Down Expand Up @@ -212,8 +212,8 @@ declare const SiteAdmin: {
el: HTMLElement;
title?: string;
disableSensitivityLabelOverride?: boolean;
flowGetSiteAdminsAndOwners?: string;
hideCreateSiteBtn?: boolean;
hideLoadAdminOwnerBtn: boolean;
hideLoadOneDriveBtn: boolean;
hideReports: {
dlp: boolean;
Expand Down Expand Up @@ -481,12 +481,6 @@ export default class SiteAdminWebPart extends BaseClientSideWebPart<ISiteAdminWe
onText: "The option to create a new site will be hidden.",
offText: "The option to create a new site will be visible."
}),
PropertyPaneToggle("HideLoadAdminOwnerBtn", {
label: "Hide Load Admin/Owner Button",
checked: this.properties.HideLoadAdminOwnerBtn,
onText: "The option to view a site's admins/owners will be hidden.",
offText: "The option to view a site's admins/owners will be visible."
}),
PropertyPaneToggle("HideLoadOneDriveBtn", {
label: "Hide Load OneDrive Button",
checked: this.properties.HideLoadOneDriveBtn,
Expand Down Expand Up @@ -539,6 +533,16 @@ export default class SiteAdminWebPart extends BaseClientSideWebPart<ISiteAdminWe
})
]
},
{
groupName: "Flow Settings",
groupFields: [
PropertyPaneTextField("FlowGetSiteAdminsAndOwners", {
label: "Get Site Admins/Owners Flow",
description: "The URL to the Power Automate flow that will return the site admins/owners.",
value: this.properties.FlowGetSiteAdminsAndOwners
}),
]
},
{
groupName: "Show/Hide Tabs",
groupFields: [
Expand Down Expand Up @@ -1028,8 +1032,8 @@ export default class SiteAdminWebPart extends BaseClientSideWebPart<ISiteAdminWe
context: this.context,
el: this.domElement,
disableSensitivityLabelOverride: this.properties.DisableSensitivityLabelOverride ? true : false,
flowGetSiteAdminsAndOwners: this.properties.FlowGetSiteAdminsAndOwners,
hideCreateSiteBtn: this.properties.HideCreateSiteBtn ? true : false,
hideLoadAdminOwnerBtn: this.properties.HideLoadAdminOwnerBtn ? true : false,
hideLoadOneDriveBtn: this.properties.HideLoadOneDriveBtn ? true : false,
hideReports: {
dlp: this.properties.HideReportDLP ? true : false,
Expand Down
6 changes: 3 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export interface IAppProps {
context?: any;
disableSensitivityLabelOverride?: boolean;
el: HTMLElement;
flowGetSiteAdminsAndOwners?: string;
hideCreateSiteBtn?: boolean;
hideLoadAdminOwnerBtn?: boolean;
hideLoadOneDriveBtn?: boolean;
hideReports: {
dlp?: boolean;
Expand Down Expand Up @@ -119,7 +119,7 @@ export class App {

// Render the form
new LoadForm(el.children[0].children[0] as HTMLElement, el.children[0].children[1] as HTMLElement,
this._props.hideLoadAdminOwnerBtn, this._props.hideLoadOneDriveBtn, () => {
this._props.flowGetSiteAdminsAndOwners, this._props.hideLoadOneDriveBtn, () => {
// Render the tabs
new App(this._props, false);
}, () => {
Expand Down Expand Up @@ -154,7 +154,7 @@ export class App {
text: "Load Site",
onClick: () => {
// Show the load form
LoadForm.showModal(this._props.hideLoadAdminOwnerBtn, this._props.hideLoadOneDriveBtn, () => {
LoadForm.showModal(this._props.flowGetSiteAdminsAndOwners, this._props.hideLoadOneDriveBtn, () => {
// Render the tabs
this.renderTabs(elRow.children[1] as HTMLElement, false);
}, () => {
Expand Down
72 changes: 29 additions & 43 deletions src/ds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,51 +433,37 @@ export class DataSource {
}

// Loads the admins/owners of the site
static loadAdminsOwners(siteUrl: string): PromiseLike<{ admins: IUserInfo[], owners: IUserInfo[] }> {
static loadAdminsOwners(flowUri: string, siteUrl: string): PromiseLike<{ admins: IUserInfo[], owners: IUserInfo[] }> {
// Return a promise
return new Promise((resolve, reject) => {
let errorFl = false;
let web = Web(siteUrl);

// Get the admins
let admins: IUserInfo[] = [];
web.SiteUserInfoList().Items().query({
Filter: "IsSiteAdmin eq 1"
}).execute(users => {
// Add the admins
users.results.forEach(user => {
admins.push({
email: user["EMail"] || user["UserName"],
name: user.Title,
type: "Site Admin"
});
});
}, () => {
// Set the flag
errorFl = true;
});

// Get the owners
let owners: IUserInfo[] = [];
web.AssociatedOwnerGroup().Users().execute(users => {
// Add the owners
users.results.forEach(user => {
owners.push({
email: user.Email || user.LoginName,
name: user.Title,
type: "Site Owner"
});
});
}, () => {
// Set the flag
errorFl = true;
}, true);

// Wait for the requests to complete
web.done(() => {
// Complete the request
errorFl ? reject() : resolve({ admins, owners })
});
// Call the flow
fetch(flowUri, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({ url: siteUrl })
})
.then(resp => {
// Ensure it was successful
if (resp.ok) {
return resp.json();
} else {
throw new Error("Failed to load admins/owners");
}
})
.then(resp => {
// Ensure the data is converted
if (typeof (resp.admins) === "string") {
resp.admins = JSON.parse(resp.admins);
}
if (typeof (resp.owners) === "string") {
resp.owners = JSON.parse(resp.owners);
}
resolve(resp);
})
.catch(ex => { reject(ex); });
});
}

Expand Down
32 changes: 21 additions & 11 deletions src/loadForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,30 @@ export class LoadForm {
private _onLoadOneDrive: () => void = null;

// Renders the modal
constructor(elForm: HTMLElement, elFooter: HTMLElement, hideLoadAdminOwnerBtn: boolean = false, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) {
constructor(elForm: HTMLElement, elFooter: HTMLElement, flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) {
this._onSuccess = onSuccess;
this._onLoadOneDrive = onLoadOneDrive;

// Render the form and footer
this.renderForm(elForm);
this.renderFooter(elFooter, hideLoadAdminOwnerBtn, hideLoadOneDriveBtn);
this.renderFooter(elFooter, flowGetSiteAdminsAndOwners, hideLoadOneDriveBtn);
}

// Renders the footer
private renderFooter(el: HTMLElement, hideLoadAdminOwnerBtn: boolean, hideLoadOneDriveBtn: boolean) {
private renderFooter(el: HTMLElement, flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean) {
// Clear the element
while (el.firstChild) { el.removeChild(el.firstChild); }

// Set the tooltips
let tooltips = []
if (!hideLoadAdminOwnerBtn) {
if (flowGetSiteAdminsAndOwners) {
tooltips.push({
content: "Views the owners/admins of the site.",
btnProps: {
text: "View Admins/Owners",
onClick: () => {
// Load the admins/owners of the site
this.viewAdminsOwners();
this.viewAdminsOwners(flowGetSiteAdminsAndOwners);
}
}
});
Expand Down Expand Up @@ -265,15 +265,15 @@ export class LoadForm {
}

// Shows the modal
static showModal(hideLoadAdminOwnerBtn: boolean = false, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) {
static showModal(flowGetSiteAdminsAndOwners: string, hideLoadOneDriveBtn: boolean = false, onSuccess: () => void, onLoadOneDrive: () => void) {
// Clear the modal
Modal.clear();

// Set the header
Modal.setHeader("Load Site");

// Create the form/footer
new LoadForm(Modal.BodyElement, Modal.FooterElement, hideLoadAdminOwnerBtn, hideLoadOneDriveBtn, onSuccess, onLoadOneDrive);
new LoadForm(Modal.BodyElement, Modal.FooterElement, flowGetSiteAdminsAndOwners, hideLoadOneDriveBtn, onSuccess, onLoadOneDrive);

// Show the modal
Modal.show();
Expand Down Expand Up @@ -322,19 +322,22 @@ export class LoadForm {
}

// Views the admins/owners of the site
private viewAdminsOwners() {
private viewAdminsOwners(flowGetSiteAdminsAndOwners: string) {
// Validate the form
if (this._form.isValid()) {
let ctrl = this._form.getControl("url");
let url = this._form.getValues()["url"];

// Show a loading dialog
LoadingDialog.setHeader("Validating Site");
LoadingDialog.setBody("This will close after the site url is validated...");
LoadingDialog.setHeader("Calling Flow");
LoadingDialog.setBody("This will close after the flow has completed...");
LoadingDialog.show();

// Set the absolute url of the site
let siteUrl = document.location.origin + (typeof (url) === "string" ? url : url.value);

// Load the admins/owners
DataSource.loadAdminsOwners(typeof (url) === "string" ? url : url.value).then(
DataSource.loadAdminsOwners(flowGetSiteAdminsAndOwners, siteUrl).then(
// Success
users => {
// Clear the modal
Expand All @@ -347,6 +350,13 @@ export class LoadForm {
new DataTable({
el: Modal.BodyElement,
rows: users.admins.concat(users.owners),
onRendering: dtProps => {
// Order by the 1st column
dtProps.order = [[0, "asc"]];

// Return the properties
return dtProps;
},
columns: [
{
name: "type",
Expand Down
Loading