Public/Persistence/Add-GroupObject.ps1


function Add-GroupObject {
    [CmdletBinding(DefaultParameterSetName = 'ObjectId')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'ObjectId')]
        [string]$GroupObjectId,

        [Parameter(Mandatory = $true, ParameterSetName = 'Name')]
        [string]$GroupName,

        [Parameter(Mandatory = $false)]
        [string]$ObjectId,

        [Parameter(Mandatory = $false)]
        [string]$UserPrincipalName,

        [Parameter(Mandatory = $false)]
        [string]$ServicePrincipalName,

        [Parameter(Mandatory = $false)]
        [string]$ServicePrincipalId,

        [Parameter(Mandatory = $false)]
        [string]$ApplicationId,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Owner', 'Member')]
        [string]$ObjectType = 'Owner'
    )

    begin {
        Write-Verbose "Starting function $($MyInvocation.MyCommand.Name)"
        $MyInvocation.MyCommand.Name | Invoke-BlackCat
    }
    process {
        try {
            # Resolve group ObjectId if Name is provided
            if ($PSCmdlet.ParameterSetName -eq 'Name') {
                $group = Invoke-MsGraph -relativeUrl "groups?`$filter=startswith(displayName,'$GroupName')" | Select-Object -First 1
                if (-not $group) {
                    throw "No group found with display name starting with '$GroupName'."
                }
                $GroupObjectId = $group.id
            }

            # Resolve ObjectId if not provided
            if (-not $ObjectId) {
                switch ($true) {
                    { $UserPrincipalName } {
                        $user = Invoke-MsGraph -relativeUrl "users?`$filter=userPrincipalName eq '$UserPrincipalName'" | Select-Object -First 1
                        if (-not $user) { throw "No user found with userPrincipalName '$UserPrincipalName'." }
                        $ObjectId = $user.id
                        break
                    }
                    { $ServicePrincipalId } {
                        $sp = Invoke-MsGraph -relativeUrl "servicePrincipals/$ServicePrincipalId"
                        if (-not $sp) { throw "No service principal found with id '$ServicePrincipalId'." }
                        $ObjectId = $sp.id
                        break
                    }
                    { $ServicePrincipalName } {
                        $sp = Invoke-MsGraph -relativeUrl "servicePrincipals?`$filter=displayName eq '$ServicePrincipalName'" | Select-Object -First 1
                        if (-not $sp) { throw "No service principal found with displayName '$ServicePrincipalName'." }
                        $ObjectId = $sp.id
                        break
                    }
                    { $ApplicationId } {
                        $sp = Invoke-MsGraph -relativeUrl "servicePrincipals?`$filter=appId eq '$ApplicationId'" | Select-Object -First 1
                        if (-not $sp) { throw "No service principal found with applicationId '$ApplicationId'." }
                        $ObjectId = $sp.id
                        break
                    }
                    default {
                        throw "You must provide ObjectId, UserPrincipalName, ServicePrincipalId, ServicePrincipalName, or ApplicationId."
                    }
                }
            }

            # Prepare the request body
            $body = @{
                "@odata.id" = "https://graph.microsoft.com/beta/directoryObjects/$ObjectId"
            } | ConvertTo-Json

            # Set endpoint and check for existing membership/ownership
            if ($ObjectType -eq 'Owner') {
                $url = "https://graph.microsoft.com/beta/groups/$GroupObjectId/owners/`$ref"
                $existing = Invoke-MsGraph -relativeUrl "groups/$GroupObjectId/owners"
            } else {
                $url = "https://graph.microsoft.com/beta/groups/$GroupObjectId/members/`$ref"
                $existing = Invoke-MsGraph -relativeUrl "groups/$GroupObjectId/members"
            }

            if ($existing | Where-Object { $_.id -eq $ObjectId }) {
                Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "Identity is already $ObjectType of the group."
                return
            }

            $requestParameters = @{
                Uri         = $url
                Headers     = $script:graphHeader
                Method      = 'POST'
                Body        = $body
                ContentType = 'application/json'
                ErrorAction = 'SilentlyContinue'
            }

            Invoke-RestMethod @requestParameters

            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message "$ObjectType with $ObjectId added to group with id $GroupObjectId." -Severity Information
        }
        catch {
            Write-Message -FunctionName $($MyInvocation.MyCommand.Name) -Message $_.Exception.Message -Severity 'Error'
        }
    }
<#
.SYNOPSIS
    Adds an object (user, service principal, or application) as a member or owner to an Azure AD group.
 
.DESCRIPTION
    The Add-GroupObject function adds a specified object (user, service principal, or application) to an Azure AD group as either a member or an owner.
    The group can be specified by ObjectId or by Name. The object to add can be specified by ObjectId, UserPrincipalName, ServicePrincipalId, ServicePrincipalName, or ApplicationId.
    The function checks for existing membership or ownership before attempting to add the object.
 
.PARAMETER GroupObjectId
    The ObjectId of the Azure AD group. Mandatory if using the 'ObjectId' parameter set.
 
.PARAMETER GroupName
    The display name of the Azure AD group. Mandatory if using the 'Name' parameter set.
 
.PARAMETER ObjectId
    The ObjectId of the object (user, service principal, or application) to add to the group.
 
.PARAMETER UserPrincipalName
    The UserPrincipalName of the user to add to the group.
 
.PARAMETER ServicePrincipalName
    The display name of the service principal to add to the group.
 
.PARAMETER ServicePrincipalId
    The ObjectId of the service principal to add to the group.
 
.PARAMETER ApplicationId
    The ApplicationId of the service principal to add to the group.
 
.PARAMETER ObjectType
    Specifies whether to add the object as an 'Owner' or 'Member' of the group. Default is 'Owner'.
 
.EXAMPLE
    Add-GroupObject -GroupObjectId "12345678-90ab-cdef-1234-567890abcdef" -UserPrincipalName "user@domain.com" -ObjectType "Member"
 
    Adds the user with the specified UserPrincipalName as a member to the specified group.
 
.EXAMPLE
    Add-GroupObject -GroupName "MyGroup" -ServicePrincipalId "abcdef12-3456-7890-abcd-ef1234567890" -ObjectType "Owner"
 
    Adds the service principal as an owner to the group with a display name starting with "MyGroup".
 
.NOTES
    Requires appropriate permissions to manage group memberships and ownerships in Azure AD.
    Uses Microsoft Graph API via custom helper functions (Invoke-MsGraph, Write-Message, etc.).
 
#>

}