Use-AzureAD.psm1


#
## Created by: lucas.cueff[at]lucas-cueff.com
#
## released on 04/2020
#
# v0.5 : first public release - beta version - cmdlets to manage your Azure Active Directory Tenant (focusing on Administrative Unit features) when AzureADPreview cannot handle it correctly ;-)
# Note : currently Powershell Core and AzureADPreview are not working well together (logon / token request issue) : https://github.com/PowerShell/PowerShell/issues/10473 ==> this module will work only with Windows Powershell 5.1
# - cmdlet to get a valid access token (MFA supported) for Microsoft Graph Beta APIs
# - cmdlet to get a valid token for Microsoft Graph API standard / cloud endpoint (ressource graph.windows.net) and be able to use AzureADPreview cmdlets without reauthenticating
# - cmdlet to get all properties available (ex : extensionattribute) for an AAD user account
# - cmdlet to set a web proxy to be used with Use-AzureAD and AzureADPreview cmdlets
# - cmdlet to get all info for current logged in (@ Azure AD Tenant and Graph APIs) AAD user account
# - cmdlet to create / synchronize your on premise Active Directory OUs with Azure AD Administrive Units (not managed currently through Azure AD Connect or other Microsoft cmdlets / modules)
# - cmdlet to add / synchronize your on premise Active Directory users DN with Azure AD Administrative Unit membership (not managed currently through Azure AD Connect or other Microsoft cmdlets / modules)
# - cmdlet to add / remove Azure AD user account in Administrative Unit Role (everything managed in an easy and smooth way including, enabling the AAD role if missing and so on)
# - cmdlet to list all members of an Azure AD Administrative Unit (limited @ first 100 objets with default MS cmdlet... #WTF)
#
#'(c) 2020 lucas-cueff.com - Distributed under Artistic Licence 2.0 (https://opensource.org/licenses/artistic-license-2.0).'

<#
    .SYNOPSIS
    cmdlets to use several APIs of Microsoft Graph Beta web service (mainly users,me,AdministrativeUnit)
    extend AzureADPreview capabilities in Azure AD Administrative Unit management
 
    .DESCRIPTION
    use-AzureAD.psm1 module provides easy to use cmdlets to manage your Azure AD tenant with a focus on Administrative Unit objects.
     
    .EXAMPLE
    C:\PS> import-module use-AzureAD.psm1
#>

Function Get-AzureADAccessToken {
<#
    .SYNOPSIS
    Get a valid Access Token / Refresh Token for MS Graph APIs and MS Graph APIs Beta
 
    .DESCRIPTION
    Get a valid Access Token / Refresh Token for MS Graph APIs and MS Graph APIs Beta, using ADAL library, all authentication supported including MFA. Tenant ID automatically resolved.
     
    .PARAMETER adminUPN
    -adminUPN System.Net.Mail.MailAddress
    UserPrincipalName of an Azure AD account with rights on Directory (for instance a user with Global Admin right)
         
    .OUTPUTS
       TypeName : System.Management.Automation.PSCustomObject
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    AccessToken NoteProperty string AccessToken=xxxxx...
    ObjectID NoteProperty string ObjectID=e3ab4983-22c4-417b-b18d-0271f81a9cde
    TenantID NoteProperty string TenantID=fbf266be-12e8-48a4-bd5f-c513713bd96d
    TokenExpiresOn NoteProperty DateTimeOffset TokenExpiresOn=26/04/2020 16:20:40 +00:00
    UserName NoteProperty mailaddress UserName=my-admin@mydomain.tld
         
    .EXAMPLE
    Get an access token for my admin account (my-admin@mydomain.tld)
    C:\PS> Get-AzureADAccessToken -adminUPN my-admin@mydomain.tld
#>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$true)]
            [System.Net.Mail.MailAddress]$adminUPN
    )
    Process {
    $clientId = "1b730954-1685-4b74-9bfd-dac224a7b894"
    #$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
    $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
    $resourceURI = "https://graph.microsoft.com"
    $authority = "https://login.microsoftonline.com/$($adminUPN.Host)"
    $AadModule = Test-ADModule -AzureAD
    try {
        $adallib = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalformslib = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
        [System.Reflection.Assembly]::LoadFrom($adallib) | Out-Null
        [System.Reflection.Assembly]::LoadFrom($adalformslib) | Out-Null
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
        $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
        $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($adminUPN.Address, "OptionalDisplayableId")
        $authResult = $authContext.AcquireTokenAsync($resourceURI, $ClientId, $redirectUri, $platformParameters, $userId)
    } catch {
        Write-Error -Message "$($_.Exception.Message)"
        throw "Not Able to log you on your Azure AD Tenant - exiting"
    }
    if ($authResult.result) {
        $tmpobj = [PSCustomObject]@{
            UserName = $adminUPN
            AccessToken = $authResult.result.AccessToken
            TokenExpiresOn = $authResult.result.ExpiresOn
        }
        $global:AADConnectInfo = $tmpobj.psobject.Copy()
        $tmpobj | add-member -MemberType NoteProperty -Name 'ObjectID' -Value (Get-AzureADMyInfo).id
        $tmpobj | add-member -MemberType NoteProperty -Name 'TenantID' -Value (Get-AzureADTenantInfo -adminUPN $adminUPN).TenantID
    } else {
        throw "Authorization Access Token is null, please re-run authentication - exiting"
    }
    $global:AADConnectInfo = $tmpobj.psobject.Copy()
    $global:AADConnectInfo
    }
}
Function Get-AzureADMyInfo {
<#
    .SYNOPSIS
    Get all Azure AD account properties of current logged in user
 
    .DESCRIPTION
    Get all Azure AD account properties of current logged in user. Note : including hidden properties like extensionattribute.
         
    .OUTPUTS
       TypeName : System.Management.Automation.PSCustomObject
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    @odata.context NoteProperty string @odata.context=https://graph.microsoft.com/beta/$metadata#users/$entity
    accountEnabled NoteProperty bool accountEnabled=True
    ageGroup NoteProperty object ageGroup=null
    assignedLicenses NoteProperty Object[] assignedLicenses=System.Object[]
    assignedPlans NoteProperty Object[] assignedPlans=System.Object[]
    businessPhones NoteProperty Object[] businessPhones=System.Object[]
    city NoteProperty object city=null
    companyName NoteProperty object companyName=null
    consentProvidedForMinor NoteProperty object consentProvidedForMinor=null
    country NoteProperty object country=null
    createdDateTime NoteProperty string createdDateTime=2020-04-21T15:17:08Z
    creationType NoteProperty object creationType=null
    deletedDateTime NoteProperty object deletedDateTime=null
    department NoteProperty object department=null
    deviceKeys NoteProperty Object[] deviceKeys=System.Object[]
    displayName NoteProperty string displayName=admin
    employeeId NoteProperty object employeeId=null
    externalUserState NoteProperty object externalUserState=null
    externalUserStateChangeDateTime NoteProperty object externalUserStateChangeDateTime=null
    faxNumber NoteProperty object faxNumber=null
    givenName NoteProperty string givenName=firsname
    id NoteProperty string id=72a50bb8-20cf-494c-969d-fbcd2324b822
    identities NoteProperty Object[] identities=System.Object[]
    imAddresses NoteProperty Object[] imAddresses=System.Object[]
    infoCatalogs NoteProperty Object[] infoCatalogs=System.Object[]
    isResourceAccount NoteProperty object isResourceAccount=null
    jobTitle NoteProperty object jobTitle=null
    legalAgeGroupClassification NoteProperty object legalAgeGroupClassification=null
    mail NoteProperty object mail=null
    mailNickname NoteProperty string mailNickname=my-admin
    mobilePhone NoteProperty string mobilePhone=
    officeLocation NoteProperty object officeLocation=null
    onPremisesDistinguishedName NoteProperty object onPremisesDistinguishedName=null
    onPremisesDomainName NoteProperty object onPremisesDomainName=null
    onPremisesExtensionAttributes NoteProperty System.Management.Automation.PSCustomObject onPremisesExtensionAttributes=@{extensionAttribute1=; extensionAttribute2=; extensionAttribute3=; extensionAttribute...
    onPremisesImmutableId NoteProperty object onPremisesImmutableId=null
    onPremisesLastSyncDateTime NoteProperty object onPremisesLastSyncDateTime=null
    onPremisesProvisioningErrors NoteProperty Object[] onPremisesProvisioningErrors=System.Object[]
    onPremisesSamAccountName NoteProperty object onPremisesSamAccountName=null
    onPremisesSecurityIdentifier NoteProperty object onPremisesSecurityIdentifier=null
    onPremisesSyncEnabled NoteProperty object onPremisesSyncEnabled=null
    onPremisesUserPrincipalName NoteProperty object onPremisesUserPrincipalName=null
    otherMails NoteProperty Object[] otherMails=System.Object[]
    passwordPolicies NoteProperty object passwordPolicies=null
    passwordProfile NoteProperty object passwordProfile=null
    postalCode NoteProperty object postalCode=null
    preferredDataLocation NoteProperty object preferredDataLocation=null
    preferredLanguage NoteProperty object preferredLanguage=null
    provisionedPlans NoteProperty Object[] provisionedPlans=System.Object[]
    proxyAddresses NoteProperty Object[] proxyAddresses=System.Object[]
    refreshTokensValidFromDateTime NoteProperty string refreshTokensValidFromDateTime=2020-04-21T15:24:29Z
    showInAddressList NoteProperty object showInAddressList=null
    signInSessionsValidFromDateTime NoteProperty string signInSessionsValidFromDateTime=2020-04-21T15:24:29Z
    state NoteProperty object state=null
    streetAddress NoteProperty object streetAddress=null
    surname NoteProperty string surname=name
    usageLocation NoteProperty string usageLocation=US
    userPrincipalName NoteProperty string userPrincipalName=my-admin@mydomain.tld
    userType NoteProperty string userType=Member
         
    .EXAMPLE
    Get all user account properties of my current account (my-admin@mydomain.tld)
    C:\PS> Get-AzureADMyInfo
#>

    [cmdletbinding()]
    Param ()
    process {
        Test-AzureADAccesToken
        Invoke-APIMSGraphBeta -API "me" -Method "GET"
    }
}
Function Get-AzureADTenantInfo {
<#
    .SYNOPSIS
    Get a valid Access Tokem / Refresh Token for MS Graph APIs and MS Graph APIs Beta
 
    .DESCRIPTION
    Get a valid Access Tokem / Refresh Token for MS Graph APIs and MS Graph APIs Beta, using ADAL library, all authentication supported including MFA. Tenant ID automatically resolved.
     
    .PARAMETER adminUPN
    -adminUPN System.Net.Mail.MailAddress
    UserPrincipalName of an Azure AD account with rights on Directory (for instance a user with Global Admin right)
         
    .OUTPUTS
       TypeName : System.Management.Automation.PSCustomObject
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    AccessToken NoteProperty string AccessToken=xxxxx...
    ObjectID NoteProperty string ObjectID=e3ab4983-22c4-417b-b18d-0271f81a9cde
    TenantID NoteProperty string TenantID=fbf266be-12e8-48a4-bd5f-c513713bd96d
    TokenExpiresOn NoteProperty DateTimeOffset TokenExpiresOn=26/04/2020 16:20:40 +00:00
    UserName NoteProperty mailaddress UserName=my-admin@mydomain.tld
         
    .EXAMPLE
    Get an access token for my admin account (my-admin@mydomain.tld)
    C:\PS> Get-AzureADTenantInfo -adminUPN my-admin@mydomain.tld
#>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$true)]
            [System.Net.Mail.MailAddress]$adminUPN
    )
    process {
        $url = "https://login.microsoftonline.com/$($adminUPN.Host)/.well-known/openid-configuration"
        write-verbose -Message "GET method to $($url)"
        Try {
            $tmpobj = (Invoke-WebRequest $url).content | ConvertFrom-Json
        } catch {
            $tmpobj = $_ | ConvertFrom-Json
        }
        if ($tmpobj.issuer) {
            $tmpobj | Add-Member -MemberType NoteProperty -Name 'TenantID' -Value ([uri]$tmpobj.issuer).AbsolutePath.Replace("/","")
        }
        $tmpobj
    }
}
Function Connect-AzureADFromAccessToken {
<#
    .SYNOPSIS
    Connect to your Azure AD Tenant / classic MS Graph endpoint used by AzureADPreview module using an existing Access token requested with Get-AzureADAccessToken
 
    .DESCRIPTION
    Connect to your Azure AD Tenant / classic MS Graph endpoint used by AzureADPreview module using an existing Access token requested with Get-AzureADAccessToken
             
    .OUTPUTS
       TypeName : Microsoft.Open.Azure.AD.CommonLibrary.PSAzureContext
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    Account Property Microsoft.Open.Azure.AD.CommonLibrary.AzureAccount Account {get;}
    Environment Property Microsoft.Open.Azure.AD.CommonLibrary.AzureEnvironment Environment {get;}
    Tenant Property Microsoft.Open.Azure.AD.CommonLibrary.AzureTenant Tenant {get;}
    TenantDomain Property string TenantDomain {get;}
    TenantId Property guid TenantId {get;}
         
    .EXAMPLE
    Connect to your Azure AD Tenant / classic MS Graph endpoint used by AzureADPreview module using an existing Access token requested with Get-AzureADAccessToken
    C:\PS> Connect-AzureADFromAccessToken
#>

    [cmdletbinding()]
    Param ()
    Test-AzureADAccesToken
    $AadModule = Test-ADModule -AzureAD
    if ($global:AADConnectInfo.AccessToken) {
        $resourceURI = "https://graph.windows.net"
        $clientId = "1b730954-1685-4b74-9bfd-dac224a7b894"
        $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
        $authority = "https://login.microsoftonline.com/$(($global:AADConnectInfo.UserName).host)"
        try {
            $adallib = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
            $adalformslib = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
            [System.Reflection.Assembly]::LoadFrom($adallib) | Out-Null
            [System.Reflection.Assembly]::LoadFrom($adalformslib) | Out-Null
            $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
            $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
            $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($global:AADConnectInfo.UserName, "OptionalDisplayableId")
            $authResult = $authContext.AcquireTokenAsync($resourceURI, $ClientId, $redirectUri, $platformParameters, $userId)
        } catch {
            Write-Error -Message "$($_.Exception.Message)"
            throw "Not Able to log you on your Azure AD Tenant - exiting"
        }        
        connect-azuread -tenantid $global:AADConnectInfo.TenantID -AadAccessToken $authResult.result.AccessToken -AccountId $global:AADConnectInfo.ObjectID
    } else {
        throw "No valid Access Token found - exiting"
    }
}
Function Set-AzureADProxy {
<#
    .SYNOPSIS
    Set a web proxy to connect to Azure AD graph API
 
    .DESCRIPTION
    Set / remove a proxy to connect to your Azure AD environment using AzureADPreview module or this module. Can handle anonymous proxy or authenticating proxy.
     
    .PARAMETER DirectNoProxy
    -DirectNoProxy Swith
    Remove proxy set, set to "direct" connection
     
    .PARAMETER Proxy
    -Proxy uri
    Set the proxy settings to URI provided. Must be provided as a valid URI like http://proxy:port
     
    .PARAMETER ProxyCredential
    -ProxyCredential Management.Automation.PSCredential
    must be use with Proxy parameter
    Set the credential to be used with the proxy to set. Must be provided as a valid PSCredential object (can be generated with Get-Credential)
 
    .PARAMETER ProxyUseDefaultCredentials
    -ProxyUseDefaultCredentials Swith
    must be use with Proxy parameter
    Set the credential to be used with the proxy to set. this switch will tell the system to use the current logged in credential to be authenticated with the proxy service.
         
    .OUTPUTS
       TypeName : System.Net.WebProxy
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetObjectData Method void ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    GetProxy Method uri GetProxy(uri destination), uri IWebProxy.GetProxy(uri destination)
    GetType Method type GetType()
    IsBypassed Method bool IsBypassed(uri host), bool IWebProxy.IsBypassed(uri host)
    ToString Method string ToString()
    Address Property uri Address {get;set;}
    BypassArrayList Property System.Collections.ArrayList BypassArrayList {get;}
    BypassList Property string[] BypassList {get;set;}
    BypassProxyOnLocal Property bool BypassProxyOnLocal {get;set;}
    Credentials Property System.Net.ICredentials Credentials {get;set;}
    UseDefaultCredentials Property bool UseDefaultCredentials {get;set;}
         
    .EXAMPLE
    Remove Proxy
    C:\PS> Set-AzureADProxy -DirectNoProxy
     
    .EXAMPLE
    Set a local anonymous proxy 127.0.0.1:8888
    C:\PS> Set-AzureADProxy -Proxy "http://127.0.0.1:8888"
#>

    [cmdletbinding()]
    Param (
      [Parameter(Mandatory=$false)]
        [switch]$DirectNoProxy,
      [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
        [uri]$Proxy,
      [Parameter(Mandatory=$false)]
        [Management.Automation.PSCredential]$ProxyCredential,
      [Parameter(Mandatory=$false)]
        [Switch]$ProxyUseDefaultCredentials
    )
    process {
        if ($DirectNoProxy.IsPresent){
            [System.Net.WebRequest]::DefaultWebProxy = $null
        } ElseIf ($Proxy) {
            $proxyobj = New-Object System.Net.WebProxy $proxy.AbsoluteUri            
            if ($ProxyCredential){
                $proxy.Credentials = $ProxyCredential
            } Elseif ($ProxyUseDefaultCredentials.IsPresent){
                $proxyobj.UseDefaultCredentials = $true
            } 
            [System.Net.WebRequest]::DefaultWebProxy = $proxyobj
            $proxyobj
        }
    }
}
Function Clear-AzureADAccessToken {
<#
    .SYNOPSIS
    Clear an existing MS Graph APIs and MS Graph APIs Beta Access Token
 
    .DESCRIPTION
    Clear an existing MS Graph APIs and MS Graph APIs Beta Access Token. Required to be already authenticated.
     
    .PARAMETER adminUPN
    -adminUPN System.Net.Mail.MailAddress
    UserPrincipalName of the Azure AD account currently logged in that you want the access token to be removed
         
    .OUTPUTS
    None
         
    .EXAMPLE
    Get an access token for my admin account (my-admin@mydomain.tld)
    C:\PS> Clear-AzureADAccessToken -adminUPN my-admin@mydomain.tld
#>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$true)]
            [System.Net.Mail.MailAddress]$adminUPN
    )
    $authority = "https://login.microsoftonline.com/$($adminUPN.Host)"
    Test-ADModule -AzureAD | Out-Null
    try {
        $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
        $authContext.TokenCache.Clear()
    } catch {
        throw "Not Able to clear your current tokens and disconnect your session - exiting"
    }
}
Function Sync-ADOUtoAzureADAdministrativeUnit {
<#
    .SYNOPSIS
    Create new Azure AD Administrative Unit based on on premise AD Organizational Unit
 
    .DESCRIPTION
    Create new Azure AD Administrative Unit based on on premise AD Organizational Unit. Can be used to synchronize all existing on prem AD root OU with new cloud Admin unit.
     
    .PARAMETER AllRootOU
    -AllRootOU Switch
    Synchronize all existing OU to new cloud Admin Unit (except default OU like Domain Controllers)
     
    .PARAMETER RootOUFilterName
    -RootOUFilterName string
    must be used with AllRootOU parameter
    Set a "like" filter to synchronize only OU based on a specific pattern. For instance "TP-*" to synchronize only OU with a name starting with TP-
 
    .PARAMETER OUsDN
    -OUsDN string / array of string
    must not be used with AllRootOU parameter. you must choose between these 2 parameters.
    string must be a LDAP Distinguished Name. For instance : "OU=TP-VB,DC=domain,DC=xyz"
         
    .OUTPUTS
       TypeName : Microsoft.Open.AzureAD.Model.AdministrativeUnit
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj), bool Equals(Microsoft.Open.AzureAD.Model.AdministrativeUnit other), bool Equals(Microsoft.Open.AzureAD.Model.DirectoryObject, Mic...
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ShouldSerializeDeletionTimeStamp Method bool ShouldSerializeDeletionTimeStamp()
    ShouldSerializeObjectId Method bool ShouldSerializeObjectId()
    ShouldSerializeObjectType Method bool ShouldSerializeObjectType()
    ToJson Method string ToJson()
    ToString Method string ToString()
    Validate Method System.Collections.Generic.IEnumerable[System.ComponentModel.DataAnnotations.ValidationResult] Validate(System.ComponentModel.DataAnnotations.ValidationContext v...
    DeletionTimeStamp Property System.Nullable[datetime] DeletionTimeStamp {get;}
    Description Property string Description {get;set;}
    DisplayName Property string DisplayName {get;set;}
    ObjectId Property string ObjectId {get;}
    ObjectType Property string ObjectType {get;}
         
    .EXAMPLE
    Create new cloud Azure AD administrative Unit for each on prem' OU found with a name starting with "TP-"
    The verbose option can be used to write basic message on console (for instance when an admin unit already existing)
    C:\PS> Sync-ADOUtoAzureADAdministrativeUnit -AllRootOU -RootOUFilterName "TP-*" -Verbose
#>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$false)]
            [switch]$AllRootOU,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$RootOUFilterName,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string[]]$OUsDN
    )
    process {
        if (!($AllRootOU.IsPresent) -and !($OUsDN)) {
            throw "AllRootOU switch parameter or OUsDN parameter must be used - exiting"
        }
        Test-ADModule -AzureAD -AD | Out-Null
        If ($AllRootOU.IsPresent) {
            if ($RootOUFilterName) {
                $OUs = Get-ADOrganizationalUnit -Filter {name -like $RootOUFilterName}
            } else {
                $Ous = Get-ADOrganizationalUnit -Filter {name -ne "Domain Controllers"}
            }
        } elseif ($OUsDN) {
            $OUs = @()
            foreach ($OU in $OUsDN) {
                if ($OU -match "^(?:(?<cn>CN=(?<name>[^,]*)),)?(?:(?<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?<domain>(?:DC=[^,]+,?)+)$") {
                    try {
                        $OU = Get-ADOrganizationalUnit -Identity $OU
                    } Catch {
                        write-verbose "OU $($OU) not found in directory"
                    }
                    if ($OU) {
                        $OUs += $OU
                    }
                }
            }
        }
        foreach ($OU in $OUs) {
            If (!(Get-AzureADAdministrativeUnit -Filter "displayname eq '$($OU.name)'")) {
                try {
                    New-AzureADAdministrativeUnit -Description "Windows Server AD OU $($OU.DistinguishedName)" -DisplayName $OU.name
                } catch {
                    Write-Error -Message "$($_.Exception.Message)"
                }
                write-verbose "$($OU.name) Azure Administrative Unit created"
            } else {
                write-verbose "$($OU.name) Azure Administrative Unit already exists"
            }
        }
        Get-AzureADAdministrativeUnit
    }
}
Function Sync-ADUsertoAzureADAdministrativeUnitMember {
<#
    .SYNOPSIS
    Add Azure AD user account into Azure AD Administrative Unit based on their on premise LDAP Distinguished Name
 
    .DESCRIPTION
    Add Azure AD user account into Azure AD Administrative Unit based on their on premise LDAP Distinguished Name.
     
    .PARAMETER CloudUPNAttribute
    -CloudUPNAttribute string
    On premise AD user account attribute hosting the cloud Azure AD User userprincipal name. For instance, it could be also the userPrincipalName attribute or mail attribute.
     
    .PARAMETER AllRootOU
    -AllRootOU Switch
    Synchronize all existing OU to new cloud Admin Unit (except default OU like Domain Controllers)
     
    .PARAMETER RootOUFilterName
    -RootOUFilterName string
    must be used with AllRootOU parameter
    Set a "like" filter to synchronize only OU based on a specific pattern. For instance "TP-*" to synchronize only OU with a name starting with TP-
 
    .PARAMETER OUsDN
    -OUsDN string / array of string
    must not be used with AllRootOU parameter. you must choose between these 2 parameters.
    string must be a LDAP Distinguished Name. For instance : "OU=TP-VB,DC=domain,DC=xyz"
         
    .OUTPUTS
       None. verbose can be used to display message on console.
         
    .EXAMPLE
    Add Azure AD users to administrative unit based on their source Distinguished Name, do it only for users account with a DN containing a root OU name starting with "TP-"
    The verbose option can be used to write basic message on console (for instance when a user is already member of an admin unit)
    C:\PS> Sync-ADUsertoAzureADAdministrativeUnitMember -CloudUPNAttribute mail -AllRootOU -RootOUFilterName "TP-*" -Verbose
#>

    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string]$CloudUPNAttribute,
        [parameter(Mandatory=$false)]
            [switch]$AllRootOU,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$RootOUFilterName,
        [parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string[]]$OUsDN
    )
    process {
        if (!($AllRootOU.IsPresent) -and !($OUsDN)) {
            throw "AllRootOU switch parameter or OUsDN parameter must be used - exiting"
        }
        Test-ADModule -AzureAD -AD | Out-Null
        If ($AllRootOU.IsPresent) {
            if ($RootOUFilterName) {
                $OUs = Get-ADOrganizationalUnit -Filter {name -like $RootOUFilterName}
            } else {
                $Ous = Get-ADOrganizationalUnit -Filter {name -ne "Domain Controllers"}
            }
        } elseif ($OUsDN) {
            $OUs = @()
            foreach ($OU in $OUsDN) {
                if ($OU -match "^(?:(?<cn>CN=(?<name>[^,]*)),)?(?:(?<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?<domain>(?:DC=[^,]+,?)+)$") {
                    try {
                        $OU = Get-ADOrganizationalUnit -Identity $OU
                    } Catch {
                        write-verbose -message "OU $($OU) not found in directory"
                    }
                    if ($OU) {
                        write-verbose -message "OU $($OU) found in directory"
                        $OUs += $OU
                    }
                }
            }
        }
        foreach ($OU in $OUs) {
            $AZADMUnit = Get-AzureADAdministrativeUnit -Filter "displayname eq '$($OU.name)'"
            If ($AZADMUnit) {
                write-verbose -Message "Azure AD Administrative Unit $($OU.name) found"
                $AZADMUnitMember = $AZADMUnit | Get-AzureADAdministrativeUnitMember
                $users = Get-ADUser -SearchBase $OU.DistinguishedName -SearchScope Subtree -Filter * -Properties $CloudUPNAttribute
                foreach ($user in $users) {
                    try {
                        $azureaduser = get-azureaduser -objectid $user.($CloudUPNAttribute)
                    } catch {
                        write-verbose -message "Azure AD User $($user.$CloudUPNAttribute) not found"
                    }
                    if ($user.($CloudUPNAttribute)) {
                        write-verbose -message "Azure AD User $($user.$CloudUPNAttribute) found"
                        if ($AZADMUnitMember) {
                            if ($AZADMUnitMember.ObjectID -contains $azureaduser.ObjectID) {
                                Write-Verbose -message "Azure AD User $($user.($CloudUPNAttribute)) already member of $($OU.name) Azure Administrative Unit"
                            } else {
                                Write-Verbose -message "Azure AD User $($user.($CloudUPNAttribute)) not member of $($OU.name) Azure Administrative Unit"
                                try {
                                    Add-AzureADAdministrativeUnitMember -ObjectId $AZADMUnit.ObjectID -RefObjectId $azureaduser.ObjectID
                                } catch {
                                    Write-Error -Message "$($_.Exception.Message)"
                                    Write-Error -Message "not able to add $($user.($CloudUPNAttribute)) Azure AD User in $($OU.name) Azure Administrative Unit"
                                }
                                Write-Verbose -message "Azure AD User $($user.($CloudUPNAttribute)) added in $($OU.name) Azure Administrative Unit" 
                            }
                        } else {
                            Write-Verbose -message "Azure AD User $($user.($CloudUPNAttribute)) not member of $($OU.name) Azure Administrative Unit"
                            try {
                                Add-AzureADAdministrativeUnitMember -ObjectId $AZADMUnit.ObjectID -RefObjectId $azureaduser.ObjectID
                            } catch {
                                Write-Error -Message "$($_.Exception.Message)"
                                Write-Error -Message "not able to add $($user.($CloudUPNAttribute)) Azure AD User in $($OU.name) Azure Administrative Unit"
                            }
                            Write-Verbose -message "Azure AD User $($user.($CloudUPNAttribute)) added in $($OU.name) Azure Administrative Unit" 
                        }
                    }
                }
            } else {
                write-verbose -message "Azure AD Administrative Unit $($OU.name) not exist"
            }
        }
    }
}
Function Set-AzureADAdministrativeUnitAdminRole {
<#
    .SYNOPSIS
    Add / remove Azure AD administrative unit role to Azure AD user
 
    .DESCRIPTION
    Add / remove Azure AD administrative unit role to Azure AD user
     
    .PARAMETER AdministrativeUnit
    -AdministrativeUnit string
    Dynamic parameter built using the list of Administrative Unit created in your Tenant.
     
    .PARAMETER AdministrativeRole
    -AdministrativeRole string
    Dynamic parameter built using the list of Role template available in your Tenant.
    Note : warning, currently all roles are not compliant with Administrative Unit object.
 
    .PARAMETER userUPN
    -userUPN System.Net.Mail.MailAddress
    user principal name of the account you want to add a new role
 
    .PARAMETER RoleAction
    -RoleAction string {Add, Remove}
    Specify the action to be done with the target role on the Azure AD user object : add or remove the role
         
    .OUTPUTS
       TypeName : Microsoft.Open.AzureAD.Model.ScopedRoleMembership
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj), bool Equals(Microsoft.Open.AzureAD.Model.ScopedRoleMembership other), bool IEquatable[ScopedRoleMembership].Equals(Microsoft.Open.Azure...
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ShouldSerializeId Method bool ShouldSerializeId()
    ToJson Method string ToJson()
    ToString Method string ToString()
    Validate Method System.Collections.Generic.IEnumerable[System.ComponentModel.DataAnnotations.ValidationResult] Validate(System.ComponentModel.DataAnnotations.ValidationContext validat...
    AdministrativeUnitObjectId Property string AdministrativeUnitObjectId {get;set;}
    Id Property string Id {get;}
    RoleMemberInfo Property Microsoft.Open.AzureAD.Model.RoleMemberInfo RoleMemberInfo {get;set;}
    RoleObjectId Property string RoleObjectId {get;set;}
         
    .EXAMPLE
    Give the role Password Administrator for the Admin unit TP-NF to my-admin-unit@mydomain.tld
    C:\PS> Set-AzureADAdministrativeUnitAdminRole -userUPN my-admin-unit@mydomain.tld -RoleAction ADD -AdministrativeUnit TP-NF -AdministrativeRole 'Password Administrator' -Verbose
#>

    [cmdletbinding()]
    param (
        [parameter(Mandatory=$true,Position=2)]
        [ValidateNotNullOrEmpty()]
            [System.Net.Mail.MailAddress]$userUPN,
        [parameter(Mandatory=$true,position=4)]
        [validateSet("Add","Remove")]
            [string]$RoleAction
    )
    DynamicParam
    {
        $ParameterNameAdmUnit = 'AdministrativeUnit'
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.ValueFromPipeline = $false
        $ParameterAttribute.ValueFromPipelineByPropertyName = $false
        $ParameterAttribute.Mandatory = $true
        $ParameterAttribute.Position = 1
        $AttributeCollection.Add($ParameterAttribute)
        $arrSet = (Get-AzureADAdministrativeUnit).displayname
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
        $AttributeCollection.Add($ValidateSetAttribute)
        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterNameAdmUnit, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterNameAdmUnit, $RuntimeParameter)
        
        $ParameterNameAdmRole = 'AdministrativeRole'
        $AttributeCollection2 = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParameterAttribute2 = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute2.ValueFromPipeline = $false
        $ParameterAttribute2.ValueFromPipelineByPropertyName = $false
        $ParameterAttribute2.Mandatory = $true
        $ParameterAttribute2.Position = 3
        $AttributeCollection2.Add($ParameterAttribute2)
        $arrSet =  (Get-AzureADDirectoryRoleTemplate).displayname
        $ValidateSetAttribute2 = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
        $AttributeCollection2.Add($ValidateSetAttribute2)
        $RuntimeParameter2 = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterNameAdmRole, [string], $AttributeCollection2)
        $RuntimeParameterDictionary.Add($ParameterNameAdmRole, $RuntimeParameter2)
        
        return $RuntimeParameterDictionary
   }
    Process {
       $AdminUnit = $PsBoundParameters[$ParameterNameAdmUnit]
       $AdminRole = $PsBoundParameters[$ParameterNameAdmRole]
       Test-ADModule -AzureAD | out-null
       try {
           $UserObj = Get-AzureADUser -ObjectId $userUPN.Address
       } catch {
            Write-Error -Message "$($_.Exception.Message)"
            throw "Not able to find user $($userUPN.Address) - exiting"  
       }
       $AdminUnitObj = Get-AzureADAdministrativeUnit -filter "DisplayName eq '$($AdminUnit)'"
       if (!($AdminUnitObj)) {
           throw "Not able to find $($AdminUnit) Azure AD Administrative Unit - exiting"
       }
       $AdminroleObj = Get-AzureADDirectoryRole -Filter "DisplayName eq '$($AdminRole)'"
       if (!($AdminroleObj)) {
           write-verbose -message "$($AdminRole) role currently disabled, needed to enable it before adding member"
            try {
                $AdminroleObj = Enable-AzureADDirectoryRole -RoleTemplateId (Get-AzureADDirectoryRoleTemplate | Where-Object {$_.Displayname -eq $AdminRole}).ObjectId
            } catch {
                Write-Error -Message "$($_.Exception.Message)"
                throw "Not able to enable requested role $($AdminRole) - exiting"
            }
       }
       try { 
           $AdminRoleMember = (Get-AzureADScopedRoleMembership -ObjectId $AdminUnitObj.ObjectID | Where-Object {$_.RoleObjectID -eq $AdminroleObj.ObjectID}).RoleMemberInfo
       } catch {
            Write-Error -Message "$($_.Exception.Message)"
       }
       If (($AdminRoleMember.ObjectId -contains $UserObj.ObjectId) -and ($RoleAction -eq "ADD")) {
           write-verbose "User $($userUPN.Address) already member of $($AdminRole) role"
       } elseif (($AdminRoleMember.ObjectId -notcontains $UserObj.ObjectId) -and ($RoleAction -eq "ADD")) {
           try {
                $AdmRoleMemberInfo = New-Object -TypeName Microsoft.Open.AzureAD.Model.RoleMemberInfo -Property @{ ObjectId =  $UserObj.ObjectId }
                Add-AzureADScopedRoleMembership -RoleObjectId $AdminroleObj.ObjectID -ObjectId $AdminUnitObj.ObjectID -RoleMemberInfo $AdmRoleMemberInfo
           } catch {
                Write-Error -Message "$($_.Exception.Message)"
                throw "Not able to add $($userUPN.Address) user in $($AdminRole) role for $($AdminUnit) Azure administrative unit - exiting"
           }
       }
       If (($AdminRoleMember.ObjectId -notcontains $UserObj.ObjectId) -and ($RoleAction -eq "Remove")) {
        write-verbose "User $($userUPN.Address) already removed from $($AdminRole) role"
        } elseif (($AdminRoleMember.ObjectId -contains $UserObj.ObjectId) -and ($RoleAction -eq "Remove")) {
            try {
                $AdmRoleMembershipID = (Get-AzureADScopedRoleMembership -ObjectId $AdminUnitObj.ObjectID | Where-Object {($_.RoleObjectID -eq $AdminroleObj.ObjectID) -and ($_.RoleMemberInfo.ObjectId -eq $UserObj.ObjectID)}).ID
                Remove-AzureADScopedRoleMembership -ObjectId $AdminUnitObj.ObjectID -ScopedRoleMembershipId $AdmRoleMembershipID
            } catch {
                Write-Error -Message "$($_.Exception.Message)"
                throw "Not able to remove $($userUPN.Address) user from $($AdminRole) role for $($AdminUnit) Azure administrative unit - exiting"
            }
        }
        Get-AzureADScopedRoleMembership -ObjectId $AdminUnitObj.ObjectID
   }
}
Function Get-AzureADUserAllInfo {
<#
    .SYNOPSIS
    Get all info available for an existing Azure AD account
 
    .DESCRIPTION
    Get all info available for an existing Azure AD account (all user properties available including all the hidden one not managed by Get-AzureADUsers)
     
    .PARAMETER userUPN
    -userUPN System.Net.Mail.MailAddress
    UserPrincipalName of an Azure AD account
     
    .PARAMETER inputobject
    -inputobject Microsoft.Open.AzureAD.Model.User
     Microsoft.Open.AzureAD.Model.User object (for instance generated by Get-AzureADUser)
         
    .OUTPUTS
       TypeName : System.Management.Automation.PSCustomObject
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    @odata.context NoteProperty string @odata.context=https://graph.microsoft.com/beta/$metadata#users/$entity
    accountEnabled NoteProperty bool accountEnabled=True
    ageGroup NoteProperty object ageGroup=null
    assignedLicenses NoteProperty Object[] assignedLicenses=System.Object[]
    assignedPlans NoteProperty Object[] assignedPlans=System.Object[]
    businessPhones NoteProperty Object[] businessPhones=System.Object[]
    city NoteProperty object city=null
    companyName NoteProperty object companyName=null
    consentProvidedForMinor NoteProperty object consentProvidedForMinor=null
    country NoteProperty object country=null
    createdDateTime NoteProperty string createdDateTime=2020-04-21T15:17:08Z
    creationType NoteProperty object creationType=null
    deletedDateTime NoteProperty object deletedDateTime=null
    department NoteProperty object department=null
    deviceKeys NoteProperty Object[] deviceKeys=System.Object[]
    displayName NoteProperty string displayName=admin
    employeeId NoteProperty object employeeId=null
    externalUserState NoteProperty object externalUserState=null
    externalUserStateChangeDateTime NoteProperty object externalUserStateChangeDateTime=null
    faxNumber NoteProperty object faxNumber=null
    givenName NoteProperty string givenName=firsname
    id NoteProperty string id=72a50bb8-20cf-494c-969d-fbcd2324b822
    identities NoteProperty Object[] identities=System.Object[]
    imAddresses NoteProperty Object[] imAddresses=System.Object[]
    infoCatalogs NoteProperty Object[] infoCatalogs=System.Object[]
    isResourceAccount NoteProperty object isResourceAccount=null
    jobTitle NoteProperty object jobTitle=null
    legalAgeGroupClassification NoteProperty object legalAgeGroupClassification=null
    mail NoteProperty object mail=null
    mailNickname NoteProperty string mailNickname=my-admin
    mobilePhone NoteProperty string mobilePhone=
    officeLocation NoteProperty object officeLocation=null
    onPremisesDistinguishedName NoteProperty object onPremisesDistinguishedName=null
    onPremisesDomainName NoteProperty object onPremisesDomainName=null
    onPremisesExtensionAttributes NoteProperty System.Management.Automation.PSCustomObject onPremisesExtensionAttributes=@{extensionAttribute1=; extensionAttribute2=; extensionAttribute3=; extensionAttribute...
    onPremisesImmutableId NoteProperty object onPremisesImmutableId=null
    onPremisesLastSyncDateTime NoteProperty object onPremisesLastSyncDateTime=null
    onPremisesProvisioningErrors NoteProperty Object[] onPremisesProvisioningErrors=System.Object[]
    onPremisesSamAccountName NoteProperty object onPremisesSamAccountName=null
    onPremisesSecurityIdentifier NoteProperty object onPremisesSecurityIdentifier=null
    onPremisesSyncEnabled NoteProperty object onPremisesSyncEnabled=null
    onPremisesUserPrincipalName NoteProperty object onPremisesUserPrincipalName=null
    otherMails NoteProperty Object[] otherMails=System.Object[]
    passwordPolicies NoteProperty object passwordPolicies=null
    passwordProfile NoteProperty object passwordProfile=null
    postalCode NoteProperty object postalCode=null
    preferredDataLocation NoteProperty object preferredDataLocation=null
    preferredLanguage NoteProperty object preferredLanguage=null
    provisionedPlans NoteProperty Object[] provisionedPlans=System.Object[]
    proxyAddresses NoteProperty Object[] proxyAddresses=System.Object[]
    refreshTokensValidFromDateTime NoteProperty string refreshTokensValidFromDateTime=2020-04-21T15:24:29Z
    showInAddressList NoteProperty object showInAddressList=null
    signInSessionsValidFromDateTime NoteProperty string signInSessionsValidFromDateTime=2020-04-21T15:24:29Z
    state NoteProperty object state=null
    streetAddress NoteProperty object streetAddress=null
    surname NoteProperty string surname=name
    usageLocation NoteProperty string usageLocation=US
    userPrincipalName NoteProperty string userPrincipalName=my-admin@mydomain.tld
    userType NoteProperty string userType=Member
         
    .EXAMPLE
    Get all users properties available for the Azure AD account my-admin@mydomain.tld
    C:\PS> get-azureaduser -ObjectId "my-admin@mydomain.tld" | Get-AzureADUserAllInfo
 
    .EXAMPLE
    Get all users properties available for the Azure AD account my-admin@mydomain.tld
    C:\PS> Get-AzureADUserAllInfo -userUPN "my-admin@mydomain.tld"
#>

    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)]
            [Microsoft.Open.AzureAD.Model.User]$inputobject,
        [parameter(Mandatory=$false)]
            [System.Net.Mail.MailAddress]$userUPN,
        [parameter(Mandatory=$false)]
            [switch]$All
    )
    process {
        Test-AzureADAccesToken
        if (!($userUPN) -and !($inputobject) -and !($all.IsPresent)) {
            throw "Please use userUPN or inputobject parameter or All switch - exiting"
        }
        $params = @{
            API = "users"
            Method = "GET"
        }
        if ($inputobject.ObjectId) {
            $params.add('APIParameter',$inputobject.ObjectId)
        } elseif ($userUPN.Address) {
            $params.add('APIParameter',$userUPN.Address)
        }
        Invoke-APIMSGraphBeta @params
    }
}
Function Get-AzureADAdministrativeUnitAllMembers {
<#
    .SYNOPSIS
    Get all Azure AD account member of an Azure AD Administrative Unit
 
    .DESCRIPTION
    Get all Azure AD account member of an Azure AD Administrative Unit
     
    .PARAMETER ObjectId
    -ObjectId guid
    GUID of the Administrative Unit
     
    .PARAMETER inputobject
    -inputobject Microsoft.Open.AzureAD.Model.AdministrativeUnit
    Microsoft.Open.AzureAD.Model.AdministrativeUnit object (for instance created by Get-AzureADAdministrativeUnit)
         
    .OUTPUTS
       TypeName : System.Management.Automation.PSCustomObject
 
    Name MemberType Definition
    ---- ---------- ----------
    Equals Method bool Equals(System.Object obj)
    GetHashCode Method int GetHashCode()
    GetType Method type GetType()
    ToString Method string ToString()
    @odata.context NoteProperty string @odata.context=https://graph.microsoft.com/beta/$metadata#users/$entity
    accountEnabled NoteProperty bool accountEnabled=True
    ageGroup NoteProperty object ageGroup=null
    assignedLicenses NoteProperty Object[] assignedLicenses=System.Object[]
    assignedPlans NoteProperty Object[] assignedPlans=System.Object[]
    businessPhones NoteProperty Object[] businessPhones=System.Object[]
    city NoteProperty object city=null
    companyName NoteProperty object companyName=null
    consentProvidedForMinor NoteProperty object consentProvidedForMinor=null
    country NoteProperty object country=null
    createdDateTime NoteProperty string createdDateTime=2020-04-21T15:17:08Z
    creationType NoteProperty object creationType=null
    deletedDateTime NoteProperty object deletedDateTime=null
    department NoteProperty object department=null
    deviceKeys NoteProperty Object[] deviceKeys=System.Object[]
    displayName NoteProperty string displayName=admin
    employeeId NoteProperty object employeeId=null
    externalUserState NoteProperty object externalUserState=null
    externalUserStateChangeDateTime NoteProperty object externalUserStateChangeDateTime=null
    faxNumber NoteProperty object faxNumber=null
    givenName NoteProperty string givenName=firsname
    id NoteProperty string id=72a50bb8-20cf-494c-969d-fbcd2324b822
    identities NoteProperty Object[] identities=System.Object[]
    imAddresses NoteProperty Object[] imAddresses=System.Object[]
    infoCatalogs NoteProperty Object[] infoCatalogs=System.Object[]
    isResourceAccount NoteProperty object isResourceAccount=null
    jobTitle NoteProperty object jobTitle=null
    legalAgeGroupClassification NoteProperty object legalAgeGroupClassification=null
    mail NoteProperty object mail=null
    mailNickname NoteProperty string mailNickname=my-admin
    mobilePhone NoteProperty string mobilePhone=
    officeLocation NoteProperty object officeLocation=null
    onPremisesDistinguishedName NoteProperty object onPremisesDistinguishedName=null
    onPremisesDomainName NoteProperty object onPremisesDomainName=null
    onPremisesExtensionAttributes NoteProperty System.Management.Automation.PSCustomObject onPremisesExtensionAttributes=@{extensionAttribute1=; extensionAttribute2=; extensionAttribute3=; extensionAttribute...
    onPremisesImmutableId NoteProperty object onPremisesImmutableId=null
    onPremisesLastSyncDateTime NoteProperty object onPremisesLastSyncDateTime=null
    onPremisesProvisioningErrors NoteProperty Object[] onPremisesProvisioningErrors=System.Object[]
    onPremisesSamAccountName NoteProperty object onPremisesSamAccountName=null
    onPremisesSecurityIdentifier NoteProperty object onPremisesSecurityIdentifier=null
    onPremisesSyncEnabled NoteProperty object onPremisesSyncEnabled=null
    onPremisesUserPrincipalName NoteProperty object onPremisesUserPrincipalName=null
    otherMails NoteProperty Object[] otherMails=System.Object[]
    passwordPolicies NoteProperty object passwordPolicies=null
    passwordProfile NoteProperty object passwordProfile=null
    postalCode NoteProperty object postalCode=null
    preferredDataLocation NoteProperty object preferredDataLocation=null
    preferredLanguage NoteProperty object preferredLanguage=null
    provisionedPlans NoteProperty Object[] provisionedPlans=System.Object[]
    proxyAddresses NoteProperty Object[] proxyAddresses=System.Object[]
    refreshTokensValidFromDateTime NoteProperty string refreshTokensValidFromDateTime=2020-04-21T15:24:29Z
    showInAddressList NoteProperty object showInAddressList=null
    signInSessionsValidFromDateTime NoteProperty string signInSessionsValidFromDateTime=2020-04-21T15:24:29Z
    state NoteProperty object state=null
    streetAddress NoteProperty object streetAddress=null
    surname NoteProperty string surname=name
    usageLocation NoteProperty string usageLocation=US
    userPrincipalName NoteProperty string userPrincipalName=my-admin@mydomain.tld
    userType NoteProperty string userType=Member
         
    .EXAMPLE
    Get all Azure AD user member of the admin unit TP-AL
    C:\PS> Get-AzureADAdministrativeUnit -Filter "displayname eq 'TP-AL'" | Get-AzureADAdministrativeUnitAllMembers
#>

    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)]
            [Microsoft.Open.AzureAD.Model.AdministrativeUnit]$inputobject,
        [parameter(Mandatory=$false)]
            [guid]$ObjectId
    )
    process {
        Test-AzureADAccesToken
        if (!($ObjectId) -and !($inputobject)) {
            throw "Please use ObjectID or inputobject parameter - exiting"
        }
        $params = @{
            API = "administrativeUnits"
            Method = "GET"
        }
        if ($inputobject.ObjectId) {
            $parameter = $inputobject.ObjectId + "/" + "members?"   
        } elseif ($ObjectId) {
            $parameter = $ObjectId + "/" + "members?$top=999"
        }
        $params.add('APIParameter',$parameter)
        Invoke-APIMSGraphBeta @params
    }
}
Function Invoke-APIMSGraphBeta {
    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$false)]
        [validateSet("users","me","administrativeUnits")]
            [string]$API,
        [parameter(Mandatory=$true)]
        [validateSet("GET","POST","PUT","PATCH","DELETE")]
            [string]$Method,
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$APIParameter,
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [string]$APIBody,
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
            [uri]$Paging
    )
    process {
        $authHeader = @{
            'Content-Type' = "application/json"
            'Authorization' = "Bearer $($global:AADConnectInfo.AccessToken)"
            'ExpiresOn' = $global:AADConnectInfo.TokenExpiresOn
            'x-ms-client-request-id' = [guid]::NewGuid()
            'x-ms-correlation-id'    = [guid]::NewGuid()
        }
        if ($paging.AbsoluteUri -like "*skiptoken=*") {
            $uri = $paging.AbsoluteUri
            write-verbose -Message $uri
        } else {
            $uri = "https://graph.microsoft.com/beta/$($API)"
            if ($APIParameter) {
                $uri = $uri + "/" + $APIParameter
            }
        }
        write-verbose -message "$($Method) to $($uri)"
        try {
            $response = Invoke-WebRequest -UseBasicParsing -headers $authHeader -Uri $uri -Method $Method
        } catch {
            $ex = $_.Exception
            $errorResponse = $ex.Response.GetResponseStream()
            $reader = New-Object System.IO.StreamReader($errorResponse)
            $reader.BaseStream.Position = 0
            $reader.DiscardBufferedData()
            $responseBody = $reader.ReadToEnd();
            write-verbose -message "Response content:`n$responseBody"
            throw "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription) - exiting"
        }
        if ($response.Content) {
            $result = ConvertFrom-Json $response.Content
            if ($result.value) {
                $result.value
            } else {
                $result
            }
            if ($result.'@odata.nextLink') {
                write-verbose -Message "Paging : $($result.'@odata.nextLink')"
                Invoke-APIMSGraphBeta -Method GET -Paging $result.'@odata.nextLink'
            }
        } else {
            throw "response is null - exiting"
        }
    }
}
Function Test-ADModule {
    [cmdletbinding()]
    Param (
        [parameter(Mandatory=$false)]
        [switch]$AzureAD,
        [parameter(Mandatory=$false)]
        [switch]$AD
    )
    Process {
        if ($AzureAD.IsPresent) {
            try {
                $AadModule = get-module -Name AzureADPreview
                if (!($AadModule)) {
                    import-module -name AzureADPreview
                    $AadModule = get-module -Name AzureADPreview
                }
            } catch {
                throw "AzureADPreview module is missing, please install this module using 'install-module AzureADPreview' - exiting"
            }
            $AadModule
        }
        if ($AD.IsPresent) {
            try {
                $AdModule = get-module -Name ActiveDirectory
                if (!($AdModule)) {
                    import-module -name ActiveDirectory
                    $AdModule = get-module -Name ActiveDirectory
                }
            } catch {
                throw "ActiveDirectory module is missing, please install this module using 'Add-WindowsCapability -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0 -Online' or 'Add-WindowsFeature -Name RSAT-AD-Tools' - exiting"
            }
            $AdModule
        }
    }
}
Function Test-AzureADAccesToken {
    Process {
        if(!($global:AADConnectInfo.AccessToken)) {
            throw "Not able to find a valid Azure AD Access Token in cache, please use Get-AzureADAccessToken first - exiting"
        }
    }
}

Export-ModuleMember -Function Get-AzureADTenantInfo, Get-AzureADMyInfo, Get-AzureADAccessToken, Connect-AzureADFromAccessToken, Clear-AzureADAccessToken, 
                                Set-AzureADProxy, Test-ADModule, Sync-ADOUtoAzureADAdministrativeUnit, Invoke-APIMSGraphBeta, Get-AzureADUserAllInfo, Test-AzureADAccesToken,
                                Sync-ADUsertoAzureADAdministrativeUnitMember,Set-AzureADAdministrativeUnitAdminRole, Get-AzureADAdministrativeUnitAllMembers