source/Public/Grant-RJUpdateComplianceWorkspaceAccess.ps1

function Grant-RJUpdateComplianceWorkspaceAccess {
    <#
    .SYNOPSIS
    Grants the RealmJoin Azure Resources service principal read access to an Update Compliance workspace.
 
    .DESCRIPTION
    Assigns the Log Analytics Reader role on an existing Update Compliance Log Analytics
    workspace to the RealmJoin Azure Resources service principal, so RealmJoin can read
    Update Compliance data. The workspace is identified by its workspace ID (CustomerId GUID)
    and is looked up in the selected Azure subscription.
 
    This cmdlet only assigns the role - it does not deploy or modify any resources.
 
    Prerequisites:
    - Az.Accounts and Az.Resources PowerShell modules
    - The RealmJoin Azure Resources service principal must already exist in the tenant
    - The caller needs permission to create role assignments on the workspace
      (e.g. Owner or User Access Administrator)
 
    .PARAMETER WorkspaceId
    The workspace ID (CustomerId GUID) of the Update Compliance Log Analytics workspace.
 
    .PARAMETER SubscriptionId
    Optional. The Azure subscription ID containing the workspace. If not provided, uses the current context.
 
    .PARAMETER WhatIf
    Shows what role assignment would be created without actually creating it.
 
    .EXAMPLE
    Grant-RJUpdateComplianceWorkspaceAccess -WorkspaceId "00000000-0000-0000-0000-000000000000"
 
    .EXAMPLE
    Grant-RJUpdateComplianceWorkspaceAccess -WorkspaceId "00000000-0000-0000-0000-000000000000" -SubscriptionId "11111111-1111-1111-1111-111111111111"
 
    .EXAMPLE
    Grant-RJUpdateComplianceWorkspaceAccess -WorkspaceId "00000000-0000-0000-0000-000000000000" -WhatIf
    Shows the role assignment that would be created without actually creating it.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [guid]$WorkspaceId,

        [string]$SubscriptionId
    )

    begin {
        Write-Verbose "Grant-RJUpdateComplianceWorkspaceAccess: Starting"
    }

    process {
        if (-not (Test-RJModuleVersion)) {
            # Module is outdated - exit gracefully without throwing
            return
        }

        # Initialize Az modules
        Write-Information "Initializing required Azure modules..."
        if (-not (Initialize-RequiredModule)) {
            Write-Verbose "Azure module initialization failed - cannot proceed"
            return
        }

        # Connect to Azure
        $connectParams = @{}
        if ($SubscriptionId) { $connectParams.SubscriptionId = $SubscriptionId }
        $context = Connect-RJAzureContext @connectParams
        if ($null -eq $context) {
            Write-Information "Operation cancelled."
            return
        }

        Write-Information ""
        Write-Information "============================================"
        Write-Information "RealmJoin Update Compliance Workspace Access"
        Write-Information "============================================"
        Write-Information ""

        # Resolve the RealmJoin Azure Resources service principal
        Write-Information "Resolving RealmJoin Azure Resources service principal..."
        $appId = Get-RJAzureResourcesAppId
        $servicePrincipal = Get-ServicePrincipalByAppId -AppId $appId
        if (-not $servicePrincipal) {
            throw "RealmJoin Azure Resources service principal (AppId: $appId) not found in this tenant. Run Set-RJLogAnalyticsWorkspace first."
        }

        # Resolve the workspace GUID to its ARM resource
        Write-Information "Looking up Log Analytics workspace with workspace ID '$WorkspaceId'..."
        $workspace = Get-RJLogAnalyticsWorkspaceByCustomerId -CustomerId $WorkspaceId.ToString()
        Write-Information "Found workspace '$($workspace.Name)'."

        # Idempotency pre-check (read-only, before ShouldProcess so -WhatIf also reports it)
        Write-Information "Checking existing role assignments..."
        if (Test-RJRoleAssignmentExist -Scope $workspace.ResourceId -PrincipalId $servicePrincipal.Id -RoleDefinitionId $LogAnalyticsReaderRoleId) {
            Write-Information "Log Analytics Reader role is already assigned to '$($servicePrincipal.DisplayName)' on workspace '$($workspace.Name)'. Nothing to do."
            return
        }

        if ($PSCmdlet.ShouldProcess("Log Analytics Workspace '$($workspace.Name)' (WorkspaceId: $WorkspaceId)", "Assign Log Analytics Reader role to '$($servicePrincipal.DisplayName)'")) {
            try {
                New-AzRoleAssignment -ObjectId $servicePrincipal.Id -RoleDefinitionId $LogAnalyticsReaderRoleId -Scope $workspace.ResourceId -ErrorAction Stop | Out-Null
                Write-Information "Assigned Log Analytics Reader role to '$($servicePrincipal.DisplayName)' on workspace '$($workspace.Name)'."
            }
            catch {
                # A concurrent/portal assignment racing us is success, not failure
                if ($_.FullyQualifiedErrorId -match 'RoleAssignmentExists' -or $_.Exception.Message -match 'already exists') {
                    Write-Information "Role assignment already exists. Nothing to do."
                }
                else {
                    throw
                }
            }
        }
        else {
            Write-Information "Would assign Log Analytics Reader role to '$($servicePrincipal.DisplayName)' ($($servicePrincipal.Id)) on scope: $($workspace.ResourceId)"
        }
    }

    end {
        Write-Verbose "Grant-RJUpdateComplianceWorkspaceAccess: Completed"
    }
}