en-US/about_Omnicit.EntraRBAC.help.txt
|
TOPIC about_Omnicit.EntraRBAC SHORT DESCRIPTION Manage Entra ID and Azure RBAC building blocks across tenants. LONG DESCRIPTION Omnicit.EntraRBAC manages Entra ID groups and PIM, Administrative Units, Entitlement Management, Access Reviews, Azure resources and RBAC, and Azure PIM across multiple tenants, plus a JSON inventory and a declarative apply engine. Authenticate with Connect-OER, then use the OER cmdlets. Configuration per tenant is stored in Tenant Profile files managed by New/Get/Set/Remove-OERConfiguration. Calling Connect-OER is optional. Every cmdlet that reaches Graph or Azure authenticates on first use, reusing a cached token for the same tenant and identity. Cmdlets that reach Azure Resource Manager need an ARM token; pass -IncludeARM to Connect-OER to acquire one up front. All state-changing cmdlets support -WhatIf and -Confirm. Deletions of high-value objects use ConfirmImpact High and warn before the destructive call. Use Get-Command -Module Omnicit.EntraRBAC to list all 98 cmdlets, and Get-Help <cmdlet> -Full for parameters and examples. Use Get-OERRequiredScope for the permissions any of them needs; see PERMISSIONS below. COMMAND COHORTS The cohorts below are for orientation only. They are NOT permission boundaries, so none of them carries a scope line -- ask Get-OERRequiredScope instead. Authentication and Tenant Profiles Connect-OER, Disconnect-OER New-OERConfiguration, Get-OERConfiguration, Set-OERConfiguration, Remove-OERConfiguration Groups and PIM for Groups New-OERGroup, Get-OERGroup, Set-OERGroup, Remove-OERGroup Add-OERGroupMember, Get-OERGroupMember, Remove-OERGroupMember Add-OERGroupEligibility, Get-OERGroupEligibility, Remove-OERGroupEligibility Get-OERGroupPimPolicy, Set-OERGroupPimPolicy Administrative Units New-OERAdministrativeUnit, Get-OERAdministrativeUnit, Set-OERAdministrativeUnit, Remove-OERAdministrativeUnit Add-OERAdministrativeUnitMember, Remove-OERAdministrativeUnitMember Add-OERAdministrativeUnitScopedRole, Get-OERAdministrativeUnitScopedRole, Remove-OERAdministrativeUnitScopedRole New-OERGroup -AdministrativeUnit places a new group into an AU at creation time. Entitlement Management Catalogs: New-OERCatalog, Get-OERCatalog, Set-OERCatalog, Remove-OERCatalog Catalog resources: Add-OERCatalogResource, Get-OERCatalogResource, Remove-OERCatalogResource Access packages: New-OERAccessPackage, Get-OERAccessPackage, Set-OERAccessPackage, Remove-OERAccessPackage Resource roles: Add-OERAccessPackageResourceRole, Get-OERAccessPackageResourceRole, Remove-OERAccessPackageResourceRole Policy builders: New-OERAccessPackageApprovalStage, New-OERAccessPackageRequestorScope, New-OERAccessPackageRequestorSettings Policies: New-OERAccessPackageAssignmentPolicy, Get-OERAccessPackageAssignmentPolicy, Set-OERAccessPackageAssignmentPolicy, Remove-OERAccessPackageAssignmentPolicy Assignments: New-OERAccessPackageAssignment, Get-OERAccessPackageAssignment, Remove-OERAccessPackageAssignment Access Reviews New-OERAccessReviewDefinition, Get-OERAccessReviewDefinition, Set-OERAccessReviewDefinition, Remove-OERAccessReviewDefinition New-OERAccessReviewStage composes one stage of a multi-stage review. Get-OERAccessReviewInstance, Get-OERAccessReviewInstanceDecision, Stop-OERAccessReviewInstance, Invoke-OERAccessReviewInstanceDecision, Send-OERAccessReviewReminder Directory roles Get-OERDirectoryRoleManagementPolicy, Set-OERDirectoryRoleManagementPolicy Eligible: New-OEREligibleDirectoryRoleAssignment, Get-OEREligibleDirectoryRoleAssignment, Remove-OEREligibleDirectoryRoleAssignment Active: New-OERActiveDirectoryRoleAssignment, Get-OERActiveDirectoryRoleAssignment, Remove-OERActiveDirectoryRoleAssignment Azure inventory, RBAC, resource groups and resources Get-OERManagementGroup, Get-OERSubscription, Get-OERRoleDefinition Get-OERRoleAssignment, New-OERRoleAssignment, Set-OERRoleAssignment, Remove-OERRoleAssignment New-OERResourceGroup, Get-OERResourceGroup, Set-OERResourceGroup, Remove-OERResourceGroup, Get-OERResource Azure PIM Eligibility: New-OEREligibleRoleAssignment, Get-OEREligibleRoleAssignment, Remove-OEREligibleRoleAssignment Active: New-OERActiveRoleAssignment, Get-OERActiveRoleAssignment, Remove-OERActiveRoleAssignment Activation: Enable-OEREligibleRoleAssignment, Disable-OEREligibleRoleAssignment Policy: Get-OERRoleManagementPolicy, Set-OERRoleManagementPolicy, New-OERPolicyNotificationRule Inventory and JSON orchestration Get-OERInventory reads tenant state into a round-trippable inventory object. Export-OERInventory writes that state to a bundle folder (JSON, schema, LLM prompt, README). Test-OERStructure validates a structure document offline with no tenant call. Invoke-OERStructure applies one idempotently in dependency order, with -WhatIf plan mode and opt-in child-scope -Prune. Permissions Get-OERRequiredScope reports the Graph permissions and Azure RBAC roles one or more cmdlets need. See PERMISSIONS below. Conditional Access Get-OERAuthenticationContext PERMISSIONS Ask the module rather than reading a table kept in step by hand: Get-OERRequiredScope -Cmdlet New-OERGroup Get-OERRequiredScope -Cmdlet Get-OERGroup* -Unique Get-OERRequiredScope -Unique It reads a static table, so it needs no tenant and no sign-in and answers before Connect-OER. Three things worth knowing about the answers: - The area a cmdlet belongs to is not its permission boundary. Microsoft Graph grants permissions by the resource a call touches, so one scope line covering a whole cohort is wrong by construction. The three *AdministrativeUnitScopedRole cmdlets write directory role memberships rather than unit memberships and need a role-management write permission; Get-OERGroupPimPolicy and Set-OERGroupPimPolicy hit roleManagementPolicies and need the RoleManagementPolicy permissions, not the eligibility permissions the other PIM-for-Groups cmdlets use. - Each answer is transitive. It covers the cmdlet's own calls plus every call its internal helpers make, including the directory reads behind friendly-name parameters such as -User, -Group and -ServicePrincipal. Consenting to what is listed is enough for every parameter form; the Note property says so where a permission is needed for only one of them. - The Transport property disambiguates an empty list: Graph, Arm, GraphAndArm, or None for a cmdlet that touches no tenant at all. An empty GraphScope on an ARM-only cmdlet means "none needed", never "unknown". Where an entry lists User.ReadBasic.All, Group.Read.All and Application.Read.All, a single Directory.Read.All covers all three; the narrower permissions are listed because the module prefers least privilege. Where an entry lists Directory.Read.All instead, that cmdlet reads directoryObjects, which accepts nothing narrower, so the covered permissions are deliberately NOT also listed and the Note property says which parameter makes the call. Azure cmdlets additionally need an ARM token: pass -IncludeARM to Connect-OER, or let the cmdlet acquire one. TENANT PROFILE A Tenant Profile is a PSD1 file holding per-tenant configuration, stored under <home>/.config/Omnicit.EntraRBAC/Profiles/<alias>.psd1 and managed with New-OERConfiguration, Get-OERConfiguration, Set-OERConfiguration and Remove-OERConfiguration. <home> is the current user's profile folder as .NET reports it, falling back to $HOME: the same directory as $env:USERPROFILE on Windows, and the user's home directory on Linux and macOS. Connect-OER -TenantAlias <alias> resolves the TenantId from it. Only TenantId is required; every other section is optional. @{ TenantId = '00000000-0000-0000-0000-000000000000' Naming = @{ Group = 'role_sec_{area}_{tier}' AU = '{prefix}_au_{name}' Catalog = 'CAT-{org}-{scope}' } Defaults = @{ PrimaryApprovers = @('<guid>', '<guid>') EscalationApprovers = @('<guid>') Catalog = 'CAT-IT-PRG-Core' AuthenticationContextId = 'c1' ActivationMaxHours = 8 } } Naming templates substitute {token} placeholders case-insensitively. An unresolved placeholder is an error, so a malformed name is never produced. Set-OERConfiguration preserves sections you do not supply; New-OERConfiguration is create-only and errors if the alias already exists. A profile may also carry an optional Environment key naming the sovereign cloud that tenant lives in: Global, USGov, USGovDoD or China. See SOVEREIGN CLOUDS below. LONG RUNS An access token lasts about an hour: Microsoft Entra ID gives one a default lifetime of 60 to 90 minutes, so a run longer than that outlives the token it started with. Where the sign-in type allows it, the module renews a token in two places: when a command starts within five minutes of the token's expiry, and in the middle of a command when Microsoft Graph or Azure Resource Manager rejects the token with 401, after which the rejected request is sent once more. What a renewal costs depends on the sign-in type: Interactive Every renewal opens the browser to sign in again, so a long run asks you to sign in about once an hour. The account picker lets you choose another account of the same tenant, and the command then carries on as that account: choose the account the session signed in with Device code Every renewal prints a new code to enter, about once an hour, as every device code sign-in does: see the known limitation under SWITCHING TENANTS Managed identity Renews with no prompt Client secret Not renewed, since the module never keeps the and certificate secret or certificate: once the token has expired a request is refused with AppOnlyTokenRefreshUnsatisfiable, and a command that starts within five minutes of its expiry, or later, fails to sign in with AppOnlySessionCredentialUnavailable, until you run Connect-OER with the secret or certificate again Before a long run, run Connect-OER -Force with the session's own sign-in (the same -TenantId, credential and -IncludeARM as the first Connect-OER), so the run starts with a new token. A plain Connect-OER with the same sign-in returns the session it has while its token has more than five minutes left, and a bare Connect-OER -Force signs in interactively, as a bare Connect-OER always does. Stay at the keyboard during an interactive or device code run: a managed identity is the only sign-in that renews unattended. An app-only run renews only between commands. Run Connect-OER -Force with the secret or certificate before each command, and keep each command shorter than a token's lifetime. Without -Force, Connect-OER with the same credential returns at once while the token has more than five minutes left and signs in again when it has less. A renewal nobody completes (a browser sign-in or a device code left unanswered) fails like any other sign-in: the request it was for is not sent, and the session is left uncertain, so a later command that names no tenant sends nothing, as GRAPH SDK SESSION below describes. SWITCHING TENANTS One PowerShell session works in one tenant at a time. Whether a later Connect-OER call naming a different tenant actually switches depends on the sign-in type: Client secret Reuses the credential built for the earlier tenant, so the switching sign-in fails by default -- or, with AZURE_IDENTITY_DISABLE_MULTITENANTAUTH set, requests its token from the earlier tenant, and the module refuses that token (TenantMismatch) -- until you run Connect-OER ... -Force Device code Does not send the tenant you name; its token may come from the signed-in account's own tenant when that account cannot obtain one in the tenant you name, and the module then refuses it (TenantMismatch) -- and every device code sign-in asks for a new code: see the known limitation below Managed identity Does not send the tenant you name; its token normally comes from the identity's own tenant, and the module refuses it when that is not the tenant you name (TenantMismatch) Interactive Signs in to the tenant you name Certificate Signs in to the tenant you name KNOWN LIMITATION -- every device code sign-in asks for a new code. The module makes AzAuth build a new credential for every device code sign-in, Microsoft Graph and Azure Resource Manager alike, so each one prints a new code to enter -- two with -IncludeARM, one for each token. That covers the first sign-in in a process, one naming another tenant, a new Connect-OER after Disconnect-OER, and every renewal of the token: a command that starts within five minutes of its expiry, a request whose token Microsoft Graph or Azure Resource Manager rejects, and a claims challenge. Before this release such a sign-in could reuse the credential AzAuth keeps for the PowerShell process and never return, with no code printed and no error; Connect-OER -Force is no longer needed to avoid that. A command that needs only tokens the session already holds, each with more than five minutes left, asks for no code. Each code has to be entered, so a long device code run needs someone at the keyboard. The module checks each token against the tenant you name, whether you named it by its tenant ID or by a domain, and refuses a token issued for another tenant with TenantMismatch before it is used. For a domain it first looks the tenant ID up at the cloud's Microsoft Entra ID authority: one unauthenticated request per domain and cloud, remembered for as long as the module stays imported, and asked again at the next sign-in if it fails. A domain the authority does not resolve to a tenant is refused with TenantResolutionFailed, and no token is requested. A tenant is a GUID or a verified domain: any other value is looked up the same way and refused when it names no tenant, 'common' among them. 'organizations', which the module uses when no tenant is named and there is no session, names no tenant and is not checked against one. When a sign-in under it acquires an Azure Resource Manager token, that token must come from the same tenant as the session's Microsoft Graph token, or it is refused with TenantMismatch. When only the Microsoft Graph token is renewed, the session keeps its Azure Resource Manager token only if the renewed Microsoft Graph token comes from the same tenant; otherwise it drops it and, as soon as a command needs Azure Resource Manager, acquires a new one, checked the same way. The module also warns before a client secret sign-in for the same application when the tenant you name differs from the one the credential AzAuth is currently holding for that application was built for, and no Force is on the call: you did not pass -Force, and the module did not add Force itself for a sovereign-cloud switch. AzAuth only rebuilds that credential for -Force, for a different application, or for a different kind of sign-in -- a plain retry of the same switch, with none of those, reuses the same credential and keeps warning every time, whether the earlier attempt's token came from the old tenant or was refused outright. This is the module's own view: a Get-AzToken call made outside the module does not update it, and it resets only when Omnicit.EntraRBAC itself is re-imported. The warning does not stop the sign-in, but under -WarningAction Stop (or $WarningPreference = 'Stop') it does, before any token is requested -- the same safe direction as the module's existing ambient AZURE_AUTHORITY_HOST warning. Disconnect-OER clears this module's own session state and closes the Graph SDK session the module connected; it does not clear the credential AzAuth keeps for the process. Connect-OER -Force is the supported way, inside the same PowerShell process, to move a client secret sign-in to a new tenant. Name a tenant by its tenant ID (a GUID) or by a verified domain -- a Tenant Profile's TenantId included. Both are checked, and a token issued for another tenant is refused either way; a GUID saves the lookup. # Move a client secret sign-in for the same application to # another tenant Connect-OER -TenantId '00000000-0000-0000-0000-000000000000' ` -ClientId $AppId -ClientSecret $Secret -Force GRAPH SDK SESSION Connect-OER sets up a Microsoft Graph PowerShell SDK session in the current process: it calls Connect-MgGraph with the module's token, and so does the automatic sign-in of any other OER cmdlet. Disconnect-OER closes only the session the module connected. A session it did not connect, or has no record of connecting -- one another Connect-MgGraph started, say -- is left connected, with a warning written before the confirmation, so -WhatIf shows it. Run Disconnect-MgGraph to end that one; the next OER cmdlet signs in again, and its own Connect-MgGraph replaces it, as any Connect-MgGraph does. If another Connect-MgGraph -- yours, or another tool's -- replaces the module's session in the same process, the next OER cmdlet sends nothing: it refuses its Microsoft Graph calls with GraphSessionChanged instead of sending them under that session, and its Azure Resource Manager calls with SignInRefused. An error can be reported more than once for one cmdlet. The module never switches the session back by itself. Run Connect-OER with the same sign-in the session used -- for an app-only session, its certificate or client secret, since a bare Connect-OER signs in interactively -- to connect the module again, which takes the session back, or use a new PowerShell process. If the session is closed with Disconnect-MgGraph instead of Disconnect-OER, the next OER cmdlet signs in again by itself, except on an app-only session (client secret or certificate), which reports AppOnlySessionCredentialUnavailable until Connect-OER is run with the secret or certificate. Runspaces in one process (ForEach-Object -Parallel, Start-ThreadJob) share one Graph SDK session, so a parallel fan-out across tenants in one process gets GraphSessionChanged; run each tenant in its own process instead, with Start-Job or a separate PowerShell process. A sign-in that fails or is refused usually leaves the module's session as it was -- the previous tenant's, or none -- and a script carries on past the error. From then on the module sends nothing for a command that names no tenant: such a command (no -TenantId, or -TenantId organizations) is refused with SignInRefused before it requests a token, and so are its requests and the sign-ins of the cmdlets it calls. A command that names its tenant signs in as usual. A successful sign-in by a command that names its tenant, a successful Connect-OER with or without a tenant, or Disconnect-OER makes the module send again. Before this release, when a Connect-OER in this loop failed, the Invoke-OERStructure after it applied that alias's document, -Prune included, in the previous tenant; now it sends nothing: foreach ($Alias in $Aliases) { Connect-OER -TenantAlias $Alias -ClientId $AppId -CertificatePath $Pfx Invoke-OERStructure -Path "$Alias.json" -Prune } Invoke-OERStructure without -TenantId likewise refuses every document piped to it after one whose sign-in was refused. A Connect-OER whose parameters PowerShell cannot bind never runs, so it leaves nothing behind for the module to see. An empty -TenantAlias, typed or piped, is refused with InvalidTenantAlias, and an empty, whitespace or $null -TenantId with InvalidTenantId; both count as a refused sign-in. Every other cmdlet refuses an empty or $null -TenantId while PowerShell binds its parameters, so that command never runs and sends nothing: an empty cell in a loop over tenants no longer stands for the current session's tenant. New-OERConfiguration and Set-OERConfiguration store the tenant instead of signing in to it, so they also refuse a -TenantId of white space only while PowerShell binds their parameters: a blank tenant can no longer be entered into a profile through them. A profile already on disk with one is still read as it is, and keeps it through an update that does not name -TenantId. On any other cmdlet, a -TenantId of spaces is looked up like any value that is not a tenant ID, and refused with TenantResolutionFailed when it names no tenant. One OER pipeline works in one tenant with one identity. If commands in the same pipeline sign in to different tenants or identities, a command whose sign-in another one replaced sends nothing more: every request made while it runs is refused with SignInSuperseded, including the requests of a command that handles its output, a ForEach-Object script block among them. Most OER cmdlets sign in before any command in the pipeline processes input, so that is usually the first command. A cmdlet that reports a failed lookup under an error of its own carries the refusal's message in that error instead -- New-OERGroup's GroupResolveFailed, for one. A tenant counts by the tenant its token was issued for, so naming one tenant by its GUID on one command and by its domain on another, or not naming it at all, is not refused for that alone. But a command that names the tenant differently from the session inherits nothing from it and signs in again -- interactively, when it names no credential, which on an app-only session means a browser prompt and another identity, and the pipeline is then refused as above. So name it the same way on every command, as "Name the tenant explicitly and consistently" under SOVEREIGN CLOUDS below says. Invoke-OERStructure signs in when it processes its document, not before, but without -TenantId it acts only under the session it began with: when another command in the same pipeline has signed in to a different tenant or identity by then -- any sign-in counts if the module held no session when it began -- it refuses that document with SignInSuperseded and sends nothing for it. New-OERAccessPackageApprovalStage, New-OERAccessPackageRequestorScope and New-OERAccessReviewStage do the same before they resolve the approvers, targets or reviewers they are given. Called inside a script block or a function in a pipeline, such as ForEach-Object { Invoke-OERStructure ... }, each of these commands begins only when that block runs, after every other command in the pipeline has begun and so after most of their sign-ins, and it takes the session they left for its own. A document exported by Get-OERInventory or Export-OERInventory names its tenant (tenantId, when it carries one), and Invoke-OERStructure refuses it in any other tenant with DocumentTenantMismatch, reading and writing nothing for it, whichever session it began with. A document without tenantId, and the three cmdlets above, still take that session for their own, so name -TenantId there. Run the commands as separate statements; to move objects between tenants, collect them in a variable first: # Read in one tenant, then write in another, as two statements $Filter = "startswith(displayName,'role_sec_')" $Groups = @(Get-OERGroup -TenantId $TenantA -Filter $Filter) foreach ($Group in $Groups) { New-OERGroup -TenantId $TenantB -DisplayName $Group.DisplayName } # Copy a structure the same way: read it, then apply it $Inventory = Get-OERInventory -TenantId $TenantA -Include Groups # The export names tenant A; apply it to B as a template $Inventory.PSObject.Properties.Remove('tenantId') Invoke-OERStructure -InputObject $Inventory -TenantId $TenantB -WhatIf # Not as one pipeline: New-OERGroup would run inside Get-OERGroup's # output and send nothing, and so would Invoke-OERStructure inside # Get-OERInventory's # Get-OERGroup -TenantId $TenantA ... | # ForEach-Object { New-OERGroup -TenantId $TenantB ... } # Get-OERInventory -TenantId $TenantA ... | # Invoke-OERStructure -TenantId $TenantB ... The second statement switches tenant in the same PowerShell process, so whether it reaches the tenant it names depends on the sign-in type, as SWITCHING TENANTS above describes. Invoke-OERStructure applies a document that names its tenant only in that tenant. Given a -TenantId that is another tenant ID, it refuses the document with DocumentTenantMismatch before it signs in, with no token request; otherwise, once it has signed in, it compares the document with the tenant the session's tokens were issued for. To apply an export in another tenant -- as a template, like the copy above -- change its tenantId to that tenant's ID or remove the key first. SOVEREIGN CLOUDS Connect-OER -Environment selects the sovereign cloud a session signs in to and calls. Four values are supported: Global (default) Worldwide commercial cloud graph.microsoft.com / management.azure.com USGov GCC High graph.microsoft.us / management.usgovcloudapi.net USGovDoD DoD dod-graph.microsoft.us / management.usgovcloudapi.net China 21Vianet microsoftgraph.chinacloudapi.cn / management.chinacloudapi.cn Microsoft 365 GCC uses the commercial (Global) endpoints and needs no -Environment at all. Only GCC High, DoD and a 21Vianet tenant are separate cloud boundaries; GCC itself is not. The chosen cloud becomes part of the session: every later cmdlet calls the cloud the session was established in, and switching clouds re-authenticates instead of reusing a token minted at the previous cloud's authority. Connect-OER -TenantId 'contoso.onmicrosoft.us' -Environment USGov -IncludeARM A Tenant Profile's optional Environment key stores the cloud for that tenant, so Connect-OER -TenantAlias picks it up automatically without repeating -Environment on every call; an explicit -Environment on the command line still overrides it. Name the tenant explicitly and consistently. The session is keyed on the tenant exactly as you spell it, so a later call naming a tenant the session was not established for inherits nothing from it -- including the same tenant written as a GUID one time and as a domain the next, and a Connect-OER -Environment USGov with no -TenantId at all, which records the tenant as 'organizations' rather than as yours. The cloud then resets to Global and that call signs in at the commercial authority. Its token and its endpoints stay consistent with each other, so nothing crosses a cloud boundary, but the sign-in fails at the authority with an error that names the tenant and never the cloud, which makes it easy to misread. (A tenant named by domain is looked up at that same authority first, and may be refused there instead, with TenantResolutionFailed, whose message does mention -Environment.) Pass the same -TenantId spelling on every call, or use a Tenant Profile whose Environment key carries the cloud for you. Known limitation: PIM-for-Groups is pinned to the Microsoft Graph beta endpoint (see PERMISSIONS above), and beta endpoint availability in US Government and China clouds is not established. A sovereign tenant that needs PIM-for-Groups may find that pinned beta path behaves differently, or not at all, from the commercial cloud this module is built and tested against. AZURE RESOURCE TARGETING Azure RBAC and PIM assignment cmdlets (New/Get-OERRoleAssignment, New/Get/Remove-OEREligibleRoleAssignment, New/Get/Remove-OERActiveRoleAssignment, Enable/Disable-OEREligibleRoleAssignment) accept a resource scope in three forms: - Raw scope id: -Scope '<full ARM resource id>' - Friendly decomposition: -Subscription <s> -ResourceGroup <rg> -ResourceType <type> -ResourceName <name> - Management group: -ManagementGroup <name-or-id> Get-OERRoleManagementPolicy and Set-OERRoleManagementPolicy accept the same scope forms minus -ResourceType and -ResourceName, because a role management policy is assigned at a management group, subscription or resource group scope. By contrast Set-OERRoleAssignment and Remove-OERRoleAssignment take -Id only: they act on one existing assignment, which already carries its own scope. Get-OERResource lists Azure resources within a subscription or resource group (client-side -Name/-ResourceType filters) and, with -IncludeRoleAssignments (optionally -ResolveNames), reports the role-assignment delegations on each resource. Output is tagged Omnicit.EntraRBAC.Resource and binds directly into the assignment cmdlets by property name -- for example: Get-OERResource -Subscription Prod -ResourceGroup rg-app -Name stgfoo | New-OERRoleAssignment -Role Reader -Group role_sec_readers ARM OBJECT IDS Get-OERResource, Get-OERResourceGroup, Get-OERSubscription and Get-OERManagementGroup expose the full ARM resource path as ResourceId, never as a bare Id. This is deliberate: -PolicyId on Get-OERRoleManagementPolicy/Set-OERRoleManagementPolicy and -RoleEligibilityScheduleId on Enable-OEREligibleRoleAssignment both carry an Id alias and bind from the pipeline by property name, so a bare Id on an ARM-resource object would silently mis-route as a policy id or an eligibility schedule id instead of the resource path it actually is. Get-OERRoleDefinition follows the same no-bare-Id rule but stores the ARM role definition path the other way round: RoleDefinitionId is the one STORED value (so a piped role definition still binds -Role on New-OERRoleAssignment, and its PIM siblings, through that alias), and ResourceId is exposed as an AliasProperty of it rather than a second stored copy. Either property reads the same value; there is deliberately no bare Id that could mis-bind downstream. Role-ASSIGNMENT-shaped objects (RoleAssignment, EligibleRoleAssignment, ActiveRoleAssignment) are a different case: they identify an assignment INSTANCE, not an ARM resource, so their own domain-specific id (RoleAssignmentId, RoleEligibilityScheduleId, RoleAssignmentScheduleId) is the right property to carry, not ResourceId. Set-OERRoleAssignment and Remove-OERRoleAssignment act on -Id alone (the RoleAssignmentId, which already embeds the scope) rather than on the friendly -Subscription/-ResourceGroup/-ManagementGroup decomposition above. ACCESS PACKAGE ASSIGNMENT POLICY -- GRANULAR SETTINGS New-OERAccessPackageRequestorSettings builds a requestor-settings object that controls how users and managers may submit requests: $Rs = New-OERAccessPackageRequestorSettings -AllowSelfRequest ` -AllowManagerRequest -ManagerLevel 1 -AllowSelfExtend All -Allow* parameters are switches. Pass the result to New-OERAccessPackageAssignmentPolicy or Set-OERAccessPackageAssignmentPolicy via -RequestorSettings. Both policy cmdlets also accept (the toggles are switches): -RequireApproval -- enable the approval workflow -RequireRequestorJustification -- require a justification from the requestor -RequireApprovalForUpdate -- require approval for extension requests -DurationInHours <int> -- assignment lifetime in hours (PT{n}H) -DisableAssignmentNotifications -- suppress built-in assignment emails New-OERAccessPackageApprovalStage accepts -ApproverInfoVisibility (Default / Visible / NotVisible) to control whether approver identity is shown to the requestor. Set-OERAccessPackageAssignmentPolicy is a read-modify-write (GET then PUT): fields you do not supply are preserved from the live policy (including the out-of-scope reviewSettings and custom questions). On both New- and Set-OERAccessPackageAssignmentPolicy the Description parameter defaults to the policy display name when omitted. Limitations: policy-embedded access reviews (reviewSettings) and custom requestor questions cannot be expressed in an apply document -- they are preserved by the read-modify-write. Fallback approvers are preserved when their stage is unchanged but are dropped when the stage is rebuilt. DURATION VOCABULARY Most cmdlets that accept a lifetime or expiration share the same duration vocabulary. (A few narrowly-scoped parameters, such as Set-OERRoleManagementPolicy -ActivationMaxHours and New-OERAccessPackageApprovalStage -EscalationDays, keep their own already-unit-specific name instead.) -Duration <string> -- raw ISO 8601 duration (P365D, PT8H) -DurationDays <int> -- whole days, converted to P{n}D -DurationHours <int> -- whole hours, converted to PT{n}H -DurationInDays / -DurationInHours (access packages, access reviews) and -EligibleDuration / -ActiveDuration (PIM policies) are the canonical parameter names; -DurationDays / -DurationHours and -EligibleDurationDays / -ActiveDurationDays are the module-standard aliases this unification added. Exception: Add-OERGroupEligibility -Duration also accepts a bare whole day count (-Duration 365 means 365 days) for back-compat with its original shipped signature. This is deliberate and scoped to that one cmdlet -- New-OEREligibleRoleAssignment -Duration, by contrast, validates strictly against the ISO 8601 pattern and rejects a bare number such as 365 outright. SEE ALSO Connect-OER Get-OERConfiguration Get-OERGroup Get-OERAdministrativeUnit Get-OERAccessPackage Get-OERAccessReviewDefinition Get-OERRoleAssignment Get-OEREligibleRoleAssignment Get-OERRoleManagementPolicy Get-OERInventory Invoke-OERStructure |