☁️ Cloud-First. Then We Went Back for the On-Prem Part.
Most organisations start on-premises and spend years clawing their way to the cloud. SCEAI — the Supreme Council for Economic Affairs and Investment, Qatar — did it the other way around. We were cloud-first from day one: Entra ID, Microsoft 365, the works. Now, in partnership with Ooredoo, we're building the on-prem layer underneath a cloud environment that already exists. Same hybrid destination as everyone else. Just approached from the opposite direction.
It was a busy stretch. We were deep in the on-prem infrastructure implementation — servers, domain design, the usual groundwork — when a question landed in my head that had absolutely nothing to do with what I was working on that day.
How do we actually manage our users? And who's going to do it?
📋 Today: Simple. Tomorrow: Not My Problem — Except It Is.
Today, the situation is genuinely simple. IT support handles the entire user story through Entra, and licenses get assigned from the Microsoft 365 admin portal. Clean, contained, nobody needs a sysadmin in the loop.
But that changes the moment on-prem AD goes live. In a proper hybrid Microsoft environment, the source of truth shifts. Once AD exists, a user has to be created there first and synced back to Entra — that's just how hybrid identity works. So if someone needs a new user account tomorrow, after the on-prem rollout, that request lands on AD. And AD is sysadmin territory. Which means it lands on me.
Not just account creation, either. Trace the full lifecycle and it's mine end to end: create the user in AD, trigger an Entra Connect delta sync if they need to exist in Entra immediately, verify the sync actually landed correctly, then hop over to the Microsoft 365 admin portal to assign licenses. After all that, IT support would still manually write the new user's credentials on a sticky note and hand it over.
🔗 Ten Steps. Five Teams. One Sticky Note.
I mapped out what the "tomorrow" version of this actually looked like without any automation, and it wasn't pretty. HR emails IT with the new hire's details. IT logs into a jump server to touch AD. IT connects to a separate Entra Connect server to trigger — or wait for — the delta sync. Someone manually checks Entra to confirm the user landed correctly. Someone else writes custom security attributes by hand. The Exchange team creates the mailbox. The infrastructure team builds the home folder and sets NTFS permissions. The licensing team assigns Microsoft 365 licenses. Only after all of that does the user get access — and their credentials arrive on a handwritten sticky note.
Five teams. Multiple manual handoffs. No single audit trail. Credentials handed over on paper, which is a security problem every compliance framework will flag the moment anyone looks closely. This wasn't a "someone will fix it eventually" situation. This was the actual plan for how a government entity would manage identities going forward, and it was already showing every symptom of falling apart before it even went live.
🎪 I Didn't Want This Workload. So I Proposed Getting Rid of It.
I sat with that chain for a while, and the honest reaction underneath the frustration was: I don't want to carry this. Not because it's beneath a sysadmin — plenty of sysadmin work is unglamorous — but because it's recurring, high-frequency, low-value repetition layered on top of a job that's already full. If I absorbed it as-is, I'd spend more time being a human relay between AD, Entra, and a licensing portal than doing actual infrastructure work.
So instead of accepting the workload, I proposed removing the need for it. A centralised user management portal — one place to create and manage users, with the entire lifecycle automated behind a single "Approve" click. I did the research, sketched an architecture that solved our specific hybrid problem, and built a quick browser-only HTML demo — no backend, everything stored client-side — just to show management what the experience could look like.
🧠 A Policy Engine That Decides. A Job Engine That Executes. Never the Same Thing.
Before writing anything meaningful, I spent real time on the architecture — partly because the Kural at the top of this post isn't decoration, it's a habit I actually try to keep. The design that came out of that thinking had one rule at its centre: the component that decides what should happen must never be the component that makes it happen.
In SIMP, that split has names. A Policy Engine reads every request, checks it against configurable approval, validation, and execution policies, and returns a decision — approve automatically, route for human approval, or reject. It touches nothing outside its own database. It doesn't create users, doesn't call Graph, doesn't send email. It only decides. A separate Job Engine — its own standalone process, no shared runtime state with the API — polls for approved requests and actually executes the plan: create the AD account, trigger the sync, assign the license, build the home folder, fire the notifications, with retries and full logging if any step fails along the way.
Around that core sits everything else: a REST API layer handling requests, approvals, and reporting; Entra ID SSO with Five app roles (HR, IT Admin, Approver, System Admin, Auditor — assigned directly, no group nesting to untangle later); two SQLite databases, one for policy configuration and one for operational job state; and full audit logging on every single action, immutable, with its own retention policy.
None of this was theoretical box-drawing for its own sake. Every one of these boundaries came from a specific worry: what happens when this system needs to change six months from now, and I'm the only one who understands why it was built this way? Clean separation was the answer I could actually defend to a future version of myself.
😴 Four to Five Hours of Sleep. Repeat for Thirty Days.
Design is the easy part to talk about after the fact. Living through the build was a different experience entirely. A month of consistently short nights, constant mental context-switching between PowerShell modules, SQLite schemas, and Graph API quirks, and more than one evening where I genuinely questioned why I'd volunteered for this. I'm not a developer. I've said that in every post on this blog, and I'll say it again here, because it stayed true the whole way through — even with AI tools sitting right next to me the entire time.
What kept it moving wasn't stubbornness alone. It was that every time I hit a wall — a Graph API call that behaved differently than documented, a race condition in how the runspace pool handled shared state, a hashtable that didn't enumerate the way I expected — I could actually trace it back to something I understood: the infrastructure underneath. That's the part six years of sysadmin work gives you that a tutorial never will.
⚙️ Fill the Form. Click Approve. Sit Down.
Here's the version that shipped. HR — or IT, depending on the request type — fills in a new user's details and submits. An approver reviews and clicks Approve. Everything after that is automatic:
The exact same automation covers existing users too — license changes, department moves, security attribute updates, deactivations. One portal handles the entire identity lifecycle, not just the "create" half of it. A system admin no longer needs to be personally involved in day-to-day user management at all; IT support runs it all from SIMP, submits or approves, and the portal takes care of the rest.
🔍 We Sent It to the Red Team Anyway.
I'll say the quiet part out loud: this is, by most definitions, a vibe-coded application, built by someone without a formal development background. That's a fair thing to be skeptical about, and I don't push back on the skepticism itself. What I do push back on is the assumption that "AI-assisted" means "untested" or "unreviewed." It doesn't — not if you treat it seriously, and I did. A proper design phase, deliberate module-by-module testing, and hours of debugging went into this before it touched anything resembling a real user account. AI tools accelerate the writing. They don't replace the engineering discipline around it.
To make sure that wasn't just my own confidence talking, I submitted SIMP for an internal security review — even though it's an internal-only application — because our internal cybersecurity and red team exist for exactly this reason, and skipping that step would have defeated the entire point of building something properly.
Sixteen findings on a first pass isn't a badge of shame for internally-built software — it's what a real review looks like when it's done properly instead of rubber-stamped. What matters is what happened next: every finding got fixed, the re-assessment confirmed it, and SIMP went into production with a documented, closed-out security review behind it. That's the difference between "vibe-coded" as a dismissal and "vibe-coded, then engineered properly" as an actual process.
💬 The Job Description Was Never the Ceiling
I went into on-prem infrastructure work and came out having designed a policy engine, an approval workflow, and a REST API that now runs identity management for an entire government entity. None of that was in any version of my job description. All of it turned out to be something I could actually do, once I stopped treating "that's not my job" as a real boundary and started treating it as a decision I was making by default.
The AI tools mattered — genuinely. But they weren't the reason this worked. I brought the part that actually can't be outsourced: knowing how hybrid AD/Entra sync behaves under real conditions, what a government entity's approval chain needs to look like, and where the failure modes in a system like this actually hide. The tools brought syntax and speed. I brought the judgment about what to build and why. That combination is the whole story.
Don't let your job title decide what you're capable of building. I'm a sysadmin. I designed a decision engine, built a REST API, wired up an approval workflow, and shipped a production identity portal for a government entity. None of that is in a classic sysadmin job description. All of it turned out to be exactly the kind of problem a sysadmin is positioned to solve — because we're usually the ones who actually understand how the pieces fit together underneath.
"Somewhere between the fourth SQLite migration and the fifth Graph API rate limit, I stopped asking whether I was allowed to build this — and started asking whether it worked yet."
💬 Discussion
Built something similar? Fighting your own AD-to-Entra sync right now? Drop it below.