Public/New-IntuneStaticGroup.ps1
|
function New-IntuneStaticGroup { <# .SYNOPSIS Creates a static Azure AD security group for Intune .DESCRIPTION Creates a static (assigned) security group. If a group with the same name exists, returns the existing group. For Autopilot device preparation groups, adds the Intune Provisioning Client as owner. .PARAMETER DisplayName The display name for the group .PARAMETER Description Description of the group .PARAMETER RequiresServicePrincipalOwner If set, adds the Intune Provisioning Client service principal as owner (required for Autopilot device preparation) .EXAMPLE New-IntuneStaticGroup -DisplayName "Intune - Update Ring Pilot Users" -Description "Users for pilot ring" .EXAMPLE New-IntuneStaticGroup -DisplayName "Windows Autopilot device preparation" -RequiresServicePrincipalOwner #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [string]$DisplayName, [Parameter()] [string]$Description = "", [Parameter()] [switch]$RequiresServicePrincipalOwner ) # Intune Provisioning Client app ID (required for Autopilot device preparation) $intuneProvisioningClientAppId = "f1346770-5b25-470b-88bd-d5744ab7952c" try { # If this group requires the service principal owner, ensure it exists first $servicePrincipalId = $null if ($RequiresServicePrincipalOwner) { Write-Verbose "Checking for Intune Provisioning Client service principal..." try { $spResponse = Invoke-MgGraphRequest -Method GET -Uri "v1.0/servicePrincipals?`$filter=appId eq '$intuneProvisioningClientAppId'" -ErrorAction Stop $existingSP = $spResponse.value | Select-Object -First 1 if ($existingSP) { $servicePrincipalId = $existingSP.id Write-Verbose "Found existing service principal: $servicePrincipalId" } else { # Create the service principal Write-Verbose "Service principal not found, creating..." if ($PSCmdlet.ShouldProcess("Intune Provisioning Client", "Create service principal")) { $newSP = Invoke-MgGraphRequest -Method POST -Uri "v1.0/servicePrincipals" -Body @{ appId = $intuneProvisioningClientAppId } -ErrorAction Stop $servicePrincipalId = $newSP.id Write-Verbose "Created service principal: $servicePrincipalId" } } } catch { $spError = $_.Exception.Message Write-HydrationLog -Message " Warning: Could not get/create service principal: $spError" -Level Warning # Continue without the service principal - group can still be created } } # Check if group already exists (escape single quotes for OData filter) $safeDisplayName = $DisplayName -replace "'", "''" $listUri = "v1.0/groups?`$filter=displayName eq '$safeDisplayName'" $existingGroup = $null do { $response = Invoke-MgGraphRequest -Method GET -Uri $listUri -ErrorAction Stop if ($response.value.Count -gt 0) { $existingGroup = $response.value[0] break } $listUri = $response.'@odata.nextLink' } while ($listUri) if ($existingGroup) { # If service principal owner is required, ensure it's an owner if ($RequiresServicePrincipalOwner -and $servicePrincipalId) { if ($PSCmdlet.ShouldProcess($DisplayName, "Ensure Intune Provisioning Client is owner")) { try { $ownerRef = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId" } Invoke-MgGraphRequest -Method POST -Uri "v1.0/groups/$($existingGroup.id)/owners/`$ref" -Body $ownerRef -ErrorAction Stop return New-HydrationResult -Name $existingGroup.displayName -Id $existingGroup.id -Type 'StaticGroup' -Action 'Updated' -Status 'Added service principal owner' } catch { # Check if the error is "already exists" - that's fine, means SP is already owner $errorMsg = $_.Exception.Message if ($_.ErrorDetails.Message) { $errorMsg = $_.ErrorDetails.Message } if ($errorMsg -like "*already exist*") { Write-Verbose "Service principal is already an owner of $DisplayName" return New-HydrationResult -Name $existingGroup.displayName -Id $existingGroup.id -Type 'StaticGroup' -Action 'Skipped' -Status 'Group already exists with correct owner' } # Re-throw other errors throw } } } return New-HydrationResult -Name $existingGroup.displayName -Id $existingGroup.id -Type 'StaticGroup' -Action 'Skipped' -Status 'Group already exists' } # Create new static group if ($PSCmdlet.ShouldProcess($DisplayName, "Create static group")) { $fullDescription = if ($Description) { "$Description - Imported by Intune Hydration Kit" } else { "Imported by Intune Hydration Kit" } # Generate a safe mailNickname (alphanumeric only, max 64 chars) $mailNickname = ($DisplayName -replace '[^a-zA-Z0-9]', '') if ($mailNickname.Length -gt 64) { $mailNickname = $mailNickname.Substring(0, 64) } # Ensure mailNickname is not empty if ([string]::IsNullOrWhiteSpace($mailNickname)) { $mailNickname = "group" + [guid]::NewGuid().ToString("N").Substring(0, 8) } $groupBody = @{ displayName = $DisplayName description = $fullDescription mailEnabled = $false mailNickname = $mailNickname securityEnabled = $true } Write-Verbose "Creating group with body: $($groupBody | ConvertTo-Json -Compress)" $newGroup = Invoke-MgGraphRequest -Method POST -Uri "v1.0/groups" -Body $groupBody -ErrorAction Stop # Add service principal as owner if required if ($RequiresServicePrincipalOwner -and $servicePrincipalId) { $ownerRef = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$servicePrincipalId" } Invoke-MgGraphRequest -Method POST -Uri "v1.0/groups/$($newGroup.id)/owners/`$ref" -Body $ownerRef -ErrorAction Stop return New-HydrationResult -Name $newGroup.displayName -Id $newGroup.id -Type 'StaticGroup' -Action 'Created' -Status 'Created with service principal owner' } return New-HydrationResult -Name $newGroup.displayName -Id $newGroup.id -Type 'StaticGroup' -Action 'Created' -Status 'New group created' } else { return New-HydrationResult -Name $DisplayName -Type 'StaticGroup' -Action 'WouldCreate' -Status 'DryRun' } } catch { $errMessage = Get-GraphErrorMessage -ErrorRecord $_ Write-HydrationLog -Message " Failed: $DisplayName - $errMessage" -Level Warning return New-HydrationResult -Name $DisplayName -Type 'StaticGroup' -Action 'Failed' -Status $errMessage } } |