Omnicit.EntraRBAC.psd1

@{
    RootModule           = 'Omnicit.EntraRBAC.psm1'
    ModuleVersion        = '1.3.0'
    CompatiblePSEditions = @('Core')
    GUID                 = '7b9e4a1c-2d6f-4f3a-9c8b-1e5d0a7c3f42'
    Author               = 'Omnicit AB / Philip Haglund'
    CompanyName          = 'Omnicit'
    Copyright            = '(c) 2026 Omnicit AB'
    Description          = 'Manage Entra ID and Azure RBAC building blocks across tenants: Entra ID groups, PIM, Administrative Units, Entitlement Management, Access Reviews, Azure resources and RBAC, plus a JSON inventory and declarative apply engine.'
    PowerShellVersion    = '7.2'

    RequiredModules = @(
        @{ ModuleName = 'AzAuth'; ModuleVersion = '2.9.0' }
        @{ ModuleName = 'Microsoft.Graph.Authentication'; ModuleVersion = '2.36.0' }
    )

    # Loaded via Update-TypeData in suffix.ps1 (Remove-Module does not clean type data).
    TypesToProcess   = @()
    FormatsToProcess = @('Formats/Omnicit.EntraRBAC.Format.ps1xml')

    FunctionsToExport = @('Add-OERAccessPackageResourceRole','Add-OERAdministrativeUnitMember','Add-OERAdministrativeUnitScopedRole','Add-OERCatalogResource','Add-OERGroupEligibility','Add-OERGroupMember','Connect-OER','Disable-OEREligibleRoleAssignment','Disconnect-OER','Enable-OEREligibleRoleAssignment','Export-OERInventory','Get-OERAccessPackage','Get-OERAccessPackageAssignment','Get-OERAccessPackageAssignmentPolicy','Get-OERAccessPackageResourceRole','Get-OERAccessReviewDefinition','Get-OERAccessReviewInstance','Get-OERAccessReviewInstanceDecision','Get-OERActiveDirectoryRoleAssignment','Get-OERActiveRoleAssignment','Get-OERAdministrativeUnit','Get-OERAdministrativeUnitScopedRole','Get-OERAuthenticationContext','Get-OERCatalog','Get-OERCatalogResource','Get-OERConfiguration','Get-OERDirectoryRoleManagementPolicy','Get-OEREligibleDirectoryRoleAssignment','Get-OEREligibleRoleAssignment','Get-OERGroup','Get-OERGroupEligibility','Get-OERGroupMember','Get-OERGroupPimPolicy','Get-OERInventory','Get-OERManagementGroup','Get-OERRequiredScope','Get-OERResource','Get-OERResourceGroup','Get-OERRoleAssignment','Get-OERRoleDefinition','Get-OERRoleManagementPolicy','Get-OERSubscription','Invoke-OERAccessReviewInstanceDecision','Invoke-OERStructure','New-OERAccessPackage','New-OERAccessPackageApprovalStage','New-OERAccessPackageAssignment','New-OERAccessPackageAssignmentPolicy','New-OERAccessPackageRequestorScope','New-OERAccessPackageRequestorSettings','New-OERAccessReviewDefinition','New-OERAccessReviewStage','New-OERActiveDirectoryRoleAssignment','New-OERActiveRoleAssignment','New-OERAdministrativeUnit','New-OERCatalog','New-OERConfiguration','New-OEREligibleDirectoryRoleAssignment','New-OEREligibleRoleAssignment','New-OERGroup','New-OERPolicyNotificationRule','New-OERResourceGroup','New-OERRoleAssignment','Remove-OERAccessPackage','Remove-OERAccessPackageAssignment','Remove-OERAccessPackageAssignmentPolicy','Remove-OERAccessPackageResourceRole','Remove-OERAccessReviewDefinition','Remove-OERActiveDirectoryRoleAssignment','Remove-OERActiveRoleAssignment','Remove-OERAdministrativeUnit','Remove-OERAdministrativeUnitMember','Remove-OERAdministrativeUnitScopedRole','Remove-OERCatalog','Remove-OERCatalogResource','Remove-OERConfiguration','Remove-OEREligibleDirectoryRoleAssignment','Remove-OEREligibleRoleAssignment','Remove-OERGroup','Remove-OERGroupEligibility','Remove-OERGroupMember','Remove-OERResourceGroup','Remove-OERRoleAssignment','Send-OERAccessReviewReminder','Set-OERAccessPackage','Set-OERAccessPackageAssignmentPolicy','Set-OERAccessReviewDefinition','Set-OERAdministrativeUnit','Set-OERCatalog','Set-OERConfiguration','Set-OERDirectoryRoleManagementPolicy','Set-OERGroup','Set-OERGroupPimPolicy','Set-OERResourceGroup','Set-OERRoleAssignment','Set-OERRoleManagementPolicy','Stop-OERAccessReviewInstance','Test-OERStructure')
    CmdletsToExport   = @()
    VariablesToExport = @()
    AliasesToExport   = @()

    PrivateData = @{
        PSData = @{
            Tags                     = @('EntraID', 'Azure', 'RBAC', 'PIM', 'Identity', 'Governance',
                                          'PSEdition_Core', 'Windows', 'Linux', 'MacOS')
            ProjectUri               = 'https://github.com/Omnicit/Omnicit.EntraRBAC'
            LicenseUri               = 'https://github.com/Omnicit/Omnicit.EntraRBAC/blob/main/LICENSE'
            IconUri                  = 'https://raw.githubusercontent.com/Omnicit/Omnicit.EntraRBAC/main/assets/icon.png'
            RequireLicenseAcceptance = $false
            ReleaseNotes             = '## [1.3.0] - 2026-10-19

A PIM for Groups activation is no longer written or applied as a standing membership. Inventories
leave out a member or owner held by an activation or time-bound active assignment, counted per group
in the export README; with PIM assignments unread, they omit eligible ones and report
`InventoryPartial`. `Invoke-OERStructure` adds and prunes no member or owner PIM holds or makes
eligible, and none of an existing group whose PIM state is unread. Needs
`PrivilegedAssignmentSchedule.Read.AzureADGroup`.

Inventories leave out role assignments at the Azure root scope `/` with a warning, listed in the
README as elevated access; `Test-OERStructure` warns on one and apply never creates or prunes
there. A review found by name is updated only if it reviews the declared package and policy (a
shared name must be an id), else it fails (`AccessReviewScopeMismatch`), writing nothing.

`Set-OERAccessPackageAssignmentPolicy` keeps custom extension stages, updates an automatic policy
and renames a policy given `-DisplayName` alone. `Test-OERStructure` refuses an invalid
`mailNickname` and a scope with a dot segment, `%`, `\`, `?`, `#` or stray white space, and warns
when a renamed group keeps its alias; `New-OERGroup` refuses an underivable alias.

`Invoke-OERStructure` renames administrative units, catalogs, access packages, access reviews and
assignment policies within their package through `previousDisplayName`, like groups: the object
found only under its previous name is renamed; if both names find different objects, or neither
finds one, the entry fails and writes nothing; a same-run reference by the new name reaches the
renamed object. A catalog it fails to read is now `Failed`, with nothing written, instead of
being updated. `-WhatIf` plans a package in a new catalog as created.

`groupsRoster.json` rows now carry each group''s id, type, visibility and creation time. Inventories
count shared group names tenant-wide, leave out same-named policies of one package (now refused in
a document) and name a shared catalog, review target or role-assignment group by id; the export
README lists each name and count. `Invoke-OERStructure` fails, writing nothing, when a package it
would create exists in another catalog.

Inventories set `principalStatus` `Deleted`/`Unresolved` for a role assignment whose principal
Entra ID does not return (apply never creates it), omit one at an undeclarable scope, write
`principalType` on administrative unit scoped roles and mark failed lookups partial. Exports flag
PIM groups in restricted units. Reviews reach a declared package via its entry, failing if
`-Include` lacks `AccessPackages` (or use the id) or `-WhatIf` plans it as new.

`Export-OERInventory` writes `lighthouseDelegations.json`: Azure Lighthouse delegations at the
subscriptions it reads, access a managing tenant holds that no role assignment shows, listed in the
README. With `-IncludeServicePrincipalAccess` it writes `servicePrincipalAccess.json`: application
permissions, delegated grants and owners of service principals and app registrations (about one
request per list page). Both are read-only findings, never applied; a failed read makes the export
partial (an unread part is `null`).

Exports read only the session tenant''s own subscriptions, naming the others in the README, and
`Invoke-OERStructure` refuses a role assignment or policy in another tenant''s. `Get-OERInventory`
now reads none there (`SubscriptionTenantMismatch`); with `-Scope` or `-Subscription` its Azure
sections read the subscription once per call, get the ARM token at sign-in (if it fails, nothing is
sent) and are left unread (partial) for an undecidable subscription or an undeclarable or
unresolvable scope.

`Test-OERStructure -NamingStandard` checks names in a document or export against a naming standard
file: one warning per broken rule, never an error.

'

            Prerelease               = ''
        }
    }
}