Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ codeunit 9018 "Azure AD Plan Impl."
begin
case true of
AzureADGraphUser.IsUserDelegatedAdmin():
PlanId := PlanIds.GetDelegatedAdminPlanId();
PlanId := PlanIds.GetGlobalAdminPlanId();
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegated admins are being assigned PlanIds.GetGlobalAdminPlanId() here. GetGlobalAdminPlanId() is the tenant Global Administrator role GUID, not the delegated admin plan GUID (DelegatedAdminGUIDTxt). This will assign the wrong plan to delegated admins and can grant/track the wrong entitlements. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated-admin path.

Suggested change
PlanId := PlanIds.GetGlobalAdminPlanId();
PlanId := PlanIds.GetDelegatedGlobalAdminPlanId();

Copilot uses AI. Check for mistakes.
AzureADGraphUser.IsUserDelegatedHelpdesk():
PlanId := PlanIds.GetHelpDeskPlanId();
else begin
Expand Down Expand Up @@ -805,7 +805,7 @@ codeunit 9018 "Azure AD Plan Impl."
begin
exit(
IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsUserAdmin now checks GetGlobalAdminPlanId() twice, which makes the second condition redundant and drops the delegated-admin check that previously existed. Replace the duplicate with IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), SecurityID) (or the appropriate delegated plan ID) so delegated admins are still treated as admins when intended.

Suggested change
or IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), SecurityID)
or IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), SecurityID)

Copilot uses AI. Check for mistakes.
or IsPlanAssignedToUser(PlanIds.GetD365AdminPlanId(), SecurityID));
end;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,28 +105,52 @@ codeunit 9027 "Plan Ids"
exit(ExternalAccountantPlanGUIDTxt);
end;

#if not CLEAN32
/// <summary>
/// Returns the ID for the Delegated Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated Admin agent - Partner plan.</returns>
[Obsolete('Use GetDelegatedGlobalAdminPlanId instead', '28.0')]
procedure GetDelegatedAdminPlanId(): Guid
begin
exit(DelegatedAdminGUIDTxt);
end;
#endif

/// <summary>
/// Returns the ID for the Delegated Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated Admin agent - Partner plan.</returns>
procedure GetDelegatedGlobalAdminPlanId(): Guid
Comment thread
BenPlunk marked this conversation as resolved.
begin
exit(DelegatedAdminGUIDTxt);
end;
Comment on lines +120 to +127
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML doc comments for GetDelegatedGlobalAdminPlanId() still mention the old plan name ('Delegated Admin agent - Partner'). Since this PR is renaming plans to Entra role names, please update the summary/returns text to the new display name ('Delegated Global Administrator') to keep the public API documentation accurate.

Copilot uses AI. Check for mistakes.

#if not CLEAN32
/// <summary>
/// Returns the ID for the Delegated BC Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated BC Admin agent - Partner plan.</returns>
[Obsolete('Use GetD365BCAdminPlanId instead', '28.0')]
procedure GetDelegatedBCAdminPlanId(): Guid
begin
exit(BCAdminPartnerGUIDTxt);
end;
#endif

/// <summary>
/// Returns the ID for the Delegated BC Admin agent - Partner plan.
/// </summary>
/// <returns>The ID for the Delegated BC Admin agent - Partner plan.</returns>
procedure GetD365BCAdminPlanId(): Guid
Comment thread
BenPlunk marked this conversation as resolved.
begin
exit(BCAdminPartnerGUIDTxt);
end;
Comment on lines +141 to +148
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comments for GetD365BCAdminPlanId() still refer to the old delegated plan naming ('Delegated BC Admin agent - Partner'). Please update the summary/returns text to match the new delegated BC admin display name introduced in this PR (and keep it consistent with Plan Installer / upgrade naming).

Copilot uses AI. Check for mistakes.

/// <summary>
/// Returns the ID for the Internal BC Administrator plan.
/// Returns the ID for the D365 Business Central Administrator plan.
/// </summary>
/// <returns>The ID for the Internal BC Administrator plan.</returns>
/// <returns>The ID for the D365 Business Central Administrator plan.</returns>
procedure GetBCAdminPlanId(): Guid
begin
exit(BCAdminGUIDTxt);
Expand Down
Comment thread
BenPlunk marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ codeunit 9056 "Plan Installer"
UpgradeTag: Codeunit "Upgrade Tag";
PlanUpgradeTag: Codeunit "Plan Upgrade Tag";
begin
CreatePlan(PlanIds.GetDelegatedBCAdminPlanId(), 'Delegated BC Admin agent - Partner', 9022, 'FFF16A30-3B0B-47CB-9751-54A5C8F634ED');
CreatePlan(PlanIds.GetBCAdminPlanId(), 'Internal BC Administrator', 9022, 'A2BB1194-FC0B-4C6B-840F-963851B783C9');
CreatePlan(PlanIds.GetDelegatedAdminPlanId(), 'Delegated Admin agent - Partner', 9022, '7584DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk agent - Partner', 9022, '8884DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Internal Administrator', 9022, '9B84DDCA-27B8-E911-BB26-000D3A2B005C'); // Global admin
CreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated D365 Business Central Administrator', 9022, 'FFF16A30-3B0B-47CB-9751-54A5C8F634ED');
CreatePlan(PlanIds.GetBCAdminPlanId(), 'D365 Business Central Administrator', 9022, 'A2BB1194-FC0B-4C6B-840F-963851B783C9');
CreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Global Administrator', 9022, '7584DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk Administrator', 9022, '8884DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Global Administrator', 9022, '9B84DDCA-27B8-E911-BB26-000D3A2B005C'); // Global admin
CreatePlan(PlanIds.GetD365AdminPlanId(), 'Dynamics 365 Administrator', 9022, 'F67B9B96-C667-4DD2-B370-FA065A895C9D');
CreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Admin agent - Partner', 9022, '5C28E514-8AFD-4158-B8C0-93A5915938F9');
CreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Administrator', 9022, '5C28E514-8AFD-4158-B8C0-93A5915938F9');
CreatePlan(PlanIds.GetEssentialISVPlanId(), 'Dynamics 365 Business Central Essential - Embedded', 9022, '2E84DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetTeamMemberPlanId(), 'Dynamics 365 Business Central Team Member', 9028, '5784DDCA-27B8-E911-BB26-000D3A2B005C');
CreatePlan(PlanIds.GetMicrosoft365PlanId(), 'Microsoft 365', 8999, '57ff2da0-773e-42df-b2af-ffb7a2317929');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ codeunit 9057 "Plan Upgrade"
begin
UpdateSubscriptionPlan();
RenamePlansAndDeleteOldPlans();
RenameDelegatedAdminPlans();
RenameTeamMemberPlan();
RenameDevicePlan();
AddPremiumPartnerSandbox();
Expand Down Expand Up @@ -86,7 +87,7 @@ codeunit 9057 "Plan Upgrade"
RenameOrCreatePlan(PlanIds.GetExternalAccountantPlanId(), 'Dynamics 365 Business Central External Accountant');
RenameOrCreatePlan(PlanIds.GetPremiumISVPlanId(), 'Dynamics 365 Business Central Premium - Embedded');
RenameOrCreatePlan(PlanIds.GetViralSignupPlanId(), 'Dynamics 365 Business Central for IWs');
RenameOrCreatePlan(PlanIds.GetDelegatedAdminPlanId(), 'Delegated Admin agent - Partner');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Admin agent - Partner');
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RenamePlansAndDeleteOldPlans renames 'Delegated Admin agent - Partner' using PlanIds.GetGlobalAdminPlanId(). That GUID corresponds to the Global Administrator plan, not the delegated admin plan (DelegatedAdminGUIDTxt). This will rename the wrong plan during upgrade. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated plan rename.

Suggested change
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Admin agent - Partner');
RenameOrCreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Admin agent - Partner');

Copilot uses AI. Check for mistakes.
RenameOrCreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk agent - Partner');
RenameOrCreatePlan('996DEF3D-B36C-4153-8607-A6FD3C01B89F', 'D365 Business Central Infrastructure');

Expand All @@ -96,11 +97,33 @@ codeunit 9057 "Plan Upgrade"
DeletePlan('46764787-E039-4AB0-8F00-820FC2D89BF9');
DeletePlan('312BDEEE-8FBD-496E-B529-EB985F305FCF');

Session.LogMessage('0000AHN', 'Subscription Plans were renamed and old plans werer deleted.', Verbosity::Normal, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');
Session.LogMessage('0000AHN', 'Subscription Plans were renamed and old plans were deleted.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');

UpgradeTag.SetUpgradeTag(PlanUpgradeTag.GetRenamePlansUpgradeTag());
end;

[NonDebuggable]
local procedure RenameDelegatedAdminPlans()
Comment thread
BenPlunk marked this conversation as resolved.
var
UpgradeTag: Codeunit "Upgrade Tag";
PlanUpgradeTag: Codeunit "Plan Upgrade Tag";
PlanIds: Codeunit "Plan Ids";
begin
if UpgradeTag.HasUpgradeTag(PlanUpgradeTag.GetRenameDelegatedAdminPlansUpgradeTag()) then
exit;

Comment thread
stkillen marked this conversation as resolved.
RenameOrCreatePlan(PlanIds.GetBCAdminPlanId(), 'D365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Global Administrator');
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated Dynamics 365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Global Administrator');
Comment on lines +117 to +118
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RenameDelegatedAdminPlans renames GetGlobalAdminPlanId() twice (to 'Global Administrator' and then to 'Delegated Global Administrator'). The second rename should target the delegated admin plan GUID (GetDelegatedGlobalAdminPlanId()), otherwise it will overwrite the global admin plan name and never rename the delegated plan. Also note that 'Delegated Dynamics 365 Business Central Administrator' exceeds the Plan.Name length (50) and will be truncated by CopyStr, so the resulting display name will not match the intended role name.

Suggested change
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated Dynamics 365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetGlobalAdminPlanId(), 'Delegated Global Administrator');
RenameOrCreatePlan(PlanIds.GetD365BCAdminPlanId(), 'Delegated D365 Business Central Administrator');
RenameOrCreatePlan(PlanIds.GetDelegatedGlobalAdminPlanId(), 'Delegated Global Administrator');

Copilot uses AI. Check for mistakes.
RenameOrCreatePlan(PlanIds.GetHelpDeskPlanId(), 'Delegated Helpdesk Administrator');
RenameOrCreatePlan(PlanIds.GetD365AdminPartnerPlanId(), 'Delegated Dynamics 365 Administrator');

Session.LogMessage('0000AHN', 'Subscription Plans were renamed.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'AL SaaS Upgrade');

UpgradeTag.SetUpgradeTag(PlanUpgradeTag.GetRenameDelegatedAdminPlansUpgradeTag());
end;

[NonDebuggable]
local procedure RenameTeamMemberPlan()
var
Expand Down Expand Up @@ -225,15 +248,15 @@ codeunit 9057 "Plan Upgrade"

// Create internal plan
PlanId := PlanIds.GetBCAdminPlanId();
PlanName := 'Internal BC Administrator';
PlanName := 'D365 Business Central Administrator';
RoleCenterId := 9022;

if not Plan.Get(PlanId) then
CreatePlan(PlanId, PlanName, RoleCenterId);

// Create delegated plan
PlanId := PlanIds.GetDelegatedBCAdminPlanId();
PlanName := 'Delegated BC Admin agent - Partner';
PlanId := PlanIds.GetD365BCAdminPlanId();
PlanName := 'Delegated Dynamics 365 Business Central Admin';
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In AddBCAdmin, the delegated BC admin plan name is set to 'Delegated Dynamics 365 Business Central Admin', while Plan Installer creates 'Delegated D365 Business Central Administrator' and RenameDelegatedAdminPlans tries to rename to yet another variant. Please align on a single <=50-char display name across install + upgrade paths to avoid inconsistent names and accidental truncation.

Suggested change
PlanName := 'Delegated Dynamics 365 Business Central Admin';
PlanName := 'Delegated D365 Business Central Administrator';

Copilot uses AI. Check for mistakes.
RoleCenterId := 9022;

if not Plan.Get(PlanId) then
Expand Down Expand Up @@ -283,7 +306,7 @@ codeunit 9057 "Plan Upgrade"
exit;

PlanId := PlanIds.GetD365AdminPartnerPlanId();
PlanName := 'Delegated Dynamics 365 Admin agent - Partner';
PlanName := 'Delegated Dynamics 365 Administrator';
RoleCenterId := 9022;

if Plan.Get(PlanId) then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ codeunit 9058 "Plan Upgrade Tag"
exit('MS-329421-RenamePlans-20211028');
end;

/// <summary>
/// Returns the rename delegated admin plans upgrade tag.
/// </summary>
/// <returns>The rename delegated admin plans upgrade tag.</returns>
internal procedure GetRenameDelegatedAdminPlansUpgradeTag(): Code[250]
begin
exit('MS-582117-RenameDelegatedAdminPlans-20260128');
end;
Comment on lines +52 to +59
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new upgrade tag is referenced from Plan Upgrade via HasUpgradeTag/SetUpgradeTag, but it isn't added to RegisterPerDatabaseTags() in this codeunit. Please register it there as well; otherwise the tag list is incomplete and the upgrade tag framework may not preserve/report it correctly.

Copilot uses AI. Check for mistakes.

/// <summary>
/// Returns the rename team member plan upgrade tag.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ codeunit 776 "Plan User Details"
end;

UserDetails."User Plans" := CopyStr(UserPlansTextBuilder.ToText().TrimEnd(' ; '), 1, MaxStrLen(UserDetails."User Plans"));
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), UserSecId) or
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecId) or
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Is Delegated" is currently derived from GetGlobalAdminPlanId(), which represents the real Global Administrator role, not the delegated admin plan. This will mark global admins as delegated and fail to mark delegated admins if they only have the delegated plan GUID. Use PlanIds.GetDelegatedGlobalAdminPlanId() for the delegated-global-admin plan check.

Suggested change
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecId) or
UserDetails."Is Delegated" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), UserSecId) or

Copilot uses AI. Check for mistakes.
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetHelpDeskPlanId(), UserSecId) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365AdminPartnerPlanId(), UserSecId) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedBCAdminPlanId(), UserSecId);
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365BCAdminPlanId(), UserSecId);

UserDetails."Has M365 Plan" := AzureADPlan.IsPlanAssignedToUser(PlanIds.GetMicrosoft365PlanId(), UserSecId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,10 @@ codeunit 9017 "Azure AD User Mgmt. Impl."
var
PlanIds: Codeunit "Plan Ids";
begin
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedAdminPlanId(), UserSecID) or
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecID) or
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsUserDelegated should check the delegated admin plan ({000...007}), not the tenant Global Administrator role (GetGlobalAdminPlanId(), {62e9...}). Using the global admin plan here will classify true global admins as "delegated" and miss actual delegated admins. Replace GetGlobalAdminPlanId() with GetDelegatedGlobalAdminPlanId() in this predicate.

Suggested change
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetGlobalAdminPlanId(), UserSecID) or
exit(AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedGlobalAdminPlanId(), UserSecID) or

Copilot uses AI. Check for mistakes.
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetHelpDeskPlanId(), UserSecID) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365AdminPartnerPlanId(), UserSecID) or
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetDelegatedBCAdminPlanId(), UserSecID))
AzureADPlan.IsPlanAssignedToUser(PlanIds.GetD365BCAdminPlanId(), UserSecID))
end;

[NonDebuggable]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,9 @@ codeunit 9029 "Azure AD User Sync Impl."
end;

// If the user's plans are any of the following:
// - Internal Administrator (Global Administrator or Dynamics 365 Administrator or BC Administrator)
// - Global Administrator (Global Administrator or Dynamics 365 Administrator or BC Administrator)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment still refers to "BC Administrator", but the plan display name was renamed to "D365 Business Central Administrator" elsewhere in this PR. Please update the comment text so it matches the current plan naming (and avoids confusion with the delegated/global variants).

Suggested change
// - Global Administrator (Global Administrator or Dynamics 365 Administrator or BC Administrator)
// - Global Administrator (Global Administrator or Dynamics 365 Administrator or D365 Business Central Administrator)

Copilot uses AI. Check for mistakes.
// - Microsoft 365
// - Internal Administrator + Microsoft 365
// - Global Administrator + Microsoft 365
// and there is no environment security group defined,
// then we don't want to create a BC user during user sync.
local procedure SkipCreatingUserDuringSync(UserPlanIDs: List of [Guid]): Boolean
Expand Down
Loading
Loading