← Back to insights

Enterprise IT · 19 August 2026 · 5 min

Script debt

The PowerShell folder, the app registrations, and the secrets nobody owns

Every IT department that has run Microsoft 365 for more than three years has one. It is usually called fileserverITScripts, or it sits in C:Users<firstname>DocumentsWindowsPowerShell on the machine belonging to the person who knows the most. It contains between twenty and two hundred .ps1 files. Nobody knows which ones are still running.

This is not sloppiness. It is the rational outcome of Entra ID lacking a delegation layer: when the portal won't let you hand off a narrowly scoped task, you write a script that does it, and you run it yourself.

How the debt accumulates

The life cycle is predictable:

1. An administrator writes a script for a repetitive task — create a user with the right groups, clean up licenses, generate a report.

2. The script is run interactively with the administrator's own privileges. It works.

3. Someone asks for it to run automatically overnight. Interactive sign-in won't do, so an app registration with a client secret is created.

4. The permissions are set broadly, because that is faster than finding the minimal set — typically Directory.ReadWrite.All, User.ReadWrite.All, Group.ReadWrite.All as application permissions, with admin consent.

5. The script is set up as a scheduled task on a server, or in an Azure Automation account.

6. The administrator changes jobs.

Now you have a service identity with write access to the entire directory, running code nobody has read in two years, authenticated with a secret sitting in clear text in the script file or in a variable someone set once.

Why application permissions are the dangerous part

The distinction between delegated permissions and application permissions is the technical crux, and it is under-communicated.

Delegated permissions are exercised on behalf of a signed-in user. The effective access is the intersection of the app's permission and the user's own role — an app with User.ReadWrite.All delegated cannot do anything the user could not have done themselves.

Application permissions have no user. The service principal acts in its own name, with no role intersection, with no Conditional Access policy that hits a user, with no MFA. An app registration with RoleManagement.ReadWrite.Directory can assign itself Global Administrator. It runs just as well at 03:00 from a compromised build server as from your own console.

That is why "we have removed all permanent administrator roles" is often a half-truth. The roles are gone. The service principals that do the same work remain — and they are rarely included in the same review.

Secrets that expire at the worst possible moment

Client secrets have an expiry date set at creation. There are two outcomes, and both are bad:

  • The secret expires, the nightly job stops, and the failure is discovered only when someone wonders why new employees haven't been given a license for three weeks.
  • The secret is renewed in a panic with the longest possible lifetime, without the permission scope ever being reassessed.

Certificate-based authentication and managed identities solve the secret problem technically, but not the governance problem: who approved that this identity should be able to write to the directory, and where is that written down?

The log that would have exposed it has to be bought separately

The Entra audit log shows that a change happened and which service principal made it. It does not show which code called, with which parameter, from which host. Microsoft Graph Activity Logs do — logging every HTTP request Graph receives for the tenant — but they require P1 or P2, a Security Administrator to configure diagnostic settings, an Azure subscription, and they have no retention in the portal. They do not exist unless you actively route them to Log Analytics, a storage account or Event Hubs, and pay for the volume: around 14 GiB per month at 1,000 users.

Very few mid-sized Norwegian companies have this turned on. That means the API calls that change their directory are, in practice, not logged beyond 30 days of coarse-grained audit information.

The arithmetic nobody does

Script debt looks free because the work has already been done. The real cost has four line items:

  • Key-person risk. The scripts can be read by one person. If that person is on sick leave during a reorganization, the process stops.
  • Attack surface. Every app registration with broad application permissions is a permanent back door that Conditional Access does not cover.
  • Unverifiability. You cannot show an auditor what the script did in March, only that something happened.
  • Duplication. In a group with several tenants, the same script exists in five variants, which have diverged.

What this means for Entra Logic

Entra Logic's execution engine is built on exactly the technology IT already trusts — PowerShell against Microsoft Graph. It is not a proprietary scripting language administrators have to learn. The difference is where the code lives and what triggers it.

Instead of n administrators each having their own local copy and their own app registration, execution runs centrally, from one versioned code base, triggered by an approved order — a request that carries its own approval and execution — not by a scheduled task nobody owns. The service identity is one, its permissions are in one place, and every run has an approver and a justification attached to it.

This is also where the multi-tenant architecture delivers a concrete gain: the same operation, the same code, against 58 companies — instead of 58 diverging variants of the same script.

A necessary caveat: this replaces Entra Logic's own executions and the operations that are entered into the portal. Existing local scripts in a customer's environment do not disappear on their own — they have to be mapped and decommissioned as a separate piece of work. Any presentation that suggests otherwise will not survive the first technical meeting.

Sources

RELEVANT SOLUTION

See how this is handled in practice:

Norwegian version: Les artikkelen på norsk