Public/New-IntuneAppGroup.ps1
|
<# .SYNOPSIS Creates Intune app assignment groups (for example "Available" and "Required") in Microsoft Entra ID. .DESCRIPTION The New-IntuneAppGroup function creates one security group in Microsoft Entra ID for each application name and each assignment intent. By default the "Available" and "Required" groups are created; use -Intent to choose (Available, Required, Uninstall). Groups are named 'Intune-AG-<Name>-<Intent>' (app groups) or 'Intune-ACG-<Name>-<Intent>' (app collection groups, with -Collection). The name is written in PascalCase with spaces removed: the first letter of each word is upper-cased (culture-independent) and the rest is kept, for example 'company portal' becomes 'CompanyPortal' and '7-zip' stays '7-zip'. The description is 'Intune <Intent> assignment group for <Name>.' The mail nickname is the group name made valid for Entra ID: accents are removed (an A with an umlaut becomes A), characters Entra ID does not allow are removed, a name with no usable characters left gets a short stable hash instead, and a nickname longer than 64 characters is shortened with a hash of the full group name added, so different applications keep different nicknames. Each group is looked up first. An existing group (same display name) is not created again: a warning is written and it is returned with Status 'Existing'. Only groups that do not exist yet are passed to -WhatIf/-Confirm and created. For every group an object is written to the pipeline with the properties Name, Id, AppName, Intent, Status (Created, Existing, Failed or WhatIf) and MailNickname. DisplayName is an alias of Name. Requires the Microsoft Entra PowerShell module (Microsoft.Entra or Microsoft.Entra.Groups) and an authenticated session (Connect-Entra) with permission to create groups, for example the Group.ReadWrite.All scope. When Get-EntraContext shows no session, the command stops with one error asking you to run Connect-Entra. Supports -WhatIf and -Confirm. .PARAMETER Name One or more application names. One group is created for each name and intent. Accepts pipeline input, and pipeline input by property name (Name, AppName or DisplayName). .PARAMETER Intent The assignment intents to create groups for: Available, Required and/or Uninstall. The default is Available and Required. .PARAMETER Collection Create app collection groups ('Intune-ACG-...') instead of app groups ('Intune-AG-...'). .INPUTS System.String Application names. System.Management.Automation.PSObject Objects with a Name, AppName or DisplayName property. .OUTPUTS Tcs.Azure.IntuneAppGroup One object for each group, with Name, Id, AppName, Intent, Status and MailNickname. .EXAMPLE New-IntuneAppGroup -Name 'App1', 'App2' Creates Intune-AG-App1-Available, Intune-AG-App1-Required, Intune-AG-App2-Available and Intune-AG-App2-Required, and returns an object for each of them. .EXAMPLE New-IntuneAppGroup -Collection -Name 'office apps' -WhatIf Shows that Intune-ACG-OfficeApps-Available and Intune-ACG-OfficeApps-Required would be created, without creating them. Groups that already exist are returned with Status 'Existing'. .EXAMPLE 'Company Portal', '7-Zip' | New-IntuneAppGroup -Intent Required, Uninstall Creates the Required and Uninstall groups for both applications. .EXAMPLE New-IntuneAppGroup -Name 'Company Portal' | Where-Object Status -EQ 'Created' Returns only the groups that were created by this run. .NOTES Author: Nigel Tatschner Company: TheCodeSaiyan .LINK Get-IntuneAppGroup .LINK Remove-IntuneAppGroup .LINK https://learn.microsoft.com/powershell/module/microsoft.entra.groups/new-entragroup #> function New-IntuneAppGroup { [CmdletBinding(SupportsShouldProcess = $true)] [OutputType('Tcs.Azure.IntuneAppGroup')] param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('AppName', 'DisplayName')] [ValidateNotNullOrEmpty()] [string[]] $Name, [ValidateSet('Available', 'Required', 'Uninstall')] [string[]] $Intent = @('Available', 'Required'), [switch] $Collection ) begin { $TelemetryArgs = @{ ModuleName = $MyInvocation.MyCommand.Module.Name ModuleVersion = [string]$MyInvocation.MyCommand.Module.Version CommandName = $MyInvocation.MyCommand.Name ExecutionID = [guid]::NewGuid().ToString() } Invoke-TelemetryCollection @TelemetryArgs -Stage Start -ClearTimer $lastError = $null $telemetrySent = $false $prerequisiteError = Get-EntraPrerequisiteError -CommandName $MyInvocation.MyCommand.Name -RequiredCommand 'Get-EntraGroup', 'New-EntraGroup' if ($prerequisiteError) { $telemetrySent = $true Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $prerequisiteError $PSCmdlet.ThrowTerminatingError($prerequisiteError) } $groupType = if ($Collection) { 'ACG' } else { 'AG' } # Canonical casing and order of first use; ValidateSet itself is case-insensitive $intents = New-Object -TypeName 'System.Collections.Generic.List[string]' foreach ($requestedIntent in $Intent) { $canonical = @('Available', 'Required', 'Uninstall') | Where-Object { [string]::Equals($_, $requestedIntent, [System.StringComparison]::OrdinalIgnoreCase) } if (-not $intents.Contains($canonical)) { $intents.Add($canonical) } } } process { $completed = $false try { foreach ($appName in $Name) { $pascalName = ConvertTo-TcsPascalCaseName -Text $appName if (-not $pascalName) { $exception = New-Object -TypeName System.ArgumentException -ArgumentList 'An application name cannot be blank or whitespace only.', 'Name' $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, 'BlankName', ([System.Management.Automation.ErrorCategory]::InvalidArgument), $appName $lastError = $errorRecord $PSCmdlet.WriteError($errorRecord) continue } $friendlyName = @($appName -split '\s+' | Where-Object { $_ }) -join ' ' foreach ($groupIntent in $intents) { Write-Progress -Activity 'Creating Intune groups' -Status "Processing $pascalName ($groupIntent)" $displayName = "Intune-$groupType-$pascalName-$groupIntent" $result = [pscustomobject]@{ PSTypeName = 'Tcs.Azure.IntuneAppGroup' Name = $displayName Id = $null AppName = $friendlyName Intent = $groupIntent Status = $null MailNickname = $null } $result | Add-Member -MemberType AliasProperty -Name DisplayName -Value Name try { # Look up first so -WhatIf and -Confirm only cover groups that would really be created. # Single quotes are doubled so the name is a valid OData string literal. $filter = "DisplayName eq '$($displayName -replace "'", "''")'" $existing = @(Get-EntraGroup -Filter $filter -ErrorAction Stop) if ($existing.Count -gt 0) { Write-Warning "Group `"$displayName`" already exists." $result.Id = $existing[0].Id $result.MailNickname = $existing[0].MailNickname $result.Status = 'Existing' $result continue } $result.MailNickname = ConvertTo-TcsMailNickname -Text $pascalName -Prefix "Intune-$groupType-" -Suffix "-$groupIntent" -HashSource $displayName if (-not $PSCmdlet.ShouldProcess($displayName, 'Create Entra ID security group')) { if ($WhatIfPreference) { $result.Status = 'WhatIf' $result } continue } $groupParams = @{ DisplayName = $displayName MailNickname = $result.MailNickname Description = "Intune $groupIntent assignment group for $friendlyName." MailEnabled = $false SecurityEnabled = $true } Write-Verbose "Creating Intune group `"$displayName`"" $group = New-EntraGroup @groupParams -ErrorAction Stop $result.Id = $group.Id $result.Status = 'Created' Write-Verbose "Group `"$displayName`" created." $result } catch [System.Management.Automation.PipelineStoppedException] { throw } catch { $lastError = $_ $result.Status = 'Failed' $result Write-Error -ErrorRecord $_ } } } $completed = $true } catch { # Reached when the caller asked for errors to stop (-ErrorAction Stop) $lastError = $_ throw } finally { # The end block does not run after a terminating error or when a downstream command # (for example Select-Object -First) stops the pipeline, so End telemetry is sent here. if (-not $completed -and -not $telemetrySent) { $telemetrySent = $true Write-Progress -Activity 'Creating Intune groups' -Completed if ($lastError) { Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $lastError } else { Invoke-TelemetryCollection @TelemetryArgs -Stage End } } } } end { if (-not $telemetrySent) { $telemetrySent = $true Write-Progress -Activity 'Creating Intune groups' -Completed if ($lastError) { Invoke-TelemetryCollection @TelemetryArgs -Stage End -Failed $true -Exception $lastError } else { Invoke-TelemetryCollection @TelemetryArgs -Stage End } } } } |