Deploy-PimHybridWorker
PIM for Active Directory: deploy the hybrid worker (Azure VM or on-premises with Azure Arc)
Purpose
Deploys the PIM for Active Directory hybrid worker in the right order, step by step, and remembers where it got to so it can stop for the Active Directory step and continue later.
When to run it
Once per hybrid worker, when you start using PIM for Active Directory: -Target Azure builds a new Azure VM for it, -Target Arc uses your own on-premises Windows server that is already Azure Arc-enabled and domain-joined. Run it first without -Apply to see the plan, then with -Apply; it stops for the Active Directory step (and, when a new KDS root key was created, for the ~10 hours that key needs) and continues with -Resume -Apply.
Who may run it
| Kind | Role | Scope | Why |
|---|---|---|---|
| azureRbac | Contributor (or Owner) | -Target Azure: the subscription or the worker's resource group, and Network Contributor on the networks it peers with; -Target Arc: Reader on the Arc machine and the Azure Connected Machine Resource Administrator role (run-command) on it | creates the worker's network and VM (Azure), reads the Arc machine and runs the install on it through run-command |
| azureRbac | User Access Administrator (or Role Based Access Control Administrator) | the source-store container | grants the worker's identity Storage Blob Data Reader on the container that holds the PIM code |
| entraRole | Privileged Role Administrator | the tenant | grants the worker's identity the Graph application roles Group.Read.All, User.Read.All and PrivilegedAssignmentSchedule.Read.AzureADGroup (read only) |
| other | Microsoft Entra admin of the PIM SQL server (or a member of its admin group) | the PIM database | creates the worker's contained database user (db_datareader + db_datawriter). Run the script in Windows PowerShell 5.1 for this step. |
| other | Active Directory: an account that may create gMSAs, groups, GPOs and OU permissions (on a domain controller) | the domain -- the AD step only, which this script PRINTS and the AD administrator runs | the worker writes AD only as group managed service accounts, which the AD step creates with their least-privilege rights |
What it changes
| Where | Action | What | Permission / role | Scope | Why | Undo |
|---|---|---|---|---|---|---|
| Azure (-Target Azure) | create | resource group, VNet + subnet, deny-all-inbound NSG, NAT gateway egress, peerings to -PeerVnetId, the worker VM with a system-assigned identity and no public IP, a SQL VNet rule (New-PimHybridWorkerInfra.ps1) | the worker's own isolated network and machine | delete the resource group and the peerings on the remote networks | ||
| PIM SQL server (-Target Arc with -SqlAllowedPublicIp) | create | one firewall rule per on-premises egress address | lets the on-premises worker reach a public SQL endpoint; not needed with a private endpoint | delete the pimhybridwrk-arc-* firewall rules | ||
| the worker machine | update | Install-PimHybridWorker.ps1 through run-command: domain join (Azure only), PowerShell 7, the gMSAs installed and tested, log on as a batch job for them, C:\PIM locked down, the PIM code, six scheduled tasks; on Arc the gMSAs join the local group 'Hybrid agent extension applications' | the worker itself | delete the scheduled tasks 'PIM Hybrid *' and C:\PIM; remove the gMSAs from the local group | ||
| Microsoft Graph | grant | the worker identity's application roles | Group.Read.All, User.Read.All, PrivilegedAssignmentSchedule.Read.AzureADGroup (+ Mail.Send with -GrantMailSend) | the tenant | the worker reads the PIM groups and who is active in them; it never writes to Entra | remove the app role assignments from the identity's enterprise application |
| Azure RBAC | grant | the worker identity | Storage Blob Data Reader | the source-store container (-SourceContainerId) | the worker updates itself with its identity -- no SAS, no PAT | remove the role assignment |
| PIM database | grant | a contained user (-SqlUserName) created from the identity | db_datareader, db_datawriter (never db_ddladmin) | the PIM database (-SqlDatabase) | the worker reads its work and writes its state and audit | DROP USER [<name>] |
| this computer | create | the state file $env:LOCALAPPDATA\pim\hybrid\deploy-<target>-<name>.json (no secrets) | -Resume continues where the run stopped | delete the file |
What it only reads
- the subscription (its tenant, checked against -TenantId) and, for -Target Arc, the Arc machine (status, OS, identity)
- the state file of an earlier run (-Resume)
- the run-command result (state, exit code, output) of each install phase
Parameters
| Parameter | Required | Default | What it does |
|---|---|---|---|
| -Target | yes | Azure (a new Azure VM) or Arc (your own Azure Arc-enabled on-premises server). Not needed with -Resume. | |
| -SubscriptionId | yes | The subscription of the worker (Azure) or of the Arc machine. | |
| -TenantId | yes | The tenant. The REST session is pinned to it and every step refuses another. | |
| -ResourceGroup | no | rg-pimhybridwrk | -Target Azure: the worker's resource group. |
| -Location | no | westeurope | -Target Azure: the region. |
| -VmName | no | PIMHYBRIDWRK | -Target Azure: the VM and computer name (max 15 characters). |
| -VmSize | no | Standard_B2s | -Target Azure: the VM size. |
| -VnetName | no | vnet-pimhybridwrk | -Target Azure: the worker's VNet. |
| -AddressPrefix | no | 10.221.16.0/24 | -Target Azure: the VNet address space. |
| -SubnetPrefix | no | 10.221.16.0/27 | -Target Azure: the worker subnet. |
| -PeerVnetId | no | -Target Azure (required there): the VNet resource ids to peer with -- the network with the domain controllers, optionally the PIM environment's. | |
| -DnsServers | no | -Target Azure (required there): the domain controllers' addresses (the VNet DNS). | |
| -Egress | no | NatGateway | -Target Azure: NatGateway, or None when your network already routes outbound. Never a public IP on the VM. |
| -UseRemoteGateways | no | false | -Target Azure: the DCs are reached through the hub's VPN / ExpressRoute gateway (with -GatewayVnetId). Passed to the infra step. |
| -GatewayVnetId | no | -Target Azure: the hub VNet with the gateway (one of -PeerVnetId). | |
| -AllowForwardedTraffic | no | false | -Target Azure: the worker-side peering(s) accept traffic the hub forwards. |
| -PrivateSqlDnsZoneId | no | A private-only PIM database: the existing privatelink.database.windows.net zone, linked to the worker VNet; the DCs' DNS forwarder is printed. | |
| -PrivateDnsLinkVnetId | no | More VNets to link the zone to (the DCs' VNet when they are Azure VMs; the DNS Private Resolver's VNet). | |
| -CreatePrivateDnsZone | no | false | Create the standard privatelink.database.windows.net zone only when it is missing. |
| -PrivateDnsZoneResourceGroup | no | Where the standard zone lives, instead of -PrivateSqlDnsZoneId. | |
| -PrivateSql | no | false | Print the conditional forwarder for a private-only database (implied by the zone options). |
| -DnsForwarderIp | no | The address the printed forwarder points at (168.63.129.16 for DCs in Azure; a Private Resolver inbound endpoint for DCs on-premises). | |
| -ArcMachineResourceId | no | -Target Arc (required there): /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.HybridCompute/machines/<server>. | |
| -SqlAllowedPublicIp | no | -Target Arc with a public SQL endpoint: your on-premises egress address(es); one firewall rule each. Leave empty with a private endpoint. | |
| -SqlServerId | yes | The PIM SQL server's resource id. | |
| -SqlDatabase | no | PimPlatform | The PIM database. |
| -SqlUserName | no | PIMHYBRIDWRK | The worker's contained database user. Name it per worker when you run more than one. |
| -SourceContainerId | yes | The resource id of the blob container with the PIM code (pim-src-<version>.tar.gz + channel.json). | |
| -Ring | no | The update ring this environment follows (or -Version). | |
| -Version | no | Pin a version instead of following a ring. | |
| -GrantMailSend | no | false | Also grant Graph Mail.Send, so new AD admin accounts get their initial password by mail. |
| -PlanOnly | no | false | Install the worker in plan-only mode: every run plans and logs, nothing is written to AD until Configure is re-run without it. |
| -AdDomain | yes | The domain's NetBIOS name (the gMSAs are DOMAIN\name). | |
| -WorkerComputerName | no | -VmName / the Arc machine name | The worker's computer name in AD. |
| -ComputerOu | yes | The OU of the worker's computer account (printed into the AD command). | |
| -GmsaName | no | gMSA-PIM-L1-T0 | The Tier-0 worker identity. |
| -GmsaOu | yes | The OU for the gMSAs and their groups. | |
| -GroupsOu | yes | The OU where PIM for AD creates its groups. | |
| -AdminAccountsOu | no | The OU(s) of the AD admin accounts PIM creates. | |
| -ServerGmsaName | no | The separate Tier-1 identity for server onboarding (e.g. gMSA-PIM-L2-T1); needs -ServerOu. | |
| -ServerOu | no | The servers OU(s) whose local Administrators get the server identity (a GPO). Keep the worker itself out of it. | |
| -GrantProtectedGroupMembership | no | false | Let the worker change the members of protected groups (AdminSDHolder) -- Tier 0. |
| -CreateKdsRootKey | no | false | Let the AD step create the forest's KDS root key when there is none; the run then waits ~10 hours before Configure. |
| -AdServer | no | A specific domain controller for the worker's AD calls. | |
| -Apply | no | false | Run the steps. Without it the script prints the plan only. |
| -Resume | no | false | Continue from the state file; every value given on the first run is remembered. |
| -StatePath | no | the newest $env:LOCALAPPDATA\pim\hybrid\deploy-*.json | The state file to use. |
| -KdsRootKeyCreatedUtc | no | When the AD step created a new KDS root key (it prints the time): Configure waits until it is ~10 hours old. | |
| -OdjBlob | no | -Target Azure: the offline domain-join blob the AD step printed (never stored in the state file). | |
| -OdjBlobFile | no | -Target Azure: a file holding that blob. | |
| -PrintOnly | no | false | Do not use run-command: write the self-contained install script for the server administrator to run locally. |
| -JoinRebootWaitSeconds | no | 240 | -Target Azure: how long to wait for the reboot after the domain join. |
Preview with -WhatIf
Prints the plan: every step in order with what it would create or grant and the exact Active Directory command, plus the state file it would use. Nothing is changed, nothing is called, no state file is written. -Apply runs it.
Undo
Remove-PimHybridWorker.ps1 removes it all (plan first, -Apply). By hand: -Target Azure: delete the worker's resource group and the peerings on the remote networks. Both targets: remove the worker identity's Graph app roles, its Storage Blob Data Reader assignment and its database user (DROP USER); on the server delete the 'PIM Hybrid *' scheduled tasks and C:\PIM; in AD delete the gMSAs and their groups (Initialize-PimHybridWorkerAd.ps1 created them) and the servers GPO.
Network
It connects to: management.azure.com (ARM), graph.microsoft.com, login.microsoftonline.com, the PIM SQL server (1433) for the grant step, nothing to the worker machine directly: the install reaches it through Azure run-command (outbound from the machine's agent)
Download and run
Run these lines in PowerShell: the first downloads the script into the current folder, the second runs it.
Invoke-WebRequest https://invardia.com/support/pim/Deploy-PimHybridWorker.ps1 -OutFile Deploy-PimHybridWorker.ps1
.\Deploy-PimHybridWorker.ps1 -Target <Target> -SubscriptionId <SubscriptionId> -TenantId <TenantId> -SqlServerId <SqlServerId> -SourceContainerId <SourceContainerId> -AdDomain <AdDomain> -ComputerOu <ComputerOu> -GmsaOu <GmsaOu> -GroupsOu <GroupsOu>To check the download first, compare its SHA-256 with the published one:
SHA-256 0af9f1e2f40032dc5db98114ca974c68588b8bac90bf82e4de4b124ee2e73c30
Checksums: SHA256SUMS.txt · Version 3.0.70
Before you run it, compare the file's SHA-256 with its line in SHA256SUMS.txt.
Every parameter: Get-Help .\Deploy-PimHybridWorker.ps1 -Full · Source: Deploy-PimHybridWorker.ps1
Every script, identity and permission for your security review: PIM Manager