#Requires -Version 7.2 <# .SYNOPSIS Delegates the Entra Policy Suite hybrid worker in Active Directory: the right to write EXACTLY the tag attributes you choose on user (and, for device tags, computer) objects - nothing else (DESIGN ยง6). Run once by a domain admin on a domain machine. .DESCRIPTION The same way for every hybrid worker (framework HYBRID-AD-1): - every right goes to the group "-PermissionGroup" (the gMSA its ONLY member) - never to the gMSA itself; the group is made in the gMSA's own container when it is missing; - at the DOMAIN ROOT, inherited by descendant objects of the classes you name (users are spread over many OUs): one Allow ACE per attribute and class, WriteProperty on that attribute; - only the attributes you list (extensionAttribute1-15 - your tag attributes; anything else is refused); - protected accounts (Domain Admins and the like) only with -IncludeProtectedAccounts: the same right on AdminSDHolder. Plan first: without -Apply it shows every step and changes nothing. With -Apply it makes the changes and reads them back. -Remove takes exactly these rights away again (the group stays). No module: .NET System.DirectoryServices. The gMSA itself (and its -PrincipalsAllowedAccess group, the worker computers that may use it) is made beforehand by your AD admin; this script finds it by name. .EXAMPLE .\Initialize-EpsHybridWorkerAd.ps1 -Gmsa 'gMSA-EPS-L1-T0' -Attributes extensionAttribute6, extensionAttribute7 .EXAMPLE .\Initialize-EpsHybridWorkerAd.ps1 -Gmsa 'gMSA-EPS-L1-T0' -Attributes extensionAttribute6, extensionAttribute7 -ObjectClasses user, computer -Apply .EXAMPLE .\Initialize-EpsHybridWorkerAd.ps1 -Gmsa 'gMSA-EPS-L1-T0' -Attributes extensionAttribute6 -IncludeProtectedAccounts -Apply Also protected admins: the same right on AdminSDHolder - a Tier 0 decision. .LINK https://invardia.com/docs/eps/scripts/Initialize-EpsHybridWorkerAd #> # PUBLISHED as a self-contained launcher of Initialize-EpsHybridWorkerAd.ps1 (EntraPolicySuite 2.0.108, Publish-EpsHandover): it runs release 2.0.108 unless -EpsVersion picks another. [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][ValidatePattern('^([A-Za-z0-9._-]{1,15}\\)?[A-Za-z0-9._-]{1,15}\$?$')][string]$Gmsa, [Parameter(Mandatory)][ValidatePattern('^extensionAttribute([1-9]|1[0-5])$')][string[]]$Attributes, [ValidateSet('user', 'computer')][string[]]$ObjectClasses = @('user'), [string]$ProductOu, [string]$Server, [switch]$IncludeProtectedAccounts, [switch]$Apply, [switch]$Remove, [ValidatePattern('^$|^2\.\d+\.\d+$')][string]$EpsVersion = '2.0.108' ) $ErrorActionPreference = 'Stop' # A self-contained launcher of Initialize-EpsHybridWorkerAd.ps1 (invardia.com/support/eps/): it downloads the EntraPolicySuite release this script # was handed over for (2.0.108 - the version of your install's ring; -EpsVersion picks another, -EpsVersion '' the # latest), checks it against the release's published SHA-256 digest and runs the release's own Initialize-EpsHybridWorkerAd.ps1 with the # parameters you gave. Nothing is installed on this machine; the download goes to a temporary folder. $what = [bool]$WhatIfPreference; $WhatIfPreference = $false $repo = 'KnudsenMorten/EntraPolicySuite'; $h = @{ 'User-Agent' = 'EntraPolicySuite-support' } $rel = Invoke-RestMethod -Uri $(if ($EpsVersion) { "https://api.github.com/repos/$repo/releases/tags/v$EpsVersion" } else { "https://api.github.com/repos/$repo/releases?per_page=30" }) -Headers $h $rel = @($rel | Where-Object { $_.tag_name -match '^v2\.' -and -not $_.draft -and -not $_.prerelease } | Sort-Object { [version]($_.tag_name.TrimStart('v') -replace '[^0-9.].*$', '') } -Descending)[0] if (-not $rel) { Write-Host 'No EntraPolicySuite v2 release found.' -ForegroundColor Red; exit 3 } $asset = @($rel.assets | Where-Object { $_.name -match '^EntraPolicySuite-v2[0-9A-Za-z.+-]*\.zip$' })[0] if (-not $asset) { Write-Host "Release $($rel.tag_name) has no EntraPolicySuite-v2*.zip." -ForegroundColor Red; exit 3 } $dir = Join-Path ([IO.Path]::GetTempPath()) "eps-$($rel.tag_name)-$([guid]::NewGuid().ToString('N').Substring(0, 6))" New-Item -ItemType Directory -Path $dir | Out-Null $zip = Join-Path $dir $asset.name Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zip -Headers $h $got = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant() $published = if ("$($asset.digest)" -match '^(?i)sha256:([0-9a-f]{64})$') { $Matches[1].ToLowerInvariant() } else { '' } if (-not $published -or $got -ne $published) { [IO.Directory]::Delete($dir, $true); Write-Host "The download does not match the release's SHA-256 - deleted, nothing run." -ForegroundColor Red; exit 3 } Write-Host "Release $($rel.tag_name), SHA-256 $got (matches the release)" Expand-Archive -LiteralPath $zip -DestinationPath $dir -Force $script = Get-ChildItem -LiteralPath $dir -Recurse -Filter 'Initialize-EpsHybridWorkerAd.ps1' -File | Select-Object -First 1 if (-not $script) { Write-Host 'The release has no Initialize-EpsHybridWorkerAd.ps1.' -ForegroundColor Red; exit 3 } $pass = @{} foreach ($k in @($PSBoundParameters.Keys)) { if ($k -notin 'EpsVersion', 'WhatIf', 'Confirm') { $pass[$k] = $PSBoundParameters[$k] } } if ($what) { $pass.WhatIf = $true } & $script.FullName @pass exit $LASTEXITCODE