Functions/Add-AGSPEntraGroupMember.ps1

Function Add-AGSPEntraGroupMember{
<#
    .SYNOPSIS
        Adds a service principal (SP/App) as a member of an Entra group via MS Graph API.
 
    .DESCRIPTION
        Adds a service principal (SP/App) as a member of an Entra group via MS Graph API.
        You can identify the service principal by either -ObjectID or -AppID, and the group by either -GroupID or -DisplayName.
 
    .EXAMPLE
        $AccessToken = Get-AGGraphAccessToken -TenantID $TenantID -ClientID $ClientId -ClientSecret $ClientSecret
        Add-AGSPEntraGroupMember -AccessToken $AccessToken -ObjectID "12345678-1234-1234-1234-123456789abc" -GroupID "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
 
        This command first gets an access token, which is used to grant access to Graph, and then adds the specified service principal to the specified group.
 
    .EXAMPLE
        Add-AGSPEntraGroupMember -AccessToken $AccessToken -AppID "12345678-1234-1234-1234-123456789abc" -DisplayName "SecurityGroup_01"
 
        This command looks up the service principal using the Application (Client) ID and the group using display name, then adds the service principal to that group.
 
    .PARAMETER AccessToken
        This is the access token that grants you access to Microsoft Graph. If omitted, the function uses the module-scoped token created by Get-AGGraphAccessToken or Get-AGGraphAccessTokenFromAz.
 
    .PARAMETER ObjectID
        This is the Object ID (OID) of the service principal you want to add to the group.
        Either -ObjectID or -AppID must be provided.
 
    .PARAMETER AppID
        This is the Application (Client) ID of the app registration you want to add to the group.
        The function will look up the corresponding service principal.
        Either -ObjectID or -AppID must be provided.
 
    .PARAMETER GroupID
        This is the Object ID (OID) of the Entra group you want to add the service principal to.
        Either -GroupID or -DisplayName must be provided.
 
    .PARAMETER DisplayName
        This is the start of the name of the group you are looking for. However, if more than one group is found, an error is returned.
        Either -GroupID or -DisplayName must be provided.
 
        Example: for the group "Admin_Desktops" you could use -DisplayName Admin_D
 
    .INPUTS
        None. You cannot pipe input to this function.
 
    .OUTPUTS
        System.Boolean. Returns $true when the service principal is successfully added to the group.
 
    .NOTES
        Author: Lars Panzerbjørn
        Creation Date: 2026.09.02
#>

    [CmdletBinding(DefaultParameterSetName='ObjectIDGroupID')]
    param
    (
        [Parameter(ParameterSetName='ObjectIDGroupID')]
        [Parameter(ParameterSetName='ObjectIDDisplayName')]
        [Parameter(ParameterSetName='AppIDGroupID')]
        [Parameter(ParameterSetName='AppIDDisplayName')]
        [psobject]$AccessToken,

        [Parameter(Mandatory=$true,ParameterSetName='ObjectIDGroupID')]
        [Parameter(Mandatory=$true,ParameterSetName='ObjectIDDisplayName')]
        [string]$ObjectID,

        [Parameter(Mandatory=$true,ParameterSetName='AppIDGroupID')]
        [Parameter(Mandatory=$true,ParameterSetName='AppIDDisplayName')]
        [string]$AppID,

        [Parameter(Mandatory=$true,ParameterSetName='ObjectIDGroupID')]
        [Parameter(Mandatory=$true,ParameterSetName='AppIDGroupID')]
        [string]$GroupID,

        [Parameter(Mandatory=$true,ParameterSetName='ObjectIDDisplayName')]
        [Parameter(Mandatory=$true,ParameterSetName='AppIDDisplayName')]
        [string]$DisplayName
    )

    BEGIN{
        IF (($AccessToken) -or ($TokenResponse)){
            IF($AccessToken){$Headers = @{Authorization = "Bearer $($AccessToken.access_token)"}}
            IF(!($AccessToken)){$Headers = @{Authorization = "Bearer $($TokenResponse.access_token)"}}
        }
        ELSE {THROW "Please provide access token"}

        $Version = "/v1.0"
        $BaseURI = "https://graph.microsoft.com"
    }

    PROCESS{
        IF($PSCmdlet.ParameterSetName -eq "AppIDGroupID" -or $PSCmdlet.ParameterSetName -eq "AppIDDisplayName"){
            Write-Verbose "Looking up service principal with AppID: $AppID"
            $LookupURI = $BaseURI + "/v1.0/servicePrincipals(appId='$AppID')"
            $LookupResult = Invoke-RestMethod -Uri $LookupURI -Headers $Headers

            IF($LookupResult.value){
                $SPObjectID = $LookupResult.value.id
            }
            ELSEIF($LookupResult.id){
                $SPObjectID = $LookupResult.id
            }
            ELSE{
                THROW "No service principal found with AppID: $AppID"
            }
        }
        ELSE{
            $SPObjectID = $ObjectID
        }

        IF("ObjectIDDisplayName" -eq $PSCmdlet.ParameterSetName -or "AppIDDisplayName" -eq $PSCmdlet.ParameterSetName){
            $ResolvedGroupID = (Get-AGGroups -AccessToken $AccessToken -DisplayNameStartsWith $DisplayName).id
            IF($ResolvedGroupID.count -lt 1){THROW "There were no groups found"}
            IF($ResolvedGroupID.count -gt 1){THROW "More than one group was found"}
        }
        ELSE{
            $ResolvedGroupID = $GroupID
        }

        $URI = $BaseURI + $Version + "/groups/$ResolvedGroupID/members/`$ref"
        $Body = @{
            "@odata.id" = "$BaseURI/$Version/directoryObjects/$SPObjectID"
        } | ConvertTo-Json

        Invoke-RestMethod -Uri $URI -Headers $Headers -Method Post -Body $Body -ContentType "application/json"
        $Added = $True
    }
    END{
        Return $Added
    }
}