Field notes · powershell

Role-based access without an IGA platform

A 7-stage request, approval, mapping, enforcement, and reconciliation loop gave my small IT shop consistent role access without claiming full IGA coverage.

·7 min read

I needed consistent role-based access, but I did not have an identity governance and administration platform to model every entitlement and approval. I built a narrower control loop around structured requests, a reviewed role map, PowerShell enforcement, explicit exceptions, and reconciliation reports.

It made ordinary onboarding repeatable without claiming capabilities it does not have. There is no access-certification campaign, privileged access manager, or automatic policy discovery hiding behind the script.

Key takeaways

  • Treat the role map as reviewed policy input, not discovered truth.
  • Keep request, approval, enforcement, and verification as separate stages.
  • Use stable role identifiers rather than display names as automation keys.
  • Preflight users, managers, groups, and naming collisions before mutation.
  • Make reruns idempotent and report partial success.
  • Keep exceptions visible, owned, and time-bounded where possible.
  • Do not automate access removals until the exact change set is reviewed.

What problem was I actually trying to solve?

The problem was ordinary access consistency. A person starting in the same job as the last person should not receive a different set of baseline groups because I remembered a different checklist that morning. Managers should approve the role, and the run should show what succeeded, failed, or still needs manual work.

My onboarding workflow already had structured intake and a human approval gate. The missing piece was a durable translation from an approved organizational role to technical targets such as an Active Directory OU, security groups, a mail group, and a small set of application actions.

Before the map, those decisions lived in old tickets and in my head. A mail-group failure might not stop the account from being created, which meant the run looked mostly successful while the person quietly missed a routine communication list. The automation needed to report the whole role outcome, not just the first account object.

This builds on the onboarding intake form, where I made structured, validated inputs more important than clever provisioning code. It also fits the HR-driven trigger in HR-Driven Provisioning: the system of record supplies the event, a manager supplies business approval, and IT supplies the technical policy map.

What does the seven-stage control loop look like?

The loop moves an approved role request through validation, mapping, preview, enforcement, result capture, and later reconciliation. Each stage produces evidence the next stage can inspect instead of allowing one large script to decide everything implicitly.

1. Request       -> structured hire or change data
2. Approval      -> manager and IT approve the role
3. Mapping       -> stable role ID resolves to technical targets
4. Preflight     -> validate identity, manager, groups, and collisions
5. Preview       -> show the exact proposed changes
6. Enforcement   -> apply approved idempotent operations
7. Reconciliation-> compare expected and actual access later

The separation matters when something fails. An unknown role is a request or mapping problem. A missing directory group is a preflight problem. A transient cloud error is an enforcement problem. A person who drifted months later is a reconciliation problem.

I do not let the script invent a substitute when a role key is unknown or a target group is missing. It stops before account mutation. That fail-closed behavior makes the map’s gaps visible while they are still cheap to fix.

How does a role become technical access?

Each stable role identifier resolves to a reviewed record containing baseline attributes and groups. The record can express no groups at all for a special case, but it cannot silently fall back to another role just because the display names look similar.

A fictional example looks like this:

@{
    Roles = @{
        'parks-field' = @{
            DisplayName    = 'Parks Field Staff'
            DepartmentCode = 'PARKS'
            TargetOU       = 'OU=Field Staff,OU=Users,DC=example,DC=gov'
            SecurityGroups = @(
                'APP-WorkOrders-User'
                'FILE-Parks-Modify'
            )
            MailGroup      = 'MAIL-Parks'
            Note           = $null
        }
    }
}

The names are synthetic, but the shape comes from the map I use in practice. One entry becomes the source for account placement, department attributes, security groups, and the default mail group. That removes repeated group lists from the provisioning and recovery scripts.

The map is not absolute truth. A department owner and IT still have to decide what the baseline should be. The file only makes that decision inspectable, testable, and repeatable once approved.

A later article in this cluster covers the matrix schema and validation rules in detail.

Where should human approval remain?

Keep human approval at the business-role boundary and at exceptional or high-impact access. Automation can prove a request is internally consistent; it cannot prove that a person should hold a sensitive entitlement merely because a field was populated.

My normal path is:

  • The manager selects the organizational role and start information.
  • The intake form validates required fields and known role values.
  • IT reviews the proposed identity and baseline access.
  • The script previews exact technical changes.
  • An operator authorizes the enforcement run.

Special access remains a separate request. I do not put privileged administrator groups, emergency access, or broad financial permissions into an ordinary role bundle for convenience.

Microsoft’s best practices for Microsoft Entra roles describe least privilege in terms of the needed permissions, scope, and time. The same discipline applies even when the local implementation is a small PowerShell workflow rather than an Entra role assignment.

How does enforcement stay safe to rerun?

Each operation checks current state before changing it. An existing group membership is reported and skipped, an existing account collision stops the new-account path, and a partial-recovery script can complete only the missing tail. The output distinguishes success, skip, warning, and failure.

PowerShell’s Active Directory cmdlets already support useful safety behavior. Microsoft documents that Add-ADGroupMember supports -WhatIf, and permissive modification normally suppresses the already-a-member error. I still check membership explicitly because the preview and result should explain why no change was needed.

A safe pattern is:

[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory = $true)]
    [string]$SamAccountName,

    [Parameter(Mandatory = $true)]
    [string]$RoleId
)

$ErrorActionPreference = 'Stop'

if ($PSCmdlet.ShouldProcess($SamAccountName, "apply role $RoleId")) {
    # Call validated, idempotent operations here.
}

Microsoft’s ShouldProcess guidance explains how SupportsShouldProcess adds -WhatIf and -Confirm. I pass that intent explicitly through nested functions rather than assuming every called operation inherits it correctly.

Cloud and on-premises operations also have different failure modes. Directory group work may succeed while a cloud service is still waiting on identity replication. I record a step ledger and return partial success with actionable warnings instead of reporting the entire person as either perfect or absent.

What is this approach not able to replace?

It is not a full IGA platform. It provides a reviewed baseline and operational evidence for a bounded set of systems. It does not automatically discover every entitlement, certify access with every owner, manage privileged elevation, model separation-of-duties conflicts, or prove regulatory compliance.

Those limits should be written next to the success claims:

Capability This workflow Full governance platform
Baseline role mapping Yes Yes
Structured approval Yes, for covered requests Yes, with broader workflows
PowerShell enforcement Yes, for integrated systems Connector dependent
Exception tracking Basic and explicit Usually lifecycle managed
Access certification campaigns No Common capability
Privileged just-in-time access No Separate PAM or PIM capability
Enterprise entitlement discovery No Common capability
Separation-of-duties policy Manual Often modeled and enforced

Microsoft’s Entra RBAC overview describes built-in and custom roles, group assignment, and scoped administration inside Entra. My matrix may drive ordinary group membership, but that should not be confused with the complete Entra RBAC and governance feature set.

How do exceptions avoid becoming permanent shadow roles?

Record the requested entitlement, approver, reason, owner, date, and review or expiration condition separately from the baseline role. Reconciliation should label the difference as an approved exception rather than teaching the role map to accept every one-off membership it sees.

An exception record should answer:

  • Who approved it?
  • What exact group or application right was granted?
  • Why is it outside the baseline role?
  • When should it be reviewed or removed?
  • Which system contains the real entitlement?

The map must not learn from current state automatically. Existing access can be wrong. Turning every observed membership into policy would institutionalize the drift the reconciliation process is meant to find.

That is also why removals remain more guarded than additions. An omitted group might reflect a stale map, nested access, an emergency duty, or a system owner decision not yet recorded. I generate the expected-versus-actual difference and require review before destructive enforcement.

Frequently asked questions

This pattern is a practical control for a small organization, provided its scope and omissions remain visible.

Is a department the same thing as a role?

No. A department may contain several jobs with different baseline access, and one role can share common access across departments. Use stable identifiers that match real responsibility rather than relying on one organizational field.

Should the role map include individual usernames?

No. Put baseline policy in the role map and individual deviations in an exception record. Usernames in the map turn reusable policy into a hidden access list.

Can this workflow manage every application?

Only applications with a safe integration path should be enforced directly. For the rest, create a tracked manual task and verify its completion, as I do in Account Provisioning in Systems Never Built for You.