4 minute read

A PowerShell script can work perfectly for someone else and still need changes before it works for you. Different tenants, permissions, cloud endpoints, and module versions can change the result.

When I adapt a script, I want those assumptions out in the open. Which environment will it connect to? Which tenant? What access does it request? Those questions should have answers before the script starts doing its work.

Choose the Environment Before Connecting

Microsoft Entra ID administration through Microsoft Graph is a useful example. Signing in with a government account doesn’t automatically change a script’s configured Graph endpoint.

This example offers three choices:

Environment Graph PowerShell environment
Commercial and Microsoft 365 GCC Global
GCC High USGov
DoD USGovDoD

GCC uses the global Graph endpoint; GCC High uses USGov. Additional environments are available for various sovereign clouds. This example focuses on the three above.

Start With an Explicit Connection

Install the authentication module once if it isn’t already available:

Install-Module Microsoft.Graph.Authentication -Scope CurrentUser

Run the following example in a dedicated PowerShell session. It prompts for the cloud and tenant, connects, displays the connection details, and disconnects. It doesn’t change directory objects.

# Select the environment before connecting.
$environments = @(
    @{ Label = 'Commercial / Microsoft 365 GCC'; Name = 'Global' }
    @{ Label = 'GCC High'; Name = 'USGov' }
    @{ Label = 'DoD'; Name = 'USGovDoD' }
)

Write-Host "`nSelect your Microsoft Entra environment:"

for ($i = 0; $i -lt $environments.Count; $i++) {
    Write-Host ('{0}. {1}' -f ($i + 1), $environments[$i].Label)
}

do {
    $choice = Read-Host 'Enter a number from 1 to 3'
} until ($choice -match '^[1-3]$')

$selected = $environments[[int]$choice - 1]

Import-Module Microsoft.Graph.Authentication -ErrorAction Stop

# Explicitly identify the tenant, not just the cloud.
do {
    $tenantId = (Read-Host 'Enter the tenant ID (GUID)').Trim()
    $validTenantId = $tenantId -match '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$'
} until ($validTenantId)

# Request ALL scopes needed by your tasks in this one list.
# User.Read is sufficient for this connection-only example.
$scopes = @('User.Read')

$connection = @{
    Environment  = $selected.Name
    TenantId     = $tenantId
    Scopes       = $scopes
    ContextScope = 'Process'
    NoWelcome    = $true
    ErrorAction  = 'Stop'
}

try {
    Connect-MgGraph @connection

    Get-MgContext |
        Select-Object Account, TenantId, Environment, Scopes

    # Add your Graph tasks here, before the finally block.
    # Import only the additional Graph submodules those tasks need.
    # Use -ErrorAction Stop where a task failure should stop execution.
}
finally {
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
}

The environment selection controls the cloud; the tenant ID identifies the directory. Check both in the output before adding tasks. A correctly formatted tenant ID can still be the wrong tenant.

ContextScope = 'Process' limits authentication persistence to the current process. The finally block attempts to disconnect after normal execution or an error. Put the actual work inside try, before cleanup runs. Abruptly terminating the PowerShell process can prevent cleanup code from executing.

Make the Required Scopes Visible

-Scopes requests delegated permissions: access on behalf of the signed-in user. Here, $scopes is passed through the $connection hashtable. Connect-MgGraph @connection uses splatting, which passes those entries as named parameters.

This entry:

Scopes = $scopes

belongs inside that hashtable. It has the same purpose as supplying -Scopes $scopes directly on the connection command.

User.Read covers reading the signed-in user’s profile. It doesn’t authorize a directory-wide inventory or administrative changes. Before adding tasks, check their documented permissions and collect the necessary scopes in $scopes so the script requests them in its initial connection.

To look up available scopes and what each one allows, see the Microsoft Graph permissions reference, which also identifies permissions that require administrator consent.

Request the least access that supports the work. A reporting script shouldn’t request write permissions simply because another script used them. Import only the required modules as well; loading the entire Graph module isn’t necessary for a small task.

Scopes don’t bypass consent or Entra roles. Effective delegated access depends on the permissions granted to the application and the user’s access. Some operations also require specific roles, and tenant consent policies can require administrator approval.

An explicit list documents what the script requests; it isn’t proof that an existing application consent grant contains no other permissions. Review consent grants separately when assessing access.

Adapt the Script, Then Test It

An environment selector is useful, but it doesn’t make every Graph operation available in every cloud. Check API availability as well as permissions before adding a task.

For unattended automation, use a validated parameter or approved configuration instead of an interactive menu. That keeps the environment explicit without leaving a scheduled job waiting for input.

Before expanding a script’s use, test against a limited scope. Check the selected tenant, inspect the output, and understand any changes it makes. Connection success only proves the sign-in worked, not that every later command will succeed.

A few explicit choices at the start make a script easier to review, troubleshoot, and reuse. The goal is to make it fit the work and the environment you actually have.

References

Updated: