Dynamics 365 Business Central: How to get Security Groups, Available Security Groups (not yet added to BC), and Security Group Members in AL

Dynamics 365 Business Central

Hi, Readers.
I was asked a question recently, whether developers could get Security Groups and Security Group Members via AL. In this post, I’d like to talk briefly about this.

Security groups make it easier for administrators to manage user permissions. For example, for Business Central online, they’re reusable across Dynamics 365 applications, such as SharePoint Online, CRM Online, and Business Central. Administrators add permissions to their Business Central security groups, and when they add users to the group the permissions apply to all members. More details: Business Central 2023 wave 1 (BC22): Manage user permissions using Azure Active Directory security groups (‘User Group’ -> ‘Security Group’) and Control Access to Business Central Using Security Groups

Azure Active Directory is now Microsoft Entra ID. Learn more

First of all, why is this a problem? As you might know, table ‘User Group’ is marked for removal (Replaced by the Security Group) from Business Central 2023 wave 1 (BC22).

Table ‘User Group’ is marked for removal. Reason: [220_UserGroups] Replaced by the Security Group table and Security Group codeunit in the security groups system; by Tenant Permission Set table in the permission sets system. To learn more, go to https://go.microsoft.com/fwlink/?linkid=2245709.. Tag: 22.0.

Then, the source table used on the page are all temporary tables.
For example,
Security Groups (9871, List): Security Group Buffer (9022)

Security Group Lookup (9877, List) – Available Security Groups: Security Group Buffer (9022) Table

Security Group Members (9869, List): Security Group Member Buffer (9021)

Finally there is a table, table 9020 “Security Group”.

The Access property is set to Internal. (The object can be accessed only by code in the same module, but not from another module)
PS: Can we access the standard internal table/field (Access Property = Internal) via AL???

Is there no way out? Please don’t give up. This is actually not very difficult, it just requires another method. Let’s see more details.
All Security Groups:

Already added to BC:

Not yet added to BC:

1. Security Groups (Already added to BC)

The key method is Codeunit “Security Group”.GetGroups(var SecurityGroupBuffer: Record “Security Group Buffer”).

In page 9871 “Security Groups”:

In codeunit 9031 “Security Group”:

A simple example:

2. Available Security Groups (not yet added to BC)

This should be the most difficult in this discussion, because it is collected when the page is opened, and its logic cannot be copied.

In page 9877 “Security Group Lookup”:

And codeunit 9871 “Security Group Impl.” (Access = Internal): procedure GetAvailableGroups(var SecurityGroupBuffer: Record “Security Group Buffer”)

So if you need to collect Available Security Groups (not yet added to BC), you can only use the standard page.
A simple example:

3. Security Group Members

Similar to the first situation, the key method is Codeunit “Security Group”.procedure GetMembers(var SecurityGroupMemberBuffer: Record “Security Group Member Buffer”).

In page 9869 “Security Group Members”:

In codeunit 9031 “Security Group”:

A simple example:

Note that the data retrieved by default includes members of all groups.

Great. Maybe I didn’t give a very good example, but I hope it can give you some tips. Give it a try!!!😁

My test code:

pageextension 50100 CustomerListExt extends "Customer List"
{
    actions
    {
        addafter("Sent Emails")
        {
            action(GetSecurityGroups)
            {
                Caption = 'Get Security Groups';
                Image = Open;
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;

                trigger OnAction()
                begin
                    GetSecurityGroups();
                end;
            }
            action(GetAvailableGroups)
            {
                Caption = 'Get Available Security Groups';
                Image = Open;
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;

                trigger OnAction()
                begin
                    GetAvailableSecurityGroups();
                end;
            }
            action(GetSecurityGroupMembers)
            {
                Caption = 'Get Security Group Members';
                Image = Open;
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;

                trigger OnAction()
                begin
                    GetSecurityMembers();
                end;
            }
        }
    }

    local procedure GetSecurityGroups()
    var
        SecurityGroupBuffer: Record "Security Group Buffer";
        SecurityGroup: Codeunit "Security Group";
        Msg: Label 'There are %1 Security Groups, and the code of the first one is %2.';
    begin
        SecurityGroup.GetGroups(SecurityGroupBuffer);
        if SecurityGroupBuffer.FindFirst() then
            Message(Msg, SecurityGroupBuffer.Count, SecurityGroupBuffer.Code);
    end;

    local procedure GetSecurityMembers()
    var
        SecurityGroupMemberBuffer: Record "Security Group Member Buffer";
        SecurityGroup: Codeunit "Security Group";
        Msg: Label 'There are %1 Security Group Members, and the user name of the first one is %2.';
    begin
        SecurityGroup.GetMembers(SecurityGroupMemberBuffer);
        SecurityGroupMemberBuffer.SetAutoCalcFields("User Name");
        if SecurityGroupMemberBuffer.FindFirst() then
            Message(Msg, SecurityGroupMemberBuffer.Count, SecurityGroupMemberBuffer."User Name");
    end;

    local procedure GetAvailableSecurityGroups()
    var
        SelectedSecurityGroup: Record "Security Group Buffer";
    begin
        if Page.RunModal(Page::"Security Group Lookup", SelectedSecurityGroup) = Action::LookupOK then
            Message(SelectedSecurityGroup."Group Name");
    end;
}

PS: If your code is also used in On-Premise version, please note that it may be Windows authentication, and the Group Code needs to be obtained from the Domain name. Here is a simple example that references the standard code.

pageextension 50100 CustomerListExt extends "Customer List"
{
    actions
    {
        addafter("Sent Emails")
        {
            action(GetAvailableGroups)
            {
                Caption = 'Get Available Security Groups';
                Image = Open;
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;

                trigger OnAction()
                begin
                    LookupAvailableGroups();
                end;
            }
        }
    }

    local procedure LookupAvailableGroups()
    var
        SecurityGroupLookup: Page "Security Group Lookup";
        SelectedSecurityGroup: Record "Security Group Buffer";
        NewSecurityGroupCodeValue: Code[20];
        NewSecurityGroupNameValue: Text;
        NewSecurityGroupIdValue: Text;
    begin
        SecurityGroupLookup.LookupMode(true);
        if SecurityGroupLookup.RunModal() = Action::LookupOK then begin
            SecurityGroupLookup.GetRecord(SelectedSecurityGroup);
            NewSecurityGroupNameValue := SelectedSecurityGroup."Group Name";
            NewSecurityGroupIdValue := SelectedSecurityGroup."Group ID";
            NewSecurityGroupCodeValue := GetDesirableCode(NewSecurityGroupNameValue);
        end;
        Message(Format(NewSecurityGroupNameValue) + Format(NewSecurityGroupIdValue) + Format(NewSecurityGroupCodeValue));
    end;

    procedure GetDesirableCode(GroupName: Text): Code[20]
    var
        GroupDomainAndNameList: List of [Text];
        SecurityGroup: Codeunit "Security Group";
    begin
        if SecurityGroup.IsWindowsAuthentication() then begin
            GroupDomainAndNameList := GroupName.Split('\');
            exit(CopyStr(GroupDomainAndNameList.Get(GroupDomainAndNameList.Count()), 1, 20));
        end else
            exit(CopyStr(GroupName, 1, 20));
    end;
}

END

Hope this will help.

Thanks for reading.

ZHU

コメント

Copied title and URL