Public/Import-AzureADApp.ps1

function Import-AzureADApp {
    <#
    .SYNOPSIS
    Import Azure AD App name & API permissions from filesystem-based or GIST-based xml
 
    .DESCRIPTION
    Import Azure AD App name & API permissions from filesystem-based or GIST-based xml
 
    .PARAMETER Owner
    The owner of the application. For convenience, should be the owner
    that can grant admin consent of the requested API permissions
 
    .PARAMETER xmlPath
    Filesystem path to the xml created by Export-AzureAD
    Choose this or the Github paramters to grab the xml from a GIST
 
    .PARAMETER GithubUsername
    Github username where the GIST you wish to import lives
 
    .PARAMETER GistFilename
    filename of GIST, example: Test.xml
    This is the most recently created file named, Test.xml, for example.
    If there is more than one filename bypassed
 
    .PARAMETER SecretDurationYears
    Specify how many years the secret should live.
    If you dont use this parameter, no secret will be created
 
    .PARAMETER Name
    Name of the App to create in the target AzureAD tenant.
    If left blank, will use source tenant app name (plus timestamp of export)
 
    .PARAMETER ConsentAction
    Valid choices are OpenBrowser, OutputUrl, or Both
 
    Used to "grant admin consent" for the APIs via URL.
    1. Either the URL is provided via PowerShell console (can be copy/pasted into a browser)
    2. The URL is opened in the default browser
    3. or both
 
    Alternatively, and admin can simply login to the Azure portal then select the following:
 
    Azure AD > App Registrations > find/click the App > API permissions > Grant Admin Consent for Tenant
 
    .EXAMPLE
    Import-AzureADApp -Owner admin@contoso.onmicrosoft.com -GithubUsername kevinblumenfeld `
                      -GistFilename testapp.xml -Name NewApp09 -SecretDurationYears 1 -ConsentAction Both
 
    .EXAMPLE
    Import-AzureADApp -Owner admin@contoso.onmicrosoft.com `
                      -xmlPath C:\Scripts\TestApp-20200808-0349.xml
                      -Name NewApp01 -SecretDurationYears 1 -ConsentAction OutputUrl
 
    .NOTES
    If SecretDurationYears is choosen the Secret will be included with the object this function produces
 
    Example:
 
    DisplayName : AppTwo
    ApplicationId : e8f3a6cb-f3c1-4a7d-8cb2-6a72d99816fa
    TenantId : a334d325-ce6e-49be-bea1-b2058b125a8e
    ObjectId : b559ab26-31bf-4462-97e4-afc80dbb51e5
    Owner : demo@contoso.onmicrosoft.com
    Secret : kHOPOoOWL++Jir0yiYD3SN7bcIoYzvS+XeQJjju/Ef4=
 
 
    #>


    [cmdletbinding(DefaultParameterSetName = 'PlaceHolder')]
    param (

        [Parameter(Mandatory, ParameterSetName = 'FileSystem')]
        [Parameter(Mandatory, ParameterSetName = 'GIST')]
        [mailaddress]
        $Owner,

        [Parameter(Mandatory, ParameterSetName = 'FileSystem')]
        [string]
        [ValidateScript( { Test-Path $_ })]
        $xmlPath,

        [Parameter(Mandatory, ParameterSetName = 'GIST')]
        [string]
        $GithubUsername,

        [Parameter(Mandatory, ParameterSetName = 'GIST')]
        [string]
        $GistFilename,

        [Parameter(ParameterSetName = 'FileSystem')]
        [Parameter(ParameterSetName = 'GIST')]
        $SecretDurationYears,

        [Parameter(Mandatory, ParameterSetName = 'FileSystem')]
        [Parameter(Mandatory, ParameterSetName = 'GIST')]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'FileSystem')]
        [Parameter(ParameterSetName = 'GIST')]
        [ValidateSet('OpenBrowser', 'OutputUrl', 'Both')]
        [string]
        $ConsentAction
    )
    $NewAppSplat = @{ }
    if ($ConsentAction) {
        Write-Host 'Consent via Browser/URL requires adding the ReplyUrl, https://portal.azure.com to the new app' -ForegroundColor Cyan
        do {
            $Agree = Read-Host 'Okay to add https://portal.azure.com as a ReplyUrl? (Y/N)'
        } until ($Agree -match 'Y|N')
        if ($Agree -eq 'N') { continue }
        $NewAppSplat['ReplyUrls'] = 'https://portal.azure.com'
    }
    Write-Host "Owner: $Owner" -ForegroundColor Cyan -NoNewline
    try {
        $AppOwner = Get-AzureADUser -ObjectId $Owner -ErrorAction Stop
        Write-Host " Found" -ForegroundColor Green
    }
    catch {
        Write-Host " Not Found. Halting script" -ForegroundColor Red
        continue
    }
    try {
        $null = Get-AzureADApplication -filter "DisplayName eq '$Name'" -ErrorAction Stop
    }
    catch {
        Write-Host "Azure AD Application Name: $Name already exists" -ForegroundColor Red
        Write-Host "Choose a new name with the -Name parameter" -ForegroundColor Cyan
        continue
    }

    if ($PSCmdlet.ParameterSetName -eq 'FileSystem') { $App = Import-Clixml $xmlPath }
    else {
        try {
            $Tempfilepath = Join-Path -Path $Env:TEMP -ChildPath ('{0}.xml' -f [guid]::newguid().guid)
            (Get-CloneGist -Username $GithubUserName -Filename $GistFilename)[0].content | Set-Content -Path $Tempfilepath -ErrorAction Stop
            $App = Import-Clixml $Tempfilepath
        }
        catch {
            Write-Host "Error importing GIST $($_.Exception.Message)" -ForegroundColor Red
            continue
        }
        finally {
            Remove-Item -Path $Tempfilepath -Force -Confirm:$false -ErrorAction SilentlyContinue
        }
    }
    $Tenant = Get-AzureADTenantDetail
    try {
        $NewAppSplat['DisplayName'] = $Name
        $NewAppSplat['ErrorAction'] = 'Stop'
        $TargetApp = New-AzureADApplication @NewAppSplat
    }
    catch {
        Write-Host "Unable to create new application: $($_.Exception.Message)" -ForegroundColor Red
        continue
    }

    $Output = [ordered]@{ }
    $Output['DisplayName'] = $Name
    $Output['ApplicationId'] = $TargetApp.AppId
    $Output['TenantId'] = $Tenant.ObjectID
    $Output['ObjectId'] = $TargetApp.ObjectId
    $Output['Owner'] = $Owner

    $RequiredList = [System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]]::new()
    foreach ($ResourceAppId in $App['API'].keys) {
        $RequiredObject = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]::new()
        $AccessObject = [System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]]::new()
        foreach ($ResourceAccess in $App['API'][$ResourceAppId]['ResourceList']) {
            $AccessObject.Add([Microsoft.Open.AzureAD.Model.ResourceAccess]@{
                    Id   = $ResourceAccess.Id
                    Type = $ResourceAccess.Type
                })
        }
        $RequiredObject.ResourceAppId = $ResourceAppId
        $RequiredObject.ResourceAccess = $AccessObject
        $RequiredList.Add($RequiredObject)
    }
    Set-AzureADApplication -ObjectId $TargetApp.ObjectId -RequiredResourceAccess $RequiredList
    Add-AzureADApplicationOwner -ObjectId $TargetApp.ObjectId -RefObjectId $AppOwner.ObjectId

    if ($SecretDurationYears) {
        $Date = Get-Date
        $Params = @{
            ObjectId            = $TargetApp.ObjectId
            EndDate             = $Date.AddYears($SecretDurationYears)
            CustomKeyIdentifier = "{0}-{1}" -f $TargetApp.Displayname, $Date.ToString("yyyyMMddTHHmm")
        }
        $SecretResult = New-AzureADApplicationPasswordCredential @Params
        $Output['Secret'] = $SecretResult.value
    }

    if ($ConsentAction -match 'OutputUrl|Both') {
        Write-Host "Grant Admin Consent by logging in as $Owner here:`r`n" -ForegroundColor Cyan
        $ConsentURL = 'https://login.microsoftonline.com/{0}/v2.0/adminconsent?client_id={1}&state=12345&redirect_uri={2}&scope={3}&prompt=admin_consent' -f @(
            $Tenant.ObjectID, $TargetApp.AppId, 'https://portal.azure.com/', 'https://graph.microsoft.com/.default')

        Write-Host "$ConsentURL" -ForegroundColor Green
    }
    [PSCustomObject]$Output

    if ($ConsentAction -match 'OpenBrowser|Both') { Start-Process $ConsentURL }
}