Skip to content
InvardiaInvardia
Why InvardiaProductsFeaturesPricingCommunity
Docs
All documentation
Product documentationPIM ManagerSecurityInsight
SettingsPIM Manager settingsSecurityInsight settingsEntra Policy Suite settings
ScriptsPIM Manager scriptsSecurityInsight scriptsEntra Policy Suite scriptsInvardia scripts
Error codesPIM Manager error codesSecurityInsight error codes
API and MCPPIM Manager APIPIM Manager MCP toolsSecurityInsight APISecurityInsight MCP tools
SecurityPIM Manager security packSecurityInsight security packEntra Policy Suite security pack
Network and designPIM Manager network guidesPIM Manager command wizardNetwork egressDesign your installationCalculate your cost
SupportSetup scriptsRemote support access
Search the docs
SupportAboutContactBook a demoCustomer portalDesign your installationFree Pro trial

PIM Manager guides

  1. PIM Manager on a private network: your options for DNS, routing, segmentation and policy
  2. Install PIM Manager in private (internal) mode from scratch
  3. Convert a public (external) PIM Manager environment to private (internal)
  4. Give PIM Manager your own address (custom host name)
  5. Deploy the PIM Manager hybrid worker (PIM for Active Directory) in Azure
  6. Deploy the PIM Manager hybrid worker (PIM for Active Directory) on your own server with Azure Arc

Command wizard

Deploy the PIM Manager hybrid worker (PIM for Active Directory) in Azure

Written by the PIM Manager team for PIM Manager 2.4.576 · 2026-10-11

The hybrid worker brings PIM Manager’s just-in-time model to your on-premises Active Directory. When a person activates a PIM group in Entra ID, the worker puts their AD admin account into the matching AD group for as long as the activation lasts, and takes it out again afterwards. It can also create and maintain the AD admin accounts, mirror PIM groups to AD groups, and make per-server groups local administrators on your servers.

This guide builds the worker as an Azure VM in its own virtual network, peered to the network where your domain controllers are reachable. For a worker on your own on-premises server with Azure Arc, see Hybrid worker with Azure Arc.

Edition: PIM for Active Directory is a Pro feature (On-premises Active Directory). The worker’s jobs do nothing without a Pro licence on the environment.


What you get

The machine One Windows Server 2022 Azure Edition Core VM (default size Standard_B2s, 4 GiB), Trusted Launch (Secure Boot + vTPM), platform patching with hotpatch, in its own resource group and VNet.
Network No public IP on the VM, ever. The subnet’s NSG denies all inbound traffic, including from peered networks. Outbound traffic (Microsoft Graph, the PIM database, the update source, Windows Update) leaves through a NAT gateway on the subnet, and Azure’s implicit default outbound access is switched off.
Management Azure run-command only (an outbound agent channel). No RDP, no WinRM, nothing listening. The local administrator password is generated, used once at creation and discarded; break-glass is the portal’s Reset password.
Cloud identity The VM’s system-assigned managed identity reaches the PIM database (read/write rows, never schema), Microsoft Graph (read only) and the PIM source store (read only, for self-update). No SAS, no PAT, no client secret, no certificate on the machine.
AD identity Active Directory is written only by group managed service accounts (gMSAs), one per privilege tier. Their passwords are managed by AD; nothing is stored on the server.
Isolation switch One command cuts the worker’s network reach (and restores it) without deleting anything.
Self-update A daily task moves the worker to the version your environment’s update ring approves. It never moves backwards and never moves without a ring answer.

The security model in one paragraph

The worker can change membership of your Tier-0 groups, so treat it as a Tier-0 machine, like a domain controller. That is why it has no public IP, accepts no inbound connections, is managed only through Azure run-command, and holds no password, secret or certificate. Restrict who holds Virtual Machine Contributor (or any right that allows run-command) on its resource group to your Tier-0 administrators: run-command executes as SYSTEM on the worker.

Never put a worker identity (its gMSAs, their PermissionGroups, or the worker computer account) into a PIM just-in-time group. The members of those groups are the people with an active elevation, and the membership sync removes anyone else. The worker would remove itself, or worse, keep standing access that PIM exists to prevent. The AD setup script never does this.

Modules and tools

  • Azure side: REST only. Every Azure step in this guide calls Azure Resource Manager, Microsoft Graph and Azure SQL over REST with your browser sign-in (authorization code with PKCE, never device code) or an Invardia support session. No Azure CLI, no Az or Microsoft Graph PowerShell modules.
  • AD side: LDAP through .NET. The AD setup script uses no ActiveDirectory PowerShell module. It uses dsacls.exe for OU rights and djoin.exe for the offline domain join. The only PowerShell modules used are the two the product owner approved for the AD / hybrid worker side:
    • the Windows GroupPolicy module, only to create the servers GPO the first time it does not exist;
    • Add-KdsRootKey (the in-box Kds module), only when you ask the script to create a missing KDS root key (-CreateKdsRootKey).
  • On the worker: the installer uses the in-box Windows scheduled-task cmdlets (Register-ScheduledTask and related) to create its tasks, and installs PowerShell 7 from Microsoft’s signed MSI. No other module is installed.

Who runs what

Step Who Where Rights needed
1. Infrastructure (VM, VNet, NSG, NAT gateway, peering, SQL VNet rule) Azure administrator Any Windows machine with the PIM Manager release package Contributor on the worker’s subscription; Network Contributor on every VNet you peer to (the script creates both sides); Contributor on the PIM SQL server (VNet rule)
2. Peering check / isolation switch Azure administrator Same Network Contributor on both sides
3. Outbound path (NAT gateway) Azure administrator Same (normally run for you by step 1) Network Contributor on the worker’s resource group
4. Active Directory preparation AD administrator A domain controller, or a domain-joined host with the AD DS tools (dsacls.exe) and the Group Policy Management tools Domain Admins. Creating a KDS root key needs Enterprise Admins (or Domain Admins of the forest root domain).
5. Domain join of the worker Azure administrator (runs it), with the blob from the AD administrator Azure portal, run-command on the worker VM Virtual Machine Contributor (run-command) on the worker
6. Cloud access grants Azure / Entra administrator Windows PowerShell 5.1 or PowerShell 7 on a machine with the release package Privileged Role Administrator (Graph application permissions); Owner or User Access Administrator on the source storage container; a SQL Entra administrator of the PIM database
7. Install and configure the worker Azure administrator Azure portal, run-command on the worker VM Virtual Machine Contributor (run-command) on the worker
8. Naming and switch-on PIM SuperAdmin PIM Manager, Settings > Hybrid AD & workloads > Hybrid Active Directory SuperAdmin in PIM Manager

Where the scripts come from. All six scripts are in the PIM Manager release package under .\tools\pim-hybrid-worker\ (Pro / trial: the signed package Install-Invardia downloaded for your installation). The Azure-side scripts (New-PimHybridWorkerInfra, Set-PimHybridWorkerEgress, Set-PimHybridWorkerPeering, Grant-PimHybridWorkerAccess) load the engine’s REST libraries from the package, so run them from the package folder; they do not work as single downloaded files. Initialize-PimHybridWorkerAd.ps1 and Install-PimHybridWorker.ps1 run on their own machine (the domain controller, the worker): use the published single files from https://invardia.com/support/pim/ (each is one self-contained file), or run them from the package folder – copying the package’s copy alone no longer works since the next release after 2.4.580 (it loads a shared helper from the package). From that release, Deploy-PimHybridWorker.ps1 -Target Azure runs the whole sequence and delivers the worker install through Azure run-command for you.

Every script supports -WhatIf (prints the plan, changes nothing) except Install-PimHybridWorker.ps1, which has -PlanOnly instead (see step 7). Every script is read-before-write and idempotent: if a step fails, fix the cause and run the same command again.


Step 0: Decide and collect

Decision Your value (example) Parameter (script)
Tenant and subscription for the worker <tenant-id> / <subscription-id> -TenantId -SubscriptionId (all Azure scripts)
Region: the customer decides; usually the PIM environment’s region westeurope -Location (infra; default westeurope)
Resource group rg-pim-hybrid -ResourceGroup (pass it to every Azure script: the default is rg-pimhybridwrk)
VM name (max 15 characters; it is also the AD computer name) PIMHYBRIDWRK (the default) -VmName (infra, egress) / -WorkerComputerName (AD)
VM size Standard_B2s (default, 4 GiB). Standard_B1ms (2 GiB) works but leaves little memory. -VmSize
Worker VNet range and subnet (must not overlap anything it peers to) 10.221.16.0/24 / 10.221.16.0/27 (defaults) -AddressPrefix -SubnetPrefix
The VNet where the domain controllers are reachable (required), and the PIM environment’s VNet (optional) /subscriptions/<sub>/resourceGroups/rg-identity/providers/Microsoft.Network/virtualNetworks/vnet-identity, .../vnet-pim -PeerVnetId (infra)
DNS servers for the worker VNet: your domain controllers 10.0.1.4, 10.0.1.5 -DnsServers (infra)
The PIM database server Resource id /subscriptions/<sub>/resourceGroups/rg-pim/providers/Microsoft.Sql/servers/sql-pim-contoso and FQDN sql-pim-contoso.database.windows.net -SqlServerId (infra), -SqlServer (grant, install)
PIM database name PimPlatform (default) -SqlDatabase
Outbound path NAT gateway (default) or your own firewall route -Egress NatGateway / -Egress None (infra)
The PIM source store container (holds pim-src-<version>.tar.gz and channel.json for your environment) Resource id /subscriptions/<sub>/resourceGroups/rg-pim/providers/Microsoft.Storage/storageAccounts/stpimcontoso/blobServices/default/containers/pim-src and URL https://stpimcontoso.blob.core.windows.net/pim-src -SourceContainerId (grant), -SourceContainerUrl (install)
Update ring of your PIM environment 3 (the worker must follow the same ring as the environment) -Ring (install)
OU for the worker computer account OU=Tier0 Servers,DC=contoso,DC=local -ComputerOu (AD)
OU for the gMSAs and their groups OU=Service Accounts,OU=Tier0,DC=contoso,DC=local -GmsaOu (AD)
OU where PIM creates and maintains AD groups OU=PIM Groups,OU=Tier0,DC=contoso,DC=local -GroupsOu (AD), then the OU for new AD groups setting
OU(s) for AD admin accounts PIM creates OU=Admin Accounts,OU=Tier0,DC=contoso,DC=local -AdminAccountsOu (AD)
Should PIM manage membership of protected groups (Domain Admins, Enterprise Admins, Schema Admins and their nesting)? yes / no -GrantProtectedGroupMembership (AD)
Server onboarding (per-server local administrator groups)? Which server OUs? yes, OU=Servers,DC=contoso,DC=local -ServerGmsaName -ServerOu (AD), -ServerGmsaName (install)
gMSA names (max 15 characters, pattern gMSA-<code>-L<level>-T<tier>) gMSA-PIM-L1-T0 (default), gMSA-PIM-L2-T1 -GmsaName -ServerGmsaName

Cost (West Europe, pay-as-you-go, list prices October 2026): B2s Windows about 41 USD, disk about 2.4 USD, NAT gateway about 33 USD plus its public IP 3.65 USD: about 80 USD/month for the default build. With -Egress None (your existing firewall route) the NAT gateway and its IP are not created.

Outbound allow-list (only if you choose -Egress None and route through your own firewall): login.microsoftonline.com, graph.microsoft.com, management.azure.com, your SQL server (*.database.windows.net, port 1433), the source storage account (<account>.blob.core.windows.net), Windows Update, and, once during step 7, api.github.com and github.com / objects.githubusercontent.com (the PowerShell 7 MSI). The worker also needs to reach your domain controllers (LDAP, Kerberos, DNS) and, for server onboarding, your servers.


Step 1: Build the Azure infrastructure

Sign in happens on the first call: a browser window opens for an account of <tenant-id>. Plan first:

cd <release package folder>
$infra = @{
    SubscriptionId = '<subscription-id>'
    TenantId       = '<tenant-id>'
    ResourceGroup  = 'rg-pim-hybrid'
    Location       = 'westeurope'
    VmName         = 'PIMHYBRIDWRK'
    PeerVnetId     = @('/subscriptions/<sub>/resourceGroups/rg-identity/providers/Microsoft.Network/virtualNetworks/vnet-identity',
                       '/subscriptions/<sub>/resourceGroups/rg-pim/providers/Microsoft.Network/virtualNetworks/vnet-pim')
    DnsServers     = @('10.0.1.4', '10.0.1.5')
    SqlServerId    = '/subscriptions/<sub>/resourceGroups/rg-pim/providers/Microsoft.Sql/servers/sql-pim-contoso'
    # Egress       = 'None'     # only when your network already routes this subnet's outbound traffic
}
.\tools\pim-hybrid-worker\New-PimHybridWorkerInfra.ps1 @infra -WhatIf

Then build:

$out = .\tools\pim-hybrid-worker\New-PimHybridWorkerInfra.ps1 @infra
$out          # vmName, resourceGroup, principalId, subnetId, vnetId

What it builds, in order:

  1. the resource group;
  2. the NSG nsg-<vmname> with one rule, DenyAllInbound (priority 4096), which overrides Azure’s default allow VNet inbound;
  3. the VNet vnet-pimhybridwrk with one subnet snet-worker (NSG attached, service endpoints Microsoft.Sql and Microsoft.Storage), DNS servers = your domain controllers;
  4. a peering in both directions to every -PeerVnetId (named pimhybridwrk-to-<remote> and <remote>-to-pimhybridwrk), with VNet access on, forwarded traffic off, no gateway transit;
  5. the outbound path: it calls Set-PimHybridWorkerEgress.ps1 (step 3) to create the NAT gateway, unless -Egress None;
  6. the NIC without a public IP configuration (it refuses to continue if an existing NIC carries one) and the VM: system-assigned managed identity, Trusted Launch, local administrator pimlocal with a generated password that is discarded;
  7. a SQL VNet rule named pimhybridwrk on the PIM SQL server, admitting the worker subnet (when -SqlServerId is given).

Keep $out.principalId. It is the managed identity’s object id, the input for step 6.

Domain controllers on-premises, behind a hub gateway? The peerings are created with VNet access only. If your DCs are reached through the hub’s VPN or ExpressRoute gateway, add -UseRemoteGateways -GatewayVnetId <hub-vnet-id> (the hub must be one of -PeerVnetId), plus -AllowForwardedTraffic when a firewall / NVA in the hub forwards the traffic. The script first checks that the hub really has a VPN / ExpressRoute gateway, then needs allow gateway transit on the hub side of the peering: it sets that itself only on a hub-side peering it created (<hub>-to-pimhybridwrk). If the hub side belongs to someone else, it changes nothing there, prints exactly what the hub owner sets (allowGatewayTransit = true), and leaves use remote gateways off until you run it again. The same switches work later: Set-PimHybridWorkerPeering.ps1 ... -State Connected -UseRemoteGateways -GatewayVnetId <hub>. Make sure the on-premises side routes the worker range 10.221.16.0/24 back. The isolation switch (step 2) keeps those flags as they are.

A private-only PIM database (public network access disabled). The SQL VNet rule applies to the server’s public endpoint only. The worker then reaches SQL through its private endpoint, and the worker asks your DCs for the name. Add -PrivateSqlDnsZoneId <id of your existing privatelink.database.windows.net zone>: the script links that zone to the worker VNet (only the standard zone name is accepted; it never creates or links a zone named after your domain; -CreatePrivateDnsZone -PrivateDnsZoneResourceGroup <rg> creates the standard zone only if it is missing). It then prints the one conditional forwarder your AD DNS administrator adds on the DCs. PIM never writes AD DNS:

Add-DnsServerConditionalForwarderZone -Name 'privatelink.database.windows.net' -MasterServers &lt;address> -ReplicationScope Forest
  • DCs that are Azure VMs: <address> = 168.63.129.16 (Azure DNS, reachable only from inside Azure). Azure answers from the zones linked to the VNet of the DNS server that asks, so link the zone to the DCs’ VNet too: -PrivateDnsLinkVnetId <dc-vnet-id>.
  • DCs on-premises (reached over VPN / ExpressRoute): 168.63.129.16 does not answer outside Azure. Create an Azure DNS Private Resolver with an inbound endpoint in a VNet linked to the zone (normally the hub, -PrivateDnsLinkVnetId <hub-vnet-id>), and forward to that endpoint’s IP (-DnsForwarderIp <ip> puts it into the printed command).

Check on the worker: Resolve-DnsName <server>.database.windows.net returns the privatelink alias and a private address. (Forwarding the public zone database.windows.net to the same address also works; the privatelink zone is the narrower choice.)

Verify: the run ends with [infra] done. and prints VM PIMHYBRIDWRK (Standard_B2s) managed identity <guid>. In the portal, the VM’s Networking page shows no public IP and the NSG rule DenyAllInbound.


Step 2: Check the peering (and know the isolation switch)

The peerings were created in step 1. Show their state:

.\tools\pim-hybrid-worker\Set-PimHybridWorkerPeering.ps1 -SubscriptionId <subscription-id> -TenantId <tenant-id> `
    -ResourceGroup rg-pim-hybrid -State Show

Each side prints access=True state=Connected.

This same script is your isolation switch. -State Disconnected sets allow virtual network access to off on both sides of every peering of the worker’s VNet. Traffic stops at once; the peerings stay, so -State Connected restores exactly the same topology. While disconnected the worker cannot reach a domain controller: its AD jobs fail, loudly, in Jobs rather than act. SQL and Graph still work over its outbound path, which is how it reports that.

.\tools\pim-hybrid-worker\Set-PimHybridWorkerPeering.ps1 -SubscriptionId <subscription-id> -TenantId <tenant-id> `
    -ResourceGroup rg-pim-hybrid -State Disconnected      # or -State Connected

Optional: -VnetName if you changed the worker VNet’s name.


Step 3: The outbound path (NAT gateway)

Step 1 already ran this for you (-Egress NatGateway, the default). Run it yourself only when:

  • you built with -Egress None and now want the NAT gateway after all; or
  • you want to verify the worker’s outbound path at any time (the script ends with a check); or
  • a worker built by an older release has a public IP on its NIC: the script migrates it.
.\tools\pim-hybrid-worker\Set-PimHybridWorkerEgress.ps1 -SubscriptionId <subscription-id> -TenantId <tenant-id> `
    -ResourceGroup rg-pim-hybrid -VmName PIMHYBRIDWRK -WhatIf

Remove -WhatIf to apply. Optional parameters: -Location (default: the VNet’s region), -VnetName, -SubnetName (default snet-worker), -NatGatewayName (default natgw-<vmname>), -PublicIpName (default pip-<vmname>).

What it does, in order:

  1. creates the NAT gateway (Standard, idle timeout 4 minutes);
  2. detaches any public IP from the VM’s NICs (the address itself is kept);
  3. makes that same address (or a new Standard static IP pip-<vmname>) the NAT gateway’s outbound address, so an allow-list that names the address keeps working;
  4. points the subnet at the NAT gateway and switches Azure’s default outbound access off (the NSG and service endpoints are kept);
  5. verifies: no public IP on any NIC of the VM, the subnet uses the NAT gateway.

When migrating an existing worker, it has no outbound path for about a minute between steps 2 and 4; its loops fail that pass and recover by themselves.

Verify: [egress] done: PIMHYBRIDWRK has NO public IP; outbound leaves through natgw-pimhybridwrk as <ip>; inbound stays denied by the NSG. Give that outbound IP to anyone who allow-lists the worker.


Step 4: Prepare Active Directory (AD administrator)

Download the published single file Initialize-PimHybridWorkerAd.ps1 (invardia.com/support/pim/) to a domain controller (or a domain-joined admin host with dsacls.exe and the Group Policy Management tools) and run it in an elevated PowerShell session as a Domain Admin.

4.1 What it creates

One identity per privilege tier. Each identity is three objects in -GmsaOu:

Object Purpose
<name> the gMSA (AES256 only, 30-day password interval)
<name>-PermissionGroup the gMSA is its only member. Every right is granted to this group, never to the account.
<name>-PrincipalsAllowedAccess the computers allowed to retrieve the gMSA’s password: the worker computer
Identity (default name) Runs Rights granted to its PermissionGroup
gMSA-PIM-L1-T0 (-GmsaName) AD admin accounts, the AD group mirror, the continuous just-in-time membership sync On -GroupsOu: read the OU, create groups, write member / displayName / description on groups below it. On each -AdminAccountsOu: create users and full control of the user objects below it. With -GrantProtectedGroupMembership: write member on AdminSDHolder, the only way to reach protected groups (adminCount=1), because SDProp resets their permissions every hour. That makes this identity, and the worker, Tier 0.
gMSA-PIM-L2-T1 (-ServerGmsaName, optional) server onboarding: makes per-server groups members of each server’s local Administrators Local administrator on the servers below each -ServerOu, through a GPO (below). It has no rights in AD itself.

The servers GPO (only with -ServerGmsaName and -ServerOu): a GPO named PIM for AD: <ServerGmsaName>-PermissionGroup is local administrator (servers) (or -ServerAdminGpoName) with one Restricted Groups “member of” entry: the server identity’s PermissionGroup joins BUILTIN\Administrators on every computer below the OU. It adds only; it removes nobody. It is linked to each -ServerOu and takes effect at each server’s next policy refresh (gpupdate /target:computer).

What it deliberately does not do:

  • enable the AD Privileged Access Management optional feature (it is irreversible for the forest). The script detects it and reports it: with the feature on, just-in-time membership carries a time-to-live equal to the time left on the activation; without it, membership is plain and the sync removes it when the activation ends;
  • put any identity into a PIM just-in-time group;
  • grant anything on protected user accounts (adminCount=1).

4.2 The KDS root key

A gMSA cannot exist without a KDS root key in the forest. The script checks for one and stops if there is none, unless you pass -CreateKdsRootKey. That runs Add-KdsRootKey -EffectiveImmediately once (a forest-wide object, needs Enterprise Admins). The key is usable only after it has replicated, about 10 hours later. Create it, wait, then continue; or create it yourself in advance.

4.3 Run it

Plan first:

$ad = @{
    WorkerComputerName            = 'PIMHYBRIDWRK'                                  # = -VmName from step 1
    ComputerOu                    = 'OU=Tier0 Servers,DC=contoso,DC=local'
    GmsaOu                        = 'OU=Service Accounts,OU=Tier0,DC=contoso,DC=local'
    GmsaName                      = 'gMSA-PIM-L1-T0'                                # the default
    GroupsOu                      = 'OU=PIM Groups,OU=Tier0,DC=contoso,DC=local'
    AdminAccountsOu               = @('OU=Admin Accounts,OU=Tier0,DC=contoso,DC=local')
    GrantProtectedGroupMembership = $true                                           # only if PIM should manage protected groups
    ServerGmsaName                = 'gMSA-PIM-L2-T1'                                # only for server onboarding
    ServerOu                      = @('OU=Servers,DC=contoso,DC=local')
    UsedBy                        = 'PIM Manager hybrid worker'                     # written into the gMSA descriptions
    ProvisionOdj                  = $true                                           # create the computer account + the join blob
    # CreateKdsRootKey            = $true                                           # only if 4.2 applies
    # TriggerSdProp               = $true                                           # apply AdminSDHolder now, not within the hour
}
.\Initialize-PimHybridWorkerAd.ps1 @ad -WhatIf

Then run it without -WhatIf.

Parameters in short:

Parameter Required Meaning
-WorkerComputerName yes The worker’s computer name (= the VM name).
-ComputerOu yes Where the computer account is created (-ProvisionOdj).
-GmsaOu yes Where the gMSAs and their two groups each are created.
-GroupsOu yes Where PIM creates and maintains AD groups.
-GmsaName no Default gMSA-PIM-L1-T0.
-AdminAccountsOu no One or more OUs for AD admin accounts PIM creates.
-GrantProtectedGroupMembership no Write member on AdminSDHolder (Tier 0). -TriggerSdProp applies it at once on the PDC emulator.
-ServerGmsaName, -ServerOu, -ServerAdminGpoName no Server onboarding identity, the OUs whose servers it administers, the GPO name. -ServerGmsaName needs -ServerOu and must differ from -GmsaName.
-ProvisionOdj no Create the computer account with djoin /provision and print the offline domain-join blob. Refuses if the computer exists, unless -ReuseComputer (that resets its password).
-CreateKdsRootKey no See 4.2.
-UsedBy no Text written into each gMSA’s description.

Every OU you name must already exist; the script stops on a missing one.

4.4 Output that feeds later steps

  • With -ProvisionOdj the output contains the join blob between the lines ODJ-BLOB-BEGIN and ODJ-BLOB-END. It starts with gz: (compressed so it fits in run-command’s 4 KB output if you run this script itself through run-command on an Azure DC). Hand it to the Azure administrator over a secure channel: it carries the computer’s initial password until step 7 rotates it.
  • The last lines print the exact gMSA names for step 7, for example Install-PimHybridWorker.ps1 -GmsaName 'CONTOSO\gMSA-PIM-L1-T0' -ServerGmsaName 'CONTOSO\gMSA-PIM-L2-T1', and the OU to set in PIM Manager (step 8).

Verify: in Active Directory Users and Computers, -GmsaOu holds the gMSA(s) and their -PermissionGroup / -PrincipalsAllowedAccess groups; the worker computer account is in -ComputerOu and is a member of each -PrincipalsAllowedAccess group; with server onboarding, Group Policy Management shows the GPO linked to each -ServerOu.


Step 5: Join the worker to the domain (run-command)

The worker has no inbound access, so everything on it runs through Azure run-command, as SYSTEM: Azure portal > the worker VM > Operations > Run command > RunPowerShellScript.

Not yet automated: there is no PIM Manager script that sends Install-PimHybridWorker.ps1 to the VM through run-command. Use the portal as shown here. The portal’s script box takes no separate parameters, so paste the script inside a script block and pass the parameters after it.

Paste this, with the full contents of Install-PimHybridWorker.ps1 between the braces and the blob from step 4:

& {
# <paste the full contents of Install-PimHybridWorker.ps1 here>
} -Phase Join -OdjBlob 'gz:H4sIAAAA...'

What it does: writes the blob to a temporary file, runs djoin /requestODJ, deletes the file, and reboots after 15 seconds (-NoReboot skips the reboot). If the VM is already joined it does nothing.

Verify: the output ends with offline domain join staged -- rebooting to complete it. After the reboot the VM’s computer account in AD shows a recent logon.


Step 6: Grant the worker’s cloud access

Run in Windows PowerShell 5.1 or PowerShell 7, from the release package folder (under PowerShell 7, if the SQL connection fails at the transport level, the script repeats the SQL statements once in Windows PowerShell 5.1 and says so; the access token is passed on that process’s standard input, never on a command line). Feed it principalId from step 1:

powershell.exe -File .\tools\pim-hybrid-worker\Grant-PimHybridWorkerAccess.ps1 `
    -SubscriptionId <subscription-id> -TenantId <tenant-id> -PrincipalId <principalId from step 1> `
    -SqlServer sql-pim-contoso.database.windows.net -SqlDatabase PimPlatform `
    -SourceContainerId /subscriptions/<sub>/resourceGroups/rg-pim/providers/Microsoft.Storage/storageAccounts/stpimcontoso/blobServices/default/containers/pim-src `
    -WhatIf

Remove -WhatIf to apply. What the managed identity gets, least privilege:

Where Grant Why
PIM database A contained database user (default name PIMHYBRIDWRK, -SqlUserName) created from the identity’s application id, in db_datareader + db_datawriter. Never db_ddladmin. The worker changes rows (scheduler state, run history, audit), never the schema.
Microsoft Graph Application permissions, read only: Group.Read.All, User.Read.All, PrivilegedAssignmentSchedule.Read.AzureADGroup To read the active PIM-for-Groups members that the AD membership mirrors.
Source store Storage Blob Data Reader on the one container you name The worker fetches its code and channel.json with its own identity: no SAS, no PAT.

Mail.Send is deliberately not granted. Without it, a new AD admin account whose initial password cannot be mailed is held by the worker and reported, never created with a password nobody receives. Add -GrantMailSend if you want the worker to create those accounts and mail the initial password.

A new Graph application permission takes about 25 minutes to reach the identity’s tokens. The Storage Blob Data Reader role must be in place before step 7 (Azure role assignments usually apply within a few minutes).

Verify: each line reports granted or already granted, and the run ends with [grant] done.


Step 7: Install and configure the worker (run-command)

After the reboot from step 5, run this through Run command > RunPowerShellScript again, with the script pasted between the braces. Use the gMSA names step 4 printed:

& {
# <paste the full contents of Install-PimHybridWorker.ps1 here>
} -Phase Configure -GmsaName 'CONTOSO\gMSA-PIM-L1-T0' -ServerGmsaName 'CONTOSO\gMSA-PIM-L2-T1' `
  -SqlServer sql-pim-contoso.database.windows.net -SqlDatabase PimPlatform -TenantId <tenant-id> `
  -SourceContainerUrl https://stpimcontoso.blob.core.windows.net/pim-src -Ring 3 -PlanOnly
Parameter Required Meaning
-GmsaName yes DOMAIN\name of the Tier-0 gMSA.
-ServerGmsaName no DOMAIN\name of the server-onboarding gMSA. Leave it out and the main gMSA also onboards servers.
-SqlServer, -SqlDatabase yes / no The PIM database (default PimPlatform).
-TenantId yes Your tenant.
-SourceContainerUrl yes The URL of the same container you granted in step 6.
-Ring or -Version one of them The update ring your PIM environment follows; or a fixed version (N.N.N), which pins the worker.
-PlanOnly no Every job runs with -WhatIf: it plans and logs, and changes nothing in AD. Recommended for the first run.
-AdServer no A specific domain controller to talk to (default: the domain’s locator).
-IntervalMinutes no The safety-net tick (default 5).
-Root no Install folder (default C:\PIM).

What -Phase Configure does, in order:

  1. checks the VM is domain-joined, then changes the machine password (nltest /sc_change_pwd), so the join blob is worthless from now on, and deletes run-command’s downloaded script copies from the disk;
  2. installs PowerShell 7 from the latest official release if missing, after checking the MSI’s Microsoft Authenticode signature;
  3. installs and tests each gMSA on the machine (purging the computer’s Kerberos tickets first so it sees its new group membership). A worker that cannot use its gMSA fails here, not at 03:00;
  4. grants Log on as a batch job to the worker gMSA(s) only (added to the existing holders);
  5. locks down C:\PIM: inheritance off; only SYSTEM and Administrators can write the code the gMSAs run; the gMSAs can read it and write only to C:\PIM\logs;
  6. fetches the PIM code your ring approves (channel.json in the source container) with the VM’s managed identity, checks the archive and switches C:\PIM\app\current to it (the previous version is kept);
  7. registers the scheduled tasks:
Task Runs as Cadence Jobs
PIM Hybrid AD Sync Tier-0 gMSA continuous (5 s pause between passes), restarted within a minute if it stops hybrid-ad-sync: active PIM-for-Groups members to AD group membership, for the critical (non-server) groups
PIM Hybrid AD Sync Servers Tier-0 gMSA continuous hybrid-ad-sync-servers: the same for the per-server groups, in parallel batches
PIM Hybrid AD Changes Tier-0 gMSA continuous hybrid-ad-changes: AD accounts and AD groups the moment their definitions change
PIM Hybrid Worker Tier-0 gMSA every 5 minutes (safety net) hybrid-ad-apply (AD admin accounts) and hybrid-ad-groups (the group mirror; hourly)
PIM Hybrid Servers server gMSA (only with -ServerGmsaName) every 5 minutes hybrid-ad-servers (hourly): per-server groups into each server’s local Administrators, add only
PIM Hybrid Worker Update SYSTEM daily 04:30 moves the worker to the version its ring approves

If the fetched version predates the hybrid worker, the tasks are created disabled and the daily update enables them once the ring approves a version that has it.

Go live: when the plan-only runs look right (step 9), run the same -Phase Configure command again without -PlanOnly.

Verify: the output contains gMSA CONTOSO\gMSA-PIM-L1-T0$ with no error, PIM <version> is current, and ends with done: 'PIM Hybrid Worker' every 5 min (safety net) + ... continuously, as CONTOSO\gMSA-PIM-L1-T0$, plus PLAN ONLY (-WhatIf) if you used -PlanOnly.

If the gMSA test fails with is this computer in …-PrincipalsAllowedAccess?, the computer is not yet a member (or the KDS root key is younger than 10 hours). Fix it in AD, restart the VM, and run -Phase Configure again.


Step 8: Naming and switch-on in PIM Manager (SuperAdmin)

The naming is yours. Every knob is one setting on Settings > Hybrid AD & workloads > Hybrid Active Directory (PIM for AD), each with its own help text. Blank means derived from your tenant’s naming conventions.

Setting What it decides
AD group marker Which PIM groups are mirrored to AD (the marker in their name).
Pattern override A full name pattern instead of the marker.
Cloud admin suffix / AD admin suffix How a cloud admin account maps to its AD admin account. The sync only ever removes accounts carrying the AD admin suffix.
OU for new AD groups Set it to -GroupsOu from step 4. Blank: missing AD groups are never created, only existing ones are kept in sync. Groups the worker creates are not deleted again when you clear it.
Per-server group format The name of the per-server administrator groups.
Shared server-admins group An optional group added to every onboarded server’s local Administrators.
Never remove Members the sync must never remove.
Groups per batch / Batches at once Throughput of the server-group sync (PowerShell 7).

Verify: the page shows one status per worker job (working / needs attention, with the last check-in).


Step 9: Verify end to end

  1. Jobs view in PIM Manager: hybrid-ad-sync, hybrid-ad-apply, hybrid-ad-groups (and hybrid-ad-servers) show the worker as live (or plan-only during the first run). The main engine leaves these jobs to the worker while its heartbeat is fresh.
  2. Live view: Jobs > hybrid-ad-sync > Live view follows every pass: per group the current and expected time-to-live, and each add, correction and removal.
  3. On the worker (run-command): Get-ChildItem C:\PIM\logs | Sort-Object LastWriteTime | Select-Object -Last 5 and Get-Content C:\PIM\logs\sync-<yyyyMMdd>.log -Tail 40.
  4. Network: Set-PimHybridWorkerPeering.ps1 ... -State Show (step 2) and Set-PimHybridWorkerEgress.ps1 ... (step 3, its final check).
  5. A real activation: activate a mirrored PIM group for a test admin; within seconds their AD admin account is a member of the AD group; when the activation ends, it is removed.

Updates

The daily PIM Hybrid Worker Update task reads channel.json for the worker’s ring and installs the approved version when it differs: it fetches the archive with the managed identity, switches C:\PIM\app\current, and applies the new version’s task layout. The continuous loops restart on the new code by themselves. It never rolls back unattended and never moves without a ring answer. To run it at once: run-command Start-ScheduledTask -TaskName 'PIM Hybrid Worker Update'.


On-premises server with Azure Arc

Built, see Hybrid worker with Azure Arc (offline-tested; not yet proven on a live Arc server). In short:

  • the server is connected to Azure with Azure Arc, and the Arc machine’s managed identity takes the place of the VM’s system-assigned managed identity for the PIM database, Microsoft Graph (read only) and the source store, with the same least-privilege grants;
  • the same gMSA model in AD: one identity per tier, rights on the PermissionGroups, no password, secret or certificate on the server;
  • no inbound connections: it is managed through Arc’s outbound agent channel, like run-command on the Azure VM;
  • the same Tier-0 rule: treat the server like a domain controller.

Use that guide’s commands (Deploy-PimHybridWorker.ps1 -Target Arc); do not adapt the Azure VM steps in this guide to an Arc server.


Rollback and removal

Situation What to do
A step failed Fix the cause and re-run the same command. Every script checks what exists first.
Stop the worker acting at once Set-PimHybridWorkerPeering.ps1 ... -State Disconnected (step 2). Its AD jobs fail visibly; nothing is written to AD. -State Connected restores.
Back to plan-only Re-run step 7 with -PlanOnly.
Pause the worker Run-command: Disable-ScheduledTask -TaskName 'PIM Hybrid Worker', and the same for PIM Hybrid AD Sync, PIM Hybrid AD Sync Servers, PIM Hybrid AD Changes, PIM Hybrid Servers. Note: the daily update re-enables tasks when it runs. Use the peering switch for a lasting stop.
Remove the worker completely Remove-PimHybridWorker.ps1 – see Remove the worker below.

Remove the worker

Remove-PimHybridWorker.ps1 removes the worker and only what PIM’s worker scripts created. Plan first; it changes nothing without -Apply:

.\tools\pim-hybrid-worker\Remove-PimHybridWorker.ps1 -Target Azure -SubscriptionId <subscription-id> -TenantId <tenant-id> `
    -SqlServerId <sql-server-id> -SourceContainerId <source-container-id> `
    -PrivateSqlDnsZoneId <zone-id, if you linked one> -AdDomain CONTOSO -GmsaOu '<gMSA OU>' -ComputerOu '<computer OU>' -GroupsOu '<groups OU>'
# ...or -StatePath <the Deploy-PimHybridWorker.ps1 state file> to reuse every value given at deploy time
.\tools\pim-hybrid-worker\Remove-PimHybridWorker.ps1 ...same... -Apply

In this order it deletes: the identity’s Graph read permissions, its Storage Blob Data Reader assignment and its database user (dropped only when it carries the identity’s id); the SQL VNet rule pimhybridwrk (only when it names the worker subnet); the private DNS zone’s link to the worker VNet (links to other VNets and the zone itself are kept and listed); the peerings it created on both sides (a peering someone else made is listed, never touched); then the VM with its NIC and OS disk, the VNet, the NAT gateway and its public IP, the NSG, and the resource group if PIM created it and it is empty. It recognises its own resources by the tag pim-hybrid-worker = <VM name> that the build puts on them. A worker built before the tags existed: check the plan, then add -IncludeUntagged (accepts untagged resources with exactly the build’s names in the worker’s resource group; never the resource group itself). A VNet that holds anything else is kept, and so are its NAT gateway and NSG.

Active Directory is never changed by the script. It prints the AD administrator’s clean-up: take back the rights of <GmsaName>-PermissionGroup with dsacls <dn> /R (groups OU, each admin-accounts OU, AdminSDHolder if it was granted), unlink and delete the servers GPO (Remove-GPLink / Remove-GPO), then delete the gMSAs and their two groups and the worker VM’s computer account with dsrm. Keep the KDS root key. Groups and admin accounts the worker created stay; remove them yourself if you no longer want them.

The KDS root key and the AD Privileged Access Management feature (if you enabled it yourself) are forest-wide; leave them.

InvardiaInvardia

See the risk. Control the access.

Products

PIM ManagerSecurityInsightEntra Policy SuiteAll featuresPricingFree 60-day Pro trialBook a demoWhy Invardia

Docs

DocumentationSearch the docsPIM Manager settingsSecurityInsight settingsPIM network guidesCommand wizardNetwork egressPIM Manager design configuratorSecurityInsight design configuratorDesign your installationCalculate your cost

Company

AboutContactCustomer portalSupportHow support worksHow updates reach youSupport in numbersSetup scriptsRemote support accessContact: info@invardia.com

Legal

Terms of saleLicence termsData processingPrivacyCookiesCompany information
© 2026 Invardia – a brand of 2LINKIT, founded and owned by Morten Knudsen · Strandvejen 21A, 6000 Kolding, Denmark · VAT DK39208032 · Contact: info@invardia.com