Demo-Autopilot.ps1


<#PSScriptInfo
 
.VERSION 1.0.0.2
 
.GUID bb30a59d-b328-44da-9aa3-6f766d4e4f12
 
.AUTHOR Frits van Drie
 
.COMPANYNAME 3-Link Opleidingen (3-link.nl)
 
.COPYRIGHT (c) 2023 3-Link bv (NL). Anyone is free to use and distribute this module freely without modification. If you like this or experience failures, please notify me
 
.TAGS Demoscript
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES microsoft.graph.authentication
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES 1.0.0.0 (2024-09-06) Initial release
              1.0.0.1 (2024-09-06) Removed Break
              1.0.0.2 (2024-09-06) Minor updates in Comment-Based Help
 
 
.PRIVATEDATA
 
#>


<#
    .SYNOPSIS
        Full setup for demonstrating Autopilot with 'Entra Join' or 'Hybrid Join'
 
    .DESCRIPTION
        This script will setup all components for Autopilot (optionally with Hybrid Join) in a demo environment.
 
    .PARAMETER CsvPath
        The full path to the csv-file for importing Autopilot devices.
        The file may contain one or multiple devices and can be generated with 'Get-WindowsAutopilotInfo.ps1'
 
    .PARAMETER TenantPrefix
        The prefix of the Entra tenant. The prefix will be appended with 'onmicrosoft.com'
 
    .PARAMETER HybridJoin
        [switch] If not used, a standard Autopilot deployment will be created. If this switch is used a Hybrid-Join deployment will be created.
 
    .PARAMETER SyncedOU
        Optional parameter to specify the distinguishedName of the Organization Unit that is synchronized to Entra ID by 'Entra Connect'.
        If not specified the path is determined from the Entra Connect configuration.
 
    .PARAMETER OdjConnectorServer
        The ComputerName of the server where 'Entra Connect' and the 'Intune Offline Domain Join (ODJ) Connector' are installed.
 
    .PARAMETER AssignUser
        [switch] When used a demo user account will be created in Entra ID. The user will be assigned to all imported devices.
        The user will take one license for EMS.
 
    .EXAMPLE
        .\Demo-Autopilot.ps1 -CsvPath Import.csv -TenantPrefix LODSM123456
        This will import the device(s) in the csv-file for LODSM23456.onmicrosoft.com and configure it for an Entra joined Autopilot
 
    .EXAMPLE
        .\Demo-Autopilot.ps1 -CsvPath Import.csv -OdjConnectorServer SEA-SVR1 -TenantPrefix LODSM123456 -AssignUser -HybridJoin -SyncedOU 'OU=Devices,OU=ENTRA,DC=contoso,DC=com'
        This will import the device(s) in the csv-file for LODSM123456.onmicrosoft.com for Hybrid Join Autopilot.
        The parameter -SyncedOU refers to the OU that is synchronized with 'Entra Connect'
        The device(s) will be assigned to a newly created Entra user.
 
    .NOTES
        Author : Frits van Drie (f.vandrie)
        Company : 3-Link Opleidingen (3-Link.nl)
 
        This script is developed for training and demonstration only. Do not run this script in a production environment.
 
        A perfect way to run this script is to open it in your favorite PowerShell editor and run each region separate in sequence.
 
        If no LDAP path is specified for the service accounts, the default location for new Users will be used
        If no LDAP path is specified for the computer accounts, it will be extracted from the Entra Connect configuration.
 
        The creation of a service account is optional and the ODJ Server system account can be used instead.
 
        Prerequisites for this demo:
            . Global Administrator access to a Microsoft Entra ID tenant
            . A valid subscription for 'EMS Enterprise E3 or E5'
            . Domain Administrator access to an on-premise AD Domain
            . Entra Connect in place and configured for Hybrid Join
            . An AD domain controller configured for WinRM
            . An AD domain joined server (WS 2016 or later) configured for WinRM
            . An AD domain joined computer (Win10 or later) for running this script
            . A test computer or VM for registration with Autopilot in OOBE phase. Windows 10 or later, at least 4GB memory and 4 (v)CPU's
            . A Csv-file containing the hardware info of the test computer for import into Autopilot (Get-WindowsAutopilotInfo.ps1)
            . Access to internet for all computers
            . PowerShell 5.1, 7+
 
 
        This script will create or install:
            . An AD user for the OdjConnectorSvc.
              The user will be an Administrator on the ODJ Server and will be delegated to create computer objects in AD
            . An AD group for the ODJ Server
            . Change the local security policy on the ODJ Server that will allow the user to logon as a service
            . An Entra dynamic group for all users with a EMS license
            . An Entra dynamic group for all standard Autopilot devices
            . An Entra dynamic group for all Hybrid Join Autopilot devices
            . An Intune Enrollment profile for Autopilot devices
            . An Intune Enrollment Status Page (ESP) for Autopilot devices
            . An Intune Device Configuration profile for skipping ESP User part
            . An Intune Device Configuration profile for joining an AD domain
            . Import a CSV file into Autopilot with one or more devices
 
            . Optional: An Entra ID user account for assigning to the device
              If the variable $assignUser is $true, an Entra user will be created and assigned an EMS license.
              This user will be assigned to the Autopilot device(s).
              If $assignUser is declared $false, no user will be assigned.
 
            . If this script is run multiple times, most creations from previous runs will be removed and recreated.
 
 
#>
                   

[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High",DefaultParameterSetName="Standard")] 

param(
    [Parameter(Mandatory=$false,ParameterSetName='Standard')]
    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [string]$CsvPath = $CsvPath,

    [Parameter(Mandatory=$false,ParameterSetName='Standard')]
    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [string]$TenantPrefix = $TenantPrefix,

    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [switch]$HybridJoin = $HybridJoin,

    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [string]$SyncedOU = $SyncedOU,

    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [string]$OdjConnectorServer = $OdjConnectorServer,

    [Parameter(Mandatory=$false,ParameterSetName='Standard')]
    [Parameter(Mandatory=$false,ParameterSetName='HybridJoin')]
    [switch]$AssignUser = $AssignUser

)

#Requires -RunAsAdministrator

#region: Requirements

    $invocationInfo   = $myInvocation.InvocationName
    $currentUser      = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $windowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($currentUser)

    Write-Host "Requirements" -f Cyan
    Write-Host "`tScript: $invocationInfo"
    Write-Host "`tCurrent user: $($currentUser.Name)"
    Write-Host "`tDomain Admin: " -NoNewline

    if ($windowsPrincipal.IsInRole("Domain Admins")) {
        Write-Host "true" -f Green
    }
    else {
       Write-Host "false " -f Red
       Write-Host "`tThis script requires 'Domain Admins' membership and elevated permissions" -f Yellow
       exit
    }
    Write-Host

#endregion

#region: Variables

    Write-Host "Setup: Variables" -f Cyan

    Write-Host "`tVariables"

    $startTime = Get-Date

    if ($HybridJoin) {
        $autopilotType = 'HybridJoin'
    }
    else {
        $autopilotType = 'Standard'
    }

    if ( -not $tenantPrefix)           { $tenantPrefix        = Read-Host "Tenantname prefix" }
    #DEL if ( -not $tenantAdminPassPlain) { $tenantAdminPassPlain = Read-Host "Tenant admin password" }

    $tenantDnsName          = "$tenantPrefix.onmicrosoft.com"
    $tenantId               = $tenantDnsName
    #DEL $tenantAdminUPN = "Admin@$tenantDnsName"

    $domainDnsName          = (Get-CimInstance -ClassName Win32_ComputerSystem).domain
    $domainNBName           = $domainDnsName.Split('.')[0].toUpper()
    $domainDN               = "DC=$($domainDnsName.Split('.') -join ',DC=')"

    $domainControllerName   = (Get-WmiObject -Namespace 'root\directory\ldap' -Class 'ds_computer' | Where-Object {$_.ds_useraccountcontrol -eq 532480} ).DS_dNSHostName


    if ($HybridJoin) {

        $autopilotGroupName     = 'Autopilot Hybrid-Join Devices'
        $orderID                = 'HybridJoin'

        $odjGroupName           = 'ODJ Connector Servers'
        $odjGroupDescription    = 'Servers running AD-ODJ-Connector for Autopilot Hybrid Join'

        $odjSvcName             = 'ODJConnectorSvc'
        $odjSvcAccountName      = "IntuneODJ_$odjConnectorServer"
        $odjSvcAccountPass      = 'Pa55w.rd'

        $ldapPathUsers          = (([adsisearcher]'(&(objectclass=domain))').FindOne().properties.wellknownobjects | Where-Object {$_ -match '^B:32:A9D1CA15768811D1ADED00C04FD8D5CD:(.*)$'}).split(':')[-1]
        $ldapPathComputers      = (([adsisearcher]'(&(objectclass=domain))').FindOne().properties.wellknownobjects | Where-Object {$_ -match '^B:32:AA312825768811D1ADED00C04FD8D5CD:(.*)$'}).split(':')[-1]
        
        $ldapPathGroups         = $ldapPathUsers
        $ldapPathSvcAcct        = $ldapPathUsers

        $devicePolicyDJName     = "Hybrid Join Domain $domainNBName"
        $devicePolicyDJDescr    = "Demo Policy for $devicePolicyDJName"

        $enrollmentType         = '#microsoft.graph.activeDirectoryWindowsAutopilotDeploymentProfile'  # Hybrid-Joined
        $enrollmentProfileName  = 'Autopilot with Hybrid Join'
        $enrollmentProfileDescr = "Demo enrollment profile for $enrollmentProfileName"
    }
    else {

        $autopilotGroupName     = 'Autopilot Devices'
        $orderID                = 'Standard'


        $enrollmentType         = '#microsoft.graph.azureADWindowsAutopilotDeploymentProfile'          # Entra ID joined
        $enrollmentProfileName  = 'Autopilot'
        $enrollmentProfileDescr = "Demo enrollment profile for $enrollmentProfileName"

    }

    $apMembershipRule       = "(device.devicePhysicalIds -any (_ -contains `"[ZTDId]`")) and (device.devicePhysicalIds -any (_ -eq `"[OrderID]:$orderID`"))"

    $mdmUsersGroupName      = 'Intune Users'
    $intuneProductId        = 'c1ec4a95-1f05-45b3-a911-aa3fa01094f5'
    $mdmMembershipRule      = "(user.assignedPlans -any (assignedPlan.servicePlanId -eq `"$intuneProductId`" -and assignedPlan.capabilityStatus -eq `"Enabled`"))"


    $devicePolicyESP        = 'Disable User ESP'

    $espDisplayName         = "ESP $enrollmentProfileName"

    $assignedUserGivenName   = 'Ademo'
    $assignedUserSurName     = 'Youser'
    $assignedUserDisplayName = "$assignedUserGivenName $assignedUserSurName"
    $assignedUserUpn         = "$assignedUserGivenName@$tenantDnsName"
    $assignedUserPassPlain   = 'DemoY0user'
    if (-not $assignUser) {
        Remove-Variable "assignedUser*" -Force -ErrorAction SilentlyContinue
    }

#endregion

#region: Packages

    Write-Host "Setup: Packages" -f Cyan

    # Package provider
    $packageProviderName = 'NuGet'
    $ppMinimalVersion = '2.8.5.201'
    Write-Host "`tPackage provider"
    Write-Host "`t`tProvider Name : $packageProviderName"
    Write-Host "`t`tMinimal Version: $ppMinimalVersion`t" -NoNewline
    try {
        $packageProvider = Get-PackageProvider -Name $packageproviderName -ForceBootstrap -ErrorAction Stop | Where Version -ge $minimalVersion | Sort Version
        if ($packageProvider) {
            Write-Host 'present' -f Green
            Write-Host "`t`tCurrent version: $($packageProvider.Version)"

        }
        else {
            Write-Host "failed" -f Red
            
        }
    }
    catch {
        throw $_
    }

    $repoName = 'PSGallery'
    try {
        Write-Host "`tGet Repository"
        Write-Host "`t`tName : $repoName`t" -NoNewline
        $repo = Get-PSRepository -Name $repoName -ErrorAction Stop
        Write-Host "present" -f Yellow
        if ($repo.InstallationPolicy -ne 'Trusted') {
            $color = 'Yellow'
        }
        else {
            $color = 'Green'
        }
        Write-Host "`t`tPolicy: $($repo.InstallationPolicy)" -f $color
    }
    catch {
        Write-Host "failed" -f Red

        try {
            Write-Host "`tRegister Repository"
            Write-Host "`t`tName : $repoName`t" -NoNewline
            $repo = Register-PSRepository -Default -ErrorAction Stop
            Write-Host 'success' -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

    }

    if ($repo.InstallationPolicy -ne 'Trusted') {
        try {
            Write-Host "`tTrust Repository"
            Write-Host "`t`tName : $repoName"
            Set-PSRepository -Name $repoName -InstallationPolicy Trusted -ErrorAction Stop
            $repo = Get-PSRepository -Name $repoName -ErrorAction Stop
            Write-Host "`t`tPolicy: $($repo.InstallationPolicy)" -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

    }
    
#endregion

#region: Modules

    Write-Host "Setup: Modules" -f Cyan

    $modules = @(
        'Microsoft.Graph.Authentication'
    )

    Write-Host "`tImport Module"
    $missingModule = $false

    foreach ($module in $modules) {
        try {
            Write-Host "`t`t$module`t" -NoNewline
            Import-Module -Name $module -ErrorAction Stop
            Write-Host "present" -f Green
        }
        catch {
            Write-Host "installing`t" -NoNewline -f yellow
            try {
                Install-Module $module -Repository PSGallery -ErrorAction Stop
                Write-Host "success" -f Green
            }
            catch {
                Write-Host "failed" -f Red
                $missingModule = $true
            }
        }
    }

    if ($missingModule) {
        throw 'Missing one or more required modules'
    }

#endregion

#region: Functions

    Write-Host "Setup: Functions" -f Cyan

    Write-Host "`tGet-3gIntuneDeviceConfigurationProfile{}"
    Function Get-3gIntuneDeviceConfigurationProfile {

        <#
            .NOTES
             
                Name: Get-3gIntuneDeviceConfigurationProfile
                Author: Frits van Drie (3-Link.nl)
                Date: 2024-06-18
     
        #>


        [CmdletBinding()]

        param()

        $batch = @'
{
    "requests":[
        {
            "id":"/deviceManagement/configurationPolicies",
            "method":"GET",
            "url":"/deviceManagement/configurationPolicies?$top=1000&$select=id,name,lastModifiedDateTime,roleScopeTagIds,createdDateTime&$orderBy=name asc"
        },
        {
            "id":"/deviceManagement/deviceConfigurations",
            "method":"GET",
            "url":"/deviceManagement/deviceConfigurations?$top=1000&$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,createdDateTime&$orderBy=displayName asc"
        },
        {
            "id":"/deviceManagement/groupPolicyConfigurations",
            "method":"GET",
            "url":"/deviceManagement/groupPolicyConfigurations?$top=1500"
        },
        {
            "id":"/deviceAppManagement/mobileAppConfigurations",
            "method":"GET",
            "url":"/deviceAppManagement/mobileAppConfigurations?$top=1000&$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig eq true"
        },
        {
            "id":"/deviceManagement/resourceAccessProfiles",
            "method":"GET",
            "url":"/deviceManagement/resourceAccessProfiles?$top=1000"
        }
    ]
}
'@

        #$batch | ConvertFrom-Json
        $uri = 'https://graph.microsoft.com/beta/$batch'
        Write-Verbose "Invoke-MgGraphRequest"
        Write-Verbose "`tUri : '$uri'"
        Write-Verbose "`tMethod: POST"
        $restResult = (Invoke-MgGraphRequest -Method POST -Uri $uri -Body $batch -OutputType PSObject -ErrorAction Stop) #.value


        foreach ($response in $restResult.responses) {

            foreach ($item in $response.body.value) {
                $ht = @{
                    '@odata.type'        = $item.'@odata.type'
                    id                   = $item.id
                    createdDateTime      = $item.createdDateTime
                    lastModifiedDateTime = $item.lastModifiedDateTime
                    roleScopeTagIds      = $item.roleScopeTagIds
                    resourceType         = $response.id
                }
                if ($item.displayName) {
                    $ht.Add('displayName', $item.displayName)
                }
                elseif ($item.profileName) {
                    $ht.Add('displayName', $item.profileName)
                }
                elseif ($item.name) {
                    $ht.Add('displayName', $item.name)
                }
                else {
                    $ht.Add('displayName', '')
                }

                $objOut = [PSCustomObject]$ht
                Write-Verbose ("Output: {0} [{1}] " -f ($objOut.displayName).PadRight(60), $objOut.id)
                Write-Output $objOut
            }

        }

    }

    Write-Host "`tGetEnrollmentStatusPage{}"
    Function GetEnrollmentStatusPage {

        [cmdletbinding()]

        param (
            [Parameter(mandatory=$false)]
            $Id,
            [Parameter(mandatory=$false)]
            $Filter

        )


        # Defining Variables
        $version  = "beta"
        $resource = "deviceManagement/deviceEnrollmentConfigurations"
        $uri      = "https://graph.microsoft.com/$version/$resource"

        if ($PSBoundParameters.ContainsKey('Id')) {
            $uri += "/$id"
        }

        if ($PSBoundParameters.ContainsKey('Filter')) {
            $uri += "?`$Filter=$Filter"
        }

        try {
            Write-Verbose "Invoke Graph Request"
            $response = Invoke-MgGraphRequest -Uri $uri -Method Get
            if ($id) {
                $objOut = $response
            }
            else {
                $objOut = $response.Value | Where { $_.'@odata.type' -eq "#microsoft.graph.windows10EnrollmentCompletionPageConfiguration" }
            }
        }
        catch {
            throw $_
        }

        Write-Output $objOut




    }

    Write-Host "`tNewEnrollmentStatusPage{}"
    Function NewEnrollmentStatusPage {

    <#
    .SYNOPSIS
        Creates a new Intune Enrollment Status Page (ESP)
 
    .DESCRIPTION
        This function creates an Autopilot Enrollment Status Page (ESP) for Intune Enrollments
 
    .PARAMETER DisplayName
        Type: String - Configure the display name of the enrollment status page
 
    .PARAMETER Description
        Type: String - Configure the description of the enrollment status page
 
    .PARAMETER ShowProgress
        Type: Switch - Configure the option: Show app and profile installation progress
 
    .PARAMETER AllowCollectLogs
        Type: Switch - Configure the option: Allow users to collect logs about installation errors
 
    .PARAMETER Message
        Type: String - Configure the option: Show custom message when an error occurs
 
    .PARAMETER AllowUseOnFailure
        Type: Switch - Configure the option: Allow users to use device if installation error occurs
 
    .PARAMETER AllowResetOnError
        Type: Switch - Configure the option: Allow users to reset device if installation error occurs
 
    .PARAMETER BlockDeviceUntilComplete
        Type: Switch - Configure the option: Block device use until all apps and profiles are installed
 
    .PARAMETER TimeoutInMinutes
        Type: Integer - Configure the option: Show error when installation takes longer than specified number of minutes
 
    .PARAMETER Replace
        Type: Switch - When using this parameter any ESP with the same name will be deleted and recreated
 
    .PARAMETER WhatIf
        Common parameter WhatIf is supported
 
    .PARAMETER Confirm
        Common parameter Confirm is supported
 
    .EXAMPLE
        $esp = NewEnrollmentStatusPage -DisplayName 'Autopilot ESP' -Description 'ESP for Autopilot devices' -ErrorMessage "An error occured, please contact your support" -ShowProgress -AllowResetOnError -Replace -verbose
 
    .EXAMPLE
        NewEnrollmentStatusPage -ErrorMessage "An error occured, please contact your support" -ShowProgress -AllowResetOnErrortrue
 
    .NOTES
        Version : 2024-07-04
        Author : Frits van Drie (3-link.nl)
        Releases: initial release
    #>


        [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Medium")] # 'none', 'Low', ('Medium'), 'High'

        param (
            [Parameter(Mandatory=$true)] [string]$displayName,
            [Parameter(Mandatory=$false)][string]$Description,
            [Parameter(Mandatory=$false)][switch]$ShowProgress,
            [Parameter(Mandatory=$false)][switch]$AllowCollectLogs,
            [Parameter(Mandatory=$false)][switch]$BlockDeviceSetupRetryByUser,
            [Parameter(Mandatory=$false)][string]$ErrorMessage,
            [Parameter(Mandatory=$false)][switch]$AllowUseOnFailure,
            [Parameter(Mandatory=$false)][switch]$AllowResetOnError,
            [Parameter(Mandatory=$false)][switch]$BlockDeviceUntilComplete,
            [Parameter(Mandatory=$false)][int]$Priority = 100,
            [Parameter(Mandatory=$false)][validateRange(1,1440)][int]$TimeoutInMinutes = 90,
            [Parameter(Mandatory=$false)][switch]$Replace
        )

        if (-not $PSBoundParameters.ContainsKey('Verbose')) {
            $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference')
        }

        $Confirm = ( $PSBoundParameters.'Confirm'.IsPresent -eq $true )
                if (-not $Confirm) {
        $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference')
    }

        $WhatIf = ( $PSBoundParameters.'WhatIf'.IsPresent -eq $true )
                if (-not $WhatIf) {
        $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference')
    }


                        if ($PSBoundParameters.Keys -notcontains 'Description') {
        Write-Verbose "Using default description"
        $description = "The Enrollment Status Page appears during initial device setup and during first user sign in. If enabled, users can see the configuration progress of assigned apps and profiles targeted to their device"
        Write-Verbose "`tDescription: $Description"
    }


                                                                                    $hashBody = @{
        '@odata.type'                           = '#microsoft.graph.windows10EnrollmentCompletionPageConfiguration'
        deviceEnrollmentConfigurationType       = "windows10EnrollmentCompletionPageConfiguration"
        displayName                             = $DisplayName
        description                             = $Description
        priority                                = $Priority
        roleScopeTagIds                         = @("0")
        showInstallationProgress                = $($ShowProgress.IsPresent)
        installProgressTimeoutInMinutes         = $TimeoutInMinutes
        customErrorMessage                      = $ErrorMessage
        allowLogCollectionOnInstallFailure      = $($AllowCollectLogs.IsPresent)
        trackInstallProgressForAutopilotOnly    = $false
        selectedMobileAppIds                    = @()
        allowDeviceResetOnInstallFailure        = $($AllowResetOnError.IsPresent)
        allowDeviceUseOnInstallFailure          = $($AllowUseOnFailure.IsPresent)
        installQualityUpdates                   = $false
        disableUserStatusTrackingAfterFirstUser = $false
        allowNonBlockingAppInstallation         = $false
        blockDeviceSetupRetryByUser             = $($BlockDeviceSetupRetryByUser.IsPresent)
    }
                                                                If ( $PSBoundParameters.Keys -notContains 'ShowProgress') {
        Write-Verbose "Some parameter values might have changed because 'Show app and profile configuration progress' is disabled"

        Write-Verbose "`tblockDeviceSetupRetryByUser : $true"
        $hashBody.blockDeviceSetupRetryByUser        = $true

        Write-Verbose "`tallowDeviceUseOnInstallFailure : $false"
        $hashBody.allowDeviceUseOnInstallFailure     = $false

        Write-Verbose "`tallowDeviceResetOnInstallFailure : $false"
        $hashBody.allowDeviceResetOnInstallFailure   = $false

        Write-Verbose "`tallowLogCollectionOnInstallFailure: $false"
        $hashBody.allowLogCollectionOnInstallFailure = $false

        Write-Verbose "`tcustomErrorMessage : ''"
        $hashBody.customErrorMessage                 = ""

        Write-Verbose "`tinstallProgressTimeoutInMinutes : 90"
        $hashBody.installProgressTimeoutInMinutes    = 90

    }
            
                                elseif ($PSBoundParameters.ContainsKey('BlockDeviceSetupRetryByUser')) {
        Write-Verbose "Some parameter values might have changed because BlockDeviceSetupRetryByUser is enabled"
        Write-Verbose "`tallowDeviceUseOnInstallFailure : $false"
        $hashBody.allowDeviceUseOnInstallFailure     = $false
        Write-Verbose "`tallowDeviceResetOnInstallFailure : $false"
        $hashBody.allowDeviceResetOnInstallFailure   = $false
    }


        $version  = "beta"
        $resource = "deviceManagement/deviceEnrollmentConfigurations"
        $uri      = "https://graph.microsoft.com/$version/$resource"


        if ( $response = (GetEnrollmentStatusPage -Filter "displayName eq '$displayName'") ) {
            if ($PSBoundParameters.ContainsKey('Replace')) {
                foreach ($esp in $response) {
                    Write-Verbose "Remove existing ESP: $($esp.displayName)"

                    # ShouldProcess(message, target, action | target,action | target)
                    if ( ($yesToAll) -or ($PSCmdlet.ShouldProcess("Intune ESP: $($esp.displayName) [$($esp.id)])", 'Remove ESP')) ) {
                        try {
                            $uriESP = "$uri/$($esp.id)"
                            Write-Verbose "Invoke Graph Request"
                            $objOut = Invoke-MgGraphRequest -Uri $uriESP -Method DELETE -ErrorAction Stop
                            Write-Verbose "Successfully removed ESP: $($esp.id)"
                        }
                        catch {
                            throw $_
                        }
                    }    
                    elseif ( -not $yesToAll ) {
                        Write-Verbose "Existing ESP is not removed because the WhatIf parameter was used"
                    }

                }
            }
            else {
                throw "ESP with this name already exists: $displayName. Use the Replace parameter if you want to delete and recreate it"
            }
        }

        <#
         
        showInstallationProgress Show app and profile configuration progress
        installProgressTimeoutInMinutes Show an error when installation takes longer than specified number of minutes
        customErrorMessage Show custom message when time limit or error occurs
        allowLogCollectionOnInstallFailure Turn on log collection and diagnostics page for end users
        trackInstallProgressForAutopilotOnly Only show page to devices provisioned by out-of-box experience (OOBE)
        selectedMobileAppIds Block device use until all apps and profiles are installed
        allowDeviceResetOnInstallFailure Allow users to reset device if installation error occurs
        allowDeviceUseOnInstallFailure Allow users to use device if installation error occurs
                            Block device use until required apps are installed if they are assigned to the user/device
        installQualityUpdates
        disableUserStatusTrackingAfterFirstUser
        allowNonBlockingAppInstallation
        blockDeviceSetupRetryByUser
        #>


        $jsonBody = $hashBody | ConvertTo-Json

        Write-Verbose "JSON payload:`n$jsonBody"


        # ShouldProcess(message, target, action | target,action | target)
        if ( ($yesToAll) -or ($PSCmdlet.ShouldProcess("DisplayName: $displayName", 'Create new ESP')) ) {

            try {
                Write-Verbose "Invoke Graph Request"
                $objOut = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ContentType "application/json"
            }
            catch {
                throw $_
            }

            Write-Output $objOut


        }
    }

    Write-Host "`tGet-3gIntuneDeviceConfigurationPolicy{}"
    Function Get-3gIntuneDeviceConfigurationPolicy {

    <#
        .NOTES
             
            Name: Get-3gIntuneDeviceConfigurationPolicy
            Author: Frits van Drie (3-Link.nl)
            Date: 2024-06-18
     
    #>


        [CmdletBinding()]

        param()

        $batch = @'
{
"requests":[
    {
        "id":"/deviceManagement/configurationPolicies",
        "method":"GET",
        "url":"/deviceManagement/configurationPolicies?$top=1000&$select=id,name,lastModifiedDateTime,roleScopeTagIds,createdDateTime&$orderBy=name asc"
    },
    {
        "id":"/deviceManagement/deviceConfigurations",
        "method":"GET",
        "url":"/deviceManagement/deviceConfigurations?$top=1000&$select=id,displayName,lastModifiedDateTime,roleScopeTagIds,createdDateTime&$orderBy=displayName asc"
    },
    {
        "id":"/deviceManagement/groupPolicyConfigurations",
        "method":"GET",
        "url":"/deviceManagement/groupPolicyConfigurations?$top=1500"
    },
    {
        "id":"/deviceAppManagement/mobileAppConfigurations",
        "method":"GET",
        "url":"/deviceAppManagement/mobileAppConfigurations?$top=1000&$filter=microsoft.graph.androidManagedStoreAppConfiguration/appSupportsOemConfig eq true"
    },
    {
        "id":"/deviceManagement/resourceAccessProfiles",
        "method":"GET",
        "url":"/deviceManagement/resourceAccessProfiles?$top=1000"
    }
]
}
'@


        $uri = 'https://graph.microsoft.com/beta/$batch'
        Write-Verbose "Invoke-MgGraphRequest"
        Write-Verbose "`tUri : '$uri'"
        Write-Verbose "`tMethod: POST"
        $restResult = (Invoke-MgGraphRequest -Method POST -Uri $uri -Body $batch -OutputType PSObject -ErrorAction Stop) #.value


        foreach ($response in $restResult.responses) {

            foreach ($item in $response.body.value) {
                $ht = @{
                    '@odata.type'        = $item.'@odata.type'
                    id                   = $item.id
                    createdDateTime      = $item.createdDateTime
                    lastModifiedDateTime = $item.lastModifiedDateTime
                    roleScopeTagIds      = $item.roleScopeTagIds
                    resourceType         = $response.id
                }
                if ($item.displayName) {
                    $ht.Add('displayName', $item.displayName)
                }
                elseif ($item.profileName) {
                    $ht.Add('displayName', $item.profileName)
                }
                elseif ($item.name) {
                    $ht.Add('displayName', $item.name)
                }
                else {
                    $ht.Add('displayName', '')
                }

                $objOut = [PSCustomObject]$ht
                Write-Verbose ("Output: {0} [{1}] " -f ($objOut.displayName).PadRight(60), $objOut.id)
                Write-Output $objOut
            }

        }

    }

    Write-Host "`tRemove-3gIntuneDeviceConfigurationPolicy{}"
    Function Remove-3gIntuneDeviceConfigurationPolicy {

        [CmdletBinding()]

        param(
        
            [Parameter(mandatory=$true)]
            [String[]]$Id,

            [Parameter(mandatory=$true)]
            [ValidateSet(
                '/deviceManagement/configurationPolicies',
                '/deviceManagement/deviceConfigurationProfiles',
                '/deviceManagement/deviceConfigurations',
                '/deviceManagement/groupPolicyConfigurations',
                '/deviceAppManagement/mobileAppConfigurations',
                '/deviceAppManagement/mobileAppConfigurations')
            ]
            [String[]]$ResourceType,

            [string]$ApiVersion = 'beta'

        )


        $uri = "https://graph.microsoft.com/$ApiVersion$ResourceType/$Id"
        Write-Verbose "Invoke-MgGraphRequest"
        Write-Verbose "`tUri : '$uri'"
        Write-Verbose "`tMethod: DELETE"
        try {
            $restResult = (Invoke-MgGraphRequest -Method DELETE -Uri $uri -OutputType PSObject -ErrorAction Stop) #.value
            Write-Verbose 'Success'
        }
        catch {
            Write-Verbose 'failed'
            throw $_
        }


        Write-Output $restResult

    }

    Write-Host "`tNewMgDeviceManagementDeviceConfigurationAssignment{}"
    Function NewMgDeviceManagementDeviceConfigurationAssignment {

        [CmdletBinding()]

        param (
            [parameter(Mandatory=$true)]
            $deviceConfigurationId,

            [parameter(Mandatory=$true)]
            $target
            
        )

        Write-Host "`t`tAssigning group`t" -NoNewline
        try {
            $assignments = New-MgBetaDeviceManagementDeviceConfigurationAssignment -DeviceConfigurationId $deviceConfigurationId -Target $target -ErrorAction Stop
            Write-Host "Success" -f Green
        }
        catch {
            Write-Host "Red" -f Red
        }

        Write-Host "`tCurrent Assignments:"
        
        $assignments = Get-MgBetaDeviceManagementDeviceConfigurationAssignment -DeviceConfigurationId $deviceConfigurationId


        foreach ($item in $assignments.target.additionalProperties) {
            $odataType = $item.'@odata.type'
            if ($odataType -like '*exclusionGroupAssignmentTarget') {
                Write-Host "`t`tExcluded: " -f Yellow -NoNewline
            }
            else {
                Write-Host "`t`tIncluded: " -f Green -NoNewline
            }

            if ($item.groupId) {
                $grpDisplayName = (Get-MgGroup -GroupId $item.groupId).DisplayName
                Write-Host $grpDisplayName "[$($item.groupId)]"
            }
            else {
                Write-Host $item.'@odata.type'
            }

        }

    }

    Write-Host "`tNew3gDeviceManagementDeviceConfigurationAssignment{}"
    Function New3gDeviceManagementDeviceConfigurationAssignment {

        [CmdletBinding()]

        param (
            [parameter(Mandatory=$true)]
            $deviceConfigurationId,

            [parameter(Mandatory=$true)]
            $target
            
        )

        $jsonTarget = $target | ConvertTo-Json

        $jsonBody = @"
{
    "target": $jsonTarget
}
"@


        Write-Host "`t`tAssigning group`t" -NoNewline
        $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$deviceConfigurationId/assignments"
        try {
            $assignments = (Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ErrorAction Stop).value
            Write-Host 'success' -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

        Write-Host "`tGet Current Assignments`t" -NoNewline
        
        $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$deviceConfigurationId/assignments"
        try {
            $assignments = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
            Write-Host 'success' -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

        foreach ($item in $assignments.target) {
            $odataType = $item.'@odata.type'
            if ($odataType -like '*exclusionGroupAssignmentTarget') {
                Write-Host "`t`tExcluded: " -f Yellow -NoNewline
            }
            else {
                Write-Host "`t`tIncluded: " -f Green -NoNewline
            }

            if ($item.groupId) {
                
                #$grpDisplayName = (Get-MgGroup -GroupId $item.groupId).DisplayName
                $uri = "https://graph.microsoft.com/beta/groups/$($item.groupId)"
                $grpDisplayName = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).displayName
                Write-Host $grpDisplayName "[$($item.groupId)]"
            }
            else {
                Write-Host $item.'@odata.type'
            }

        }

    }

    Write-Host "`tSetServiceAccount{}"
    Function SetServiceAccount {

        <#
         
            .NOTES
                Author : Frits van Drie( 3-link.nl)
                Releases: 2024-07-07 Initial release
         
        #>


        [CmdLetBinding(DefaultParameterSetName="SvcAccount")]

        PARAM (
            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(ParameterSetName='SystemAccount')]
            [Parameter(Mandatory=$true, Position=0)]
            [String] $serviceName,

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(ParameterSetName='SystemAccount')]
            [Parameter(Mandatory=$false, Position=1)]
            [String] $computerName = 'localhost',

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(ParameterSetName='SystemAccount')]
            [Parameter(Mandatory=$false, Position=3)]
            [ValidateSet('WMI', 'DCOM', 'WSMAN')]
            [String] $connectionMethod = 'WSMAN',

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(Mandatory=$false,  Position=4)]
            [ValidateNotNullOrEmpty()]
            [String] $serviceAccountName,

            [Parameter(ParameterSetName='SystemAccount')]
            [Parameter(Mandatory=$false,  Position=4)]
            [validateset('LocalSystem', 'NT AUTHORITY\System', 'NT AUTHORITY\LocalService', 'NT AUTHORITY\NetworkService')]
            [String] $SystemAccountName='LocalSystem',

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(Mandatory=$false,  Position=5)]
            [ValidateNotNullOrEmpty()]
            [String]$serviceAccountPassword,

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(Mandatory=$false,  Position=6)]
            [ValidateNotNullOrEmpty()]
            [String] $RunAsUserName,

            [Parameter(ParameterSetName='SvcAccount')]
            [Parameter(Mandatory=$false,  Position=7)]
            [ValidateNotNullOrEmpty()]
            [System.Security.SecureString] $RunAsPassword


        )

        if (($serviceAccountName) -and !($serviceAccountPassword)) {
            Write-Verbose "No password specified for RunAsUser $RunAsUserName"
            return $false
        }

        if ( ($PSCmdlet.ParameterSetName -eq 'SvcAccount') -and !($serviceAccountPassword)) {
            Write-Verbose "No password specified for ServiceAccount $serviceAccountName"
            return $false
        }
        Write-Verbose "New ServiceAccountName and Password"


        if ($PSCmdlet.ParameterSetName -eq 'SystemAccount') {
            $serviceAccountName = $SystemAccountName
            $serviceAccountPassword = "notneeded"
            #$serviceAccountPassword = ("notneeded" | ConvertTo-SecureString -AsPlainText -Force)
        }




        # Check input: Username
        <#
            user OK
            domain\user OK
            pc\user OK
            \user OK
            .\user ERROR
            localhost\user ERROR
            notexistinguser ERROR Check after connecting
        #>

        if ($serviceAccountName.split('\')[0] -eq '.') {
            $serviceAccountName = $serviceAccountName.Replace('.',$computerName)
        }
        $serviceAccountName = $serviceAccountName.Replace('localhost',$env:COMPUTERNAME)


        $Connected = $false





        # Test if Computer exist
        if ($computerName -notin 'localhost', $env:COMPUTERNAME ) {
            try {
                Resolve-DnsName $computerName -ErrorAction Stop |Out-Null
                Write-Verbose “[$computerName] Computername found in DNS"
            }
            catch {
                Write-Verbose “[$computerName] ERROR: Computername not found in DNS"
                Return $false
            }
        }




        # Add Credentials
        $Filter = "name='$serviceName'"
        $Parms  = @{
            'ComputerName' = $computerName;
            "Filter"       = $Filter
        }

        if (($computerName -notin 'localhost', $env:COMPUTERNAME ) -and ($RunAsUserName) -and ($RunAsUserPassword)) {
            $CredRunAs = New-Object System.Management.Automation.PSCredential ($RunAsUserName, $RunAsPassword)
            $Parms.add("Credential",$CredRunAs)
        }



        If ($connectionMethod -eq "WMI") {

            try {
                Write-Verbose "[$computerName`:$odjSvcName] - Retrieving information using $connectionMethod"
                $svc = $null
                $svc = Get-WmiObject win32_service @Parms -ErrorAction Stop
                Write-Verbose "[$computerName`:$odjSvcName] - Previous username: $($svc.StartName)"
                Write-Verbose "[$computerName`:$odjSvcName] - Previous state : $($svc.State)"    
            }
            catch  {
                Write-Verbose "[$computerName`:$odjSvcName] - ERROR connecting using $connectionMethod"
                # $connectionMethod = "DCOM"
            }
            


            if ($svc) {
            <# https://4sysops.com/archives/managing-services-the-powershell-way-part-8-service-accounts/
             
 
                $svc = get-wmiobject win32_service -filter "name='BITS'"
                $svc.GetMethodParameters("change")
                 1 System.String DisplayName,
                 2 System.String PathName,
                 3 System.Byte ServiceType,
                 4 System.Byte ErrorControl,
                 5 System.String StartMode,
                 6 System.Boolean DesktopInteract,
                 7 System.String StartName,
                 8 System.String StartPassword,
                 9 System.String LoadOrderGroup,
                10 System.String[] LoadOrderGroupDependencies,
                11 System.String[] ServiceDependencies
            #>

                $return = $svc.Change($null,$null,$null,$null,$null,$null,$serviceAccountName,$serviceAccountPassword)
                if ($return.ReturnValue -eq 0) {
                    $Connected = $true
                    Write-Verbose "[$computerName`:$odjSvcName] - New username: $serviceAccountName" 
                }
                else {
                    Write-Verbose "[$computerName`:$odjSvcName] - ERROR modifying service (ReturnValue: $return)"
                    Write-Verbose "[$computerName`:$odjSvcName] - Check if account ($serviceAccountName) exists"
                }


            }
        }

        If ($connectionMethod -in “WSMAN”,"DCOM") {

            try {
                Write-Verbose "[$computerName] Connecting using $connectionMethod"
                $opt  = New-CimSessionOption –Protocol $connectionMethod
                $sess = New-CimSession –ComputerName $computerName –SessionOption $opt -ErrorAction Stop
                Write-Verbose "[$computerName] Connected using $connectionMethod" 
                $Connected = $true
                $Parms.Remove(“ComputerName”)
                $Parms.add("CimSession",$sess)
                $svc = $null
                # $Parms
                $svc = Get-CimInstance win32_service @Parms -ErrorAction Stop

                $ret = Invoke-CimMethod `
                    -CimSession $sess `
                    -Query "SELECT * FROM Win32_Service WHERE Name=`'$odjSvcName`'" `
                    -MethodName Change `
                    -Arguments @{'StartName'    =$serviceAccountName;
                                 'StartPassword'=$serviceAccountPassword
                               }
  
                if ($ret.ReturnValue -eq 0){
                    Write-Verbose “[$computerName`:$serviceName] - Service successfully modified"
                }
                else {
                    Write-Verbose "[$computerName`:$serviceName] - ERROR modifying service"
                    Write-Verbose "[$computerName`:$serviceName] - Check if account ($serviceAccountName) exists"
                }


            }
            catch  {
                Write-Verbose "[$computerName] Connection ERROR using $connectionMethod"
                $connectionMethod = "WSMAN"
                Write-Verbose "[$computerName] Retrying using $connectionMethod"
                #return $false
            }
        }




        If ($connectionMethod -eq "xxx") {
            $InvokeCommandParms  = @{
                'ComputerName' = $computerName;
            }
            if ($CredRunAs) {
                $InvokeCommandParms.Add('Credential',$CredRunAs)
            }

            try {
                Write-Verbose "[$computerName] Connecting using $connectionMethod"
                Invoke-Command @InvokeCommandParms {
                    Write-Verbose "[$env:ComputerName] Connected using $Using:ConnectionMethod"
                    $Parms = $Using:Parms
                    $Parms.Remove("Credential")
                    $svc = Get-WmiObject win32_service @Parms -ErrorAction Stop
                }
                $Connected = $true
            }
            catch {
                Write-Verbose "[$computerName] ERROR connecting using $connectionMethod" 
                throw $_
            }
        }

        # restart service
        if ($svc.State -eq 'Stopped') {
            Write-Verbose "[$computerName`:$serviceName] - Service will not be started because it was not running" 
        }

        elseif ($return.ReturnValue -eq 0) {
            $svc.StopService() | Out-Null
            while ($svc.Started){
                Write-Verbose "[$computerName`:$serviceName] - Stopping service" 
                sleep 1
                $svc = Get-WmiObject win32_service @Parms
            }
            Write-Verbose "[$computerName`:$serviceName] - service is $($svc.State)" 



            # start Service
            try {
                Write-Verbose "[$computerName`:$serviceName] - Starting service" 
                $return = ($svc.StartService()).ReturnValue
                if ($return -ne 0) {
                    throw
                }
            }
            catch {
                Write-Verbose "[$computerName`:$serviceName] - ERROR: cannot start service (ReturnValue: $return)"    
                Write-Verbose "[$computerName`:$serviceName] - ERROR: Check if user account has 'log on as a service' rights"
            }
        }



        return $true
    }


#endregion

#region: Connections

    Write-Host "Setup: Connections" -f Cyan

    Write-Host "`tMicrosoft Graph"

    $scopes = @(
        'Device.ReadWrite.All',
        'DeviceManagementManagedDevices.ReadWrite.All',
        'DeviceManagementApps.ReadWrite.All',
        'DeviceManagementConfiguration.ReadWrite.All',
        'DeviceManagementServiceConfig.ReadWrite.All',
        'Group.ReadWrite.All',
        'Organization.ReadWrite.All',
        'Policy.ReadWrite.MobilityManagement',
        'User.ReadWrite.All'

    )

    try {
        Write-Host "`t`tscopes: $($scopes -join "`n`t ")"
        Write-Host "`t`tState :`t" -NoNewline
        Connect-MgGraph -Scopes $scopes -NoWelcome -ErrorAction Stop
        Write-Host "Connected" -f Green
        $tenantId = (Get-MgContext).tenantId

    }
    catch {
        Write-Host "failed" -f Red
        Write-Error $_
    }

    if ($HybridJoin) {

        Write-Host "`tDomain Controller (WinRM): $($domainControllerName.split('.')[0])`t" -NoNewline
        $sessionDC = Get-PSSession -ComputerName $domainControllerName -ErrorAction SilentlyContinue
        $retry = 0
        while ($sessionDC.State -ne 'Opened' -and $retry -le 10) {
            try {
                $retry++
                Write-Host "." -NoNewLine
                $sessionDC = New-PSSession -ComputerName $domainControllerName -ErrorAction Stop
            }
            catch {
                if ($retry -gt 10) {
                    Write-Host ' failed' -f Red
                    throw "Failed to connect to $domainControllerName using WinRM"
                }

            }
        }
        Write-Host " connected" -f Green
 
        Write-Host "`tODJ Connector (WinRM): $odjConnectorServer " -NoNewline
        $sessionSVR = Get-PSSession -ComputerName $odjConnectorServer -ErrorAction SilentlyContinue
        $retry = 0
        while ($sessionSVR.State -ne 'Opened' -and $retry -le 10) {
            try {
                $retry++
                Write-Host "." -NoNewLine
                $sessionSVR = New-PSSession -ComputerName $odjConnectorServer -ErrorAction Stop
            }
            catch {
                if ($retry -gt 10) {
                    Write-Host ' failed' -f Red
                    throw "Failed to connect to $odjConnectorServer using WinRM"
                }

            }
        }
        Write-Host " connected" -f Green

        
    }

#endregion

#region: [Entra ] Assigned User

    if ($assignUser) {

        Write-Host "Entra User account" -f Cyan

        write-Host "`tGet User: $assignedUserDisplayName`t" -NoNewline
        try {
            $uri  = "https://graph.microsoft.com/beta/users?`$filter=displayName eq '$assignedUserDisplayName'"
            if ($assignedUser =  (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value) {
                Write-Host "found" -f Green
            }
            else {
                Write-Host 'not found' -f Red
            }
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }

        if ( -not $assignedUser) {

            $jsonBody = @"
{
    "accountEnabled" : true,
    "givenName" : "$assignedUserGivenName",
    "surname" : "$assignedUserSurName",
    "displayName" : "$assignedUserDisplayName",
    "userPrincipalName": "$assignedUserUpn",
    "mailNickname" : "$($assignedUserDisplayName.replace(' ',''))",
    "passwordProfile" : {
        "forceChangePasswordNextSignIn": false,
        "password" : "$assignedUserPassPlain"
    },
    "ageGroup" : "Adult",
    "department" : "Learning",
    "usageLocation" : "NL"
}
"@


            Write-Host "`tCreate user: $assignedUserDisplayName`t" -NoNewline
            try {
                $uri  = 'https://graph.microsoft.com/beta/users'
                $assignedUser = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ErrorAction Stop
                write-Host "[$($assignedUser.id)]`tsuccess" -f Green
            }
            catch {
                Write-Host "failed" -f Red
                throw $_
            }

        }

    }

#endregion

#region: Prerequisites

    Write-Host "Setup: Prerequisites" -f Cyan

    #region: Entra Connect

        if ( $HybridJoin ) {

            Write-Host "`tEntra Connect"
            Write-Host "`t`tSCP configured: " -NoNewline
            try {
                Invoke-Command -Session $sessionDC -ErrorAction Stop {
                    $scp = "CN=62a0ff2e-97b9-4513-943f-0d221bd30080,CN=Device Registration Configuration,CN=Services,CN=Configuration,dc=sure,dc=local"
                    try {
                        if ($objSCP = Get-ADObject $scp -Properties Keywords -ErrorAction Stop) {
                            Write-Host 'True' -f Green
                            Write-Host "`t`tTenant name : $(($objSCP.Keywords | where {$_ -match 'azureADName'}).split(':')[-1])"
                        }
                        else {
                            throw "SCP not configured"
                        }
                    }
                    catch {
                        Write-Host 'False' -f Red
                        throw $_
                    }
                }
            }
            catch {
                throw $_
            }

            Write-Host "`t`tLDAP Path OU : " -NoNewline
            if ( $SyncedOU ) {
                Write-Host $SyncedOU
            }
            else {
                try {

                    $SyncedOU = Invoke-Command -Session $sessionSVR {

                        $module = "$env:ProgramFiles\Microsoft Azure AD Sync\Bin\ADSync\ADSync"
                        Import-Module $module -ErrorAction Stop

                        # Connect information for your on-premises domain.
                        $syncConnector = Get-ADSyncConnector -ErrorAction Stop | Where-Object {$_.Name -notmatch ' - aad'}

                        # OU inclusion list
                        $syncOUIncluded = ($syncConnector.Partitions.ConnectorPartitionScope.ContainerInclusionList)[0]
                        Write-Output $syncOUIncluded

                        # OU exclusion list
                        #$syncOUExcluded = $syncConnector.Partitions.ConnectorPartitionScope.ContainerExclusionList

                    }

                    Write-Host "$SyncedOU" -f Green
                }
                catch {
                    Write-Host 'failed' -f Red
                    throw $_

                }

            }

        }

    #endregion

    #region: Server OS is minimal WS2016 (buildnr: 14393)

        if ($HybridJoin) {
            Write-Host "`tServer OS"
            Write-Host "`t`tMinimal version: WS 2016"

            $osVersion = Invoke-Command -Session $sessionSVR -ErrorAction Stop -ScriptBlock { 
                [Environment]::OSVersion.Version
            }
            if ($osVersion.Build -ge 14393) {
                Write-Host "`t`tCurrent version: $($osVersion.Build)" -f Green
            }
            else {
                Write-Host "`t`tCurrent version: $($osVersion.Build)" -f Red
                throw "OS Version ($($osVersion.Build)) on $odjConnectorServer is not supported. Upgrade your server OS"
            }
        }

    #endregion

    #region: DNS Names
        Write-Host "`tDNS domains"
        $result = @()
        $DnsNameList = @(
            "EnterpriseRegistration.windows.net",
            "login.MicrosoftOnline.com",
            "device.login.MicrosoftOnline.com",
            "autologon.MicrosoftAzureAD-sso.com"  # for Seamless SSO
        )
        foreach($DnsName in $DnsNameList) {
            try {
                Write-Host "`t`t$DnsName`t" -NoNewline
                $null = Resolve-DnsName $DnsName -ErrorAction Stop
                Write-Host "success" -f Green
                $result += [PSCustomObject]@{'DNSName' = $DnsName ;'Resolved' = 'True'}
            }
            catch {
                $result += [PSCustomObject]@{'DNSName' = $DnsName ;'Resolved' = 'False'}
                Write-Host "failed" -f Red
            }
        }

    #endregion

    #region: Assigned user exists

        Write-Host "`tAssigned user"

        if ($assignUser) {

            Write-Host "`t`tUser: $assignedUserUpn`t" -NoNewLine
            try {
                $uri  = "https://graph.microsoft.com/beta/users/$assignedUserUpn"
                $assignedUser = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop
                Write-Host 'found' -f Green
            }
            catch {
                Write-Host 'not found' -f Red
                throw $_
            }

            Write-Host "`t`tUsage Location: " -NoNewLine
            if ($assignedUser.UsageLocation) {
                Write-Host "$($assignedUser.UsageLocation)" -f Yellow
            }
            else {
                Write-Host "missing" -f Red
                $usageLocation = 'US'
                Write-Host "`tSet Usage Location: $usageLocation`t" -NoNewline
                try {
                    $jsonBody = "{usageLocation: `"$usageLocation`"}"
                    $uri  = "https://graph.microsoft.com/beta/users/$assignedUserUpn"
                    Invoke-MgGraphRequest -Method PATCH -Uri $uri -Body $jsonBody -ErrorAction Stop
                    Write-Host 'success' -f Green
                }
                catch {
                    Write-Host 'failed' -f Red
                    throw $_
                }
            }
        }

    #endregion

    #region: Licenses

        Write-Host "`tLicenses:"

        $requiredSubscriptions = 'EMSPREMIUM'
        #$subscribedSku = Get-MgSubscribedSku -ErrorAction Stop

        Write-Host "`t`tGet all licensed products`t" -NoNewline
        $uri  = "https://graph.microsoft.com/beta/subscribedSkus"
        try {
            $subscribedSku = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
            Write-Host "found $($subscribedSku.count)" -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

        if ($assignUser) {

            Write-Host "`t`tGet user licenses"

            foreach ($userName in $assignedUser.displayName) {

                Write-Host "`t`t`tUser: $userName`t" -NoNewline

                #$userLicenses = Get-MgUserLicenseDetail -UserId $assignedUser.id -ErrorAction Stop
                $uri  = "https://graph.microsoft.com/beta/users/$($assignedUser.id)/licenseDetails"
                try {
                    $userLicenses = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
                    Write-Host "found $($userLicenses.count)" -f Green
                }
                catch {
                    Write-Host 'failed' -f Red
                    throw $_
                }


                foreach ($product in $requiredSubscriptions) {
                    Write-Host "`t`t`tSku : $product`t" -NoNewline
                    if ($product -in $userLicenses.skuPartNumber) {
                        Write-Host 'licensed' -f Green
                        continue
                    }

                    Write-Host 'missing license' -f Red

                    $sku = $subscribedSku | Where SkuPartNumber -eq $product
                    if ( -not $sku) {
                        Write-Host "`t`t`tProduct not found" -f Red
                        throw "No subscription found for product: $product"
                    }

                    Write-Host "`t`tAssign license`t" -NoNewline

                    $params = @{
                            addLicenses = @(
                                @{
                                    disabledPlans = @()
                                    skuId = $sku.skuId
                                }
                            )
                            removeLicenses = @()
                        }
                    $jsonBody = @"
{
    "addLicenses": [
        {
            "disabledPlans": [],
            "skuId" : "$($sku.skuId)"
        }
    ],
    "removeLicenses": []
}
"@

                    try {
                        #$userLicense = Set-MgUserLicense -UserId $assignedUser.Id -BodyParameter $params -ErrorAction Stop
                        $uri  = "https://graph.microsoft.com/beta/users/$($assignedUser.id)/assignLicense"
                        $userLicense = (Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ContentType 'application/json' -ErrorAction Stop).value
                        Write-Host "success" -f Green
                    }
                    catch {
                        Write-Host "failed" -f Red
                        throw $_
                    }


                } # foreach product

            } # foreach username

        }

    #endregion


    # Entra Connect ID must be configured for Hybrid Join

    # https://docs.microsoft.com/en-us/windows/deployment/windows-autopilot/windows-autopilot-requirements#networking-requirements

#endregion

#region: Overview

    Write-Host "Overview" -f Cyan

    Write-Host "`tScript : $PSCommandPath" #$($myInvocation.InvocationName)"
    Write-Host "`tTenant Name : $tenantDnsName"
    Write-Host "`tTenant Id : $tenantId"
    Write-Host "`tHybrid Join : $HybridJoin"
    if ($HybridJoin) {
        Write-Host "`tAD Domain : $domainDnsName"
        Write-Host "`tDC : $domainControllerName (WinRM: $($sessionDC.State))"
        Write-Host "`tSynced OU : $SyncedOU"
        Write-Host "`tUsers OU : $ldapPathUsers"
        Write-Host "`tODJ Server : $odjConnectorServer (WinRM: $($sessionSVR.State))"
        Write-Host "`tSvc Account : $odjSvcAccountName"
        Write-Host "`tDevice group : $autopilotGroupName"
    }
    Write-Host "`tLicensed group: $mdmUsersGroupName"
    Write-Host "`tAssigned User : " -NoNewline
    if ($assignUser) {
        Write-Host "$assignedUserUpn [Pass: $assignedUserPassPlain]"
    }
    else {
        Write-Host 
    }
    Write-Host "`tEnrollPolicy : $enrollmentProfileName"
    Write-Host "`tESP : $espDisplayName"
    Write-Host "`tMgGraph : $(if (Get-MgContext) {'Connected'} else {'Not connected'})"

#endregion

##################################################

#region: [Entra ] Intune Users group

    Write-Host "Entra Intune Users group" -f Cyan

    write-Host "`tGet existing Entra group: $mdmUsersGroupName`t" -NoNewline
    try {
        #[array]$entraGroups = Get-MgGroup -Filter "displayName eq '$autopilotGroupName'" -ErrorAction Stop
        $uri  = "https://graph.microsoft.com/beta/groups?`$filter=displayName eq '$mdmUsersGroupName'"
        [array]$entraGroups =  (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value

        Write-Host "found $($entraGroups.count)" -f Yellow
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

    foreach ($group in $entraGroups) {
        write-Host "`tRemove group: $mdmUsersGroupName`t" -NoNewline
        try {
            $uri = "https://graph.microsoft.com/beta/groups/$($group.id)"
            $return = Invoke-MgGraphRequest -Method DELETE -Uri $uri -ErrorAction Stop
            Write-Host "success" -f Green
       }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }

    }

    Write-Host "`tCreate group: $mdmUsersGroupName`t" -NoNewline
    try {
        #$autopilotGroup = New-MgGroup -DisplayName $autopilotGroupName -MailEnabled:$false -MailNickName $autopilotGroupName.Replace(' ','') -GroupTypes 'DynamicMembership' -MembershipRule $apMembershipRule -MembershipRuleProcessingState 'On' -SecurityEnabled -ErrorAction Stop
        $jsonBody = @"
{
    "DisplayName" : "$mdmUsersGroupName",
    "MailEnabled" : false,
    "MailNickName" : "$($mdmUsersGroupName.Replace(' ',''))",
    "GroupTypes" :
        [
                "DynamicMembership"
        ],
    "MembershipRule" : "$($mdmMembershipRule.Replace('"','\"'))",
    "MembershipRuleProcessingState": "On",
    "SecurityEnabled": true
}
"@
 #| ConvertFrom-Json
        $uri      = 'https://graph.microsoft.com/beta/groups'
        $mdmGroup = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ErrorAction Stop
        write-Host "[$($mdmGroup.id)]`tsuccess" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

    if ($assignUser) {

        Write-Host "`tProcessing membershiprule for assigned user " -NoNewline
        try {
            do {
                Write-Host '.' -NoNewline
                sleep 5
                $uri  = "https://graph.microsoft.com/beta/groups/$($mdmGroup.id)/members"
                [array]$mdmGroupMembers = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value

            }
            until ($assignedUserUpn -in $mdmGroupMembers.UserPrincipalName)
            Write-Host " $assignedUserUpn" -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }
    }

#endregion

#region: [Entra ] Autopilot group

    Write-Host "Entra Autopilot group" -f Cyan

    write-Host "`tGet existing Entra group: $autopilotGroupName`t" -NoNewline
    try {
        #[array]$entraGroups = Get-MgGroup -Filter "displayName eq '$autopilotGroupName'" -ErrorAction Stop
        $uri  = "https://graph.microsoft.com/beta/groups?`$filter=displayName eq '$autopilotGroupName'"
        [array]$entraGroups =  (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value

        Write-Host "found $($entraGroups.count)" -f Yellow
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

    foreach ($group in $entraGroups) {
        write-Host "`tRemove group: $autopilotGroupName`t" -NoNewline
        try {
            $uri = "https://graph.microsoft.com/beta/groups/$($group.id)"
            $return = Invoke-MgGraphRequest -Method DELETE -Uri $uri -ErrorAction Stop
            Write-Host "success" -f Green
       }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }

    }

    Write-Host "`tCreate group: $autopilotGroupName`t" -NoNewline
    try {
        #$autopilotGroup = New-MgGroup -DisplayName $autopilotGroupName -MailEnabled:$false -MailNickName $autopilotGroupName.Replace(' ','') -GroupTypes 'DynamicMembership' -MembershipRule $apMembershipRule -MembershipRuleProcessingState 'On' -SecurityEnabled -ErrorAction Stop
        $jsonBody = @"
{
    "DisplayName" : "$autopilotGroupName",
    "MailEnabled" : false,
    "MailNickName" : "$($autopilotGroupName.Replace(' ',''))",
    "GroupTypes" :
        [
                "DynamicMembership"
        ],
    "MembershipRule" : "$($apMembershipRule.Replace('"','\"'))",
    "MembershipRuleProcessingState": "On",
    "SecurityEnabled": true
}
"@
 #| ConvertFrom-Json
        $uri            = 'https://graph.microsoft.com/beta/groups'
        $autopilotGroup = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ErrorAction Stop
        write-Host "[$($autopilotGroup.id)]`tsuccess" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }



#endregion

#region: [Intune] Autopilot Enrollment profile

    Write-Host "Intune Autopilot Enrollment profile" -f Cyan

    Write-Host "`tGet existing profile(s): $enrollmentProfileName`t" -NoNewline
    try {

        #[array]$profiles = Get-MgBetaDeviceManagementWindowsAutopilotDeploymentProfile -Filter "displayName eq '$enrollmentProfileName'" -ErrorAction Stop -ExpandProperty *
        $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles?`$filter=displayName eq '$enrollmentProfileName'"
        [array]$profiles = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
        Write-Host "found $($profiles.count)" -f Yellow
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

    # Remove Profiles + Assignments
    foreach ($profile in $profiles) {
        try {
            Write-Host "`tRemove assignments from profile: $($profile.displayName)"
            #$assignments = Get-MgBetaDeviceManagementWindowsAutopilotDeploymentProfileAssignment -WindowsAutopilotDeploymentProfileId $profile.Id
            $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($profile.id)/assignments"
            $assignments = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
            foreach ($assignmentId in $assignments.Id) {
                write-Host "`t`tID: $assignmentId`t" -NoNewline
                #Remove-MgBetaDeviceManagementWindowsAutopilotDeploymentProfileAssignment -WindowsAutopilotDeploymentProfileId $profile.Id -WindowsAutopilotDeploymentProfileAssignmentId $assignmentId -ErrorAction Stop
                $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($profile.id)/assignments/$assignmentId"
                Invoke-MgGraphRequest -Method DELETE -Uri $uri -ErrorAction Stop
                Write-Host "success" -f Green
            }
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }

        try {
            Write-Host "`tRemoving profile: $($profile.displayName)`t" -NoNewline
            #Remove-MgBetaDeviceManagementWindowsAutopilotDeploymentProfile -WindowsAutopilotDeploymentProfileId $profile.Id
            $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($profile.id)"
            Invoke-MgGraphRequest -Method DELETE -Uri $uri -ErrorAction Stop
            Write-Host "success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }

    }

    Write-Host "`tCreate new profile: $enrollmentProfileName`t" -NoNewline
    try {     
        $scopes = 'DeviceManagementServiceConfig.ReadWrite.All'
        Connect-MgGraph -Scopes $scopes -NoWelcome
        $description = "Demo AutoPilot Profile"
        $jsonBody = @{
            "@odata.type"              = "$enrollmentType"
            displayName                = "$($enrollmentProfileName)"
            description                = "$($description)"
            language                   = 'os-default'
            extractHardwareHash        = $false
            enableWhiteGlove           = $false
            outOfBoxExperienceSettings = @{
                "@odata.type"             = "microsoft.graph.outOfBoxExperienceSettings"
                hidePrivacySettings       = $true
                hideEULA                  = $true
                userType                  = 'standard'
                deviceUsageType           = 'singleuser'
                skipKeyboardSelectionPage = $true
                hideEscapeLink            = $true
            }
            enrollmentStatusScreenSettings = @{
                '@odata.type'                                    = "microsoft.graph.windowsEnrollmentStatusScreenSettings"
                hideInstallationProgress                         = $false
                allowDeviceUseBeforeProfileAndAppInstallComplete = $true
                blockDeviceSetupRetryByUser                      = $false
                allowLogCollectionOnInstallFailure               = $true
                customErrorMessage                               = "An error has occured. Please contact your IT Administrator"
                installProgressTimeoutInMinutes                  = "90"
                allowDeviceUseOnInstallFailure                   = $true
            }
        } | ConvertTo-Json        
        
        $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles"
        $created_Profile = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ContentType 'application/json'
        $profileID = $created_Profile.ID

        # New-MgBetaDeviceManagementWindowsAutopilotDeploymentProfile

        Write-Host "success" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }


    write-Host "`tAssign group: $($autopilotGroup.DisplayName)`t" -NoNewline
    try {      
        $jsonBody = @"
{
    "target":
    {
        "@odata.type":"#microsoft.graph.groupAssignmentTarget",
        "groupId":"$($autopilotGroup.Id)"
    }
}
"@

        $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($profileID)/assignments"
        $assignment = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ContentType 'application/json'
        Write-Host "success" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

#endregion

#region: [Intune] ESP

    Write-Host "Intune Enrollment Status Page (ESP)" -f Cyan

    Write-Host "`tGet ESP`t" -NoNewLine
    try {
        $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations"
        [array]$response = (GetEnrollmentStatusPage -Filter "displayName eq '$espDisplayName'")
        Write-Host "found $($response.count)" -f Yellow
    }
    catch {
        Write-Host "failed" -f Red
    }

    foreach ($esp in $response) {
        Write-Host "`tRemove ESP: $($esp.displayName)`t" -NoNewline
        try {
            $uriESP = "$uri/$($esp.id)"
            $objOut = Invoke-MgGraphRequest -Uri $uriESP -Method DELETE -ErrorAction Stop
            Write-Host "success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
        }
    }

    Write-Host "`tCreate ESP"
    Write-Host "`t`tType: $autopilotType"
    Write-Host "`t`tName: $espDisplayName`t" -NoNewline
    try {
        $esp = NewEnrollmentStatusPage `
            -displayName $espDisplayName `
            -description 'ESP for demo Autopilot' `
            -ShowProgress `
            -allowCollectLogs `
            -blockDeviceSetupRetryByUser:$true `
            -ErrorMessage "Oops, an error occurred! Please contact your IT department" `
            -allowUseOnFailure:$false `
            -allowResetOnError `
            -BlockDeviceUntilComplete `
            -Priority 99 `
            -timeoutInMinutes 90 `
            -ErrorAction Stop `
            -Replace

        Write-Host "success" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }



    write-Host "`tAssign group: $($autopilotGroup.DisplayName)`t" -NoNewline
    try {      
        $assignmentBody = @"
{
    enrollmentConfigurationAssignments: [
        {
          "target": {
            "@odata.type": "#microsoft.graph.groupAssignmentTarget",
            "groupId": "$($autopilotGroup.Id)"
          }
        }
    ]
}
"@


        $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations/$($esp.id)/assign"

        $assignment = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $assignmentBody -ContentType 'application/json'
        Write-Host "success" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }

#endregion

#region: [Intune] Device Policy: Disable User ESP

    Write-Host "Device Policy: Disable User ESP" -f Cyan

    $omaName  = 'SkipUserStatusPage'
    $omaUri   = "./Vendor/MSFT/DMClient/Provider/MS DM Server/FirstSyncStatus/SkipUserStatusPage"
    $omaValue = $true

    Write-Host "`tCreate Device Configuration Policy"
    Write-Host "`t`tPolicy name: $devicePolicyESP"


    Write-Host "`t`tGet existing Policy`t" -NoNewline
    # $deviceConfigurations = Get-MgDeviceManagementDeviceConfiguration | Where DisplayName -eq $devicePolicyESP # Not retrieving all kinds of policies
    $deviceConfigurations = Get-3gIntuneDeviceConfigurationProfile | Where DisplayName -eq $devicePolicyESP
    Write-Host "found $(($deviceConfigurations|Measure-Object).count)" -f Green

    foreach ($config in $deviceConfigurations) {
        Write-Host "`t`tRemove existing Policy: $($config.id)`t" -NoNewline
        try {
            # Remove-MgDeviceManagementDeviceConfiguration -DeviceConfigurationId $config.Id -ErrorAction Stop
            $removed = Remove-3gIntuneDeviceConfigurationPolicy -Id $config.Id -ResourceType $config.resourceType -ErrorAction Stop

            Write-Host "Success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }
    }


    $jsonBody = @"
{
    "@odata.type" : "#microsoft.graph.windows10CustomConfiguration",
    "displayName" : "$devicePolicyESP",
    "omaSettings" : [
        {
            "@odata.type" : "#microsoft.graph.omaSettingBoolean",
            "displayName" : "$omaName",
            "omaUri" : "$omaUri",
            "value" : "$omaValue"
        }
    ]
}
"@

    $test     = $jsonBody | ConvertFrom-Json -ErrorAction Stop

    Write-Host "`t`tCreate new Policy`t" -NoNewline
    try {
        #New-MgDeviceManagementDeviceConfiguration -DisplayName $policyDisplayName -AdditionalProperties $jsonBody
        $method  = 'POST'
        $version = 'beta'
        $uri     = "https://graph.microsoft.com/$version/deviceManagement/deviceConfigurations"
        $restResult = (Invoke-MgGraphRequest -Method $method -Uri $uri -Body $jsonBody -ErrorAction Stop).value
        Write-Host "success" -f Green
    }
    catch {
        Write-Host "failed" -f Red
        throw $_
    }


    # Assign policy
    $deviceConfigurations = Get-3gIntuneDeviceConfigurationProfile | Where DisplayName -eq $devicePolicyESP

    Write-Host "`t`tGet Group: $autopilotGroupName`t" -NoNewline
    $uri = "https://graph.microsoft.com/beta/groups?`$filter=displayName eq '$autopilotGroupName'"
    try {
        $autopilotGroup = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value
        Write-Host "found $($autopilotGroup.count)" -f Yellow
        $autopilotGroupId = $autopilotGroup.Id
    }
    catch {
        Write-Host 'failed' -f Red
        throw $_
    }

    Write-Host "`tAssign Group to policy"
    write-host "`t`tPolicy: $($deviceConfigurations.id)"
    Write-Host "`t`tGroup : $($autopilotGroupName)"
    $target = @{
        '@odata.type' ='#microsoft.graph.groupAssignmentTarget'
        "deviceAndAppManagementAssignmentFilterId" = ""
        "deviceAndAppManagementAssignmentFilterType" = "none"
        "groupId" = $autopilotGroupId
    }
    New3gDeviceManagementDeviceConfigurationAssignment -DeviceConfigurationId $deviceConfigurations.Id -Target $target -ErrorAction Stop

#endregion

#region: [Entra ] MDM enrollment

    Write-Host "Entra ID Automatic MDM enrollment" -f Cyan

    # Policy.ReadWrite.MobilityManagement
    Write-Host "`tCurrent MDM policy`t" -NoNewline
    try {
        $uri = 'https://graph.microsoft.com/beta/policies/mobileDeviceManagementPolicies/0000000a-0000-0000-c000-000000000000?$expand=includedGroups'
        $policy = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop

    }
    catch {
        Write-Host 'failed' -f Red
        throw $_
    }

    $mdmAppliesTo = $policy.appliesTo
    Write-Host $mdmAppliesTo -f Green

    foreach ($group in $policy.includedGroups) {
        Write-Host "`t`tGroup: " -NoNewline
        if ($group.id -eq $mdmGroup.Id) {
            Write-Host "$($group.displayName) [$($group.id)]" -f Green
        }
        else {
            Write-Host "$($group.displayName) [$($group.id)]"
        }
    }

    if ( ($mdmAppliesTo -ne 'all') -and ($mdmGroup.Id -notin $policy.includedGroups.id) ) {

        Write-Host "`tUpdate current MDM policy"
        Write-Host "`t`tScope: Selected"
        Write-Host "`t`tGroup: $mdmUsersGroupName [$($mdmGroup.id)]`t" -NoNewline
        #$uri = "https://graph.microsoft.com/beta/groups?`$filter=displayName eq '$mdmUsersGroupName'"
        $uri = "https://graph.microsoft.com/beta/groups/$($mdmGroup.id)"
        try {
            $entraGroup = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop)
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }
        if ($entraGroup.id) {
            Write-Host 'found' -f Green
            Write-Host "`t`tID : $($entraGroup.id)`t" -NoNewline
        }
        else {
            Write-Host 'not found' -f Red
            throw "Group not found: $mdmGroupName"
        }
        $jsonBody = @"
{
    "requests": [
        {
            "id": "1",
            "method": "POST",
            "url": "/policies/mobileDeviceManagementPolicies/0000000a-0000-0000-c000-000000000000/includedGroups/`$ref",
            "body": {
                "@odata.id": "https://graph.microsoft.com/odata/groups('$($entraGroup.id)')"
            },
            "headers": {
                "x-ms-command-name": "MDMApplications - AddMdmGroup",
                "Content-Type": "application/json"
            }
        }
    ]
}
"@

        try {
            $uri = 'https://graph.microsoft.com/beta/policies/mobileDeviceManagementPolicies/0000000a-0000-0000-c000-000000000000/includedGroups/$ref'
            $uri = 'https://graph.microsoft.com/beta/$batch'
            $policy = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $jsonBody -ErrorAction Stop
            Write-Host 'added' -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }
    }


#endregion

if ($HybridJoin) {

    #region: [ADDS ] AD User for ODJ-Connector

        Write-Host "AD Service account for ODJ Connector" -f Cyan

        <#
        This connector service account must have the following permissions:
            Log on as a service
            Must be part of the Domain user group
            Must be a member of the local Administrators group on the Windows server that hosts the connectorm
 
        Important
        Managed service accounts aren't supported for the service account.
        The service account must be a domain account.
 
        #>


        # https://petri.com/creating-active-directory-user-accounts-adsi-powershell/


        Write-Host "`tODJ service account"
        Invoke-Command -Session $sessionDC {

            $odjSvcAccountName  = $using:odjSvcAccountName
            $ldapPathSvcAcct    = $using:ldapPathSvcAcct
            $passPlain          = $using:passPlain
            $passSecure         = $using:passSecure

            Write-Host "`t`tUser : $odjSvcAccountName`t" -NoNewline
            try {
                $objUser = Get-ADUser -Identity $odjSvcAccountName -ErrorAction Stop
                Write-Host "present" -f Green
            }
            catch {
                Write-Host 'not found' -f Yellow
                $objUser = $null
            }
            if ( -not $objUser) {
                try {
                    Write-Host "`t`tCreate account`t" -NoNewline
                    $objUser = New-ADUser `
                        -Path $ldapPathSvcAcct  `
                        -Name $odjSvcAccountName `
                        -AccountPassword $passSecure `
                        -Enabled $true `
                        -ErrorAction Stop
                    Write-Host 'success' -f Green
                }
                catch {
                    Write-Host 'failed' -f Red
                    throw $_
                }
            }

            Write-Host "`t`tPass : " -NoNewline
            try {
                # make sure the password has not changed
                $objUser | Set-ADAccountPassword -NewPassword $passSecure -ErrorAction Stop
                Write-Host $passPlain -f Green
            }
            catch {
                Write-Host 'failed' -f Red
                throw $_
            }

        }


    #endregion

    #region: [ADDS ] Delegate permissions to Organizational Unit

        Write-Host "Allow ODJ Connector to add computer accounts" -f Cyan

        Invoke-Command -Session $sessionDC {

            $SyncedOU      = $using:SyncedOU
            $odjSvcAccountName = $using:odjSvcAccountName

            write-Host "`tDelegate AD permissions"
            write-Host "`t`tOU : $SyncedOU"
            write-Host "`t`tPermission: Full Control on Computer objects"

            Write-Host "`t`tUsername : $odjSvcAccountName`t" -NoNewline

            try {
                $user     = Get-ADuser -Identity $odjSvcAccountName -ErrorAction Stop
                $userSID  = [System.Security.Principal.SecurityIdentifier] $user.SID
                $identity = [System.Security.Principal.IdentityReference] $userSID

                $computers            = [GUID]"bf967a86-0de6-11d0-a285-00aa003049e2"
                $resetPassword        = [GUID]"00299570-246d-11d0-a768-00aa006e0529"
                $validatedDNSHostName = [GUID]"72e39547-7b18-11d1-adef-00c04fd8d5cd"
                $validatedSPN         = [GUID]"f3a64788-5306-11d1-a9c5-0000f80367c1"
                $accountRestrictions  = [GUID]"4c164200-20c0-11d0-a768-00aa006e0529"

                $ruleCreateAndDeleteComputer = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($identity, "CreateChild, DeleteChild", "Allow", $computers, "All")
                $ruleResetPassword           = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($identity, "ExtendedRight", "Allow", $resetPassword, "Descendents", $computers)
                $ruleValidatedDNSHostName    = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($userSID,  "Self", "Allow", $validatedDNSHostName, "Descendents", $computers)
                $ruleValidatedSPN            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($userSID,  "Self", "Allow", $validatedSPN, "Descendents", $computers)
                $ruleAccountRestrictions     = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($identity, "ReadProperty, WriteProperty", "Allow", $accountRestrictions, "Descendents", $computers)

                $acl = Get-Acl -Path "AD:\$SyncedOU" -ErrorAction Stop
                $acl.AddAccessRule($ruleCreateAndDeleteComputer)
                $acl.AddAccessRule($ruleResetPassword)
                $acl.AddAccessRule($ruleValidatedDNSHostName)
                $acl.AddAccessRule($ruleValidatedSPN)
                $acl.AddAccessRule($ruleAccountRestrictions)
                Set-Acl -Path "AD:\$SyncedOU" -AclObject $acl -ErrorAction Stop

                Write-Host "success" -f Green

            }
            catch {

                Write-Host "failed" -f Red
                throw $_

            }

        } # Invoke-Command

    #endregion

    #region: [Server] Add AD-User-Account to local Administrators on ODJ-Servers

        Write-Host "Local Administrators on ODJ-Servers" -f Cyan

        Write-Host "`tAdd User to local group"
           
        Write-Host "`t`tServer: $odjConnectorServer"
        Write-Host "`t`tGroup : Administrators"

        Write-Host "`t`t Remove invalid member references because of bug " -NoNewline
        Invoke-Command -Session $sessionSVR -ErrorAction Stop {

            $domainNBName      = $using:domainNBName
            $odjSvcAccountName = $using:odjSvcAccountName
            $newMember         = "$domainNBName\$odjSvcAccountName"
            $localGroup        = 'Administrators'

            try {
                $localAdmins = (@(
                    ([ADSI]"WinNT://./$localGroup").psbase.Invoke('Members') | ForEach-Object { 
                        $_.GetType().InvokeMember('AdsPath', 'GetProperty', $null, $($_), $null) 
                    }
                ) -match '^WinNT') -replace 'WinNT://', ''
        
                foreach ($member in $localAdmins) {   
                    if ($member -like "$env:COMPUTERNAME/*" -or $member -like "$domainNBName/*" -or $member -like "AzureAd/*") {
                        #Write-Host "`t`t`t$member"
                    }
                    else {
                        Remove-LocalGroupMember -group $localGroup -member $member -ErrorAction Stop
                    }
                }
                Write-Host 'success' -f Green
            }
            catch {
                Write-Host "failed" -f Red
                throw $_
            }

            Write-Host "`t`tUser : $newMember`t" -NoNewline
            try {
                if ($newMember.Replace('\','/') -notin $localAdmins) {
                    Add-LocalGroupMember -Name 'Administrators' -Member $newMember  -ErrorAction Stop
                    Write-Host "success" -f Green
                }
                else {
                    Write-Host "present" -f Yellow
                }
            }
            catch {
                Write-Host "failed" -f Red
                throw $_
            }
        }


    #endregion

    #region: [Server] Assign AD-User-Account 'Logon as a Service' right

        Write-Host "`tAssign privilege"
        Write-Host "`t`tPrivilege: Logon as a Service"
        Write-Host "`t`tComputer : $odjConnectorServer"
        Write-Host "`t`tAccount : $odjSvcAccountName " -NoNewline

        Invoke-Command -Session $sessionSVR {

            Function GrantLogonAsService {

                [CmdletBinding()]

                param($accountToAdd)

                # written by Ingo Karstein, https://blog.kenaro.com/2012/10/12/powershell-script-to-add-account-to-allow-logon-locally-privilege-on-local-security-policy/
                # Original: https://gallery.technet.microsoft.com/PowerShell-script-to-add-b005e0f6
                # v1.0, 01/03/2014
 
                ## <--- Configure here
 
                if( [string]::IsNullOrEmpty($accountToAdd) ) {
                    Write-Error "no account specified"
                    exit
                }
 
                ## ---> End of Config
 
                $sidstr = $null
                try {
                    $ntprincipal = new-object System.Security.Principal.NTAccount "$accountToAdd"
                    $sid = $ntprincipal.Translate([System.Security.Principal.SecurityIdentifier])
                    $sidstr = $sid.Value.ToString()
                } catch {
                    $sidstr = $null
                }
 
                Write-Verbose "Account: $($accountToAdd)"
 
                if( [string]::IsNullOrEmpty($sidstr) ) {
                    Write-Verbose "Account not found!"
                    exit -1
                }
 
                Write-Verbose "Account SID: $($sidstr)"
 
                $tmp = [System.IO.Path]::GetTempFileName()
 
                Write-Verbose "Export current Local Security Policy"
                $msg = (secedit.exe /export /cfg "$($tmp)")
                $msg | foreach {Write-Verbose $_}
 
                $c = Get-Content -Path $tmp
 
                $currentSetting = ""
 
                foreach($s in $c) {
                    if( $s -like "SeServiceLogonRight*") {
                        $x = $s.split("=",[System.StringSplitOptions]::RemoveEmptyEntries)
                        $currentSetting = $x[1].Trim()
                    }
                }
 
                if( $currentSetting -notlike "*$($sidstr)*" ) {
                    # Write-Verbose "Modify Setting ""Allow Logon Locally"""
                    Write-Verbose "Modify Setting ""Logon as a Service"""
 
                    if( [string]::IsNullOrEmpty($currentSetting) ) {
                        $currentSetting = "*$($sidstr)"
                    } else {
                        $currentSetting = "*$($sidstr),$($currentSetting)"
                    }
 
                    Write-Verbose "$currentSetting"
 
                    $outfile = @"
[Unicode]
Unicode=yes
[Version]
signature="`$CHICAGO`$"
Revision=1
[Privilege Rights]
SeServiceLogonRight = $($currentSetting)
"@

 
                    $tmp2 = [System.IO.Path]::GetTempFileName()
 
 
                    Write-Verbose "Import new settings to Local Security Policy" 
                    $outfile | Set-Content -Path $tmp2 -Encoding Unicode -Force
 
                    Push-Location (Split-Path $tmp2)
 
                    try {
                        Write-Verbose "secedit.exe /configure /db ""secedit.sdb"" /cfg ""$($tmp2)"" /areas USER_RIGHTS "
                        $msg = (secedit.exe /configure /db "secedit.sdb" /cfg "$($tmp2)" /areas USER_RIGHTS)
                        $msg | foreach {Write-Verbose $_}


                    } finally {
                        Pop-Location
                    }
                } else {
                    Write-Verbose "NO ACTIONS REQUIRED! Account already in ""Logon as a Service""" 
                }
 
                Write-Verbose "Done" 
            }

            try {
                GrantLogonAsService -accountToAdd "$using:domainNBName\$using:odjSvcAccountName" -ErrorAction Stop
                Write-Host 'success' -f Green
            }
            catch {
                Write-Host 'failed' -f Red
                throw $_
            }

        }

    #endregion

    #region: [Server] Intune Connector

        Write-Host "Install Intune Connector" -f Cyan

        Write-Host "`tServer : $odjConnectorServer"
        try {
            $svc = Invoke-Command -Session $sessionSVR -ErrorAction Stop -ScriptBlock {
                Write-Host "`tService : $($using:odjSvcName)`t" -NoNewline
                Get-Service -ErrorAction Stop | Where-Object Name -eq $using:odjSvcName
            }
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

        if ($svc) {
            Write-Host 'installed' -f Yellow
            Write-Host "`tStatus : $($svc.status)"
        }
        else {
            Write-Host 'not found' -f Yellow
            Write-Host "`tDownload ODJBootstrapper.exe on $odjConnectorServer`t" -NoNewline
            try {

                Invoke-Command -Session $sessionSVR -ErrorAction Stop -ScriptBlock {
                    $url  = 'https://download.microsoft.com/download/C/6/D/C6DAA9FD-7DCA-4577-9016-AE72A8150149/ODJConnectorBootstrapper.exe'
                    $file = "$env:USERPROFILE\Downloads\ODJConnectorBootstrapper.exe"
                    if (Test-Path $file) {
                        Write-Host "present" -f Yellow
                    }
                    else {
                        Invoke-WebRequest -Uri $url -OutFile $file -ErrorAction Stop
                        Write-Host "success" -f Green
                    }
                    Write-Host "`tPath: $file"

                }

            }
            catch {

                Write-Host "failed" -f Red
                throw $_

            }


            Write-Host "`tInstall 'ODJConnectorBootstrapper.exe' on $odjConnectorServer" -f Yellow -b Red
            #Read-Host "`tPress Enter when ready"

        }

        Write-Host "`tConnectors " -NoNewline
        Do {
            try {
                $uri = "https://graph.microsoft.com/beta/deviceManagement/domainJoinConnectors"
                $connectors = (Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop).value
            }
            catch {
                throw $_
            }
            if ($connectors.count -eq 0) {
                Write-Host '.' -NoNewline
                sleep 10
            }
        } Until ($connectors.count -gt 0)
        Write-Host "found $($connectors.count)" -f Green
        foreach ($djConnector in $connectors) {
            if ($djconnector.state -eq 'active') {$fColor = 'Green'} else {$fColor = 'Red'}
            write-host "`t`tServer: $($djConnector.displayName)`t`tstate: $($djConnector.state)" -f $fColor -NoNewline
            write-host "`t`t(last sync: $($djConnector.lastConnectionDateTime))"


        }

    #endregion

    #region: [Server] Set Intune Connector service

        Write-Host "Set Intune Connector service" -f Cyan


        Write-Host "`tServer : $odjConnectorServer"
        Write-Host "`tService: $odjSvcName"
        write-Host "`tStatus : " -NoNewLine
        $svcStatus = (Invoke-Command -Session $sessionSVR -ScriptBlock {
            Get-Service -Name $using:odjSvcName -ErrorAction Stop
        }).Status
        if ($svcStatus -eq 'Running') {
            $fColor = 'Green'
        }
        else {
            $fColor = 'Red'
        }
        Write-Host $svcStatus -f $fColor

        Write-Host "`tAccount: $domainNBName\$odjSvcAccountName`t" -NoNewline
        try {
            $null = SetServiceAccount -ComputerName $odjConnectorServer -ServiceName $odjSvcName -ConnectionMethod WSMAN -serviceAccountName "$domainNBName\$odjSvcAccountName" -serviceAccountPassword $odjSvcAccountPass
            Write-Host "success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }


        Write-Host "`tStartup: Automatic (delayed)`t" -NoNewline
        try {
            Invoke-Command -Session $sessionSVR -ErrorAction Stop -ScriptBlock {
                Set-Service -Name $using:odjSvcName -StartupType Automatic -ErrorAction Stop
                Write-Host "success" -f Green
            }
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }


        Write-Host "`tRestart service`t" -NoNewline
        try {
            Invoke-Command -Session $sessionSVR -ErrorAction Stop -ScriptBlock {
                Restart-Service -Name $using:odjSvcName -ErrorAction Stop
                Write-Host "success" -f Green
            }
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }


    #endregion

    #region: [Intune] Device Policy: Domain Join

        Write-Host "Device Policy: Domain Join" -f Cyan

        Write-Host "`tCreate Device Configuration Policy"
        Write-Host "`t`tName : $devicePolicyDJName"
        Write-Host "`t`tDomain: $domainDnsName"
        Write-Host "`t`tOU : $SyncedOU"


        Write-Host "`t`tGet existing policies`t" -NoNewline
        [array]$intunePolicies = Get-3gIntuneDeviceConfigurationPolicy -ErrorAction Stop | Where displayName -eq $devicePolicyDJName
        Write-Host "found $($intunePolicies.count)" -f Yellow

        if ($intunePolicies.count -gt 0) {
        
            foreach ($policy in $intunePolicies) {
                    Write-Host "`t`tRemove existing policy: $($policy.id)`t" -NoNewline
                try {
                    $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$($policy.id)"
                    $null = Invoke-MgGraphRequest -Uri $uri -Method DELETE -ErrorAction Stop
                    Write-Host "success" -f Green
                }
                catch {
                    Write-Host "failed" -f Red
                    throw $_
                }            

            } # foreach policy
        }

        Write-Host "`t`tCreate new policy`t" -NoNewline
        $jsonBody = @"
{
    "id": "00000000-0000-0000-0000-000000000000",
    "displayName": "$devicePolicyDJName",
    "description": "$devicePolicyDJDescr",
    "roleScopeTagIds": [
    "0"
    ],
    "@odata.type": "#microsoft.graph.windowsDomainJoinConfiguration",
    "computerNameStaticPrefix": "HJ-",
    "activeDirectoryDomainName": "$domainDnsName",
    "organizationalUnit": "$SyncedOU",
    "computerNameSuffixRandomCharCount": 12
}
"@

        try {
            $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations"
            $policy = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ErrorAction Stop
            Write-Host "success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }


        Write-Host "`t`tAssign policy"
        Write-Host "`t`t`tGroup: $($autopilotGroup.DisplayName)`t" -NoNewline

        $jsonBody = @"
{
    "assignments": [
    {
        "target": {
        "@odata.type": "#microsoft.graph.groupAssignmentTarget",
        "groupId": "$($autopilotGroup.id)"
        }
    }
    ]
}
"@


        try {
            $uri = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$($policy.id)/assign"
            $connectors = (Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ErrorAction Stop).value
            Write-Host "success" -f Green
        }
        catch {
            Write-Host "failed" -f Red
            throw $_
        }



    #endregion

}

#region: [Intune] Prepare Import devices

    #region: [Intune] Wait for other import activities to finish
    
        Write-Host "Import devices (csv file)" -f Cyan

        Write-Host "`tWait for running imports to finish`t" -NoNewline

        $count    = 0
        $savCount = 0
        $uri = "https://graph.microsoft.com/beta/deviceManagement/importedWindowsAutopilotDeviceIdentities?`$`select=id,state"
        Do {
            $responses = (Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop)
            if ($responses.value) {
                $responses = $responses.value
            }
            $count = ($responses.state | Where deviceImportStatus -eq 'unknown').Count
            if (($count -ne 0) -and ($savCount -ne $count)) {
                Write-Host "[$count]" -f Yellow -NoNewline
                $savCount = $count
            }
            if ($count -gt 0) {
                Write-Host '.' -NoNewline
                sleep 5
            }
        }
        Until (
            $count -eq 0
        )
        Write-Host "[$count]" -f Green

    #endregion

    #region: [Intune] Read CSV file

        Write-Host "`tRead CSV file"

        While (-not (Test-Path $csvPath) ) {
            $csvPath = Read-Host "Enter path to Csv-file"
        }

        Write-Host "`t`tFile : $csvPath"
        Write-Host "`t`tImport : " -NoNewline
        try {
            $csvImport = (Import-Csv -Path $csvPath -ErrorAction Stop)
            Write-Host "success" -f Green
        }
        catch {
            Write-Host 'failed' -f Red
            throw $_
        }

    #endregion

    foreach ($csvItem in $csvImport) {

        #region: [Intune] Process CSV items


                Write-Host "Process CSV-item" -f Cyan
                $deviceSerNo = $csvItem.'Device Serial Number'
                Write-Host "`tSerial Nr: $deviceSerNo"


                if ($csvItem.PSobject.Properties.Name -notcontains 'Group Tag') {
                    try {
                        Write-Host "`t`tAdd missing property: Group Tag`t" -f Yellow -NoNewline
                        Add-Member -InputObject $csvItem -MemberType NoteProperty -Name 'Group Tag' -Value '' -ErrorAction Stop
                        Write-Host 'success' -f Green
                    }
                    catch {
                        Write-Host 'failed' -f Red
                        throw $_
                    }

                }

                Write-Host "`tGroup Tag: " -NoNewline
                if ($csvItem.'Group Tag' -eq $orderID) {
                    Write-Host $csvItem.'Group Tag' -f Green
                }
                else {
                    Write-Host "$($csvItem.'Group Tag') " -NoNewline
                    Write-Host '(invalid for this demo)' -f Yellow
                    $csvItem.'Group Tag' = $orderID
                    Write-Host "`tNew Group Tag applied: " -NoNewline
                    Write-Host "$orderID" -f Green
                }

                $deviceGroupTag  = $csvItem.'Group Tag'
                $deviceHash      = $csvItem.'Hardware Hash'
                $deviceProductId = $csvItem.'Windows Product ID'


        #endregion

        #region: [Intune] Remove device from Intune

            Write-Host "Remove previously registered devices" -f Cyan

            Write-Host "`tGet Intune devices"
            Write-Host "`t`tSerial Nr : $deviceSerNo`t" -NoNewline
            try {
                #[array]$devices = Get-MgDeviceManagementManagedDevice -ErrorAction Stop | where serialNumber -eq $deviceSerNo
                $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=serialNumber eq '$deviceSerNo'"
                [array]$intuneDevices = (Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop).value #| Where-Object serialNumber -eq $deviceSerNo
                Write-Host "found $($intuneDevices.count)" -f Yellow
            }
            catch {
                Write-Host 'failed' -f Red
                throw $_
            }
            foreach ($device in $intuneDevices) {
                try {
                    Write-Host "`tRemove Intune device"
                    write-Host "`t`tName: $($device.deviceName)"
                    Write-Host "`t`tID : [$($device.id)] " -NoNewline
                    #Remove-MgDeviceManagementManagedDevice -ManagedDeviceId $device.id -ErrorAction Stop
                    $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices/$($device.id)"
                    $null = Invoke-MgGraphRequest -Uri $uri -Method DELETE -ErrorAction Stop
                    write-Host 'deletion initiated' -f Green
                }
                catch {
                    Write-Host 'failed' -f Red
                }
            }

        #endregion

        #region: [Intune] Remove device from Autopilot
 
            Write-Host "`tGet Autopilot device"
            write-host "`t`tSerial Nr: $deviceSerNo`t" -NoNewline
            try {
                $uri      = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities"
                $apDevice = (Invoke-MgGraphRequest -Uri $uri -Method GET -OutputType PSObject -ErrorAction Stop).value | Where serialNumber -eq $deviceSerNo
                if ($apdevice) {
                    Write-Host "found" -f Green
                    Write-Host "`t`tId : $($apDevice.Id)"
                    Write-Host "`t`tEntra Id : $($apDevice.azureAdDeviceId)"
                    Write-Host "`t`tIntuneId : $($apDevice.managedDeviceId)"
                    Write-Host "`t`tState : $($apDevice.enrollmentState)"
                    Write-Host "`t`tName : $(($intuneDevices | Where-Object id -eq $apDevice.managedDeviceId).deviceName)"
                    Write-Host "`t`tContacted: $($apDevice.lastContactedDateTime)"

                }
                else {
                    Write-Host "not found" -f Yellow
                }
            }
            catch {
                Write-Host 'failed' -f Red
                throw $_
            }

            if ($apDevice) {

                Write-Host "`tRemove Autopilot device"
                try {
                    Write-Host "`t`tID: $($apDevice.id)`t" -NoNewline
                    $uri  = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$($apDevice.id)"
                    $null = Invoke-MgGraphRequest -Uri $uri -Method DELETE -ErrorAction Stop
                    Write-Host 'deletion initiated' -f Green
                }
                catch {
                    if ($_.ErrorDetails.Message -match 'ZtdDeviceDeletionInProgess') {
                        Write-Host "previous deletion in progress" -f Yellow
                    }
                    else {
                        Write-Host ' failed' -f Red
                        throw $_
                    }
                }

                Write-Host "`t`tWait for deregistration`t" -NoNewline
                try {
                    # wait for deregistration
                    $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities"
                    Do {
                        Write-Host "." -NoNewline
                        $autopilotDevice = (Invoke-MgGraphRequest -Uri $uri -Method GET -OutputType PSObject -ErrorAction Stop).value | Where serialNumber -eq $deviceSerNo
                        sleep 5
                    }
                    While ( $autopilotDevice )

                    Write-Host ' removed' -f Green
                }
                catch {
                    Write-Host ' failed' -f Red
                    throw $_
                }
            }

        #endregion

        #region: [Entra ] Get device from Entra ID

            if ($apDevice) {

                Write-Host "`tGet Entra device: $($apDevice.azureAdDeviceId)`t" -NoNewline

                try {
                    $uri  = "https://graph.microsoft.com/beta/devices"
                    $entraDevice = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).value | Where-Object deviceId -eq $apDevice.azureAdDeviceId
                    if ($entraDevice) {
                        Write-Host "found" -f Green
                        Write-Host "`t`tDisplayname: $($entraDevice.displayName)"
                        Write-Host "`t`tEnrollment : $($entraDevice.enrollmentType)"
                    }
                    else {
                        Write-Host "not found" -f Yellow
                    }
                }
                catch {
                    Write-Host 'failed' -f Red
                    throw $_
                }

            }
            # $entraDevice | foreach {[pscustomObject]$_} | ogv
            # $entraDevice.enrollmentType -eq 'OnPremiseCoManaged' # Hybrid-Joined OnPremiseSyncEnabled:True
            # $entraDevice.enrollmentType -eq 'AzureDomainJoined' # Entra-Joined
            # $entraDevice.enrollmentType -eq '' # not used


        #endregion

        #region: [ADDS ] Remove device from ADDS

            <#
        Try {
            Write-host "Get computer from Active Directory" -NoNewline
            $adsiSearch = [ADSISearcher]::new()
            $adsiSearch.Filter = "(sAMAccountName=$Computer`$)"
            [void]$adsiSearch.PropertiesToLoad.Add("distinguishedName")
            $computerAccount = $adsiSearch.FindOne()
            If ($computerAccount) {
                Write-host "success" -ForegroundColor Green
 
                Write-Host "Removing computer from Active Directory`t" -NoNewline
                $directoryEntry = $ComputerAccount.GetDirectoryEntry()
                $deletedDevice = $directoryEntry.DeleteTree()
                Write-Host "success" -f Green
            }
            else {
                Write-Host "failed" -f Red
                Write-Error "Device not found in Active Directory"
            }
        }
        Catch
        {
            Write-Host "failed" -f Red
            throw $_
        }
            #>

            if ($entraDevice) {
                try {
                    Write-Host "`tGet ADDS computer: $($entraDevice.displayName)`t" -NoNewline
                    $adsiSearch = [ADSISearcher]::new()
                    $adsiSearch.Filter = "(sAMAccountName=$($entraDevice.displayName)`$)"
                    $null = $adsiSearch.PropertiesToLoad.Add("distinguishedName")
                    $adComputer = $adsiSearch.FindOne()
                    if ($adComputer) {
                        Write-Host "found" -f Green

                        Write-Host "`tRemove ADDS computer: $($entraDevice.displayName)`t" -NoNewline
                        $directoryEntry = $adComputer.GetDirectoryEntry()
                        $null = $directoryEntry.DeleteTree()
                        write-Host 'success' -f Green
                    }
                    else {
                        Write-Host "not found" -f Yellow
                    }
                }
                catch {
                    Write-Host 'failed' -f Red
                }
            }


        #endregion

    }

#endregion

#region: [Intune] Import Device into Autopilot
    
    write-Host "Import devices into Autopilot" -f Cyan

    [array]$importedWindowsAutopilotDeviceIdentities = @()
    foreach ($csvItem in $csvImport) {
        $ht = @{
            serialNumber              = $csvItem.'Device Serial Number'
            productKey                = $csvItem.'Windows Product ID'
            hardwareIdentifier        = $csvItem.'Hardware Hash'
            groupTag                  = $csvItem.'Group Tag'
        }
        if ($assignedUserUpn) {
            $ht.Add('assignedUserPrincipalName', $assignedUserUpn)
        }
        $importedWindowsAutopilotDeviceIdentities += $ht
    }
    $hashBody = @{importedWindowsAutopilotDeviceIdentities = $importedWindowsAutopilotDeviceIdentities}

    $jsonBody = $hashBody | ConvertTo-Json

    try {
        Write-Host "`tImport device"
        Write-Host "`t`tFile : $csvPath`t" -NoNewline
        $uri = "https://graph.microsoft.com/beta/deviceManagement/importedWindowsAutopilotDeviceIdentities/import"
        $request = Invoke-MgGraphRequest -Uri $uri -Method POST -Body $jsonBody -ErrorAction Stop
        Write-Host 'import initiated' -f Green
        Write-Host "`t`tUser : $assignedUserUpn"
    }
    catch {
        Write-Host 'failed' -f Red
        throw $_

    }

    [array]$importRegistrationList = @()

    foreach ($item in $request.value) {

        
        Write-Host "`t`tSerial Nr: $($item.serialNumber)`t" -NoNewline
        $importId = $item.id
        $uri      = "https://graph.microsoft.com/beta/deviceManagement/importedWindowsAutopilotDeviceIdentities/$importId`?`$select=id,state"

        $importReady = $false
        Do {
            Write-Host '.' -NoNewline
            $responses = Invoke-MgGraphRequest -Uri $uri -Method GET -ErrorAction Stop
            if ($responses.state.deviceImportStatus -in 'complete', 'error') {
                $importReady = $true
            }
            else {
                sleep 5
            }
        }
        Until (
            $importReady
        )

        if ($responses.state.deviceImportStatus -eq 'complete') {
            Write-Host "`tsuccess" -f Green
            $importRegistrationList += @{id=$responses.state.deviceRegistrationId;serialNumber=$item.serialNumber}
        }
        elseif ($responses.state.deviceImportStatus -eq 'error') {
            Write-Host "`t$($responses.state.deviceImportStatus)" -f Red
            Write-Host "`t`tErrorCode: $($responses.state.deviceErrorCode)"
            Write-Host "`t`tErrorName: $($responses.state.deviceErrorName)"
           # throw $_
        }

    }

#endregion

#region: [Intune] Assign Device to Enrollment profile

    write-host "Assign Enrollment Profile" -f Cyan

    Write-Host "`tSync : " -NoNewline
    try {
        $uri = 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings'
        $lastSync = (Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop).lastSyncDateTime
        if ( ((Get-Date) - $lastSync).Minutes -gt 10) {
            try {
                $uri = 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings/sync'
                Invoke-MgGraphRequest -Method POST -Uri $uri -ErrorAction Stop
                Write-Host "success [$(Get-Date -format 'HH:mm')]" -f Green
            }
            catch {
                Write-Host 'failed' -f Red
            }
         }
         else {   
            Write-Host "last sync $($lastSync.ToString('HH:mm'))" -f Yellow
        }

    }
    catch {
        Write-Host 'failed' -f Red
    }

    Write-Host "`tProfile : $($enrollmentProfileName)"

    foreach ($importRegistration in $importRegistrationList) {
        try {
            # unknown | notAssigned ==> pending ==> (assignedUnkownSyncState) ==> assignedInSync
            Write-Host "`tSerial Nr: $($importRegistration.serialNumber)`t" -NoNewline

            $prevStatus = ''
            Do {
                $uri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$($importRegistration.id)`?`$expand=deploymentProfile,intendedDeploymentProfile"
                $assignments = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop

                if ($assignments.deploymentProfileAssignmentStatus -ne $prevStatus) {
                    $prevStatus = $assignments.deploymentProfileAssignmentStatus
                    Write-Host "[$($assignments.deploymentProfileAssignmentStatus)]" -NoNewline -f Yellow
                }
                if ($assignments.deploymentProfileAssignmentStatus -ne 'assignedInSync') {
                    Write-Host '.' -NoNewline
                    sleep 10
                }
            }
            Until ($assignments.deploymentProfileAssignmentStatus -eq 'assignedInSync')
            Write-Host "`tsuccess" -f Green


        }
        catch {
            Write-Host 'failed' -f Red
        }

    } # foreach importRegistrationId

#endregion

Write-Host "`nScript finished" -f Green
Write-Host "`tFile : $invocationInfo"
Write-Host "`tDate : $(Get-Date -Format 'yyyy-MM-dd / HH:mm:ss')"
Write-Host "`tDuration: $(((Get-Date) -$startTime).ToString('hh\:mm\:ss'))"