#Requires -Version 7.0 # PIM Manager 3.0.70 -- Deploy-PimHybridWorker.ps1, standalone (built by tools/setup/Build-PimSupportScripts.ps1; helpers inlined: ..\setup\_pimscriptdoc.ps1; files carried, each checked by SHA-256 and unpacked to a private temp folder at run time: 13) <# .SYNOPSIS PIM Manager hybrid worker (PIM for Active Directory): deploy it end to end with one command -- in Azure (a new Azure VM) or on your own on-premises Windows server that is Azure Arc-enabled. .DESCRIPTION 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. It runs the worker's own scripts (tools\pim-hybrid-worker) in this order: -Target Azure 1 infra New-PimHybridWorkerInfra.ps1 (resource group, VNet, deny-all NSG, NAT gateway egress, the VM) 2 peering Set-PimHybridWorkerPeering.ps1 -State Show (the peerings are there and connected) 3 egress Set-PimHybridWorkerEgress.ps1 (no public IP on the VM; outbound through the NAT gateway) 4 ad printed: the exact Initialize-PimHybridWorkerAd.ps1 command for the AD administrator to run on a domain controller -- then STOP (exit 10) until it is done 5 join Install-PimHybridWorker.ps1 -Phase Join through run-command (offline domain join + reboot) 6 grant Grant-PimHybridWorkerAccess.ps1 (SQL contained user, Graph read roles, source-store reader) 7 kds a KDS root key created by the AD step is usable only ~10 hours later: STOP (exit 12) until then 8 configure Install-PimHybridWorker.ps1 -Phase Configure through run-command (needs the grant from 6) 9 verify the Configure run finished, the tasks are registered -Target Arc the same without infra / peering / egress / join: 1 checks the Arc machine (connected, Windows, a system-assigned identity) and builds NOTHING in Azure; the on-premises network is yours. PLAN FIRST: without -Apply it prints the plan and the commands it would run, and changes nothing (-WhatIf is the same). -Apply runs the steps. -Resume continues from the state file ($env:LOCALAPPDATA\pim\hybrid\deploy--.json; the newest one when -StatePath is not given) -- every value given on the first run is remembered (never a secret: the offline-join blob is passed again with -OdjBlob / -OdjBlobFile). Exit codes: 0 done, 10 waiting for the AD step, 11 waiting for a script the server administrator runs locally (run-command was not available), 12 waiting for the KDS root key, 1 failed. Network options (§100.63), passed to the infra step only when given: -UseRemoteGateways -GatewayVnetId -AllowForwardedTraffic (DCs behind a hub VPN / ExpressRoute gateway), -PrivateSqlDnsZoneId / -PrivateDnsLinkVnetId / -CreatePrivateDnsZone -PrivateDnsZoneResourceGroup / -PrivateSql / -DnsForwarderIp (a private-only PIM database: the standard privatelink zone is linked, the DNS forwarder for your DCs is printed). Remove-PimHybridWorker.ps1 undoes it all. One REST session, pinned to -TenantId (the Invardia Support app's session, or the person signed in in the browser), shared by every step. No az, no PowerShell module. ONE FILE: the copy published at https://invardia.com/support/pim/Deploy-PimHybridWorker.ps1 carries the worker's other scripts inside it, each with its SHA-256. It checks every one before it starts (a changed copy refuses to run), and with -Apply writes them to a new private folder in your temp folder for the run and removes that folder when it ends. The Active Directory step's script is published on its own (Initialize-PimHybridWorkerAd.ps1); the STOP message shows the download line. .EXAMPLE # on-premises (Azure Arc): plan, then apply .\Deploy-PimHybridWorker.ps1 -Target Arc -SubscriptionId -TenantId ` -ArcMachineResourceId /subscriptions//resourceGroups//providers/Microsoft.HybridCompute/machines/ ` -SqlServerId -SourceContainerId /blobServices/default/containers/pim-src -Ring 2 ` -AdDomain CORP -ComputerOu 'OU=Tier0 Servers,DC=corp,DC=local' -GmsaOu 'OU=Service Accounts,DC=corp,DC=local' ` -GroupsOu 'OU=PIM Groups,DC=corp,DC=local' -AdminAccountsOu 'OU=Admin Accounts,DC=corp,DC=local' .\Deploy-PimHybridWorker.ps1 ...same... -Apply # stops at the AD step (exit 10) .\Deploy-PimHybridWorker.ps1 -Resume -Apply # after the AD administrator ran the printed command .NOTES Requirements: PowerShell: 7.0 or later (pwsh) for the published copy. The copy inside the PIM Manager package also runs in Windows PowerShell 5.1. Role: Contributor (or Owner) for the worker's Azure resources, User Access Administrator on the source container, Privileged Role Administrator for the worker identity's Graph read roles, and the Microsoft Entra admin of the PIM SQL server; the Active Directory step is run by the AD administrator. Hosts: management.azure.com, graph.microsoft.com, login.microsoftonline.com and the PIM SQL server (port 1433). PERMISSIONS AND CHANGES (from Deploy-PimHybridWorker.ps1 manifest, PIM Manager 3.0.70; full page: https://invardia.com/docs/pim/scripts/Deploy-PimHybridWorker/) The person running it needs: - Contributor (or Owner) [azureRbac, -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 - User Access Administrator (or Role Based Access Control Administrator) [azureRbac, the source-store container] -- grants the worker's identity Storage Blob Data Reader on the container that holds the PIM code - Privileged Role Administrator [entraRole, the tenant] -- grants the worker's identity the Graph application roles Group.Read.All, User.Read.All and PrivilegedAssignmentSchedule.Read.AzureADGroup (read only) - Microsoft Entra admin of the PIM SQL server (or a member of its admin group) [other, 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. - Active Directory: an account that may create gMSAs, groups, GPOs and OU permissions (on a domain controller) [other, 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 creates, changes or grants: - 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) (Azure (-Target Azure)) -- the worker's own isolated network and machine Undo: delete the resource group and the peerings on the remote networks - create one firewall rule per on-premises egress address (PIM SQL server (-Target Arc with -SqlAllowedPublicIp)) -- lets the on-premises worker reach a public SQL endpoint; not needed with a private endpoint Undo: delete the pimhybridwrk-arc-* firewall rules - 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 machine) -- the worker itself Undo: delete the scheduled tasks 'PIM Hybrid *' and C:\PIM; remove the gMSAs from the local group - grant the worker identity's application roles (Microsoft Graph): 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 Undo: remove the app role assignments from the identity's enterprise application - grant the worker identity (Azure RBAC): Storage Blob Data Reader @ the source-store container (-SourceContainerId) -- the worker updates itself with its identity -- no SAS, no PAT Undo: remove the role assignment - grant a contained user (-SqlUserName) created from the identity (PIM database): db_datareader, db_datawriter (never db_ddladmin) @ the PIM database (-SqlDatabase) -- the worker reads its work and writes its state and audit Undo: DROP USER [] - create the state file $env:LOCALAPPDATA\pim\hybrid\deploy--.json (no secrets) (this computer) -- -Resume continues where the run stopped Undo: delete the file -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. .LINK https://invardia.com/docs/pim/scripts/Deploy-PimHybridWorker/ #> [CmdletBinding(SupportsShouldProcess)] param( [ValidateSet('Azure', 'Arc')][string]$Target, [string]$SubscriptionId, [string]$TenantId, # Azure [string]$ResourceGroup = 'rg-pimhybridwrk', [string]$Location = 'westeurope', [string]$VmName = 'PIMHYBRIDWRK', [string]$VmSize = 'Standard_B2s', [string]$VnetName = 'vnet-pimhybridwrk', [string]$AddressPrefix = '10.221.16.0/24', [string]$SubnetPrefix = '10.221.16.0/27', [string[]]$PeerVnetId = @(), [string[]]$DnsServers = @(), [ValidateSet('NatGateway', 'None')][string]$Egress = 'NatGateway', # §100.63 DCs behind a hub VPN / ExpressRoute gateway (Set-PimHybridWorkerPeering.ps1 sets the flags) [switch]$UseRemoteGateways, [string]$GatewayVnetId = '', [switch]$AllowForwardedTraffic, # §100.63 a private-only PIM SQL: link the standard privatelink.database.windows.net zone + print the DNS forwarder [string]$PrivateSqlDnsZoneId = '', [string[]]$PrivateDnsLinkVnetId = @(), [switch]$CreatePrivateDnsZone, [string]$PrivateDnsZoneResourceGroup = '', [switch]$PrivateSql, [string]$DnsForwarderIp = '', # Arc [string]$ArcMachineResourceId = '', [string[]]$SqlAllowedPublicIp = @(), # the PIM environment [string]$SqlServerId = '', [string]$SqlDatabase = 'PimPlatform', [string]$SqlUserName = 'PIMHYBRIDWRK', [string]$SourceContainerId = '', [string]$Ring = '', [string]$Version = '', [switch]$GrantMailSend, [switch]$PlanOnly, # Active Directory (printed for the AD administrator; the worker is configured with them) [string]$AdDomain = '', [string]$WorkerComputerName = '', [string]$ComputerOu = '', [string]$GmsaName = 'gMSA-PIM-L1-T0', [string]$GmsaOu = '', [string]$GroupsOu = '', [string[]]$AdminAccountsOu = @(), [string]$ServerGmsaName = '', [string[]]$ServerOu = @(), [switch]$GrantProtectedGroupMembership, [switch]$CreateKdsRootKey, [string]$AdServer = '', # flow [switch]$Apply, [switch]$Resume, [string]$StatePath = '', [string]$KdsRootKeyCreatedUtc = '', [string]$OdjBlob = '', [string]$OdjBlobFile = '', [switch]$PrintOnly, [int]$JoinRebootWaitSeconds = 240, [string]$ScriptRoot = '' ) $ErrorActionPreference = 'Stop' # HYBRID-DEPLOY-STANDALONE: this file keeps '#Requires -Version 5.1' for the package; the PUBLISHED copy is for PowerShell 7 # BUILD-PUBLISH-REQUIRES-7 # 12.7 run frame: "