Skip to content

Instantly share code, notes, and snippets.

@griffeth-barker
Created January 9, 2026 18:56
Show Gist options
  • Select an option

  • Save griffeth-barker/c72ef9d62fae3f1016bee6d41ef6d7c5 to your computer and use it in GitHub Desktop.

Select an option

Save griffeth-barker/c72ef9d62fae3f1016bee6d41ef6d7c5 to your computer and use it in GitHub Desktop.
Create EntraID Enterprise Apps with SSO and Security Groups
<#
.SYNOPSIS
Creates necessary componenets for an EntraID Enterprise Application with SAML SSO.
.DESCRIPTION
Creates an Application Registration, Enterprise Application, and two Security Groups
in Microsoft EntraID based on provided parameters and associates them as necessary.
.PARAMETER Name
The name of the enterprise application. This is used to formulate the groups names
as well.
.PARAMETER IdentifierUri
The Microsoft EntraID Identifier. This is the first text field in the SAML SSO setup
GUI.
.PARAMETER ReplyUri
The ACS/Reply URL. This would be the second text field in the SAML SSO setup GUI.
.PARAMETER OwnerUPN
The UPN of the user who should be assigned as the owner of the User group.
.PARAMETER Note
An optional string to be added to the description of the groups and the notes
field of the enterprise application. Helpful for including a reference ID to
an internal ticketing system, etc.
.INPUTS
System.String[]
.OUTPUTS
System.Management.Automation.PSObject[]
.EXAMPLE
$appParams = @{
Name = 'App Name'
IdentifierUri = 'https://example.domain.tld/app'
ReplyUri = 'https://example.domain.tld/saml/acs'
OwnerUPN = 'admin@domain.tld'
Note = 'Requested by person in reference #12345'
}
New-GJBEntraAppDeployment $appParams
Create a single enterprise app with SAML SSO.
.EXAMPLE
Import-Csv -Path "C:\path\to\appsToCreate.csv" | New-GJBEntraAppDeployment
Create many enterprise apps with SAML SSO based on CSV input.
.NOTES
Requires Cloud Application Administrator or Global Administrator role in EntraID.
Requires connection to Graph API with the following scopes:
- 'Group.ReadWrite.all'
- 'Application.ReadWrite.All'
- 'AppRoleAssignment.ReadWrite.All'
#>
function New-GJBEntraAppDeployment {
[CmdletBinding()]
[OutputType([System.Management.Automation.PSObject])]
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty]
[string]$Name,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty]
[string]$IdentifierUri,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty]
[string]$ReplyUri,
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty]
[string]$UserGroupOwnerUpn,
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Note
)
begin {
function timestamp {
Get-Date -Format 'yyyy-MM-dd hh:mm:ss'
}
try {
Write-Verbose "[$(timestamp)] Check for Microsoft Graph Connection"
$MgContext = Get-MgContext -ErrorAction Stop
Write-Verbose "[$(timestamp)] Connected to Tenant: $($MgContext.TenantId) as $($MgContext.Account)"
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
exit 1
}
Write-Verbose "[$(timestamp)] Deployment cycle beginning"
}
process {
try {
Write-Verbose "[$(timestamp)] Beginning processing for $($Name)"
Write-Verbose "[$(timestamp)] Creating security groups '$($Name) Users Security' and '$($Name) Admins Security'"
$CleanNickname = $Name -replace '[^a-zA-Z0-9]', '' # mailNickname is mandatory and must be alphanumeric
$TargetOwner = Get-MgUser -UserId $UserGroupOwnerUpn -ErrorAction Stop
$Me = Get-MgUser -UserId $($MgContext.Account) -ErrorAction Stop
$GroupParams = @{
MailEnabled = $false
SecurityEnabled = $true
Description = $Note
}
try {
$UserGroup = New-MgGroup @GroupParams -DisplayName "$Name Users" -MailNickname "$($CleanNickname)Users"
$AdminGroup = New-MgGroup @GroupParams -DisplayName "$Name Admins" -MailNickname "$($CleanNickname)Admins"
$userGroupOwnerOdata = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$($TargetOwner.Id)"
}
New-MgGroupOwnerByRef -GroupId $UserGroup.Id -BodyParameter $userGroupOwnerOdata
$adminGroupOwnerOdata = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$($Me.Id)"
}
New-MgGroupOwnerByRef -GroupId $AdminGroup.Id -BodyParameter $adminGroupOwnerOdata
Write-Verbose "[$(timestamp)] Created security groups"
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
}
Write-Verbose "[$(timestamp)] Creating app registration $Name"
$AppParams = @{
DisplayName = $Name
IdentifierUris = @($IdentifierUri)
Web = @{ RedirectUris = @($ReplyUri) }
}
try {
$AppReg = New-MgApplication @AppParams -ErrorAction Stop
Write-Verbose "[$(timestamp)] Created app registration $Name"
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
}
Write-Verbose "[$(timestamp)] Provisioning enterprise app '$Name' with SAML SSO"
$SpParams = @{
AppId = $AppReg.AppId
PreferredSingleSignOnMode = "saml"
AppRoleAssignmentRequired = $true
Notes = $Note
Tags = @("WindowsAzureActiveDirectoryCustomSingleSignOnApplication", "WindowsAzureActiveDirectoryIntegratedApp")
}
try {
$ServicePrincipal = New-MgServicePrincipal @SpParams -ErrorAction Stop
Write-Verbose "[$(timestamp)] Provisioned enterprise app '$Name' with SAML SSO"
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
}
Write-Verbose "[$(timestamp)] Assigning security groups to enterprise app"
$AssignmentParams = @{
ServicePrincipalId = $ServicePrincipal.Id
ResourceId = $ServicePrincipal.Id # the app itself
Id = "00000000-0000-0000-0000-000000000000" # default role Id
}
try {
New-MgServicePrincipalAppRoleAssignedTo @AssignmentParams -PrincipalId $UserGroup.Id | Out-Null
New-MgServicePrincipalAppRoleAssignedTo @AssignmentParams -PrincipalId $AdminGroup.Id | Out-Null
Write-Verbose "[$(timestamp)] Assigned security groups to enterprise app"
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
}
Write-Verbose "[$(timestamp)] Done processing for $($Name)"
# output: emit a PSCustomObject
[PSCustomObject]@{
ApplicationId = $AppReg.AppId
ApplicationName = $Name
SamlIdentifier = $IdentifierUri
SamlReplyUri = $ReplyUri
SamlMetadataUrl = "https://login.microsoftonline.com/$($MgContext.TenantId)/federationmetadata/2007-06/federationmetadata.xml?appid=$($AppReg.AppId)"
UserGroupObjectId = $UserGroup.Id
UserGroupDisplayName = $UserGroup.DisplayName
AdminGroupObjectId = $AdminGroup.Id
AdminGroupDisplayName = $AdminGroup.DisplayName
}
}
catch {
Write-Error "[$(timestamp)] $($_.ErrorDetails) - $($_.Exception.Message))"
}
}
end {
Write-Verbose "[$(timestamp)] Deployment cycle complete."
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment